From e25f65b406ad6bff39f3a1d1c494a7305acb9c6d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 4 May 2023 15:41:19 -0500 Subject: [PATCH 0001/1120] [TST] Make jpl units instantiated with datetimes consistent with mpl converters --- lib/matplotlib/testing/jpl_units/EpochConverter.py | 4 +--- lib/matplotlib/tests/test_axes.py | 8 ++++---- lib/matplotlib/tests/test_units.py | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/testing/jpl_units/EpochConverter.py b/lib/matplotlib/testing/jpl_units/EpochConverter.py index f42d7b71d041..1edc2acf2b24 100644 --- a/lib/matplotlib/testing/jpl_units/EpochConverter.py +++ b/lib/matplotlib/testing/jpl_units/EpochConverter.py @@ -12,9 +12,7 @@ class EpochConverter(units.ConversionInterface): classes. """ - # julian date reference for "Jan 1, 0001" minus 1 day because - # Matplotlib really wants "Jan 0, 0001" - jdRef = 1721425.5 - 1 + jdRef = 1721425.5 @staticmethod def axisinfo(unit, axis): diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0d6877f1e364..5743e9ef3964 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -856,8 +856,8 @@ def test_axvspan_epoch(): units.register() # generate some data - t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 20)) - tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21)) + t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21)) + tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 22)) dt = units.Duration("ET", units.day.convert("sec")) ax = plt.gca() @@ -871,8 +871,8 @@ def test_axhspan_epoch(): units.register() # generate some data - t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 20)) - tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21)) + t0 = units.Epoch("ET", dt=datetime.datetime(2009, 1, 21)) + tf = units.Epoch("ET", dt=datetime.datetime(2009, 1, 22)) dt = units.Duration("ET", units.day.convert("sec")) ax = plt.gca() diff --git a/lib/matplotlib/tests/test_units.py b/lib/matplotlib/tests/test_units.py index d3b8c5a71643..c1fad6476648 100644 --- a/lib/matplotlib/tests/test_units.py +++ b/lib/matplotlib/tests/test_units.py @@ -134,7 +134,7 @@ def test_jpl_bar_units(): day = units.Duration("ET", 24.0 * 60.0 * 60.0) x = [0 * units.km, 1 * units.km, 2 * units.km] w = [1 * day, 2 * day, 3 * day] - b = units.Epoch("ET", dt=datetime(2009, 4, 25)) + b = units.Epoch("ET", dt=datetime(2009, 4, 26)) fig, ax = plt.subplots() ax.bar(x, w, bottom=b) ax.set_ylim([b - 1 * day, b + w[-1] + (1.001) * day]) @@ -149,7 +149,7 @@ def test_jpl_barh_units(): day = units.Duration("ET", 24.0 * 60.0 * 60.0) x = [0 * units.km, 1 * units.km, 2 * units.km] w = [1 * day, 2 * day, 3 * day] - b = units.Epoch("ET", dt=datetime(2009, 4, 25)) + b = units.Epoch("ET", dt=datetime(2009, 4, 26)) fig, ax = plt.subplots() ax.barh(x, w, left=b) From 231704fc6d70c8a57d23aa3aadcc1c7bdcf88726 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 4 May 2023 16:06:45 -0500 Subject: [PATCH 0002/1120] [TST] Add unit test for consistency of datetime units between mpl.dates and jpl_units --- lib/matplotlib/tests/test_units.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/matplotlib/tests/test_units.py b/lib/matplotlib/tests/test_units.py index c1fad6476648..fa8e5709db64 100644 --- a/lib/matplotlib/tests/test_units.py +++ b/lib/matplotlib/tests/test_units.py @@ -156,6 +156,17 @@ def test_jpl_barh_units(): ax.set_xlim([b - 1 * day, b + w[-1] + (1.001) * day]) +def test_jpl_datetime_units_consistent(): + import matplotlib.testing.jpl_units as units + units.register() + + dt = datetime(2009, 4, 26) + jpl = units.Epoch("ET", dt=dt) + dt_conv = munits.registry.get_converter(dt).convert(dt, None, None) + jpl_conv = munits.registry.get_converter(jpl).convert(jpl, None, None) + assert dt_conv == jpl_conv + + def test_empty_arrays(): # Check that plotting an empty array with a dtype works plt.scatter(np.array([], dtype='datetime64[ns]'), np.array([])) From 8d6519c32cede57e0f43bd8a7d1cdff378e93141 Mon Sep 17 00:00:00 2001 From: patel-zeel Date: Sun, 14 May 2023 20:24:16 +0530 Subject: [PATCH 0003/1120] update _unpack_to_numpy to handle JAX and PyTorch arrays --- lib/matplotlib/cbook.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ae78eab8318d..1bbe04150a3b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2237,7 +2237,10 @@ def _picklable_class_constructor(mixin_class, fmt, attr_name, base_class): def _unpack_to_numpy(x): """Internal helper to extract data from e.g. pandas and xarray objects.""" if isinstance(x, np.ndarray): - # If numpy, return directly + # If numpy array, return directly + return x + if isinstance(x, np.generic): + # If numpy scalar, return directly return x if hasattr(x, 'to_numpy'): # Assume that any to_numpy() method actually returns a numpy array @@ -2248,6 +2251,12 @@ def _unpack_to_numpy(x): # so in this case we do not want to return a function if isinstance(xtmp, np.ndarray): return xtmp + if hasattr(x, '__array__'): + # Assume that any to __array__() method returns a numpy array (e.g. TensorFlow, JAX or PyTorch arrays) + x = x.__array__() + # Anything that doesn't return ndarray via __array__() method will be filtered by the following check + if isinstance(x, np.ndarray): + return x return x From 7b992fab40dfebb263af6bd1b3f82116bda8ee8e Mon Sep 17 00:00:00 2001 From: patel-zeel Date: Sun, 14 May 2023 20:50:35 +0530 Subject: [PATCH 0004/1120] update _unpack_to_numpy to handle JAX and PyTorch arrays --- lib/matplotlib/cbook.py | 398 +++++++++++++++++++++++----------------- 1 file changed, 230 insertions(+), 168 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ae78eab8318d..d214e328ae73 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -52,6 +52,7 @@ def _get_running_interactive_framework(): if Gtk: if Gtk.MAJOR_VERSION == 4: from gi.repository import GLib + if GLib.main_depth(): return "gtk4" if Gtk.MAJOR_VERSION == 3 and Gtk.main_level(): @@ -187,9 +188,14 @@ def __getstate__(self): **vars(self), # In general, callbacks may not be pickled, so we just drop them, # unless directed otherwise by self._pickled_cids. - "callbacks": {s: {cid: proxy() for cid, proxy in d.items() - if cid in self._pickled_cids} - for s, d in self.callbacks.items()}, + "callbacks": { + s: { + cid: proxy() + for cid, proxy in d.items() + if cid in self._pickled_cids + } + for s, d in self.callbacks.items() + }, # It is simpler to reconstruct this from callbacks in __setstate__. "_func_cid_map": None, } @@ -197,12 +203,16 @@ def __getstate__(self): def __setstate__(self, state): vars(self).update(state) self.callbacks = { - s: {cid: _weak_or_strong_ref(func, self._remove_proxy) - for cid, func in d.items()} - for s, d in self.callbacks.items()} + s: { + cid: _weak_or_strong_ref(func, self._remove_proxy) + for cid, func in d.items() + } + for s, d in self.callbacks.items() + } self._func_cid_map = { s: {proxy: cid for cid, proxy in d.items()} - for s, d in self.callbacks.items()} + for s, d in self.callbacks.items() + } def connect(self, signal, func): """Register *func* to be called when signal *signal* is generated.""" @@ -357,8 +367,8 @@ def __repr__(self): def _local_over_kwdict( - local_var, kwargs, *keys, - warning_cls=_api.MatplotlibDeprecationWarning): + local_var, kwargs, *keys, warning_cls=_api.MatplotlibDeprecationWarning +): out = local_var for key in keys: kwarg_val = kwargs.pop(key, None) @@ -366,8 +376,9 @@ def _local_over_kwdict( if out is None: out = kwarg_val else: - _api.warn_external(f'"{key}" keyword argument will be ignored', - warning_cls) + _api.warn_external( + f'"{key}" keyword argument will be ignored', warning_cls + ) return out @@ -380,15 +391,15 @@ def strip_math(s): if len(s) >= 2 and s[0] == s[-1] == "$": s = s[1:-1] for tex, plain in [ - (r"\times", "x"), # Specifically for Formatter support. - (r"\mathdefault", ""), - (r"\rm", ""), - (r"\cal", ""), - (r"\tt", ""), - (r"\it", ""), - ("\\", ""), - ("{", ""), - ("}", ""), + (r"\times", "x"), # Specifically for Formatter support. + (r"\mathdefault", ""), + (r"\rm", ""), + (r"\cal", ""), + (r"\tt", ""), + (r"\it", ""), + ("\\", ""), + ("{", ""), + ("}", ""), ]: s = s.replace(tex, plain) return s @@ -399,7 +410,7 @@ def _strip_comment(s): pos = 0 while True: quote_pos = s.find('"', pos) - hash_pos = s.find('#', pos) + hash_pos = s.find("#", pos) if quote_pos < 0: without_comment = s if hash_pos < 0 else s[:hash_pos] return without_comment.strip() @@ -410,13 +421,14 @@ def _strip_comment(s): if closing_quote_pos < 0: raise ValueError( f"Missing closing quote in: {s!r}. If you need a double-" - 'quote inside a string, use escaping: e.g. "the \" char"') + 'quote inside a string, use escaping: e.g. "the " char"' + ) pos = closing_quote_pos + 1 # behind closing quote def is_writable_file_like(obj): """Return whether *obj* looks like a file object with a *write* method.""" - return callable(getattr(obj, 'write', None)) + return callable(getattr(obj, "write", None)) def file_requires_unicode(x): @@ -425,14 +437,14 @@ def file_requires_unicode(x): written to it. """ try: - x.write(b'') + x.write(b"") except TypeError: return True else: return False -def to_filehandle(fname, flag='r', return_opened=False, encoding=None): +def to_filehandle(fname, flag="r", return_opened=False, encoding=None): """ Convert a path to an open file handle or pass-through a file-like object. @@ -464,21 +476,22 @@ def to_filehandle(fname, flag='r', return_opened=False, encoding=None): if isinstance(fname, os.PathLike): fname = os.fspath(fname) if isinstance(fname, str): - if fname.endswith('.gz'): + if fname.endswith(".gz"): fh = gzip.open(fname, flag) - elif fname.endswith('.bz2'): + elif fname.endswith(".bz2"): # python may not be compiled with bz2 support, # bury import until we need it import bz2 + fh = bz2.BZ2File(fname, flag) else: fh = open(fname, flag, encoding=encoding) opened = True - elif hasattr(fname, 'seek'): + elif hasattr(fname, "seek"): fh = fname opened = False else: - raise ValueError('fname must be a PathLike or file handle') + raise ValueError("fname must be a PathLike or file handle") if return_opened: return fh, opened return fh @@ -496,7 +509,8 @@ def is_scalar_or_string(val): @_api.delete_parameter( - "3.8", "np_load", alternative="open(get_sample_data(..., asfileobj=False))") + "3.8", "np_load", alternative="open(get_sample_data(..., asfileobj=False))" +) def get_sample_data(fname, asfileobj=True, *, np_load=True): """ Return a sample data file. *fname* is a path relative to the @@ -510,20 +524,20 @@ def get_sample_data(fname, asfileobj=True, *, np_load=True): filename ends with .npy or .npz, and *asfileobj* is `True`, the file is loaded with `numpy.load`. """ - path = _get_data_path('sample_data', fname) + path = _get_data_path("sample_data", fname) if asfileobj: suffix = path.suffix.lower() - if suffix == '.gz': + if suffix == ".gz": return gzip.open(path) - elif suffix in ['.npy', '.npz']: + elif suffix in [".npy", ".npz"]: if np_load: return np.load(path) else: - return path.open('rb') - elif suffix in ['.csv', '.xrc', '.txt']: - return path.open('r') + return path.open("rb") + elif suffix in [".csv", ".xrc", ".txt"]: + return path.open("r") else: - return path.open('rb') + return path.open("rb") else: return str(path) @@ -600,7 +614,7 @@ def push(self, o): *o* is returned. """ - self._elements = self._elements[:self._pos + 1] + [o] + self._elements = self._elements[: self._pos + 1] + [o] self._pos = len(self._elements) - 1 return self() @@ -634,7 +648,7 @@ def bubble(self, o): If *o* is not in the stack. """ if o not in self._elements: - raise ValueError('Given element not contained in the stack') + raise ValueError("Given element not contained in the stack") old_elements = self._elements.copy() self.clear() top_elements = [] @@ -657,7 +671,7 @@ def remove(self, o): If *o* is not in the stack. """ if o not in self._elements: - raise ValueError('Given element not contained in the stack') + raise ValueError("Given element not contained in the stack") old_elements = self._elements.copy() self.clear() for elem in old_elements: @@ -670,7 +684,7 @@ def safe_masked_invalid(x, copy=False): if not x.dtype.isnative: # If we have already made a copy, do the byteswap in place, else make a # copy with the byte order swapped. - x = x.byteswap(inplace=copy).newbyteorder('N') # Swap to native order. + x = x.byteswap(inplace=copy).newbyteorder("N") # Swap to native order. try: xm = np.ma.masked_invalid(x, copy=False) xm.shrink_mask() @@ -784,7 +798,8 @@ class Grouper: def __init__(self, init=()): self._mapping = weakref.WeakKeyDictionary( - {x: weakref.WeakSet([x]) for x in init}) + {x: weakref.WeakSet([x]) for x in init} + ) def __getstate__(self): return { @@ -797,7 +812,8 @@ def __setstate__(self, state): vars(self).update(state) # Convert strong refs to weak ones. self._mapping = weakref.WeakKeyDictionary( - {k: weakref.WeakSet(v) for k, v in self._mapping.items()}) + {k: weakref.WeakSet(v) for k, v in self._mapping.items()} + ) def __contains__(self, item): return item in self._mapping @@ -824,7 +840,7 @@ def join(self, a, *args): def joined(self, a, b): """Return whether *a* and *b* are members of the same set.""" - return (self._mapping.get(a, object()) is self._mapping.get(b)) + return self._mapping.get(a, object()) is self._mapping.get(b) def remove(self, a): set_a = self._mapping.pop(a, None) @@ -850,11 +866,20 @@ def get_siblings(self, a): class GrouperView: """Immutable view over a `.Grouper`.""" - def __init__(self, grouper): self._grouper = grouper - def __contains__(self, item): return item in self._grouper - def __iter__(self): return iter(self._grouper) - def joined(self, a, b): return self._grouper.joined(a, b) - def get_siblings(self, a): return self._grouper.get_siblings(a) + def __init__(self, grouper): + self._grouper = grouper + + def __contains__(self, item): + return item in self._grouper + + def __iter__(self): + return iter(self._grouper) + + def joined(self, a, b): + return self._grouper.joined(a, b) + + def get_siblings(self, a): + return self._grouper.get_siblings(a) def simple_linear_interpolation(a, steps): @@ -877,8 +902,9 @@ def simple_linear_interpolation(a, steps): fps = a.reshape((len(a), -1)) xp = np.arange(len(a)) * steps x = np.arange((len(a) - 1) * steps + 1) - return (np.column_stack([np.interp(x, xp, fp) for fp in fps.T]) - .reshape((len(x),) + a.shape[1:])) + return np.column_stack([np.interp(x, xp, fp) for fp in fps.T]).reshape( + (len(x),) + a.shape[1:] + ) def delete_masked_points(*args): @@ -996,7 +1022,7 @@ def _combine_masks(*args): nrecs = len(args[0]) margs = [] # Output args; some may be modified. seqlist = [False] * len(args) # Flags: True if output will be masked. - masks = [] # List of masks. + masks = [] # List of masks. for i, x in enumerate(args): if is_scalar_or_string(x) or len(x) != nrecs: margs.append(x) # Leave it unmodified. @@ -1023,8 +1049,7 @@ def _combine_masks(*args): return margs -def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, - autorange=False): +def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, autorange=False): r""" Return a list of dictionaries of statistics used to draw a series of box and whisker plots using `~.Axes.bxp`. @@ -1123,7 +1148,6 @@ def _compute_conf_interval(data, med, iqr, bootstrap): notch_min = CI[0] notch_max = CI[1] else: - N = len(data) notch_min = med - 1.57 * iqr / np.sqrt(N) notch_max = med + 1.57 * iqr / np.sqrt(N) @@ -1144,11 +1168,10 @@ def _compute_conf_interval(data, med, iqr, bootstrap): input_whis = whis for ii, (x, label) in enumerate(zip(X, labels)): - # empty dict stats = {} if label is not None: - stats['label'] = label + stats["label"] = label # restore whis to the input values in case it got changed in the loop whis = input_whis @@ -1158,74 +1181,76 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # if empty, bail if len(x) == 0: - stats['fliers'] = np.array([]) - stats['mean'] = np.nan - stats['med'] = np.nan - stats['q1'] = np.nan - stats['q3'] = np.nan - stats['iqr'] = np.nan - stats['cilo'] = np.nan - stats['cihi'] = np.nan - stats['whislo'] = np.nan - stats['whishi'] = np.nan + stats["fliers"] = np.array([]) + stats["mean"] = np.nan + stats["med"] = np.nan + stats["q1"] = np.nan + stats["q3"] = np.nan + stats["iqr"] = np.nan + stats["cilo"] = np.nan + stats["cihi"] = np.nan + stats["whislo"] = np.nan + stats["whishi"] = np.nan continue # up-convert to an array, just to be safe x = np.asarray(x) # arithmetic mean - stats['mean'] = np.mean(x) + stats["mean"] = np.mean(x) # medians and quartiles q1, med, q3 = np.percentile(x, [25, 50, 75]) # interquartile range - stats['iqr'] = q3 - q1 - if stats['iqr'] == 0 and autorange: + stats["iqr"] = q3 - q1 + if stats["iqr"] == 0 and autorange: whis = (0, 100) # conf. interval around median - stats['cilo'], stats['cihi'] = _compute_conf_interval( - x, med, stats['iqr'], bootstrap + stats["cilo"], stats["cihi"] = _compute_conf_interval( + x, med, stats["iqr"], bootstrap ) # lowest/highest non-outliers if np.iterable(whis) and not isinstance(whis, str): loval, hival = np.percentile(x, whis) elif np.isreal(whis): - loval = q1 - whis * stats['iqr'] - hival = q3 + whis * stats['iqr'] + loval = q1 - whis * stats["iqr"] + hival = q3 + whis * stats["iqr"] else: - raise ValueError('whis must be a float or list of percentiles') + raise ValueError("whis must be a float or list of percentiles") # get high extreme wiskhi = x[x <= hival] if len(wiskhi) == 0 or np.max(wiskhi) < q3: - stats['whishi'] = q3 + stats["whishi"] = q3 else: - stats['whishi'] = np.max(wiskhi) + stats["whishi"] = np.max(wiskhi) # get low extreme wisklo = x[x >= loval] if len(wisklo) == 0 or np.min(wisklo) > q1: - stats['whislo'] = q1 + stats["whislo"] = q1 else: - stats['whislo'] = np.min(wisklo) + stats["whislo"] = np.min(wisklo) # compute a single array of outliers - stats['fliers'] = np.concatenate([ - x[x < stats['whislo']], - x[x > stats['whishi']], - ]) + stats["fliers"] = np.concatenate( + [ + x[x < stats["whislo"]], + x[x > stats["whishi"]], + ] + ) # add in the remaining stats - stats['q1'], stats['med'], stats['q3'] = q1, med, q3 + stats["q1"], stats["med"], stats["q3"] = q1, med, q3 return bxpstats #: Maps short codes for line style to their full name used by backends. -ls_mapper = {'-': 'solid', '--': 'dashed', '-.': 'dashdot', ':': 'dotted'} +ls_mapper = {"-": "solid", "--": "dashed", "-.": "dashdot", ":": "dotted"} #: Maps full names for line styles used by backends to their short codes. ls_mapper_r = {v: k for k, v in ls_mapper.items()} @@ -1241,7 +1266,7 @@ def contiguous_regions(mask): return [] # Find the indices of region changes, and correct offset - idx, = np.nonzero(mask[:-1] != mask[1:]) + (idx,) = np.nonzero(mask[:-1] != mask[1:]) idx += 1 # List operations are faster for moderately sized arrays @@ -1264,8 +1289,8 @@ def is_math_text(s): non-escaped dollar signs. """ s = str(s) - dollar_count = s.count(r'$') - s.count(r'\$') - even_dollars = (dollar_count > 0 and dollar_count % 2 == 0) + dollar_count = s.count(r"$") - s.count(r"\$") + even_dollars = dollar_count > 0 and dollar_count % 2 == 0 return even_dollars @@ -1274,7 +1299,7 @@ def _to_unmasked_float_array(x): Convert a sequence to a float array; if input was a masked array, masked values are converted to nans. """ - if hasattr(x, 'mask'): + if hasattr(x, "mask"): return np.ma.asarray(x, float).filled(np.nan) else: return np.asarray(x, float) @@ -1287,9 +1312,7 @@ def _check_1d(x): # plot requires `shape` and `ndim`. If passed an # object that doesn't provide them, then force to numpy array. # Note this will strip unit information. - if (not hasattr(x, 'shape') or - not hasattr(x, 'ndim') or - len(x.shape) < 1): + if not hasattr(x, "shape") or not hasattr(x, "ndim") or len(x.shape) < 1: return np.atleast_1d(x) else: return x @@ -1323,7 +1346,7 @@ def _reshape_2D(X, name): # 2D array, or 1D array of iterables: flatten them first. return [np.reshape(x, -1) for x in X] else: - raise ValueError(f'{name} must have 2 or fewer dimensions') + raise ValueError(f"{name} must have 2 or fewer dimensions") # Iterate over list of iterables. if len(X) == 0: @@ -1344,7 +1367,7 @@ def _reshape_2D(X, name): xi = np.asanyarray(xi) nd = np.ndim(xi) if nd > 1: - raise ValueError(f'{name} must have 2 or fewer dimensions') + raise ValueError(f"{name} must have 2 or fewer dimensions") result.append(xi.reshape(-1)) if is_1d: @@ -1422,11 +1445,13 @@ def violin_stats(X, method, points=100, quantiles=None): # quantiles should have the same size as dataset if len(X) != len(quantiles): - raise ValueError("List of violinplot statistics and quantiles values" - " must have the same length") + raise ValueError( + "List of violinplot statistics and quantiles values" + " must have the same length" + ) # Zip x and quantiles - for (x, q) in zip(X, quantiles): + for x, q in zip(X, quantiles): # Dictionary of results for this distribution stats = {} @@ -1437,15 +1462,15 @@ def violin_stats(X, method, points=100, quantiles=None): # Evaluate the kernel density estimate coords = np.linspace(min_val, max_val, points) - stats['vals'] = method(x, coords) - stats['coords'] = coords + stats["vals"] = method(x, coords) + stats["coords"] = coords # Store additional statistics for this distribution - stats['mean'] = np.mean(x) - stats['median'] = np.median(x) - stats['min'] = min_val - stats['max'] = max_val - stats['quantiles'] = np.atleast_1d(quantile_val) + stats["mean"] = np.mean(x) + stats["median"] = np.median(x) + stats["min"] = min_val + stats["max"] = max_val + stats["quantiles"] = np.atleast_1d(quantile_val) # Append to output vpstats.append(stats) @@ -1565,11 +1590,13 @@ def pts_to_midstep(x, *args): return steps -STEP_LOOKUP_MAP = {'default': lambda x, y: (x, y), - 'steps': pts_to_prestep, - 'steps-pre': pts_to_prestep, - 'steps-post': pts_to_poststep, - 'steps-mid': pts_to_midstep} +STEP_LOOKUP_MAP = { + "default": lambda x, y: (x, y), + "steps": pts_to_prestep, + "steps-pre": pts_to_prestep, + "steps-post": pts_to_poststep, + "steps-mid": pts_to_midstep, +} def index_of(y): @@ -1604,7 +1631,7 @@ def index_of(y): pass else: return np.arange(y.shape[0], dtype=float), y - raise ValueError('Input could not be cast to an at-least-1D NumPy array') + raise ValueError("Input could not be cast to an at-least-1D NumPy array") def safe_first_element(obj): @@ -1627,6 +1654,7 @@ def _safe_first_finite(obj, *, skip_nonfinite=True): This is a type-independent way of obtaining the first finite element, supporting both index access and the iterator protocol. """ + def safe_isfinite(val): if val is None: return False @@ -1636,6 +1664,7 @@ def safe_isfinite(val): # This is something that NumPy cannot make heads or tails of, # assume "finite" return True + if skip_nonfinite is False: if isinstance(obj, collections.abc.Iterator): # needed to accept `array.flat` as input. @@ -1647,15 +1676,13 @@ def safe_isfinite(val): return obj[0] except TypeError: pass - raise RuntimeError("matplotlib does not support generators " - "as input") + raise RuntimeError("matplotlib does not support generators " "as input") return next(iter(obj)) elif isinstance(obj, np.flatiter): # TODO do the finite filtering on this return obj[0] elif isinstance(obj, collections.abc.Iterator): - raise RuntimeError("matplotlib does not " - "support generators as input") + raise RuntimeError("matplotlib does not " "support generators as input") else: return next((val for val in obj if safe_isfinite(val)), safe_first_element(obj)) @@ -1664,8 +1691,7 @@ def sanitize_sequence(data): """ Convert dictview objects to list. Other inputs are returned unchanged. """ - return (list(data) if isinstance(data, collections.abc.MappingView) - else data) + return list(data) if isinstance(data, collections.abc.MappingView) else data def normalize_kwargs(kw, alias_mapping=None): @@ -1703,21 +1729,28 @@ def normalize_kwargs(kw, alias_mapping=None): # deal with default value of alias_mapping if alias_mapping is None: alias_mapping = dict() - elif (isinstance(alias_mapping, type) and issubclass(alias_mapping, Artist) - or isinstance(alias_mapping, Artist)): + elif ( + isinstance(alias_mapping, type) + and issubclass(alias_mapping, Artist) + or isinstance(alias_mapping, Artist) + ): alias_mapping = getattr(alias_mapping, "_alias_map", {}) - to_canonical = {alias: canonical - for canonical, alias_list in alias_mapping.items() - for alias in alias_list} + to_canonical = { + alias: canonical + for canonical, alias_list in alias_mapping.items() + for alias in alias_list + } canonical_to_seen = {} ret = {} # output dictionary for k, v in kw.items(): canonical = to_canonical.get(k, k) if canonical in canonical_to_seen: - raise TypeError(f"Got both {canonical_to_seen[canonical]!r} and " - f"{k!r}, which are aliases of one another") + raise TypeError( + f"Got both {canonical_to_seen[canonical]!r} and " + f"{k!r}, which are aliases of one another" + ) canonical_to_seen[canonical] = k ret[canonical] = v @@ -1751,12 +1784,15 @@ def _lock_path(path): except FileExistsError: time.sleep(sleeptime) else: - raise TimeoutError("""\ + raise TimeoutError( + """\ Lock error: Matplotlib failed to acquire the following lock file: {} This maybe due to another process holding this lock file. If you are sure no other Matplotlib process is running, remove this file and try again.""".format( - lock_path)) + lock_path + ) + ) try: yield finally: @@ -1764,8 +1800,8 @@ def _lock_path(path): def _topmost_artist( - artists, - _cached_max=functools.partial(max, key=operator.attrgetter("zorder"))): + artists, _cached_max=functools.partial(max, key=operator.attrgetter("zorder")) +): """ Get the topmost artist of a list. @@ -1827,14 +1863,16 @@ def _array_perimeter(arr): """ # note we use Python's half-open ranges to avoid repeating # the corners - forward = np.s_[0:-1] # [0 ... -1) + forward = np.s_[0:-1] # [0 ... -1) backward = np.s_[-1:0:-1] # [-1 ... 0) - return np.concatenate(( - arr[0, forward], - arr[forward, -1], - arr[-1, backward], - arr[backward, 0], - )) + return np.concatenate( + ( + arr[0, forward], + arr[forward, -1], + arr[-1, backward], + arr[backward, 0], + ) + ) def _unfold(arr, axis, size, step): @@ -1881,10 +1919,9 @@ def _unfold(arr, axis, size, step): new_strides = [*arr.strides, arr.strides[axis]] new_shape[axis] = (new_shape[axis] - size) // step + 1 new_strides[axis] = new_strides[axis] * step - return np.lib.stride_tricks.as_strided(arr, - shape=new_shape, - strides=new_strides, - writeable=False) + return np.lib.stride_tricks.as_strided( + arr, shape=new_shape, strides=new_strides, writeable=False + ) def _array_patch_perimeters(x, rstride, cstride): @@ -1932,8 +1969,9 @@ def _array_patch_perimeters(x, rstride, cstride): bottom = _unfold(x[rstride::rstride, 1:], 1, cstride, cstride)[..., ::-1] right = _unfold(x[:-1, cstride::cstride], 0, rstride, rstride) left = _unfold(x[1:, :-1:cstride], 0, rstride, rstride)[..., ::-1] - return (np.concatenate((top, right, bottom, left), axis=2) - .reshape(-1, 2 * (rstride + cstride))) + return np.concatenate((top, right, bottom, left), axis=2).reshape( + -1, 2 * (rstride + cstride) + ) @contextlib.contextmanager @@ -2010,16 +2048,16 @@ def _premultiplied_argb32_to_unmultiplied_rgba8888(buf): Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer. """ rgba = np.take( # .take() ensures C-contiguity of the result. - buf, - [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2) + buf, [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2 + ) rgb = rgba[..., :-1] alpha = rgba[..., -1] # Un-premultiply alpha. The formula is the same as in cairo-png.c. mask = alpha != 0 for channel in np.rollaxis(rgb, -1): - channel[mask] = ( - (channel[mask].astype(int) * 255 + alpha[mask] // 2) - // alpha[mask]) + channel[mask] = (channel[mask].astype(int) * 255 + alpha[mask] // 2) // alpha[ + mask + ] return rgba @@ -2038,8 +2076,8 @@ def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888): # Only bother premultiplying when the alpha channel is not fully opaque, # as the cost is not negligible. The unsafe cast is needed to do the # multiplication in-place in an integer buffer. - if alpha8.min() != 0xff: - np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe") + if alpha8.min() != 0xFF: + np.multiply(rgb24, alpha8 / 0xFF, out=rgb24, casting="unsafe") return argb32 @@ -2051,8 +2089,8 @@ def _get_nonzero_slices(buf): that encloses all non-zero entries in *buf*. If *buf* is fully zero, then ``(slice(0, 0), slice(0, 0))`` is returned. """ - x_nz, = buf.any(axis=0).nonzero() - y_nz, = buf.any(axis=1).nonzero() + (x_nz,) = buf.any(axis=0).nonzero() + (y_nz,) = buf.any(axis=1).nonzero() if len(x_nz) and len(y_nz): l, r = x_nz[[0, -1]] b, t = y_nz[[0, -1]] @@ -2063,8 +2101,11 @@ def _get_nonzero_slices(buf): def _pformat_subprocess(command): """Pretty-format a subprocess command for printing/logging purposes.""" - return (command if isinstance(command, str) - else " ".join(shlex.quote(os.fspath(arg)) for arg in command)) + return ( + command + if isinstance(command, str) + else " ".join(shlex.quote(os.fspath(arg)) for arg in command) + ) def _check_and_log_subprocess(command, logger, **kwargs): @@ -2077,7 +2118,7 @@ def _check_and_log_subprocess(command, logger, **kwargs): Regardless of the return code, the command is logged at DEBUG level on *logger*. In case of success, the output is likewise logged. """ - logger.debug('%s', _pformat_subprocess(command)) + logger.debug("%s", _pformat_subprocess(command)) proc = subprocess.run(command, capture_output=True, **kwargs) if proc.returncode: stdout = proc.stdout @@ -2092,7 +2133,8 @@ def _check_and_log_subprocess(command, logger, **kwargs): f"failed and generated the following output:\n" f"{stdout}\n" f"and the following error:\n" - f"{stderr}") + f"{stderr}" + ) if proc.stdout: logger.debug("stdout:\n%s", proc.stdout) if proc.stderr: @@ -2105,8 +2147,11 @@ def _backend_module_name(name): Convert a backend name (either a standard backend -- "Agg", "TkAgg", ... -- or a custom backend -- "module://...") to the corresponding module name). """ - return (name[9:] if name.startswith("module://") - else f"matplotlib.backends.backend_{name.lower()}") + return ( + name[9:] + if name.startswith("module://") + else f"matplotlib.backends.backend_{name.lower()}" + ) def _setup_new_guiapp(): @@ -2119,8 +2164,7 @@ def _setup_new_guiapp(): try: _c_internal_utils.Win32_GetCurrentProcessExplicitAppUserModelID() except OSError: - _c_internal_utils.Win32_SetCurrentProcessExplicitAppUserModelID( - "matplotlib") + _c_internal_utils.Win32_SetCurrentProcessExplicitAppUserModelID("matplotlib") def _format_approx(number, precision): @@ -2128,7 +2172,7 @@ def _format_approx(number, precision): Format the number with at most the number of decimals given as precision. Remove trailing zeros and possibly the decimal point. """ - return f'{number:.{precision}f}'.rstrip('0').rstrip('.') or '0' + return f"{number:.{precision}f}".rstrip("0").rstrip(".") or "0" def _g_sig_digits(value, delta): @@ -2146,10 +2190,15 @@ def _g_sig_digits(value, delta): # is 4 significant digits. A value of 0 contributes 1 "digit" before the # decimal point. # For inf or nan, the precision doesn't matter. - return max( - 0, - (math.floor(math.log10(abs(value))) + 1 if value else 1) - - math.floor(math.log10(delta))) if math.isfinite(value) else 0 + return ( + max( + 0, + (math.floor(math.log10(abs(value))) + 1 if value else 1) + - math.floor(math.log10(delta)), + ) + if math.isfinite(value) + else 0 + ) def _unikey_or_keysym_to_mplkey(unikey, keysym): @@ -2214,9 +2263,11 @@ class subcls(mixin_class, base_class): __module__ = mixin_class.__module__ def __reduce__(self): - return (_picklable_class_constructor, - (mixin_class, fmt, attr_name, base_class), - self.__getstate__()) + return ( + _picklable_class_constructor, + (mixin_class, fmt, attr_name, base_class), + self.__getstate__(), + ) subcls.__name__ = subcls.__qualname__ = fmt.format(base_class.__name__) if attr_name is not None: @@ -2237,17 +2288,28 @@ def _picklable_class_constructor(mixin_class, fmt, attr_name, base_class): def _unpack_to_numpy(x): """Internal helper to extract data from e.g. pandas and xarray objects.""" if isinstance(x, np.ndarray): - # If numpy, return directly + # If numpy array, return directly + return x + if isinstance(x, np.generic): + # If numpy scalar, return directly return x - if hasattr(x, 'to_numpy'): + if hasattr(x, "to_numpy"): # Assume that any to_numpy() method actually returns a numpy array return x.to_numpy() - if hasattr(x, 'values'): + if hasattr(x, "values"): xtmp = x.values # For example a dict has a 'values' attribute, but it is not a property # so in this case we do not want to return a function if isinstance(xtmp, np.ndarray): return xtmp + if hasattr(x, "__array__"): + # Assume that any to __array__() method returns a numpy array + # (e.g. TensorFlow, JAX or PyTorch arrays) + x = x.__array__() + # Anything that doesn't return ndarray via __array__() method, + # will be filtered by the following check + if isinstance(x, np.ndarray): + return x return x From 2cd9857370b38c2bebc7b62fbd7d9998287a7ee0 Mon Sep 17 00:00:00 2001 From: patel-zeel Date: Mon, 15 May 2023 01:30:04 +0530 Subject: [PATCH 0005/1120] roll back to previous clean changes --- lib/matplotlib/cbook.py | 395 +++++++++++++++++----------------------- 1 file changed, 172 insertions(+), 223 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index d214e328ae73..03678b0a968b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -52,7 +52,6 @@ def _get_running_interactive_framework(): if Gtk: if Gtk.MAJOR_VERSION == 4: from gi.repository import GLib - if GLib.main_depth(): return "gtk4" if Gtk.MAJOR_VERSION == 3 and Gtk.main_level(): @@ -188,14 +187,9 @@ def __getstate__(self): **vars(self), # In general, callbacks may not be pickled, so we just drop them, # unless directed otherwise by self._pickled_cids. - "callbacks": { - s: { - cid: proxy() - for cid, proxy in d.items() - if cid in self._pickled_cids - } - for s, d in self.callbacks.items() - }, + "callbacks": {s: {cid: proxy() for cid, proxy in d.items() + if cid in self._pickled_cids} + for s, d in self.callbacks.items()}, # It is simpler to reconstruct this from callbacks in __setstate__. "_func_cid_map": None, } @@ -203,16 +197,12 @@ def __getstate__(self): def __setstate__(self, state): vars(self).update(state) self.callbacks = { - s: { - cid: _weak_or_strong_ref(func, self._remove_proxy) - for cid, func in d.items() - } - for s, d in self.callbacks.items() - } + s: {cid: _weak_or_strong_ref(func, self._remove_proxy) + for cid, func in d.items()} + for s, d in self.callbacks.items()} self._func_cid_map = { s: {proxy: cid for cid, proxy in d.items()} - for s, d in self.callbacks.items() - } + for s, d in self.callbacks.items()} def connect(self, signal, func): """Register *func* to be called when signal *signal* is generated.""" @@ -367,8 +357,8 @@ def __repr__(self): def _local_over_kwdict( - local_var, kwargs, *keys, warning_cls=_api.MatplotlibDeprecationWarning -): + local_var, kwargs, *keys, + warning_cls=_api.MatplotlibDeprecationWarning): out = local_var for key in keys: kwarg_val = kwargs.pop(key, None) @@ -376,9 +366,8 @@ def _local_over_kwdict( if out is None: out = kwarg_val else: - _api.warn_external( - f'"{key}" keyword argument will be ignored', warning_cls - ) + _api.warn_external(f'"{key}" keyword argument will be ignored', + warning_cls) return out @@ -391,15 +380,15 @@ def strip_math(s): if len(s) >= 2 and s[0] == s[-1] == "$": s = s[1:-1] for tex, plain in [ - (r"\times", "x"), # Specifically for Formatter support. - (r"\mathdefault", ""), - (r"\rm", ""), - (r"\cal", ""), - (r"\tt", ""), - (r"\it", ""), - ("\\", ""), - ("{", ""), - ("}", ""), + (r"\times", "x"), # Specifically for Formatter support. + (r"\mathdefault", ""), + (r"\rm", ""), + (r"\cal", ""), + (r"\tt", ""), + (r"\it", ""), + ("\\", ""), + ("{", ""), + ("}", ""), ]: s = s.replace(tex, plain) return s @@ -410,7 +399,7 @@ def _strip_comment(s): pos = 0 while True: quote_pos = s.find('"', pos) - hash_pos = s.find("#", pos) + hash_pos = s.find('#', pos) if quote_pos < 0: without_comment = s if hash_pos < 0 else s[:hash_pos] return without_comment.strip() @@ -421,14 +410,13 @@ def _strip_comment(s): if closing_quote_pos < 0: raise ValueError( f"Missing closing quote in: {s!r}. If you need a double-" - 'quote inside a string, use escaping: e.g. "the " char"' - ) + 'quote inside a string, use escaping: e.g. "the \" char"') pos = closing_quote_pos + 1 # behind closing quote def is_writable_file_like(obj): """Return whether *obj* looks like a file object with a *write* method.""" - return callable(getattr(obj, "write", None)) + return callable(getattr(obj, 'write', None)) def file_requires_unicode(x): @@ -437,14 +425,14 @@ def file_requires_unicode(x): written to it. """ try: - x.write(b"") + x.write(b'') except TypeError: return True else: return False -def to_filehandle(fname, flag="r", return_opened=False, encoding=None): +def to_filehandle(fname, flag='r', return_opened=False, encoding=None): """ Convert a path to an open file handle or pass-through a file-like object. @@ -476,22 +464,21 @@ def to_filehandle(fname, flag="r", return_opened=False, encoding=None): if isinstance(fname, os.PathLike): fname = os.fspath(fname) if isinstance(fname, str): - if fname.endswith(".gz"): + if fname.endswith('.gz'): fh = gzip.open(fname, flag) - elif fname.endswith(".bz2"): + elif fname.endswith('.bz2'): # python may not be compiled with bz2 support, # bury import until we need it import bz2 - fh = bz2.BZ2File(fname, flag) else: fh = open(fname, flag, encoding=encoding) opened = True - elif hasattr(fname, "seek"): + elif hasattr(fname, 'seek'): fh = fname opened = False else: - raise ValueError("fname must be a PathLike or file handle") + raise ValueError('fname must be a PathLike or file handle') if return_opened: return fh, opened return fh @@ -509,8 +496,7 @@ def is_scalar_or_string(val): @_api.delete_parameter( - "3.8", "np_load", alternative="open(get_sample_data(..., asfileobj=False))" -) + "3.8", "np_load", alternative="open(get_sample_data(..., asfileobj=False))") def get_sample_data(fname, asfileobj=True, *, np_load=True): """ Return a sample data file. *fname* is a path relative to the @@ -524,20 +510,20 @@ def get_sample_data(fname, asfileobj=True, *, np_load=True): filename ends with .npy or .npz, and *asfileobj* is `True`, the file is loaded with `numpy.load`. """ - path = _get_data_path("sample_data", fname) + path = _get_data_path('sample_data', fname) if asfileobj: suffix = path.suffix.lower() - if suffix == ".gz": + if suffix == '.gz': return gzip.open(path) - elif suffix in [".npy", ".npz"]: + elif suffix in ['.npy', '.npz']: if np_load: return np.load(path) else: - return path.open("rb") - elif suffix in [".csv", ".xrc", ".txt"]: - return path.open("r") + return path.open('rb') + elif suffix in ['.csv', '.xrc', '.txt']: + return path.open('r') else: - return path.open("rb") + return path.open('rb') else: return str(path) @@ -614,7 +600,7 @@ def push(self, o): *o* is returned. """ - self._elements = self._elements[: self._pos + 1] + [o] + self._elements = self._elements[:self._pos + 1] + [o] self._pos = len(self._elements) - 1 return self() @@ -648,7 +634,7 @@ def bubble(self, o): If *o* is not in the stack. """ if o not in self._elements: - raise ValueError("Given element not contained in the stack") + raise ValueError('Given element not contained in the stack') old_elements = self._elements.copy() self.clear() top_elements = [] @@ -671,7 +657,7 @@ def remove(self, o): If *o* is not in the stack. """ if o not in self._elements: - raise ValueError("Given element not contained in the stack") + raise ValueError('Given element not contained in the stack') old_elements = self._elements.copy() self.clear() for elem in old_elements: @@ -684,7 +670,7 @@ def safe_masked_invalid(x, copy=False): if not x.dtype.isnative: # If we have already made a copy, do the byteswap in place, else make a # copy with the byte order swapped. - x = x.byteswap(inplace=copy).newbyteorder("N") # Swap to native order. + x = x.byteswap(inplace=copy).newbyteorder('N') # Swap to native order. try: xm = np.ma.masked_invalid(x, copy=False) xm.shrink_mask() @@ -798,8 +784,7 @@ class Grouper: def __init__(self, init=()): self._mapping = weakref.WeakKeyDictionary( - {x: weakref.WeakSet([x]) for x in init} - ) + {x: weakref.WeakSet([x]) for x in init}) def __getstate__(self): return { @@ -812,8 +797,7 @@ def __setstate__(self, state): vars(self).update(state) # Convert strong refs to weak ones. self._mapping = weakref.WeakKeyDictionary( - {k: weakref.WeakSet(v) for k, v in self._mapping.items()} - ) + {k: weakref.WeakSet(v) for k, v in self._mapping.items()}) def __contains__(self, item): return item in self._mapping @@ -840,7 +824,7 @@ def join(self, a, *args): def joined(self, a, b): """Return whether *a* and *b* are members of the same set.""" - return self._mapping.get(a, object()) is self._mapping.get(b) + return (self._mapping.get(a, object()) is self._mapping.get(b)) def remove(self, a): set_a = self._mapping.pop(a, None) @@ -866,20 +850,11 @@ def get_siblings(self, a): class GrouperView: """Immutable view over a `.Grouper`.""" - def __init__(self, grouper): - self._grouper = grouper - - def __contains__(self, item): - return item in self._grouper - - def __iter__(self): - return iter(self._grouper) - - def joined(self, a, b): - return self._grouper.joined(a, b) - - def get_siblings(self, a): - return self._grouper.get_siblings(a) + def __init__(self, grouper): self._grouper = grouper + def __contains__(self, item): return item in self._grouper + def __iter__(self): return iter(self._grouper) + def joined(self, a, b): return self._grouper.joined(a, b) + def get_siblings(self, a): return self._grouper.get_siblings(a) def simple_linear_interpolation(a, steps): @@ -902,9 +877,8 @@ def simple_linear_interpolation(a, steps): fps = a.reshape((len(a), -1)) xp = np.arange(len(a)) * steps x = np.arange((len(a) - 1) * steps + 1) - return np.column_stack([np.interp(x, xp, fp) for fp in fps.T]).reshape( - (len(x),) + a.shape[1:] - ) + return (np.column_stack([np.interp(x, xp, fp) for fp in fps.T]) + .reshape((len(x),) + a.shape[1:])) def delete_masked_points(*args): @@ -1022,7 +996,7 @@ def _combine_masks(*args): nrecs = len(args[0]) margs = [] # Output args; some may be modified. seqlist = [False] * len(args) # Flags: True if output will be masked. - masks = [] # List of masks. + masks = [] # List of masks. for i, x in enumerate(args): if is_scalar_or_string(x) or len(x) != nrecs: margs.append(x) # Leave it unmodified. @@ -1049,7 +1023,8 @@ def _combine_masks(*args): return margs -def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, autorange=False): +def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, + autorange=False): r""" Return a list of dictionaries of statistics used to draw a series of box and whisker plots using `~.Axes.bxp`. @@ -1148,6 +1123,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap): notch_min = CI[0] notch_max = CI[1] else: + N = len(data) notch_min = med - 1.57 * iqr / np.sqrt(N) notch_max = med + 1.57 * iqr / np.sqrt(N) @@ -1168,10 +1144,11 @@ def _compute_conf_interval(data, med, iqr, bootstrap): input_whis = whis for ii, (x, label) in enumerate(zip(X, labels)): + # empty dict stats = {} if label is not None: - stats["label"] = label + stats['label'] = label # restore whis to the input values in case it got changed in the loop whis = input_whis @@ -1181,76 +1158,74 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # if empty, bail if len(x) == 0: - stats["fliers"] = np.array([]) - stats["mean"] = np.nan - stats["med"] = np.nan - stats["q1"] = np.nan - stats["q3"] = np.nan - stats["iqr"] = np.nan - stats["cilo"] = np.nan - stats["cihi"] = np.nan - stats["whislo"] = np.nan - stats["whishi"] = np.nan + stats['fliers'] = np.array([]) + stats['mean'] = np.nan + stats['med'] = np.nan + stats['q1'] = np.nan + stats['q3'] = np.nan + stats['iqr'] = np.nan + stats['cilo'] = np.nan + stats['cihi'] = np.nan + stats['whislo'] = np.nan + stats['whishi'] = np.nan continue # up-convert to an array, just to be safe x = np.asarray(x) # arithmetic mean - stats["mean"] = np.mean(x) + stats['mean'] = np.mean(x) # medians and quartiles q1, med, q3 = np.percentile(x, [25, 50, 75]) # interquartile range - stats["iqr"] = q3 - q1 - if stats["iqr"] == 0 and autorange: + stats['iqr'] = q3 - q1 + if stats['iqr'] == 0 and autorange: whis = (0, 100) # conf. interval around median - stats["cilo"], stats["cihi"] = _compute_conf_interval( - x, med, stats["iqr"], bootstrap + stats['cilo'], stats['cihi'] = _compute_conf_interval( + x, med, stats['iqr'], bootstrap ) # lowest/highest non-outliers if np.iterable(whis) and not isinstance(whis, str): loval, hival = np.percentile(x, whis) elif np.isreal(whis): - loval = q1 - whis * stats["iqr"] - hival = q3 + whis * stats["iqr"] + loval = q1 - whis * stats['iqr'] + hival = q3 + whis * stats['iqr'] else: - raise ValueError("whis must be a float or list of percentiles") + raise ValueError('whis must be a float or list of percentiles') # get high extreme wiskhi = x[x <= hival] if len(wiskhi) == 0 or np.max(wiskhi) < q3: - stats["whishi"] = q3 + stats['whishi'] = q3 else: - stats["whishi"] = np.max(wiskhi) + stats['whishi'] = np.max(wiskhi) # get low extreme wisklo = x[x >= loval] if len(wisklo) == 0 or np.min(wisklo) > q1: - stats["whislo"] = q1 + stats['whislo'] = q1 else: - stats["whislo"] = np.min(wisklo) + stats['whislo'] = np.min(wisklo) # compute a single array of outliers - stats["fliers"] = np.concatenate( - [ - x[x < stats["whislo"]], - x[x > stats["whishi"]], - ] - ) + stats['fliers'] = np.concatenate([ + x[x < stats['whislo']], + x[x > stats['whishi']], + ]) # add in the remaining stats - stats["q1"], stats["med"], stats["q3"] = q1, med, q3 + stats['q1'], stats['med'], stats['q3'] = q1, med, q3 return bxpstats #: Maps short codes for line style to their full name used by backends. -ls_mapper = {"-": "solid", "--": "dashed", "-.": "dashdot", ":": "dotted"} +ls_mapper = {'-': 'solid', '--': 'dashed', '-.': 'dashdot', ':': 'dotted'} #: Maps full names for line styles used by backends to their short codes. ls_mapper_r = {v: k for k, v in ls_mapper.items()} @@ -1266,7 +1241,7 @@ def contiguous_regions(mask): return [] # Find the indices of region changes, and correct offset - (idx,) = np.nonzero(mask[:-1] != mask[1:]) + idx, = np.nonzero(mask[:-1] != mask[1:]) idx += 1 # List operations are faster for moderately sized arrays @@ -1289,8 +1264,8 @@ def is_math_text(s): non-escaped dollar signs. """ s = str(s) - dollar_count = s.count(r"$") - s.count(r"\$") - even_dollars = dollar_count > 0 and dollar_count % 2 == 0 + dollar_count = s.count(r'$') - s.count(r'\$') + even_dollars = (dollar_count > 0 and dollar_count % 2 == 0) return even_dollars @@ -1299,7 +1274,7 @@ def _to_unmasked_float_array(x): Convert a sequence to a float array; if input was a masked array, masked values are converted to nans. """ - if hasattr(x, "mask"): + if hasattr(x, 'mask'): return np.ma.asarray(x, float).filled(np.nan) else: return np.asarray(x, float) @@ -1312,7 +1287,9 @@ def _check_1d(x): # plot requires `shape` and `ndim`. If passed an # object that doesn't provide them, then force to numpy array. # Note this will strip unit information. - if not hasattr(x, "shape") or not hasattr(x, "ndim") or len(x.shape) < 1: + if (not hasattr(x, 'shape') or + not hasattr(x, 'ndim') or + len(x.shape) < 1): return np.atleast_1d(x) else: return x @@ -1346,7 +1323,7 @@ def _reshape_2D(X, name): # 2D array, or 1D array of iterables: flatten them first. return [np.reshape(x, -1) for x in X] else: - raise ValueError(f"{name} must have 2 or fewer dimensions") + raise ValueError(f'{name} must have 2 or fewer dimensions') # Iterate over list of iterables. if len(X) == 0: @@ -1367,7 +1344,7 @@ def _reshape_2D(X, name): xi = np.asanyarray(xi) nd = np.ndim(xi) if nd > 1: - raise ValueError(f"{name} must have 2 or fewer dimensions") + raise ValueError(f'{name} must have 2 or fewer dimensions') result.append(xi.reshape(-1)) if is_1d: @@ -1445,13 +1422,11 @@ def violin_stats(X, method, points=100, quantiles=None): # quantiles should have the same size as dataset if len(X) != len(quantiles): - raise ValueError( - "List of violinplot statistics and quantiles values" - " must have the same length" - ) + raise ValueError("List of violinplot statistics and quantiles values" + " must have the same length") # Zip x and quantiles - for x, q in zip(X, quantiles): + for (x, q) in zip(X, quantiles): # Dictionary of results for this distribution stats = {} @@ -1462,15 +1437,15 @@ def violin_stats(X, method, points=100, quantiles=None): # Evaluate the kernel density estimate coords = np.linspace(min_val, max_val, points) - stats["vals"] = method(x, coords) - stats["coords"] = coords + stats['vals'] = method(x, coords) + stats['coords'] = coords # Store additional statistics for this distribution - stats["mean"] = np.mean(x) - stats["median"] = np.median(x) - stats["min"] = min_val - stats["max"] = max_val - stats["quantiles"] = np.atleast_1d(quantile_val) + stats['mean'] = np.mean(x) + stats['median'] = np.median(x) + stats['min'] = min_val + stats['max'] = max_val + stats['quantiles'] = np.atleast_1d(quantile_val) # Append to output vpstats.append(stats) @@ -1590,13 +1565,11 @@ def pts_to_midstep(x, *args): return steps -STEP_LOOKUP_MAP = { - "default": lambda x, y: (x, y), - "steps": pts_to_prestep, - "steps-pre": pts_to_prestep, - "steps-post": pts_to_poststep, - "steps-mid": pts_to_midstep, -} +STEP_LOOKUP_MAP = {'default': lambda x, y: (x, y), + 'steps': pts_to_prestep, + 'steps-pre': pts_to_prestep, + 'steps-post': pts_to_poststep, + 'steps-mid': pts_to_midstep} def index_of(y): @@ -1631,7 +1604,7 @@ def index_of(y): pass else: return np.arange(y.shape[0], dtype=float), y - raise ValueError("Input could not be cast to an at-least-1D NumPy array") + raise ValueError('Input could not be cast to an at-least-1D NumPy array') def safe_first_element(obj): @@ -1654,7 +1627,6 @@ def _safe_first_finite(obj, *, skip_nonfinite=True): This is a type-independent way of obtaining the first finite element, supporting both index access and the iterator protocol. """ - def safe_isfinite(val): if val is None: return False @@ -1664,7 +1636,6 @@ def safe_isfinite(val): # This is something that NumPy cannot make heads or tails of, # assume "finite" return True - if skip_nonfinite is False: if isinstance(obj, collections.abc.Iterator): # needed to accept `array.flat` as input. @@ -1676,13 +1647,15 @@ def safe_isfinite(val): return obj[0] except TypeError: pass - raise RuntimeError("matplotlib does not support generators " "as input") + raise RuntimeError("matplotlib does not support generators " + "as input") return next(iter(obj)) elif isinstance(obj, np.flatiter): # TODO do the finite filtering on this return obj[0] elif isinstance(obj, collections.abc.Iterator): - raise RuntimeError("matplotlib does not " "support generators as input") + raise RuntimeError("matplotlib does not " + "support generators as input") else: return next((val for val in obj if safe_isfinite(val)), safe_first_element(obj)) @@ -1691,7 +1664,8 @@ def sanitize_sequence(data): """ Convert dictview objects to list. Other inputs are returned unchanged. """ - return list(data) if isinstance(data, collections.abc.MappingView) else data + return (list(data) if isinstance(data, collections.abc.MappingView) + else data) def normalize_kwargs(kw, alias_mapping=None): @@ -1729,28 +1703,21 @@ def normalize_kwargs(kw, alias_mapping=None): # deal with default value of alias_mapping if alias_mapping is None: alias_mapping = dict() - elif ( - isinstance(alias_mapping, type) - and issubclass(alias_mapping, Artist) - or isinstance(alias_mapping, Artist) - ): + elif (isinstance(alias_mapping, type) and issubclass(alias_mapping, Artist) + or isinstance(alias_mapping, Artist)): alias_mapping = getattr(alias_mapping, "_alias_map", {}) - to_canonical = { - alias: canonical - for canonical, alias_list in alias_mapping.items() - for alias in alias_list - } + to_canonical = {alias: canonical + for canonical, alias_list in alias_mapping.items() + for alias in alias_list} canonical_to_seen = {} ret = {} # output dictionary for k, v in kw.items(): canonical = to_canonical.get(k, k) if canonical in canonical_to_seen: - raise TypeError( - f"Got both {canonical_to_seen[canonical]!r} and " - f"{k!r}, which are aliases of one another" - ) + raise TypeError(f"Got both {canonical_to_seen[canonical]!r} and " + f"{k!r}, which are aliases of one another") canonical_to_seen[canonical] = k ret[canonical] = v @@ -1784,15 +1751,12 @@ def _lock_path(path): except FileExistsError: time.sleep(sleeptime) else: - raise TimeoutError( - """\ + raise TimeoutError("""\ Lock error: Matplotlib failed to acquire the following lock file: {} This maybe due to another process holding this lock file. If you are sure no other Matplotlib process is running, remove this file and try again.""".format( - lock_path - ) - ) + lock_path)) try: yield finally: @@ -1800,8 +1764,8 @@ def _lock_path(path): def _topmost_artist( - artists, _cached_max=functools.partial(max, key=operator.attrgetter("zorder")) -): + artists, + _cached_max=functools.partial(max, key=operator.attrgetter("zorder"))): """ Get the topmost artist of a list. @@ -1863,16 +1827,14 @@ def _array_perimeter(arr): """ # note we use Python's half-open ranges to avoid repeating # the corners - forward = np.s_[0:-1] # [0 ... -1) + forward = np.s_[0:-1] # [0 ... -1) backward = np.s_[-1:0:-1] # [-1 ... 0) - return np.concatenate( - ( - arr[0, forward], - arr[forward, -1], - arr[-1, backward], - arr[backward, 0], - ) - ) + return np.concatenate(( + arr[0, forward], + arr[forward, -1], + arr[-1, backward], + arr[backward, 0], + )) def _unfold(arr, axis, size, step): @@ -1919,9 +1881,10 @@ def _unfold(arr, axis, size, step): new_strides = [*arr.strides, arr.strides[axis]] new_shape[axis] = (new_shape[axis] - size) // step + 1 new_strides[axis] = new_strides[axis] * step - return np.lib.stride_tricks.as_strided( - arr, shape=new_shape, strides=new_strides, writeable=False - ) + return np.lib.stride_tricks.as_strided(arr, + shape=new_shape, + strides=new_strides, + writeable=False) def _array_patch_perimeters(x, rstride, cstride): @@ -1969,9 +1932,8 @@ def _array_patch_perimeters(x, rstride, cstride): bottom = _unfold(x[rstride::rstride, 1:], 1, cstride, cstride)[..., ::-1] right = _unfold(x[:-1, cstride::cstride], 0, rstride, rstride) left = _unfold(x[1:, :-1:cstride], 0, rstride, rstride)[..., ::-1] - return np.concatenate((top, right, bottom, left), axis=2).reshape( - -1, 2 * (rstride + cstride) - ) + return (np.concatenate((top, right, bottom, left), axis=2) + .reshape(-1, 2 * (rstride + cstride))) @contextlib.contextmanager @@ -2048,16 +2010,16 @@ def _premultiplied_argb32_to_unmultiplied_rgba8888(buf): Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer. """ rgba = np.take( # .take() ensures C-contiguity of the result. - buf, [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2 - ) + buf, + [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2) rgb = rgba[..., :-1] alpha = rgba[..., -1] # Un-premultiply alpha. The formula is the same as in cairo-png.c. mask = alpha != 0 for channel in np.rollaxis(rgb, -1): - channel[mask] = (channel[mask].astype(int) * 255 + alpha[mask] // 2) // alpha[ - mask - ] + channel[mask] = ( + (channel[mask].astype(int) * 255 + alpha[mask] // 2) + // alpha[mask]) return rgba @@ -2076,8 +2038,8 @@ def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888): # Only bother premultiplying when the alpha channel is not fully opaque, # as the cost is not negligible. The unsafe cast is needed to do the # multiplication in-place in an integer buffer. - if alpha8.min() != 0xFF: - np.multiply(rgb24, alpha8 / 0xFF, out=rgb24, casting="unsafe") + if alpha8.min() != 0xff: + np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe") return argb32 @@ -2089,8 +2051,8 @@ def _get_nonzero_slices(buf): that encloses all non-zero entries in *buf*. If *buf* is fully zero, then ``(slice(0, 0), slice(0, 0))`` is returned. """ - (x_nz,) = buf.any(axis=0).nonzero() - (y_nz,) = buf.any(axis=1).nonzero() + x_nz, = buf.any(axis=0).nonzero() + y_nz, = buf.any(axis=1).nonzero() if len(x_nz) and len(y_nz): l, r = x_nz[[0, -1]] b, t = y_nz[[0, -1]] @@ -2101,11 +2063,8 @@ def _get_nonzero_slices(buf): def _pformat_subprocess(command): """Pretty-format a subprocess command for printing/logging purposes.""" - return ( - command - if isinstance(command, str) - else " ".join(shlex.quote(os.fspath(arg)) for arg in command) - ) + return (command if isinstance(command, str) + else " ".join(shlex.quote(os.fspath(arg)) for arg in command)) def _check_and_log_subprocess(command, logger, **kwargs): @@ -2118,7 +2077,7 @@ def _check_and_log_subprocess(command, logger, **kwargs): Regardless of the return code, the command is logged at DEBUG level on *logger*. In case of success, the output is likewise logged. """ - logger.debug("%s", _pformat_subprocess(command)) + logger.debug('%s', _pformat_subprocess(command)) proc = subprocess.run(command, capture_output=True, **kwargs) if proc.returncode: stdout = proc.stdout @@ -2133,8 +2092,7 @@ def _check_and_log_subprocess(command, logger, **kwargs): f"failed and generated the following output:\n" f"{stdout}\n" f"and the following error:\n" - f"{stderr}" - ) + f"{stderr}") if proc.stdout: logger.debug("stdout:\n%s", proc.stdout) if proc.stderr: @@ -2147,11 +2105,8 @@ def _backend_module_name(name): Convert a backend name (either a standard backend -- "Agg", "TkAgg", ... -- or a custom backend -- "module://...") to the corresponding module name). """ - return ( - name[9:] - if name.startswith("module://") - else f"matplotlib.backends.backend_{name.lower()}" - ) + return (name[9:] if name.startswith("module://") + else f"matplotlib.backends.backend_{name.lower()}") def _setup_new_guiapp(): @@ -2164,7 +2119,8 @@ def _setup_new_guiapp(): try: _c_internal_utils.Win32_GetCurrentProcessExplicitAppUserModelID() except OSError: - _c_internal_utils.Win32_SetCurrentProcessExplicitAppUserModelID("matplotlib") + _c_internal_utils.Win32_SetCurrentProcessExplicitAppUserModelID( + "matplotlib") def _format_approx(number, precision): @@ -2172,7 +2128,7 @@ def _format_approx(number, precision): Format the number with at most the number of decimals given as precision. Remove trailing zeros and possibly the decimal point. """ - return f"{number:.{precision}f}".rstrip("0").rstrip(".") or "0" + return f'{number:.{precision}f}'.rstrip('0').rstrip('.') or '0' def _g_sig_digits(value, delta): @@ -2190,15 +2146,10 @@ def _g_sig_digits(value, delta): # is 4 significant digits. A value of 0 contributes 1 "digit" before the # decimal point. # For inf or nan, the precision doesn't matter. - return ( - max( - 0, - (math.floor(math.log10(abs(value))) + 1 if value else 1) - - math.floor(math.log10(delta)), - ) - if math.isfinite(value) - else 0 - ) + return max( + 0, + (math.floor(math.log10(abs(value))) + 1 if value else 1) + - math.floor(math.log10(delta))) if math.isfinite(value) else 0 def _unikey_or_keysym_to_mplkey(unikey, keysym): @@ -2263,11 +2214,9 @@ class subcls(mixin_class, base_class): __module__ = mixin_class.__module__ def __reduce__(self): - return ( - _picklable_class_constructor, - (mixin_class, fmt, attr_name, base_class), - self.__getstate__(), - ) + return (_picklable_class_constructor, + (mixin_class, fmt, attr_name, base_class), + self.__getstate__()) subcls.__name__ = subcls.__qualname__ = fmt.format(base_class.__name__) if attr_name is not None: @@ -2293,23 +2242,23 @@ def _unpack_to_numpy(x): if isinstance(x, np.generic): # If numpy scalar, return directly return x - if hasattr(x, "to_numpy"): + if hasattr(x, 'to_numpy'): # Assume that any to_numpy() method actually returns a numpy array return x.to_numpy() - if hasattr(x, "values"): + if hasattr(x, 'values'): xtmp = x.values # For example a dict has a 'values' attribute, but it is not a property # so in this case we do not want to return a function if isinstance(xtmp, np.ndarray): return xtmp - if hasattr(x, "__array__"): + if hasattr(x, '__array__'): # Assume that any to __array__() method returns a numpy array # (e.g. TensorFlow, JAX or PyTorch arrays) - x = x.__array__() - # Anything that doesn't return ndarray via __array__() method, + xtmp = x.__array__() + # Anything that doesn't return ndarray via __array__() method # will be filtered by the following check - if isinstance(x, np.ndarray): - return x + if isinstance(xtmp, np.ndarray): + return xtmp return x From 519ace87c9994ac8c3d3310e950e5da94f2ab044 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 17 May 2023 23:44:17 +0200 Subject: [PATCH 0006/1120] Tweak transforms tutorial. - In the Axes coordinates example, point to the "labelling subplots" example which is arguably a better approach for labelling (it ensures that the labels stay aligned even if the axes have different widths). The current example in the tutorial is still useful for pedagogical purposes, though. - Shorten the entries in the transformations tables to avoid having overly long lines. --- .../artists/transforms_tutorial.py | 111 +++++++++--------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/galleries/users_explain/artists/transforms_tutorial.py b/galleries/users_explain/artists/transforms_tutorial.py index c6e71ad111d7..a39cf7a30192 100644 --- a/galleries/users_explain/artists/transforms_tutorial.py +++ b/galleries/users_explain/artists/transforms_tutorial.py @@ -23,55 +23,51 @@ :class:`~matplotlib.figure.SubFigure` instance. -+----------------+-----------------------------------+---------------------------------------------------+ -|Coordinate |Description |Transformation object | -|system | |from system to display | -+================+===================================+===================================================+ -|"data" |The coordinate system of the data |``ax.transData`` | -| |in the Axes. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"axes" |The coordinate system of the |``ax.transAxes`` | -| |`~matplotlib.axes.Axes`; (0, 0) | | -| |is bottom left of the axes, and | | -| |(1, 1) is top right of the axes. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"subfigure" |The coordinate system of the |``subfigure.transSubfigure`` | -| |`.SubFigure`; (0, 0) is bottom left| | -| |of the subfigure, and (1, 1) is top| | -| |right of the subfigure. If a | | -| |figure has no subfigures, this is | | -| |the same as ``transFigure``. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"figure" |The coordinate system of the |``fig.transFigure`` | -| |`.Figure`; (0, 0) is bottom left | | -| |of the figure, and (1, 1) is top | | -| |right of the figure. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"figure-inches" |The coordinate system of the |``fig.dpi_scale_trans`` | -| |`.Figure` in inches; (0, 0) is | | -| |bottom left of the figure, and | | -| |(width, height) is the top right | | -| |of the figure in inches. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"xaxis", |Blended coordinate systems, using |``ax.get_xaxis_transform()``, | -|"yaxis" |data coordinates on one direction |``ax.get_yaxis_transform()`` | -| |and axes coordinates on the other. | | -+----------------+-----------------------------------+---------------------------------------------------+ -|"display" |The native coordinate system of the|`None`, or | -| |output ; (0, 0) is the bottom left |:class:`~matplotlib.transforms.IdentityTransform()`| -| |of the window, and (width, height) | | -| |is top right of the output in | | -| |"display units". | | -| | | | -| |The exact interpretation of the | | -| |units depends on the back end. For | | -| |example it is pixels for Agg and | | -| |points for svg/pdf. | | -+----------------+-----------------------------------+---------------------------------------------------+ - - - - ++----------------+-----------------------------------+-----------------------------+ +|Coordinate |Description |Transformation object | +|system | |from system to display | ++================+===================================+=============================+ +|"data" |The coordinate system of the data |``ax.transData`` | +| |in the Axes. | | ++----------------+-----------------------------------+-----------------------------+ +|"axes" |The coordinate system of the |``ax.transAxes`` | +| |`~matplotlib.axes.Axes`; (0, 0) | | +| |is bottom left of the axes, and | | +| |(1, 1) is top right of the axes. | | ++----------------+-----------------------------------+-----------------------------+ +|"subfigure" |The coordinate system of the |``subfigure.transSubfigure`` | +| |`.SubFigure`; (0, 0) is bottom left| | +| |of the subfigure, and (1, 1) is top| | +| |right of the subfigure. If a | | +| |figure has no subfigures, this is | | +| |the same as ``transFigure``. | | ++----------------+-----------------------------------+-----------------------------+ +|"figure" |The coordinate system of the |``fig.transFigure`` | +| |`.Figure`; (0, 0) is bottom left | | +| |of the figure, and (1, 1) is top | | +| |right of the figure. | | ++----------------+-----------------------------------+-----------------------------+ +|"figure-inches" |The coordinate system of the |``fig.dpi_scale_trans`` | +| |`.Figure` in inches; (0, 0) is | | +| |bottom left of the figure, and | | +| |(width, height) is the top right | | +| |of the figure in inches. | | ++----------------+-----------------------------------+-----------------------------+ +|"xaxis", |Blended coordinate systems, using |``ax.get_xaxis_transform()``,| +|"yaxis" |data coordinates on one direction |``ax.get_yaxis_transform()`` | +| |and axes coordinates on the other. | | ++----------------+-----------------------------------+-----------------------------+ +|"display" |The native coordinate system of the|`None`, or | +| |output ; (0, 0) is the bottom left |`.IdentityTransform()` | +| |of the window, and (width, height) | | +| |is top right of the output in | | +| |"display units". | | +| | | | +| |The exact interpretation of the | | +| |units depends on the back end. For | | +| |example it is pixels for Agg and | | +| |points for svg/pdf. | | ++----------------+-----------------------------------+-----------------------------+ The `~matplotlib.transforms.Transform` objects are naive to the source and destination coordinate systems, however the objects referred to in the table @@ -247,14 +243,15 @@ # # After the *data* coordinate system, *axes* is probably the second most # useful coordinate system. Here the point (0, 0) is the bottom left of -# your axes or subplot, (0.5, 0.5) is the center, and (1.0, 1.0) is the -# top right. You can also refer to points outside the range, so (-0.1, -# 1.1) is to the left and above your axes. This coordinate system is -# extremely useful when placing text in your axes, because you often -# want a text bubble in a fixed, location, e.g., the upper left of the axes -# pane, and have that location remain fixed when you pan or zoom. Here -# is a simple example that creates four panels and labels them 'A', 'B', -# 'C', 'D' as you often see in journals. +# your axes or subplot, (0.5, 0.5) is the center, and (1.0, 1.0) is the top +# right. You can also refer to points outside the range, so (-0.1, 1.1) +# is to the left and above your axes. This coordinate system is extremely +# useful when placing text in your axes, because you often want a text bubble +# in a fixed, location, e.g., the upper left of the axes pane, and have that +# location remain fixed when you pan or zoom. Here is a simple example that +# creates four panels and labels them 'A', 'B', 'C', 'D' as you often see in +# journals. A more sophisticated approach for such labeling is presented at +# :doc:`/gallery/text_labels_and_annotations/label_subplots`. fig = plt.figure() for i, label in enumerate(('A', 'B', 'C', 'D')): From 00eb561042e74ae4b1102381c820f4cf811053d4 Mon Sep 17 00:00:00 2001 From: melissawm Date: Mon, 26 Jun 2023 18:13:30 -0300 Subject: [PATCH 0007/1120] DOC: Add documentation on codespaces usage --- .devcontainer/devcontainer.json | 19 +++--- doc/devel/codespaces.md | 107 ++++++++++++++++++++++++++++++++ doc/devel/contribute.rst | 97 +++++++++++++++++++++++------ 3 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 doc/devel/codespaces.md diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 87d381c9a68b..a43a383536a1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,4 +1,3 @@ - { "hostRequirements": { "memory": "8gb", @@ -22,11 +21,17 @@ "ms-vscode.live-server" ], "settings": {} + }, + "codespaces": { + "openFiles": [ + "README.md", + "doc/devel/codespaces.md" + ] } -}, -"portsAttributes": { - "6080": { - "label": "desktop" - } -} + }, + "portsAttributes": { + "6080": { + "label": "desktop" + } + } } diff --git a/doc/devel/codespaces.md b/doc/devel/codespaces.md new file mode 100644 index 000000000000..3a41aae560a3 --- /dev/null +++ b/doc/devel/codespaces.md @@ -0,0 +1,107 @@ +# Contributing to Matplotlib using GitHub codespaces + +You've discovered a bug or something else you want to change +in Matplotlib — excellent! + +You've worked out a way to fix it — even better! + +You want to tell us about it — best of all! + +This project is a community effort, and everyone is welcome to contribute. +Everyone within the community is expected to abide by our +[Code of Conduct](../../CODE_OF_CONDUCT.md). + +## GitHub codespaces contribution workflow + +The preferred way to contribute to Matplotlib is to fork the main +repository at https://github.com/matplotlib/matplotlib, then submit a "pull +request" (PR). You can do this by cloning a copy of the Maplotlib repository to +your own computer, or alternatively using +[GitHub Codespaces](https://docs.github.com/codespaces) (a cloud-based +in-browser development environment, that comes with the appropriated setup to +contribute to Matplotlib). + +A brief overview of the workflows is as follows. + +1. Go to the GitHub web interface +2. Fork the [project repository](https://github.com/matplotlib/matplotlib): +3. Open codespaces on your fork by clicking on the green "Code" button on the + GitHub web interface and selecting the "Codespaces" tab. Next, click on "Open + codespaces on ". You will be able to change branches later, + so you can select the default `main` branch. + + After the codespace is created, you will be taken to a new browser tab where + you can use the terminal to activate a pre-defined conda environment called + `mpl-dev`: + + ``` + conda activate mpl-dev + ``` + +4. Install the local version of Matplotlib with: + + ``` + python -m pip install -e . + ``` + + (See [Setting up Matplotlib for development](https://matplotlib.org/devdocs/devel/development_setup.html) + for detailed instructions.) + +5. Create a branch to hold your changes: + + ``` + git checkout -b my-feature origin/main + ``` + + and start making changes. Never work in the `main` branch! + +6. Work on this task using Git to do the version control. Codespaces persist for + some time (check the [documentation for details](https://docs.github.com/codespaces/getting-started/the-codespace-lifecycle)) + and can be managed on https://github.com/codespaces. When you're done editing + e.g., `lib/matplotlib/collections.py`, do: + + ``` + git add lib/matplotlib/collections.py + git commit + ``` + + to record your changes in Git, then push them to your GitHub fork with: + + ``` + git push -u origin my-feature + ``` + +Finally, go to the web page of your fork of the Matplotlib repo, and click +'Pull request' to send your changes to the maintainers for review. + +## Other stuff you may want to do + +* If you need to run tests to verify your changes before sending your PR, you + can run (in your `mpl-dev` conda environment): + + ``` + pytest + ``` + +* If you need to build the documentation, you can move to the `doc` folder and + run (in your `mpl-dev` conda environment): + + ``` + cd doc + make html + ``` + +* If you need to open a GUI window with Matplotlib output, our Codespaces + configuration includes a + [light-weight Fluxbox-based desktop](https://github.com/devcontainers/features/tree/main/src/desktop-lite). + You can use it by connecting to this desktop via your web browser. To do this: + + 1. Press `F1` or `Ctrl/Cmd+Shift+P` and select `Ports: Focus on Ports View` in + VS Code to bring it into focus. Open the ports view in your tool, select + the `noVNC` port, and click the Globe icon. + 2. In the browser that appears, click the Connect button and enter the desktop + password (`vscode` by default). + + Check the + [GitHub instructions](https://github.com/devcontainers/features/tree/main/src/desktop-lite#connecting-to-the-desktop) + for more details on connecting to the desktop. diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index c6e1a2199fae..2f8ed9271e10 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -222,39 +222,96 @@ How to contribute The preferred way to contribute to Matplotlib is to fork the `main repository `__ on GitHub, -then submit a "pull request" (PR). +then submit a "pull request" (PR). You can do this by cloning a copy of the +Maplotlib repository to your own computer, or alternatively using +`GitHub Codespaces `_ (a cloud-based +in-browser development environment, that comes with the appropriated setup to +contribute to Matplotlib). -A brief overview is: +A brief overview of the workflows is as follows. -1. `Create an account `_ on GitHub if you do not - already have one. +.. tab-set:: -2. Fork the `project repository `_: - click on the 'Fork' button near the top of the page. This creates a copy of - the code under your account on the GitHub server. + .. tab-item:: Local development -3. Clone this copy to your local disk:: + 1. `Create an account `_ on GitHub if you do + not already have one. - git clone https://github.com//matplotlib.git + 2. Fork the + `project repository `_: + click on the 'Fork' button near the top of the page. This creates a + copy of the code under your account on the GitHub server. -4. Enter the directory and install the local version of Matplotlib. - See :ref:`installing_for_devs` for instructions + 3. Clone this copy to your local disk:: -5. Create a branch to hold your changes:: + git clone https://github.com//matplotlib.git - git checkout -b my-feature origin/main + 4. Enter the directory and install the local version of Matplotlib. + See :ref:`installing_for_devs` for instructions - and start making changes. Never work in the ``main`` branch! + 5. Create a branch to hold your changes:: -6. Work on this copy, on your computer, using Git to do the version control. - When you're done editing e.g., ``lib/matplotlib/collections.py``, do:: + git checkout -b my-feature origin/main - git add lib/matplotlib/collections.py - git commit + and start making changes. Never work in the ``main`` branch! - to record your changes in Git, then push them to GitHub with:: + 6. Work on this copy, on your computer, using Git to do the version + control. When you're done editing e.g., + ``lib/matplotlib/collections.py``, do:: - git push -u origin my-feature + git add lib/matplotlib/collections.py + git commit + + to record your changes in Git, then push them to GitHub with:: + + git push -u origin my-feature + + .. tab-item:: Using GitHub Codespaces + + 1. `Create an account `_ on GitHub if you do + not already have one. + + 2. Fork the + `project repository `_: + click on the 'Fork' button near the top of the page. This creates a + copy of the code under your account on the GitHub server. + + 3. Open codespaces on your fork by clicking on the green "Code" button + on the GitHub web interface and selecting the "Codespaces" tab. Next, + click on "Open codespaces on ". You will be able to + change branches later, so you can select the default ``main`` branch. + + After the codespace is created, you will be taken to a new browser + tab where you can use the terminal to activate a pre-defined conda + environment called ``mpl-dev``:: + + conda activate mpl-dev + + 4. Install the local version of Matplotlib with:: + + python -m pip install -e . + + (See :ref:`installing_for_devs` for detailed instructions.) + + 5. Create a branch to hold your changes:: + + git checkout -b my-feature origin/main + + and start making changes. Never work in the ``main`` branch! + + 6. Work on this task using Git to do the version control. Codespaces + persist for some time (check the `documentation for details + `_) + and can be managed on https://github.com/codespaces. When you're done + editing e.g., ``lib/matplotlib/collections.py``, do:: + + git add lib/matplotlib/collections.py + git commit + + to record your changes in Git, then push them to your GitHub fork + with:: + + git push -u origin my-feature Finally, go to the web page of your fork of the Matplotlib repo, and click 'Pull request' to send your changes to the maintainers for review. From a28db8665683188cb30d985618935813199500ea Mon Sep 17 00:00:00 2001 From: melissawm Date: Mon, 26 Jun 2023 20:12:03 -0300 Subject: [PATCH 0008/1120] MAINT: Fix port forwarding for desktop on codespaces --- .devcontainer/devcontainer.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a43a383536a1..814c066c43b1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,6 +12,12 @@ }, "onCreateCommand": ".devcontainer/setup.sh", "postCreateCommand": "", + "forwardPorts": [6080], + "portsAttributes": { + "6080": { + "label": "desktop" + } + }, "customizations": { "vscode": { "extensions": [ @@ -28,10 +34,5 @@ "doc/devel/codespaces.md" ] } - }, - "portsAttributes": { - "6080": { - "label": "desktop" - } } } From c50f62bc7530e877b718c0929f3302ec9172aad3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:07:23 +0000 Subject: [PATCH 0009/1120] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.2.4 → v2.2.5](https://github.com/codespell-project/codespell/compare/v2.2.4...v2.2.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 198624ec9911..e213bf188f4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: additional_dependencies: [pydocstyle>5.1.0, flake8-docstrings>1.4.0, flake8-force] args: ["--docstring-convention=all"] - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell files: ^.*\.(py|c|cpp|h|m|md|rst|yml)$ From fcf75ddbe535fe6a562efbe88f928437b2cd43a6 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Fri, 23 Jun 2023 12:23:49 +0200 Subject: [PATCH 0010/1120] Synchronize mathtext docs and handling --- doc/sphinxext/math_symbol_table.py | 98 ++++++++----------- .../next_whats_new/mathtext_documentation.rst | 5 + .../mathtext_relation_operators.rst | 2 - lib/matplotlib/_mathtext.py | 75 +++++++++++--- lib/matplotlib/_mathtext_data.py | 6 +- lib/matplotlib/tests/test_mathtext.py | 2 +- 6 files changed, 109 insertions(+), 79 deletions(-) create mode 100644 doc/users/next_whats_new/mathtext_documentation.rst diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index 74b1ac638d73..a195e073831f 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -1,7 +1,11 @@ +import re from docutils.parsers.rst import Directive from matplotlib import _mathtext, _mathtext_data +bb_pattern = re.compile("Bbb[A-Z]") +scr_pattern = re.compile("scr[a-zA-Z]") +frak_pattern = re.compile("frak[A-Z]") symbols = [ ["Lower-case Greek", @@ -18,6 +22,9 @@ ["Hebrew", 6, (r"\aleph", r"\beth", r"\gimel", r"\daleth")], + ["Latin named characters", + 6, + r"""\aa \AA \ae \AE \oe \OE \O \o \thorn \Thorn \ss \eth \dh \DH""".split()], ["Delimiters", 5, _mathtext.Parser._delims], @@ -27,71 +34,42 @@ ["Standard function names", 5, {fr"\{fn}" for fn in _mathtext.Parser._function_names}], - ["Binary operation and relation symbols", + ["Binary operation symbols", + 4, + _mathtext.Parser._binary_operators], + ["Relation symbols", 4, - r"""\ast \pm \slash \cap \star \mp \cup \cdot \uplus - \triangleleft \circ \odot \sqcap \triangleright \bullet \ominus - \sqcup \bigcirc \oplus \wedge \diamond \oslash \vee - \bigtriangledown \times \otimes \dag \bigtriangleup \div \wr - \ddag \barwedge \veebar \boxplus \curlywedge \curlyvee \boxminus - \Cap \Cup \boxtimes \bot \top \dotplus \boxdot \intercal - \rightthreetimes \divideontimes \leftthreetimes \equiv \leq \geq - \perp \cong \prec \succ \mid \neq \preceq \succeq \parallel \sim - \ll \gg \bowtie \simeq \subset \supset \Join \approx \subseteq - \supseteq \ltimes \asymp \sqsubset \sqsupset \rtimes \doteq - \sqsubseteq \sqsupseteq \smile \propto \dashv \vdash \frown - \models \in \ni \notin \approxeq \leqq \geqq \lessgtr \leqslant - \geqslant \lesseqgtr \backsim \lessapprox \gtrapprox \lesseqqgtr - \backsimeq \lll \ggg \gtreqqless \triangleq \lessdot \gtrdot - \gtreqless \circeq \lesssim \gtrsim \gtrless \bumpeq \eqslantless - \eqslantgtr \backepsilon \Bumpeq \precsim \succsim \between - \doteqdot \precapprox \succapprox \pitchfork \Subset \Supset - \fallingdotseq \subseteqq \supseteqq \risingdotseq \sqsubset - \sqsupset \varpropto \preccurlyeq \succcurlyeq \Vdash \therefore - \curlyeqprec \curlyeqsucc \vDash \because \blacktriangleleft - \blacktriangleright \Vvdash \eqcirc \trianglelefteq - \trianglerighteq \neq \vartriangleleft \vartriangleright \ncong - \nleq \ngeq \nsubseteq \nmid \nsupseteq \nparallel \nless \ngtr - \nprec \nsucc \subsetneq \nsim \supsetneq \nVDash \precnapprox - \succnapprox \subsetneqq \nvDash \precnsim \succnsim \supsetneqq - \nvdash \lnapprox \gnapprox \ntriangleleft \ntrianglelefteq - \lneqq \gneqq \ntriangleright \lnsim \gnsim \ntrianglerighteq - \coloneq \eqsim \nequiv \napprox \nsupset \doublebarwedge \nVdash - \Doteq \nsubset \eqcolon \ne - """.split()], + _mathtext.Parser._relation_symbols], ["Arrow symbols", 4, - r"""\leftarrow \longleftarrow \uparrow \Leftarrow \Longleftarrow - \Uparrow \rightarrow \longrightarrow \downarrow \Rightarrow - \Longrightarrow \Downarrow \leftrightarrow \updownarrow - \longleftrightarrow \updownarrow \Leftrightarrow - \Longleftrightarrow \Updownarrow \mapsto \longmapsto \nearrow - \hookleftarrow \hookrightarrow \searrow \leftharpoonup - \rightharpoonup \swarrow \leftharpoondown \rightharpoondown - \nwarrow \rightleftharpoons \leadsto \dashrightarrow - \dashleftarrow \leftleftarrows \leftrightarrows \Lleftarrow - \Rrightarrow \twoheadleftarrow \leftarrowtail \looparrowleft - \leftrightharpoons \curvearrowleft \circlearrowleft \Lsh - \upuparrows \upharpoonleft \downharpoonleft \multimap - \leftrightsquigarrow \rightrightarrows \rightleftarrows - \rightrightarrows \rightleftarrows \twoheadrightarrow - \rightarrowtail \looparrowright \rightleftharpoons - \curvearrowright \circlearrowright \Rsh \downdownarrows - \upharpoonright \downharpoonright \rightsquigarrow \nleftarrow - \nrightarrow \nLeftarrow \nRightarrow \nleftrightarrow - \nLeftrightarrow \to \Swarrow \Searrow \Nwarrow \Nearrow - \leftsquigarrow - """.split()], + _mathtext.Parser._arrow_symbols], + ["Dot symbols", + 4, + r"""\cdots \vdots \ldots \ddots \adots \Colon \therefore \because""".split()], + ["Black-board characters", + 6, + [fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(bb_pattern, symbol)]], + ["Script characters", + 6, + [fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(scr_pattern, symbol)]], + ["Fraktur characters", + 6, + [fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(frak_pattern, symbol)]], ["Miscellaneous symbols", 4, r"""\neg \infty \forall \wp \exists \bigstar \angle \partial - \nexists \measuredangle \eth \emptyset \sphericalangle \clubsuit + \nexists \measuredangle \emptyset \sphericalangle \clubsuit \varnothing \complement \diamondsuit \imath \Finv \triangledown - \heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle \cdots - \hslash \vdots \blacksquare \ldots \blacktriangle \ddots \sharp + \heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle + \hslash \blacksquare \blacktriangle \sharp \increment \prime \blacktriangledown \Im \flat \backprime \Re \natural - \circledS \P \copyright \ss \circledR \S \yen \AA \checkmark \$ - \cent \triangle \QED \sinewave \nabla \mho""".split()] + \circledS \P \copyright \circledR \S \yen \checkmark \$ + \cent \triangle \QED \sinewave \dag \ddag \perthousand + \lambdabar \L \l \degree \danger \maltese + \i \hermitmatrix \sterling \nabla \mho""".split()], ] @@ -165,7 +143,9 @@ def setup(app): if sym[1:] not in _mathtext_data.tex2uni: print(sym) + # Add accents + all_symbols.update({v[1:]: k for k, v in _mathtext.Parser._accent_map.items()}) print("SYMBOLS NOT IN TABLE:") - for sym in _mathtext_data.tex2uni: + for sym, val in _mathtext_data.tex2uni.items(): if sym not in all_symbols: - print(sym) + print(f"{sym} = {chr(val)}") diff --git a/doc/users/next_whats_new/mathtext_documentation.rst b/doc/users/next_whats_new/mathtext_documentation.rst new file mode 100644 index 000000000000..2b7cd51b702c --- /dev/null +++ b/doc/users/next_whats_new/mathtext_documentation.rst @@ -0,0 +1,5 @@ +``mathtext`` documentation improvements +--------------------------------------- + +The documentation is updated to take information directly from the parser. This +means that (almost) all supported symbols, operators etc are shown at :ref:`mathtext`. diff --git a/doc/users/next_whats_new/mathtext_relation_operators.rst b/doc/users/next_whats_new/mathtext_relation_operators.rst index 7bc7f47bdddc..1f9773a845df 100644 --- a/doc/users/next_whats_new/mathtext_relation_operators.rst +++ b/doc/users/next_whats_new/mathtext_relation_operators.rst @@ -5,5 +5,3 @@ There has been a number of operators added and corrected when a Unicode font is In addition, correct spacing has been added to a number of the previous operators. Especially, the characters used for ``\gnapprox``, ``\lnapprox``, ``\leftangle``, and ``\rightangle`` have been corrected. - -All supported operators can be seen at :ref:`mathtext`. diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index d86385e94589..7a8cd0ae96fa 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -1734,7 +1734,17 @@ class _MathStyle(enum.Enum): \cap \triangleleft \dagger \cup \triangleright \ddagger \uplus \lhd \amalg - \dotplus \dotminus'''.split()) + \dotplus \dotminus \Cap + \Cup \barwedge \boxdot + \boxminus \boxplus \boxtimes + \curlyvee \curlywedge \divideontimes + \doublebarwedge \leftthreetimes \rightthreetimes + \slash \veebar \barvee + \cupdot \intercal \amalg + \circledcirc \circleddash \circledast + \boxbar \obar \merge + \minuscolon \dotsminusdots + '''.split()) _relation_symbols = set(r''' = < > : @@ -1768,21 +1778,54 @@ class _MathStyle(enum.Enum): \triangleright \ntriangleleft \ntriangleright \trianglelefteq \ntrianglelefteq \trianglerighteq \ntrianglerighteq \blacktriangleleft \blacktriangleright - \equalparallel \measuredrightangle \varlrtriangle - '''.split()) - - _arrow_symbols = set(r''' - \leftarrow \longleftarrow \uparrow - \Leftarrow \Longleftarrow \Uparrow - \rightarrow \longrightarrow \downarrow - \Rightarrow \Longrightarrow \Downarrow - \leftrightarrow \longleftrightarrow \updownarrow - \Leftrightarrow \Longleftrightarrow \Updownarrow - \mapsto \longmapsto \nearrow - \hookleftarrow \hookrightarrow \searrow - \leftharpoonup \rightharpoonup \swarrow - \leftharpoondown \rightharpoondown \nwarrow - \rightleftharpoons \leadsto'''.split()) + \equalparallel \measuredrightangle \lrtriangle + \Doteq \Bumpeq \Subset \Supset + \backepsilon \because \therefore \bot + \top \bumpeq \circeq \coloneq + \curlyeqprec \curlyeqsucc \eqcirc \eqcolon + \eqsim \fallingdotseq \gtrdot \gtrless + \ltimes \rtimes \lessdot \ne + \ncong \nequiv \ngeq \ngtr + \nleq \nless \nmid \notin + \nprec \nsubset \nsubseteq \nsucc + \nsupset \nsupseteq \pitchfork \preccurlyeq + \risingdotseq \subsetneq \succcurlyeq \supsetneq + \varpropto \vartriangleleft \scurel + \vartriangleright \rightangle \equal \backcong + \eqdef \wedgeq \questeq \between + \veeeq \disin \varisins \isins + \isindot \varisinobar \isinobar \isinvb + \isinE \nisd \varnis \nis + \varniobar \niobar \bagmember \ratio + \Equiv \stareq \measeq \arceq + \rightassert \rightModels \smallin \smallowns + \notsmallowns'''.split()) + + _arrow_symbols = set(r""" + \leftarrow \longleftarrow \uparrow \Leftarrow \Longleftarrow + \Uparrow \rightarrow \longrightarrow \downarrow \Rightarrow + \Longrightarrow \Downarrow \leftrightarrow \updownarrow + \longleftrightarrow \updownarrow \Leftrightarrow + \Longleftrightarrow \Updownarrow \mapsto \longmapsto \nearrow + \hookleftarrow \hookrightarrow \searrow \leftharpoonup + \rightharpoonup \swarrow \leftharpoondown \rightharpoondown + \nwarrow \rightleftharpoons \leadsto \dashrightarrow + \dashleftarrow \leftleftarrows \leftrightarrows \Lleftarrow + \Rrightarrow \twoheadleftarrow \leftarrowtail \looparrowleft + \leftrightharpoons \curvearrowleft \circlearrowleft \Lsh + \upuparrows \upharpoonleft \downharpoonleft \multimap + \leftrightsquigarrow \rightrightarrows \rightleftarrows + \rightrightarrows \rightleftarrows \twoheadrightarrow + \rightarrowtail \looparrowright \rightleftharpoons + \curvearrowright \circlearrowright \Rsh \downdownarrows + \upharpoonright \downharpoonright \rightsquigarrow \nleftarrow + \nrightarrow \nLeftarrow \nRightarrow \nleftrightarrow + \nLeftrightarrow \to \Swarrow \Searrow \Nwarrow \Nearrow + \leftsquigarrow \overleftarrow \overleftrightarrow \cwopencirclearrow + \downzigzagarrow \cupleftarrow \rightzigzagarrow \twoheaddownarrow + \updownarrowbar \twoheaduparrow \rightarrowbar \updownarrows + \barleftarrow \mapsfrom \mapsdown \mapsup \Ldsh \Rdsh + """.split()) _spaced_symbols = _binary_operators | _relation_symbols | _arrow_symbols diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index 40ba4f96f103..8d1bffc57570 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -800,6 +800,8 @@ 'eta' : 951, 'forall' : 8704, 'eth' : 240, + 'dh' : 240, + 'DH' : 208, 'colon' : 58, 'sqcup' : 8852, 'bigsqcup' : 10758, @@ -1066,7 +1068,7 @@ 'hermitmatrix' : 8889, 'barvee' : 8893, 'measuredrightangle' : 8894, - 'varlrtriangle' : 8895, + 'lrtriangle' : 8895, 'equalparallel' : 8917, 'npreccurlyeq' : 8928, 'nsucccurlyeq' : 8929, @@ -1090,6 +1092,8 @@ 'bagmember' : 8959, 'triangle' : 9651, 'iiiint' : 10764, + 'amalg' : 10815, + 'merge' : 10837, } # Each element is a 4-tuple of the form: diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index dab981f29a23..bae09c7dbe95 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -525,7 +525,7 @@ def test_mathtext_operators(): \ngtrsim \nlessgtr \ngtrless \cupleftarrow \oequal \rightassert \rightModels \hermitmatrix \barvee - \measuredrightangle \varlrtriangle + \measuredrightangle \lrtriangle \equalparallel \npreccurlyeq \nsucccurlyeq \nsqsubseteq \nsqsupseteq \sqsubsetneq \sqsupsetneq \disin \varisins From 24c7333be2934a0dce9cae51af59b6a2a5759e0f Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 27 Jul 2023 23:17:14 -0500 Subject: [PATCH 0011/1120] Add aas (abbrev for antialiaseds) to codespell ignores --- ci/codespell-ignore-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/codespell-ignore-words.txt b/ci/codespell-ignore-words.txt index a450473ab3e4..1a29d054cdc3 100644 --- a/ci/codespell-ignore-words.txt +++ b/ci/codespell-ignore-words.txt @@ -1,3 +1,4 @@ +aas ans axises ba From 6dfdc023153cb76df74963c9e417c12248c7fb5b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 9 Aug 2023 18:52:42 -0400 Subject: [PATCH 0012/1120] MNT: Remove consider argument from Legend._find_best_position It is never passed in explicitly, and this is otherwise private. --- lib/matplotlib/legend.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 18bb7242e34e..7855fa095482 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1158,13 +1158,8 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer): loc, bbox, parentbbox, self.borderaxespad * renderer.points_to_pixels(self._fontsize)) - def _find_best_position(self, width, height, renderer, consider=None): - """ - Determine the best location to place the legend. - - *consider* is a list of ``(x, y)`` pairs to consider as a potential - lower-left corner of the legend. All are display coords. - """ + def _find_best_position(self, width, height, renderer): + """Determine the best location to place the legend.""" assert self.isaxes # always holds, as this is only called internally start_time = time.perf_counter() @@ -1172,14 +1167,12 @@ def _find_best_position(self, width, height, renderer, consider=None): bboxes, lines, offsets = self._auto_legend_data() bbox = Bbox.from_bounds(0, 0, width, height) - if consider is None: - consider = [self._get_anchored_bbox(x, bbox, - self.get_bbox_to_anchor(), - renderer) - for x in range(1, len(self.codes))] candidates = [] - for idx, (l, b) in enumerate(consider): + for idx in range(1, len(self.codes)): + l, b = self._get_anchored_bbox(idx, bbox, + self.get_bbox_to_anchor(), + renderer) legendBox = Bbox.from_bounds(l, b, width, height) badness = 0 # XXX TODO: If markers are present, it would be good to take them From 35163568b388aa0cd699ac88877c3f6916b7bb37 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 9 Aug 2023 23:24:47 -0400 Subject: [PATCH 0013/1120] Ensure legend(loc='best') warning is always triggered If a candidate location was picked, then that would be returned directly without warning even if it took just as long. --- lib/matplotlib/legend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 7855fa095482..18d0ee913237 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1174,7 +1174,6 @@ def _find_best_position(self, width, height, renderer): self.get_bbox_to_anchor(), renderer) legendBox = Bbox.from_bounds(l, b, width, height) - badness = 0 # XXX TODO: If markers are present, it would be good to take them # into account when checking vertex overlaps in the next line. badness = (sum(legendBox.count_contains(line.vertices) @@ -1183,10 +1182,10 @@ def _find_best_position(self, width, height, renderer): + legendBox.count_overlaps(bboxes) + sum(line.intersects_bbox(legendBox, filled=False) for line in lines)) - if badness == 0: - return l, b # Include the index to favor lower codes in case of a tie. candidates.append((badness, idx, (l, b))) + if badness == 0: + break _, _, (l, b) = min(candidates) From 3cc6610597ee16a0cce39f7b033ae529972177e7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2023 00:09:53 -0400 Subject: [PATCH 0014/1120] TST: Improve test for Legend(loc='best') warning By patching the timer instead of using actually large data, we can both a) speed up these tests (~7.5s vs <0.2s for both), and b) consistently trigger the warning even on systems which are fast (such as the M1 systems on Cirrus.) Also, copy the test data from `test_legend_auto3`, which correctly hits all candidate locations for the 'best' legend locator without having to fill up the entire Axes with data. --- lib/matplotlib/tests/test_legend.py | 38 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 759ac6aadaff..1549354ba56b 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1,5 +1,7 @@ import collections +import itertools import platform +import time from unittest import mock import warnings @@ -1109,29 +1111,43 @@ def test_usetex_no_warn(caplog): assert "Font family ['serif'] not found." not in caplog.text -def test_warn_big_data_best_loc(): +def test_warn_big_data_best_loc(monkeypatch): + # Force _find_best_position to think it took a long time. + counter = itertools.count(0, step=1.5) + monkeypatch.setattr(time, 'perf_counter', lambda: next(counter)) + fig, ax = plt.subplots() fig.canvas.draw() # So that we can call draw_artist later. - for idx in range(1000): - ax.plot(np.arange(5000), label=idx) + + # Place line across all possible legend locations. + x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5] + y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5] + ax.plot(x, y, 'o-', label='line') + with rc_context({'legend.loc': 'best'}): legend = ax.legend() - with pytest.warns(UserWarning) as records: + with pytest.warns(UserWarning, + match='Creating legend with loc="best" can be slow with large ' + 'amounts of data.') as records: fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow. # The _find_best_position method of Legend is called twice, duplicating # the warning message. assert len(records) == 2 - for record in records: - assert str(record.message) == ( - 'Creating legend with loc="best" can be slow with large ' - 'amounts of data.') -def test_no_warn_big_data_when_loc_specified(): +def test_no_warn_big_data_when_loc_specified(monkeypatch): + # Force _find_best_position to think it took a long time. + counter = itertools.count(0, step=1.5) + monkeypatch.setattr(time, 'perf_counter', lambda: next(counter)) + fig, ax = plt.subplots() fig.canvas.draw() - for idx in range(1000): - ax.plot(np.arange(5000), label=idx) + + # Place line across all possible legend locations. + x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5] + y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5] + ax.plot(x, y, 'o-', label='line') + legend = ax.legend('best') fig.draw_artist(legend) # Check that no warning is emitted. From af3780d90c894156de47b0d01b0285b086c44d34 Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 9 Aug 2023 20:00:39 -0400 Subject: [PATCH 0015/1120] print theme versions --- doc/conf.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 1d6c352435e2..4658f0059068 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -144,6 +144,14 @@ def _check_dependencies(): raise ImportError( "The following dependencies are missing to build the " f"documentation: {', '.join(missing)}") + + # debug sphinx-pydata-theme and mpl-theme-version + if 'mpl_sphinx_theme' not in missing: + import pydata_sphinx_theme + import mpl_sphinx_theme + print(f"pydata sphinx theme: {pydata_sphinx_theme.__version__}") + print(f"mpl sphinx theme: {mpl_sphinx_theme.__version__}") + if shutil.which('dot') is None: raise OSError( "No binary named dot - graphviz must be installed to build the " From 09ab594011cd5c7712c03a2b6a5348d3c224ab9c Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 19 Jul 2023 04:12:07 +0200 Subject: [PATCH 0016/1120] Test some untested Locator code and code improvements --- lib/matplotlib/tests/test_ticker.py | 51 ++++++++++++++++++++++ lib/matplotlib/ticker.py | 67 +++++++++++------------------ 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 9a1c8f0f5881..52a78055bfb1 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -38,6 +38,27 @@ def test_integer(self, vmin, vmax, steps, expected): loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps) assert_almost_equal(loc.tick_values(vmin, vmax), expected) + @pytest.mark.parametrize('kwargs, errortype, match', [ + ({'foo': 0}, TypeError, + re.escape("set_params() got an unexpected keyword argument 'foo'")), + ({'steps': [2, 1]}, ValueError, "steps argument must be an increasing"), + ({'steps': 2}, ValueError, "steps argument must be an increasing"), + ({'steps': [2, 11]}, ValueError, "steps argument must be an increasing"), + ]) + def test_errors(self, kwargs, errortype, match): + with pytest.raises(errortype, match=match): + mticker.MaxNLocator(**kwargs) + + @pytest.mark.parametrize('steps, result', [ + ([1, 2, 10], [1, 2, 10]), + ([2, 10], [1, 2, 10]), + ([1, 2], [1, 2, 10]), + ([2], [1, 2, 10]), + ]) + def test_padding(self, steps, result): + loc = mticker.MaxNLocator(steps=steps) + assert (loc._steps == result).all() + class TestLinearLocator: def test_basic(self): @@ -45,6 +66,10 @@ def test_basic(self): test_value = np.array([-0.8, -0.3, 0.2]) assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value) + def test_zero_numticks(self): + loc = mticker.LinearLocator(numticks=0) + loc.tick_values(-0.8, 0.2) == [] + def test_set_params(self): """ Create linear locator with presets={}, numticks=2 and change it to @@ -55,6 +80,15 @@ def test_set_params(self): assert loc.numticks == 8 assert loc.presets == {(0, 1): []} + def test_presets(self): + loc = mticker.LinearLocator(presets={(1, 2): [1, 1.25, 1.75], + (0, 2): [0.5, 1.5]}) + assert loc.tick_values(1, 2) == [1, 1.25, 1.75] + assert loc.tick_values(2, 1) == [1, 1.25, 1.75] + assert loc.tick_values(0, 2) == [0.5, 1.5] + assert loc.tick_values(0.0, 2.0) == [0.5, 1.5] + assert (loc.tick_values(0, 1) == np.linspace(0, 1, 11)).all() + class TestMultipleLocator: def test_basic(self): @@ -590,6 +624,23 @@ def test_values(self, vmin, vmax, expected): ticks = sym.tick_values(vmin=vmin, vmax=vmax) assert_array_equal(ticks, expected) + def test_subs(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1, subs=[2.0, 4.0]) + sym.create_dummy_axis() + sym.axis.set_view_interval(-10, 10) + assert (sym() == [-20., -40., -2., -4., 0., 2., 4., 20., 40.]).all() + + def test_extending(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1) + sym.create_dummy_axis() + sym.axis.set_view_interval(8, 9) + assert (sym() == [1.0]).all() + sym.axis.set_view_interval(8, 12) + assert (sym() == [1.0, 10.0]).all() + assert sym.view_limits(10, 10) == (1, 100) + assert sym.view_limits(-10, -10) == (-100, -1) + assert sym.view_limits(0, 0) == (-0.001, 0.001) + class TestAsinhLocator: def test_init(self): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index b4793929de9b..df848ef04ad8 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -657,12 +657,12 @@ def format_data(self, value): # docstring inherited e = math.floor(math.log10(abs(value))) s = round(value / 10**e, 10) - exponent = self._format_maybe_minus_and_locale("%d", e) significand = self._format_maybe_minus_and_locale( "%d" if s % 1 == 0 else "%1.10g", s) if e == 0: return significand - elif self._useMathText or self._usetex: + exponent = self._format_maybe_minus_and_locale("%d", e) + if self._useMathText or self._usetex: exponent = "10^{%s}" % exponent return (exponent if s == 1 # reformat 1x10^y as 10^y else rf"{significand} \times {exponent}") @@ -675,7 +675,6 @@ def get_offset(self): """ if len(self.locs) == 0: return '' - s = '' if self.orderOfMagnitude or self.offset: offsetStr = '' sciNotStr = '' @@ -694,8 +693,8 @@ def get_offset(self): s = fr'${sciNotStr}\mathdefault{{{offsetStr}}}$' else: s = ''.join((sciNotStr, offsetStr)) - - return self.fix_minus(s) + return self.fix_minus(s) + return '' def set_locs(self, locs): # docstring inherited @@ -1055,9 +1054,6 @@ def _non_decade_format(self, sign_string, base, fx, usetex): def __call__(self, x, pos=None): # docstring inherited - usetex = mpl.rcParams['text.usetex'] - min_exp = mpl.rcParams['axes.formatter.min_exponent'] - if x == 0: # Symlog return r'$\mathdefault{0}$' @@ -1070,23 +1066,25 @@ def __call__(self, x, pos=None): is_x_decade = _is_close_to_int(fx) exponent = round(fx) if is_x_decade else np.floor(fx) coeff = round(b ** (fx - exponent)) - if is_x_decade: - fx = round(fx) if self.labelOnlyBase and not is_x_decade: return '' if self._sublabels is not None and coeff not in self._sublabels: return '' + if is_x_decade: + fx = round(fx) + # use string formatting of the base if it is not an integer if b % 1 == 0.0: base = '%d' % b else: base = '%s' % b - if abs(fx) < min_exp: + if abs(fx) < mpl.rcParams['axes.formatter.min_exponent']: return r'$\mathdefault{%s%g}$' % (sign_string, x) elif not is_x_decade: + usetex = mpl.rcParams['text.usetex'] return self._non_decade_format(sign_string, base, fx, usetex) else: return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx) @@ -1554,7 +1552,7 @@ def symbol(self): symbol = self._symbol if not symbol: symbol = '' - elif mpl.rcParams['text.usetex'] and not self._is_latex: + elif not self._is_latex and mpl.rcParams['text.usetex']: # Source: http://www.personal.ceu.hu/tex/specchar.htm # Backslash must be first for this to work correctly since # it keeps getting added in @@ -1801,8 +1799,6 @@ def __call__(self): def tick_values(self, vmin, vmax): vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - if vmax < vmin: - vmin, vmax = vmax, vmin if (vmin, vmax) in self.presets: return self.presets[(vmin, vmax)] @@ -2356,13 +2352,13 @@ def tick_values(self, vmin, vmax): numdec = math.floor(log_vmax) - math.ceil(log_vmin) if isinstance(self._subs, str): - _first = 2.0 if self._subs == 'auto' else 1.0 if numdec > 10 or b < 3: if self._subs == 'auto': return np.array([]) # no minor or major ticks else: subs = np.array([1.0]) # major ticks else: + _first = 2.0 if self._subs == 'auto' else 1.0 subs = np.arange(_first, b) else: subs = self._subs @@ -2386,23 +2382,14 @@ def tick_values(self, vmin, vmax): decades = np.arange(math.floor(log_vmin) - stride, math.ceil(log_vmax) + 2 * stride, stride) - if hasattr(self, '_transform'): - ticklocs = self._transform.inverted().transform(decades) - if have_subs: - if stride == 1: - ticklocs = np.ravel(np.outer(subs, ticklocs)) - else: - # No ticklocs if we have >1 decade between major ticks. - ticklocs = np.array([]) - else: - if have_subs: - if stride == 1: - ticklocs = np.concatenate( - [subs * decade_start for decade_start in b ** decades]) - else: - ticklocs = np.array([]) + if have_subs: + if stride == 1: + ticklocs = np.concatenate( + [subs * decade_start for decade_start in b ** decades]) else: - ticklocs = b ** decades + ticklocs = np.array([]) + else: + ticklocs = b ** decades _log.debug('ticklocs %r', ticklocs) if (len(subs) > 1 @@ -2422,8 +2409,8 @@ def view_limits(self, vmin, vmax): vmin, vmax = self.nonsingular(vmin, vmax) if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': - vmin = _decade_less_equal(vmin, self._base) - vmax = _decade_greater_equal(vmax, self._base) + vmin = _decade_less_equal(vmin, b) + vmax = _decade_greater_equal(vmax, b) return vmin, vmax @@ -2503,7 +2490,6 @@ def __call__(self): return self.tick_values(vmin, vmax) def tick_values(self, vmin, vmax): - base = self._base linthresh = self._linthresh if vmax < vmin: @@ -2540,6 +2526,8 @@ def tick_values(self, vmin, vmax): # Check if linear range is present has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh) + base = self._base + def get_log_range(lo, hi): lo = np.floor(np.log(lo) / np.log(base)) hi = np.ceil(np.log(hi) / np.log(base)) @@ -2573,11 +2561,7 @@ def get_log_range(lo, hi): if has_c: decades.extend(base ** (np.arange(c_lo, c_hi, stride))) - # Add the subticks if requested - if self._subs is None: - subs = np.arange(2.0, base) - else: - subs = np.asarray(self._subs) + subs = np.asarray(self._subs) if len(subs) > 1 or subs[0] != 1.0: ticklocs = [] @@ -2604,8 +2588,7 @@ def view_limits(self, vmin, vmax): vmin = _decade_less(vmin, b) vmax = _decade_greater(vmax, b) - result = mtransforms.nonsingular(vmin, vmax) - return result + return mtransforms.nonsingular(vmin, vmax) class AsinhLocator(Locator): @@ -2773,7 +2756,7 @@ def tick_values(self, vmin, vmax): # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ... # b-scale : ... -3 -2 -1 0 1 2 3 ... def ideal_ticks(x): - return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2 + return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 0.5 vmin, vmax = self.nonsingular(vmin, vmax) binf = int( From a3b58b2f2a361dc3fac27bffea91b8638383e709 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2023 16:55:14 -0400 Subject: [PATCH 0017/1120] DOC: Remove unused image rotator The use of the rotator was dropped in #26332. Currently, it causes an exception on every page, because there is no 'image_rotator' element, but this is only visible in the console or, if you have the inspector open, it'll start the debugger. --- doc/_static/image-rotator.js | 46 ------------------------------------ doc/conf.py | 1 - 2 files changed, 47 deletions(-) delete mode 100644 doc/_static/image-rotator.js diff --git a/doc/_static/image-rotator.js b/doc/_static/image-rotator.js deleted file mode 100644 index f056b5b0c254..000000000000 --- a/doc/_static/image-rotator.js +++ /dev/null @@ -1,46 +0,0 @@ -// accessible JavaScript tab switcher -// modified from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role - -function getRandomInt(max) { - return Math.floor(Math.random() * max); -} - -var images_rotate = [ - {"image": "sphx_glr_plot_001_2_00x.png", "caption": "plot(x, y)", "link": "plot_types/basic/plot.html"}, - {"image": "sphx_glr_fill_between_001_2_00x.png", "caption": "fill_between(x, y1, y2)", "link": "plot_types/basic/fill_between.html"}, - {"image": "sphx_glr_scatter_plot_001_2_00x.png", "caption": "scatter(x, y)", "link": "plot_types/basic/scatter_plot.html"}, - {"image": "sphx_glr_pcolormesh_001_2_00x.png", "caption": "pcolormesh(X, Y, Z)", "link": "plot_types/arrays/pcolormesh.html"}, - {"image": "sphx_glr_contourf_001_2_00x.png", "caption": "contourf(X, Y, Z)", "link": "plot_types/arrays/contourf.html"}, - {"image": "sphx_glr_stairs_001_2_00x.png", "caption": "stairs(y)", "link": "plot_types/basic/stairs.html"}, - {"image": "sphx_glr_streamplot_001_2_00x.png", "caption": "streamplot(X, Y, U, V)", "link": "plot_types/arrays/streamplot.html"}, - {"image": "sphx_glr_bar_001_2_00x.png", "caption": "bar(x, height) / barh(y, width)", "link": "plot_types/basic/bar.html"}, - {"image": "sphx_glr_hist_plot_001_2_00x.png", "caption": "hist(x)", "link": "plot_types/stats/hist_plot.html"}, - {"image": "sphx_glr_imshow_001_2_00x.png", "caption": "imshow(Z)", "link": "plot_types/arrays/imshow.html"}, -]; - -document.addEventListener("DOMContentLoaded", function(event) { - /////////////////////////////////////// - // rotate images in images-rotate directory: - var ind = getRandomInt(images_rotate.length); - var info = images_rotate[ind]; - var img_src = "../_images/" + info.image; - var caption = info.caption; - var link = "https://matplotlib.org/stable/" + info.link; - var html = '' + - '' + - '
' + caption + '
' + - '
'; -document.getElementById('image_rotator').innerHTML = html; - - ind = getRandomInt(images_rotate.length); - info = images_rotate[ind]; - img_src = "../_images/" + info.image; - caption = info.caption; - link = "https://matplotlib.org/stable/" + info.link; - html = '' + - '' + - '
' + caption + '
' + - '
'; -document.getElementById('image_rotator2').innerHTML = html; - -}); diff --git a/doc/conf.py b/doc/conf.py index 4658f0059068..65781e694958 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -785,5 +785,4 @@ def setup(app): bld_type = 'rel' app.add_config_value('skip_sub_dirs', 0, '') app.add_config_value('releaselevel', bld_type, 'env') - app.add_js_file('image-rotator.js') app.connect('html-page-context', add_html_cache_busting, priority=1000) From 897549be1a0da4963c009a6772c9a3dce2374563 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2023 17:50:48 -0400 Subject: [PATCH 0018/1120] Import PIL.Image explicitly over PIL We seem to use `PIL.Image.*` pretty often while only importing `PIL`. This mostly works, but seems to be by fluke depending on what may or may not have imported `PIL.Image`. For example, if you have a file that just does `import matplotlib.widgets` (which itself imports only `PIL`), then `mypy` will crash due to incomplete `PIL`. --- lib/matplotlib/axes/_axes.pyi | 2 +- lib/matplotlib/backends/backend_wx.py | 2 +- lib/matplotlib/image.py | 1 + lib/matplotlib/image.pyi | 2 +- lib/matplotlib/pyplot.py | 2 +- lib/matplotlib/widgets.pyi | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index bdd29684925d..30c0622b89f5 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -30,7 +30,7 @@ import matplotlib.stackplot as mstack import matplotlib.streamplot as mstream import datetime -import PIL +import PIL.Image from collections.abc import Callable, Sequence from typing import Any, Literal, overload import numpy as np diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index a2952557edce..218be8947695 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -15,7 +15,7 @@ import weakref import numpy as np -import PIL +import PIL.Image import matplotlib as mpl from matplotlib.backend_bases import ( diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 55126c9c0f38..757f0ba3476e 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -10,6 +10,7 @@ import warnings import numpy as np +import PIL.Image import PIL.PngImagePlugin import matplotlib as mpl diff --git a/lib/matplotlib/image.pyi b/lib/matplotlib/image.pyi index d39490457eae..426e34ec83c9 100644 --- a/lib/matplotlib/image.pyi +++ b/lib/matplotlib/image.pyi @@ -5,7 +5,7 @@ from typing import Any, BinaryIO, Literal import numpy as np from numpy.typing import ArrayLike, NDArray -import PIL # type: ignore +import PIL.Image import matplotlib.artist as martist from matplotlib.axes import Axes diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 11a998334b6e..415d5c042241 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -88,7 +88,7 @@ from typing import Any, BinaryIO, Literal, TypeVar from typing_extensions import ParamSpec - import PIL + import PIL.Image from numpy.typing import ArrayLike from matplotlib.axis import Tick diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index 8af25957e9d1..00c2d0da8a7e 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -7,7 +7,7 @@ from .lines import Line2D from .patches import Circle, Polygon, Rectangle from .text import Text -import PIL +import PIL.Image from collections.abc import Callable, Collection, Iterable, Sequence from typing import Any, Literal From ee02182c54628301f7e2359d597d121299600c17 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sat, 24 Jun 2023 15:04:41 +0200 Subject: [PATCH 0019/1120] Do not space symbols in limits --- doc/sphinxext/math_symbol_table.py | 9 +++++---- doc/users/next_whats_new/mathtext_spacing.rst | 5 +++++ lib/matplotlib/_mathtext.py | 12 +++++++----- lib/matplotlib/_mathtext_data.py | 4 ++-- lib/matplotlib/tests/test_mathtext.py | 2 +- 5 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 doc/users/next_whats_new/mathtext_spacing.rst diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index a195e073831f..ee1022e75fef 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -67,8 +67,8 @@ \hslash \blacksquare \blacktriangle \sharp \increment \prime \blacktriangledown \Im \flat \backprime \Re \natural \circledS \P \copyright \circledR \S \yen \checkmark \$ - \cent \triangle \QED \sinewave \dag \ddag \perthousand - \lambdabar \L \l \degree \danger \maltese + \cent \triangle \QED \sinewave \dag \ddag \perthousand \ac + \lambdabar \L \l \degree \danger \maltese \clubsuitopen \i \hermitmatrix \sterling \nabla \mho""".split()], ] @@ -76,14 +76,14 @@ def run(state_machine): def render_symbol(sym, ignore_variant=False): - if ignore_variant and sym != r"\varnothing": + if ignore_variant and sym not in (r"\varnothing", r"\varlrtriangle"): sym = sym.replace(r"\var", "\\") if sym.startswith("\\"): sym = sym.lstrip("\\") if sym not in (_mathtext.Parser._overunder_functions | _mathtext.Parser._function_names): sym = chr(_mathtext_data.tex2uni[sym]) - return f'\\{sym}' if sym in ('\\', '|') else sym + return f'\\{sym}' if sym in ('\\', '|', '+', '-', '*') else sym lines = [] for category, columns, syms in symbols: @@ -145,6 +145,7 @@ def setup(app): # Add accents all_symbols.update({v[1:]: k for k, v in _mathtext.Parser._accent_map.items()}) + all_symbols.update({v: v for v in _mathtext.Parser._wide_accents}) print("SYMBOLS NOT IN TABLE:") for sym, val in _mathtext_data.tex2uni.items(): if sym not in all_symbols: diff --git a/doc/users/next_whats_new/mathtext_spacing.rst b/doc/users/next_whats_new/mathtext_spacing.rst new file mode 100644 index 000000000000..42da810c3a39 --- /dev/null +++ b/doc/users/next_whats_new/mathtext_spacing.rst @@ -0,0 +1,5 @@ +``mathtext`` spacing corrections +-------------------------------- + +As consequence of the updated documentation, the spacing on a number of relational and +operator symbols were classified like that and therefore will be spaced properly. diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 7a8cd0ae96fa..395c011f11b7 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -1778,7 +1778,7 @@ class _MathStyle(enum.Enum): \triangleright \ntriangleleft \ntriangleright \trianglelefteq \ntrianglelefteq \trianglerighteq \ntrianglerighteq \blacktriangleleft \blacktriangleright - \equalparallel \measuredrightangle \lrtriangle + \equalparallel \measuredrightangle \varlrtriangle \Doteq \Bumpeq \Subset \Supset \backepsilon \because \therefore \bot \top \bumpeq \circeq \coloneq @@ -1799,7 +1799,7 @@ class _MathStyle(enum.Enum): \varniobar \niobar \bagmember \ratio \Equiv \stareq \measeq \arceq \rightassert \rightModels \smallin \smallowns - \notsmallowns'''.split()) + \notsmallowns \nsimeq'''.split()) _arrow_symbols = set(r""" \leftarrow \longleftarrow \uparrow \Leftarrow \Longleftarrow @@ -2154,9 +2154,11 @@ def symbol(self, s, loc, toks): # such as ${ -2}$, $ -2$, or $ -2$. prev_char = next((c for c in s[:loc][::-1] if c != ' '), '') # Binary operators at start of string should not be spaced - if (c in self._binary_operators and - (len(s[:loc].split()) == 0 or prev_char == '{' or - prev_char in self._left_delims)): + # Also, operators in sub- or superscripts should not be spaced + if (self._in_subscript_or_superscript or ( + c in self._binary_operators and ( + len(s[:loc].split()) == 0 or prev_char == '{' or + prev_char in self._left_delims))): return [char] else: return [Hlist([self._make_space(0.2), diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index 8d1bffc57570..d37cdff22e57 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -910,7 +910,7 @@ 'O' : 216, 'hookleftarrow' : 8617, 'trianglerighteq' : 8885, - 'nsime' : 8772, + 'nsimeq' : 8772, 'oe' : 339, 'nwarrow' : 8598, 'o' : 248, @@ -1068,7 +1068,7 @@ 'hermitmatrix' : 8889, 'barvee' : 8893, 'measuredrightangle' : 8894, - 'lrtriangle' : 8895, + 'varlrtriangle' : 8895, 'equalparallel' : 8917, 'npreccurlyeq' : 8928, 'nsucccurlyeq' : 8929, diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index bae09c7dbe95..dab981f29a23 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -525,7 +525,7 @@ def test_mathtext_operators(): \ngtrsim \nlessgtr \ngtrless \cupleftarrow \oequal \rightassert \rightModels \hermitmatrix \barvee - \measuredrightangle \lrtriangle + \measuredrightangle \varlrtriangle \equalparallel \npreccurlyeq \nsucccurlyeq \nsqsubseteq \nsqsupseteq \sqsubsetneq \sqsupsetneq \disin \varisins From a01fbaeba4bdefc2ff6782bac73a9a03b8a9bce7 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 11 Aug 2023 01:48:00 +0100 Subject: [PATCH 0020/1120] Disable ``add_html_cache_busting`` on Sphinx 7.1+ --- doc/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 65781e694958..66b367c9e267 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,6 +19,8 @@ import sys from urllib.parse import urlsplit, urlunsplit import warnings + +import sphinx import yaml import matplotlib @@ -408,6 +410,9 @@ def add_html_cache_busting(app, pagename, templatename, context, doctree): This adds the Matplotlib version as a query to the link reference in the HTML, if the path is not absolute (i.e., it comes from the `_static` directory) and doesn't already have a query. + + .. note:: Sphinx 7.1 provides asset checksums; so this hook only runs on + Sphinx 7.0 and earlier. """ from sphinx.builders.html import Stylesheet, JavaScript @@ -785,4 +790,5 @@ def setup(app): bld_type = 'rel' app.add_config_value('skip_sub_dirs', 0, '') app.add_config_value('releaselevel', bld_type, 'env') - app.connect('html-page-context', add_html_cache_busting, priority=1000) + if sphinx.version_info[:2] < (7, 1): + app.connect('html-page-context', add_html_cache_busting, priority=1000) From 9a089fb03e92b7419e40b16b1fb1ce172c29e116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 11 Aug 2023 11:07:11 -0300 Subject: [PATCH 0021/1120] Addressing reviewer's comments * Reducing duplication by linking out to contribute guide * Rewording instructions and adding notes on codespaces-specific workflows --- doc/devel/codespaces.md | 106 ++------------------------------------- doc/devel/contribute.rst | 100 +++++++++++++++++------------------- 2 files changed, 51 insertions(+), 155 deletions(-) diff --git a/doc/devel/codespaces.md b/doc/devel/codespaces.md index 3a41aae560a3..f0a4f96ba249 100644 --- a/doc/devel/codespaces.md +++ b/doc/devel/codespaces.md @@ -1,107 +1,9 @@ # Contributing to Matplotlib using GitHub codespaces -You've discovered a bug or something else you want to change -in Matplotlib — excellent! +For a general overview of contributing to Matplotlib, see https://matplotlib.org/devdocs/devel/index.html -You've worked out a way to fix it — even better! +For instructions on how to submit Pull Requests using GitHub codespaces, see https://matplotlib.org/devdocs/devel/contribute.html#contributing-code -You want to tell us about it — best of all! +For instructions on running tests to verify your changes, see https://matplotlib.org/devdocs/devel/testing.html -This project is a community effort, and everyone is welcome to contribute. -Everyone within the community is expected to abide by our -[Code of Conduct](../../CODE_OF_CONDUCT.md). - -## GitHub codespaces contribution workflow - -The preferred way to contribute to Matplotlib is to fork the main -repository at https://github.com/matplotlib/matplotlib, then submit a "pull -request" (PR). You can do this by cloning a copy of the Maplotlib repository to -your own computer, or alternatively using -[GitHub Codespaces](https://docs.github.com/codespaces) (a cloud-based -in-browser development environment, that comes with the appropriated setup to -contribute to Matplotlib). - -A brief overview of the workflows is as follows. - -1. Go to the GitHub web interface -2. Fork the [project repository](https://github.com/matplotlib/matplotlib): -3. Open codespaces on your fork by clicking on the green "Code" button on the - GitHub web interface and selecting the "Codespaces" tab. Next, click on "Open - codespaces on ". You will be able to change branches later, - so you can select the default `main` branch. - - After the codespace is created, you will be taken to a new browser tab where - you can use the terminal to activate a pre-defined conda environment called - `mpl-dev`: - - ``` - conda activate mpl-dev - ``` - -4. Install the local version of Matplotlib with: - - ``` - python -m pip install -e . - ``` - - (See [Setting up Matplotlib for development](https://matplotlib.org/devdocs/devel/development_setup.html) - for detailed instructions.) - -5. Create a branch to hold your changes: - - ``` - git checkout -b my-feature origin/main - ``` - - and start making changes. Never work in the `main` branch! - -6. Work on this task using Git to do the version control. Codespaces persist for - some time (check the [documentation for details](https://docs.github.com/codespaces/getting-started/the-codespace-lifecycle)) - and can be managed on https://github.com/codespaces. When you're done editing - e.g., `lib/matplotlib/collections.py`, do: - - ``` - git add lib/matplotlib/collections.py - git commit - ``` - - to record your changes in Git, then push them to your GitHub fork with: - - ``` - git push -u origin my-feature - ``` - -Finally, go to the web page of your fork of the Matplotlib repo, and click -'Pull request' to send your changes to the maintainers for review. - -## Other stuff you may want to do - -* If you need to run tests to verify your changes before sending your PR, you - can run (in your `mpl-dev` conda environment): - - ``` - pytest - ``` - -* If you need to build the documentation, you can move to the `doc` folder and - run (in your `mpl-dev` conda environment): - - ``` - cd doc - make html - ``` - -* If you need to open a GUI window with Matplotlib output, our Codespaces - configuration includes a - [light-weight Fluxbox-based desktop](https://github.com/devcontainers/features/tree/main/src/desktop-lite). - You can use it by connecting to this desktop via your web browser. To do this: - - 1. Press `F1` or `Ctrl/Cmd+Shift+P` and select `Ports: Focus on Ports View` in - VS Code to bring it into focus. Open the ports view in your tool, select - the `noVNC` port, and click the Globe icon. - 2. In the browser that appears, click the Connect button and enter the desktop - password (`vscode` by default). - - Check the - [GitHub instructions](https://github.com/devcontainers/features/tree/main/src/desktop-lite#connecting-to-the-desktop) - for more details on connecting to the desktop. +For instructions on building the Matplotlib documentation, see https://matplotlib.org/devdocs/devel/document.html#documenting-matplotlib diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 2f8ed9271e10..62e4d50af8c5 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -230,55 +230,26 @@ contribute to Matplotlib). A brief overview of the workflows is as follows. -.. tab-set:: +1. `Create an account `_ on GitHub if you do not + already have one. - .. tab-item:: Local development +2. Fork the `project repository `_: + click on the 'Fork' button near the top of the page. This creates a copy of + the code under your account on the GitHub server. - 1. `Create an account `_ on GitHub if you do - not already have one. +.. tab-set:: - 2. Fork the - `project repository `_: - click on the 'Fork' button near the top of the page. This creates a - copy of the code under your account on the GitHub server. + .. tab-item:: Local development 3. Clone this copy to your local disk:: git clone https://github.com//matplotlib.git - 4. Enter the directory and install the local version of Matplotlib. - See :ref:`installing_for_devs` for instructions - - 5. Create a branch to hold your changes:: - - git checkout -b my-feature origin/main - - and start making changes. Never work in the ``main`` branch! - - 6. Work on this copy, on your computer, using Git to do the version - control. When you're done editing e.g., - ``lib/matplotlib/collections.py``, do:: - - git add lib/matplotlib/collections.py - git commit - - to record your changes in Git, then push them to GitHub with:: - - git push -u origin my-feature - .. tab-item:: Using GitHub Codespaces - 1. `Create an account `_ on GitHub if you do - not already have one. - - 2. Fork the - `project repository `_: - click on the 'Fork' button near the top of the page. This creates a - copy of the code under your account on the GitHub server. - 3. Open codespaces on your fork by clicking on the green "Code" button on the GitHub web interface and selecting the "Codespaces" tab. Next, - click on "Open codespaces on ". You will be able to + click on "Open codespaces on ". You will be able to change branches later, so you can select the default ``main`` branch. After the codespace is created, you will be taken to a new browser @@ -287,31 +258,30 @@ A brief overview of the workflows is as follows. conda activate mpl-dev - 4. Install the local version of Matplotlib with:: +4. Install the local version of Matplotlib with:: - python -m pip install -e . + python -m pip install -e . - (See :ref:`installing_for_devs` for detailed instructions.) + (See :ref:`installing_for_devs` for detailed instructions.) - 5. Create a branch to hold your changes:: +5. Create a branch to hold your changes:: - git checkout -b my-feature origin/main + git checkout -b my-feature origin/main - and start making changes. Never work in the ``main`` branch! + and start making changes. Never work in the ``main`` branch! - 6. Work on this task using Git to do the version control. Codespaces - persist for some time (check the `documentation for details - `_) - and can be managed on https://github.com/codespaces. When you're done - editing e.g., ``lib/matplotlib/collections.py``, do:: +6. Work on this task using Git to do the version control. Codespaces persist for + some time (check the `documentation for details + `_) + and can be managed on https://github.com/codespaces. When you're done editing + e.g., ``lib/matplotlib/collections.py``, do:: - git add lib/matplotlib/collections.py - git commit + git add lib/matplotlib/collections.py + git commit - to record your changes in Git, then push them to your GitHub fork - with:: + to record your changes in Git, then push them to your GitHub fork with:: - git push -u origin my-feature + git push -u origin my-feature Finally, go to the web page of your fork of the Matplotlib repo, and click 'Pull request' to send your changes to the maintainers for review. @@ -319,6 +289,30 @@ Finally, go to the web page of your fork of the Matplotlib repo, and click For more detailed instructions on how to set up Matplotlib for development and best practices for contribution, see :ref:`installing_for_devs`. +.. note:: GitHub Codespaces workflows + + * If you need to open a GUI window with Matplotlib output on Codespaces, our + configuration includes a `light-weight Fluxbox-based desktop + `_. + You can use it by connecting to this desktop via your web browser. To do + this: + + 1. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select + ``Ports: Focus on Ports View`` in the VSCode session to bring it into + focus. Open the ports view in your tool, select the ``noVNC`` port, and + click the Globe icon. + 2. In the browser that appears, click the Connect button and enter the desktop + password (``vscode`` by default). + + Check the `GitHub instructions + `_ + for more details on connecting to the desktop. + + * If you also built the documentation pages, you can view them using + Codespaces. Use the "Extensions" icon in the activity bar to install the + "Live Server" extension. Locate the ``doc/build/html`` folder in the + Explorer, right click the file you want to open and select "Open with Live + Server." .. _contributing_documentation: From 97c999cdb73a49c26f6aa949b3c3cac8c9cd5830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 11 Aug 2023 12:28:58 -0300 Subject: [PATCH 0022/1120] Fix Codespaces admonition title --- doc/devel/contribute.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 62e4d50af8c5..48aabfae286a 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -289,7 +289,7 @@ Finally, go to the web page of your fork of the Matplotlib repo, and click For more detailed instructions on how to set up Matplotlib for development and best practices for contribution, see :ref:`installing_for_devs`. -.. note:: GitHub Codespaces workflows +.. admonition:: GitHub Codespaces workflows * If you need to open a GUI window with Matplotlib output on Codespaces, our configuration includes a `light-weight Fluxbox-based desktop From 06ceba5e8539e44e6390043f4434a0638cd6de78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 11 Aug 2023 12:33:39 -0300 Subject: [PATCH 0023/1120] Add plausible analytics to the documentation pages The Plausible service instance is provided by the Scientific Python project. --- doc/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 66b367c9e267..21da93ee6f58 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -484,7 +484,10 @@ def js_tag_with_cache_busting(js): } include_analytics = is_release_build if include_analytics: - html_theme_options["analytics"] = {"google_analytics_id": "UA-55954603-1"} + html_theme_options["analytics"] = { + "plausible_analytics_domain": "matplotlib.org", + "plausible_analytics_url": "https://views.scientific-python.org/js/script.js" + } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 1d56e654e269ba8b05304794d756cce664c5958f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Aug 2023 05:13:38 -0400 Subject: [PATCH 0024/1120] TST: Increase some tolerances for non-x86 arches --- lib/matplotlib/tests/test_axes.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ac9dd0787a43..f1074d38e567 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2502,7 +2502,7 @@ def test_contour_hatching(): @image_comparison( ['contour_colorbar'], style='mpl20', - tol=0.02 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) + tol=0.54 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) def test_contour_colorbar(): x, y, z = contour_dat() diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ad04b12526c2..644e2f245812 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1,5 +1,6 @@ import functools import itertools +import platform import pytest @@ -217,7 +218,9 @@ def test_bar3d_lightsource(): np.testing.assert_array_max_ulp(color, collection._facecolor3d[1::6], 4) -@mpl3d_image_comparison(['contour3d.png'], style='mpl20') +@mpl3d_image_comparison( + ['contour3d.png'], style='mpl20', + tol=0.002 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) def test_contour3d(): fig = plt.figure() ax = fig.add_subplot(projection='3d') From 029f852ade2b50821701abe16f571e4e84b72bfe Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 11 Aug 2023 20:21:30 -0500 Subject: [PATCH 0025/1120] TYP: Add overload to specify output of Colormap.__call__ when possible Closes #26501 --- lib/matplotlib/colors.pyi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 354250a7bd22..9bb1725f4f78 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -69,6 +69,15 @@ class Colormap: N: int colorbar_extend: bool def __init__(self, name: str, N: int = ...) -> None: ... + @overload + def __call__( + self, X: Sequence[float] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: float, alpha: float | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload def __call__( self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... ) -> tuple[float, float, float, float] | np.ndarray: ... From f9d4000c1db8852392a57e93d93431e8edd813ce Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 12 Aug 2023 19:38:06 +0200 Subject: [PATCH 0026/1120] Update/tweak SpanSelector docs. --- lib/matplotlib/widgets.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 00397104d624..b0d026e3095b 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2548,12 +2548,9 @@ class SpanSelector(_SelectorWidget): ---------- ax : `~matplotlib.axes.Axes` - onselect : callable + onselect : callable with signature ``func(min: float, max: float)`` A callback function that is called after a release event and the selection is created, changed or removed. - It must have the signature:: - - def on_select(min: float, max: float) -> Any direction : {"horizontal", "vertical"} The direction along which to draw the span selector. @@ -2564,22 +2561,14 @@ def on_select(min: float, max: float) -> Any useblit : bool, default: False If True, use the backend-dependent blitting features for faster - canvas updates. See the tutorial :ref:`blitting` - for details. + canvas updates. See the tutorial :ref:`blitting` for details. - props : dict, optional + props : dict, default: {'facecolor': 'red', 'alpha': 0.5} Dictionary of `matplotlib.patches.Patch` properties. - Default:: - - dict(facecolor='red', alpha=0.5) - onmove_callback : func(min, max), min/max are floats, default: None + onmove_callback : callable with signature ``func(min: float, max: float)``, optional Called on mouse move while the span is being selected. - span_stays : bool, default: False - If True, the span stays visible after the mouse is released. - Deprecated, use *interactive* instead. - interactive : bool, default: False Whether to draw a set of handles that allow interaction with the widget after it is drawn. @@ -2593,8 +2582,7 @@ def on_select(min: float, max: float) -> Any properties. grab_range : float, default: 10 - Distance in pixels within which the interactive tool handles can be - activated. + Distance in pixels within which the interactive tool handles can be activated. state_modifier_keys : dict, optional Keyboard modifiers which affect the widget's behavior. Values @@ -2603,12 +2591,10 @@ def on_select(min: float, max: float) -> Any - "clear": Clear the current shape, default: "escape". drag_from_anywhere : bool, default: False - If `True`, the widget can be moved by clicking anywhere within - its bounds. + If `True`, the widget can be moved by clicking anywhere within its bounds. ignore_event_outside : bool, default: False - If `True`, the event triggered outside the span selector will be - ignored. + If `True`, the event triggered outside the span selector will be ignored. snap_values : 1D array-like, optional Snap the selector edges to the given values. From ff16da1b92ce4258b1d9c5fbfec8d5dbef8e3d25 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 13 Aug 2023 15:32:25 +0200 Subject: [PATCH 0027/1120] Clarify interaction between params of get_path_collection_extents. That master_transform is ignored is clear looking at the implementation in _path.h (the `if (Ntransforms)` branch drops master_transform; compare with the implementation of point_in_path_collection). One can also compare ``` from matplotlib.path import *; from matplotlib.transforms import * get_path_collection_extents( Affine2D().scale(2, 3), [Path([(1, 2), (3, 4)])], [], [(0, 0)], IdentityTransform()) get_path_collection_extents( Affine2D().scale(2, 3), [Path([(1, 2), (3, 4)])], [IdentityTransform()], [(0, 0)], IdentityTransform()) ``` --- lib/matplotlib/path.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index a687db923c3c..e72eb1a9ca73 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -1063,6 +1063,7 @@ def get_path_collection_extents( Global transformation applied to all paths. paths : list of `Path` transforms : list of `~matplotlib.transforms.Affine2DBase` + If non-empty, this overrides *master_transform*. offsets : (N, 2) array-like offset_transform : `~matplotlib.transforms.Affine2DBase` Transform applied to the offsets before offsetting the path. From d0642b219efa2d6edd7310fe7abb8be1be0d54fa Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 18 Oct 2022 11:52:32 +0200 Subject: [PATCH 0028/1120] List the webagg_core module in the sphinx docs. --- doc/api/backend_webagg_core_api.rst | 8 ++++++++ doc/api/index_backend_api.rst | 1 + doc/api/prev_api_changes/api_changes_2.1.0.rst | 2 +- lib/matplotlib/backend_bases.py | 8 ++++---- lib/matplotlib/backends/backend_webagg.py | 4 +--- lib/matplotlib/backends/backend_webagg_core.py | 4 +--- 6 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 doc/api/backend_webagg_core_api.rst diff --git a/doc/api/backend_webagg_core_api.rst b/doc/api/backend_webagg_core_api.rst new file mode 100644 index 000000000000..0d1e58dd8f9f --- /dev/null +++ b/doc/api/backend_webagg_core_api.rst @@ -0,0 +1,8 @@ +******************************************* +``matplotlib.backends.backend_webagg_core`` +******************************************* + +.. automodule:: matplotlib.backends.backend_webagg_core + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/index_backend_api.rst b/doc/api/index_backend_api.rst index 639d96a9a0dd..6012f71c52a4 100644 --- a/doc/api/index_backend_api.rst +++ b/doc/api/index_backend_api.rst @@ -20,5 +20,6 @@ backend_qt_api.rst backend_svg_api.rst backend_tk_api.rst + backend_webagg_core_api.rst backend_webagg_api.rst backend_wx_api.rst diff --git a/doc/api/prev_api_changes/api_changes_2.1.0.rst b/doc/api/prev_api_changes/api_changes_2.1.0.rst index 39ea78bdf587..7d72d95783bb 100644 --- a/doc/api/prev_api_changes/api_changes_2.1.0.rst +++ b/doc/api/prev_api_changes/api_changes_2.1.0.rst @@ -296,7 +296,7 @@ Third-party backends should also migrate to the ``*_dashes`` methods. ``NavigationToolbar2.dynamic_update`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use :meth:`.draw_idle` method on the ``Canvas`` instance instead. +Use `~.FigureCanvasBase.draw_idle` method on the ``Canvas`` instance instead. Testing diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 840082cc91ea..36c72af3c696 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1116,10 +1116,10 @@ def __init__(self, interval=None, callbacks=None): The time between timer events in milliseconds. Will be stored as ``timer.interval``. callbacks : list[tuple[callable, tuple, dict]] - List of (func, args, kwargs) tuples that will be called upon - timer events. This list is accessible as ``timer.callbacks`` and - can be manipulated directly, or the functions `add_callback` and - `remove_callback` can be used. + List of (func, args, kwargs) tuples that will be called upon timer + events. This list is accessible as ``timer.callbacks`` and can be + manipulated directly, or the functions `~.TimerBase.add_callback` + and `~.TimerBase.remove_callback` can be used. """ self.callbacks = [] if callbacks is None else callbacks.copy() # Set .interval and not ._interval to go through the property setter. diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index 27c6a885a69f..14c0b525fb8f 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -1,6 +1,4 @@ -""" -Displays Agg images in the browser, with interactivity -""" +"""Displays Agg images in the browser, with interactivity.""" # The WebAgg backend is divided into two modules: # diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 4c7a553f0d2d..4ceac1699543 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -1,6 +1,4 @@ -""" -Displays Agg images in the browser, with interactivity -""" +"""Displays Agg images in the browser, with interactivity.""" # The WebAgg backend is divided into two modules: # # - `backend_webagg_core.py` contains code necessary to embed a WebAgg From e2d45d56ca84372158938caf3a6bc3cb982583e5 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 13 Aug 2023 19:26:18 +0200 Subject: [PATCH 0029/1120] Replace reference to %pylab by %matplotlib. ... as %pylab is deprecated, and %matplotlib is really the relevant mode here. --- lib/matplotlib/backend_bases.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 840082cc91ea..009410593b34 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3412,9 +3412,9 @@ def show(cls, *, block=None): """ Show all figures. - `show` blocks by calling `mainloop` if *block* is ``True``, or if it - is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in - `interactive` mode. + `show` blocks by calling `mainloop` if *block* is ``True``, or if it is + ``None`` and we are not in `interactive` mode and if IPython's + ``%matplotlib`` integration has not been activated. """ managers = Gcf.get_all_fig_managers() if not managers: @@ -3427,9 +3427,9 @@ def show(cls, *, block=None): if cls.mainloop is None: return if block is None: - # Hack: Are we in IPython's %pylab mode? In pylab mode, IPython - # (>= 0.10) tacks a _needmain attribute onto pyplot.show (always - # set to False). + # Hack: Is IPython's %matplotlib integration activated? If so, + # IPython's activate_matplotlib (>= 0.10) tacks a _needmain + # attribute onto pyplot.show (always set to False). pyplot_show = getattr(sys.modules.get("matplotlib.pyplot"), "show", None) ipython_pylab = hasattr(pyplot_show, "_needmain") block = not ipython_pylab and not is_interactive() From 69d796ac50fe6095ba9451c07a1dce668213cae5 Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Mon, 14 Aug 2023 14:52:12 +0530 Subject: [PATCH 0030/1120] Fix mathtext mismatched braces --- lib/matplotlib/_mathtext.py | 3 ++- lib/matplotlib/tests/test_mathtext.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 25a825b7b0d9..4761ea7fc626 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2030,7 +2030,8 @@ def main(self, s, loc, toks): return [Hlist(toks)] def math_string(self, s, loc, toks): - return self._math_expression.parseString(toks[0][1:-1]) + return self._math_expression.parseString(toks[0][1:-1], + parseAll=True) def math(self, s, loc, toks): hlist = Hlist(toks) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index d80312495d91..ba30b9be214a 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -320,6 +320,7 @@ def test_fontinfo(): (r'$a^2^2$', r'Double superscript'), (r'$a_2_2$', r'Double subscript'), (r'$a^2_a^2$', r'Double superscript'), + (r'$a = {b$', r'Expected end of text'), ], ids=[ 'hspace without value', @@ -347,7 +348,8 @@ def test_fontinfo(): 'unknown symbol', 'double superscript', 'double subscript', - 'super on sub without braces' + 'super on sub without braces', + 'math string with no closing braces' ] ) def test_mathtext_exceptions(math, msg): From 1fa70c69f7d7b42e41d0dea53e9792a33548c4eb Mon Sep 17 00:00:00 2001 From: Talha Irfan Date: Mon, 14 Aug 2023 20:38:30 +0500 Subject: [PATCH 0031/1120] Replaced list with tuple in pyplot for axes --- lib/matplotlib/pyplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 415d5c042241..c804adc93cf6 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1134,7 +1134,7 @@ def figlegend(*args, **kwargs) -> Legend: @_docstring.dedent_interpd def axes( - arg: None | tuple[float, float, float, float] = None, + arg: None | tuple(float, float, float, float) = None, **kwargs ) -> matplotlib.axes.Axes: """ @@ -1153,7 +1153,7 @@ def axes( - *None*: A new full window Axes is added using ``subplot(**kwargs)``. - - 4-tuple of floats *rect* = ``[left, bottom, width, height]``. + - 4-tuple of floats *rect* = ``(left, bottom, width, height)``. A new Axes is added with dimensions *rect* in normalized (0, 1) units using `~.Figure.add_axes` on the current figure. From 8c3a9c02faa261be891a48adb723a562be1e6744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 11 Aug 2023 18:48:49 -0300 Subject: [PATCH 0032/1120] Fix headings, wording and numbered list in codespaces docs Co-authored-by: marbled-toast <69227427+marbled-toast@users.noreply.github.com> Co-authored-by: hannah --- doc/devel/codespaces.md | 8 ++--- doc/devel/contribute.rst | 78 +++++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/doc/devel/codespaces.md b/doc/devel/codespaces.md index f0a4f96ba249..cb002c9b2e6e 100644 --- a/doc/devel/codespaces.md +++ b/doc/devel/codespaces.md @@ -1,9 +1,9 @@ # Contributing to Matplotlib using GitHub codespaces -For a general overview of contributing to Matplotlib, see https://matplotlib.org/devdocs/devel/index.html +* For a general overview of contributing to Matplotlib, see https://matplotlib.org/devdocs/devel/index.html -For instructions on how to submit Pull Requests using GitHub codespaces, see https://matplotlib.org/devdocs/devel/contribute.html#contributing-code +* For instructions on how to submit Pull Requests using GitHub codespaces, see https://matplotlib.org/devdocs/devel/contribute.html#contributing-code -For instructions on running tests to verify your changes, see https://matplotlib.org/devdocs/devel/testing.html +* For instructions on running tests to verify your changes, see https://matplotlib.org/devdocs/devel/testing.html -For instructions on building the Matplotlib documentation, see https://matplotlib.org/devdocs/devel/document.html#documenting-matplotlib +* For instructions on building the Matplotlib documentation, see https://matplotlib.org/devdocs/devel/document.html#documenting-matplotlib diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 48aabfae286a..4e03e0b2c811 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -224,11 +224,11 @@ The preferred way to contribute to Matplotlib is to fork the `main repository `__ on GitHub, then submit a "pull request" (PR). You can do this by cloning a copy of the Maplotlib repository to your own computer, or alternatively using -`GitHub Codespaces `_ (a cloud-based -in-browser development environment, that comes with the appropriated setup to -contribute to Matplotlib). +`GitHub Codespaces `_, a cloud-based +in-browser development environment that comes with the appropriated setup to +contribute to Matplotlib. -A brief overview of the workflows is as follows. +A brief overview of the workflow is as follows. 1. `Create an account `_ on GitHub if you do not already have one. @@ -247,22 +247,27 @@ A brief overview of the workflows is as follows. .. tab-item:: Using GitHub Codespaces - 3. Open codespaces on your fork by clicking on the green "Code" button - on the GitHub web interface and selecting the "Codespaces" tab. Next, - click on "Open codespaces on ". You will be able to - change branches later, so you can select the default ``main`` branch. + 3. Check out the Matplotlib repository and activate your development + environment: - After the codespace is created, you will be taken to a new browser - tab where you can use the terminal to activate a pre-defined conda - environment called ``mpl-dev``:: + a. Open codespaces on your fork by clicking on the green "Code" + button on the GitHub web interface and selecting the "Codespaces" + tab. Next, click on "Open codespaces on ". You + will be able to change branches later, so you can select the + default ``main`` branch. + + b. After the codespace is created, you will be taken to a new browser + tab where you can use the terminal to activate a pre-defined conda + environment called ``mpl-dev``:: + + conda activate mpl-dev - conda activate mpl-dev 4. Install the local version of Matplotlib with:: python -m pip install -e . - (See :ref:`installing_for_devs` for detailed instructions.) + See :ref:`installing_for_devs` for detailed instructions. 5. Create a branch to hold your changes:: @@ -289,30 +294,29 @@ Finally, go to the web page of your fork of the Matplotlib repo, and click For more detailed instructions on how to set up Matplotlib for development and best practices for contribution, see :ref:`installing_for_devs`. -.. admonition:: GitHub Codespaces workflows - - * If you need to open a GUI window with Matplotlib output on Codespaces, our - configuration includes a `light-weight Fluxbox-based desktop - `_. - You can use it by connecting to this desktop via your web browser. To do - this: - - 1. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select - ``Ports: Focus on Ports View`` in the VSCode session to bring it into - focus. Open the ports view in your tool, select the ``noVNC`` port, and - click the Globe icon. - 2. In the browser that appears, click the Connect button and enter the desktop - password (``vscode`` by default). - - Check the `GitHub instructions - `_ - for more details on connecting to the desktop. - - * If you also built the documentation pages, you can view them using - Codespaces. Use the "Extensions" icon in the activity bar to install the - "Live Server" extension. Locate the ``doc/build/html`` folder in the - Explorer, right click the file you want to open and select "Open with Live - Server." +GitHub Codespaces workflows +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* If you need to open a GUI window with Matplotlib output on Codespaces, our + configuration includes a `light-weight Fluxbox-based desktop + `_. + You can use it by connecting to this desktop via your web browser. To do this: + + 1. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select + ``Ports: Focus on Ports View`` in the VSCode session to bring it into + focus. Open the ports view in your tool, select the ``noVNC`` port, and + click the Globe icon. + 2. In the browser that appears, click the Connect button and enter the desktop + password (``vscode`` by default). + + Check the `GitHub instructions + `_ + for more details on connecting to the desktop. + +* If you also built the documentation pages, you can view them using Codespaces. + Use the "Extensions" icon in the activity bar to install the "Live Server" + extension. Locate the ``doc/build/html`` folder in the Explorer, right click + the file you want to open and select "Open with Live Server." .. _contributing_documentation: From 204e971de6cca36faa8b8b5a458c686d06e0f0bc Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Aug 2023 22:29:14 -0400 Subject: [PATCH 0033/1120] ci: Use new name for WQY-ZenHei font package --- .github/workflows/tests.yml | 8 ++++---- azure-pipelines.yml | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2a67c7ffcbfb..a8514e22530d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -102,7 +102,9 @@ jobs: cm-super \ dvipng \ ffmpeg \ + fonts-freefont-otf \ fonts-noto-cjk \ + fonts-wqy-zenhei \ gdb \ gir1.2-gtk-3.0 \ graphviz \ @@ -123,8 +125,6 @@ jobs: libxcb-render-util0 \ libxcb-xinerama0 \ lmodern \ - fonts-freefont-otf \ - texlive-pictures \ pkg-config \ qtbase5-dev \ texlive-fonts-recommended \ @@ -132,8 +132,8 @@ jobs: texlive-latex-extra \ texlive-latex-recommended \ texlive-luatex \ - texlive-xetex \ - ttf-wqy-zenhei + texlive-pictures \ + texlive-xetex if [[ "${{ matrix.os }}" = ubuntu-20.04 ]]; then sudo apt-get install -yy libopengl0 else # ubuntu-22.04 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb38804ae121..027923cdc3e9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -97,6 +97,7 @@ stages: dvipng \ ffmpeg \ fonts-noto-cjk \ + fonts-wqy-zenhei \ gdb \ gir1.2-gtk-3.0 \ graphviz \ @@ -111,8 +112,8 @@ stages: texlive-latex-base \ texlive-latex-extra \ texlive-latex-recommended \ - texlive-xetex texlive-luatex \ - ttf-wqy-zenhei + texlive-luatex \ + texlive-xetex ;; darwin) brew install --cask xquartz From c774216be27350aef5b6b28ec7ade0b09915a9bb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Aug 2023 22:32:26 -0400 Subject: [PATCH 0034/1120] ci: Don't install recommended packages on Ubuntu We don't need the optional packages and save a little download/install time. --- .github/workflows/tests.yml | 13 +++++++------ azure-pipelines.yml | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8514e22530d..2314f65225a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: Linux) echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries sudo apt-get update -yy - sudo apt-get install -yy \ + sudo apt-get install -yy --no-install-recommends \ ccache \ cm-super \ dvipng \ @@ -135,9 +135,10 @@ jobs: texlive-pictures \ texlive-xetex if [[ "${{ matrix.os }}" = ubuntu-20.04 ]]; then - sudo apt-get install -yy libopengl0 + sudo apt-get install -yy --no-install-recommends libopengl0 else # ubuntu-22.04 - sudo apt-get install -yy gir1.2-gtk-4.0 libnotify4 + sudo apt-get install -yy --no-install-recommends \ + gir1.2-gtk-4.0 libnotify4 fi ;; macOS) @@ -178,10 +179,10 @@ jobs: ~/.cache/matplotlib !~/.cache/matplotlib/tex.cache !~/.cache/matplotlib/test_cache - key: 3-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} + key: 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} restore-keys: | - 3-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- - 3-${{ runner.os }}-py${{ matrix.python-version }}-mpl- + 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- + 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl- - name: Install Python dependencies run: | diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 027923cdc3e9..7d00e499fd18 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -92,7 +92,7 @@ stages: linux) echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries sudo apt update - sudo apt install \ + sudo apt install --no-install-recommends \ cm-super \ dvipng \ ffmpeg \ From 4f6681b474b4da57959175563ec9276d4e0e25ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:59:11 +0000 Subject: [PATCH 0035/1120] Bump pypa/cibuildwheel from 2.14.1 to 2.15.0 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.14.1 to 2.15.0. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/f21bb8376a051ffb6cb5604b28ccaef7b90e8ab7...39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index abcd97592ad6..58b96d38c883 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -136,7 +136,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@f21bb8376a051ffb6cb5604b28ccaef7b90e8ab7 # v2.14.1 + uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -144,7 +144,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@f21bb8376a051ffb6cb5604b28ccaef7b90e8ab7 # v2.14.1 + uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -152,7 +152,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@f21bb8376a051ffb6cb5604b28ccaef7b90e8ab7 # v2.14.1 + uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -160,7 +160,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@f21bb8376a051ffb6cb5604b28ccaef7b90e8ab7 # v2.14.1 + uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From 34c7e524fa2b8862ecb4e47b72cb68f439dfdfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Mon, 14 Aug 2023 18:22:28 -0300 Subject: [PATCH 0036/1120] Add bullet points to codespaces instructions --- doc/devel/contribute.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 4e03e0b2c811..994d5448b876 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -250,17 +250,17 @@ A brief overview of the workflow is as follows. 3. Check out the Matplotlib repository and activate your development environment: - a. Open codespaces on your fork by clicking on the green "Code" - button on the GitHub web interface and selecting the "Codespaces" - tab. Next, click on "Open codespaces on ". You - will be able to change branches later, so you can select the - default ``main`` branch. + * Open codespaces on your fork by clicking on the green "Code" button + on the GitHub web interface and selecting the "Codespaces" tab. + * Next, click on "Open codespaces on ". You will be + able to change branches later, so you can select the default + ``main`` branch. + * After the codespace is created, you will be taken to a new browser + tab where you can use the terminal to activate a pre-defined conda + environment called ``mpl-dev``:: - b. After the codespace is created, you will be taken to a new browser - tab where you can use the terminal to activate a pre-defined conda - environment called ``mpl-dev``:: + conda activate mpl-dev - conda activate mpl-dev 4. Install the local version of Matplotlib with:: From 09b8ee83aafb260d7c1127e46b7c84f0b8f28908 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 14 Aug 2023 20:38:24 -0400 Subject: [PATCH 0037/1120] Fix MathText antialiasing I think an accident during confict resolution of #26168 incorrectly overwrote antialias settings for MathText. This puts the `_val_or_rc` call in the correct location. Also, re-arrange the tests to better catch this error. --- lib/matplotlib/_mathtext.py | 3 +- lib/matplotlib/mathtext.py | 3 +- .../baseline_images/test_text/antialiased.png | Bin 3143 -> 3045 bytes lib/matplotlib/tests/test_text.py | 76 +++++------------- 4 files changed, 20 insertions(+), 62 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 25a825b7b0d9..5073bdde0ff2 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -106,7 +106,7 @@ def to_vector(self): for x1, y1, x2, y2 in self.rects] return VectorParse(w, h + d, d, gs, rs) - def to_raster(self, antialiased=None): + def to_raster(self, *, antialiased): # Metrics y's and mathtext y's are oriented in opposite directions, # hence the switch between ymin and ymax. xmin = min([*[ox + info.metrics.xmin for ox, oy, info in self.glyphs], @@ -128,7 +128,6 @@ def to_raster(self, antialiased=None): # old approach and keeps baseline images backcompat. shifted = ship(self.box, (-xmin, -ymin)) - antialiased = mpl.rcParams['text.antialiased'] for ox, oy, info in shifted.glyphs: info.font.draw_glyph_to_bitmap( image, ox, oy - info.metrics.iceberg, info.glyph, diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index 7f639d2c0987..e25b76c4037c 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -75,8 +75,7 @@ def parse(self, s, dpi=72, prop=None, *, antialiased=None): # is mutable; key the cache using an internal copy (see # text._get_text_metrics_with_cache for a similar case). prop = prop.copy() if prop is not None else None - if antialiased is None: - antialiased = mpl.rcParams['text.antialiased'] + antialiased = mpl._val_or_rc(antialiased, 'text.antialiased') return self._parse_cached(s, dpi, prop, antialiased) @functools.lru_cache(50) diff --git a/lib/matplotlib/tests/baseline_images/test_text/antialiased.png b/lib/matplotlib/tests/baseline_images/test_text/antialiased.png index 11ba1b082df94b965371f44699a556afdb09b6a6..06c007591d966acc6a2ef96fcf2429bc823d88a6 100644 GIT binary patch literal 3045 zcmbVOXIN9&77Z#4MPx=t)X+vy6p#>l5s9K8Ar$Go*U%Cnv`_-hD1wv`rAiqA=^a81 zf-=A$O+gX`B4CILAs{7`VBj6+$NRqd^}aVh?!D*Ud+s^=uC?~sJMosuO+kJrKLi31 zL>k;Mhd>Sqf-#Dh2b?$VH4lS}4NlJ%XMyp?g}ViNL5$sS*Z>SJ0PX%um{)KJ8WW@p z(}2NYo?c;!8gNxPAGn*EyO)=zx{@c%Qwgr54A)Rpz4VJO4u=iVQcwu|@8>W~u#bYH zv-NSX1s~SHE(8J*a{GR8l_Sc~5Qu;Z^2RmGh&(2hU}VLT7+9m6^!#>@=aD6UhAm@2 z!d)a05iEb)+cqhw!P*T=X) zv!cIv8w~hN2s`xX-P`d(HnFcZ8mN(DnuTM#q;}eey^g|-_K)?KhCoO?7e5(=azP-j z-oL1T0HuBdfVlql8Vr+vghC*3Kj|KZK;XK*;DJ6bfUJl2DVTlw>^~-_d3k#;Eet;D zX9@@ius%eszMqbajFgp=dlL6*m`UxliP~PtABRFrHAGYWmuOfGZLNWb++(V)0lb2z zt&qq>@#wEF&h80`X_aJV3ez@35wVT~sbWp7-JC6lNeU$ml~PbyU2W;*MSo0=%Rwv6 zE=PFu;pVSQa=Wtq{rxA#5C__x2eyZPz5IiaIH`2MCm^hb@JBWRd(;#@0T&Rxg11DY z(Mb;;Oe{@S5d){yLc_ug)1gr40!}NGy@}S$H<5Q}F*6^YBG*C-4bZw=$J>Y zwkyud6Xoo!b|3DKKHzK~mTeUYkz zEd!4f@3sd0Dwcg}8d2W}F|0CtXf|d$KNIFuIr2oE@v-m0tC5jOQv|1EeLa}i(o)&n zYzfOk<>cf9YvoThcq9}=uOnmD`c5S!CtC&tRNLFzKTc1VMA|ocCZ4OPt~Qd9kx5EP zvBF^L{QUfyBR9OM5wsNc{@yAPJt3Wund!%2vw|3GHluEGa`M^Rx0Xor&`@G}diu{| zVl~P>qhDAQ7s_3MQ_cPq7;;=df%HdzzrL4`&(Q3w3z10FBJ7jSRkXI+@bU4jZ){kE zg|(bR1NJNH>TJx+9+p%wPPGJ4D@i16YVeT!RFhAdpTB=bcDBZCA)QkBgkCqo)C!>+ z<1ija<4oITs+Nplc?u~`-7km{N@QO|ukQ>?!ZE9}(5H>I5^EjKRP43Q4t0cOogdVS z+Nus+ey4~^w=y$xlR!l8)YIv7uh8&thfFWK-Q8VsP3s4(1#K=aF7k_Ce@GC!ASXA3 z|ME0pnORmCJB8nTbCb#3Tj?BoH99KZau9#`hwA(Hd877HFR#?|^YhQN$jiz$ej0h= z1JpLPXHuvWs404Eh#qVQR#%^mZpz5Y@{w3wUTzL*o9B^}mk)dg#d02c3dSyqkjAK} z;!d4{RK!%IHd>k7FmvUY6iWi-FtKLj*R%Q3sP__f|63&UebdbgCV98~{j2gfonTp1 zZ~Ci@EzWsVW_elJ&6=8;Tm<3yn#OGOMhXaMA-H{$;zFqC>T;T&pYP|0K%vW~2SArg zOH1SJA)nt)PM${~5a%yk@E^*QudJ=Lc6TrO&Lb2mySQ{o4~wl2Tm2wE)VMr7(;i}F zV^cOa=bEn>q7RtciinSoPfANuuNC_Gr;$Be)11H{D2vBzQ&}Q`-(GOw8}+4b!Ul$h z<}oo{&M_>jjK`15OG<8}<964*I#cTE>S73^c~#xQ9LkC0(#lFh2D#>YS^!0`7Rw4{ zuUv`gIW2_-xehXzPNNmi%s2w=$zHhNb2T5B4SV5{=>nbpth)LV6*pnL<^GCHwgwaq zQYbdDv9ash+csCP#(jvO3CcT@YzYUP)^menW0;;cKyE)m3Qkt1?_(NcS`)|BS*we- z(!?#MNRaW%xivmMii9_`He_UEq_U#I%+4;SOZX{ftw;ug!LYWrQs0%5NRCz3HRC9S zF3(klgAuw2M4zSLVjUa%ylZu5jdpzsAS$P+322v5R<^)A-z_EAxzuH6pU&5gG`F*} z3vwC8M@7*YjH-?fEo$r@b8FwC%tQtT8*MWg4k{q=jh3jJJhIyI<^)|&IfCRD60!*ltQpO}d|`1( za^T}fcbekM-rmr~@$>l_w{_FszI*38rm`sml-kXDGci$2*x$YB=H`}OlAL@Lti{N% zac=OD?&#>Sbabqiv#IqR&C|qxTN@yTd^Q1YVwkS8Gj~{3MJ0K$p=Vif2cZjH4KZm> zBw5a*CGPYM-}G%8<>ux_Z?91CTkm~T3H#A{dV23?t>MHlrhVW{n;F2){MN1Cx0uuF zCC1*~rNZ*|CE!2wZru2?`?-)3GIqsByMLWNl=1N4)193NdE5G{a@N(amB+M||1bs^ z>t4T3#Lnvin_PTbVS)dgAASz`;FN-OWMsR}!M6}@9v)dqN!@GLuHm<-;Y?F~O-)Up z2Fe|7fuDtiOF4VnjE6VH&rEp4zkfkhA$6M07srIwub$Y6i0*s6pv{?%Q0i4QKX~C% zCOYH&)kCe#I($HJL!&xJC7dUX10wVb3L+QjJ^1$Vp507oWNd z${DaY8EI*K9UYzPsem!Y_AJ51*SB0!N{U)Q1L+9zC=1-2JbqkhVMn3+o1}t*!d|c- zr03}0m3Dsp$p0?D|5vsCx09v&(h&O>6ME<02=-VN=p;mlyODl`!ettpnnD-;`pGU+ z^^-{4!N!8-pBuciyRYN;C~x1E07Ilkt#(;B@xwiU`+fMPNaq#I#sUrCmm3C3JB~tn zR3%khUy6Ye?N6>gQ(sq9q-(0Z_FJuEyZh?ia?{x3oSX{4r%701PqNAko|_DJEdtvH zX=!O$+S-;UCGkhOfO_-sz+t_VFm zF){~-&phJ`-)l>bPh0@)!yJ39R|rww*l3=Tl459Rn2uwO|M@`SxkgE#fx-P2%xrD` z=) zLTy0@1Xu#6c8+4I$~$#fPPbwT7i8lZ*AH@db$O(rPq}C?RaGk%i?zgLTG`sZ`<>=9 zRxrugo~_&5#09=@)N5~VXYbI`tQ;IJb59XFKZ{d=vrmDIx)3rlNM~nfSp|i{W%KwmgH$aZvYKnrPf+;T+}zw|keMR@ s!b%huB>BYOz2wW>f74?AQ?%<6m#mu?m;LwyL0<|%>Y3cA)pfn|A05K6h5!Hn literal 3143 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 Xi@U~2IX9xfp8#ZxyoM Date: Tue, 15 Aug 2023 14:23:56 -0400 Subject: [PATCH 0038/1120] fix input check in Poly3DCollection.__init__ --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 0888853df37e..a41485f62529 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -905,7 +905,7 @@ def __init__(self, verts, *args, zsort='average', shade=False, kwargs['edgecolors'] = _shade_colors( edgecolors, normals, lightsource ) - if facecolors is None and edgecolors in None: + if facecolors is None and edgecolors is None: raise ValueError( "You must provide facecolors, edgecolors, or both for " "shade to work.") diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 644e2f245812..1f8764cbab9d 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2260,3 +2260,13 @@ def test_surface3d_zsort_inf(): ax.plot_surface(x, y, z, cmap='jet') ax.view_init(elev=45, azim=145) + + +def test_Poly3DCollection_init_value_error(): + # smoke test to ensure the input check works + # GH#26420 + with pytest.raises(ValueError, + match='You must provide facecolors, edgecolors, ' + 'or both for shade to work.'): + poly = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) + c = art3d.Poly3DCollection([poly], shade=True) From f37329bef3dfb28019fca524f03b40aa1a0ebc9c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 13 Aug 2023 15:09:32 +0200 Subject: [PATCH 0039/1120] Tweak shape repr in _api.check_shape error message. Fill in "None" dims *from the end* with the letters N, M, L, K, ... so as to get e.g. "(N, 2)", "(M, 2, N)", "(L, M, N)", etc. --- lib/matplotlib/_api/__init__.py | 8 +++----- lib/matplotlib/tests/test_api.py | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/_api/__init__.py b/lib/matplotlib/_api/__init__.py index ee00665c4f1d..13319d867dc5 100644 --- a/lib/matplotlib/_api/__init__.py +++ b/lib/matplotlib/_api/__init__.py @@ -151,12 +151,10 @@ def check_shape(shape, /, **kwargs): if (len(data_shape) != len(shape) or any(s != t and t is not None for s, t in zip(data_shape, shape))): dim_labels = iter(itertools.chain( - 'MNLIJKLH', + 'NMLKJIH', (f"D{i}" for i in itertools.count()))) - text_shape = ", ".join(str(n) - if n is not None - else next(dim_labels) - for n in shape) + text_shape = ", ".join([str(n) if n is not None else next(dim_labels) + for n in shape[::-1]][::-1]) if len(shape) == 1: text_shape += "," diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py index 34549efb0534..8b0f1e70114e 100644 --- a/lib/matplotlib/tests/test_api.py +++ b/lib/matplotlib/tests/test_api.py @@ -17,17 +17,19 @@ T = TypeVar('T') -@pytest.mark.parametrize('target,test_shape', - [((None, ), (1, 3)), - ((None, 3), (1,)), - ((None, 3), (1, 2)), - ((1, 5), (1, 9)), - ((None, 2, None), (1, 3, 1)) +@pytest.mark.parametrize('target,shape_repr,test_shape', + [((None, ), "(N,)", (1, 3)), + ((None, 3), "(N, 3)", (1,)), + ((None, 3), "(N, 3)", (1, 2)), + ((1, 5), "(1, 5)", (1, 9)), + ((None, 2, None), "(M, 2, N)", (1, 3, 1)) ]) def test_check_shape(target: tuple[int | None, ...], + shape_repr: str, test_shape: tuple[int, ...]) -> None: - error_pattern = (f"^'aardvark' must be {len(target)}D.*" + - re.escape(f'has shape {test_shape}')) + error_pattern = "^" + re.escape( + f"'aardvark' must be {len(target)}D with shape {shape_repr}, but your input " + f"has shape {test_shape}") data = np.zeros(test_shape) with pytest.raises(ValueError, match=error_pattern): _api.check_shape(target, aardvark=data) From 7f65d6b58e2cef2445f6b8778381dd3a56937dd1 Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Tue, 15 Aug 2023 15:56:35 +0530 Subject: [PATCH 0040/1120] Add unclosed group to grammar Co-authored-by: Kyle Sunden --- lib/matplotlib/_mathtext.py | 5 +++++ lib/matplotlib/tests/test_mathtext.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 4761ea7fc626..73cb0ae1189d 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -1894,6 +1894,7 @@ def csnames(group, names): p.function = csnames("name", self._function_names) p.group = p.start_group + ZeroOrMore(p.token)("group") + p.end_group + p.unclosed_group = (p.start_group + ZeroOrMore(p.token)("group") + StringEnd()) p.frac = cmd(r"\frac", p.required_group("num") + p.required_group("den")) p.dfrac = cmd(r"\dfrac", p.required_group("num") + p.required_group("den")) @@ -1942,6 +1943,7 @@ def csnames(group, names): p.token <<= ( p.simple | p.auto_delim + | p.unclosed_group | p.unknown_symbol # Must be last ) @@ -2251,6 +2253,9 @@ def end_group(self, s, loc, toks): self.pop_state() return [] + def unclosed_group(self, s, loc, toks): + raise ParseFatalException(s, len(s), "Expected '}'") + def font(self, s, loc, toks): self.get_state().font = toks["font"] return [] diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index ba30b9be214a..133469b00d8e 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -320,7 +320,7 @@ def test_fontinfo(): (r'$a^2^2$', r'Double superscript'), (r'$a_2_2$', r'Double subscript'), (r'$a^2_a^2$', r'Double superscript'), - (r'$a = {b$', r'Expected end of text'), + (r'$a = {b$', r"Expected '}'"), ], ids=[ 'hspace without value', @@ -349,7 +349,7 @@ def test_fontinfo(): 'double superscript', 'double subscript', 'super on sub without braces', - 'math string with no closing braces' + 'unclosed group' ] ) def test_mathtext_exceptions(math, msg): From e2149458614532d765d5cb75db7f15d07e64aae8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 03:23:33 -0400 Subject: [PATCH 0041/1120] TYP: Add overload for FT2Font.get_sfnt_table --- lib/matplotlib/ft2font.pyi | 135 ++++++++++- lib/matplotlib/tests/test_mathtext.py | 1 + src/ft2font_wrapper.cpp | 323 ++++++++------------------ 3 files changed, 234 insertions(+), 225 deletions(-) diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index fb74fd676f5b..8a99f7a4f2f7 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -1,4 +1,4 @@ -from typing import BinaryIO, Literal +from typing import BinaryIO, Literal, TypedDict, overload import numpy as np from numpy.typing import NDArray @@ -42,6 +42,122 @@ SCALABLE: int SFNT: int VERTICAL: int +class _SfntHeadDict(TypedDict): + version: tuple[int, int] + fontRevision: tuple[int, int] + checkSumAdjustment: int + magicNumber: int + flags: int + unitsPerEm: int + created: tuple[int, int] + modified: tuple[int, int] + xMin: int + yMin: int + xMax: int + yMax: int + macStyle: int + lowestRecPPEM: int + fontDirectionHint: int + indexToLocFormat: int + glyphDataFormat: int + +class _SfntMaxpDict(TypedDict): + version: tuple[int, int] + numGlyphs: int + maxPoints: int + maxContours: int + maxComponentPoints: int + maxComponentContours: int + maxZones: int + maxTwilightPoints: int + maxStorage: int + maxFunctionDefs: int + maxInstructionDefs: int + maxStackElements: int + maxSizeOfInstructions: int + maxComponentElements: int + maxComponentDepth: int + +class _SfntOs2Dict(TypedDict): + version: int + xAvgCharWidth: int + usWeightClass: int + usWidthClass: int + fsType: int + ySubscriptXSize: int + ySubscriptYSize: int + ySubscriptXOffset: int + ySubscriptYOffset: int + ySuperscriptXSize: int + ySuperscriptYSize: int + ySuperscriptXOffset: int + ySuperscriptYOffset: int + yStrikeoutSize: int + yStrikeoutPosition: int + sFamilyClass: int + panose: bytes + ulCharRange: tuple[int, int, int, int] + achVendID: bytes + fsSelection: int + fsFirstCharIndex: int + fsLastCharIndex: int + +class _SfntHheaDict(TypedDict): + version: tuple[int, int] + ascent: int + descent: int + lineGap: int + advanceWidthMax: int + minLeftBearing: int + minRightBearing: int + xMaxExtent: int + caretSlopeRise: int + caretSlopeRun: int + caretOffset: int + metricDataFormat: int + numOfLongHorMetrics: int + +class _SfntVheaDict(TypedDict): + version: tuple[int, int] + vertTypoAscender: int + vertTypoDescender: int + vertTypoLineGap: int + advanceHeightMax: int + minTopSideBearing: int + minBottomSizeBearing: int + yMaxExtent: int + caretSlopeRise: int + caretSlopeRun: int + caretOffset: int + metricDataFormat: int + numOfLongVerMetrics: int + +class _SfntPostDict(TypedDict): + format: tuple[int, int] + italicAngle: tuple[int, int] + underlinePosition: int + underlineThickness: int + isFixedPitch: int + minMemType42: int + maxMemType42: int + minMemType1: int + maxMemType1: int + +class _SfntPcltDict(TypedDict): + version: tuple[int, int] + fontNumber: int + pitch: int + xHeight: int + style: int + typeFamily: int + capHeight: int + symbolSet: int + typeFace: bytes + characterComplement: bytes + strokeWeight: int + widthType: int + serifStyle: int + class FT2Font: ascender: int bbox: tuple[int, int, int, int] @@ -92,9 +208,20 @@ class FT2Font: self, ) -> tuple[str, str, str, str, str, int, int, int, int]: ... def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ... - def get_sfnt_table( - self, name: Literal["head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt"] - ) -> dict[str, tuple[int, int, int, int] | tuple[int, int] | int | bytes]: ... + @overload + def get_sfnt_table(self, name: Literal["head"]) -> _SfntHeadDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["maxp"]) -> _SfntMaxpDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["OS/2"]) -> _SfntOs2Dict | None: ... + @overload + def get_sfnt_table(self, name: Literal["hhea"]) -> _SfntHheaDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["vhea"]) -> _SfntVheaDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["post"]) -> _SfntPostDict | None: ... + @overload + def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ... def get_width_height(self) -> tuple[int, int]: ... def get_xys(self, antialiased: bool = ...) -> NDArray[np.float64]: ... def load_char(self, charcode: int, flags: int = ...) -> Glyph: ... diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index d80312495d91..68c198ecd0e9 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -281,6 +281,7 @@ def test_fontinfo(): fontpath = mpl.font_manager.findfont("DejaVu Sans") font = mpl.ft2font.FT2Font(fontpath) table = font.get_sfnt_table("head") + assert table is not None assert table['version'] == (1, 0) diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 872f9c0a6023..7888a9c212a5 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1118,44 +1118,23 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:H, s:H, s:h, s:h, s:h}"; TT_Header *t = (TT_Header *)table; return Py_BuildValue(head_dict, - "version", - FIXED_MAJOR(t->Table_Version), - FIXED_MINOR(t->Table_Version), - "fontRevision", - FIXED_MAJOR(t->Font_Revision), - FIXED_MINOR(t->Font_Revision), - "checkSumAdjustment", - t->CheckSum_Adjust, - "magicNumber", - t->Magic_Number, - "flags", - t->Flags, - "unitsPerEm", - t->Units_Per_EM, - "created", - t->Created[0], - t->Created[1], - "modified", - t->Modified[0], - t->Modified[1], - "xMin", - t->xMin, - "yMin", - t->yMin, - "xMax", - t->xMax, - "yMax", - t->yMax, - "macStyle", - t->Mac_Style, - "lowestRecPPEM", - t->Lowest_Rec_PPEM, - "fontDirectionHint", - t->Font_Direction, - "indexToLocFormat", - t->Index_To_Loc_Format, - "glyphDataFormat", - t->Glyph_Data_Format); + "version", FIXED_MAJOR(t->Table_Version), FIXED_MINOR(t->Table_Version), + "fontRevision", FIXED_MAJOR(t->Font_Revision), FIXED_MINOR(t->Font_Revision), + "checkSumAdjustment", t->CheckSum_Adjust, + "magicNumber", t->Magic_Number, + "flags", t->Flags, + "unitsPerEm", t->Units_Per_EM, + "created", t->Created[0], t->Created[1], + "modified", t->Modified[0], t->Modified[1], + "xMin", t->xMin, + "yMin", t->yMin, + "xMax", t->xMax, + "yMax", t->yMax, + "macStyle", t->Mac_Style, + "lowestRecPPEM", t->Lowest_Rec_PPEM, + "fontDirectionHint", t->Font_Direction, + "indexToLocFormat", t->Index_To_Loc_Format, + "glyphDataFormat", t->Glyph_Data_Format); } case 1: { char maxp_dict[] = @@ -1163,37 +1142,21 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:H, s:H, s:H, s:H, s:H, s:H, s:H, s:H}"; TT_MaxProfile *t = (TT_MaxProfile *)table; return Py_BuildValue(maxp_dict, - "version", - FIXED_MAJOR(t->version), - FIXED_MINOR(t->version), - "numGlyphs", - t->numGlyphs, - "maxPoints", - t->maxPoints, - "maxContours", - t->maxContours, - "maxComponentPoints", - t->maxCompositePoints, - "maxComponentContours", - t->maxCompositeContours, - "maxZones", - t->maxZones, - "maxTwilightPoints", - t->maxTwilightPoints, - "maxStorage", - t->maxStorage, - "maxFunctionDefs", - t->maxFunctionDefs, - "maxInstructionDefs", - t->maxInstructionDefs, - "maxStackElements", - t->maxStackElements, - "maxSizeOfInstructions", - t->maxSizeOfInstructions, - "maxComponentElements", - t->maxComponentElements, - "maxComponentDepth", - t->maxComponentDepth); + "version", FIXED_MAJOR(t->version), FIXED_MINOR(t->version), + "numGlyphs", t->numGlyphs, + "maxPoints", t->maxPoints, + "maxContours", t->maxContours, + "maxComponentPoints", t->maxCompositePoints, + "maxComponentContours", t->maxCompositeContours, + "maxZones", t->maxZones, + "maxTwilightPoints", t->maxTwilightPoints, + "maxStorage", t->maxStorage, + "maxFunctionDefs", t->maxFunctionDefs, + "maxInstructionDefs", t->maxInstructionDefs, + "maxStackElements", t->maxStackElements, + "maxSizeOfInstructions", t->maxSizeOfInstructions, + "maxComponentElements", t->maxComponentElements, + "maxComponentDepth", t->maxComponentDepth); } case 2: { char os_2_dict[] = @@ -1202,55 +1165,28 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:y#, s:H, s:H, s:H}"; TT_OS2 *t = (TT_OS2 *)table; return Py_BuildValue(os_2_dict, - "version", - t->version, - "xAvgCharWidth", - t->xAvgCharWidth, - "usWeightClass", - t->usWeightClass, - "usWidthClass", - t->usWidthClass, - "fsType", - t->fsType, - "ySubscriptXSize", - t->ySubscriptXSize, - "ySubscriptYSize", - t->ySubscriptYSize, - "ySubscriptXOffset", - t->ySubscriptXOffset, - "ySubscriptYOffset", - t->ySubscriptYOffset, - "ySuperscriptXSize", - t->ySuperscriptXSize, - "ySuperscriptYSize", - t->ySuperscriptYSize, - "ySuperscriptXOffset", - t->ySuperscriptXOffset, - "ySuperscriptYOffset", - t->ySuperscriptYOffset, - "yStrikeoutSize", - t->yStrikeoutSize, - "yStrikeoutPosition", - t->yStrikeoutPosition, - "sFamilyClass", - t->sFamilyClass, - "panose", - t->panose, - Py_ssize_t(10), - "ulCharRange", - t->ulUnicodeRange1, - t->ulUnicodeRange2, - t->ulUnicodeRange3, - t->ulUnicodeRange4, - "achVendID", - t->achVendID, - Py_ssize_t(4), - "fsSelection", - t->fsSelection, - "fsFirstCharIndex", - t->usFirstCharIndex, - "fsLastCharIndex", - t->usLastCharIndex); + "version", t->version, + "xAvgCharWidth", t->xAvgCharWidth, + "usWeightClass", t->usWeightClass, + "usWidthClass", t->usWidthClass, + "fsType", t->fsType, + "ySubscriptXSize", t->ySubscriptXSize, + "ySubscriptYSize", t->ySubscriptYSize, + "ySubscriptXOffset", t->ySubscriptXOffset, + "ySubscriptYOffset", t->ySubscriptYOffset, + "ySuperscriptXSize", t->ySuperscriptXSize, + "ySuperscriptYSize", t->ySuperscriptYSize, + "ySuperscriptXOffset", t->ySuperscriptXOffset, + "ySuperscriptYOffset", t->ySuperscriptYOffset, + "yStrikeoutSize", t->yStrikeoutSize, + "yStrikeoutPosition", t->yStrikeoutPosition, + "sFamilyClass", t->sFamilyClass, + "panose", t->panose, Py_ssize_t(10), + "ulCharRange", t->ulUnicodeRange1, t->ulUnicodeRange2, t->ulUnicodeRange3, t->ulUnicodeRange4, + "achVendID", t->achVendID, Py_ssize_t(4), + "fsSelection", t->fsSelection, + "fsFirstCharIndex", t->usFirstCharIndex, + "fsLastCharIndex", t->usLastCharIndex); } case 3: { char hhea_dict[] = @@ -1258,33 +1194,19 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:h, s:h, s:h, s:h, s:H}"; TT_HoriHeader *t = (TT_HoriHeader *)table; return Py_BuildValue(hhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "ascent", - t->Ascender, - "descent", - t->Descender, - "lineGap", - t->Line_Gap, - "advanceWidthMax", - t->advance_Width_Max, - "minLeftBearing", - t->min_Left_Side_Bearing, - "minRightBearing", - t->min_Right_Side_Bearing, - "xMaxExtent", - t->xMax_Extent, - "caretSlopeRise", - t->caret_Slope_Rise, - "caretSlopeRun", - t->caret_Slope_Run, - "caretOffset", - t->caret_Offset, - "metricDataFormat", - t->metric_Data_Format, - "numOfLongHorMetrics", - t->number_Of_HMetrics); + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "ascent", t->Ascender, + "descent", t->Descender, + "lineGap", t->Line_Gap, + "advanceWidthMax", t->advance_Width_Max, + "minLeftBearing", t->min_Left_Side_Bearing, + "minRightBearing", t->min_Right_Side_Bearing, + "xMaxExtent", t->xMax_Extent, + "caretSlopeRise", t->caret_Slope_Rise, + "caretSlopeRun", t->caret_Slope_Run, + "caretOffset", t->caret_Offset, + "metricDataFormat", t->metric_Data_Format, + "numOfLongHorMetrics", t->number_Of_HMetrics); } case 4: { char vhea_dict[] = @@ -1292,58 +1214,33 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:h, s:h, s:h, s:h, s:H}"; TT_VertHeader *t = (TT_VertHeader *)table; return Py_BuildValue(vhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "vertTypoAscender", - t->Ascender, - "vertTypoDescender", - t->Descender, - "vertTypoLineGap", - t->Line_Gap, - "advanceHeightMax", - t->advance_Height_Max, - "minTopSideBearing", - t->min_Top_Side_Bearing, - "minBottomSizeBearing", - t->min_Bottom_Side_Bearing, - "yMaxExtent", - t->yMax_Extent, - "caretSlopeRise", - t->caret_Slope_Rise, - "caretSlopeRun", - t->caret_Slope_Run, - "caretOffset", - t->caret_Offset, - "metricDataFormat", - t->metric_Data_Format, - "numOfLongVerMetrics", - t->number_Of_VMetrics); + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "vertTypoAscender", t->Ascender, + "vertTypoDescender", t->Descender, + "vertTypoLineGap", t->Line_Gap, + "advanceHeightMax", t->advance_Height_Max, + "minTopSideBearing", t->min_Top_Side_Bearing, + "minBottomSizeBearing", t->min_Bottom_Side_Bearing, + "yMaxExtent", t->yMax_Extent, + "caretSlopeRise", t->caret_Slope_Rise, + "caretSlopeRun", t->caret_Slope_Run, + "caretOffset", t->caret_Offset, + "metricDataFormat", t->metric_Data_Format, + "numOfLongVerMetrics", t->number_Of_VMetrics); } case 5: { char post_dict[] = "{s:(h,H), s:(h,H), s:h, s:h, s:k, s:k, s:k, s:k, s:k}"; TT_Postscript *t = (TT_Postscript *)table; return Py_BuildValue(post_dict, - "format", - FIXED_MAJOR(t->FormatType), - FIXED_MINOR(t->FormatType), - "italicAngle", - FIXED_MAJOR(t->italicAngle), - FIXED_MINOR(t->italicAngle), - "underlinePosition", - t->underlinePosition, - "underlineThickness", - t->underlineThickness, - "isFixedPitch", - t->isFixedPitch, - "minMemType42", - t->minMemType42, - "maxMemType42", - t->maxMemType42, - "minMemType1", - t->minMemType1, - "maxMemType1", - t->maxMemType1); + "format", FIXED_MAJOR(t->FormatType), FIXED_MINOR(t->FormatType), + "italicAngle", FIXED_MAJOR(t->italicAngle), FIXED_MINOR(t->italicAngle), + "underlinePosition", t->underlinePosition, + "underlineThickness", t->underlineThickness, + "isFixedPitch", t->isFixedPitch, + "minMemType42", t->minMemType42, + "maxMemType42", t->maxMemType42, + "minMemType1", t->minMemType1, + "maxMemType1", t->maxMemType1); } case 6: { char pclt_dict[] = @@ -1351,35 +1248,19 @@ static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) "s:b, s:b}"; TT_PCLT *t = (TT_PCLT *)table; return Py_BuildValue(pclt_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "fontNumber", - t->FontNumber, - "pitch", - t->Pitch, - "xHeight", - t->xHeight, - "style", - t->Style, - "typeFamily", - t->TypeFamily, - "capHeight", - t->CapHeight, - "symbolSet", - t->SymbolSet, - "typeFace", - t->TypeFace, - Py_ssize_t(16), - "characterComplement", - t->CharacterComplement, - Py_ssize_t(8), - "strokeWeight", - t->StrokeWeight, - "widthType", - t->WidthType, - "serifStyle", - t->SerifStyle); + "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), + "fontNumber", t->FontNumber, + "pitch", t->Pitch, + "xHeight", t->xHeight, + "style", t->Style, + "typeFamily", t->TypeFamily, + "capHeight", t->CapHeight, + "symbolSet", t->SymbolSet, + "typeFace", t->TypeFace, Py_ssize_t(16), + "characterComplement", t->CharacterComplement, Py_ssize_t(8), + "strokeWeight", t->StrokeWeight, + "widthType", t->WidthType, + "serifStyle", t->SerifStyle); } default: Py_RETURN_NONE; From 7173482aa2fd07ae8b733261010a7339996d241c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 05:51:47 -0400 Subject: [PATCH 0042/1120] TYP: Fix type of Glyph.bbox It is in fact a read-only property, not a method. --- lib/matplotlib/ft2font.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index 8a99f7a4f2f7..6a0716e993a5 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -249,4 +249,5 @@ class Glyph: vertBearingY: int vertAdvance: int + @property def bbox(self) -> tuple[int, int, int, int]: ... From a7307af66f891f15fd6c166137f5016cab4e473c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 18 Aug 2023 01:53:59 -0400 Subject: [PATCH 0043/1120] Add ninja to Cygwin builder Since there are no contourpy wheels, it must be built from source, and pre-packaged ninja is better than trying to build from sdist. --- .github/workflows/cygwin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 652a09e45953..67b1af162914 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -86,7 +86,7 @@ jobs: with: packages: >- ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel - libgeos-devel libQt5Core-devel pkgconf libglib2.0-devel + libgeos-devel libQt5Core-devel pkgconf libglib2.0-devel ninja noto-cjk-fonts python3${{ matrix.python-minor-version }}-devel python3${{ matrix.python-minor-version }}-pip From e5d01aa4774f03a41736d863710802ee28791afb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 18 Aug 2023 03:28:58 -0400 Subject: [PATCH 0044/1120] Fix size inferral when using cairocffi With that package, rectangles appear to just be tuples, not named tuples like with pycairo. Fixes #26523 --- lib/matplotlib/backends/backend_cairo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index c733f3f9d3d7..421d857b0a03 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -95,7 +95,7 @@ def set_context(self, ctx): rect, *rest = ctx.copy_clip_rectangle_list() if rest: raise TypeError("Cannot infer surface size") - size = rect.width, rect.height + _, _, *size = rect ctx.restore() self.gc.ctx = ctx self.width, self.height = size From 3dc34576a0e70981860146a712bcc0a91812afdf Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Fri, 18 Aug 2023 15:23:57 +0530 Subject: [PATCH 0045/1120] update changes --- lib/matplotlib/_mathtext.py | 3 +-- lib/matplotlib/tests/test_mathtext.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 73cb0ae1189d..24aab9046e27 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2032,8 +2032,7 @@ def main(self, s, loc, toks): return [Hlist(toks)] def math_string(self, s, loc, toks): - return self._math_expression.parseString(toks[0][1:-1], - parseAll=True) + return self._math_expression.parseString(toks[0][1:-1], parseAll=True) def math(self, s, loc, toks): hlist = Hlist(toks) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 133469b00d8e..5a9e8a8b9264 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -349,7 +349,7 @@ def test_fontinfo(): 'double superscript', 'double subscript', 'super on sub without braces', - 'unclosed group' + 'unclosed group', ] ) def test_mathtext_exceptions(math, msg): From 7ea04e7d96d69b509ec60272242e5ad3f3e5de7d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 18 Aug 2023 22:44:54 -0400 Subject: [PATCH 0046/1120] Install contourpy without build isolation on Cygwin --- .github/workflows/cygwin.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 67b1af162914..b4b30b57dab1 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -177,7 +177,10 @@ jobs: python -m pip install --upgrade pip 'setuptools<60' wheel python -m pip install kiwisolver 'numpy!=1.21.*' pillow importlib_resources grep -v -F -e psutil requirements/testing/all.txt >requirements_test.txt - python -m pip install --upgrade 'contourpy>=1.0.1' cycler fonttools \ + python -m pip install meson-python pybind11 + export PATH="/usr/local/bin:$PATH" + python -m pip install --no-build-isolation 'contourpy>=1.0.1' + python -m pip install --upgrade cycler fonttools \ packaging pyparsing python-dateutil setuptools-scm \ -r requirements_test.txt sphinx ipython python -m pip install --upgrade pycairo 'cairocffi>=0.8' PyGObject && From b0d66a080a85aec59febfe7f98ed8af98369a17f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 19 Aug 2023 04:22:43 -0400 Subject: [PATCH 0047/1120] Remove NumPy abs overrides from pylab NumPy added abs in 1.26, which we don't want when we import * from it. Fixes #26553 --- lib/matplotlib/pylab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/pylab.py b/lib/matplotlib/pylab.py index 684021b2e977..77eb6506d87f 100644 --- a/lib/matplotlib/pylab.py +++ b/lib/matplotlib/pylab.py @@ -59,6 +59,7 @@ # "from numpy.random import *" above bytes = __import__("builtins").bytes # We also don't want the numpy version of these functions +abs = __import__("builtins").abs max = __import__("builtins").max min = __import__("builtins").min round = __import__("builtins").round From ef5689faafabf5aba9ea71010a0ac1f79e6ae326 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 21 Aug 2023 12:59:08 -0500 Subject: [PATCH 0048/1120] MAINT: Numpy 2.0 deprecations for row_stack and in1d Part of #26422, xref numpy/numpy#24445 --- lib/matplotlib/axes/_axes.py | 6 +++--- lib/matplotlib/axes/_base.py | 2 +- lib/matplotlib/contour.py | 6 +++--- lib/matplotlib/projections/polar.py | 2 +- lib/matplotlib/stackplot.py | 2 +- lib/matplotlib/tests/test_triangulation.py | 2 +- lib/mpl_toolkits/mplot3d/art3d.py | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 036d4f284fe5..4bd1e83d2f5a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3674,7 +3674,7 @@ def apply_mask(arrays, mask): # elow, ehigh = np.broadcast_to(...) # return dep - elow * ~lolims, dep + ehigh * ~uplims # except that broadcast_to would strip units. - low, high = dep + np.row_stack([-(1 - lolims), 1 - uplims]) * err + low, high = dep + np.vstack([-(1 - lolims), 1 - uplims]) * err barcols.append(lines_func( *apply_mask([indep, low, high], everymask), **eb_lines_style)) if self.name == "polar" and dep_axis == "x": @@ -5471,8 +5471,8 @@ def get_interp_point(idx): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - pts = np.row_stack([np.column_stack([ind[where], dep1[where]]), - np.column_stack([ind[where], dep2[where]])]) + pts = np.vstack([np.column_stack([ind[where], dep1[where]]), + np.column_stack([ind[where], dep2[where]])]) if ind_dir == "y": pts = pts[:, ::-1] diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3796d9bbe508..b55ce7e99886 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2403,7 +2403,7 @@ def _update_patch_limits(self, patch): vertices.append(curve([0, *dzeros, 1])) if len(vertices): - vertices = np.row_stack(vertices) + vertices = np.vstack(vertices) patch_trf = patch.get_transform() updatex, updatey = patch_trf.contains_branch_seperately(self.transData) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 79f66b896131..f148d35618dd 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -511,13 +511,13 @@ def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): if closed: # This will remove contour if shorter than label if all(i != -1 for i in I): - nlc.append(np.row_stack([xy2, lc[I[1]:I[0]+1], xy1])) + nlc.append(np.vstack([xy2, lc[I[1]:I[0]+1], xy1])) else: # These will remove pieces of contour if they have length zero if I[0] != -1: - nlc.append(np.row_stack([lc[:I[0]+1], xy1])) + nlc.append(np.vstack([lc[:I[0]+1], xy1])) if I[1] != -1: - nlc.append(np.row_stack([xy2, lc[I[1]:]])) + nlc.append(np.vstack([xy2, lc[I[1]:]])) # The current implementation removes contours completely # covered by labels. Uncomment line below to keep diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index e4a99a01a774..0bff320e5728 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -121,7 +121,7 @@ def transform_path_non_affine(self, path): codes.extend(arc.codes[1:]) else: # Interpolate. trs = cbook.simple_linear_interpolation( - np.row_stack([(last_t, last_r), trs]), + np.vstack([(last_t, last_r), trs]), path._interpolation_steps)[1:] xys.extend(self.transform_non_affine(trs)) codes.extend([Path.LINETO] * len(trs)) diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index c97a21e029f9..2629593683e5 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -68,7 +68,7 @@ def stackplot(axes, x, *args, stacked area plot. """ - y = np.row_stack(args) + y = np.vstack(args) labels = iter(labels) if colors is not None: diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 682a0fbe4b75..14c591abd4e5 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -1035,7 +1035,7 @@ def test_trirefine(): x_verif, y_verif = np.meshgrid(x_verif, x_verif) x_verif = x_verif.ravel() y_verif = y_verif.ravel() - ind1d = np.in1d(np.around(x_verif*(2.5+y_verif), 8), + ind1d = np.isin(np.around(x_verif*(2.5+y_verif), 8), np.around(x_refi*(2.5+y_refi), 8)) assert_array_equal(ind1d, True) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index a41485f62529..ac6e841f5019 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -942,8 +942,8 @@ def set_zsort(self, zsort): def get_vector(self, segments3d): """Optimize points for projection.""" if len(segments3d): - xs, ys, zs = np.row_stack(segments3d).T - else: # row_stack can't stack zero arrays. + xs, ys, zs = np.vstack(segments3d).T + else: # vstack can't stack zero arrays. xs, ys, zs = [], [], [] ones = np.ones(len(xs)) self._vec = np.array([xs, ys, zs, ones]) From 813e41a89f96dcd040a1f49726f8cc5659da6acb Mon Sep 17 00:00:00 2001 From: Prajwal Agrawal <61898798+kidkoder432@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:26:58 -0700 Subject: [PATCH 0049/1120] Boxplot fix median line extending past box boundaries (#26462) Closes #19409. - Set the default capstyles (solid and dashed) for median and mean lines in boxplots to "butt". I also modieifed the behaviror for setting the capstyle for median lines to obey the user's preference instead of overriding it. - The boxplot PDFs were using the wrong capstyle, so I modified the references to use the butt captyle. - Fixed a small typo in `bxp()` - Added a new test for the median line (from PR #23335) Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/axes/_axes.py | 13 ++++++++++++- .../baseline_images/test_axes/boxplot.pdf | Bin 5781 -> 5702 bytes .../boxplot_autorange_false_whiskers.png | Bin 5730 -> 5784 bytes .../boxplot_autorange_true_whiskers.png | Bin 5391 -> 5447 bytes .../test_axes/boxplot_custom_capwidths.png | Bin 2855 -> 2837 bytes .../boxplot_mod_artists_after_plotting.png | Bin 1255 -> 1317 bytes .../test_axes/boxplot_no_inverted_whisker.png | Bin 1392 -> 1440 bytes .../test_axes/boxplot_rc_parameters.pdf | Bin 3218 -> 3076 bytes .../test_axes/boxplot_rc_parameters.png | Bin 7709 -> 7768 bytes .../baseline_images/test_axes/boxplot_sym.png | Bin 1254 -> 1304 bytes .../test_axes/boxplot_sym2.png | Bin 5318 -> 5318 bytes .../test_axes/boxplot_with_CIarray.png | Bin 2194 -> 2246 bytes .../test_axes/bxp_baseline.png | Bin 2681 -> 2735 bytes .../test_axes/bxp_custom_capwidth.png | Bin 2765 -> 2735 bytes .../test_axes/bxp_custom_capwidths.png | Bin 2826 -> 2797 bytes .../test_axes/bxp_custombox.png | Bin 2676 -> 2729 bytes .../test_axes/bxp_customcap.png | Bin 2055 -> 2109 bytes .../test_axes/bxp_customoutlier.png | Bin 1989 -> 2042 bytes .../test_axes/bxp_custompatchartist.png | Bin 13592 -> 13548 bytes .../test_axes/bxp_custompositions.png | Bin 1968 -> 2019 bytes .../test_axes/bxp_customwhisker.png | Bin 2073 -> 2126 bytes .../test_axes/bxp_customwidths.png | Bin 2078 -> 2133 bytes .../test_axes/bxp_horizontal.png | Bin 2408 -> 2444 bytes .../test_axes/bxp_no_flier_stats.png | Bin 1403 -> 1453 bytes .../baseline_images/test_axes/bxp_nobox.png | Bin 1866 -> 1920 bytes .../baseline_images/test_axes/bxp_nocaps.png | Bin 1877 -> 1932 bytes .../test_axes/bxp_patchartist.png | Bin 2065 -> 2037 bytes .../test_axes/bxp_percentilewhis.png | Bin 2894 -> 2945 bytes .../test_axes/bxp_rangewhis.png | Bin 2135 -> 2187 bytes .../test_axes/bxp_scalarwidth.png | Bin 1960 -> 2013 bytes .../test_axes/bxp_with_xlabels.png | Bin 2721 -> 2774 bytes .../test_axes/bxp_with_ylabels.png | Bin 3072 -> 3108 bytes .../test_axes/bxp_withmean_custompoint.png | Bin 2226 -> 2280 bytes .../test_axes/bxp_withmean_line.png | Bin 2158 -> 2210 bytes .../test_axes/bxp_withmean_point.png | Bin 2163 -> 2218 bytes .../test_axes/bxp_withnotch.png | Bin 4728 -> 4782 bytes lib/matplotlib/tests/test_axes.py | 9 +++++++++ 37 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 036d4f284fe5..4d5907503f09 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4095,7 +4095,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, capwidths : float or array-like, default: None Either a scalar or a vector and sets the width of each cap. - The default is ``0.5*(with of the box)``, see *widths*. + The default is ``0.5*(width of the box)``, see *widths*. vert : bool, default: True If `True` (default), makes the boxes vertical. @@ -4147,6 +4147,17 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, -------- .. plot:: gallery/statistics/bxp.py """ + # Clamp median line to edge of box by default. + medianprops = { + "solid_capstyle": "butt", + "dash_capstyle": "butt", + **(medianprops or {}), + } + meanprops = { + "solid_capstyle": "butt", + "dash_capstyle": "butt", + **(meanprops or {}), + } # lists of artists to be output whiskers = [] diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf index 55264db5d1f9c8cbc0452970dbcbfa75ce213e00..c34dddea28c99e48841bb810eae77351088161de 100644 GIT binary patch delta 3396 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 Dv$+8# literal 5781 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_false_whiskers.png index 05ae97248c8f84608ca5131a6e3a56564b54932b..14675de05163b4883150b10b6b128dfa9331dcd1 100644 GIT binary patch literal 5784 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 literal 5730 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_true_whiskers.png index 2ef5936b2db7f4e3afbbba74803f4d9bdc90af71..c6f11f0411aefe16a380107a225bb497740ddc5e 100644 GIT binary patch literal 5447 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 Zq8CLIzsrp7C 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 literal 2855 zcmcJRXH-+!7RPVsQpDknFd}VK93@CG5HKb*sSZu5h=d{_5F`eYAcQ0!gNg#NP%Olt z$bgiH^eP}Kh-j2Z89<7m8A=2K0RlMJw`R?nd2hMae0cA}z4zR8?q27f|K9ueKgrIH zHgeL+(f|O++1XmU000CW0?kqq;C*_wF&KP!;;oM3U9rLVX#dC{z`-AXHXMr&4-L>D z21Q1NVk2NWa2-9Jz#yW&9!wv4`p}_ZJ!5^C9vr3roy(s6;A?wFpw>1|^Z5`Q zG^^6;T6V<`N6#3)AI1EI%@5GXmea2P(DMO*#wM>g*C?^0R4Jo-YhbOjQL?NG|NaNl zK&7oj*b&0_;cZ)%E*U>Yoq!i-hLHp#j;hAKy?`Q&Z62eD!QG9P3=IuKC*RP+!opk*-ja>oQ(mL^+iqZI1$NkkKqQ8RhuhQX^vll! z%xQs&y}L-7%;`FFdR$0|?R9C2s+m>>80Ds|=(7DUP1v+@JDPm zGG@W4Ix8z{X@x^05fH%i$Us{>ay4PS74gs0`|ZL2_ck+A{Dxtw+lK4gA=U1BSKN<6 zK`ZH^-*#{p z&$d)nSCfzqxDn02l;7V{{Q?m$JJ=B8%4-zdeLDL22rp`NA&72_`}l;)pwqiYTQ62d z^U%6B2II{MqJ_o9aZ@aH;sFm2j|xn$jw&kl{i}ItX-IoK#~7@@wUI>mG86bM`>^2P zy@DeH zb#iO+@-)f9*=p$|3pIyec4udbwT(?V;swEHX?dA>#v{>mMInA}F2ZWpcLV|}$BpB*G>?rvAo7MvCK*(8)5h|h2Tuo-!-dPUYcu7niqg^? z!7$Y~nm0tAA`kTU^IPRj_g%A{damTl#~t0-(b4g~zrV1q4xx&`+Zsg-o|_^!t#P4r z6v=}Tw~&k`%^IrbYs)^-7VEDFy>YTPgJY}(g)VZ^@7m_GZ-cV;lz6)%vac$ttv3&N zuVI&7Csi~w6nAyGF!C!-MMO9{A?JUo+L+GfvuY)W@+ot-J>U*6aVqX^ZYxOt%|%Th zH8nLXBBG~}B;a?#ShiXXB`uVVB~Q8u`Glmr8i)hZ82QVl{3k4jgCNe_93l&RG9ir5(GyWx#TvkzmA8wM{y0tDb zF_Ds58UcgB<`P6D2MK0vD?3yCPfV3e1-U~T%*G^n<~@BY*0 z|J~XgU6CL^9PZd@*4^%<>E3b^BG{!iX)5!6TJ;La&+5{=eSF+~e0+0eBJFFRZaHubp}fYE%K+|OxOXG)p4Z_5LlRM z-IFyrKWFL0;Ix39BX)pFo7aFv(W6IB-c{l6dVQ%SAn`~zkZS2S0BvpUkjTi$*`^SK z!k-v%F4ytn9ork%xJCWtr&$a*Ub_NBPeA$&0>Eao*H$MPy%9}|Zw_O=_twBS%Fr zk=3$oQ$vlU{)U)EGO6tGHmMp0xEoUQH-?IbdNS8P5xul}zwk_CuT^R%n3Nt>b8`lBZT_r9e;AYxaH zAVwiXqQ@kQk;O4Krv(hHtkl%i)fvFyiu`@zi9q<<_4NiFbVEUHZEcQ?J~k%C>nma~ z7@64g^DP!Ra)pJ3F7EE`Z%>&~8y?09=6p~ae6~5wMQ=q8u-XlpdtRQEI%UQGZSME7 zBBkcJxw%{(&yz$Vz0JKDJ<++t!O01F%~lL`v}ME<^lysjKR>Z;NLD>Fw}W&EoWWx$ NU}xoM$+Yl0|2x<90?hyb diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_mod_artists_after_plotting.png index 1a7553254c60c20661efbe3c77f7354dce5f5588..fc91c7911723c0ee3c9f688177e7c7b37a1e0f99 100644 GIT binary patch delta 848 zcmV-W1F!t&38e~krcga)__EJngh(b0E4S3C*HIPBt^dT|^ z+{GH)$;$;n4H3>_uPH&8LO_`cB{ZS9M6c`r^Q=O;XVFhb7oqpTdc9s35b2xN0|4y^ zlQ9D;lMn(Df7n*bi(g$`)nc)zot>SQW5AuVTCHk09M)hk`0Cecx&bT}i&`$1Ek}Sm zXSrO~VzJoBwVG`JXJ==%TrR8Yy4v60Z#4!)a}NN5HQNAu>O*@r00<@kR8>{q_cfVJ zs_*;Oqp(2&K=plJv)QbUj*ebolfiE;Gp$LY={6*UDwrUG^&%6lNyai)pcF#k=T%?KLzIQld1tU5I8$K z^Vd_*m<<4eITrwe*^?myJChIs29qxW9)J1U%X<6A+kbrQ{QA6}{P5(n&wcdWqk8(| z)2}re4G2J(FTH_}Umw?tpI>Zn8d?*8d*<1%&+6vG&8?n$etllYua9r_-0iLHdj8Y% zTRoR{1mK>z`Eb+ZQ~b8!pe;86A(#!|p1FK?+2qss^4;aeugQNW0Qbz}?;raLz&^j` zD*yx&00<@k5KI6dn45t2%&XsDeev=0>+^d5=lff|_QP)VFZBc`@UwgSsfi6)oeBsfgl1v zot~c7cs#DXy}cTb$2FhNTaUzs2mp0>cv#cvv@R|#YC4_P!NEc6k=PIcpt`QB(P&gB zCnq%;jjHRq)+4bY&HfaWyQS+oZ~Q>c1%P0_0zj~)=K`!&D_a13qVN0KH{IRc$hDf@ z0~ij6EicdC{1syN0ovQ$-QC!GqiFyJgFy`jgO(%k2`4sXd9Yru*R6*D+HeyPlW+qz a7W)XDQP0J>S}ec-000070A6vyF{VkpLh&e0O^;K6VgPxW9e#q@({)6js|%vl2&q)i_pW58Xk z!JV{R5L6IB7JDwim_k6E3L!M1I7F}G|MRRux@XZ(M;D>@(FbO;*%T1zhmqhze@@Gb zUtL|*a=EO%y}g!Wz`b&Fb5nWvexT$%Msw-S+Cc%TrRhAt!5j* z<>h6q*X!!Ku8xn7Ta5wH+yj7M%{Bm^`p}*Y0D=hsRaMpZeN85l>ifR+C~T1cP<`Lm zY&NUY)6<&GW+D(o0H}+LiyDu|K6P+#P~-8q=JR>$k=POepoYU?O{ddZtyVRiPV404 zr1eN_i2zVt*VSk=s`K;n8jVKPbzSR`*pj9{1?K*f-~luQxV*gdlNbVPfAzF6{`w19SV~w#ldXUBf|JZURCu8^8neHEaCWUYGBW z06Z{9PmX*A;D7TK0D=hsHv|&^2qpj!%uT=t=JlVizxep#dQtEHet)Oee*EL(&;C?+ zcW3ubuSr`1@W8zN?QQE3_>TArla2$GlWzkNlbHiOe^pi0_kB$!6aUf_MgXY3?`t-j z)#>SJ&1N$Z2qFN~#l=O9$KyIUIH>V>T=V(7^+;@q08qo>u%^>#tyZg=PN#Kpa?*Mv zwnPA^uIp+v8rAvvd5uP+>bkD=NNh>7KLzD}>AKDvKag_)AegTJ5UlCB05>-`wgC7< z-}kk@How1>Yc;(GaCmsw^74G--w?YG(BA(3{?^_bO#?6(3~DeKv>bs?II$_qqs?Zs pX*~qchMRzs@c|%{a03<=*gr_N&zKp6t4{y`002ovPDHLkV1k66eYOAq diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png index ec7107aea08054c5b134f4975eb5611ce26195bf..803db84f2dc241c328f176b79e0117310ba07973 100644 GIT binary patch literal 1440 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54sfsm$=7f9rU5C-64!{5;QX|b^2DN42H(Vz zf}H%4oXjMJvecsD%=|n@1Z^(_~3=FKio-U3d6?5L+HO!6Zp{Q323*R|KB zwlzOCJT-aX_w&ccWLX)RD=9`l-zA&IGOoG)`c{sa?sA6mb7`Abeyy@S72zAs@c#b( z_3K`*djEUc<%WoJ3R1-sU*epwRr{P}a!xqho(@46P8q~g1u^=s+rhl+8V{TYAl`|j(IaYmAXC0nYY{QW(UYfX6%ynB}i zR4`8$9B|L}Gb+wtZ*nk*hM6|Q*?}XCmEm9w7sJ6dz%Vst0CRysZOPJ-5XK;IMp1!f zGZSj~Kg)AiwJJ-Z&C1&PvpMJEJO>~2Z*_h)a6xN`MssIFMIm6cVNt*YB2pt-Mi&GK7b zT2~i$`Q?^f>#~8Om)=x;&?;xXry6^`@-h1+RwTE4wqf9SHZeiqj2J`HO(us8%i>uc z-LC%kr}FNdJ2hXQufDpid6RTwH8rWAo?H`LeRIS4X?WxBuF^<+2IG?6b$T z9!T%rxpU>^%a<=6pL_oK;{KEWpYs5H&)fj^{dZuPh%&Ggvq6FfEiD$CE3klY_P6&3 zYVZHnGB9}t)Vot!pvjcCVK?JK-SaGa((C>{jlDX5|8~jg@_h_FG@WI%JrfK*?KSghI>YR!Fe4pkCz@k`p$|+Hpxg&dI8b+?XURLf90!px z)SJk*JZn@CIAaV}ydjpJ)hgLTvOXxp$!E6fk813j=6~Y*^Ob&@XTQEg|5?83UfhvJ=dC4;(z`c>3Yb zBlU{!cCGv9bK~}H>9>0?nlMa0xkPQU=V{FkGxxoZ_HPvYyX9`)_O_xBN3Q?? literal 1392 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54sfsm$=7f9rU5CIVkgfK4j`!ENa+CbISV`@ ziy0W$)IgYVzOa=t0|Tp&r;B4q#hkZy4YQ-eWsW_p_tKa#!BT;V(b;Cky`#5f-<-X$ zb-v^5X${BH{M?<{vM;xv2KC$en7;hT~)4>UrEY9MkS}hZF{EGTtf3FNvp3m%N zf6j8!`<>hG=51$n`L*&ZOG6n8LlY+hi!cL+8iRljgTf332Lr~21SW=qL`l4EQvNNTidmX5>fuGf~LF; z{tS8Ti4s{mW3GMtSg1SQTaY7()nV@Z`PXyIR`1=rch=do(!#=vRbD+u6ctKKNAA`*P4bx+6=2+@0#VeytJ$=?DESkyVhmfs=8hJ((Q0ZF7|rmWA;r< zFmIc3A9#IzeeFG-Uej2{nAq5@GIEbhe~T=5UwxmY<=7lXhYiaZE55&r1-drwGJ{Rs zpB+y>2%HxK2ju7b4FYGF9TjHi!UFY-q5{iipxJh;4GCck0%s%{z+7Mm%P=_^Xftp; z6A);MWJC@2VsnMiP*Wb~x%1}zn%z=pt^j1~PCspBXSeQYktUPlEY=0@e^>qblDYcf z&m%v7R-R4UeCzh@)i-m(#KpzeE&Z|$KHi>n+%F4-Yn(90f9BHf!2Z6x~3JmmAc!!tc zAPCR;Yh7^v`#vt7UXb2*Y%K}C%n$M$GEN&azMpY;{{MBqK7TiR{As^3L;0Db_WQ5o z@Be4`Z%6&v*V`|;KaI9zX#pi^8v&N4N;P0~0Hb0XFe-pi2J&n)#09_v4^jt;BWRRC zazUli1=-K9uCA8eU$!;s>cN8td*gO*-dy~+Fw>pk-3;^mdv|_p{rq!}pP%2p$2mLR z0z+xWZbn5Aj@ZobrS|;;4yj^e0hVG84vaj--sE6#9%v_;V}N1329g!qfvJfL?k@EF zxl>x82^mAZiJWu6X)zs`&_JGq#Nmy<*Mt*d^yBxf*`9yD?qmAutE--U{uv(s`^m}4 zRo~v+?4KpYYr@;0d*H{O#-CH19YApc3lU#_MtR$+CkGdNn3*WyX04Df)$sT4-)V<5)Qrry)-(}v=|{}Q zl-%0EV#E^4rKyk^OGe|TU5gemDwm43Qd^6sXP++nKF|A}&-*#&Jm;LxA16;QMfdC= z1kAk~q6xrVm`oOj4S)y$0r>C}Ax01+{{qCE_X~3x4IO)Jx%{6@^GDYXV{j)ol4%XI zAu`g13Ih{sQzev`(mQ-AkGs@*@BDdXyQ|nT*GyqT_{v;oKaWSbf4tp(`yLAM(EOv7 z$I6p7TxRN*B@dU(ae*>LtPdwrhc^ExB{IP6-MG%>k} z*NpsiwupG_WNr|9?sD)?XOs=T5EL~1(q0o3+X2gxVD7#Xa7<_$r)XbvRAe$T@Ny1z zr&L#-%V<^db`p}Yy9^K{9PF$658+Qw7w9E>mq7OEA6Wjy*-DWo8 zeJe{*PcA2IhNa#kxRv4=9ghcwO^P^#Q+E2x2P~$pbSrP0FAQ!OHCMX{&8#-DV{nb1 zP5qy(zZ&D|p8fAuCmc zMK7F6yvrja6?f)x+7^$aT=<<6q{&V($uoDmP^nt96CdlF zjFbS6W-pG7PoCD=&>K{KYH9f3+v9b9hVUr!;sUd_4(bADxWz8H0_haBA>=GXbwmUx z%rWPb^}`$8k>fSMAxSk?XD;8@BHl(u=RYv5_2Pu6SQ1H!hU9rXuMiKZ?9b#Xjffx< zSH6=^1jy)3>2iA0Y!RaoJ$?oqnT;5s`BxCrwv5mYmJwA*tG{w$8fiqt=nUsm&!P4F z7#|y(&>q>$Yy_e3$>H;<3XA7C=JhUx_sh=pXy|7uq~UCRV^PW2LQ9Fh*SFLPo)SL0 zuwHuWx0EhZ#_S-g(U1b=t9{>L7(QC3y2Io}gSVTul}F2qYMp%7q?RQiVcNaFL0^mJ zD!%Brc%vQ@J&5yq*b5n|@;Kcd{N&QbArL3)@XUS;5CDDO8F-ih0sV|BvGQ9? zs=&hD@Ly~g3)x}yH2u%~+3~AyrgY{N$BLTc{pkrA1~1i*Ms>RiK?Y8(cb{W zjI7aIcHqK_DxB$C`lye=?lfUn=y6QtKF{AjT`Bc zW;qbWgmknXRjQV7K)YsJN#EX0!5ukwe>iz#JQEZyavMkkFaF|{%4_yVym`K~*w&!) z69%%?aJe6zX8&twqVmFGigSOu-(D2vHeE%A4q|A8Lw$@#bAFNt%AwGs` z!P=Z)T5vC)j1+epb-sx-yiHUMqRof))coa5xHX!M%sJu3YrmUqX8)sKYmCd0-d{dT?e<0x#Yd}w|K?L*!1Zi00N=-o zVR8Tvz+(R?YWu#ZlhX%md~_IH9RYx7KA?f%**dHStsQ^~sILPAXpdg)_EXM&wa_P= zgZ*c5NCe`uI3xnOD#^#W2n2e)E`LB{^ER7R1wM9z_9z7ZzJ_T4=G!8VCWXZHHCcE#9?!&(VTT@kZ3zQbr=lqMNt16 Dl=n7f delta 2151 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Π-LQXI<9aV` zpz7K-iF|i4%YpLZuo-ulgr~AJ+znKA6+yf}pIiMW8b4d=hTeA|m*Ae!rwm*rq5RYH zLrXkE_jssuQ&CZo4GxqUQECu5xT(3>4~_l>lo@qC%U&O z%r%(`rAsw#{Cn@UK|9X)3r7BwXAL=u;(t5WpY2zlH2@UETpo1#mZ`H|zu-b0{ z$8w6@TIed4dRLGMn0n%P>t5t`SZV_J&zAfbCNz}#S2#38E(A#uwLCirYRU&)Gcz+& zv?L)<+CiB1u~!{mzv>7mmxi!s{uQ3Yf&K72CGBK+-?KhM`8LlS7V?h#IETy6yj%Ls z6=L7L*jPV67=yGGAk?wLpDbbhp9q(K7K7^{bmC8vB5{0-gweNdF~8EpFljCSBBOsE zcf6}Fe1vA&8!1R3r1SdEs*}RFHA|DO7F-8+{)$c+H}PcuYWXEW(7;O)MO=%}vLz|mrhu@`el6SlBoOjkBt>!exv0Km{~~`$J+Ub` zFPPow;nux7Wx~Hj-%p~4DC<5yBjS`r^76e2aDv3zhd2#tNq}o7CnwtgDzeT-?=kE} zV9@9Pqedso42`utqucdN`2-h!3WG2FUNB}jsahYcI;KMPjEz&kGyVd0-QZPuKTgBH zSv7J=HOm3+CX=7I5cb7d-2j4yCH_myxVnGLcWg0&wHP5q??Vu(10d*ZoiwOJ83n5X zBS=8T*R|-40-3zW&UiSjo#}VO}y(F(#8O~w&q)913Sc0zL9Jl`}_o(Ohe*bV<_gbCxxDRgf$h-HPv-dvx zC7iLdmfXB=GXMaRHYa~L3jjjV000>j-vrL+by6ANNd$4i=DaxgiV*ks3H)0k;N&F) z07zX~|A9O+e|iG|6bLpy96ukD#iGIQXN_Od9;6qr?z@+i9tzy&RqK8lTkdmo{O6;) zK3a9iA3S*LluF0(_}H@vYNiJ*k9C$mD-n4eBXu#i>>%gPiLU5<4?aH|w)l<}zvt78 z^~qi8F^WZq6Sqgo@OHx;Q@;DcuX>F~q=(R+B-XCEu?NVii9z)KjL2z)R?~JkeLT`D zZiOFZ4gkM=PenoiAYU7@9eB9+|A)WXgY;KWnWB`|)>hB3Frxzp4#@7?=UpWaz;=K; zcg@R7S6@GMXeg8SMufOKJv-a(Y4?q)tpH3mFE3BW%xnNDo46ftfO&YRkOn0_`s3va z;>zZiMo8-B8S>`YQ~j8l5e1Tu8wGiR=-okIKvvU`M!4nBnqPCH2y}SWPl+!mtZPW* z5nWwfp+U8vH9o0Yz;%u*Z_h)~4#%}Y zvT5q+DQq)Uii)K!4tP-d8Pr9_7Ouxq&UDKo>v%+iB-BAdGhR^8yvovqRXnzj*f0CZ z4E*fNSkgDL5w$D_osXHVkz73|rJ{%x><5u!6}R1+(1O5Z6-w9^R1wQ}rCO0x><&FI zn!kw;^%PQeelt)@>cALmOTAOqQ^}Gw3Uptb_WSi9Om6~yP#PH8IbEa6%u%qZ3uu35 zGyKH;_DXFEHPE4S0n+@G@VtdWB&ya-2a+PW+r|rmX&IHM<$e$H0n&3h-H51w)Q7@v zuZ(oj&yQ&i?he@V7lpM&I0^D@W3Ustbj!6c&6PdOvka`JZF&g-92LWXw86ppSXl)H903eLo~;nz zsN)6Et`Ghs+{Z+o%CS68dX%g--mdP1SA&OGboT%+ZBXQ4O_(jww#&N=JTkRnIo@pc9{#@yLSUVmZ)=ysxTn> zs>rdggS@c#z4_^2^|e_gQZOo*Gn;I%@r4FG;?YGXQL{265{5KQgE|Q1>x2I+HTueq zx}|rF=TA4$%@;nxTsgjV)P?O`r2~61 zZ*T$pkc8Xn)thU41W6E%;kxD2%n_vO2Ido}!E|Qy6+4FM_|ds@=}>XGrXx` zVE5`6%Z+zgF%t(xpAIt0m_qjEUWFCU8)$k+Dc3v>7ev_A&Aho~8@FOph>H+p*FXMw zYh9Ee`>_G~kkcjy)5vM+YGl=Ty8+i4pr;7p?o2^$r@hAew@$w-xEw$$T|*erSLYo$ zGdL1KkX(PW^Ennha1zakWXn;fKlxF{*pz`6uLe&&B3$`KJtjXo_M}f=nMNpwROxYD zOV%u>#SgmDP&r*Slx5WO*ScN=&UQXOM-rCOoGvHK+ZqOXTbs?q49Oe)l4Xv%P`<6* zlVeTDlp^rq=;WFfenGWXXp`|CS1!{YOs=hU^g7e!j2zjlIPrW%zT~~DQiGe9q^=4d z4fdZ2y#8%v5fZT!z=<`lsNk9NktGN(G$S`&@k6TtJ*&ZH(QP`4$CYYaz6cMs<&aU4 zKk^#oF!4&R&I&|)H(e96OtDe+4Wi)H9b6vNEVn`(I4r?(4z>W$*XK(%Uz11=bDhg% z=;mCa`dM3z5pe10?}yqW)TC=&yS!aiq6$Oa;oZ0t0xru87cnSI8cEj9g~2+(c-&PT z++DOKRJgB1H3Mrqu++#<5?rw)cwgP0k*z4-i}J9p^_u{#5zdb3F0OxjqzNOYB=0QvT!==kaqAcG^z7AEx(a>Cit|?5a6Y4*(UEJR z&GV@fEYwGoMDhe#hI9R*=GFW~%3Ozql&afWnA-T5PUw(Wb&x7SFd3UL)F>zBzQ(6Z zm2;;qVF@w^JL#IS?f_1-Mnz0Yv4s{)ry*rEEeYl!o`itVv5b4>yeWLmd-Ek&70ur+ z+a$E#ZF%?}U2uHH-kd+b8d*(Wi9PpJD=PbZHLiwR8(S(G^PrnvRiAo@97Sm_s9pS2A2~kO zC8%S|$=?@DJldlE*gv>PQ#60uM#_YUPMsJ4%KdEHatIZXw7}TWj4f%AyiIU&k@(2S z7qF5spJk%Q#?UA}`CjumDYNv|{v1_2wWNk1>UeS^2_ONMCNcWa=Bo`1EM30F@-3V1 z%2^>xndy7WrkWgI_2LT#OWsRJRBYw9>SPc^iMwY-UUU05{ITuaVXF z3{3YrVc2VedldRoW3v)=zM5cs6w#?JO8mftr`E5sRJI>n>NbZ8CX%&K)j0wtJV!Zd z62D1%%qB{c)Tts(XVxjLExnN#CaGiG1f!mNwrXYy;yiyW64LDIR52jT2VSUQLSHUm zqj#^1$RgRVz8&=u(t?8NeRNZRsJz9QCLTQpyc!WY`_7{|l|hd3>zkz&*-Ws*ev=nH z0klBUIr~=CSNL;;xOd&J#1Bk++WYh5W?1;JN z#U5+5BtJH^90zUlvo}N8XGbixfd${NJQI(h5Ejc!ju>NAKseT6N?#qk`|Acg5^X`G z9cOIO)~tw_Te~m0xb>?>;gj5KE|)G(Sc`pyWcmsTRAR?b4JQ_XH@2Ug@YlE(91c}X zGpHS~OV^#?Ly34CG{`hdCZ|Hr<|eD$Z;tB+@3|Q8q?2Csvpe8G{9Xi0?>K|*EhX^^ z2^I0mvYOJ7jPqS`iQ8@^f`kvUj>y)u*FFwtSL5YX(^#8tGR&>Mtxa)8MLrsZXgmaE zj3ROOjMCdN?mKNYcsPX$a~EjVe?fm+w|#9+^iQxDVhNL7)j|hvQI)@xtR75TIE$UP z!l)1E^tu0Bzn~`8S21t!V4Z8Z1IB&QMShes=v-fl{x8~*H;!@#C`eD@DJJ~IPT>C-4IcZNLSlG)l_mzaTp@6kOU+9mH#r_9;lVeDfA~%BTwPy2 za3%>52hQC8^TA#y#_|)voQXvH6#7-|gq49eyaTwBV#e#w(*Sz!3i9@?%bYI?$5F_2 zLz5L65NNC*x%^E4EOZLud@EK=3Y5P{K^GI5sg)3jjA3&EYTVvunP%&r{S9N{Q2_|uN@a=|4}1j!ka1u6}4YgmX&z4M!@jf zgmq~RZhPt#Z|{R6BO}nXPH{+p=-Akpj;SdwB_(C6q@$iooUmH316VA!d43*gWo4yvh-V9j&o+7S6X@ZMJ*(mgw6l7T|D0iGP8MMb*_DUqVrJ(sQQu=j5D>w zdTGq=r-;RtxzHX8JX>Bi6rsj@@1$)7TriifBJQn$aG4p(b-ilW^^cWad6}}@7fcV|!%^%zGGx?hoWQ^0 zZ2J1*LhgJjc8_0M`Oa^=4cEWE<#UzE`Z|Jz>NrfW8>Sm2NXhb^i3EbDsr~Q!I*!Z@ ze%iSnylm5O&P3;d#HgL`Q^u3V>vHC8l!;>p03nBb)9}S3^3em-!9KvptAZ&6BBa~D zULF72uK=qT7#x*1k1_#M%im)vch0MnUpZnJ-kLp+_lG}@!x;x(YtJ@Kyf5kNFQgn` zX%w{@GH|$9+pFD#{(i}TwXe;BU8lw~7dR`L>5)%LQVIuBtdkXY2*q4b2V8TrQG#W8 z)377b($f9K?$+I-#42O3u-dXi%?iVKyb{(0T%Zza{6e>lK-n1encu(grmrd)HJ$N% zJ%VHN%x)oN)s6fOxnmSIf8D=sc}(6c;wX>HhF8g7Zz*>O?D`bgu##;2dUSY+&vxO? z<{E88H|-&L@(Fpe6&_qo)03NQ$`o z!G+C^O(4mjsXsO;OzJ2Gcyjg~3j}AH58%wS5 z9sO4i*L=D~eLXtX07ef)U-6Dz2D)c3OD_`=YSu1a4{&**Hg zpdfvBcXwIEg3u#brXUTOm}Je)&gL~&48NxMX&;^>j?|c$v`sc_oMbN0=7Nv6_H6;q zq_3;GmQ<<82@56fDslbfLr&$4UMF~K@2ey&N87*so-rpw2@%le`h$e}s_OpSy!!oJ zU%#s8L|=b@t?uq_Sy@?9N5`4&Q2@Bt!RG238)J)$i?>RVsWNIHX@YzUcFf(L0ML9` zPw&CTlCIwdDuh65)@GCa!=P$ffF3}+`d24)eQ&iidF2#8(AL&Z{}2!Fab5!T@9D4l zxAzbFx8)3h&Ngs)Yt_RF(ls*5%*c>!0xljl%g!>%m>R%k9qmNw5xu+UPtpm8Ai}^e zpxd`+GDZyTs(qs@vp~erZv_k5ZjSj*aK9=c*sBTn-Qso8{6#OT!W{DpBSwe6KaqM5 zhUld40=4;7+K=)0gy>dKN8bNC)9!cs4isR2LuUGpk^qs@Ck$35*vn5$OsqcXW*%|b z2@uf|(Y67D#F!TFnSo8$@uu^dVc*%U5s-v1l-)GnEv@#(c*Yub^_|!r zXYM>ysg2Z`&^&dmSO#^v)iTBLgP)4m&^5F5cgVO1t@VkA{KjCge{5e}>~J=()-L@r zVeE$Lr%Tps1q~AYKgI&=ewa6bXv@PWqB|QnGJrHxT3RYAC)Yd^(E4ZyY_?;D zhKBAZgAWBZP3F`5vC1$^7d`O&zbA(EReba=@1Z^(_~3=Ax-o-U3d6?5L+-Ix^}E_3|jjH3tIO(Y%LES3X{93n?dEHWC%)sX|M>LYYVPNE zYwYHWD_rv5D$a04nL)sZL16}ig8^eh0u#eQ7KSEH1{Prk4mF}A_Fu93m|&p6&(FW~ zX3n&~fB#0EZkYS|%a;(Vxqg;5HZ@<1L-#W6*uP(Y^2sI7gc6?ns7)>^FW)}3ICM9Y zg|+qXXZsu9e&oSr=c>INU&7|pTj@VO{_%6+?**~z>|#P=0`)dW%w^bf;YVRip56T` zacoC*u00e3n^DaD!Rc-Dw?oO>j_ZV}Gw452y!_6f`|3_s9Ddu7#VEnUmTDq()yg&? zhS6cm+#3=sD-Mg25CYi4>^GBxK{$iJnSDTv#k8w+Y=16({r%TyuHXFr_QQ#mwzj%U zr(DX~>b3p$+O1nGy3+!?Ds_|?vVZ;JzWVy>spI|f+H2KjN3J=qi|MpFwwOytkGjr0 zn|A+ijfKp#xb@%9o|o!%+qq{?&Dy0$4ZF_@HmIIW+q|_t`rp0j6Ti+l`fLYgfSfs| zAaJId!Mp1JXZxjpe*XSdfBG0ud;+x^MHFfhNhbmEKNI^aKtjvNjoi` z0b-gs1Af!4-ealJK4(97`p3sVe(tEfpcgK`PHUaU^vE@S3~?EMEcDFh=V$3RCyBi- zfOvK%YsJEEj@1thZzqeb7H$y#G$FHm#?e=IfXM_i->tb-&Cq;s!JT{ertRIkH_E$V zu8CCdsgEB&hMf!j>bhX@;>DM8%(C?omH*v;_~OZvC7--1xHnta+3C&oOV@tB$?yc* z-lP97_n*?4_BqdP{`qWurWJ>s9f-`)XzBTlefOGg0{gGsss)x(44$rjF6*2UngBt2 B)}jCa delta 865 zcmbQi^^9|ZNJ5KwL5AUt!etx&aifOLc#M^Fe_SdZn*I(D}@9$5| z&tL!M{rl@tY&mnEKYxBHYwM|p4(H70Xp~fI^M)yG1EcUZAkLUD7>i98)JyE>;&7k|}PS(kaOyUw7vKU`{ ztx7eJxMF1+5X0!OW$g_KmKBFZCkrxZOjcmBH7VVF*GR5EzVG+nI!jwy-KA5CN=ia* zy)9e2^yo~}?w~WtVvG@&uE($NTOPb_-8!wcYO^DCCQoD%tDkfE=uy|1K5F;>?z#Vd zy5I88XU~_Fm!E$6^l7%PZeQe@eugudMl*H(-v0D`UysgloomN+8CZnDPWE9?n8Dy+ zz}S#r#@3i{k9o%B_51$>pPIj4|Nr(iy+EzKj?cb)-~aw}`~80r@ACh@n!=wQ=Wnz* zLjF8l=VUG>p89953oP&6xpU^vpVxOY_HEg{`?r*LSE|@+WrkuYzIJ2#?b2tT%{cmN z2WvwD6T?9kh9*v!6V9A(@P1XcJJm*RzfAp3>9ZFv24-%*y>*&c`ZXOd!~RH?1kZf| zw-4(~doG5g3*_)#M}-;M3><060vt@wxF+*4X#>MijJK8%A{@`a0Tz|^3au9Yu-4*# zNnD%%;pvZ`Ic|=r-?MJVqL{Re7K{-WKV(Ma+26kw$97a_+CzvP#oQYfJ##F6W;i=p ztXsH2wDQCj+cQapd5n`7MZ&_~+BU?k4}bUW-Klfu{MM=-2$SMrTY51g=SfUxJd2f` zonCcywdwQ?V*lS4loS;ORjTf3jWm(!1*Sl=B0cHmH5a4**#BK|Cduyovdb^Gd{b($ jvSDeNyn)$F>>u;nJKwG5<+U0EGZ2HPtDnm{r-UW|57B?r diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym2.png index c9a1e8826741566eca6188aa2904570fc7443be6..a3095f5dc26fbae36dffe3e4f51839759689688f 100644 GIT binary patch literal 5318 zcmd^Dc~nzp7Jm^dS|#XAixdse>Xex(OBKqJu&5}s3UrDT1(KM8EJ6T2E6lqKvDB3o7>5Xe3+wKHcrGyhHXoSDfvIXUOP`)=}X?)@#_ zx1W1^7`$!rHUIzypL)7~0RZdL008oQ;|4Hd_jLRP_~96T_(Z&KOlW)p=G zx;NyGYgq&UytDsP_XEch3q=z?m;RDa+#!uAkGgT$@4RR3*H#f%z7_}CIC^e)AGN#e z^hS))1)H4rX2bV2I5zedD{4<=#f_zvZAIvB``w3IoB#M?;QOEuTY;PTttSWmbfAP) z*-ga?pNW(C3%!E3zCWIDxMeYw-@z=_WTX0Ry&%BWV4pEP0I&&Mw-qqI46Fw>?gAjd zgN<%Ln$7=TNEk^-v&uX6p|+`G5dx!um17Nw)iu$ZynkKclKJ%^h6;1-_UPG0J^7d*Rt z6iay!SA8gS;lb#|09D96@08>pU78m$MQh9Mmc;54+lQ=bolOEm$d2pH3&7^~6gv8H ziArpdV_a!|X#`<+X$ZON*~PleEBC|92D+u)9xj@;HL~J)jp2HC{b7s|=s`*5Y zHJV0o)ihe6TY|P*qoQ8j_*kvKDp=lLR#9%j4MC`eJGsM!e$rjqBkQN zq=`XoQ+J0roJjDGaPFy($S<8tnk%AG6|)5~rt(!3oiiIA_2rou$XANN@rT%q%S zDzhk-H(Qgmr&-K3agX6o1JCe{W2p4rHke!S;$qL!$B#c-NoEC{Jb6~XJXm<;Bm<`$ zs&}D`)U+YiUGcE>^4+s%4^B9I&nf9;h@W2(BB?qH2LBQawooedz8I?g84@Mwe zI}(7hy9`D^+f*#CH{A9NVfPPug|OD<1v{_)T4#6vD47+})?`qjNH*AzZe)>uj3;gU zrfq7Rz2WchfL>R8kjuCPYX*YXS3WltYuNXP?9N*sW_Pyp zq>1Cxwr>7EHw*gwoiF|UBOP`|tKQ2zf~BmFPpi?(wP%$^w^B=8ePjZDLY38ULr0S3?&; za?KLNYY8Wzz)SdB)qi7cb-+at9aH3g9n5m64^>0l$+mh?@PxKA_eO=CtAQb>OI zTsNS4YP2qLWhgKPyU^rM3o5_qK@l&yAB(Jj*Sgkc3ut47>b&*lJFZ{9&VPJu&khP_ zXox>h?u*l?MOR5K-S^Y=1uGmhS2i+i0uLD?F638?j^gI>BXeNSB6Zk) ziKZjHozTeCRMv*{17o~nBBg{)x1UT54%E;jEy|t)eHWI0fsBfvIOcg6UTN2e9EWU? zElzOdOLLJ)N%C)dxhz5h4b4VnWMtHlv>GpMR$X146OKP%-I5(LH~fB$tTK|uAK?^5 z(vW!1Yrrk)^_(2R{L`BSM6o?v-hH`jDoK}hloNDKK-(;|DKJ|H`E6y8ZcG><`|<`t z_KhSWOeukdp#+t}pEasoO7PPzrAXeAX;nxy7{L~Q7co4g8pDQ>BWX(E)_;^0Q5CMN zm6$r!s;i$`E!QY#v`Tn51=ho!z~dWd8NJYLX})Vu(3&qunxztZQxL#RGLKk#FHdXm zGbnWjNt0k{ZRyM*vGJk+wTSIfN#f2n4LiXLNlcM2wi{Oh`v55GldM*7B@NE)^@=%m zK12Czhaa6K57Me9NG^9snu?Lh8@b>8!*@y#nZE`Y(*c1SK|J2%>*uGVvE`)%$z+v) zL+4CSecxQH6-#rv6I>`wd9HPNFL4;QY`feOrA-k%k@_NBdqk)^-bLxZA)8wwvFX>Gm zaVDCp4^dTHHv(=uUxx~_an*GOe9E!J=K6ZsBUdf^_U+qFvCmt(LW3jg8Hbd?@FsBW zrE0$s%mtm!A3GF3n1}$S{F%~U+_~6se!ejgbYuMIuQ^+-Q!P-`e&f?LFCA524SOSW1Tn-mLj1) zhW@@$Z*5(3dKw=0Gv#9%7EP9QqjL5?(x(4)1o1y-rv4SmHq5`RW7Ba+;R8PZi~+eV z)Ai=lJEE&;&{#aMRLZgS>X&Ur-Wy@AJqPug9a&O zQ{vldB3q#*f))zK69TiD))2kdz*zrg1a%|^FgtXHK|o4DHbir&{)R099foFf7C~0; zY~*{lZHpLqc|6AoNww#u)L0=I1F}CNEZ`y6ttyds9{LywKv%$T+iW)Ut&h*Zv_%FE zBRV*C^WI^6?jfMMyflK}mA8Kdl+{Cd`@^W9Z1MQ&GyJ2y$P>)Z%~E6sCU$aDUhrHm zlkOP0DBaxR(_o?7(xUj&4>NG`mothr9!2nS<_S(Z{qWU|l{z2hO*+fSPk!mu-bP7X ze&~VI-dCTbv>T0Jic!4!*0>$N=GUkK@JAFSOqNrrF)i}ht0c)vGB&H-=(g}~1^qs) zM{SEnYN?<_o6hT1zFb8QF*77$mD@2(ESV{?TSJUPUTgyGMO9_xaXP2s+O>BRt~j+r z^KJ(=#9#$Wxz6yeT|kn1zinN0^%osku!?L8pD+CeJF_hW!}o&0;I#6QQ!6yD4s0x= zsyA3Bw4nemwo$l_)Q?_Os^gIm2?_S9?U~tgi0z z;Dg|cTj398x{{$V>MoD0#&p{`Q0WLKY8qUUB-vD|#k%P!UYw+8iTPqS7d9)+_+cbg zGRa~jErm`rC)G?6m7+42xk(lQl!IXiSRjU6JENXi6)bU6QDtlg^&|SWlg4`onpS%T zl}{uM4!Vq!s&^JQ4`;d_j_0*!Dx@7|B?`GeD(SY7kx{6_w*JpXG5+XuWD#MH3cZ_Q zjUhATc8z}E#9RWWUbhb)CqnR^1v2pna}UwWb027x32*YCrK_c0fByTvJv{9fd2S{jK5ESx=&{*vYrF+N1Gy4nFS)x}GFb$RAB>*F zYzMXcU-DQ0aR1d7Kk{8ssucpXR`F8~Z5|50Wk6$!YuYAye-^uE->83lqlacoY{97h V+J3>fB>=uYJ?!mHJ9z5CUjdVWKWG2| literal 5318 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index 3bf1c27df9478cdf419f14c946c2641b251be52c..d830e2355492a2c3c4b106010e29f4c83b941109 100644 GIT binary patch delta 1834 zcmV+_2i5qJ5ylaaB!4+{MObuGZ)S9NVRB^vO<{C!Y;SaIX<{IDWpZtK>M2lF6hR8yl5OCVAooh_&#K0Ibr;$cSRGn5wI*6^q4` zN~MAx$t>2wKLW5y_4V~iBodmOoKzx_P&671dL*-03;#l{>$*WbcsiYyl>Zk4IgX=L zDy3*Nsz@Xf^f;z%gtf@!>msnL0S-5kNFy&76lf7bUMw&8bH7} z0sstK7dHk05Dboy@F0^u2MLkiBa;^gBYzgy$AI~+udh!tGc$kueSCaeb#--;q{_-l zwYRqiHJUk@)4OHR*q8uz%T*;7)Ag9!w3Li7y$qb z<5K{S=H}+KxVX6N&%b^9rrFuqC4W|4Uan1>Xzg10{Q0vM78aKMd8V}}D=SlBVSiz^ zM$0<{z=L=1-s$w|(-|*eo}Ql8z`($g|9;}c2^~9jESn>m5{*W6{P^)@qq^K$JbwIG zjg5^ud-iO$N6Z@pz=MK<0`1$kPu<+033nwgnVOG}IAeHIrN z)zZ?U*4EZ6PL_W-F7;sR)~$N~{(rr`e*Nlu-@AA3s-dAF3sc$E(9odq@o|0n^vUx+ z$z)Q^&CMz*D$3$y`G){_bmq(%4Gs?a+UMcJhdOZJ0Oy=p(ACwYo}M00+kX7`QBR*f z)#1a3vpiM)Apjoj-Md$llau=L<%{QihK7dJ+}xbSsqCt#s8Bc@*4WtCFMqe~>FH5d zS62?E%0C3ahqksh&qo0d9z4*&g9o*FGjFqG%eiys6pzRCw;3HB)%NY%wQbwB z98Q)$cy{`*d-rY)4i0L5eqN=erOR&Tx~_(XhJ2qgz@NgxLeE*xT`g`&FCAazQ zKmS&1v;Q}5tsnqCwY9bB=6}tb>g?=XcANY6@9WT^L;Uv7vNvzu=;h0oI(IHEg+fbi zQ}x#t{jC-UTc=K)%HdS@EG#VO{Q2|h>FH5u znHRq#?cBLj(P&h+Zr#e=aCwAZQ9iwR@j~Cfe^=4#lr;Tt$!!k*#edbkd-rtY$dMdR zXUFBsm(||huJZEoUv7KyOlg?>)>({S+ z@B8-cTVL0$tskt#%5e$+7)Ag9!w3Li7y$qbBLIM5+!zEfj3WTRFm4P27{(Ek&jv^m004$@ zUjV=`jsO6|xGw-;7)O!bA%9>EIF6%uJkGTZ>i}!v4*^&u$8nTQCe_&3sAMw96E8rl zg?|KKl}1KJ6pO`FU0tnMET&W{74%4Eu@?RjfK{rmuU8_G(B$N#5{ZPO(P+>knZ;W8 z7jj+K4eG(u>9nN$zcSEq9HmkzMWazgB9WlSF>NEPMJ`_#fn5%~kqty5k-UAfZ8WTf zKb!)SF$We@fMJ1s4`9KndPPSc|HvD%I4~4?sJ_iYr-vt)z Yzd^uJ)RE9oUjP6A07*qoM6N<$g4}qNsQ>@~ delta 1789 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 Y-1BNSKc8rV{vCna{sT@lhmhod0mK5s>;M1& diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidth.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidth.png index cf5bc41bdbee463d255deebdac378f45f434bd2b..2e7c530beecfad9b7e8ce47f48e91566fc2b0af8 100644 GIT binary patch delta 2518 zcmYjS3pmql8-F&-mSR(pa#~r)>m8DMi6NDDND@LuNX}U~CTtFG@r}2S<)gx=;x3wU?h6T;2kdON zo=%j^{A?DD)f3%&(sV5?WCr_UAAE^(=cL6V-t5;o=m6Zbm18FhZMX;H*MZR!t zWznjL`@l zy|h#-%m_FNfZSk1WYzWS*AMFJ-!CZGBN}3I!iff+vY28sqgVW~pTlnIIz1W`%y|8?I%sCUv;vd{1 zpbZ}$;|l6@CIM*skshF%3c%LC^|0p^l)-rg{wA>Xz698+_QT)za&RzPMwL)rU9HC} zd>@Gb0BO@;;7n$`DqB~Hw$L8a)fdl0!b8Dw>V^Luc zAfl0m!_~+{C6nm6`!%VfqwXBJ{tltc`_6PVtxPL%m{noMpGt;f^*CmXq1LAAd$S?CB0;anPqDZtxveaXrOEZrMUQ7FPBnhv563i7T%TU#3{d53nd(}niLvz2bI`}%I- zrbe5t)kl5xa<+h>h<>m~wY9}5DVyb_HszX9+^51^+uGWoRDK_$>Gbu^e6^#~=_zX` z6yDQ%;n+;8{h3z8j66bYsq&EsKjA+{*VV;F89U6?47%Jxdj$C!oGbZHpQ+uRa8_^ zzTX)da*0#LkToBJ!Kbke-^9)&OUM!+lgEy%arc%om5p`#9z+j<=Vs?MaSqcM*p zfkMYsK=rhhpmM0xb79c5oB{*aUqhoBa-Kj!13+^W>c_kTlARx=3WEZ9@u!*1mzJ__ zP18Qb3UQ@9kG_rS9S8Xrw{NV{=2*=)HlE(5B(3LBjGT@!%uA;yig$1lj$D{8O;HaBLhU zAyo$0`Rq||KisL6ISBGah=8m?FjF8xxOuB#&MQD~62FzMiIk^^4S6ECWwnn~cjY() zRK|ml<~66p#6&H5+Uc#xe5-;@Ev>ByCoC-;wdKRtSFaXw{iV_9G6yXPHsZ>Hh)dTv z<+M(@a>d>plVo5_)k=~FG#c$rcD7xCT`#xjRE;-BCU~UYadvh#ASkF0ZMvHO>$xXM z21-;+5@jK{#X+mkL2Dl|z|YUF&dsUX*&#HP`E>nzVAHil+qAR|8wL80$BACEe6CGcgaM} z45`7=#!dCmU)}C=o*Q8>Ca|i-R*4_mkLLDc)_W}xTdB=T(@}d@Q8>3pB&BQnO!)7Wls72tS%qkaX@Zc zDFR!zY}u`>>{#s_MzZQxF^wz#@WJzCcXutmQ2nNgiC7!vXM0Q=AhUUG;cndOS4*Bx zQtJwEc5~}vvy-I$ND$hWE_`~asB9c%Cm|tGX97xtB$F^XHQ71{?3@OVXsLq;bdd4Udm(b<^{wTXG|DO3NrxY2M!gITo> zf@gHJ>vD{Hs7+{SO0WrYsNB6Vs$k1bd~sD(Pq?9DL%8r?7kzze8XI#4f0>+_d4!wl zFFQM2d-?v~0Rfydps)YUn+#W1SGTNqz8Kwdhy}8Mu836IIoJ#fZ%=Q@8gSPH&__IO Ld#v*4`GkK1{M?=7 delta 2546 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidths.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custom_capwidths.png index 936bd18a2b6c01191ebf98e6a0c2ddd0cdf964c8..1d8e44ccbecdb94928bdb69fb260565574209929 100644 GIT binary patch delta 2588 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 literal 2826 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png index 19e3731575c70f368ed50d1de701567ed8a359a2..587ac5e68b431636d3c63a71ea12af6d96544809 100644 GIT binary patch literal 2729 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 literal 2676 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png index cced1e51566ea4566d213974d34356acae0cea11..3323a7e4e1ec2e2d80a77805728de25308f7837a 100644 GIT binary patch literal 2109 zcmbtVc~nzZ8ow_D2p|yIM27W{3I&8-vkp$KoY3zP%0xDOIbt~ zrLqZX%OWHkaFD?1Py_=f93lzI8ba8&TEux#+S8fS`D@-e?{44s?)~2P`+dKg@8Rww zE3GCC0FZTY#+?HIf`_07B>}erVOJ#FS&{L6WG_M_`C3Rq1aJ!>6XOWvxR_Awq=*Dk z4B?8YG1eGsq#aEr6G_%4Ch>nb7!wkrOl)~~z2Hrxh|U*C07&SGUdYXRydMC`1{a*2 zcXGLK(lq*SzUuvJmDA>!nxOLPDo>QWbSACun^2R!ng9vA&~IvE=}1??kKcZuSwD-v z+j=zWN&Xk8iO<4U!@Y2Gx^g8|rZq=fR2_~uY53>WSk^25x;&ZkVI@jwZdqgQ`cB%1 z6l$5OT%H4j<!oci7RkxUk%bdq7WaaL+1*{g z*@O!@_BvZ6E7^3tvlR60^#>L!q|uiQ7ndrhS|36Pjt~I)^^vzTK$htb|#*owRCjU`!~%cduxMR`Sfcm zu@uS$S_^~mDDo$MGVa!)ub}Z<1FhKBva(+unp;?m#hG_qf7i~5pJY(Hgh`w&52nhd zYY)byrLFCu1%VqKAO8$~`=enjxZDaj&s~$M;W07% ztRTk+BSf3~Ysf&z$k=#sW(LRMaICGYM&7=?FVHDf{<*p5s(Rx{bRCOdHxjLXGYrP$ zUoVK3>Ydh<$Lu?p^NnMpL$@*c+dDg7T3E~rz4VX*fxzF-Z+LL9Ku%u1l}>Ne|MsH1 zVeya8Ox(o-A?FWE4oC2ZExN;&q|47fmjvq>ff*b5Ikp1@g>(dO;kD()2Jc9kZ%k)R zaaM6>7b$?YX+y1;9X3WnQyEG0rlzLj@Izsd)n=Ku_vjSOQ>k@D$$eW`rX!en&Hv{e zpT@1_tve<1nZ@jc|>lR1m2XYrE{gKC~-HOc_kmp{&FTSfk= z;0;m>2odv=c1ifM^2*VyOPUL{T50Fh`-3Eou76$!P_0Xh{mDeP)Gw;F?rU9?7tfG_ zzrO0@+;{cSE3yl%+b(x6LU{t+u?1w?vTCy-yt^R9wOT#>7jC{ zf?e*$S{nkDF~(F}&(efhZwNuXeo`n^M>sM^d6@Ph>Wd7@2PL6+P!4cm|Gvg#XPCAT zy7$je-;u2Qe{uF_uP%>U;X!a*+8zb}Zl8VQ^p!nMj@HJ5c~`z#P2A%!?>L#|TVu6y z>siu0I*n&FX7buo5V_bDtkGv;O6|Ijitxa221=Cuya` z#x9cb#2ru|FSqwi#|QJ9VXYjYl9SktaWqmCdDxlD2EAs-zUDR~ao4l4MMpn-QA5mx z4a3no$7U@C9B1Q!M-pOs!yJ6fiysR4vF1OO_#YDR7thq_zR*F^l3! zz@^yiE<1H;3C~DHShRltq|+1%ciYSVJguL+rZHah_RP`7)@i2-Bs` z7!}^*tei^IJoz&G;x5_?gg1ETKhs2T9mLbU_jrn3W`S?(E5$Zpg-@1+q4{rEPQoX^ zLHsWL)5j0sBETos9b60BVguDbC9x>)$1dZuoPXfIlBDxj?@K047 zinqc-*@)5k+J+(-WJn+nJwSZATQaG#mOmGI4&%fSGU0}X;=31nZQ&R`cz-}SNqExAhcz-w+q5Xz+SU^mhl^{1?Fd eNxA>Gb=$&5wnS4H)$kpBl>-;NJFeM2IP>4R*=<$; literal 2055 zcmbtVdoZ0`lXN%>ERrYnIwa} zNnRP#ofEFed+A^Xm#8KjB2jX`PUqZv|GaCRz1H5}@80{n*6;iM?a%(~6lX^}Y20oc z06>~*Z$kqB1-F2U6@ykd-+B%%m@o>Jj)f9~{oy+FB|_{^h5;a^v8^Z;-b@Vu@lvV{ znI4%p&)5GE?@s7gI-R4aK)zQU^>|#$_MY~=PU~8_JYPwdtZpzhPkPjN(AG!$XnI{_ zYJ-+k3ifJIEiuXApT7DmEbWHH{$C~!^eBxNV8{yR)0(2!1Y4h#E8Z;RKlsew+FV?Z zI%7hR5#>Hy^pXXLIR^MDp@5Pt07>!y@%t<8M2p3`aHJ|cB;@|F?C+WgVwpM}9Ua=) zFSK{!N?9)gfJN*EeNoWrT0{#ql_I)tY;x2SRlu&l`Os@T==GlArczwpVoT&q&70*F zB+3wl;^m2P-+GB7d+30BJxjfRW1~4sJ=F3}O}_}R%~Vf0eE9I=>gxOH>C%hV2o5Mc z69||Ov9TUVEpO?1JN}Yrbz@`6?c4ZZ217VAW0RViTABQVn^p_DBedMMumIKtT9cdJ zvH`ky-c-n&cQdU%PFeH{H&=7JEcMZeiIMT~yauOyx$>&2AU{9yp+kqvEG&i=7AhZT zU9>Fs>iQSQMB$PsakE9%d*{*dFE?v+LaanpK&Nly8g`|tt^fUlMa(B>ouwN>zqEAF z*)*7_S>Yz+2{lhsCUrS{6P6@l!iLRGU3GeL!F2>Q;;@XNoxA9?%`-mwoA1ndb`u9(lUnm^^Vo_k;zjwr% z?3;i?obwcDp@cF+@2nX{rfR$g7V~?Dno1%*Rde&k|3I9bQ6R}Q2z_qpgdI4oxWYsL zC+#5YpZptdJ~ZxO`_r93+Y;mjxos(12{Ab}%_qGdja z(@!Dt)+0}$u5kmWCDzHZ5Zrem+RHH*sY2KVh2oG>M4@gc{y=`A1om4DFZZ9=w2-Sb zHsBP0tasnTu<^8q;nFHM^Ib8Z{mJUcS<6qW8I1VxPfkT)$C#m&cPhzUAI)r^Kbe#{ z8T*cSqy!LZH-^Y@ku93O*bk0eBZdwlHw*9hR1EXKZP-1g;fwX*#6&Hq{H7!&mcwLP zt<@B>*_;Ruy=x%kya|E%nK5nR*1~rTHLSiaO$NLYp!ei4+rYtpU~c|<(u^I}^=B7~ z15UHE1b}qKGqkv?Khgza;H;*e6+?u0trj1W4yoh+BhOh~aJiKUX)OFQcz^~DVIPCJ zf%Wg+4FQ3_6N#(GhJ@iLf>@$Z4L_kllD;UA@`u#cz7yzmk9l9Y#c(%w@BI?mlD5mR%|WnJHDj&-@5r9K?r6+#`(tC6I#1%w2x<7{dzokamb%Q2h7Ib zc%mcLBqYxbY{s!{CzW3v7e8450WfVu^i@*$Hd_>jg19bmX$cy;-s;ULh;eo@L&qwb*6 zifGhLuKabedk%1NAJ`T_4e`08CeWAa>L|z{Y;#3C*p(*0B`tbgU7>dulgQ zAL$^tgRZ;fw|V$!kJDc+(!R}&r>{vjOuUjVmApz@;);_(fK{1#IAGHYrPf6!(I(Jz z)p>$cA`wkYR1*)kVVkvZAv!ubLG{50iB|$$Lj<5LW%3tT>>VA~DUCxjck$ikV6yLe zNEP`&eX!;sHHxK7>sNB^Y3E&(a~p9KD;oIRDnek@)&s%KvaA zpq^Z7z^!yQMk03=ucjX1Y<*N+B7LnD?n8AmMU*$@S`=?%lH7NRikW%Xne1@m)H59Gv|%R4S^|KT$v|H&#_4eTLV+xqoIbZ7w^ zPXPEBz%xbw<53431+LGApN0StOu)89D$O`Mg+k%zL=T3S3g3=EID=irk6~Wd&0-ga zm@@LL0fN0RAt8ab@VbHNJv1YI!RLF*F5r;B1Qy}Qs9WtO?F~9{k&$?&H+0?&G-@Br zyz{y3hMjAv@Y!P@8CgHpe%;i4L5=54J?_P>8<76&)MUuK?4B3*!>87PzMR$V4i43X zC4YMd2Se^f_lA?H9*aR-#90@u}Ikk{XDM9mP_(39*(?)4DFC2ARg)e4AGn~kQ-1?@GzisnK#j>=aqY4N z6l(UQ6+A_A@6<6Pzh%2%29oZnBgP&><9}Rfee?)=vnm}P?tlOOecnnMg+f|*CxwL1 z$=yZly?3tU6F5e_D=QBjwr>yInc5oWH{MpWkp^bv=4V=n<<3WUG>s4et$L7~fO@1$ywPKh!Dcl4Q)M#eS+|{!&udBI%Sq>HQ7~^2AH?QSxPu|7zR(a7|{< zWVvP40xoLFCB6IZ2;#TQDV(beAQ{U8ZkYzD{ybw*8HtuN1E`^C$cp2paGw7?FUx7H zjhe2xi;MG|E^B&bM*gNdpWyO=$K&N+R^5amk;F+0U8_HE2ETW5DxrQ)@5;roZ5;vr zvWT1;wQo^l36FcfDYvRfpc)8$#rs1dFRb2hY5g#Kda9sadoSkiqj*f2LqP-gMQmRUNc-+=V#SSI}rQSmzz7Rn) z9L@3T-g>zA5ZrtvYHyKBP(j3NRqzGmuS62D=e(I?()RVK&)wE#tC8-Dqp>V4A}um! zGweIe{x=p(IGbSC17fx5@h4l_GSu7+!C}FU#3tyL`&z(@wH#6vTPhPfpb%ezv-Ghx zhD;2LPUr3420oq(uDkT=OoRa33a+cYe)C0~_XSTcFPOu_s4QLmccYaSmwdh*%r)2n zo{cv@8)tkwdHY4&wNiqgZ96(|p^%UvPP%8x#eidUqoOP{v2aS?a0Nti55*Yan;YOy O40wC&cW>BD$^Hj*|0tUP delta 1749 zcmZ8ic~H~W7QSIi2q+lZ5EuhLo4C}n3xWYLA%RN5;*&*)1T0|yK{koVQpm5MEUkbf z22^$uihY*F5llr0d6cap1)|7auxJHatYTV_E&Zi^^Zs~u=FFLU&Ye4RzwbNWT{q}8 zxJu@0CA)e0W03VZCL$jIpqqF+I2iz)ed<6}8&OCAprbq)H2>6!gH12u>+E!FqfFzL4~>PxES3#_&5;+$GvCvb#>h9WOh_xna`jWh`8uT&o8R zcb2Fa{3`%l>BO)6?Ck73H8rJ>%c%o)aL5rC7ymYP!2J-PEjCSaetTKn{m$6khI^3B?>^(+F|j7K5H@eWG{6$?LdFHkB+_U?59;OGGn z)6}|PRYgx=od!P~VIaa^ZnT-BP`q*DFT&mFV8;s{W6nfsK|D z_gLwIe*^$m3!G8ovJ1er@tPWai%4nH5~=M3HXZ=t7bLafJ%vU5 zqFgIoI}9kQeONqZe(J*Oyks^fWCw@&QK1JtQ!_Kh-n5qGX|cW^mGF5I-It&F@A9x2 zuPydk!otGB`quo#!s6mQtGy+$)f}O#sA;wxkKkGN8c-^W%n%~=L)1E*YUL1Sa% zQuN~Sb_*hhqL`XuFA8u4Do?p1pUlbHHlp>m5X*1gJgExpdQ!5!y5Zp9@L{`zf!xEa z7wns>x<*kvpB7foje{&2G^}Si0$EupzSONu5TuOKu*#C&D)>Ry(ltn*^JQC?uXfg{ z^v!qG4I0J{R~s4{#*Y4$Yqj9TFb5U%G6Q}kw&U^SGynPB1+L(#c?BO^aZC%?(H%>V zX;D%3raOj0sz+aS26+4W=2`VW+}!NACzXb!XJ%${jEsz|I}Y2~)lE)Le#>+=%AAdN zoUj*9o?{AP+Ss>n*k|3Rjd{w^l6C&8@w>}WwSz}Qq4*;Ij$Xg!aR*1ovWFQ__pKJe z`PUM(@0W?czYqB8$PoW>&QI=JyQ%u&8E>@S;{wozzh=cPy%t0^4M#79zBPBD$rvAk zJFS)bwI|0`n`6?_f(Hi&tvf;`5Pe(SaXL#gw(L2f|6kcy1OfYU?>2Vhi83@q0djWB z9>`aPdgjjG2f?SfdVuhi%8Lq`KTy(YQXY6?_>gf^$o;yUoE$&Z_V%_cFfh>Pj|a#$ zUAZ`_va}3v(arBk4|Yl8IHA#KWI~xQI&Y7~9Q-{4pREJtwv&#>zI~qdtS9>cfbVq2 zqr#(|zZk(j(9W-2_WGoUoUPyBH?b4aY0BCE9DNAL3BDQ{eW^G@8xJ%faCHEIFV+NC zY>^m6sdX(U(5Z3)5s~^H=znrwOFlzRS(u%Zh^Ci15^`uwhoKD)s2-}PfFZ%OgZTt;Hz8&#<`NsCIIjbDAzb^0GB z!yOWPzJOp`3Luw04a|-6TE}jXp%qeYqflGN@9YX-ph1w{pW`rMPEYDlF8w!P*!CWR zPz_3c1EvO@Ljmsp$7U2H3D3$3ot~by?v%-7P3fK6ieE`2(#^`sp*s6b`e!O@uu|9? z@;B9Lx@UBR*4id~wIh6o5UWNq$ry161X}fj)Fz@yJHIG-%yaTYpT(Gh<&<*Tyl&9Q z0QNM3v=oKn@(_p7_{UW0+ILJ09uv?1X<`@*T56J>hO15%90c&)z*gAP&%>mqrVbniYpk-s+oH)b?g+RQc>9|$@mP$fps%m*WLF?Nd`%x$z0isn=e}4v zb~ku&KKRA?Bf{9Rm^QB_;nxJ$oSv5hLRq|YdIv`fw)sv^fTH60V1mU7^BDRg4*9fz Mr@IfM$?Z(`U%?vp%m4rY diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png index 32c9229da00a7bd06fa94c96292891d7f3d380df..234f2941bdc0d47f5961e7b1d38ba18d52de8793 100644 GIT binary patch literal 13548 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 U8Jx2>Fen6*Q@Ngh%_R7L0Zs97761SM literal 13592 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 DRBAEz diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png index d7c586645809a1edf26007f74158444aab3694ea..efd3a3e136c90ed9b0b5632253ca030be732f61c 100644 GIT binary patch literal 2126 zcmbtVdpMNq7Jp|9Gvm@o5)w0JHo{gUx6q7SW|aGsy9T-68TVo;r1T_|a;YKOQXxBS zmmy=jMaTWN5k@<08n<~85jmgg?B|@n&L8`a^{ww)@B6NIee1W@Z(X&vG!qe&76br@ zP{_u%0KlMEz(xr`Pwqm4H>7mwCJuDF5N~?4d$<>{aHod`hR_53Jg`w-;Sqiz!Rl&< z)efs-ed+Yj2)#pxg8ts17834rh&YvJ2RT89k{u%e5K!C^m<9EZb^wqpiZO{AlfN*b z?(1wO)6QdZcZoDP7m(6Omyvr(uWn+mHRRAc0G(Y+~!&m)YIMqchaq?&egu$C4*2_UjEoYRZn&=6oiMrzj;JNL}qbq zt?rQj(RTc$b2KztojPZ&DslSBP*q*sW2dtBpGp#o`g&W__6_-Jx?sF$q$R}l2jsN0 zv^d_|WQl$I(6IJvtq-)cwCb6gLPA1yk=$$v35j~l#R3%uD|*R4CdL6mYGir&8MMa* zgTC+V>}=QI28C$b;5M?cQOCuJ5N7#EDJdx)7ZO(#ugM1m1^Ib+{62n}#X*PdI zc;8xCpaSN)bm=qUIUP%qCGZlvWf+6bp>pURcu}!Eou%x)A?>ZS%?sO0$zYiXrDkj_ z7mBk!SZ`e?EIT{9s=9iuX=+t&@nmMVeRtyWT3??{S9iC9)^7tuQ2d71mKE3Vb&;2a z!S(jEWb_}4H9Y!Dd*ktJf~&uad7Mtk4M474)4or(eLlJ(4~&>Pg(yRCUiH}2zOlAF z#V4&{qHp8MY)fa?J0m96c{d&(|I)}q%!yG!l5Ny|;a|75F1Sj*EGV&JCWwQV2&w*q zj{vH*1HIsx+Jp&2z`ylpK0lE)vAVk7ov5@^O!-G)S)U){89hQ9r-*>rE(Ue-S)_n4 zRK<@5=0xG-edSzULo~dcCn@@0)P^y($D7#WN1BO@ z`XG1;rz1Q`R2(7@vOq`gvXr4GH5|SC22uMW#2#t5U|+iyD~~0$(x6 zOIUSPul_57fG;iJVAinLzrs>ymi{`BelB!!5n&vc^z7E$(U#)SOQ$rfVM(`!Q>`j+ z3J;IH+L#Ff4l2hk>cs2^xLfxa0sjH>|C6l`tT(uNuZheqSdB51yZ7_sjAWyxp@*>g z)*Of$RUuBv7XUtRcyJ{G2z#QT-0zgYPk<(Q65^o*DO9C{lT-iXqVptv_xEGQ z2z^ke9Y1c`^yd-mGr(zA3LP`rwni%h#(>1e6~Ud$cOhWkO2tzVOS?Ouz?q)Ly#|~l zrE6keLg~_=)HOx)e0D3GJ|(9p`R!oD-`(Q)o?o zrxzwTElXI$Tcp%Y1dn{ef|{nC-}ndPZs+X0ob51EL;C^fhGsjUm{=N@8__QP8$k?i AU;qFB literal 2073 zcmbtVc~n#968{nc1YVFR&>|ovDVqo(AOa00l7!t57EzW`6hs8sK_G%lSY&Y_0YqF_ z1hD}HDOC!xNGynmaG)rnf^5D}G=w!QvV?c3divgf@3i;aZ@&A@otZo5H*T> zlU0!g0Latn3=`wG;Xy4jw+ro|Tb=ff+zc?u#>I(2{ zjBj2OHLaWHr_{X*bp!O}_IUtmX#k{b1}q;0IIIi<%GUqjxKRE4LKAa_YHn^$JS%Q^ z?gPpqnaolzJeRJeuu`2T#e4shJwMi|%AY%#+YDT8zxZJ5vIGmC9Qgj|eGesBNl4id z`BtLM|32P8{;G`vn$Qsl)GDJvEkzQDcAUrQ8yM_7kL#mtRVIO(^3IBZM~_-%XJ>nQ zc+8LyrGP~a4hbpq!UqKh)A^Ae{r&y-QjO7YFy7qKl6<}MrhhT;&97b;cV{XuK^@uSn^D3wAkD{B>EX(9qBzZ~Lm)*fa z1NUI^q)sW|q;*3E;me9Ft(*GT=q?(R=KmnbhQXRk0cc*rL+C1fMHpjPad4k$=z!DR z-bj*S0}Ws{LyEb%Io%xl>xG5d2$w{K1WsD2{PsKEm4dg`L0Kiey$6&oSUtT3hmxQ> zI0S}=s{>OFp-(|w=R>4o%Y=vWifp+on@!H~F*ZUu7eDLLurZ=%OGLFZ@m{e8eOTsby4AX*_ zg84;u;VERo8Lf=VgU_PgUM&zWyJnrZ9ijPQB=393h&;f=*>(EAxB!r+N0V_&V7h8z z)hR&Lk%Om5TBHf8?Qa+7?lg#APhGhq9$#-&DB0%(L)*Xk6u}WrO$mCYHA!d1tJz>$ zvyH=mJaotf-Q*PIRh~75M}PxDhX;7j9}?EISt>bEcH^i(NUtQqml|$%ArR9i^8olToI#l;DQO`-d;LiwTdqUFrY&7_mF|K6AS&CVFO@8L#9 zfw5y=Qj&ylVnQ^(H#WM4+sBW5Xt31X^TmHB86Q!Wt?7744R0S6=wLY8*r)69@$Ah&JYPzmp&@U1)8*aQ(tE4i=wv*I9q* z4oSE2{2#6Tu*~<~Gj)SgJ2|oz&sBu65-QeaE=`_ZfAOX%jDPIOE=|XW2kDC#&G_wW zqwe9UbOIs91Y>>;82&F#Coa(g`<4M z=mSlXmGGqcvYo(a%&cXWJ)2=;WEK_+$C0S+?IIOl7c+|1)>1&V4{G7|p|g9o`K`^G zfbyJt3$+G07P_6m+ie1e{eOge^*#uw=|7L#lnT6x;Q$r25W=Gt4S(zbgCleZ!z&P; z3<=gQ7_7z}4pOQR0ChwLD7S6_go~OG*EFG$-SzbK1H;0mT86yW=RC7c6h)3?_kHQ? z%&L*SgJL}B5x#sjV0%VwY0pNHL=ap3iQ5K~$wQH#M!Tr~-|PiaW)#2Zxsb!xszS4) zH373jG!sb7D@m#~lVdTswC|=B3+Vxj9twRU!x?4t=Xe01pt0AS7>9b@#de-vLLv3Q zlA&x~b@yIZsI!O5&xwnu8y}aTfIs>3wG2{u?12oE$!s$MOSXzu>8H(zax5?Ww=27g zb+R=0bDv&UD&?XlDWl}LoKAm*YpPlAL7SgZyPr|I*Ig$ajmUhnbK&w^7BjVI??P(? macD3wyyGh&|NRNLV9HMXj)wpv=- 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 Af&c&j diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png index 0c2c9935f29e5c95f5fc71eb171d5ce8382bc9b7..210dea2413977e75595274aef2f2c50670423c6a 100644 GIT binary patch literal 2444 zcmbuBc~BEs9>-rGI1y0^AcY9w90X(oDkBPl5s-jFWF#YqutrhtAqX*GB#2{~Q9&Y? zh;ry~D4-mUQH~)76@zDlh(SUOhnz8F5ki1K*apmC4LP#3TeOQ2RHZU}}hTSz%D;7A0`V$R)A0-+nT*9J{F4fxZZsgKsg*FZg+!-o^ceF;5?|3&h73FZ> z?5Oh2PQTqPTyZLOMUL9`J2iT*aTd-RgFVKltQtd(PhVupW5y>Y>z(%N_Rf|MeE6g% zj49-aq;OWzTn!2&IlzHU3IH@DVA&`E9X9|TApk)K2GmSI(!u}X<^=tD6NkfTi`?{+ zo!vl;8lfy8ARsg>%;P>$h?4u`=-|rB5*!W}kP1BWGBPxOy880mt-f&tAu0-&KVXry zvsO>pCK>t*|NZ^_<7Y5BUK?`o_xbu$luHBF$Ht|>KvQb1ac*#OGX9Th4|J7U{nta+MY)S>+AC7J#lt!F*Dwhc{nGVCn^AN2L-yd2_Tg z5`fZaZy6rpBF2yz{f$v?`TDZFQdwj8(8N+vnlfQiO|$WPd7(>sC0* zmr!RWf0jvvzUXXjw)KJW{+Da?QK)BQf3e<5u($X6Y`WUk)APrjcehroSYhV$h@PuL zxJZZXGS#rm+8LB$% z4-Ap~vv%%y7r7T|b3B84HqMx9VFAm0udnZq9+LMBTtR)Dw5Ms=TG?Na3K>raySgwr zo=>J49=HO+RsEcT5#drcah z(K()Fd~s@m`)SI?q;m@Nq7#fGx;Y3;^RfCs$Ym{+H>dYdCd0=?LoVtDhtCtD zRX_WjrT(n%jtCjOoF5bqCm_URz7$bI6R+Qjf$NF?OV4(Otr&%7fO@v#M3TUrGZ8JFofpjB}VUm3XR>$uKcj@>Bh9qb z|8^bfAfYAH_BCI^zFfo&`^9qAkw5p%w;OT_63y~(<$hM-Cr<27CmlOh%wRCm`UjzT zJUX3pdCNEA_h-u<<`0y5o11GVTWBXkost9rfr7Ktf>76>Z@vpA^%5Q@iy0wq0=YHZV?Wc759@L?&Gn zNhuXr%-N$~MEP!KOuARC&hRvA6O-k2A3x0$r!wE`iMFP>yRzBrpg?8lb2gUn#bxgz zC*rz?cMWxySHIGH{KKU~37b_GVYhKrQcUef3OohLw~L1ga=rL#0os4I%*u2(#am_t z&^5weDS21&`sB>=KIEa|Jsu7Y4iv>qEtyO{%R&SXhq2rWuO~Q3C~7zC^HN1fCU=OW z#5cR(=8`_Hm_5;uIP0I|>6VT!#lIlR4sKb7vCLAPvEO2vxe#imCKPJEnK1DwkH=$F zR~vbBw70+R?+-TQ5nDXqqFnF`;%}uxDMHAs%a(QK!AIR2R)-TBBYI5 zq_7JrVrp~$Qvfv@1o}}cR!SJdpZeg3lgAZ%uv5E(^44mcOOZ~m*Hy9!qBP`-km|;t zVflZo2u>PShtr=H)d@!2dDUA_mKIjpDPI|s7^j_0rPJ5mLrr6W^P(K7Q+!bUyM-?7&s+BGH^52?*4E*UzAot z`_m+e(tsAq0{^knBths~?e_*s->X-z@K)OIBo#~BkeI?2Z>NGdN1D$9kCo?n{Of(U z!u7BFprmPQYu{xu(HAR}Vw%tEt!sFAkLWdAX(1RKZM{=n#uKH}y)CjURMaWC-@)Gk1C9 zIVWUx1*_-2ITJ%6g zDB^$B$GO8}hP8BbikVylJ?q`8%X4)Dk{*02HJ|fSk09 z%&cI_iyk_4uy)cU^Y?i&Jz~C%i(nZ^T2LJ`Z~Y8o^1pns!{hPQ5%VRrkqh^dlBCod zvnPf9Y&NsGIbm*YPHjXW)4&6YNwMdu?(+21a5V$L`(H8o`N6rur=7k_QxQ34l za#|~Kc3BugCkq|XX!NwHPT6e4F{)#FllM~^?IBhz-}b?txouG#EwCJkIBAM z*kIT}`z9e-x6Ic-@srsy9=ozU@9yvK&xH_huZ!wlT6%#pi&W_Cr`xLC&`h^&_TB?U z+d`&S&ahLSp(uzguddd_gs-m9QKq4}PNi-}Rh(JVqg;|px~&zL)W1NjvjiUXd&89m&k{5v=A*(ktcFNRE0K^)+0>}F=a!uqVED>HEk=nU${mY; zcq1IQm)zRlPL}=?pVab_xXd+hf@?1LpV2Sdt%9XV{yl#Em6-yYvJ||Vn`0q!TewNl zWl4T?%~pnGC(po7T8@w^CuuEuxhJiK_!^|Oa9opBFsCcoCFoE1ngQvri)RcJ&B=uShJKn&{q32{8lKta(b^be zL&J{F&Q80^e)?mRU+->EPPL)Ag!@i~x;A)r&MI8Lf39jGJf_wvztf_$&fV)eR_%ml zdRmjW0~B$E(Zbu_dx{R`b^q!uc*gN?*&#Ozxhl^k{X{xo4J0;S{5iFi z@jRyNgZ4l5qK~RC3*JsoKQYe29ld!3s;X((+YbXHh6@Dux8;3DsyMQ6lel|#Dvnz6&nhbNZ|b<8_VI~>BThZa7WXqyZ04LRX$*Wc5Kw~} zR_6zsl##o94R&ILLSgvKA4y%@r#p)rG;a@0UOFdFvD|?a>@(Vx*35arLq=^*vKD2l zn@2U-7p3*ESi3Mh9TKhnJo_z92~j~CsCtS4YO&H~8VR=HktUjkpWfCsb% zW%;XBD41&fiqs&KWVS+%x1Iot=rzM{7s1|kEbg9(`0`DtD6VO$jrVfcH&{cs7lS_K%w=a%u6K0=z)NP}_olN+0%JD>{ zbxck^6e+8z;f0GCzSfg{Y`5XV=xF^cJTTEq`J^fQO3|3G)+}o2VL)ZEPkVOLj44a? z2*StbJ2cjU+ER%1Os0KSR#uUnM&Nsb+#`L8b$*_!_E@YTI@*uB=i}Xq9rtpzza4de-|5L4qvB&ft+@W zME~Gm#!do(@TW@Q z(Fcq&yLH5_{e}?x*#Y*3SW$wecx@MbDRBZiJ6wfk@|2dvo2M?+ydn1-7g|Tu=O7EY z0qxE4iT=C4#(@}YC0okdB=dsbhEt8}qwcTM`z@CTv~_gOM@CMZ6Z05>+EP_jRsNV` z0P1S*@zIFI<@WjMpy-;nIkh3oy7}p|th$w{g`oUU^F?KrQ>nTBBOdR0Df&G5GQU@m zod)?N2dLs<>&khy6(JKBB79n*2 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_no_flier_stats.png index 87bb56b464b623601a9bee70ccaa73cdf20ca86d..df216d72ca12f5a2a45ada4cbd933469bf5fdabe 100644 GIT binary patch delta 1145 zcmYk6c}&x19LC?$mQw^GDbgKO+yG61c32#j)PrlG$Y^y%EL5=tY;BuT3kdU9I^vcv zMbl)UT*W!bkTYDtQfWu9Tc{Kx_f?3&N?BJF+BpicACS2H@lD>mN50Sde4h8UIXAi7 z{tOBe#n44mK3kM`LC6A>3nBrRFXHB0#EDtL+#EiS7(@sn;Bna^ksvobIQYtk4MBWi zRxnv#Nd*8n3F1?80kHSqE(C=(p#cDmO*nO&mS1I>ZTkwJwO_OFcZnyWt<9s$1MkJ< zUJ5&UTj9~A4M#ulv?iroLmuaHF=IScY<%Xy6OSpf5>%92Q@zQ4wH3MO8__EPgSRV+SRI&goHN5*r2dm_M905Sm~VcG%py>zss zF0JXTWo2c>Xi+E>;nC4sdXB%p|J8zmo)*Tm6GHCg6DDZ5Q3$ORacX}WWHA^FQvqLm z8oVADajW{Z)gGu(KuR%sGL0^wtsI%NiZ&FXcb=TU==Oh~fMy<^3zus!#HVU3K1ny0fEOH;x(L|~S8@_~&MNc3lxvG|yat5EBjNAuMoOK2y zXF1oXeLywf!-XRH_Kqn4IKHf(dq&q2|Y8QP#( zR$CO1evuym>G-@y^}6Ivz3$D|NeEnple})EO}3DYg|BhnjE?^#!u!_qBtWI-OUGhu zToo+hI0u6`@E;r4RVRS@$n-6s3Lq$B^`8w!4rE(fqs7I=nXjAs`}@tafl^2E$!U{y z7E+uKS~eORG|B^as5>re%ZuN2JQ@3_6a1-0zJ%-&cV3pepgSt9Ywi@TQ+L9$Qf z#fFC=f6!4S%%7_PK2mMC8455p^Q%>MG9o$6YFSkvQ-e-24c3@9@tbI8Tqfdne!EB&cu{cMz-A+(pGkTnXdb`CL z@V`mep0LO~gOZ(|yS9BuT7|)G&v2UC=nC0&efS7qjQ4oWyY9`l4#2u|{dei^ zzJ=X`l_NLF{8=*9Vx`DhQGuA2@xVNUeU>uaY$_E}kOVkS`lr#dRTL3r5-YCA0?<*I ZK0vgxpJ-nDT!#M$kPwq}s`&&{`Y&uD@Tvd+ delta 1095 zcmZ3>{hMooNTOfY1+Tm1%-U4Nz?-#gVoRC!zo92cQy&OC-~6|Jo}tr5;~&RgpZEKdy7^`fR|or-*w?ZQw*)7?)tj8h%3r_X zeRSTRmoHaddtE9kBeUfWuf&w=*RRhy|J-!;*{b_tmpK#m#)V%m)h%aaTmE`i)cWhy zH-Ul=9z0lZ?%cVojS)Z{9jUwwO`HrY!VDa03<5q33K5rAGZ>TrjrscH$B$=UfB*h% zWpDrge73r}dTC|l&h@dkIT;s-%Uc$#YOAmQ@aMjr?)1~UcI~?L`QDp(3>Ugr%3A~{ zLX4b!Mf1QF@tlO~YN8D*=alDh-@GAqUZUNTH9>2e?1kiRiN^`ZcCU$K$ji^q-Ws*_ zmQ8>fL&vo@MhosUwtSoA$51CIajS@n;ovN`2VcHydHPIo9w$SS>EC)ihwG)w7}y>@ zdbB7mF7Dpn@8{2-FD)+qyl%g(jm?_fyLac`-5U4PNMQcAiOuON*w39l?VaEM7#Kc# z_q^9L;cfW;@A8G~2aTUFr)#ZYbTIf1#KH_L(r={>h=|Sm^@}I;a9m}YgYy%>IMiLd{^aNoZ#r{CQ#{~7(Y*dy;| znE(8RS^MWdUOWe6wuAG^Wc9-rO&DUY*Sp^5o?umGB(r!f!?Fz@Sl0G_yV(#NQPFfw zhsnu+u_1wp;UEhvcFk`6W?1&%;lqW`KL6bP@7vFxJNx?j?)|;j-rjzHS2T-YLz~&` zv%CM7?Top0^=fE;wX@Q4aC!jx^J8Z1+SG{SFXo#v*qx0!R?++Iw8I8(`I*W|tCks` zoWZcb&g^K2Z~CN-92^JTtQmn0I4_;cpks6Jxh3nwhr0C56rS&1U;|sf6jpu63Xo1%~W369zdRrd&|qnnFVJ8juN$>PY4WP~ctr@^Ha5 zhGnmR{n{lbFMoeeU44E0>8GF0zJ{jF{@BOyZof+s+Rh%d;qzlunBnRGiWBC9JeJAR znJk?hY-+}-D diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png index 89a7112dc853ace96042e529402a59e86abad6ab..11a23c2ecedf3030a344c975b4dca66763411f70 100644 GIT binary patch literal 1920 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 rTSqvczm7@)!b^gNuC;gCF@1nx*GD diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png index 4d8b9c38e0870810500a3e0a1b3763bd9d0b357c..76bb606ba6c5be8bc896d6d17d56a8c4bd3a9c30 100644 GIT binary patch literal 1932 zcmbVNYgCh07QOjMc)z0(!9u2>hyqD$LKz_;AOYi)f{IkC<2QTrOE(7i;C2sqXXk4lvP0FITx|*bj|#j_Q(D9S@)iGzrFT3 zXW#sg4+G7NZH)l{X3YKmp#Y#@2)YgR;rFGslMCUGA_iL&mdq8Mh)#(ELD3>%VzMZa zAA?VeOG)J?Cz0HV?nGBSPb3ni?j;ZeKPR{+ryM8HpHzmyON@m3BT@m-cV1tpAl8Fh zFnG=M_hqG5X~#)CyF=S<&78k%YS#0ovYaxhz6IhIk+XB2tBx$f6$e%B!I2{vH4V=h znx036vU8rf7s7tf^w*xgYha#EJZ41BwTYd4dgPR9yPM$3yH7ehUM)$usng@auBEOu zDs@o7uP~M>ru~8e-4hLt76MRc1*9Yd5CZ^E8G>#c3g8?8i2V(RY0iJwIM6bO`up>B z;~h3Bw)BffqM{n3l$SZpTw3@LNdNK08CurjUjk~Z)(@+ytC^w8DB&wz^MJy2WZl%JNCcJ1r0 zo2sj;)u+H_K>WO>#!*%!>; zK}Kb}kZsM{#su%!+c%CU$t2jrLucsptrXicYM+Hs9`f${B=KOZuLWp7-TbM&3Gx;z zq_-J~<3F-PPZabRb=jPP+h5m!H+lt9Gsl9LHgS?jrMd}?ZHd6dHlv)Df{Fp$IH?9%q&2haPg z1rlmTUt;9LiI-YHczC!-ELL=OQX?ZH`EhaEXG;_NQg#WikA0*Z9pRBfm7`(T$IPgT z^1HAt^K3KslPV9#E9DL z_wBRx8(YK}{EFB-vTkHz!lE!NyTD};&`H8wLQ<=hVfzG2^YkLJGy9x}{F|7Bgx#~7 zk}#+cVXMG9|9Q{JwbiUim&eA)1x?IYy7ir0tVssI*o>*Ln+g~pQ6S)p#gtj{&HlN7 z61Io@0^1e2@UOVzkh~()MR5A!jY(9g|HFNzef}GR!@dH}eXv8Z!oXRYz?!qKD8b3) zdsf|}jI14asWR3G_CuGRo+?L&_V)5}M28rDVNX@}_4S=uTU}m0VqjqK{^Ho{*C#u( zmgW!FmAMo&ci;YwY!Q6Wp{k(3q`Io=Sc*2IQ{FF5-goa-j-x{a)tey^#`GM5|KiI4 zskEBTQ$!-)grspd$5{L3Rk{c;mU^Qq?4NV(;h|g>(e?cz zW$i>$q0aZ)SG3x`Rt>8CrZ{993v8f*iY4ZWoh>>1-BR3lkYT1i`acclz!(J097C^y0_j;%VT zyyk^ExQ*l*S8GIak>eqKa^z`dpV?RZFFn9cuk42qVjvDZFhu`fp61F1AMC(xXIjUn zgG&f#!YomaUVUgvAxj56v6e)YH6U&w0~i_>Y=@Hm_ez=5^yChOb=%Z~qRaR1-(L?W zp_>0obSm_P&A&MCO=Kf269axOz#A(*kW(Tm!qD`}(9TnMll8LE^ft=))}kZO;QWwu zjcWxm{dxrj&lNFNpnDym1F9apt!pdfzwXmAnaovB`Wds>>AEa-2-J_hEdS!HQ@&Hf zFz*;GJUr%xLH^Q>22OL?ovVMqm`mf^8spn4?yv{A@5w&-YEv(D|Kq^3Z>Cj_mJ%0? he`R~W<01JBk$&PSd9UQ3MEEiSCgVf@7Qdr8{{~Nd1K$7u literal 1877 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 D>7wf9 delta 1766 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 CnjIGa diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png index bb1997317252800308e8a5d3ccec9c12541c9503..d9dc4379c6ae270ea116aa412a00c4c5a9f0f71b 100644 GIT binary patch literal 2187 zcmbtWdsI@{9^Qb0ijSsALaM7%mZhQQ3TiBla*uJbVc3kiYCfF0qK6aGFJ{lfk z5z9Og9nOl{WNUA0ztQ3_n|&co#bsY8iT<%&`=bFsT4@coht}N= z0O}oShX*Y_?{)u!Bfd{?ZK{!UpQ!VvmwAv-F(`wz=RHbRKQqi5ARSsYZMV$fi(anq z3{;EDT`10N{NAIKlr<^Pnwi{++5gHZCsa_YSj5 zQoth%duXe(sT(oqq>X&o_=|^YR+*dIx{I zixJ1Cyv51lr=|gd3Hlba{LL^}a~S~5dO&Oshr9tW9J&ql(;&-3LaE@uaLwoAi_J=? z5?Y3DY?@nw60;}>@t6&)#>ljDRpNi$R?Iatw`p+a^3AH<1RXBGZgcnV4D3j^xS{)5 zm5$q>j9$^(*GH?6`PB74ZfoN~d%Tupcc8v=YN|G9J8V3I1*2YR*gAoYU*-Cx?_&=t!e_(X+fM_c!XR~K`_zbN#yx6E@vHe;{y|-}*-wor=c0}W9Q*d?lu<@J z73LB;5rAqw7&@kp!QFB2%HueQk`Tcb2y`Pm63%NJm0$Os1zG-rQi(+3 zYko2$pMBzuj)RaoYnGU(&5aLYrS9%PrDVV}^(Rq=($Gs-WM8homqs&_U zO&i=iymUcX-I9wu=R38Izrct+`f64v!orYg^%S=p4*MTYI#-AI3DY!V3O}dIdn;rH zAufRM1`n>ut~oh7%Oa{iBb!%o(Ghv2a&}r{HKyq8^|`rEeIyh#H}8TG(uLL8-?`?W^B4R8sY7Lu zsIs_tO=jjStD0v0T;2&1m+dlP1P6QA*w|d7S6GRf=cfnf(Hb3{zBdAR_md~STMPYa z0_p26KAf4UZ)!_cNicP3V$1UYn56Uri%6^it$lsV{F?g;s;9L!Bb2Cbn3M# z;AW=By%Q#Mb#Zz3$3%YqsuHPk>_A~b!Q)4d9$A#~L}ooBa7OjbA9o@MP=xk;FKRCwfZ183;4RAKlZonhXSl2TND?b#r79FlGOUS0ykel4IIph+g z1Lge3Vf*(2sgfP&4d<*Vaz|4I^s1Vga(cz{L~FuH%^&$EYVro^6NVD4lM|*V1#~~Z zi8s&xF85X5<$D*Z)kv;R71Z^RjMWs79~hgMRNc67<;iVUT>&}hegUDP zwbhg9V5W^~IH^%TZA5&G`XE#ziMV8v`cB!l!umFi~HKz;cN=cwuevf_tJmtx}M%OD0p=_UPjaTCLmF9|0ncju5> zC)iV%bJ*!qi%Rx98&6>M*Or)=Z+!vP@;`7OmXKdSNP0&t@gdy{iw+feMAzh5D&LMg zsEt=$9qLhM2?4eY;yt8Ta;Z2~D@?}r7%KWV;l-!jIFX(_dN1pOEQk|$+H IozIIHf=|gP(v+^UF`|Q+$vX9wWTH~ zhT2P0OVPBMQnxfn$+Zc^ZSLPFIPBo%L6O2=lrR4vxi z`c=cm`**`_>=$nk>V5dU~3V zs^GFT)kqK6U@N>%gn`xu3|uUrdO5J*fT{nmHy}88rJQp;Dk@@QVNliMA&oY~k8tFc`A}0OnQrG31GnP~mVoY@+Z-^=Ze z)JZ(AA)ORot7;QT3~4_6{-XjnRYuNS0f`IpX#!qaW-yLz6bhTx@Q(^?4)R1n@0$z2 zi$q^bnX<+}Yvsd0nh6>>h``BC6zIKQ1W1*@p%6bH&lW&A1mYkkf&5CSFt-*?T3_~T z^elTtz!jRQSt~QU{V?CbK?0w}r(B&W6l@B@*@7Q&=XrOZz)i*c>%02($f!@Z2ZNQR zFxudk>cmA-bjFeej9I^ea90|ZRFmRU92iE>AZdpo$szzr9tPx3HrO+8(frNjJk?33 zI*yTvNeq`od+=Z`(hZjaLMqtv1>5akMXSjeW*j3U;n0NC4pDlpmy%3dcDpo|sV+s# zBiqbJBabx~XQ^7! z+9-j6=gXYC>!x7qj3#i^P*Gd%RwZX~WPE&Zcel;g&o440r0B^L1B-+m*Ne68Yk|h# z)ao*ax)$xeMu~R6BYX4A`}v4JVrY;0E65+(9+#A;XJ+mNH@j-jaQZ;3>^Tf6G}Ni8 zs;Y7@;O{S(g2j&lnJ);$7_SCi;i|1ID>*VTaZxj;|JAGH&aSS#ooE1C)dO5E*Huf_ zVgJXD^Oz17^&}lbD|0xWiqJ%D>0HLWKudm~Vs% zT%q`r-A=T1TA%-Q_LPGuGWn~g7*gOryiD#asuu-P(}rq)K}lUOBz=!5nOs-EHUN?} zGuTVuEb$`%iAr?QaXsNIV_3vNA05cF3Qod^HFBe1QgD}0mXu&(il&}9c6dv^%o%^@ zuAQccs*aYIvDnRN&nW${rQA~unVLR zMc<>X0`)ijW~fI0a%(AMarXB1EfC(brlwpwO{0@?|E`)ltqQzC;^V7Fn}QdIYKXPv z15LTPN0e1m7Ut&OY`DgT3HB2#;x_G$d$jeve3`U3zpi)GU=x)~IGbu?&FAygiLoDA z!v9%#Dq)~K&hX3AhK7%^rWSF#TTdznE8S{_-Xz}OhMC|8ICY+pkPme=&a3*taE`sc mE|4?TmM2+abMQ#LBiC!F>DRm?u|CkH3h-wg&eYnFlK%&-(prW9 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png index aaa8846321953e87443efbe6541f5030dff401bd..38a0563aeb0f657734bf642fe6dc1a57262af081 100644 GIT binary patch literal 2013 zcmbtVdpwls9)I33G_gf(C}uTk8f8ajki~>?%_uV(a)$Q!xEz*f42IP()39j{MIAn} zJj+TTZVOsXcC6U~ka2}mKXV3n3KA-*Ld4KQweV*U%{awFLk}Lhh z4rMK60Kg8KlRXmv1ipf1j1qj#ylx1D!kp{C;<|+ea=-SC2mmg=Tux{hH#FD}7ab50 z863tYA0-_n5ph9WE+^8$(D3|!4;&4P_{xwvcGC?S+0JqDj08YQZ)+hg?$6o**yc>L zw{efjc|AgQ!g^xc7xE|7HCs%Bci!7|uafnwjX)Gdr_E^Y){P!Muk5S8@{HMC<5|g} zNKX4DCzd88W<(H!LzO;Z5iz=)Bcb+nZI9G7?dIqWq1=D|^kH+15p1$p9MiPcwE2Kf z`5T^g0379O@&IVj1)v=Rn#m}@xe5UG;2-#uTTT3Ztm$IK__)7i+$w2;B>YlZSLa&e zbw>Inecj7OLAEqA__Re+gstzDsHcPFMO2uny7tXWU8dm5MAS7e4 z*ph*B!91QY?FLsnmQ)&d<%sDH(ec5*=;&?)uy%0*-XqtE@P%TD-o_ zKX&t{mM}k+`@1lQZ$aJFq>G6JoRPmcmaS+B{?g*L@!EZ%t?+JO)(#J(>oM@OvuDqqGtr7ap$L75S)R?hk(qgj*45QD&9#U> zf$scqx6r@jZDDUvN#9gZNXW@t`klv&P49|3Sy}s*cny#3q{e*|WZdlO^XJ)CajOM2 zHOFjmm5#RyM@B|k7#jlvXBuJ_Y)eZ^6IGEu_2#Zj!(-;=;>n);kcbFoeSLkQ=u*P) z=%`2S@R{l%cj#1iH~p3FNYa#o4S_&lu5Xg_%u=H`oC2v-+M@GGRbAccHE+lIk)6kH z)sz2>5Q)TBZ{IR06w3Vv5BghLY+vb01AcLr9Zv3N*ABc{8DQ6{JDi3FJMGi(w2~{T zB0Czh!JF|~D;zsBKkr&yU7g6EW>rcgg|OHz493jFguPrYhiz0S$~fuy=*s8CpM$A4 zk*~bUG}5g}bj5DHMMM}UbC<>W9aJ-F6*sThfj!iU!M^3?dm5UWDq~#zs->l<+`K&J zwA9p86Jz6pwCwEcCr_R*I|}*6DBNVZX&$$Jq^BZ3Sp(%k=+5*{xm7ZMQ;0|5RvKT# z9H6ZWnmCA0_|GgGFp*P}qSv8Wjre>w`+)cS>`+WX99bJ^{G;2@?ykH3)JXl;`3eO= zHkad!Aj-m!C$zQWKMtM52O(j1s>qW_&C0MnBt9<9M5}3xEV{S{jpk5p0HJ>zg(PD= zPNClJ{i_<4Z7KM`nw*>*+!!7j%35At-dJ2@ii^Go8!fW@A-0uHE&92*)2%2eQXnWF zA0L-&6vO^D$>x4*rGb5Um2x53H2kj20LZ=-9}*^3ecF-sQAWN{OUD?+QQXvpiaJ`1 z4pkof?<^cXe?6RRuR4(Jwfwbxw>I42LPN}j`tO#`7XF6q-{z<Pv8 zLDX~64TlEI1^Iq!C+p4kOHKRNZ;bjh_SUW7ep$3)w@PH@<1fYEd?-M5>{DfYHX#xl zBL;UV@x-j}0dY$%XsP{o;oZQ^0-rFYzH(bQ-&ao^~GU&*{)5_N` z>~WZ0;_bVp^fVlY*$RM3o?~7PIJLJp8g;;$NDN>rlRN3x;b4AIcjRG_pW@K@Q;Hx! zkEG*<_!Q2fs~$Sy{!czs(EzP4UBj`W`^N#aSFR{G@oDf@jS2?&90E+MQ1%0>Zd;Ok z)homKP`XrCai=gsdN)Fv(^&&cL>Q!{C&pf{yLLRwDGk5f5(9kaw<7qX*N}>~Z9+>_ SPD2vBY5~oGZeMQelkhKw1xDlm literal 1960 zcmb7_c~DbV6o+qOf{_4;hP0`)36?-5(kWOg3=I%MSOSy+QiMQ-BAcL~h)4rMkQUTL z2tgo0Hc=^yh!inu4FnMaRn~%{BqE?=1B5EbzV*fGbory5zL|6Hd-uJ$_q_8v=Q}6e z-5j+wH){d_w3$u}PXJ)hEs&x#pmAI!356!aVLHcgoGUEzrwvL&k1>;OESzKkqc=`w!>1 zTotx%LQZ0}w_dms`_-A_nws0RJDeCnhAvJXr*wC`Z}<`~W<2~!dO7djEz#Ukk~3Un z-m=uOw(@-8+tBSISA?{8h>ig?3j_${0l?b`IA(BQ>Ht713P=esK!BXu`#)aJ?rP&E zFTP@ig@v(~=MS}g18&3|J60W0Um0_wV6pzVhICLIJ@f4DyZsUM{ALgnLM6I*F2i2@ z+GFq)1>ZkD11;`Hs$gD}9Dd&Vi5>)kht%c_*6VOg%K^vOkXc$-_<+1FE1hx}~j>8J+)!w`69`^P0WaW2+4x85^6)1$RN45915=Q4!P3Ws_D@k1NtgEZDq*Ar4$hz{?)dsetD8Hi&8g+DZ=oqDKsWmn@G}Nenjr0psz>vvg zEEbD0(WGBhRn<5i5^Gr~HDC+Nw{dcE@Cv#7*C$VC&`~U3-wjq|zuE##d6c+c8M}j^ z)u)W@ixS&GJBAu%x0Cx?9M5NwEB4g<5Ol=^g^rj5Q7VrfxU#}>VI@7_JMBEEaqMUpT# zxt6qtlJjbHW$9}AZ1WWyw&Hr^lhlaj>kAYJb+E z_Q}T}(4^9krWNuL|6+MAFgsKDOPY|uAFkn6!cU~wjc!=5#Q@V*H=302ISjVf3qk`M zGIcInL5BZ>80rdDr*$|S-m3Op&5F!*2KcJAdGV{s=gK6Turf({P4X^%1n{g~8y}jP zwn<)?ZX8<8D?1yZJUdIgkoZ`3%;Ftt*!xA6Z5Rrt;86USzD=nm&NS_a+BFJ?aKDh2 z8?TM}D~#;>5xk~!45?wso|KoD$DVrHesrzOi*nJ*gmDVAiQ0pw6vg7f12ao2Jjc|5 zL;9#do+hXe#u+znH{zAY!9TPK^7&bg5AvkiIv|03_%b&86h3(FBn52!JVnXCs{=yI zeXG!aA<)DJ@Ei;vFpW(C1`_z7XZK0j65?1dNNN^A1jq%P+7g&l#G(0^z`)*rTH>cz zPq9Q&&E=w*y-H=pBh@wXzNjd-$jHbo>7k!g{)VJko3&4KiBNRa_7*B{g;1|){>klV z=m@o5kD<(YD+)ySL;6k`iX(!IJNqo>%^(!VQ_D8SV~i|%TQ@;MM?yls5fy3!wc!El zrb2Y3B<@Zid^+C~lvoa>wlvebkC{YL=Yb9a_FDLX4JZQo5#m-uL+E>8Ig`xXo?xM& z&55YbU;Io!<rB?yJekB~03mSg&4kz9?vy^Wp1hH~hVC_;)^K&9JSq$1fPCCGf9j tQbi(HqwMZffYng{!GFd4UmMnhP1bbU%`+c$FF>spFzwwKm+67We+MU&F3|u0 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png index a47dc0a47d214cf92b52e527cce84d6fced7be2e..f864ac1c4267e5b242ea71a46e8e415314eceebb 100644 GIT binary patch literal 2774 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 literal 2721 zcmbtWc{r478-HdD#x6@)zA~R`f6jG%*Y&;E^}g@(-0!o!_x-znzx$S>{Rt^? z6>$K76v5ik82}j61Z<=T^yJO6{Gk^j)PmrGgf0rwHxcTKg;@U>3V?{#ryG_lkNpk+ z@$U(i<}Tq`^CJ!cX?-fKOSy-TG$U1`8weHo#B?@tSF=BjbZZYS29>WUss0j8LWt}s z@E^1I$x=xwAfB4uVolTxnSIbs_^y{Cq1inVfQW3Ipml7fj3|KibhlhfUF-wF!UMUTXPQieccpP5aKFd)S!L;44 zY6kL<=CU)d>BPaOm;SKN#-^{yo)HELV;`p4=jP@(gOwM1dV2C7Jv!8Ojg5_psLe0}XSTbrhs#~4)|CZCi4M4R%gvATsT%V`RSql` zD_Jkj_l+F&T+uj<#UeolY}7_YL}aA=)@*K@Z-q~zh~Hu5zWlV8ylg7uzQwV|h{ZRt z(rP$6q;W5o%S=g0X>!>9K0$i+NhhwG_I-k!8r-W4)NgNGWO&v%{}ndRd7iFkc=+&m zt%k<%;$k^;tk(kr^!j>SwpnB^nc%myf&W!e&&_gUL&shgQA=5W{4{9f_x_*Fu~@9v zjNi)2iZQdu6k0QEVVF+Al|4D%shit*ak?X$X?wUmgBaUATM-cvfiW=1%gNc7-+cj! zpPv=YQKwpkLZNuxd)?c+dqBxtHXtz2&f{qy;ay>}jD40d?b6WG3-g1Owb5I(Xs@~= zCKKJ~*X*qMwpqQ*9dKegX}r0)vmZ=a=T4hOj7qvDOIoVQX0*CWMm{=S6=~vf1w{_I?LmE)NZB4 z%j)=#TQ6ms#<&x)+4cf0)Gd(6nl{R@=CIj#bvT=UYeE8!dP^A!j|*SY1(Z534?zXr zZ$fOmxdbfw4XhWVN*Iiuk-x5ai&OtZD37;6=H?Qus~ii-HueuFc6k^4Ojwv3+1Ry$}&yD@$I#T^@0!WbPW)pmHM zB8^9?Ux2&0y3!qWbTZoM1qDB>uC8LE1#1$W^d|ScusW{*n1P{Te&Ep(lWPkz5AWhQ z+gqEfE3-s~7i%MU=jF-@ra=a2tZ*8XdQ{mD1cuGNPf|7dsi)Z0hh<7dT862H981l0 zh+*p&s>W6$@ScI(x*sJZB-*35hlhtH^tgR=$T2-7?zZ~+&hHSG1Gh6xFJ~J=BWyR_ zp7nA#a&5eufAh}$?WF8AUti~iL(=U_H4r-A^5ihe7|2SPbPY6wa;&d z+6u+ft;~z{u8+xJ=vN+4vH->|Itur8 zQr1HD6YUZS+X!Vnh-z;9rZ9@qb2}?U_0x8&ZxW%(v_sn-g~Ptx_8(**1%dwtb<>@> z?ofJMsqs{3gy}dz7%(v@37ziPpV59HB*c;KVx#Sl;{r{1_AHJY#V2ij916DWR8v!P zmqqwU$>bqu!fb({j_FzRpr+>7NG;ix!C?Fp6x93T#hv1klGQE#=*5@68~R9zq7bww z)$&J=oRX4~?)_O@tg9$4Jay$*&+FF#{DqN?<<8@-q^*Xr!NI|2Dk}I}>a(yqJ~wi( zA*R9q{P~RfgdM@8cT*;n>Oo_Gf`Wpym;U7Gj=c~8UK*};Dsf|o^QK^ys0whZDk%H$ zz&p-pd!`^u3|Za$=nd5;5CNAARRNxG5Oi4#D79!p@o$ifw_f4%c^~J84w^;J4;too z&t~ik4hV3(cI}#`O=GCs=3gMuNGtU4Cx|^1g{DFHGWaKaeNxpIis!q@er-~(&uW!@F zT>6q|BkpK#+?<>V6!X!GP1JPphkahlCTHb<=NC zN{iMO_i1ojuNWE{y7~rLCky2~pNSa_r>b&Ch1aGs zyfH_POw`_aYzGlZNm)IozyCL$^YbgS98D={={blUAn6`xYKrg3I+nV&^NST%l%09x zJO+cAgeG}bhnPdFZ$NkP-PE-8a}e_$Zooh?gE~e8jlSH;Hpd#%0h!9we_@BzK ak7{$6yr(LrXN;j56A-NIEtwW);{FQ?`OU}x diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png index b65a77e86687f554237bd9ff3b836d6f3bcd4c0a..af0fb83ddd221896d7c9e0cfe8699208c5886c23 100644 GIT binary patch literal 3108 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 Aod5s; diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png index 45a0fac0fe81bd48dccb75e7454d21762805df73..fe7b0d37fb12559f3dc54c41f7a5e1132e830c3e 100644 GIT binary patch delta 1913 zcmYjSXHXO97X4BP@j-?VX@W|`D2uZaiYUD)Xrz}#0Usy|p-BxLO%OK-tQ50rP>Lu5 zMuaQ~x>Uo`R*)`)PEb)GQUVDjAbIia&YSn+*113K%sKbmD%m;NEF9T@WOb3`66!~a zxf0c)fIHf=VgK9J#xGNAqMO#?{U${HHwitWg9lI~!~6`8!>4i)AS?yYJdl>OWrQU8 zs~!CmnLn2u9UV=Y`BQ@44SZ(j=FW`#f8oUtRDrZy4x2_@!$c_Sp#zmyT2h>OluUh!zaQ%fXWr6cy#+N!$|t zU1iXhj73&gS3gZn-E;i-@gt^iat;KTj7&_pGF%7#`s>d5;(M=Ny}ERYfrpD{dAx8v zWz^!*((`_Q|KQ+vQ8b~YEPBpDlkQS^@qK$cHLcC(!>LMxNhBQSq)E5gVVPOTqO*C7 z_$8@8aOZaDP^35dpXdlbgG($c*uJ$7sLgl^CKTyfy@b3Gwx{s`0RE z^KlaUva+&@-1mx18TdXWC8S`R+@C8(q_&p9(&4t4m4@ebvk?ZoTnY|~ zT`!o9RU_QBJ}gUyaJE@I2?#o}>AJB+L##Y7tzg?1tTzS!T{YPnc`ICFxSp3$HBCGO z?qROoQD;I~ig=Yif|NE6rji6&kbtE{g!Bd0V7Ak>Sc>leXvvd@6m2pNQwHO19K})w zE8#%;<8%_FB`*XX_&QpQF{=NHDlm2M09^$vn=_363Qdb)SQjgz^Huj3x` zgcrlk6pc5pwFwnHM!ovKh=iBC$vc7|W+s=#2zKMQ^qS3eb>r(7&gStHKz8gZd3^7r zXtrs0b6K@c#Jyp%wV291nxBgQrk_1>#2g=V(t9|F5FIdguOczb%4~PFsG@;vMW<@k zh73H|9LU_8`fn(|%8zUH>tN6CmbPictnRzL^QEGlr_A;@5tF|*Z4St2H0uJ@+wwln z#_V4DbmNQiUh#n!g*Nd|KS2O;PP@JM*rK6OYMdEtgj~n09KSk7u3N6tAbviriguV{ zgM#VmY*$l0g!f4x%}TLT`JT>3p*YtD+s8v2^GE*ad@}{AC$DezNw$jl!)Yl&sR}8v zt4&BGXQ)9F3f0eNTbYB>s|cWE4FEMxqyT2N=kyw8vb2HgbKsXRA1XoO#NOoZ>Gh$gpQ`|?9mbH zOuUq~Mw7*GSt|kz+82^(eF#0Jz#6f1mW{*Ex`1_p_y+Or%UVSUisL{#AgQtc|JmO; zyDIfaMn)zmKHe~`WX6EzBk6!14KU1mnyjUdqD0r*9a=d;YnD_Yudfp28cbovQUGzH zCwEy!o(To|p#+t_Mlldd?MYb5mG9gg*@IO2T#bw+ky>@YW(pRmmY{Elq6D8*5t`h> zX5nTdYWtSu6;y@T{>%_xod@UIsh{-0zk~vwMscM6kgC6vy@htU&xD0l~`tOoip;TmJ$41)2Q- delta 1883 zcmYjSdpOj27ytguG{!CCl2Djc>P>PFWtkxpgLsP7E@M!PTgD`V+-6*M(r(2zM6U7D zhNQQW`=v-_T^o!`lS?re*D?)jhIiU`_j%s)Jl}J^=RBW3zR&r5zURy-JWWkLy32ppuqP;1wP@tkXAGp@ECJEB3tqB-g37m8Lt13WJLC#R`w+4O}@zn^N&W`UUX zx!;JbiB$xB?h`fa!^lQ&{AUd4?Ci{Z>z~=j^)`bU{t+yPX1=X!vhvl$VcQIUhbYg7 zp-F@Z@WtAof&v0?HmEvsSN{VDz>vw<%F4>zyLSf`7DA>^(P?VHkiN7Omi0%#8E5AQ z1pUy!z}HkJ!xjKoPGO#&k*4MC_@i6w87kx-zSHdBO z`uW+Eox{t>$fRPODcUIQwbiLpLp&;{f$DVo+4JZ3ZHp)Cen11%b<61Gpu|MtZBq=U z+Oul54woUT2hl7Tx|-5ICx_JJ4i68zR}LwP*27KJbMy1v|2;tAWENj-Rx(6N96NSw z{?)I0Jv}|O;rov&OOK6=I0z?w2!z4rIKi{Jx||KvYAj-P@&)YXty>GroJMsGjiJs? z2Jg^!Q=V0K7WfU0Ww*tCn9cSUcaf-tG&VL?To7EO8HyQ-i}PZ!Sl6voOMlecuDEly z%0r_z$b#4hNP&UW*w_j_pIT5@7!nmVOl5X|o^hXaEGr4AsUSTq7kkID=?45MwRwK5 z)p_%k$dk8A%kizM<$+geV}@A7nZhQsb_tqU00lm^(8Q zps{~{6Mu7g`O%@nhiUxLjg7k5+1Zkiu`)EcuzcwwzL~f)!*^4(_032+@G5#$=#i(J z;!z%H z_%#B?XJ@q`%9?YTbEA0ko&0rm;HyrvgrN2jDWvMGNOp2{sQA97^Sn6l?h|t46R_W3 z^?}7)VVyp=*)Q3_WSamjv#<^EHJg2${`AZ~E#Xv32YGC@SU$~QMF4F)4sO3@jV^4G z{|Cnu;IN>zho_CEf4S-nhf}Oc;_odUfH{J22-Pxek1=w{%Wif0&&Gg!_I3gH!_ic0 z;b)E!4)_LD(S&COm=>XxbXC{Ho}pRp<3G{3@pKzz6vF(c^?6pyPTR-1IfwJr#HFo9 zxAH+;-mX(fa4oi(IKlSN-E&<1Cdv!;s|5;fctZ^^REEzn0-QG#8hdZ|Pfrgell6bOKPAV^k#wc8gHPs{rWjht(aE(=->qt>_NPfZfi^=TOoCwq z1G3^x)Q%MA)kN6t&w~_A)4C1(&hr z9hS5DxCiQ8bK>Vx>@d^*rEDIbU@k%5*R+eN0G(vYLr9fv8G9*(kuZ~n8u97`c$|yf JBU|6({{nBXcMkvn diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png index 966cc5a559cd7c787d8a4fde8fcc674c793bb5e3..279a9dd1c8b5a90e80593a12d22bd3d587ed51f1 100644 GIT binary patch delta 1984 zcmYjSdo$#!a^>Pq605ShD31onN!WFdr84O-la%u3MKs80`rK=K@{3> zo*Mu_?b3;VMgo8_RT<>OeAfy9PLtta$BfCJRoDfeJ#NstP}FA|hHLtdD`D>tyP0s+ zyp-TjQ|yNwvt7FLv+hhD=VkM4vE+R)yB>R0^X;pZ3tE#TuQ=R6UG)Q@THE8_BU5uO zO#h?(Kv>*u=9 z-~cIQYAQrB7_P6cU#{SUve_@AvQwTJkqGv+Ud$5LuGg<`l5aUqSk@j?;7|cBwO;KT za+%zS<@oZ8g!5ka_J=|Vua#h)gpl7XC>xCQ7QL&FAC^Cz67gS`cZ_-|&T&+*Ymj6miMj+RA_|obqyKVsPj@^T9bZkdF0IKTOrADY{W1NTPf6b;_q-ang|nVFS$ z1zXK&nIPSO;AZiE?dyF!?sj!fOq6aH9V?Y>o~`=VPE*rfwnPP*-8?G&&WVbOinxW* zWDieIHK_IF`0tOTQi~%io*o{&`61h?%F2MlsHAEzH!OS5)#dW4qa&2ZV-5}uRySNr z9~>BPt?P#obAf$X4_8rAj7lK2wY8nN(|6oP;G!ME=X>__^!#e88{p?RvMp3^DNe!7 zNFnmjR7=aU#l^@dYyE3ijSr6HiXs>*?V!NKesn zoGrt(oatC8g7YqwmX(*6wOc4hZ5len}?$WO2zvfbDp&RPIp`m`KcY z05S8EJ5HOxlnOEebVGqvF$vgg8)zY}e19L{xJ}Uu;f622_DW{r-7-ZY(bVeVhwH);p>JqNMEv!v>a6t5{4!;8nIiCQdUN-9hWV1z-`_uOV`X75+RSAJ^`dhW zZ{Uk{mJRofjQAP#nQN(hU7fhj zndDS_nQv-ZY2CJ_)HHnm(YGw_Q%6wl=2&In)A-T{P3w+^pQr3?SJ#OJg$2`huV)B8 z_V*P{4bIDYH(tditc{m-U+I^neip2CFKN#k?<{Lg;#=#FFRn@lhy>|OBX_K&A5krlmLnc=g4+qsp4$~i} zO6xyS$BfM10`MFMm!u8^J--jP-{DZ-N;8meOlah;E9Og%3m6kmb~C085}!OO!vXwk z#PG)XwZ&FXfnvb-!A7oZz=OU_D!F3F)5A?_$Lf zp64Jyii&0a>acd@UCsPSBKe^pKfm$QYIE~dwqzi*C?g{yl*`>GsvZg2zkVL9h??^? zU^z~y#r#d}R1n`6z|IQ^@OKg5+v5x2^IhQ83)*nB=3{+gUoQz*Ok{?U0(=2FR9JJ#v8nq6(e0Y)4pkeI%6{?;opf9HS9n z)fG2lwV({~Ypgz$EuO*ZYpetWP<8P>e1vd^4O*OsF%j2bG*zbac}roF{$CDJ^kDd_ O1&pK44%Kv4(*FQ4!+VDS delta 1963 zcmY+Fdpy+X9><>{wJTlM!9WAjQOx(S(vIx|wBW3JqGnVKsHgWsy5m zXd_F9&82N!D;39ZkU2~)n>`_A%#v}<`DypOUg!K?ujl#Wd0x-=^Lahb=llK@Yt7)& zj3Cq%CpRAq{9VHY-vj`ta$Wtd0Dv~$7zmNhei8r}oZBHsAAZ5d;oTR5ju^anh1vsGD00%U}q|?5%+4*m6PUj_H z7d`L#FtC|f?fYdjy0OUUIsw>*z41CII#KR(c~GOp>^>VTT^l(!Cx|jbB8B@j1WAuK z0oyVl$SF4MZEqJAn_eEieYYA7p5a`6u(7q>L!p#(b$Q5~1&|uJd4|LBd>wLJM@Q!Y zvtnp;^y=`vyEs6__Vx9J)cWr~c#v7~y0^bSbJ{}Vx(k&kJX>8xyH`9s9B_Q6p3Js- z-!KNXSmtXM?J#Tkcg)THgh$Z^Y6BoGv4`31>}CP7*JZ=H+3;8%I`MKrL(g$fqIzxHO^Z;`#r1;^tc@Wdf|1>`SB@c$v=Ar2D0>%g_6hIHr3kBI3AZU{nA5_d5;8d>@}05Q|e3-$UolxhQ0#l8%l}K8-7?!%*n3v4A>()e~gqyWDB+D2IeAa_G>p(#A%64^PjS z81l=uwzlp8tM{LN88^@{nN(wih0!M_CPM0^`yCu4Z{8G4Tjc6>l(*FSdwhxxsAL|6 z=UV?RxT2L69*eHhmqk8FFZC|Z^xXzqhM+(OgE102oxlnTa;8uyc{w>fa(Q*+)vL!V znE_XKW%S~=KwILgK&hs<4)PsQtBM^Q#vAXm|sY?9s|Qu`}L&;zN^O)-L7>Y?MRHQ$s046i9t9!ltF z*9kuAg}~7Uqx{m6lar^OCvD-N1eT7%Z`F1JC*Gi7!Ti~niVYI+B^qMn9qGsDl_WCp zOOnK!5q*p0BthWj`CI{r-O2zh!*_^>Y3F7m49yQ0osVrD)GA*me$* z#h1}UpQjj&ihPBUn`d*>1H~0t#rnghrd4V7@>o4#a{dX!etqRi*XJ;1cc8uG#QGwQ zu=bf`zqVXX@1}o}KZ9N@zkL`uDTQJymZWpbtCpgpvSmnf4N~08-!XE;ot~=9sh4(4 z%yDW>h+%_K(*u>e)bVP>IQSK+-6=&dzy zv)=g0!p`u(zf>{!b~?3%90}85qn}_B;NQs@z(xe|pDdxaT5%`G##me~cb2&xv$F7! zJ>yX$@pw{+n;|8{@blT46pOcKHQ02`$BS92y)1!w+h+?-}OD_hl}MSAqAo0Y`F z&08@$UQ0;?Ej@MXt0@PUW>Y25@X!U?dDqjuOeY2LUJQZ9Grr@@gqkDLw)=oherp4c zk^C5A7G{p#vWK%vfO}UcVB4WlSQy$*G4Kl_0r?+Nmlm8|%)*Bb%h{WA(UhqbTP4K0 zCSt6I*v{Vi^J*A%F-gf0JS_v{N2Nbnxd)|Be56x@k6;+Ndku}R>jcAg-(Jtj9401p zO2M5EDB*-!C`j;%L{?R6VKTbWBxpmeK$%)EJ+A9gm8U}730kZ#G^5RK)2i~&vr1wV z3LB&bU!FDCtApbv?SP@R5w9O%K)EBp(;OIB^7BNav)_FeU}9pzxfaXkm-$yNv`>tW zkNclGd@KfkSQ+5k^&z;P9z57^z5jt?`-TQ=gT%gDD#?7q%YUnG5Oo8fj`rwVb5?2wB1D`rP|}x} z&s*NUg9Hs>O&XI8zDfW)PQ_9Fl-bD1z5tZnfdfJMU^Uw`(R<}`xIVb}Qp1`)GeBly)z78&0#g95gEwG1r6Y4e(j_G!b(`CNH8{3`}-F7!iH IPOSL<06RWmrvLx| diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png index 9d47676beee5b6c2f317eb3ab2510bd274378123..ad938d741ac92d314c0b4bff6ec18693c445f54f 100644 GIT binary patch literal 2218 zcmbuBeKb`28pnS#CN>R*qfA8OT|;I>Vk(9j*R&3mwQ2?AoljU^AQ^$p=0IQR?z z$dG!#GcdJycEmCIIMwu-k|#MD3!S>cVwHB{bPPk^6n>A>5jh?b2@}K}YWtxus55tf#d2+)xD%O_km&!*vb6hC z8}F6ZOgCM+eC9LL*+{;Wwhuv+?G_jVu?Z3oF9U$K0H7TLV&We-tR8D_QN1W2B&VnU zI4op}V%77v7ZeoWH|IW|UbuAf1q1B;?!y7*Xv>lEz{DXmE?~m-CVyUSyub1|-ardq z@WP=8Q8-nne%RI^jAlx_VveI1_JWzJWYG8o4Y2+Ol(&O}hYcu#qMbWqfEaVf_O(=c z?@UxweN&U{JVaCj*(_MO(8_y1|4i+R*=1sC+8qDd2Lg1+$;l~3I81VLJ0cqCUtFw{ zTjz1jaYU|vIX|ZU;o92ypo$>H?i#ld2&H-ui2u%Ge$^IfzRN5~vfoo1ltYzirdr6a zF^A(riSAoCOjIM|y((^9{7Du7aySj#zqAR5XO`M8g6rkRaPp?cXZ%@Ab-N zKCi9*$|eblvV;)e-|3Y_M@CZmVuI}K?6_oamN~{>sZvJlD)Jo>ojY``uD<^6!MhWU zK{$|~qoHdW&0tWk)z#JUz02M;Qu5LE2-j@9G5$u5QC@aKL&KYB=a$E-!q6SQzP{ys zF-`H$yRt?#v6QR*{r!u-_SscdR_Y;t2*PigotsPZP#rybbZP$G{z`!$Z5z^40!;U% z@s-MiZi(bn~>FA|fI#Ww>X5L%FJ> zqvP!4G&%bOw{hb}#haZs3kpnx(avx_sZ`2NPQF`GVshfd2}XSUtK{o1wWb2c(^>^n zQ<3$0s<5*E{=}3J32b*{+?n1aW2c5dl@c8 zZF@65n4z+_xcaAPNG`q&tQu$?Ls{y9zb(u2Y_7eD7q&v29t}TR_=b&flbW?w4*- z=^Y&%B`dn864O=|W%pfn9C?p=+LgU!ugCe1*FWYA8M_)98j{@IO;u|Wnn$l;+m}E97zO0aqISCnPk{X zTz$5|^RC=gwE9rtV&As5v}q>cFR!l%TS^dITsh`?tuVtOoz}Da!b)edllSI> z9xD$JAiL%p4DoUahZAmn2CO}-lz7dk)!L|x$QOFO)90A2W?Xk%Q5Fxut!7yKHWVPj zgpBsxW{ZfSO&{Gk7?(J1kH+cX9t7s6dwG}@&U>xL5H#MNML;4|)up){;QqrF@SFCV z7Xtu)QgT66P`5mA+I_bbb?IG!1L+#gu#fF(MbR9t_B}KaIlQL~z9o`dPZ3M%Fqr#9h}ypI2X|-V*LXTs1MX zkIdF|++K`}PzTYCuyDqw+dsH~!W3lLf(!71qo76)`agkQyn!qn4uW~=pi6!UKyUGR zc9y!Dv@vg!%PJ9NDqB^ECAtB>9QWN%o)vI@7A zjVrfcLIANeq1UUe9JL9EeHY@$y17Ph;(rMXzl6>D0%JZtHD&bzj9)l#l?{u+zhRByp-;hiPGzB+>`Aw0iTDNue*SfCN m(5eQKMj#MBZ}7+0(^cIzgpyW`X63^F4S-7VJ0S3)XZ;hwZ?2sH literal 2163 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 cSuUSQELBoG9VAv9F(Ll6ucLfu3J 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 a4$bdMG@0Bn6%HO9K?qeHl`18Rm;VCSV$Od6 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f1074d38e567..0fcb2eb26cbb 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3223,6 +3223,15 @@ def test_bxp_customwhisker(): whiskerprops=dict(linestyle='-', color='m', lw=3))) +@check_figures_equal() +def test_boxplot_median_bound_by_box(fig_test, fig_ref): + data = np.arange(3) + medianprops_test = {"linewidth": 12} + medianprops_ref = {**medianprops_test, "solid_capstyle": "butt"} + fig_test.subplots().boxplot(data, medianprops=medianprops_test) + fig_ref.subplots().boxplot(data, medianprops=medianprops_ref) + + @image_comparison(['bxp_withnotch.png'], remove_text=True, savefig_kwarg={'dpi': 40}, From eb689899acf95a046a7dd7b925c4f86715fd9618 Mon Sep 17 00:00:00 2001 From: Amirreza Aflakparast <84932095+AmirAflak@users.noreply.github.com> Date: Tue, 22 Aug 2023 01:32:01 +0330 Subject: [PATCH 0050/1120] refactor: constant "ncols" to variables --- galleries/examples/color/named_colors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/color/named_colors.py b/galleries/examples/color/named_colors.py index c6cba68ab434..0181e0f06742 100644 --- a/galleries/examples/color/named_colors.py +++ b/galleries/examples/color/named_colors.py @@ -42,14 +42,14 @@ def plot_colortable(colors, *, ncols=4, sort_colors=True): n = len(names) nrows = math.ceil(n / ncols) - width = cell_width * 4 + 2 * margin + width = cell_width * ncols + 2 * margin height = cell_height * nrows + 2 * margin dpi = 72 fig, ax = plt.subplots(figsize=(width / dpi, height / dpi), dpi=dpi) fig.subplots_adjust(margin/width, margin/height, (width-margin)/width, (height-margin)/height) - ax.set_xlim(0, cell_width * 4) + ax.set_xlim(0, cell_width * ncols) ax.set_ylim(cell_height * (nrows-0.5), -cell_height/2.) ax.yaxis.set_visible(False) ax.xaxis.set_visible(False) From c6bd8340f573fdc7d13ef304f5bddb4c29320378 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 22 Aug 2023 02:10:31 -0400 Subject: [PATCH 0051/1120] codespace link in devel --- doc/devel/index.rst | 64 +++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 1146df77adf2..a49290dd8994 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -12,53 +12,55 @@ and implementing new features, helping the community... New contributors ================ -.. card:: - - .. grid:: 1 1 2 2 - :class-row: sd-align-minor-center +.. grid:: 1 1 2 2 + :class-row: sd-align-minor-center - .. grid-item:: - :class: sd-fs-5 + .. grid-item:: + :class: sd-fs-5 - :octicon:`info;1em;sd-text-info` :ref:`Where should I start? ` + :octicon:`info;1em;sd-text-info` :ref:`Where should I start? ` - :octicon:`question;1em;sd-text-info` :ref:`Where should I ask questions? ` + :octicon:`question;1em;sd-text-info` :ref:`Where should I ask questions? ` - :octicon:`issue-opened;1em;sd-text-info` :ref:`What are "good-first-issues"? ` + :octicon:`issue-opened;1em;sd-text-info` :ref:`What are "good-first-issues"? ` - :octicon:`git-pull-request;1em;sd-text-info` :ref:`How do I claim an issue? ` + :octicon:`git-pull-request;1em;sd-text-info` :ref:`How do I claim an issue? ` - .. :octicon:`codespaces;1em;sd-text-info` placeholder for codespaces link + :octicon:`codespaces;1em;sd-text-info` :ref:`How do I start a pull request? ` - .. grid-item:: + .. grid-item:: - .. grid:: 1 - :gutter: 1 - :class-row: sd-fs-5 + .. grid:: 1 + :gutter: 1 + :class-row: sd-fs-5 - .. grid-item-card:: - :link: request-a-new-feature - :link-type: ref + .. grid-item-card:: + :link: request-a-new-feature + :link-type: ref + :shadow: none - :octicon:`light-bulb;1em;sd-text-info` Request new feature + :octicon:`light-bulb;1em;sd-text-info` Request new feature - .. grid-item-card:: - :link: submitting-a-bug-report - :link-type: ref + .. grid-item-card:: + :link: submitting-a-bug-report + :link-type: ref + :shadow: none - :octicon:`bug;1em;sd-text-info` Submit bug report + :octicon:`bug;1em;sd-text-info` Submit bug report - .. grid-item-card:: - :link: contributing-code - :link-type: ref + .. grid-item-card:: + :link: contributing-code + :link-type: ref + :shadow: none - :octicon:`code;1em;sd-text-info` Contribute code + :octicon:`code;1em;sd-text-info` Contribute code - .. grid-item-card:: - :link: documenting-matplotlib - :link-type: ref + .. grid-item-card:: + :link: documenting-matplotlib + :link-type: ref + :shadow: none - :octicon:`note;1em;sd-text-info` Write documentation + :octicon:`note;1em;sd-text-info` Write documentation If you are new to contributing, we recommend that you first read our :ref:`contributing guide`. If you are contributing code or From 704f0a0ada323553d5b076210d3c6db65af1d5b5 Mon Sep 17 00:00:00 2001 From: Amirreza Aflakparast <84932095+AmirAflak@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:11:10 +0330 Subject: [PATCH 0052/1120] [Doc]: match 3D plot types with others (#26571) match 3D plot types with others --- galleries/plot_types/3D/scatter3d_simple.py | 6 +++--- galleries/plot_types/3D/surface3d_simple.py | 2 +- galleries/plot_types/3D/trisurf3d_simple.py | 6 +++--- galleries/plot_types/3D/voxels_simple.py | 6 +++--- galleries/plot_types/3D/wire3d_simple.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/galleries/plot_types/3D/scatter3d_simple.py b/galleries/plot_types/3D/scatter3d_simple.py index 023a46448ccf..27ffb6abf748 100644 --- a/galleries/plot_types/3D/scatter3d_simple.py +++ b/galleries/plot_types/3D/scatter3d_simple.py @@ -1,7 +1,7 @@ """ -============== -3D scatterplot -============== +=================== +scatter(xs, ys, zs) +=================== See `~mpl_toolkits.mplot3d.axes3d.Axes3D.scatter`. """ diff --git a/galleries/plot_types/3D/surface3d_simple.py b/galleries/plot_types/3D/surface3d_simple.py index b421469b821d..04f74d5edd14 100644 --- a/galleries/plot_types/3D/surface3d_simple.py +++ b/galleries/plot_types/3D/surface3d_simple.py @@ -1,6 +1,6 @@ """ ===================== -3D surface +plot_surface(X, Y, Z) ===================== See `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_surface`. diff --git a/galleries/plot_types/3D/trisurf3d_simple.py b/galleries/plot_types/3D/trisurf3d_simple.py index 6bf3dc0835d5..b32bd3ebc69a 100644 --- a/galleries/plot_types/3D/trisurf3d_simple.py +++ b/galleries/plot_types/3D/trisurf3d_simple.py @@ -1,7 +1,7 @@ """ -====================== -Triangular 3D surfaces -====================== +===================== +plot_trisurf(x, y, z) +===================== See `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`. """ diff --git a/galleries/plot_types/3D/voxels_simple.py b/galleries/plot_types/3D/voxels_simple.py index c3473e108969..05ce238b0935 100644 --- a/galleries/plot_types/3D/voxels_simple.py +++ b/galleries/plot_types/3D/voxels_simple.py @@ -1,7 +1,7 @@ """ -========================== -3D voxel / volumetric plot -========================== +========================= +voxels([x, y, z], filled) +========================= See `~mpl_toolkits.mplot3d.axes3d.Axes3D.voxels`. """ diff --git a/galleries/plot_types/3D/wire3d_simple.py b/galleries/plot_types/3D/wire3d_simple.py index 8ce1a460617e..1ab847f3ecf4 100644 --- a/galleries/plot_types/3D/wire3d_simple.py +++ b/galleries/plot_types/3D/wire3d_simple.py @@ -1,7 +1,7 @@ """ -================= -3D wireframe plot -================= +======================= +plot_wireframe(X, Y, Z) +======================= See `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_wireframe`. """ From 0d7d3873da4273be3cf38b892e29e224e91daef9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 18 Aug 2023 01:33:30 -0400 Subject: [PATCH 0053/1120] Use sys.platform over os.name mypy does not recognize the latter [1], and there is a typing failure on Windows because of `os.geteuid`. [1] https://github.com/python/mypy/issues/13002 --- lib/matplotlib/dviread.py | 2 +- lib/matplotlib/tests/test_animation.py | 3 ++- lib/matplotlib/tests/test_backend_webagg.py | 2 +- lib/matplotlib/tests/test_backends_interactive.py | 2 +- lib/matplotlib/tests/test_matplotlib.py | 6 +++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index c00d05de0723..b2177e5087bc 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -1086,7 +1086,7 @@ def find_tex_file(filename): if lk: path = lk.search(filename) else: - if os.name == 'nt': + if sys.platform == 'win32': # On Windows only, kpathsea can use utf-8 for cmd args and output. # The `command_line_encoding` environment variable is set to force # it to always use utf-8 encoding. See Matplotlib issue #11848. diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index 750b9b32dd24..a4de96d77b62 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -2,6 +2,7 @@ from pathlib import Path import platform import re +import shutil import subprocess import sys import weakref @@ -318,7 +319,7 @@ def test_cleanup_temporaries(method_name, tmpdir, anim): assert list(Path(str(tmpdir)).iterdir()) == [] -@pytest.mark.skipif(os.name != "posix", reason="requires a POSIX OS") +@pytest.mark.skipif(shutil.which("/bin/sh") is None, reason="requires a POSIX OS") def test_failing_ffmpeg(tmpdir, monkeypatch, anim): """ Test that we correctly raise a CalledProcessError when ffmpeg fails. diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 992827863b01..237a279c6352 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -11,7 +11,7 @@ def test_webagg_fallback(backend): if backend == "nbagg": pytest.importorskip("IPython") env = dict(os.environ) - if os.name != "nt": + if sys.platform != "win32": env["DISPLAY"] = "" env["MPLBACKEND"] = backend diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index b5f4db4efde6..4e088bd3fa1b 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -473,7 +473,7 @@ def test_cross_Qt_imports(): @pytest.mark.skipif('TF_BUILD' in os.environ, reason="this test fails an azure for unknown reasons") -@pytest.mark.skipif(os.name == "nt", reason="Cannot send SIGINT on Windows.") +@pytest.mark.skipif(sys.platform == "win32", reason="Cannot send SIGINT on Windows.") def test_webagg(): pytest.importorskip("tornado") proc = subprocess.Popen( diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index f30d678a52f0..ac1c3455c3d9 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -17,9 +17,9 @@ def test_parse_to_version_info(version_str, version_tuple): assert matplotlib._parse_to_version_info(version_str) == version_tuple -@pytest.mark.skipif( - os.name == "nt", reason="chmod() doesn't work as is on Windows") -@pytest.mark.skipif(os.name != "nt" and os.geteuid() == 0, +@pytest.mark.skipif(sys.platform == "win32", + reason="chmod() doesn't work as is on Windows") +@pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0, reason="chmod() doesn't work as root") def test_tmpconfigdir_warning(tmpdir): """Test that a warning is emitted if a temporary configdir must be used.""" From 0e9984a760eb903830f59ff0526ad85c6ceee4dc Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 22 Aug 2023 19:51:04 -0400 Subject: [PATCH 0054/1120] added __pycache__ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 74080f6c50ae..9c6bb2263c99 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ doc/.mpl_skip_subdirs.yaml *.py,cover cover/ .noseids +__pycache__ # Conda files # ############### From 863cf89966af5e6597d35812912c46c548f3d970 Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 21 Aug 2023 22:37:11 -0400 Subject: [PATCH 0055/1120] clarify pre-commits and editing workflow --- doc/devel/development_setup.rst | 23 +++++++++++++++------ doc/devel/development_workflow.rst | 32 +++++++++++------------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 8625eaa60042..f42662849339 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -164,16 +164,27 @@ true for ``*.py`` files. If you change the C-extension source (which might also happen if you change branches) you will have to re-run ``python -m pip install -ve .`` -Install pre-commit hooks (optional) -=================================== -`pre-commit `_ hooks automatically check flake8 and -other style issues when you run ``git commit``. The hooks are defined in the -top level ``.pre-commit-config.yaml`` file. To install the hooks :: +Install pre-commit hooks +======================== +`pre-commit `_ hooks save time in the review process by +identifying issues with the code before a pull request is formally opened. Most +hooks can also aide in fixing the errors, and the checks should have +corresponding :ref:`development workflow ` and +:ref:`pull request ` guidelines. Hooks are configured in +`.pre-commit-config.yaml `_ +and include checks for spelling and formatting, flake 8 conformity, accidentally +committed files, import order, and incorrect branching. + +Install pre-commit hooks :: python -m pip install pre-commit pre-commit install -The hooks can also be run manually. All the hooks can be run, in order as +Hooks are run automatically after the ``git commit`` stage of the +:ref:`editing workflow`. When a hook has found and fixed an error in a +file, that file must be *staged and committed* again. + +Hooks can also be run manually. All the hooks can be run, in order as listed in ``.pre-commit-config.yaml``, against the full codebase with :: pre-commit run --all-files diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index e9858cdecd2d..0d3a4c590de2 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -95,20 +95,8 @@ request and open a new pull request from the renamed branch. See The editing workflow ==================== -Overview --------- - -:: - - # hack hack - git add my_new_file - git commit -am 'NF - some message' - git push - -In more detail --------------- - #. Make some changes +#. Save the changes #. See which files have changed with ``git status``. You'll see a listing like this one: @@ -129,13 +117,17 @@ In more detail #. Check what the actual changes are with ``git diff``. #. Add any new files to version control ``git add new_file_name``. -#. To commit all modified files into the local copy of your repo,, do - ``git commit -am 'A commit message'``. Note the ``-am`` options to - ``commit``. The ``m`` flag just signals that you're going to type a - message on the command line. The ``a`` flag — you can just take on - faith — or see `why the -a flag?`_. The - `git commit `_ manual page might also be - useful. +#. To commit **all** modified files into the local copy of your repo, type: + + .. code-block:: bash + + git commit -am 'A commit message' + + Note the ``-am`` options to ``commit``. The ``m`` flag signals that you are + going to type a message on the command line. The ``a`` flag stages every + file that has been modified, except files listed in ``.gitignore``. For more + information, see `why the -a flag?`_ and the + `git commit `_ manual page. #. To push the changes up to your forked repo on GitHub, do a ``git push``. From f566213258ee0d08426ba12448c454e83842ae82 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 23 Aug 2023 21:28:04 +0200 Subject: [PATCH 0056/1120] Deduplicate test for toolbar button icon LA mode. This seems to have been accidentally duplicated in 7e2d904 (perhaps a merge problem). --- lib/matplotlib/tests/test_backends_interactive.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index b5f4db4efde6..678cfc80ef71 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -206,10 +206,6 @@ def check_alt_backend(alt_backend): assert fig.canvas.manager.get_window_title() == "Figure 1" - if mpl.rcParams["toolbar"] == "toolmanager": - # test toolbar button icon LA mode see GH issue 25174 - _test_toolbar_button_la_mode_icon(fig) - if mpl.rcParams["toolbar"] == "toolmanager": # test toolbar button icon LA mode see GH issue 25174 _test_toolbar_button_la_mode_icon(fig) From ebef650cd7741853b4f23265c024ef622b17eda6 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 23 Aug 2023 16:01:00 -0500 Subject: [PATCH 0057/1120] Switch to hstack for consistency --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4bd1e83d2f5a..93a60c332cf4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5471,8 +5471,8 @@ def get_interp_point(idx): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - pts = np.vstack([np.column_stack([ind[where], dep1[where]]), - np.column_stack([ind[where], dep2[where]])]) + pts = np.vstack([np.hstack([ind[where, None], dep1[where, None]]), + np.hstack([ind[where, None], dep2[where, None]])]) if ind_dir == "y": pts = pts[:, ::-1] From 3981bb36cdeab38572e12d1ed327b1db9b086b28 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 24 Aug 2023 10:45:01 +0200 Subject: [PATCH 0058/1120] Fix ToolBase.figure property setter. The property must be defined to use self.set_figure as the setter, and not the other way round; otherwise, subclasses that override set_figure (such as ToolToggleBase) won't override the setter on the property. --- lib/matplotlib/backend_tools.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 4df80ef5bda6..ac2a20f1ffa9 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -118,16 +118,15 @@ def __init__(self, toolmanager, name): lambda self: self._figure.canvas if self._figure is not None else None, doc="The canvas of the figure affected by this tool, or None.") - @property - def figure(self): - """The Figure affected by this tool, or None.""" - return self._figure - - @figure.setter - def figure(self, figure): + def set_figure(self, figure): self._figure = figure - set_figure = figure.fset + figure = property( + lambda self: self._figure, + # The setter must explicitly call self.set_figure so that subclasses can + # meaningfully override it. + lambda self, figure: self.set_figure(figure), + doc="The Figure affected by this tool, or None.") def _make_classic_style_pseudo_toolbar(self): """ From 63f1dfaf2942e8fb3e5f93b7534af564ff5acb3d Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 13 Dec 2022 14:47:08 +0100 Subject: [PATCH 0059/1120] Test with Python 3.12 --- .github/workflows/tests.yml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2314f65225a9..53b700b96255 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,6 +56,7 @@ jobs: pyside2-ver: '==5.15.1' # oldest version with working Py3.9 wheel. pyside6-ver: '==6.0.0' delete-font-cache: true + no-build-isolation: true - os: ubuntu-20.04 python-version: 3.9 extra-requirements: '-r requirements/testing/extra.txt' @@ -76,6 +77,11 @@ jobs: # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' extra-requirements: '-r requirements/testing/extra.txt' + - os: ubuntu-22.04 + python-version: '3.12-dev' + pyside6-ver: '!=6.5.1' + pre: true + no-build-isolation: true - os: macos-latest python-version: 3.9 # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 @@ -191,8 +197,10 @@ jobs: python -m pip install --upgrade pip setuptools wheel # Install pre-release versions during our weekly upcoming dependency tests. + # Also install for 3.12 to get working NumPy (remove when 1.26 is released) if [[ "${{ github.event_name == 'schedule' && - matrix.name-suffix != '(Minimum Versions)' }}" = "true" ]]; then + matrix.name-suffix != '(Minimum Versions)' }}" = "true" + || "${{ matrix.pre }}" = "true" ]]; then PRE="--pre" fi @@ -204,7 +212,7 @@ jobs: ${{ matrix.extra-requirements }} # Preinstall pybind11 on no-build-isolation builds. - if [[ "${{ matrix.name-suffix }}" == '(Minimum Versions)' ]]; then + if [[ "${{ matrix.no-build-isolation }}" == 'true' ]]; then python -m pip install 'pybind11>=2.6' fi @@ -232,7 +240,8 @@ jobs: python -c 'import PyQt5.QtCore' && echo 'PyQt5 is available' || echo 'PyQt5 is not available' - if [[ "${{ runner.os }}" != 'macOS' ]]; then + if [[ "${{ runner.os }}" != 'macOS' + && "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then python -mpip install --upgrade pyside2${{ matrix.pyside2-ver }} && python -c 'import PySide2.QtCore' && echo 'PySide2 is available' || @@ -243,18 +252,23 @@ jobs: python -c 'import PyQt6.QtCore' && echo 'PyQt6 is available' || echo 'PyQt6 is not available' + fi + if [[ "${{ runner.os }}" != 'macOS' + && "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then python -mpip install --upgrade pyside6${{ matrix.pyside6-ver }} && python -c 'import PySide6.QtCore' && echo 'PySide6 is available' || echo 'PySide6 is not available' fi - python -mpip install --upgrade \ - -f "https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }}" \ - wxPython && - python -c 'import wx' && - echo 'wxPython is available' || - echo 'wxPython is not available' + if [[ "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then + python -mpip install --upgrade \ + -f "https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }}" \ + wxPython && + python -c 'import wx' && + echo 'wxPython is available' || + echo 'wxPython is not available' + fi - name: Install the nightly dependencies # Only install the nightly dependencies during the scheduled event @@ -289,7 +303,7 @@ jobs: cat mplsetup.cfg - if [[ "${{ matrix.name-suffix }}" == '(Minimum Versions)' ]]; then + if [[ "${{ matrix.no-build-isolation }}" == 'true' ]]; then # Minimum versions run does not use build isolation so that it # builds against the pre-installed minver dependencies. python -m pip install --no-deps --no-build-isolation -ve . From a63dfaf727fa2ac14faf35f0f404318720bf3183 Mon Sep 17 00:00:00 2001 From: turnipseason <100782385+turnipseason@users.noreply.github.com> Date: Fri, 25 Aug 2023 00:30:32 +0300 Subject: [PATCH 0060/1120] Added get_xmargin(), get_ymargin() and get_zmargin() and tests. (#26293) Co-authored-by: Evgenii Radchenko --- doc/api/axes_api.rst | 2 ++ doc/api/toolkits/mplot3d/axes3d.rst | 1 + doc/users/next_whats_new/margin_getters.rst | 4 +++ lib/matplotlib/axes/_base.py | 32 +++++++++++++++++++ lib/matplotlib/axes/_base.pyi | 2 ++ lib/matplotlib/tests/test_axes.py | 8 +++++ lib/mpl_toolkits/mplot3d/axes3d.py | 16 ++++++++++ lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 9 ++++++ 8 files changed, 74 insertions(+) create mode 100644 doc/users/next_whats_new/margin_getters.rst diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index 3457368fa51c..8b01a120da5b 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -335,6 +335,8 @@ Autoscaling and margins Axes.use_sticky_edges Axes.margins + Axes.get_xmargin + Axes.get_ymargin Axes.set_xmargin Axes.set_ymargin diff --git a/doc/api/toolkits/mplot3d/axes3d.rst b/doc/api/toolkits/mplot3d/axes3d.rst index f6d8e2529896..b581494e4883 100644 --- a/doc/api/toolkits/mplot3d/axes3d.rst +++ b/doc/api/toolkits/mplot3d/axes3d.rst @@ -137,6 +137,7 @@ Autoscaling and margins :template: autosummary.rst :nosignatures: + get_zmargin set_zmargin margins autoscale diff --git a/doc/users/next_whats_new/margin_getters.rst b/doc/users/next_whats_new/margin_getters.rst new file mode 100644 index 000000000000..c43709a17d52 --- /dev/null +++ b/doc/users/next_whats_new/margin_getters.rst @@ -0,0 +1,4 @@ +Getters for xmargin, ymargin and zmargin +---------------------------------------- +``.Axes.get_xmargin()``, ``.Axes.get_ymargin()`` and ``.Axes3D.get_zmargin()`` methods have been added to return +the margin values set by ``.Axes.set_xmargin()``, ``.Axes.set_ymargin()`` and ``.Axes3D.set_zmargin()``, respectively. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3796d9bbe508..ad06e6d552bc 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2617,6 +2617,38 @@ def use_sticky_edges(self, b): self._use_sticky_edges = bool(b) # No effect until next autoscaling, which will mark the Axes as stale. + def get_xmargin(self): + """ + Retrieve autoscaling margin of the x-axis. + + .. versionadded:: 3.9 + + Returns + ------- + xmargin : float + + See Also + -------- + matplotlib.axes.Axes.set_xmargin + """ + return self._xmargin + + def get_ymargin(self): + """ + Retrieve autoscaling margin of the y-axis. + + .. versionadded:: 3.9 + + Returns + ------- + ymargin : float + + See Also + -------- + matplotlib.axes.Axes.set_ymargin + """ + return self._ymargin + def set_xmargin(self, m): """ Set padding of X data limits prior to autoscaling. diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index d41ecae1803c..e3644585296d 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -242,6 +242,8 @@ class _AxesBase(martist.Artist): def use_sticky_edges(self) -> bool: ... @use_sticky_edges.setter def use_sticky_edges(self, b: bool) -> None: ... + def get_xmargin(self) -> float: ... + def get_ymargin(self) -> float: ... def set_xmargin(self, m: float) -> None: ... def set_ymargin(self, m: float) -> None: ... diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0fcb2eb26cbb..6dea39a702fc 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6153,6 +6153,14 @@ def test_margins(): ymax + (ymax - ymin) * 0.5) +def test_margin_getters(): + fig = plt.figure() + ax = fig.add_subplot() + ax.margins(0.2, 0.3) + assert ax.get_xmargin() == 0.2 + assert ax.get_ymargin() == 0.3 + + def test_set_margin_updates_limits(): mpl.style.use("default") fig, ax = plt.subplots() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index aeb6a66d2c98..e7abdc0767b5 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -513,6 +513,22 @@ def update_datalim(self, xys, **kwargs): get_autoscalez_on = _axis_method_wrapper("zaxis", "_get_autoscale_on") set_autoscalez_on = _axis_method_wrapper("zaxis", "_set_autoscale_on") + def get_zmargin(self): + """ + Retrieve autoscaling margin of the z-axis. + + .. versionadded:: 3.9 + + Returns + ------- + zmargin : float + + See Also + -------- + mpl_toolkits.mplot3d.axes3d.Axes3D.set_zmargin + """ + return self._zmargin + def set_zmargin(self, m): """ Set padding of Z data limits prior to autoscaling. diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 1f8764cbab9d..df9f2ae52fd7 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1994,6 +1994,15 @@ def test_margins(): assert ax.margins() == (0, 0.1, 0) +def test_margin_getters(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.margins(0.1, 0.2, 0.3) + assert ax.get_xmargin() == 0.1 + assert ax.get_ymargin() == 0.2 + assert ax.get_zmargin() == 0.3 + + @pytest.mark.parametrize('err, args, kwargs, match', ( (ValueError, (-1,), {}, r'margin must be greater than -0\.5'), (ValueError, (1, -1, 1), {}, r'margin must be greater than -0\.5'), From f699d50bcef31106dfaa1dbd03b8921f2b238d23 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 23 Aug 2023 23:53:55 +0200 Subject: [PATCH 0061/1120] Support standard formatters in axisartist. Ideally, axisartist-specific formatters (and locators) should be deprecated and removed in the future. --- .../next_whats_new/stdfmt-axisartist.rst | 3 +++ lib/mpl_toolkits/axisartist/floating_axes.py | 8 +++--- lib/mpl_toolkits/axisartist/grid_finder.py | 23 ++++++++++++++---- .../axisartist/grid_helper_curvelinear.py | 8 +++--- .../polar_box.png | Bin 62526 -> 64346 bytes .../tests/test_grid_helper_curvelinear.py | 20 +++++++-------- 6 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 doc/users/next_whats_new/stdfmt-axisartist.rst diff --git a/doc/users/next_whats_new/stdfmt-axisartist.rst b/doc/users/next_whats_new/stdfmt-axisartist.rst new file mode 100644 index 000000000000..9cb014413042 --- /dev/null +++ b/doc/users/next_whats_new/stdfmt-axisartist.rst @@ -0,0 +1,3 @@ +``axisartist`` can now be used together with standard ``Formatters`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... instead of being limited to axisartist-specific ones. diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index 97dafe98c694..306ae2c5139a 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -222,10 +222,10 @@ def _update_grid(self, x1, y1, x2, y2): grid_info["lon_info"] = lon_levs, lon_n, lon_factor grid_info["lat_info"] = lat_levs, lat_n, lat_factor - grid_info["lon_labels"] = grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs) - grid_info["lat_labels"] = grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs) + grid_info["lon_labels"] = grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs) + grid_info["lat_labels"] = grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs) lon_values = lon_levs[:lon_n] / lon_factor lat_values = lat_levs[:lat_n] / lat_factor diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index f969b011c4cd..18a48b8e8ccf 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -1,6 +1,6 @@ import numpy as np -from matplotlib import ticker as mticker +from matplotlib import ticker as mticker, _api from matplotlib.transforms import Bbox, Transform @@ -150,6 +150,19 @@ def __init__(self, self.tick_formatter2 = tick_formatter2 self.set_transform(transform) + def _format_ticks(self, idx, direction, factor, levels): + """ + Helper to support both standard formatters (inheriting from + `.mticker.Formatter`) and axisartist-specific ones; should be called instead of + directly calling ``self.tick_formatter1`` and ``self.tick_formatter2``. This + method should be considered as a temporary workaround which will be removed in + the future at the same time as axisartist-specific formatters. + """ + fmt = _api.check_getitem( + {1: self.tick_formatter1, 2: self.tick_formatter2}, idx=idx) + return (fmt.format_ticks(levels) if isinstance(fmt, mticker.Formatter) + else fmt(direction, factor, levels)) + def get_grid_info(self, x1, y1, x2, y2): """ lon_values, lat_values : list of grid values. if integer is given, @@ -192,14 +205,14 @@ def get_grid_info(self, x1, y1, x2, y2): tck_labels = grid_info["lon"]["tick_labels"] = {} for direction in ["left", "bottom", "right", "top"]: levs = grid_info["lon"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter1( - direction, lon_factor, levs) + tck_labels[direction] = self._format_ticks( + 1, direction, lon_factor, levs) tck_labels = grid_info["lat"]["tick_labels"] = {} for direction in ["left", "bottom", "right", "top"]: levs = grid_info["lat"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter2( - direction, lat_factor, levs) + tck_labels[direction] = self._format_ticks( + 2, direction, lat_factor, levs) return grid_info diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index ae17452b6c58..f76fd84b55fe 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -137,10 +137,10 @@ def update_lim(self, axes): "extremes": (lon_min, lon_max, lat_min, lat_max), "lon_info": (lon_levs, lon_n, np.asarray(lon_factor)), "lat_info": (lat_levs, lat_n, np.asarray(lat_factor)), - "lon_labels": grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs), - "lat_labels": grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs), + "lon_labels": grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs), + "lat_labels": grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs), "line_xy": (xx, yy), } diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png index 04e8ceecd2cb57557a08bb1c6d7845316dd35328..8909355e9af8ac0d66827e13bd7674c8f0b82fed 100644 GIT binary patch literal 64346 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%0Du}-Th;9 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 z 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 JnQ`_3{|mGCE0zEN literal 62526 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 Date: Mon, 21 Aug 2023 22:51:30 +0530 Subject: [PATCH 0062/1120] [doc]: added section Verify installation [doc]: added section Verify Installation [doc]: added section Verify installation --- doc/devel/development_setup.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index f42662849339..4d277fca2ab4 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -164,6 +164,22 @@ true for ``*.py`` files. If you change the C-extension source (which might also happen if you change branches) you will have to re-run ``python -m pip install -ve .`` +Verify the Installation +======================= + +Run the following command to make sure you have correctly installed Matplotlib in editable mode. +The command should be run when the virtual environment is activated :: + + python -c "import matplotlib; print(matplotlib.__file__)" + +This command should return : ``\lib\matplotlib\__init__.py`` + +We encourage you to run tests and build docs to verify that the code installed correctly and that the docs build cleanly, +so that when you make code or document related changes you are aware of the existing issues beforehand. + + * Run test cases to verify installation :ref:`testing` + * Verify documentation build :ref:`documenting-matplotlib` + Install pre-commit hooks ======================== `pre-commit `_ hooks save time in the review process by From 71eed55bd95d99dc32c6b3ca3f70093eb8498177 Mon Sep 17 00:00:00 2001 From: Ruth Comer Date: Fri, 25 Aug 2023 12:09:45 +0100 Subject: [PATCH 0063/1120] FIX: array labelcolor for Tick --- lib/matplotlib/axis.py | 2 +- lib/matplotlib/tests/test_axis.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 lib/matplotlib/tests/test_axis.py diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 0ace31916ca9..77bd34df69c5 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -129,7 +129,7 @@ def __init__( if labelcolor is None: labelcolor = mpl.rcParams[f"{name}.labelcolor"] - if labelcolor == 'inherit': + if cbook._str_equal(labelcolor, 'inherit'): # inherit from tick color labelcolor = mpl.rcParams[f"{name}.color"] diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py new file mode 100644 index 000000000000..97b5f88dede1 --- /dev/null +++ b/lib/matplotlib/tests/test_axis.py @@ -0,0 +1,10 @@ +import numpy as np + +import matplotlib.pyplot as plt +from matplotlib.axis import XTick + + +def test_tick_labelcolor_array(): + # Smoke test that we can instantiate a Tick with labelcolor as array. + ax = plt.axes() + XTick(ax, 0, labelcolor=np.array([1, 0, 0, 1])) From ce385fe4464b3ffc866fe2faf8d44e14d0c8f0c6 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 25 Aug 2023 10:33:11 -0500 Subject: [PATCH 0064/1120] Avoid checking limits when updating both min and max for contours closes #26531 --- lib/matplotlib/contour.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 79f66b896131..aa3041ddce9b 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -887,11 +887,12 @@ def __init__(self, ax, *args, self.set_cmap(cmap) if norm is not None: self.set_norm(norm) - if vmin is not None: - self.norm.vmin = vmin - if vmax is not None: - self.norm.vmax = vmax - self._process_colors() + with self.norm.callbacks.blocked(signal="changed"): + if vmin is not None: + self.norm.vmin = vmin + if vmax is not None: + self.norm.vmax = vmax + self._process_colors() if self._paths is None: self._paths = self._make_paths_from_contour_generator() From e46342eea0c33366bdd66b2b4eeb2554a79b9ede Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 25 Aug 2023 23:46:51 -0400 Subject: [PATCH 0065/1120] ci: Install GTK4 from brew on macOS We try to install PyGObject everywhere, but this doesn't work because GTK itself is not available from PyPI. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2314f65225a9..a99afd5e3fa8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -144,7 +144,7 @@ jobs: macOS) brew install ccache brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk + brew install font-noto-sans-cjk gobject-introspection gtk4 ;; esac From 8b056ac2b5324604ad1291ecbbc89e0d3280ac94 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 26 Aug 2023 00:46:10 -0400 Subject: [PATCH 0066/1120] ci: Also check GTK4 when testing PyGObject availability --- .github/workflows/tests.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a99afd5e3fa8..274b963c0602 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -219,10 +219,14 @@ jobs: # libraries cannot be loaded at runtime, so an actual import is a # better check). # PyGObject, pycairo, and cariocffi do not install on OSX 10.12. - python -m pip install --upgrade pycairo 'cairocffi>=0.8' PyGObject && - python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && - echo 'PyGObject is available' || - echo 'PyGObject is not available' + python -m pip install --upgrade pycairo 'cairocffi>=0.8' PyGObject && + ( + python -c 'import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk' && + echo 'PyGObject 4 is available' || echo 'PyGObject 4 is not available' + ) && ( + python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && + echo 'PyGObject 3 is available' || echo 'PyGObject 3 is not available' + ) # There are no functioning wheels available for OSX 10.12 (as of # Sept 2020) for either pyqt5 (there are only wheels for 10.13+) or From 6caa6836a48ad44b6a91a962ba2eedee1c99c9c0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 23 Aug 2023 16:03:39 -0400 Subject: [PATCH 0067/1120] MNT: Enable wheels for Python 3.12 --- .github/workflows/cibuildwheel.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 58b96d38c883..8c45ef95990b 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,6 +135,28 @@ jobs: name: sdist path: dist/ + - name: Build wheels for CPython 3.12 + uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} + env: + CIBW_BUILD: "cp312-*" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + # Remove this once NumPy with Python 3.12 wheels is not pre-release. + CIBW_BEFORE_BUILD: >- + pip install certifi "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && + pip install --pre "numpy>=1.25" && + rm -rf {package}/build + CIBW_BEFORE_BUILD_WINDOWS: >- + pip install certifi delvewheel "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && + pip install --pre "numpy>=1.25" && + rm -rf {package}/build + CIBW_ENVIRONMENT: PIP_NO_BUILD_ISOLATION=0 + # Remove this once contourpy has Python 3.12 wheels. + CIBW_BEFORE_TEST: >- + pip install "meson>=1.2.0" "meson-python>=0.13.1" "ninja" "pybind11>=2.10.4" && + pip install --pre "numpy>=1.25" + - name: Build wheels for CPython 3.11 uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 with: From f9f673fb48c1c2282daee93ddfc81bf8e8053e61 Mon Sep 17 00:00:00 2001 From: jsdodge <4602669+jsdodge@users.noreply.github.com> Date: Sat, 26 Aug 2023 06:10:20 -0700 Subject: [PATCH 0068/1120] [Doc] Revise histogram features example (Closes #26604) (#26606) * [Doc] Revise histogram features example (Addresses Issue #26604) Remove reference to IQ scores and "Smarts" in figure; modify distribution parameters; update RNG syntax * Update histogram_features.py Wrap title string * Fix trailing whitespace * Restore RNG seed to 19680801 --- .../examples/statistics/histogram_features.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/galleries/examples/statistics/histogram_features.py b/galleries/examples/statistics/histogram_features.py index 354ef1a3b55e..21f74fd1ac44 100644 --- a/galleries/examples/statistics/histogram_features.py +++ b/galleries/examples/statistics/histogram_features.py @@ -20,14 +20,14 @@ import matplotlib.pyplot as plt import numpy as np -np.random.seed(19680801) +rng = np.random.default_rng(19680801) # example data -mu = 100 # mean of distribution -sigma = 15 # standard deviation of distribution -x = mu + sigma * np.random.randn(437) +mu = 106 # mean of distribution +sigma = 17 # standard deviation of distribution +x = rng.normal(loc=mu, scale=sigma, size=420) -num_bins = 50 +num_bins = 42 fig, ax = plt.subplots() @@ -38,9 +38,10 @@ y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * np.exp(-0.5 * (1 / sigma * (bins - mu))**2)) ax.plot(bins, y, '--') -ax.set_xlabel('Smarts') +ax.set_xlabel('Value') ax.set_ylabel('Probability density') -ax.set_title(r'Histogram of IQ: $\mu=100$, $\sigma=15$') +ax.set_title('Histogram of normal distribution sample: ' + fr'$\mu={mu:.0f}$, $\sigma={sigma:.0f}$') # Tweak spacing to prevent clipping of ylabel fig.tight_layout() From 66c02bc16dfabd9fa3a2b8b8f08ca5d751d3297c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 26 Aug 2023 22:15:16 +0200 Subject: [PATCH 0069/1120] Cleanup AutoMinorLocator implementation. No change to logic, only rewriting things in a slightly more compact manner, and making the `len(majorlocs) < 2` case tested more explicitly than via an IndexError. --- lib/matplotlib/ticker.py | 45 ++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index b4793929de9b..8dc79d4c0f30 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2893,6 +2893,7 @@ class AutoMinorLocator(Locator): Dynamically find minor tick positions based on the positions of major ticks. The scale must be linear with major ticks evenly spaced. """ + def __init__(self, n=None): """ *n* is the number of subdivisions of the interval between @@ -2908,47 +2909,33 @@ def __init__(self, n=None): self.ndivs = n def __call__(self): - """Return the locations of the ticks.""" + # docstring inherited if self.axis.get_scale() == 'log': - _api.warn_external('AutoMinorLocator does not work with ' - 'logarithmic scale') + _api.warn_external('AutoMinorLocator does not work on logarithmic scales') return [] majorlocs = self.axis.get_majorticklocs() - try: - majorstep = majorlocs[1] - majorlocs[0] - except IndexError: - # Need at least two major ticks to find minor tick locations - # TODO: Figure out a way to still be able to display minor - # ticks without two major ticks visible. For now, just display - # no ticks at all. + if len(majorlocs) < 2: + # Need at least two major ticks to find minor tick locations. + # TODO: Figure out a way to still be able to display minor ticks with less + # than two major ticks visible. For now, just display no ticks at all. return [] + majorstep = majorlocs[1] - majorlocs[0] if self.ndivs is None: - - if self.axis.axis_name == 'y': - self.ndivs = mpl.rcParams['ytick.minor.ndivs'] - else: - # for x and z axis - self.ndivs = mpl.rcParams['xtick.minor.ndivs'] + self.ndivs = mpl.rcParams[ + 'ytick.minor.ndivs' if self.axis.axis_name == 'y' + else 'xtick.minor.ndivs'] # for x and z axis if self.ndivs == 'auto': - - majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1) - - if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any(): - ndivs = 5 - else: - ndivs = 4 + majorstep_mantissa = 10 ** (np.log10(majorstep) % 1) + ndivs = 5 if np.isclose(majorstep_mantissa, [1, 2.5, 5, 10]).any() else 4 else: ndivs = self.ndivs minorstep = majorstep / ndivs - vmin, vmax = self.axis.get_view_interval() - if vmin > vmax: - vmin, vmax = vmax, vmin - + vmin, vmax = sorted(self.axis.get_view_interval()) t0 = majorlocs[0] tmin = round((vmin - t0) / minorstep) tmax = round((vmax - t0) / minorstep) + 1 @@ -2957,5 +2944,5 @@ def __call__(self): return self.raise_if_exceeds(locs) def tick_values(self, vmin, vmax): - raise NotImplementedError('Cannot get tick locations for a ' - '%s type.' % type(self)) + raise NotImplementedError( + f"Cannot get tick locations for a {type(self).__name__}") From 0da1b6c80ff067a158af07d5d65fc1e01efdd8cc Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Sat, 29 Apr 2023 01:16:15 -0600 Subject: [PATCH 0070/1120] Implement exact 3d plot limits Fix remaining large image diffs Fix remaining large image diffs Fix the rest of the tests Fix docs error Code review updates Code review updates Fix docs error One more constant Tweaks Keep fixing CI errors Docstrings Update baseline images More test images Images Test new scaling factor Test new scaling factor Test for exact limits Cleanup Tests Update doc/users/next_whats_new/3d_axis_limits.rst Co-authored-by: Elliott Sales de Andrade rebase fixes Apply suggestions from code review Co-authored-by: Oscar Gustafsson Code review cleanup Merge conflicts and better comments Merge conflicts and better comments --- doc/api/toolkits/mplot3d/axes3d.rst | 6 + doc/users/next_whats_new/3d_axis_limits.rst | 18 + lib/matplotlib/axis.py | 19 +- lib/matplotlib/mpl-data/matplotlibrc | 5 +- .../mpl-data/stylelib/classic.mplstyle | 3 +- lib/matplotlib/rcsetup.py | 6 +- lib/matplotlib/tests/test_collections.py | 1 + lib/matplotlib/ticker.py | 5 + lib/mpl_toolkits/mplot3d/axes3d.py | 466 +++++++++++++++--- lib/mpl_toolkits/mplot3d/axis3d.py | 37 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 41 +- 11 files changed, 493 insertions(+), 114 deletions(-) create mode 100644 doc/users/next_whats_new/3d_axis_limits.rst diff --git a/doc/api/toolkits/mplot3d/axes3d.rst b/doc/api/toolkits/mplot3d/axes3d.rst index b581494e4883..f99074fd9c8a 100644 --- a/doc/api/toolkits/mplot3d/axes3d.rst +++ b/doc/api/toolkits/mplot3d/axes3d.rst @@ -92,12 +92,18 @@ Axis limits and direction get_zaxis get_xlim + set_xlim get_ylim + set_ylim get_zlim set_zlim get_w_lims invert_zaxis zaxis_inverted + get_xbound + set_xbound + get_ybound + set_ybound get_zbound set_zbound diff --git a/doc/users/next_whats_new/3d_axis_limits.rst b/doc/users/next_whats_new/3d_axis_limits.rst new file mode 100644 index 000000000000..7e7d6b2e5133 --- /dev/null +++ b/doc/users/next_whats_new/3d_axis_limits.rst @@ -0,0 +1,18 @@ +Setting 3D axis limits now set the limits exactly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, setting the limits of a 3D axis would always add a small margin to +the limits. Limits are now set exactly by default. The newly introduced rcparam +``axes3d.automargin`` can be used to revert to the old behavior where margin is +automatically added. + +.. plot:: + :include-source: true + :alt: Example of the new behavior of 3D axis limits, and how setting the rcparam reverts to the old behavior. + + import matplotlib.pyplot as plt + fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) + plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0 + axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior') + plt.rcParams['axes3d.automargin'] = True + axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior') diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 0ace31916ca9..f0824dc4ed56 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -855,13 +855,14 @@ def _get_autoscale_on(self): def _set_autoscale_on(self, b): """ Set whether this Axis is autoscaled when drawing or by - `.Axes.autoscale_view`. + `.Axes.autoscale_view`. If b is None, then the value is not changed. Parameters ---------- b : bool """ - self._autoscale_on = b + if b is not None: + self._autoscale_on = b def get_children(self): return [self.label, self.offsetText, @@ -1244,8 +1245,7 @@ def _set_lim(self, v0, v1, *, emit=True, auto): # Mark viewlims as no longer stale without triggering an autoscale. for ax in self._get_shared_axes(): ax._stale_viewlims[name] = False - if auto is not None: - self._set_autoscale_on(bool(auto)) + self._set_autoscale_on(auto) if emit: self.axes.callbacks.process(f"{name}lim_changed", self.axes) @@ -1292,6 +1292,17 @@ def _update_ticks(self): if view_low > view_high: view_low, view_high = view_high, view_low + if (hasattr(self, "axes") and self.axes.name == '3d' + and mpl.rcParams['axes3d.automargin']): + # In mpl3.8, the margin was 1/48. Due to the change in automargin + # behavior in mpl3.9, we need to adjust this to compensate for a + # zoom factor of 2/48, giving us a 23/24 modifier. So the new + # margin is 0.019965277777777776 = 1/48*23/24. + margin = 0.019965277777777776 + delta = view_high - view_low + view_high = view_high - delta * margin + view_low = view_low + delta * margin + interval_t = self.get_transform().transform([view_low, view_high]) ticks_to_draw = [] diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 2c53651da3d6..a097c98429d1 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -425,8 +425,9 @@ #axes.autolimit_mode: data # If "data", use axes.xmargin and axes.ymargin as is. # If "round_numbers", after application of margins, axis # limits are further expanded to the nearest "round" number. -#polaraxes.grid: True # display grid on polar axes -#axes3d.grid: True # display grid on 3D axes +#polaraxes.grid: True # display grid on polar axes +#axes3d.grid: True # display grid on 3D axes +#axes3d.automargin: False # automatically add margin when manually setting 3D axis limits #axes3d.xaxis.panecolor: (0.95, 0.95, 0.95, 0.5) # background pane on 3D axes #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 09a38df282f1..8923ce6f0497 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -223,7 +223,8 @@ axes.spines.left : True axes.spines.right : True axes.spines.top : True polaraxes.grid : True # display grid on polar axes -axes3d.grid : True # display grid on 3d axes +axes3d.grid : True # display grid on 3D axes +axes3d.automargin : False # automatically add margin when manually setting 3D axis limits date.autoformatter.year : %Y date.autoformatter.month : %b %Y diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 276bb9f812a9..dda293f89401 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1094,8 +1094,10 @@ def _convert_validator_spec(key, conv): "axes.ymargin": _validate_greaterthan_minushalf, # margin added to yaxis "axes.zmargin": _validate_greaterthan_minushalf, # margin added to zaxis - "polaraxes.grid": validate_bool, # display polar grid or not - "axes3d.grid": validate_bool, # display 3d grid + "polaraxes.grid": validate_bool, # display polar grid or not + "axes3d.grid": validate_bool, # display 3d grid + "axes3d.automargin": validate_bool, # automatically add margin when + # manually setting 3D axis limits "axes3d.xaxis.panecolor": validate_color, # 3d background pane "axes3d.yaxis.panecolor": validate_color, # 3d background pane diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 43bbea34a2e5..1f7c104a4c07 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -401,6 +401,7 @@ def test_EllipseCollection(): @image_comparison(['polycollection_close.png'], remove_text=True, style='mpl20') def test_polycollection_close(): from mpl_toolkits.mplot3d import Axes3D # type: ignore + plt.rcParams['axes3d.automargin'] = True vertsQuad = [ [[0., 0.], [0., 1.], [1., 1.], [1., 0.]], diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 8dc79d4c0f30..d6a48e9bb637 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2106,6 +2106,11 @@ def _raw_ticks(self, vmin, vmax): steps = steps[igood] raw_step = ((_vmax - _vmin) / nbins) + if hasattr(self.axis, "axes") and self.axis.axes.name == '3d': + # Due to the change in automargin behavior in mpl3.9, we need to + # adjust the raw step to match the mpl3.8 appearance. The zoom + # factor of 2/48, gives us the 23/24 modifier. + raw_step = raw_step * 23/24 large_steps = steps >= raw_step if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': # Classic round_numbers mode may require a larger step. diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index e7abdc0767b5..b995b5a2a057 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -21,7 +21,6 @@ import matplotlib as mpl from matplotlib import _api, cbook, _docstring, _preprocess_data import matplotlib.artist as martist -import matplotlib.axes as maxes import matplotlib.collections as mcoll import matplotlib.colors as mcolors import matplotlib.image as mimage @@ -133,7 +132,9 @@ def __init__( self.xy_viewLim = Bbox.unit() self.zz_viewLim = Bbox.unit() - self.xy_dataLim = Bbox.unit() + xymargin = 0.05 * 10/11 # match mpl3.8 appearance + self.xy_dataLim = Bbox([[xymargin, xymargin], + [1 - xymargin, 1 - xymargin]]) # z-limits are encoded in the x-component of the Bbox, y is un-used self.zz_dataLim = Bbox.unit() @@ -166,6 +167,9 @@ def __init__( self.M = None self.invM = None + self._view_margin = 1/48 # default value to match mpl3.8 + self.autoscale_view() + # func used to format z -- fall back on major formatters self.fmt_zdata = None @@ -345,7 +349,8 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): self.set_ylim3d, self.set_zlim3d)): if i in ax_indices: - set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.) + set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2., + auto=True, view_margin=None) else: # 'box' # Change the box aspect such that the ratio of the length of # the unmodified axis to the length of the diagonal @@ -413,8 +418,11 @@ def set_box_aspect(self, aspect, *, zoom=1): else: aspect = np.asarray(aspect, dtype=float) _api.check_shape((3,), aspect=aspect) - # default scale tuned to match the mpl32 appearance. - aspect *= 1.8294640721620434 * zoom / np.linalg.norm(aspect) + # The scale 1.8294640721620434 is tuned to match the mpl3.2 appearance. + # The 25/24 factor is to compensate for the change in automargin + # behavior in mpl3.9. This comes from the padding of 1/48 on both sides + # of the axes in mpl3.8. + aspect *= 1.8294640721620434 * 25/24 * zoom / np.linalg.norm(aspect) self._box_aspect = aspect self.stale = True @@ -601,17 +609,17 @@ def autoscale(self, enable=True, axis='both', tight=None): scalez = True else: if axis in ['x', 'both']: - self.set_autoscalex_on(bool(enable)) + self.set_autoscalex_on(enable) scalex = self.get_autoscalex_on() else: scalex = False if axis in ['y', 'both']: - self.set_autoscaley_on(bool(enable)) + self.set_autoscaley_on(enable) scaley = self.get_autoscaley_on() else: scaley = False if axis in ['z', 'both']: - self.set_autoscalez_on(bool(enable)) + self.set_autoscalez_on(enable) scalez = self.get_autoscalez_on() else: scalez = False @@ -636,8 +644,8 @@ def auto_scale_xyz(self, X, Y, Z=None, had_data=None): # Let autoscale_view figure out how to use this data. self.autoscale_view() - def autoscale_view(self, tight=None, scalex=True, scaley=True, - scalez=True): + def autoscale_view(self, tight=None, + scalex=True, scaley=True, scalez=True): """ Autoscale the view limits using the data limits. @@ -669,7 +677,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, x1 += delta if not _tight: x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1) + self.set_xbound(x0, x1, self._view_margin) if scaley and self.get_autoscaley_on(): y0, y1 = self.xy_dataLim.intervaly @@ -681,7 +689,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, y1 += delta if not _tight: y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1) + self.set_ybound(y0, y1, self._view_margin) if scalez and self.get_autoscalez_on(): z0, z1 = self.zz_dataLim.intervalx @@ -693,7 +701,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, z1 += delta if not _tight: z0, z1 = zlocator.view_limits(z0, z1) - self.set_zbound(z0, z1) + self.set_zbound(z0, z1, self._view_margin) def get_w_lims(self): """Get 3D world limits.""" @@ -702,28 +710,347 @@ def get_w_lims(self): minz, maxz = self.get_zlim3d() return minx, maxx, miny, maxy, minz, maxz - # set_xlim, set_ylim are directly inherited from base Axes. + def _set_bound3d(self, get_bound, set_lim, axis_inverted, + lower=None, upper=None, view_margin=None): + """ + Set 3D axis bounds. + """ + if upper is None and np.iterable(lower): + lower, upper = lower + + old_lower, old_upper = get_bound() + if lower is None: + lower = old_lower + if upper is None: + upper = old_upper + + set_lim(sorted((lower, upper), reverse=bool(axis_inverted())), + auto=None, view_margin=view_margin) + + def set_xbound(self, lower=None, upper=None, view_margin=None): + """ + Set the lower and upper numerical bounds of the x-axis. + + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscalex_on()`). + + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_xlim`. + + See Also + -------- + get_xbound + get_xlim, set_xlim + invert_xaxis, xaxis_inverted + """ + self._set_bound3d(self.get_xbound, self.set_xlim, self.xaxis_inverted, + lower, upper, view_margin) + + def set_ybound(self, lower=None, upper=None, view_margin=None): + """ + Set the lower and upper numerical bounds of the y-axis. + + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscaley_on()`). + + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_ylim`. + + See Also + -------- + get_ybound + get_ylim, set_ylim + invert_yaxis, yaxis_inverted + """ + self._set_bound3d(self.get_ybound, self.set_ylim, self.yaxis_inverted, + lower, upper, view_margin) + + def set_zbound(self, lower=None, upper=None, view_margin=None): + """ + Set the lower and upper numerical bounds of the z-axis. + This method will honor axis inversion regardless of parameter order. + It will not change the autoscaling setting (`.get_autoscaley_on()`). + + Parameters + ---------- + lower, upper : float or None + The lower and upper bounds. If *None*, the respective axis bound + is not modified. + view_margin : float or None + The margin to apply to the bounds. If *None*, the margin is handled + by `.set_zlim`. + + See Also + -------- + get_zbound + get_zlim, set_zlim + invert_zaxis, zaxis_inverted + """ + self._set_bound3d(self.get_zbound, self.set_zlim, self.zaxis_inverted, + lower, upper, view_margin) + + def _set_lim3d(self, axis, lower=None, upper=None, *, emit=True, + auto=False, view_margin=None, axmin=None, axmax=None): + """ + Set 3D axis limits. + """ + if upper is None: + if np.iterable(lower): + lower, upper = lower + elif axmax is None: + upper = axis.get_view_interval()[1] + if lower is None and axmin is None: + lower = axis.get_view_interval()[0] + if axmin is not None: + if lower is not None: + raise TypeError("Cannot pass both 'lower' and 'min'") + lower = axmin + if axmax is not None: + if upper is not None: + raise TypeError("Cannot pass both 'upper' and 'max'") + upper = axmax + if np.isinf(lower) or np.isinf(upper): + raise ValueError(f"Axis limits {lower}, {upper} cannot be infinite") + if view_margin is None: + if mpl.rcParams['axes3d.automargin']: + view_margin = self._view_margin + else: + view_margin = 0 + delta = (upper - lower) * view_margin + lower -= delta + upper += delta + return axis._set_lim(lower, upper, emit=emit, auto=auto) + + def set_xlim(self, left=None, right=None, *, emit=True, auto=False, + view_margin=None, xmin=None, xmax=None): + """ + Set the 3D x-axis view limits. + + Parameters + ---------- + left : float, optional + The left xlim in data coordinates. Passing *None* leaves the + limit unchanged. + + The left and right xlims may also be passed as the tuple + (*left*, *right*) as the first positional argument (or as + the *left* keyword argument). + + .. ACCEPTS: (left: float, right: float) + + right : float, optional + The right xlim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the x-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + xmin, xmax : float, optional + They are equivalent to left and right respectively, and it is an + error to pass both *xmin* and *left* or *xmax* and *right*. + + Returns + ------- + left, right : (float, float) + The new x-axis limits in data coordinates. + + See Also + -------- + get_xlim + set_xbound, get_xbound + invert_xaxis, xaxis_inverted + + Notes + ----- + The *left* value may be greater than the *right* value, in which + case the x-axis values will decrease from *left* to *right*. + + Examples + -------- + >>> set_xlim(left, right) + >>> set_xlim((left, right)) + >>> left, right = set_xlim(left, right) + + One limit may be left unchanged. + + >>> set_xlim(right=right_lim) + + Limits may be passed in reverse order to flip the direction of + the x-axis. For example, suppose ``x`` represents depth of the + ocean in m. The x-axis limits might be set like the following + so 5000 m depth is at the left of the plot and the surface, + 0 m, is at the right. + + >>> set_xlim(5000, 0) + """ + return self._set_lim3d(self.xaxis, left, right, emit=emit, auto=auto, + view_margin=view_margin, axmin=xmin, axmax=xmax) + + def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, + view_margin=None, ymin=None, ymax=None): + """ + Set the 3D y-axis view limits. + + Parameters + ---------- + bottom : float, optional + The bottom ylim in data coordinates. Passing *None* leaves the + limit unchanged. + + The bottom and top ylims may also be passed as the tuple + (*bottom*, *top*) as the first positional argument (or as + the *bottom* keyword argument). + + .. ACCEPTS: (bottom: float, top: float) + + top : float, optional + The top ylim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the y-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + ymin, ymax : float, optional + They are equivalent to bottom and top respectively, and it is an + error to pass both *ymin* and *bottom* or *ymax* and *top*. + + Returns + ------- + bottom, top : (float, float) + The new y-axis limits in data coordinates. + + See Also + -------- + get_ylim + set_ybound, get_ybound + invert_yaxis, yaxis_inverted + + Notes + ----- + The *bottom* value may be greater than the *top* value, in which + case the y-axis values will decrease from *bottom* to *top*. + + Examples + -------- + >>> set_ylim(bottom, top) + >>> set_ylim((bottom, top)) + >>> bottom, top = set_ylim(bottom, top) + + One limit may be left unchanged. + + >>> set_ylim(top=top_lim) + + Limits may be passed in reverse order to flip the direction of + the y-axis. For example, suppose ``y`` represents depth of the + ocean in m. The y-axis limits might be set like the following + so 5000 m depth is at the bottom of the plot and the surface, + 0 m, is at the top. + + >>> set_ylim(5000, 0) + """ + return self._set_lim3d(self.yaxis, bottom, top, emit=emit, auto=auto, + view_margin=view_margin, axmin=ymin, axmax=ymax) + def set_zlim(self, bottom=None, top=None, *, emit=True, auto=False, - zmin=None, zmax=None): - """ - Set 3D z limits. - - See `.Axes.set_ylim` for full documentation - """ - if top is None and np.iterable(bottom): - bottom, top = bottom - if zmin is not None: - if bottom is not None: - raise TypeError("Cannot pass both 'bottom' and 'zmin'") - bottom = zmin - if zmax is not None: - if top is not None: - raise TypeError("Cannot pass both 'top' and 'zmax'") - top = zmax - return self.zaxis._set_lim(bottom, top, emit=emit, auto=auto) - - set_xlim3d = maxes.Axes.set_xlim - set_ylim3d = maxes.Axes.set_ylim + view_margin=None, zmin=None, zmax=None): + """ + Set the 3D z-axis view limits. + + Parameters + ---------- + bottom : float, optional + The bottom zlim in data coordinates. Passing *None* leaves the + limit unchanged. + + The bottom and top zlims may also be passed as the tuple + (*bottom*, *top*) as the first positional argument (or as + the *bottom* keyword argument). + + .. ACCEPTS: (bottom: float, top: float) + + top : float, optional + The top zlim in data coordinates. Passing *None* leaves the + limit unchanged. + + emit : bool, default: True + Whether to notify observers of limit change. + + auto : bool or None, default: False + Whether to turn on autoscaling of the z-axis. *True* turns on, + *False* turns off, *None* leaves unchanged. + + view_margin : float, optional + The additional margin to apply to the limits. + + zmin, zmax : float, optional + They are equivalent to bottom and top respectively, and it is an + error to pass both *zmin* and *bottom* or *zmax* and *top*. + + Returns + ------- + bottom, top : (float, float) + The new z-axis limits in data coordinates. + + See Also + -------- + get_zlim + set_zbound, get_zbound + invert_zaxis, zaxis_inverted + + Notes + ----- + The *bottom* value may be greater than the *top* value, in which + case the z-axis values will decrease from *bottom* to *top*. + + Examples + -------- + >>> set_zlim(bottom, top) + >>> set_zlim((bottom, top)) + >>> bottom, top = set_zlim(bottom, top) + + One limit may be left unchanged. + + >>> set_zlim(top=top_lim) + + Limits may be passed in reverse order to flip the direction of + the z-axis. For example, suppose ``z`` represents depth of the + ocean in m. The z-axis limits might be set like the following + so 5000 m depth is at the bottom of the plot and the surface, + 0 m, is at the top. + + >>> set_zlim(5000, 0) + """ + return self._set_lim3d(self.zaxis, bottom, top, emit=emit, auto=auto, + view_margin=view_margin, axmin=zmin, axmax=zmax) + + set_xlim3d = set_xlim + set_ylim3d = set_ylim set_zlim3d = set_zlim def get_xlim(self): @@ -1049,6 +1376,15 @@ def clear(self): self._zmargin = mpl.rcParams['axes.zmargin'] else: self._zmargin = 0. + + xymargin = 0.05 * 10/11 # match mpl3.8 appearance + self.xy_dataLim = Bbox([[xymargin, xymargin], + [1 - xymargin, 1 - xymargin]]) + # z-limits are encoded in the x-component of the Bbox, y is un-used + self.zz_dataLim = Bbox.unit() + self._view_margin = 1/48 # default value to match mpl3.8 + self.autoscale_view() + self.grid(mpl.rcParams['axes3d.grid']) def _button_press(self, event): @@ -1177,7 +1513,7 @@ def _calc_coord(self, xv, yv, renderer=None): # Get the pane locations for each of the axes pane_locs = [] for axis in self._axis_map.values(): - xys, loc = axis.active_pane(renderer) + xys, loc = axis.active_pane() pane_locs.append(loc) # Find the distance to the nearest pane by projecting the view vector @@ -1289,9 +1625,9 @@ def drag_pan(self, button, key, x, y): dz = (maxz - minz) * duvw_projected[2] # Set the new axis limits - self.set_xlim3d(minx + dx, maxx + dx) - self.set_ylim3d(miny + dy, maxy + dy) - self.set_zlim3d(minz + dz, maxz + dz) + self.set_xlim3d(minx + dx, maxx + dx, auto=None) + self.set_ylim3d(miny + dy, maxy + dy, auto=None) + self.set_zlim3d(minz + dz, maxz + dz, auto=None) def _calc_view_axes(self, eye): """ @@ -1420,9 +1756,9 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z): cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() # Set the scaled axis limits - self.set_xlim3d(cx - dx*scale_x/2, cx + dx*scale_x/2) - self.set_ylim3d(cy - dy*scale_y/2, cy + dy*scale_y/2) - self.set_zlim3d(cz - dz*scale_z/2, cz + dz*scale_z/2) + self.set_xlim3d(cx - dx*scale_x/2, cx + dx*scale_x/2, auto=None) + self.set_ylim3d(cy - dy*scale_y/2, cy + dy*scale_y/2, auto=None) + self.set_zlim3d(cz - dz*scale_z/2, cz + dz*scale_z/2, auto=None) def _get_w_centers_ranges(self): """Get 3D world centers and axis ranges.""" @@ -1531,43 +1867,11 @@ def get_zbound(self): get_zlim, set_zlim invert_zaxis, zaxis_inverted """ - bottom, top = self.get_zlim() - if bottom < top: - return bottom, top + lower, upper = self.get_zlim() + if lower < upper: + return lower, upper else: - return top, bottom - - def set_zbound(self, lower=None, upper=None): - """ - Set the lower and upper numerical bounds of the z-axis. - - This method will honor axes inversion regardless of parameter order. - It will not change the autoscaling setting (`.get_autoscalez_on()`). - - Parameters - ---------- - lower, upper : float or None - The lower and upper bounds. If *None*, the respective axis bound - is not modified. - - See Also - -------- - get_zbound - get_zlim, set_zlim - invert_zaxis, zaxis_inverted - """ - if upper is None and np.iterable(lower): - lower, upper = lower - - old_lower, old_upper = self.get_zbound() - if lower is None: - lower = old_lower - if upper is None: - upper = old_upper - - self.set_zlim(sorted((lower, upper), - reverse=bool(self.zaxis_inverted())), - auto=None) + return upper, lower def text(self, x, y, z, s, zdir=None, **kwargs): """ @@ -3052,10 +3356,10 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', lower limits. In that case a caret symbol is used to indicate this. *lims*-arguments may be scalars, or array-likes of the same length as the errors. To use limits with inverted axes, - `~.Axes.set_xlim` or `~.Axes.set_ylim` must be called before - `errorbar`. Note the tricky parameter names: setting e.g. - *ylolims* to True means that the y-value is a *lower* limit of the - True value, so, only an *upward*-pointing arrow will be drawn! + `~.set_xlim`, `~.set_ylim`, or `~.set_zlim` must be + called before `errorbar`. Note the tricky parameter names: setting + e.g. *ylolims* to True means that the y-value is a *lower* limit of + the True value, so, only an *upward*-pointing arrow will be drawn! xuplims, yuplims, zuplims : bool, default: False Same as above, but for controlling the upper limits. diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 58792deae963..5c102437db85 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -276,21 +276,13 @@ def get_rotate_label(self, text): else: return len(text) > 4 - def _get_coord_info(self, renderer): + def _get_coord_info(self): mins, maxs = np.array([ self.axes.get_xbound(), self.axes.get_ybound(), self.axes.get_zbound(), ]).T - # Get the mean value for each bound: - centers = 0.5 * (maxs + mins) - - # Add a small offset between min/max point and the edge of the plot: - deltas = (maxs - mins) / 12 - mins -= 0.25 * deltas - maxs += 0.25 * deltas - # Project the bounds along the current position of the cube: bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] bounds_proj = self.axes._tunit_cube(bounds, self.axes.M) @@ -314,7 +306,17 @@ def _get_coord_info(self, renderer): elif vertical == 0: # looking at YZ plane highs = np.array([highs[0], False, False]) - return mins, maxs, centers, deltas, bounds_proj, highs + return mins, maxs, bounds_proj, highs + + def _calc_centers_deltas(self, maxs, mins): + centers = 0.5 * (maxs + mins) + # In mpl3.8, the scale factor was 1/12. mpl3.9 changes this to + # 1/12 * 24/25 = 0.08 to compensate for the change in automargin + # behavior and keep appearance the same. The 24/25 factor is from the + # 1/48 padding added to each side of the axis in mpl3.8. + scale = 0.08 + deltas = (maxs - mins) * scale + return centers, deltas def _get_axis_line_edge_points(self, minmax, maxmin, position=None): """Get the edge points for the black bolded axis line.""" @@ -409,8 +411,8 @@ def _get_tickdir(self, position): tickdir = np.roll(info_i, -j)[np.roll(tickdirs_base, j)][i] return tickdir - def active_pane(self, renderer): - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) + def active_pane(self): + mins, maxs, tc, highs = self._get_coord_info() info = self._axinfo index = info['i'] if not highs[index]: @@ -431,7 +433,7 @@ def draw_pane(self, renderer): renderer : `~matplotlib.backend_bases.RendererBase` subclass """ renderer.open_group('pane3d', gid=self.get_gid()) - xys, loc = self.active_pane(renderer) + xys, loc = self.active_pane() self.pane.xy = xys[:, :2] self.pane.draw(renderer) renderer.close_group('pane3d') @@ -446,6 +448,10 @@ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, ticks = self._update_ticks() info = self._axinfo index = info["i"] + juggled = info["juggled"] + + mins, maxs, tc, highs = self._get_coord_info() + centers, deltas = self._calc_centers_deltas(maxs, mins) # Draw ticks: tickdir = self._get_tickdir(pos) @@ -575,7 +581,8 @@ def draw(self, renderer): renderer.open_group("axis3d", gid=self.get_gid()) # Get general axis information: - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) + mins, maxs, tc, highs = self._get_coord_info() + centers, deltas = self._calc_centers_deltas(maxs, mins) # Calculate offset distances # A rough estimate; points are ambiguous since 3D plots rotate @@ -645,7 +652,7 @@ def draw_grid(self, renderer): info = self._axinfo index = info["i"] - mins, maxs, _, _, _, highs = self._get_coord_info(renderer) + mins, maxs, tc, highs = self._get_coord_info() minmax = np.where(highs, maxs, mins) maxmin = np.where(~highs, maxs, mins) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index df9f2ae52fd7..75f0e2261907 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -222,6 +222,7 @@ def test_bar3d_lightsource(): ['contour3d.png'], style='mpl20', tol=0.002 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) def test_contour3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') X, Y, Z = axes3d.get_test_data(0.05) @@ -233,6 +234,7 @@ def test_contour3d(): @mpl3d_image_comparison(['contour3d_extend3d.png'], style='mpl20') def test_contour3d_extend3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') X, Y, Z = axes3d.get_test_data(0.05) @@ -244,6 +246,7 @@ def test_contour3d_extend3d(): @mpl3d_image_comparison(['contourf3d.png'], style='mpl20') def test_contourf3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') X, Y, Z = axes3d.get_test_data(0.05) @@ -257,6 +260,7 @@ def test_contourf3d(): @mpl3d_image_comparison(['contourf3d_fill.png'], style='mpl20') def test_contourf3d_fill(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25)) @@ -300,6 +304,7 @@ def test_contourf3d_extend(fig_test, fig_ref, extend, levels): @mpl3d_image_comparison(['tricontour.png'], tol=0.02, style='mpl20') def test_tricontour(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() np.random.seed(19680801) @@ -369,6 +374,7 @@ def f(t): t1 = np.arange(0.0, 5.0, 0.1) t2 = np.arange(0.0, 5.0, 0.02) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure(figsize=plt.figaspect(2.)) ax = fig.add_subplot(2, 1, 1) ax.plot(t1, f(t1), 'bo', t2, f(t2), 'k--', markerfacecolor='green') @@ -400,6 +406,7 @@ def test_tight_layout_text(fig_test, fig_ref): @mpl3d_image_comparison(['scatter3d.png'], style='mpl20') def test_scatter3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') ax.scatter(np.arange(10), np.arange(10), np.arange(10), @@ -413,6 +420,7 @@ def test_scatter3d(): @mpl3d_image_comparison(['scatter3d_color.png'], style='mpl20') def test_scatter3d_color(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') @@ -597,12 +605,14 @@ def test_surface3d(): Z = np.sin(R) surf = ax.plot_surface(X, Y, Z, rcount=40, ccount=40, cmap=cm.coolwarm, lw=0, antialiased=False) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_zlim(-1.01, 1.01) fig.colorbar(surf, shrink=0.5, aspect=5) @image_comparison(['surface3d_label_offset_tick_position.png'], style='mpl20') def test_surface3d_label_offset_tick_position(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax = plt.figure().add_subplot(projection="3d") x, y = np.mgrid[0:6 * np.pi:0.25, 0:4 * np.pi:0.25] @@ -627,6 +637,7 @@ def test_surface3d_shaded(): Z = np.sin(R) ax.plot_surface(X, Y, Z, rstride=5, cstride=5, color=[0.25, 1, 0.25], lw=1, antialiased=False) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_zlim(-1.01, 1.01) @@ -695,6 +706,7 @@ def test_text3d(): ax.text(1, 1, 1, "red", color='red') ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_xlim3d(0, 10) ax.set_ylim3d(0, 10) ax.set_zlim3d(0, 10) @@ -808,6 +820,7 @@ def test_mixedsamplesraises(): @mpl3d_image_comparison(['quiver3d.png'], style='mpl20') def test_quiver3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') pivots = ['tip', 'middle', 'tail'] @@ -976,6 +989,7 @@ def test_add_collection3d_zs_array(): assert line is not None + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_xlim(-5, 5) ax.set_ylim(-4, 6) ax.set_zlim(-2, 2) @@ -1002,6 +1016,7 @@ def test_add_collection3d_zs_scalar(): assert line is not None + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_xlim(-5, 5) ax.set_ylim(-4, 6) ax.set_zlim(0, 2) @@ -1027,7 +1042,7 @@ def test_axes3d_labelpad(): # Tick labels also respect tick.pad (also from rcParams) for i, tick in enumerate(ax.yaxis.get_major_ticks()): - tick.set_pad(tick.get_pad() - i * 5) + tick.set_pad(tick.get_pad() + 5 - i * 5) @mpl3d_image_comparison(['axes3d_cla.png'], remove_text=False, style='mpl20') @@ -1123,6 +1138,7 @@ def test_proj_axes_cube(): for x, y, t in zip(txs, tys, ts): ax.text(x, y, t) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_xlim(-0.2, 0.2) ax.set_ylim(-0.2, 0.2) @@ -1152,6 +1168,7 @@ def test_proj_axes_cube_ortho(): for x, y, t in zip(txs, tys, ts): ax.text(x, y, t) + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax.set_xlim(-200, 200) ax.set_ylim(-200, 200) @@ -1171,6 +1188,7 @@ def test_world(): def test_autoscale(): fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) assert ax.get_zscale() == 'linear' + ax._view_margin = 0 ax.margins(x=0, y=.1, z=.2) ax.plot([0, 1], [0, 1], [0, 1]) assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.2, 1.2) @@ -1555,6 +1573,7 @@ def test_errorbar3d(): @image_comparison(['stem3d.png'], style='mpl20', tol=0.003) def test_stem3d(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig, axs = plt.subplots(2, 3, figsize=(8, 6), constrained_layout=True, subplot_kw={'projection': '3d'}) @@ -1639,6 +1658,7 @@ def test_colorbar_pos(): def test_inverted_zaxis(): fig = plt.figure() ax = fig.add_subplot(projection='3d') + ax.set_zlim(0, 1) assert not ax.zaxis_inverted() assert ax.get_zlim() == (0, 1) assert ax.get_zbound() == (0, 1) @@ -1671,17 +1691,17 @@ def test_inverted_zaxis(): def test_set_zlim(): fig = plt.figure() ax = fig.add_subplot(projection='3d') - assert ax.get_zlim() == (0, 1) + assert np.allclose(ax.get_zlim(), (-1/48, 49/48)) ax.set_zlim(zmax=2) - assert ax.get_zlim() == (0, 2) + assert np.allclose(ax.get_zlim(), (-1/48, 2)) ax.set_zlim(zmin=1) assert ax.get_zlim() == (1, 2) with pytest.raises( - TypeError, match="Cannot pass both 'bottom' and 'zmin'"): + TypeError, match="Cannot pass both 'lower' and 'min'"): ax.set_zlim(bottom=0, zmin=1) with pytest.raises( - TypeError, match="Cannot pass both 'top' and 'zmax'"): + TypeError, match="Cannot pass both 'upper' and 'max'"): ax.set_zlim(top=0, zmax=1) @@ -1755,13 +1775,13 @@ def convert_lim(dmin, dmax): ("zoom", MouseButton.LEFT, 'x', # zoom in ((-0.01, 0.10), (-0.03, 0.08), (-0.06, 0.06))), ("zoom", MouseButton.LEFT, 'y', # zoom in - ((-0.07, 0.04), (-0.03, 0.08), (0.00, 0.11))), + ((-0.07, 0.05), (-0.04, 0.08), (0.00, 0.12))), ("zoom", MouseButton.RIGHT, None, # zoom out - ((-0.09, 0.15), (-0.07, 0.17), (-0.06, 0.18))), + ((-0.09, 0.15), (-0.08, 0.17), (-0.07, 0.18))), ("pan", MouseButton.LEFT, None, - ((-0.70, -0.58), (-1.03, -0.91), (-1.27, -1.15))), + ((-0.70, -0.58), (-1.04, -0.91), (-1.27, -1.15))), ("pan", MouseButton.LEFT, 'x', - ((-0.96, -0.84), (-0.58, -0.46), (-0.06, 0.06))), + ((-0.97, -0.84), (-0.58, -0.46), (-0.06, 0.06))), ("pan", MouseButton.LEFT, 'y', ((0.20, 0.32), (-0.51, -0.39), (-1.27, -1.15)))]) def test_toolbar_zoom_pan(tool, button, key, expected): @@ -1859,6 +1879,7 @@ def test_subfigure_simple(): @image_comparison(baseline_images=['computed_zorder'], remove_text=True, extensions=['png'], style=('mpl20')) def test_computed_zorder(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax1 = fig.add_subplot(221, projection='3d') ax2 = fig.add_subplot(222, projection='3d') @@ -2063,6 +2084,7 @@ def test_pathpatch_3d(fig_test, fig_ref): remove_text=True, style='mpl20') def test_scatter_spiral(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') th = np.linspace(0, 2 * np.pi * 6, 256) @@ -2260,6 +2282,7 @@ def test_scatter_masked_color(): @mpl3d_image_comparison(['surface3d_zsort_inf.png'], style='mpl20') def test_surface3d_zsort_inf(): + plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') From a84c911fbf96244bca104caed58ca962212c3c88 Mon Sep 17 00:00:00 2001 From: Kritika Verma <95202116+SunSummoner@users.noreply.github.com> Date: Mon, 28 Aug 2023 07:25:22 +0530 Subject: [PATCH 0071/1120] Resolves #26421 Added an example for fig comparison decorator (#26538) Added an example for figure comparison decorator to testing documentation --- doc/devel/testing.rst | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/devel/testing.rst b/doc/devel/testing.rst index 06296f5dc701..3b84e4aa859c 100644 --- a/doc/devel/testing.rst +++ b/doc/devel/testing.rst @@ -122,6 +122,12 @@ case :file:`lib/matplotlib/tests/baseline_images/test_lines`). Put this new file under source code revision control (with ``git add``). When rerunning the tests, they should now pass. +It is preferred that new tests use ``style='mpl20'`` as this leads to smaller +figures and reflects the newer look of default Matplotlib plots. Also, if the +texts (labels, tick labels, etc) are not really part of what is tested, use +``remove_text=True`` as this will lead to smaller figures and reduce possible +issues with font mismatch on different platforms. + Baseline images take a lot of space in the Matplotlib repository. An alternative approach for image comparison tests is to use the `~matplotlib.testing.decorators.check_figures_equal` decorator, which should be @@ -130,11 +136,24 @@ images on the figures using two different methods (the tested method and the baseline method). The decorator will arrange for setting up the figures and then collect the drawn results and compare them. -It is preferred that new tests use ``style='mpl20'`` as this leads to smaller -figures and reflects the newer look of default Matplotlib plots. Also, if the -texts (labels, tick labels, etc) are not really part of what is tested, use -``remove_text=True`` as this will lead to smaller figures and reduce possible -issues with font mismatch on different platforms. +For example, this test compares two different methods to draw the same +circle: plotting a circle using a `matplotlib.patches.Circle` patch +vs plotting the circle using the parametric equation of a circle :: + + from matplotlib.testing.decorators import check_figures_equal + import matplotib.patches as mpatches + import matplotlib.pyplot as plt + import numpy as np + + @check_figures_equal(extensions=['png'], tol=100) + def test_parametric_circle_plot(fig_test, fig_ref): + red_circle_ref = mpatches.Circle((0, 0), 0.2, color='r', clip_on=False) + fig_ref.add_artist(red_circle_ref) + theta = np.linspace(0, 2 * np.pi, 150) + radius = 0.4 + fig_test.plot(radius * np.cos(theta), radius * np.sin(theta), color='r') + +Both comparison decorators have a tolerance argument ``tol`` that is used to specify the tolerance for difference in color value between the two images, where 255 is the maximal difference. The test fails if the average pixel difference is greater than this value. See the documentation of `~matplotlib.testing.decorators.image_comparison` and `~matplotlib.testing.decorators.check_figures_equal` for additional information From bd0da748bd376015bdf5c6296a4fa6d0d08080fd Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 28 Aug 2023 12:12:09 +0200 Subject: [PATCH 0072/1120] Properly disconnect machinery when removing child axes. --- lib/matplotlib/axes/_base.py | 3 ++- lib/matplotlib/figure.py | 18 ++++++++++++++++-- lib/matplotlib/tests/test_axes.py | 8 ++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3796d9bbe508..740c59bd85df 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2237,7 +2237,8 @@ def add_child_axes(self, ax): ax.stale_callback = martist._stale_axes_callback self.child_axes.append(ax) - ax._remove_method = self.child_axes.remove + ax._remove_method = functools.partial( + self.figure._remove_axes, owners=[self.child_axes]) self.stale = True return ax diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ce263c3d8d1c..4361ef655c81 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -936,11 +936,25 @@ def delaxes(self, ax): """ Remove the `~.axes.Axes` *ax* from the figure; update the current Axes. """ + self._remove_axes(ax, owners=[self._axstack, self._localaxes]) + + def _remove_axes(self, ax, owners): + """ + Common helper for removal of standard axes (via delaxes) and of child axes. + + Parameters + ---------- + ax : `~.AxesBase` + The Axes to remove. + owners + List of objects (list or _AxesStack) "owning" the axes, from which the Axes + will be remove()d. + """ + for owner in owners: + owner.remove(ax) - self._axstack.remove(ax) self._axobservers.process("_axes_change_event", self) self.stale = True - self._localaxes.remove(ax) self.canvas.release_mouse(ax) for name in ax._axis_names: # Break link between any shared axes diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0fcb2eb26cbb..c78be0ee9cbe 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8679,6 +8679,14 @@ def test_cla_clears_children_axes_and_fig(): assert art.figure is None +def test_child_axes_removal(): + fig, ax = plt.subplots() + marginal = ax.inset_axes([1, 0, .1, 1], sharey=ax) + marginal_twin = marginal.twinx() + marginal.remove() + ax.set(xlim=(-1, 1), ylim=(10, 20)) + + def test_scatter_color_repr_error(): def get_next_color(): From bd33e37006f2059b3bd59035c01f637f93b4226f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 29 Aug 2023 14:16:38 +0200 Subject: [PATCH 0073/1120] [Doc] Improve DSP-related examples --- .../specgram_demo.py | 22 +++++++----- .../examples/lines_bars_and_markers/cohere.py | 7 ++-- .../lines_bars_and_markers/csd_demo.py | 15 ++++---- .../lines_bars_and_markers/psd_demo.py | 16 +++++---- .../lines_bars_and_markers/spectrum_demo.py | 34 +++++++++---------- .../xcorr_acorr_demo.py | 8 +++-- 6 files changed, 55 insertions(+), 47 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/specgram_demo.py b/galleries/examples/images_contours_and_fields/specgram_demo.py index ef1799c4429f..5add9172cf2a 100644 --- a/galleries/examples/images_contours_and_fields/specgram_demo.py +++ b/galleries/examples/images_contours_and_fields/specgram_demo.py @@ -1,9 +1,9 @@ """ -================ -Spectrogram Demo -================ +=========== +Spectrogram +=========== -Demo of a spectrogram plot (`~.axes.Axes.specgram`). +Plotting a spectrogram using `~.Axes.specgram`. """ import matplotlib.pyplot as plt import numpy as np @@ -12,7 +12,7 @@ np.random.seed(19680801) dt = 0.0005 -t = np.arange(0.0, 20.0, dt) +t = np.arange(0.0, 20.5, dt) s1 = np.sin(2 * np.pi * 100 * t) s2 = 2 * np.sin(2 * np.pi * 400 * t) @@ -24,16 +24,22 @@ x = s1 + s2 + nse # the signal NFFT = 1024 # the length of the windowing segments -Fs = int(1.0 / dt) # the sampling frequency +Fs = 1/dt # the sampling frequency -fig, (ax1, ax2) = plt.subplots(nrows=2) +fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True) ax1.plot(t, x) -Pxx, freqs, bins, im = ax2.specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900) +ax1.set_ylabel('Signal') + +Pxx, freqs, bins, im = ax2.specgram(x, NFFT=NFFT, Fs=Fs) # The `specgram` method returns 4 objects. They are: # - Pxx: the periodogram # - freqs: the frequency vector # - bins: the centers of the time bins # - im: the .image.AxesImage instance representing the data in the plot +ax2.set_xlabel('Time (s)') +ax2.set_ylabel('Frequency (Hz)') +ax2.set_xlim(0, 20) + plt.show() # %% diff --git a/galleries/examples/lines_bars_and_markers/cohere.py b/galleries/examples/lines_bars_and_markers/cohere.py index cd6a11566afa..64124e37645e 100644 --- a/galleries/examples/lines_bars_and_markers/cohere.py +++ b/galleries/examples/lines_bars_and_markers/cohere.py @@ -3,7 +3,7 @@ Plotting the coherence of two signals ===================================== -An example showing how to plot the coherence of two signals. +An example showing how to plot the coherence of two signals using `~.Axes.cohere`. """ import matplotlib.pyplot as plt import numpy as np @@ -20,15 +20,14 @@ s1 = np.sin(2 * np.pi * 10 * t) + nse1 s2 = np.sin(2 * np.pi * 10 * t) + nse2 -fig, axs = plt.subplots(2, 1) +fig, axs = plt.subplots(2, 1, layout='constrained') axs[0].plot(t, s1, t, s2) axs[0].set_xlim(0, 2) -axs[0].set_xlabel('Time') +axs[0].set_xlabel('Time (s)') axs[0].set_ylabel('s1 and s2') axs[0].grid(True) cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt) axs[1].set_ylabel('Coherence') -fig.tight_layout() plt.show() diff --git a/galleries/examples/lines_bars_and_markers/csd_demo.py b/galleries/examples/lines_bars_and_markers/csd_demo.py index 8bba858df574..b2d903ae0885 100644 --- a/galleries/examples/lines_bars_and_markers/csd_demo.py +++ b/galleries/examples/lines_bars_and_markers/csd_demo.py @@ -1,16 +1,14 @@ """ -======== -CSD Demo -======== +============================ +Cross spectral density (CSD) +============================ -Compute the cross spectral density of two signals +Plot the cross spectral density (CSD) of two signals using `~.Axes.csd`. """ import matplotlib.pyplot as plt import numpy as np -fig, (ax1, ax2) = plt.subplots(2, 1) -# make a little extra space between the subplots -fig.subplots_adjust(hspace=0.5) +fig, (ax1, ax2) = plt.subplots(2, 1, layout='constrained') dt = 0.01 t = np.arange(0, 30, dt) @@ -32,10 +30,11 @@ ax1.plot(t, s1, t, s2) ax1.set_xlim(0, 5) -ax1.set_xlabel('Time') +ax1.set_xlabel('Time (s)') ax1.set_ylabel('s1 and s2') ax1.grid(True) cxy, f = ax2.csd(s1, s2, 256, 1. / dt) ax2.set_ylabel('CSD (dB)') + plt.show() diff --git a/galleries/examples/lines_bars_and_markers/psd_demo.py b/galleries/examples/lines_bars_and_markers/psd_demo.py index 0d2bc7df0906..52587fd6d7bf 100644 --- a/galleries/examples/lines_bars_and_markers/psd_demo.py +++ b/galleries/examples/lines_bars_and_markers/psd_demo.py @@ -1,9 +1,9 @@ """ -======== -Psd Demo -======== +============================ +Power spectral density (PSD) +============================ -Plotting Power Spectral Density (PSD) in Matplotlib. +Plotting power spectral density (PSD) using `~.Axes.psd`. The PSD is a common plot in the field of signal processing. NumPy has many useful libraries for computing a PSD. Below we demo a few examples @@ -26,8 +26,10 @@ cnse = cnse[:len(t)] s = 0.1 * np.sin(2 * np.pi * t) + cnse -fig, (ax0, ax1) = plt.subplots(2, 1) +fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained') ax0.plot(t, s) +ax0.set_xlabel('Time (s)') +ax0.set_ylabel('Signal') ax1.psd(s, 512, 1 / dt) plt.show() @@ -64,8 +66,8 @@ ], layout='constrained') axs['signal'].plot(t, y) -axs['signal'].set_xlabel('time [s]') -axs['signal'].set_ylabel('signal') +axs['signal'].set_xlabel('Time (s)') +axs['signal'].set_ylabel('Signal') # Plot the PSD with different amounts of zero padding. This uses the entire # time series at once diff --git a/galleries/examples/lines_bars_and_markers/spectrum_demo.py b/galleries/examples/lines_bars_and_markers/spectrum_demo.py index 6f9ba3e1d7d0..147d802b6eff 100644 --- a/galleries/examples/lines_bars_and_markers/spectrum_demo.py +++ b/galleries/examples/lines_bars_and_markers/spectrum_demo.py @@ -1,6 +1,6 @@ """ ======================== -Spectrum Representations +Spectrum representations ======================== The plots show different spectrum representations of a sine signal with @@ -24,28 +24,28 @@ s = 0.1 * np.sin(4 * np.pi * t) + cnse # the signal -fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(7, 7)) +fig = plt.figure(figsize=(7, 7), layout='constrained') +axs = fig.subplot_mosaic([["signal", "signal"], + ["magnitude", "log_magnitude"], + ["phase", "angle"]]) # plot time signal: -axs[0, 0].set_title("Signal") -axs[0, 0].plot(t, s, color='C0') -axs[0, 0].set_xlabel("Time") -axs[0, 0].set_ylabel("Amplitude") +axs["signal"].set_title("Signal") +axs["signal"].plot(t, s, color='C0') +axs["signal"].set_xlabel("Time (s)") +axs["signal"].set_ylabel("Amplitude") # plot different spectrum types: -axs[1, 0].set_title("Magnitude Spectrum") -axs[1, 0].magnitude_spectrum(s, Fs=Fs, color='C1') +axs["magnitude"].set_title("Magnitude Spectrum") +axs["magnitude"].magnitude_spectrum(s, Fs=Fs, color='C1') -axs[1, 1].set_title("Log. Magnitude Spectrum") -axs[1, 1].magnitude_spectrum(s, Fs=Fs, scale='dB', color='C1') +axs["log_magnitude"].set_title("Log. Magnitude Spectrum") +axs["log_magnitude"].magnitude_spectrum(s, Fs=Fs, scale='dB', color='C1') -axs[2, 0].set_title("Phase Spectrum ") -axs[2, 0].phase_spectrum(s, Fs=Fs, color='C2') +axs["phase"].set_title("Phase Spectrum ") +axs["phase"].phase_spectrum(s, Fs=Fs, color='C2') -axs[2, 1].set_title("Angle Spectrum") -axs[2, 1].angle_spectrum(s, Fs=Fs, color='C2') +axs["angle"].set_title("Angle Spectrum") +axs["angle"].angle_spectrum(s, Fs=Fs, color='C2') -axs[0, 1].remove() # don't display empty ax - -fig.tight_layout() plt.show() diff --git a/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py b/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py index 6789f65bf6d6..eff0d7269a49 100644 --- a/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py +++ b/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py @@ -1,7 +1,7 @@ """ -================================ -Cross- and Auto-Correlation Demo -================================ +=========================== +Cross- and auto-correlation +=========================== Example use of cross-correlation (`~.Axes.xcorr`) and auto-correlation (`~.Axes.acorr`) plots. @@ -17,9 +17,11 @@ fig, [ax1, ax2] = plt.subplots(2, 1, sharex=True) ax1.xcorr(x, y, usevlines=True, maxlags=50, normed=True, lw=2) ax1.grid(True) +ax1.set_title('Cross-correlation (xcorr)') ax2.acorr(x, usevlines=True, normed=True, maxlags=50, lw=2) ax2.grid(True) +ax2.set_title('Auto-correlation (acorr)') plt.show() From a2adf8b43c39fcead2f20835c93a1461e60946d8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 29 Aug 2023 02:18:19 +0200 Subject: [PATCH 0074/1120] [DOC] Clarify some tick-related docstrings Apply suggestions from code review Co-authored-by: hannah --- lib/matplotlib/axes/_base.py | 65 +++++++++++++++++++++--------------- lib/matplotlib/axis.py | 11 ++++-- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9c1dc99107e7..e000eab92d62 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2019,26 +2019,30 @@ def axis(self, arg=None, /, *, emit=True, **kwargs): If a bool, turns axis lines and labels on or off. If a string, possible values are: - ======== ========================================================== - Value Description - ======== ========================================================== - 'on' Turn on axis lines and labels. Same as ``True``. - 'off' Turn off axis lines and labels. Same as ``False``. - 'equal' Set equal scaling (i.e., make circles circular) by - changing axis limits. This is the same as - ``ax.set_aspect('equal', adjustable='datalim')``. - Explicit data limits may not be respected in this case. - 'scaled' Set equal scaling (i.e., make circles circular) by - changing dimensions of the plot box. This is the same as - ``ax.set_aspect('equal', adjustable='box', anchor='C')``. - Additionally, further autoscaling will be disabled. - 'tight' Set limits just large enough to show all data, then - disable further autoscaling. - 'auto' Automatic scaling (fill plot box with data). - 'image' 'scaled' with axis limits equal to data limits. - 'square' Square plot; similar to 'scaled', but initially forcing - ``xmax-xmin == ymax-ymin``. - ======== ========================================================== + ================ =========================================================== + Value Description + ================ =========================================================== + 'off' or `False` Hide all axis decorations, i.e. axis labels, spines, + tick marks, tick labels, and grid lines. + This is the same as `~.Axes.set_axis_off()`. + 'on' or `True` Do not hide all axis decorations, i.e. axis labels, spines, + tick marks, tick labels, and grid lines. + This is the same as `~.Axes.set_axis_on()`. + 'equal' Set equal scaling (i.e., make circles circular) by + changing the axis limits. This is the same as + ``ax.set_aspect('equal', adjustable='datalim')``. + Explicit data limits may not be respected in this case. + 'scaled' Set equal scaling (i.e., make circles circular) by + changing dimensions of the plot box. This is the same as + ``ax.set_aspect('equal', adjustable='box', anchor='C')``. + Additionally, further autoscaling will be disabled. + 'tight' Set limits just large enough to show all data, then + disable further autoscaling. + 'auto' Automatic scaling (fill plot box with data). + 'image' 'scaled' with axis limits equal to data limits. + 'square' Square plot; similar to 'scaled', but initially forcing + ``xmax-xmin == ymax-ymin``. + ================ =========================================================== emit : bool, default: True Whether observers are notified of the axis limit change. @@ -3423,18 +3427,25 @@ def tick_params(self, axis='both', **kwargs): def set_axis_off(self): """ - Turn the x- and y-axis off. + Hide all visual components of the x- and y-axis. - This affects the axis lines, ticks, ticklabels, grid and axis labels. + This sets a flag to suppress drawing of all axis decorations, i.e. + axis labels, axis spines, and the axis tick component (tick markers, + tick labels, and grid lines). Individual visibility settings of these + components are ignored as long as `set_axis_off()` is in effect. """ self.axison = False self.stale = True def set_axis_on(self): """ - Turn the x- and y-axis on. + Do not hide all visual components of the x- and y-axis. - This affects the axis lines, ticks, ticklabels, grid and axis labels. + This reverts the effect of a prior `.set_axis_off()` call. Whether the + individual axis decorations are drawn is controlled by their respective + visibility settings. + + This is on by default. """ self.axison = True self.stale = True @@ -3681,7 +3692,8 @@ def set_xlim(self, left=None, right=None, *, emit=True, auto=False, get_xscale = _axis_method_wrapper("xaxis", "get_scale") set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs") - set_xticks = _axis_method_wrapper("xaxis", "set_ticks") + set_xticks = _axis_method_wrapper("xaxis", "set_ticks", + doc_sub={'set_ticks': 'set_xticks'}) get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels") get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels") get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels") @@ -3912,7 +3924,8 @@ def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, get_yscale = _axis_method_wrapper("yaxis", "get_scale") set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs") - set_yticks = _axis_method_wrapper("yaxis", "set_ticks") + set_yticks = _axis_method_wrapper("yaxis", "set_ticks", + doc_sub={'set_ticks': 'set_yticks'}) get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels") get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels") get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels") diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 77bd34df69c5..56d4e6b77dc6 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -2090,7 +2090,7 @@ def _set_tick_locations(self, ticks, *, minor=False): def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs): """ - Set this Axis' tick locations and optionally labels. + Set this Axis' tick locations and optionally tick labels. If necessary, the view limits of the Axis are expanded so that all given ticks are visible. @@ -2103,14 +2103,19 @@ def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs): The values may be either floats or in axis units. + Pass an empty list to remove all ticks:: + + set_ticks([]) + Some tick formatters will not label arbitrary tick positions; e.g. log formatters only label decade ticks by default. In such a case you can set a formatter explicitly on the axis using `.Axis.set_major_formatter` or provide formatted *labels* yourself. labels : list of str, optional - List of tick labels. If not set, the labels are generated with - the axis tick `.Formatter`. + Tick labels for each location in *ticks*. *labels* must be of the same + length as *ticks*. If not set, the labels are generate using the axis + tick `.Formatter`. minor : bool, default: False If ``False``, set the major ticks; if ``True``, the minor ticks. **kwargs From a4b81b10d181e11d734f88e2e308d897e77c37ce Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 30 Aug 2023 09:23:47 +0200 Subject: [PATCH 0075/1120] [Doc] Shorten documentation links --- lib/matplotlib/widgets.py | 44 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index b0d026e3095b..0a31a9dd2529 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -3,7 +3,7 @@ =================== Widgets that are designed to work for any of the GUI backends. -All of these widgets require you to predefine a `matplotlib.axes.Axes` +All of these widgets require you to predefine an `~.axes.Axes` instance and pass that as the first parameter. Matplotlib doesn't try to be too smart with respect to layout -- you will have to figure out how wide and tall you want your Axes to be to accommodate your widget. @@ -170,9 +170,9 @@ class Button(AxesWidget): Attributes ---------- ax - The `matplotlib.axes.Axes` the button renders into. + The `~.axes.Axes` the button renders into. label - A `matplotlib.text.Text` instance. + A `.Text` instance. color The color of the button when not hovering. hovercolor @@ -190,7 +190,7 @@ def __init__(self, ax, label, image=None, The button text. image : array-like or PIL Image The image to place in the button, if not *None*. The parameter is - directly forwarded to `~matplotlib.axes.Axes.imshow`. + directly forwarded to `~.axes.Axes.imshow`. color : color The color of the button when not activated. hovercolor : color @@ -1007,9 +1007,9 @@ class CheckButtons(AxesWidget): ax : `~matplotlib.axes.Axes` The parent Axes for the widget. - labels : list of `.Text` + labels : list of `~matplotlib.text.Text` - rectangles : list of `.Rectangle` + rectangles : list of `~matplotlib.patches.Rectangle` lines : list of (`.Line2D`, `.Line2D`) pairs List of lines for the x's in the checkboxes. These lines exist for @@ -1019,7 +1019,7 @@ class CheckButtons(AxesWidget): def __init__(self, ax, labels, actives=None, *, useblit=True, label_props=None, frame_props=None, check_props=None): """ - Add check buttons to `matplotlib.axes.Axes` instance *ax*. + Add check buttons to `~.axes.Axes` instance *ax*. Parameters ---------- @@ -1876,16 +1876,16 @@ def circles(self): class SubplotTool(Widget): """ - A tool to adjust the subplot params of a `matplotlib.figure.Figure`. + A tool to adjust the subplot params of a `.Figure`. """ def __init__(self, targetfig, toolfig): """ Parameters ---------- - targetfig : `.Figure` + targetfig : `~matplotlib.figure.Figure` The figure instance to adjust. - toolfig : `.Figure` + toolfig : `~matplotlib.figure.Figure` The figure instance to embed the subplot tool into. """ @@ -2051,7 +2051,7 @@ class MultiCursor(Widget): canvas : object This parameter is entirely unused and only kept for back-compatibility. - axes : list of `matplotlib.axes.Axes` + axes : list of `~matplotlib.axes.Axes` The `~.axes.Axes` to attach the cursor to. useblit : bool, default: True @@ -2564,7 +2564,7 @@ class SpanSelector(_SelectorWidget): canvas updates. See the tutorial :ref:`blitting` for details. props : dict, default: {'facecolor': 'red', 'alpha': 0.5} - Dictionary of `matplotlib.patches.Patch` properties. + Dictionary of `.Patch` properties. onmove_callback : callable with signature ``func(min: float, max: float)``, optional Called on mouse move while the span is being selected. @@ -2578,8 +2578,7 @@ class SpanSelector(_SelectorWidget): handle_props : dict, default: None Properties of the handle lines at the edges of the span. Only used - when *interactive* is True. See `matplotlib.lines.Line2D` for valid - properties. + when *interactive* is True. See `.Line2D` for valid properties. grab_range : float, default: 10 Distance in pixels within which the interactive tool handles can be activated. @@ -2951,7 +2950,7 @@ class ToolLineHandles: direction : {"horizontal", "vertical"} Direction of handles, either 'vertical' or 'horizontal' line_props : dict, optional - Additional line properties. See `matplotlib.lines.Line2D`. + Additional line properties. See `.Line2D`. useblit : bool, default: True Whether to use blitting for faster drawing (if supported by the backend). See the tutorial :ref:`blitting` @@ -3060,9 +3059,9 @@ class ToolHandles: x, y : 1D arrays Coordinates of control handles. marker : str, default: 'o' - Shape of marker used to display handle. See `matplotlib.pyplot.plot`. + Shape of marker used to display handle. See `~.pyplot.plot`. marker_props : dict, optional - Additional marker properties. See `matplotlib.lines.Line2D`. + Additional marker properties. See `.Line2D`. useblit : bool, default: True Whether to use blitting for faster drawing (if supported by the backend). See the tutorial :ref:`blitting` @@ -3147,7 +3146,7 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent) props : dict, optional Properties with which the __ARTIST_NAME__ is drawn. See - `matplotlib.patches.Patch` for valid properties. + `.Patch` for valid properties. Default: ``dict(facecolor='red', edgecolor='black', alpha=0.2, fill=True)`` @@ -3165,7 +3164,7 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent) handle_props : dict, optional Properties with which the interactive handles (marker artists) are - drawn. See the marker arguments in `matplotlib.lines.Line2D` for valid + drawn. See the marker arguments in `.Line2D` for valid properties. Default values are defined in ``mpl.rcParams`` except for the default value of ``markeredgecolor`` which will be the same as the ``edgecolor`` property in *props*. @@ -3765,7 +3764,7 @@ def onselect(verts): backend). See the tutorial :ref:`blitting` for details. props : dict, optional - Properties with which the line is drawn, see `matplotlib.lines.Line2D` + Properties with which the line is drawn, see `.Line2D` for valid properties. Default values are defined in ``mpl.rcParams``. button : `.MouseButton` or list of `.MouseButton`, optional The mouse buttons used for rectangle selection. Default is ``None``, @@ -3842,15 +3841,14 @@ class PolygonSelector(_SelectorWidget): for details. props : dict, optional - Properties with which the line is drawn, see `matplotlib.lines.Line2D` - for valid properties. + Properties with which the line is drawn, see `.Line2D` for valid properties. Default:: dict(color='k', linestyle='-', linewidth=2, alpha=0.5) handle_props : dict, optional Artist properties for the markers drawn at the vertices of the polygon. - See the marker arguments in `matplotlib.lines.Line2D` for valid + See the marker arguments in `.Line2D` for valid properties. Default values are defined in ``mpl.rcParams`` except for the default value of ``markeredgecolor`` which will be the same as the ``color`` property in *props*. From d598ecbc454a154c57ed767b9328c67b04489feb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 18 Aug 2023 01:18:10 -0400 Subject: [PATCH 0076/1120] TST: Ensure test_webagg subprocess is terminated If the final communication times out or asserts, there is nothing to enforce that the subprocess is killed. --- .../tests/test_backends_interactive.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 7341ebd3277a..dbc0f0928b74 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -477,24 +477,27 @@ def test_webagg(): inspect.getsource(_test_interactive_impl) + "\n_test_interactive_impl()", "{}"], env={**os.environ, "MPLBACKEND": "webagg", "SOURCE_DATE_EPOCH": "0"}) - url = "http://{}:{}".format( - mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"]) + url = f'http://{mpl.rcParams["webagg.address"]}:{mpl.rcParams["webagg.port"]}' timeout = time.perf_counter() + _test_timeout - while True: - try: - retcode = proc.poll() - # check that the subprocess for the server is not dead - assert retcode is None - conn = urllib.request.urlopen(url) - break - except urllib.error.URLError: - if time.perf_counter() > timeout: - pytest.fail("Failed to connect to the webagg server.") - else: - continue - conn.close() - proc.send_signal(signal.SIGINT) - assert proc.wait(timeout=_test_timeout) == 0 + try: + while True: + try: + retcode = proc.poll() + # check that the subprocess for the server is not dead + assert retcode is None + conn = urllib.request.urlopen(url) + break + except urllib.error.URLError: + if time.perf_counter() > timeout: + pytest.fail("Failed to connect to the webagg server.") + else: + continue + conn.close() + proc.send_signal(signal.SIGINT) + assert proc.wait(timeout=_test_timeout) == 0 + finally: + if proc.poll() is None: + proc.kill() def _lazy_headless(): From 23d101a28e00e21723b7718a4fce99a727dff2d9 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:05:01 +0200 Subject: [PATCH 0077/1120] Do not configure axes properties via subplots(..., subplot_kw={...}) While technical possible, this is an anti-pattern IMHO. We should keep Axes creation and customization separate. Extracted from #14411. --- .../examples/shapes_and_collections/ellipse_demo.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/galleries/examples/shapes_and_collections/ellipse_demo.py b/galleries/examples/shapes_and_collections/ellipse_demo.py index 22de6998418d..0d7e72a1100d 100644 --- a/galleries/examples/shapes_and_collections/ellipse_demo.py +++ b/galleries/examples/shapes_and_collections/ellipse_demo.py @@ -22,16 +22,15 @@ angle=np.random.rand() * 360) for i in range(NUM)] -fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'}) +fig, ax = plt.subplots() +ax.set(xlim=(0, 10), ylim=(0, 10), aspect="equal") + for e in ells: ax.add_artist(e) e.set_clip_box(ax.bbox) e.set_alpha(np.random.rand()) e.set_facecolor(np.random.rand(3)) -ax.set_xlim(0, 10) -ax.set_ylim(0, 10) - plt.show() # %% @@ -45,15 +44,13 @@ angle_step = 45 # degrees angles = np.arange(0, 180, angle_step) -fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'}) +fig, ax = plt.subplots() +ax.set(xlim=(-2.2, 2.2), ylim=(-2.2, 2.2), aspect="equal") for angle in angles: ellipse = Ellipse((0, 0), 4, 2, angle=angle, alpha=0.1) ax.add_artist(ellipse) -ax.set_xlim(-2.2, 2.2) -ax.set_ylim(-2.2, 2.2) - plt.show() # %% From 8e88d1baebb6ccd75755ff82c6e8bf34499f9dcd Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 30 Aug 2023 12:25:55 +0200 Subject: [PATCH 0078/1120] [Doc] Improve set_layout_engine docs --- lib/matplotlib/figure.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4361ef655c81..8f80658f8708 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2475,7 +2475,7 @@ def __init__(self, - 'tight': Use the tight layout mechanism. This is a relatively simple algorithm that adjusts the subplot parameters so that - decorations do not overlap. See `.Figure.set_tight_layout` for + decorations do not overlap. See `.set_tight_layout` for further details. - 'none': Do not use a layout engine. @@ -2618,8 +2618,7 @@ def set_layout_engine(self, layout=None, **kwargs): Parameters ---------- - layout: {'constrained', 'compressed', 'tight', 'none'} or \ -`LayoutEngine` or None + layout : {'constrained', 'compressed', 'tight', 'none', `.LayoutEngine`, None} - 'constrained' will use `~.ConstrainedLayoutEngine` - 'compressed' will also use `~.ConstrainedLayoutEngine`, but with @@ -2628,6 +2627,8 @@ def set_layout_engine(self, layout=None, **kwargs): - 'tight' uses `~.TightLayoutEngine` - 'none' removes layout engine. + If a `.LayoutEngine` instance, that instance will be used. + If `None`, the behavior is controlled by :rc:`figure.autolayout` (which if `True` behaves as if 'tight' was passed) and :rc:`figure.constrained_layout.use` (which if `True` behaves as if @@ -2637,7 +2638,7 @@ def set_layout_engine(self, layout=None, **kwargs): Users and libraries can define their own layout engines and pass the instance directly as well. - kwargs: dict + **kwargs The keyword arguments are passed to the layout engine to set things like padding and margin sizes. Only used if *layout* is a string. From 319bb03de1f686b31e7131749bc0f600d0494b02 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 30 Aug 2023 13:17:23 +0200 Subject: [PATCH 0079/1120] [Doc] Add ACCEPTS for some Axes set methods --- lib/matplotlib/axes/_base.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index e000eab92d62..d5114d595ad5 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1554,10 +1554,12 @@ def set_prop_cycle(self, *args, **kwargs): Parameters ---------- - cycler : Cycler + cycler : `~cycler.Cycler` Set the given Cycler. *None* resets to the cycle defined by the current style. + .. ACCEPTS: `~cycler.Cycler` + label : str The property key. Must be a valid `.Artist` property. For example, 'color' or 'linestyle'. Aliases are allowed, @@ -3553,6 +3555,8 @@ def set_xbound(self, lower=None, upper=None): The lower and upper bounds. If *None*, the respective axis bound is not modified. + .. ACCEPTS: (lower: float, upper: float) + See Also -------- get_xbound @@ -3626,7 +3630,7 @@ def set_xlim(self, left=None, right=None, *, emit=True, auto=False, (*left*, *right*) as the first positional argument (or as the *left* keyword argument). - .. ACCEPTS: (bottom: float, top: float) + .. ACCEPTS: (left: float, right: float) right : float, optional The right xlim in data coordinates. Passing *None* leaves the @@ -3802,6 +3806,8 @@ def set_ybound(self, lower=None, upper=None): The lower and upper bounds. If *None*, the respective axis bound is not modified. + .. ACCEPTS: (lower: float, upper: float) + See Also -------- get_ybound From afce3038565af3947e9965533345f7e5a3380155 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:28:03 +0200 Subject: [PATCH 0080/1120] Update lib/matplotlib/pyplot.py --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index c804adc93cf6..04fdcbc744c9 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1134,7 +1134,7 @@ def figlegend(*args, **kwargs) -> Legend: @_docstring.dedent_interpd def axes( - arg: None | tuple(float, float, float, float) = None, + arg: None | tuple[float, float, float, float] = None, **kwargs ) -> matplotlib.axes.Axes: """ From 9ee4377df3a30a07636792cc37777e9e3e64a9c2 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 24 Aug 2023 21:38:44 -0500 Subject: [PATCH 0081/1120] Squeeze post-converted values when validating limits Closes #26596 Ensures that ndarrays can be passed to later on. In this particular case, we know that we expect a single value. --- lib/matplotlib/axes/_base.py | 2 ++ lib/matplotlib/tests/test_category.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3796d9bbe508..cbc7cc0adf41 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3562,6 +3562,8 @@ def _validate_converted_limits(self, limit, convert): """ if limit is not None: converted_limit = convert(limit) + if isinstance(converted_limit, np.ndarray): + converted_limit = converted_limit.squeeze() if (isinstance(converted_limit, Real) and not np.isfinite(converted_limit)): raise ValueError("Axis limits cannot be NaN or Inf") diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index 87dece6346f7..fd4aec88b574 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -1,4 +1,6 @@ """Catch all for categorical functions""" +import warnings + import pytest import numpy as np @@ -309,3 +311,13 @@ def test_hist(): n, bins, patches = ax.hist(['a', 'b', 'a', 'c', 'ff']) assert n.shape == (10,) np.testing.assert_allclose(n, [2., 0., 0., 1., 0., 0., 1., 0., 0., 1.]) + + +def test_set_lim(): + # Numpy 1.25 deprecated casting [2.] to float, catch_warnings added to error + # with numpy 1.25 and prior to the change from gh-26597 + # can be removed once the minimum numpy version has expired the warning + f, ax = plt.subplots() + ax.plot(["a", "b", "c", "d"], [1, 2, 3, 4]) + with warnings.catch_warnings(): + ax.set_xlim("b", "c") From 23aef2f9ec6ed6365162eaa1d0805dcb2fec24be Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 30 Aug 2023 21:30:36 +0200 Subject: [PATCH 0082/1120] Use standard method for closing QApp when last window is closed. setQuitOnLastWindowClosed(True) should be equivalent to lastWindowClosed.connect(quit), but is more introspectable (e.g. third-parties can check the value of quitOnLastWindowClosed()). AFAICT the code used lastWindowClosed.connect(quit) because it dates back to Qt3, where setQuitOnLastWindowClosed did not exist yet. --- lib/matplotlib/backends/backend_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 62d7a9358544..4b3783bc87ca 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -140,7 +140,7 @@ def _create_qApp(): image = str(cbook._get_data_path('images/matplotlib.svg')) icon = QtGui.QIcon(image) app.setWindowIcon(icon) - app.lastWindowClosed.connect(app.quit) + app.setQuitOnLastWindowClosed(True) cbook._setup_new_guiapp() if qt_version == 5: app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) From b50753ce2db412abc705961fe0921736ada5eec8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 14 Aug 2023 14:57:50 -0400 Subject: [PATCH 0083/1120] Remove unused parameters to parse actions Pyparsing allows you to leave out the parameters (starting from the left) if they are unused. --- lib/matplotlib/_mathtext.py | 49 +++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 5b4e8ed20db3..ba0f1d4aa81d 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2070,18 +2070,18 @@ def push_state(self): """Push a new `State` onto the stack, copying the current state.""" self._state_stack.append(self.get_state().copy()) - def main(self, s, loc, toks): + def main(self, toks): return [Hlist(toks)] - def math_string(self, s, loc, toks): + def math_string(self, toks): return self._math_expression.parseString(toks[0][1:-1], parseAll=True) - def math(self, s, loc, toks): + def math(self, toks): hlist = Hlist(toks) self.pop_state() return [hlist] - def non_math(self, s, loc, toks): + def non_math(self, toks): s = toks[0].replace(r'\$', '$') symbols = [Char(c, self.get_state()) for c in s] hlist = Hlist(symbols) @@ -2092,7 +2092,7 @@ def non_math(self, s, loc, toks): float_literal = staticmethod(pyparsing_common.convertToFloat) - def text(self, s, loc, toks): + def text(self, toks): self.push_state() state = self.get_state() state.font = 'rm' @@ -2132,12 +2132,12 @@ def _make_space(self, percentage): r'\!': -0.16667, # -3/18 em = -3 mu } - def space(self, s, loc, toks): + def space(self, toks): num = self._space_widths[toks["space"]] box = self._make_space(num) return [box] - def customspace(self, s, loc, toks): + def customspace(self, toks): return [self._make_space(toks["space"])] def symbol(self, s, loc, toks): @@ -2215,7 +2215,7 @@ def unknown_symbol(self, s, loc, toks): _wide_accents = set(r"widehat widetilde widebar".split()) - def accent(self, s, loc, toks): + def accent(self, toks): state = self.get_state() thickness = state.get_current_underline_thickness() accent = toks["accent"] @@ -2276,30 +2276,30 @@ def operatorname(self, s, loc, toks): return Hlist(hlist_list) - def start_group(self, s, loc, toks): + def start_group(self, toks): self.push_state() # Deal with LaTeX-style font tokens if toks.get("font"): self.get_state().font = toks.get("font") return [] - def group(self, s, loc, toks): + def group(self, toks): grp = Hlist(toks.get("group", [])) return [grp] - def required_group(self, s, loc, toks): + def required_group(self, toks): return Hlist(toks.get("group", [])) optional_group = required_group - def end_group(self, s, loc, toks): + def end_group(self): self.pop_state() return [] def unclosed_group(self, s, loc, toks): raise ParseFatalException(s, len(s), "Expected '}'") - def font(self, s, loc, toks): + def font(self, toks): self.get_state().font = toks["font"] return [] @@ -2320,9 +2320,6 @@ def is_slanted(self, nucleus): return nucleus.is_slanted() return False - def is_between_brackets(self, s, loc): - return False - def subsuper(self, s, loc, toks): nucleus = toks.get("nucleus", Hbox(0)) subsuper = toks.get("subsuper", []) @@ -2523,26 +2520,26 @@ def _genfrac(self, ldelim, rdelim, rule, style, num, den): return self._auto_sized_delimiter(ldelim, result, rdelim) return result - def style_literal(self, s, loc, toks): + def style_literal(self, toks): return self._MathStyle(int(toks["style_literal"])) - def genfrac(self, s, loc, toks): + def genfrac(self, toks): return self._genfrac( toks.get("ldelim", ""), toks.get("rdelim", ""), toks["rulesize"], toks.get("style", self._MathStyle.TEXTSTYLE), toks["num"], toks["den"]) - def frac(self, s, loc, toks): + def frac(self, toks): return self._genfrac( "", "", self.get_state().get_current_underline_thickness(), self._MathStyle.TEXTSTYLE, toks["num"], toks["den"]) - def dfrac(self, s, loc, toks): + def dfrac(self, toks): return self._genfrac( "", "", self.get_state().get_current_underline_thickness(), self._MathStyle.DISPLAYSTYLE, toks["num"], toks["den"]) - def binom(self, s, loc, toks): + def binom(self, toks): return self._genfrac( "(", ")", 0, self._MathStyle.TEXTSTYLE, toks["num"], toks["den"]) @@ -2579,7 +2576,7 @@ def _genset(self, s, loc, toks): overset = underset = _genset - def sqrt(self, s, loc, toks): + def sqrt(self, toks): root = toks.get("root") body = toks["value"] state = self.get_state() @@ -2619,7 +2616,7 @@ def sqrt(self, s, loc, toks): rightside]) # Body return [hlist] - def overline(self, s, loc, toks): + def overline(self, toks): body = toks["body"] state = self.get_state() @@ -2670,14 +2667,14 @@ def _auto_sized_delimiter(self, front, middle, back): hlist = Hlist(parts) return hlist - def auto_delim(self, s, loc, toks): + def auto_delim(self, toks): return self._auto_sized_delimiter( toks["left"], # if "mid" in toks ... can be removed when requiring pyparsing 3. toks["mid"].asList() if "mid" in toks else [], toks["right"]) - def boldsymbol(self, s, loc, toks): + def boldsymbol(self, toks): self.push_state() state = self.get_state() hlist = [] @@ -2703,7 +2700,7 @@ def boldsymbol(self, s, loc, toks): return Hlist(hlist) - def substack(self, s, loc, toks): + def substack(self, toks): parts = toks["parts"] state = self.get_state() thickness = state.get_current_underline_thickness() From 03651fc6cd6c025c12896d5a2c8ba1921d628d48 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:25:19 +0200 Subject: [PATCH 0084/1120] [DOC] Remove "Discouraged" notices that have been superseded by deprecation --- lib/matplotlib/figure.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4361ef655c81..5c115c868187 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2788,12 +2788,7 @@ def get_tight_layout(self): pending=True) def set_tight_layout(self, tight): """ - [*Discouraged*] Set whether and how `.tight_layout` is called when - drawing. - - .. admonition:: Discouraged - - This method is discouraged in favor of `~.set_layout_engine`. + Set whether and how `.tight_layout` is called when drawing. Parameters ---------- @@ -2822,8 +2817,7 @@ def get_constrained_layout(self): pending=True) def set_constrained_layout(self, constrained): """ - [*Discouraged*] Set whether ``constrained_layout`` is used upon - drawing. + Set whether ``constrained_layout`` is used upon drawing. If None, :rc:`figure.constrained_layout.use` value will be used. @@ -2832,10 +2826,6 @@ def set_constrained_layout(self, constrained): overridden. These pads are in inches and default to 3.0/72.0. ``w_pad`` is the width padding and ``h_pad`` is the height padding. - .. admonition:: Discouraged - - This method is discouraged in favor of `~.set_layout_engine`. - Parameters ---------- constrained : bool or dict or None From 63cfa1685dc0c39934f7cffcb8de256931540b2d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 03:58:53 -0400 Subject: [PATCH 0085/1120] TYP: Add typed classes around mathtext data objects --- lib/matplotlib/_mathtext.py | 149 ++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 41 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index ba0f1d4aa81d..3dd165a11555 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2,8 +2,9 @@ Implementation details for :mod:`.mathtext`. """ +from __future__ import annotations + import copy -from collections import namedtuple import enum import functools import logging @@ -12,6 +13,8 @@ import types import unicodedata import string +import typing as T +from typing import NamedTuple import numpy as np from pyparsing import ( @@ -34,6 +37,9 @@ else: from pyparsing import nested_expr +if T.TYPE_CHECKING: + from .ft2font import FT2Font, Glyph + ParserElement.enablePackrat() _log = logging.getLogger("matplotlib.mathtext") @@ -64,24 +70,49 @@ def get_unicode_index(symbol): # Publicly exported. ) from err -VectorParse = namedtuple("VectorParse", "width height depth glyphs rects", - module="matplotlib.mathtext") -VectorParse.__doc__ = r""" -The namedtuple type returned by ``MathTextParser("path").parse(...)``. +class VectorParse(NamedTuple): + """ + The namedtuple type returned by ``MathTextParser("path").parse(...)``. -This tuple contains the global metrics (*width*, *height*, *depth*), a list of -*glyphs* (including their positions) and of *rect*\angles. -""" + Attributes + ---------- + width, height, depth : float + The global metrics. + glyphs : list + The glyphs including their positions. + rect : list + The list of rectangles. + """ + width: float + height: float + depth: float + glyphs: list[tuple[FT2Font, float, int, float, float]] + rects: list[tuple[float, float, float, float]] +VectorParse.__module__ = "matplotlib.mathtext" -RasterParse = namedtuple("RasterParse", "ox oy width height depth image", - module="matplotlib.mathtext") -RasterParse.__doc__ = r""" -The namedtuple type returned by ``MathTextParser("agg").parse(...)``. -This tuple contains the global metrics (*width*, *height*, *depth*), and a -raster *image*. The offsets *ox*, *oy* are always zero. -""" +class RasterParse(NamedTuple): + """ + The namedtuple type returned by ``MathTextParser("agg").parse(...)``. + + Attributes + ---------- + ox, oy : float + The offsets are always zero. + width, height, depth : float + The global metrics. + image : FT2Image + A raster image. + """ + ox: float + oy: float + width: float + height: float + depth: float + image: FT2Image + +RasterParse.__module__ = "matplotlib.mathtext" class Output: @@ -143,6 +174,48 @@ def to_raster(self, *, antialiased): return RasterParse(0, 0, w, h + d, d, image) +class FontMetrics(NamedTuple): + """ + Metrics of a font. + + Attributes + ---------- + advance : float + The advance distance (in points) of the glyph. + height : float + The height of the glyph in points. + width : float + The width of the glyph in points. + xmin, xmax, ymin, ymax : float + The ink rectangle of the glyph. + iceberg : float + The distance from the baseline to the top of the glyph. (This corresponds to + TeX's definition of "height".) + slanted : bool + Whether the glyph should be considered as "slanted" (currently used for kerning + sub/superscripts). + """ + advance: float + height: float + width: float + xmin: float + xmax: float + ymin: float + ymax: float + iceberg: float + slanted: bool + + +class FontInfo(NamedTuple): + font: FT2Font + fontsize: float + postscript_name: str + metrics: FontMetrics + num: int + glyph: Glyph + offset: float + + class Fonts: """ An abstract base class for a system of fonts to use for mathtext. @@ -197,19 +270,7 @@ def get_metrics(self, font, font_class, sym, fontsize, dpi): Returns ------- - object - - The returned object has the following attributes (all floats, - except *slanted*): - - - *advance*: The advance distance (in points) of the glyph. - - *height*: The height of the glyph in points. - - *width*: The width of the glyph in points. - - *xmin*, *xmax*, *ymin*, *ymax*: The ink rectangle of the glyph - - *iceberg*: The distance from the baseline to the top of the - glyph. (This corresponds to TeX's definition of "height".) - - *slanted*: Whether the glyph should be considered as "slanted" - (currently used for kerning sub/superscripts). + FontMetrics """ info = self._get_info(font, font_class, sym, fontsize, dpi) return info.metrics @@ -295,7 +356,7 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi): xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox] offset = self._get_offset(font, glyph, fontsize, dpi) - metrics = types.SimpleNamespace( + metrics = FontMetrics( advance = glyph.linearHoriAdvance/65536.0, height = glyph.height/64.0, width = glyph.width/64.0, @@ -308,7 +369,7 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi): slanted = slanted ) - return types.SimpleNamespace( + return FontInfo( font = font, fontsize = fontsize, postscript_name = font.postscript_name, @@ -810,33 +871,33 @@ class FontConstantsBase: be reliably retrieved from the font metrics in the font itself. """ # Percentage of x-height of additional horiz. space after sub/superscripts - script_space = 0.05 + script_space: T.ClassVar[float] = 0.05 # Percentage of x-height that sub/superscripts drop below the baseline - subdrop = 0.4 + subdrop: T.ClassVar[float] = 0.4 # Percentage of x-height that superscripts are raised from the baseline - sup1 = 0.7 + sup1: T.ClassVar[float] = 0.7 # Percentage of x-height that subscripts drop below the baseline - sub1 = 0.3 + sub1: T.ClassVar[float] = 0.3 # Percentage of x-height that subscripts drop below the baseline when a # superscript is present - sub2 = 0.5 + sub2: T.ClassVar[float] = 0.5 # Percentage of x-height that sub/superscripts are offset relative to the # nucleus edge for non-slanted nuclei - delta = 0.025 + delta: T.ClassVar[float] = 0.025 # Additional percentage of last character height above 2/3 of the # x-height that superscripts are offset relative to the subscript # for slanted nuclei - delta_slanted = 0.2 + delta_slanted: T.ClassVar[float] = 0.2 # Percentage of x-height that superscripts and subscripts are offset for # integrals - delta_integral = 0.1 + delta_integral: T.ClassVar[float] = 0.1 class ComputerModernFontConstants(FontConstantsBase): @@ -1333,8 +1394,14 @@ def __init__(self, state): super().__init__(thickness, np.inf, np.inf, state) -_GlueSpec = namedtuple( - "_GlueSpec", "width stretch stretch_order shrink shrink_order") +class _GlueSpec(NamedTuple): + width: float + stretch: float + stretch_order: int + shrink: float + shrink_order: int + + _GlueSpec._named = { # type: ignore[attr-defined] 'fil': _GlueSpec(0., 1., 1, 0., 0), 'fill': _GlueSpec(0., 1., 2, 0., 0), @@ -1358,7 +1425,7 @@ class Glue(Node): def __init__(self, glue_type): super().__init__() if isinstance(glue_type, str): - glue_spec = _GlueSpec._named[glue_type] + glue_spec = _GlueSpec._named[glue_type] # type: ignore[attr-defined] elif isinstance(glue_type, _GlueSpec): glue_spec = glue_type else: From 7c8ed22173f5880107cb2125ba16c21d6ee36bd0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 04:48:46 -0400 Subject: [PATCH 0086/1120] Add abstract methods and ABC on Fonts classes mypy rightfully complains that these methods called in the base class don't exist, so correctly indicate what should be implemented there. The ABC is not strictly necessary, but it helps with understanding as a lot of these have code that makes them seem like _not_ an ABC. --- lib/matplotlib/_mathtext.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 3dd165a11555..e41fdee5444c 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -4,6 +4,7 @@ from __future__ import annotations +import abc import copy import enum import functools @@ -216,7 +217,7 @@ class FontInfo(NamedTuple): offset: float -class Fonts: +class Fonts(abc.ABC): """ An abstract base class for a system of fonts to use for mathtext. @@ -248,6 +249,13 @@ def get_kern(self, font1, fontclass1, sym1, fontsize1, """ return 0. + def _get_font(self, font: str) -> FT2Font: + raise NotImplementedError + + def _get_info(self, font: str, font_class: str, sym: str, fontsize: float, + dpi: float) -> FontInfo: + raise NotImplementedError + def get_metrics(self, font, font_class, sym, fontsize, dpi): r""" Parameters @@ -313,7 +321,7 @@ def get_sized_alternatives_for_symbol(self, fontname, sym): return [(fontname, sym)] -class TruetypeFonts(Fonts): +class TruetypeFonts(Fonts, metaclass=abc.ABCMeta): """ A generic base class for all font setups that use Truetype fonts (through FT2Font). @@ -324,6 +332,7 @@ def __init__(self, *args, **kwargs): # Per-instance cache. self._get_info = functools.cache(self._get_info) self._fonts = {} + self.fontmap: dict[str | int, str] = {} filename = findfont(self.default_font_prop) default_font = get_font(filename) @@ -348,6 +357,10 @@ def _get_offset(self, font, glyph, fontsize, dpi): return (glyph.height / 64 / 2) + (fontsize/3 * dpi/72) return 0. + def _get_glyph(self, fontname: str, font_class: str, + sym: str) -> tuple[FT2Font, int, bool]: + raise NotImplementedError + # The return value of _get_info is cached per-instance. def _get_info(self, fontname, font_class, sym, fontsize, dpi): font, num, slanted = self._get_glyph(fontname, font_class, sym) @@ -429,7 +442,6 @@ def __init__(self, *args, **kwargs): self._stix_fallback = StixFonts(*args, **kwargs) super().__init__(*args, **kwargs) - self.fontmap = {} for key, val in self._fontmap.items(): fullpath = findfont(val) self.fontmap[key] = fullpath @@ -543,7 +555,6 @@ def __init__(self, *args, **kwargs): self._fallback_font = font_cls(*args, **kwargs) if font_cls else None super().__init__(*args, **kwargs) - self.fontmap = {} for texfont in "cal rm tt it bf sf bfit".split(): prop = mpl.rcParams['mathtext.' + texfont] font = findfont(prop) @@ -641,7 +652,8 @@ def get_sized_alternatives_for_symbol(self, fontname, sym): return [(fontname, sym)] -class DejaVuFonts(UnicodeFonts): +class DejaVuFonts(UnicodeFonts, metaclass=abc.ABCMeta): + _fontmap: dict[str | int, str] = {} def __init__(self, *args, **kwargs): # This must come first so the backend's owner is set correctly @@ -651,7 +663,6 @@ def __init__(self, *args, **kwargs): self._fallback_font = StixSansFonts(*args, **kwargs) self.bakoma = BakomaFonts(*args, **kwargs) TruetypeFonts.__init__(self, *args, **kwargs) - self.fontmap = {} # Include Stix sized alternatives for glyphs self._fontmap.update({ 1: 'STIXSizeOneSym', @@ -749,7 +760,6 @@ class StixFonts(UnicodeFonts): def __init__(self, *args, **kwargs): TruetypeFonts.__init__(self, *args, **kwargs) - self.fontmap = {} for key, name in self._fontmap.items(): fullpath = findfont(name) self.fontmap[key] = fullpath From d0eca17a75906e47636166debf63f4e729b97e2f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 05:38:17 -0400 Subject: [PATCH 0087/1120] Re-arrange some mathtext code to avoid variable reuse --- lib/matplotlib/_mathtext.py | 53 +++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index e41fdee5444c..10e591d55ca6 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -768,21 +768,25 @@ def __init__(self, *args, **kwargs): def _map_virtual_font(self, fontname, font_class, uniindex): # Handle these "fonts" that are actually embedded in # other fonts. - mapping = stix_virtual_fonts.get(fontname) - if (self._sans and mapping is None + font_mapping = stix_virtual_fonts.get(fontname) + if (self._sans and font_mapping is None and fontname not in ('regular', 'default')): - mapping = stix_virtual_fonts['sf'] + font_mapping = stix_virtual_fonts['sf'] doing_sans_conversion = True else: doing_sans_conversion = False - if mapping is not None: - if isinstance(mapping, dict): - try: - mapping = mapping[font_class] - except KeyError: - mapping = mapping['rm'] + if isinstance(font_mapping, dict): + try: + mapping = font_mapping[font_class] + except KeyError: + mapping = font_mapping['rm'] + elif isinstance(font_mapping, list): + mapping = font_mapping + else: + mapping = None + if mapping is not None: # Binary search for the source glyph lo = 0 hi = len(mapping) @@ -1965,11 +1969,14 @@ def csnames(group, names): ends_with_alpha.append(name) else: ends_with_nonalpha.append(name) - return Regex(r"\\(?P<{}>(?:{})(?![A-Za-z]){})".format( - group, - "|".join(map(re.escape, ends_with_alpha)), - "".join(f"|{s}" for s in map(re.escape, ends_with_nonalpha)), - )) + return Regex( + r"\\(?P<{group}>(?:{alpha})(?![A-Za-z]){additional}{nonalpha})".format( + group=group, + alpha="|".join(map(re.escape, ends_with_alpha)), + additional="|" if ends_with_nonalpha else "", + nonalpha="|".join(map(re.escape, ends_with_nonalpha)), + ) + ) p.float_literal = Regex(r"[-+]?([0-9]+\.?[0-9]*|\.[0-9]+)") p.space = oneOf(self._space_widths)("space") @@ -2458,9 +2465,9 @@ def subsuper(self, s, loc, toks): hlist.hpack(width, 'exactly') vlist.extend([Vbox(0, vgap), hlist]) shift = hlist.height + vgap + nucleus.depth - vlist = Vlist(vlist) - vlist.shift_amount = shift - result = Hlist([vlist]) + vlt = Vlist(vlist) + vlt.shift_amount = shift + result = Hlist([vlt]) return [result] # We remove kerning on the last character for consistency (otherwise @@ -2781,20 +2788,20 @@ def substack(self, toks): parts = toks["parts"] state = self.get_state() thickness = state.get_current_underline_thickness() - vlist = [] hlist = [Hlist(k) for k in parts[0]] max_width = max(map(lambda c: c.width, hlist)) + vlist = [] for sub in hlist: cp = HCentered([sub]) cp.hpack(max_width, 'exactly') vlist.append(cp) - vlist = [val for pair in zip(vlist, - [Vbox(0, thickness * 2)] * - len(vlist)) for val in pair] - del vlist[-1] - vlt = Vlist(vlist) + stack = [val + for pair in zip(vlist, [Vbox(0, thickness * 2)] * len(vlist)) + for val in pair] + del stack[-1] + vlt = Vlist(stack) result = [Hlist([vlt])] return result From 2bd74a6fe39490bdaf031f3ada183c487b2ecd31 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 05:49:51 -0400 Subject: [PATCH 0088/1120] mathtext: Make pyparsing cache reset more explicit Just as enabling the packrat cache occurs on the class overall, so does the reset. --- lib/matplotlib/_mathtext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 10e591d55ca6..5df366de8f6b 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2139,7 +2139,7 @@ def parse(self, s, fonts_object, fontsize, dpi): self._in_subscript_or_superscript = False # prevent operator spacing from leaking into a new expression self._em_width_cache = {} - self._expression.resetCache() + ParserElement.resetCache() return result[0] def get_state(self): From a5f8ec4b3a520b35117b2ee197aea40bc6db9df7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 05:10:21 -0400 Subject: [PATCH 0089/1120] TYP: Add type hints to all mathtext internals --- lib/matplotlib/_mathtext.py | 418 +++++++++++++++++-------------- lib/matplotlib/_mathtext_data.py | 6 +- 2 files changed, 236 insertions(+), 188 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 5df366de8f6b..b23cb67116ed 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -29,7 +29,7 @@ from ._mathtext_data import ( latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni) from .font_manager import FontProperties, findfont, get_font -from .ft2font import FT2Image, KERNING_DEFAULT +from .ft2font import FT2Font, FT2Image, KERNING_DEFAULT from packaging.version import parse as parse_version from pyparsing import __version__ as pyparsing_version @@ -39,7 +39,8 @@ from pyparsing import nested_expr if T.TYPE_CHECKING: - from .ft2font import FT2Font, Glyph + from collections.abc import Iterable + from .ft2font import Glyph ParserElement.enablePackrat() _log = logging.getLogger("matplotlib.mathtext") @@ -49,7 +50,7 @@ # FONTS -def get_unicode_index(symbol): # Publicly exported. +def get_unicode_index(symbol: str) -> int: # Publicly exported. r""" Return the integer index (from the Unicode table) of *symbol*. @@ -124,12 +125,12 @@ class Output: a `RasterParse` by `.MathTextParser.parse`. """ - def __init__(self, box): + def __init__(self, box: Box): self.box = box - self.glyphs = [] # (ox, oy, info) - self.rects = [] # (x1, y1, x2, y2) + self.glyphs: list[tuple[float, float, FontInfo]] = [] # (ox, oy, info) + self.rects: list[tuple[float, float, float, float]] = [] # (x1, y1, x2, y2) - def to_vector(self): + def to_vector(self) -> VectorParse: w, h, d = map( np.ceil, [self.box.width, self.box.height, self.box.depth]) gs = [(info.font, info.fontsize, info.num, ox, h - oy + info.offset) @@ -138,7 +139,7 @@ def to_vector(self): for x1, y1, x2, y2 in self.rects] return VectorParse(w, h + d, d, gs, rs) - def to_raster(self, *, antialiased): + def to_raster(self, *, antialiased: bool) -> RasterParse: # Metrics y's and mathtext y's are oriented in opposite directions, # hence the switch between ymin and ymax. xmin = min([*[ox + info.metrics.xmin for ox, oy, info in self.glyphs], @@ -226,7 +227,7 @@ class Fonts(abc.ABC): to do the actual drawing. """ - def __init__(self, default_font_prop, load_glyph_flags): + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): """ Parameters ---------- @@ -240,8 +241,9 @@ def __init__(self, default_font_prop, load_glyph_flags): self.default_font_prop = default_font_prop self.load_glyph_flags = load_glyph_flags - def get_kern(self, font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi): + def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float, + font2: str, fontclass2: str, sym2: str, fontsize2: float, + dpi: float) -> float: """ Get the kerning distance for font between *sym1* and *sym2*. @@ -256,7 +258,8 @@ def _get_info(self, font: str, font_class: str, sym: str, fontsize: float, dpi: float) -> FontInfo: raise NotImplementedError - def get_metrics(self, font, font_class, sym, fontsize, dpi): + def get_metrics(self, font: str, font_class: str, sym: str, fontsize: float, + dpi: float) -> FontMetrics: r""" Parameters ---------- @@ -283,8 +286,8 @@ def get_metrics(self, font, font_class, sym, fontsize, dpi): info = self._get_info(font, font_class, sym, fontsize, dpi) return info.metrics - def render_glyph( - self, output, ox, oy, font, font_class, sym, fontsize, dpi): + def render_glyph(self, output: Output, ox: float, oy: float, font: str, + font_class: str, sym: str, fontsize: float, dpi: float) -> None: """ At position (*ox*, *oy*), draw the glyph specified by the remaining parameters (see `get_metrics` for their detailed description). @@ -292,26 +295,28 @@ def render_glyph( info = self._get_info(font, font_class, sym, fontsize, dpi) output.glyphs.append((ox, oy, info)) - def render_rect_filled(self, output, x1, y1, x2, y2): + def render_rect_filled(self, output: Output, + x1: float, y1: float, x2: float, y2: float) -> None: """ Draw a filled rectangle from (*x1*, *y1*) to (*x2*, *y2*). """ output.rects.append((x1, y1, x2, y2)) - def get_xheight(self, font, fontsize, dpi): + def get_xheight(self, font: str, fontsize: float, dpi: float) -> float: """ Get the xheight for the given *font* and *fontsize*. """ raise NotImplementedError() - def get_underline_thickness(self, font, fontsize, dpi): + def get_underline_thickness(self, font: str, fontsize: float, dpi: float) -> float: """ Get the line thickness that matches the given font. Used as a base unit for drawing lines such as in a fraction or radical. """ raise NotImplementedError() - def get_sized_alternatives_for_symbol(self, fontname, sym): + def get_sized_alternatives_for_symbol(self, fontname: str, + sym: str) -> list[tuple[str, str]]: """ Override if your font provides multiple sizes of the same symbol. Should return a list of symbols matching *sym* in @@ -327,10 +332,10 @@ class TruetypeFonts(Fonts, metaclass=abc.ABCMeta): (through FT2Font). """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): + super().__init__(default_font_prop, load_glyph_flags) # Per-instance cache. - self._get_info = functools.cache(self._get_info) + self._get_info = functools.cache(self._get_info) # type: ignore[method-assign] self._fonts = {} self.fontmap: dict[str | int, str] = {} @@ -339,20 +344,23 @@ def __init__(self, *args, **kwargs): self._fonts['default'] = default_font self._fonts['regular'] = default_font - def _get_font(self, font): + def _get_font(self, font: str | int) -> FT2Font: if font in self.fontmap: basename = self.fontmap[font] else: - basename = font + # NOTE: An int is only passed by subclasses which have placed int keys into + # `self.fontmap`, so we must cast this to confirm it to typing. + basename = T.cast(str, font) cached_font = self._fonts.get(basename) if cached_font is None and os.path.exists(basename): cached_font = get_font(basename) self._fonts[basename] = cached_font self._fonts[cached_font.postscript_name] = cached_font self._fonts[cached_font.postscript_name.lower()] = cached_font - return cached_font + return T.cast(FT2Font, cached_font) # FIXME: Not sure this is guaranteed. - def _get_offset(self, font, glyph, fontsize, dpi): + def _get_offset(self, font: FT2Font, glyph: Glyph, fontsize: float, + dpi: float) -> float: if font.postscript_name == 'Cmex10': return (glyph.height / 64 / 2) + (fontsize/3 * dpi/72) return 0. @@ -362,7 +370,8 @@ def _get_glyph(self, fontname: str, font_class: str, raise NotImplementedError # The return value of _get_info is cached per-instance. - def _get_info(self, fontname, font_class, sym, fontsize, dpi): + def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float, + dpi: float) -> FontInfo: font, num, slanted = self._get_glyph(fontname, font_class, sym) font.set_size(fontsize, dpi) glyph = font.load_char(num, flags=self.load_glyph_flags) @@ -392,7 +401,7 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi): offset = offset ) - def get_xheight(self, fontname, fontsize, dpi): + def get_xheight(self, fontname: str, fontsize: float, dpi: float) -> float: font = self._get_font(fontname) font.set_size(fontsize, dpi) pclt = font.get_sfnt_table('pclt') @@ -404,14 +413,15 @@ def get_xheight(self, fontname, fontsize, dpi): xHeight = (pclt['xHeight'] / 64.0) * (fontsize / 12.0) * (dpi / 100.0) return xHeight - def get_underline_thickness(self, font, fontsize, dpi): + def get_underline_thickness(self, font: str, fontsize: float, dpi: float) -> float: # This function used to grab underline thickness from the font # metrics, but that information is just too un-reliable, so it # is now hardcoded. return ((0.75 / 12.0) * fontsize * dpi) / 72.0 - def get_kern(self, font1, fontclass1, sym1, fontsize1, - font2, fontclass2, sym2, fontsize2, dpi): + def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float, + font2: str, fontclass2: str, sym2: str, fontsize2: float, + dpi: float) -> float: if font1 == font2 and fontsize1 == fontsize2: info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi) info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi) @@ -438,10 +448,10 @@ class BakomaFonts(TruetypeFonts): 'ex': 'cmex10', } - def __init__(self, *args, **kwargs): - self._stix_fallback = StixFonts(*args, **kwargs) + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): + self._stix_fallback = StixFonts(default_font_prop, load_glyph_flags) - super().__init__(*args, **kwargs) + super().__init__(default_font_prop, load_glyph_flags) for key, val in self._fontmap.items(): fullpath = findfont(val) self.fontmap[key] = fullpath @@ -449,7 +459,8 @@ def __init__(self, *args, **kwargs): _slanted_symbols = set(r"\int \oint".split()) - def _get_glyph(self, fontname, font_class, sym): + def _get_glyph(self, fontname: str, font_class: str, + sym: str) -> tuple[FT2Font, int, bool]: font = None if fontname in self.fontmap and sym in latex_to_bakoma: basename, num = latex_to_bakoma[sym] @@ -521,7 +532,8 @@ def _get_glyph(self, fontname, font_class, sym): (r'\]', ']')]: _size_alternatives[alias] = _size_alternatives[target] - def get_sized_alternatives_for_symbol(self, fontname, sym): + def get_sized_alternatives_for_symbol(self, fontname: str, + sym: str) -> list[tuple[str, str]]: return self._size_alternatives.get(sym, [(fontname, sym)]) @@ -545,16 +557,18 @@ class UnicodeFonts(TruetypeFonts): 0x2212: 0x00A1, # Minus sign. } - def __init__(self, *args, **kwargs): + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): # This must come first so the backend's owner is set correctly fallback_rc = mpl.rcParams['mathtext.fallback'] - font_cls = {'stix': StixFonts, - 'stixsans': StixSansFonts, - 'cm': BakomaFonts - }.get(fallback_rc) - self._fallback_font = font_cls(*args, **kwargs) if font_cls else None - - super().__init__(*args, **kwargs) + font_cls: type[TruetypeFonts] | None = { + 'stix': StixFonts, + 'stixsans': StixSansFonts, + 'cm': BakomaFonts + }.get(fallback_rc) + self._fallback_font = (font_cls(default_font_prop, load_glyph_flags) + if font_cls else None) + + super().__init__(default_font_prop, load_glyph_flags) for texfont in "cal rm tt it bf sf bfit".split(): prop = mpl.rcParams['mathtext.' + texfont] font = findfont(prop) @@ -580,10 +594,12 @@ def __init__(self, *args, **kwargs): _slanted_symbols = set(r"\int \oint".split()) - def _map_virtual_font(self, fontname, font_class, uniindex): + def _map_virtual_font(self, fontname: str, font_class: str, + uniindex: int) -> tuple[str, int]: return fontname, uniindex - def _get_glyph(self, fontname, font_class, sym): + def _get_glyph(self, fontname: str, font_class: str, + sym: str) -> tuple[FT2Font, int, bool]: try: uniindex = get_unicode_index(sym) found_symbol = True @@ -645,7 +661,8 @@ def _get_glyph(self, fontname, font_class, sym): return font, uniindex, slanted - def get_sized_alternatives_for_symbol(self, fontname, sym): + def get_sized_alternatives_for_symbol(self, fontname: str, + sym: str) -> list[tuple[str, str]]: if self._fallback_font: return self._fallback_font.get_sized_alternatives_for_symbol( fontname, sym) @@ -655,14 +672,14 @@ def get_sized_alternatives_for_symbol(self, fontname, sym): class DejaVuFonts(UnicodeFonts, metaclass=abc.ABCMeta): _fontmap: dict[str | int, str] = {} - def __init__(self, *args, **kwargs): + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): # This must come first so the backend's owner is set correctly if isinstance(self, DejaVuSerifFonts): - self._fallback_font = StixFonts(*args, **kwargs) + self._fallback_font = StixFonts(default_font_prop, load_glyph_flags) else: - self._fallback_font = StixSansFonts(*args, **kwargs) - self.bakoma = BakomaFonts(*args, **kwargs) - TruetypeFonts.__init__(self, *args, **kwargs) + self._fallback_font = StixSansFonts(default_font_prop, load_glyph_flags) + self.bakoma = BakomaFonts(default_font_prop, load_glyph_flags) + TruetypeFonts.__init__(self, default_font_prop, load_glyph_flags) # Include Stix sized alternatives for glyphs self._fontmap.update({ 1: 'STIXSizeOneSym', @@ -676,7 +693,8 @@ def __init__(self, *args, **kwargs): self.fontmap[key] = fullpath self.fontmap[name] = fullpath - def _get_glyph(self, fontname, font_class, sym): + def _get_glyph(self, fontname: str, font_class: str, + sym: str) -> tuple[FT2Font, int, bool]: # Override prime symbol to use Bakoma. if sym == r'\prime': return self.bakoma._get_glyph(fontname, font_class, sym) @@ -740,7 +758,7 @@ class StixFonts(UnicodeFonts): - handles sized alternative characters for the STIXSizeX fonts. """ - _fontmap = { + _fontmap: dict[str | int, str] = { 'rm': 'STIXGeneral', 'it': 'STIXGeneral:italic', 'bf': 'STIXGeneral:weight=bold', @@ -755,17 +773,18 @@ class StixFonts(UnicodeFonts): 4: 'STIXSizeFourSym', 5: 'STIXSizeFiveSym', } - _fallback_font = False + _fallback_font = None _sans = False - def __init__(self, *args, **kwargs): - TruetypeFonts.__init__(self, *args, **kwargs) + def __init__(self, default_font_prop: FontProperties, load_glyph_flags: int): + TruetypeFonts.__init__(self, default_font_prop, load_glyph_flags) for key, name in self._fontmap.items(): fullpath = findfont(name) self.fontmap[key] = fullpath self.fontmap[name] = fullpath - def _map_virtual_font(self, fontname, font_class, uniindex): + def _map_virtual_font(self, fontname: str, font_class: str, + uniindex: int) -> tuple[str, int]: # Handle these "fonts" that are actually embedded in # other fonts. font_mapping = stix_virtual_fonts.get(fontname) @@ -819,7 +838,10 @@ def _map_virtual_font(self, fontname, font_class, uniindex): return fontname, uniindex @functools.cache - def get_sized_alternatives_for_symbol(self, fontname, sym): + def get_sized_alternatives_for_symbol( # type: ignore[override] + self, + fontname: str, + sym: str) -> list[tuple[str, str]] | list[tuple[int, str]]: fixes = { '\\{': '{', '\\}': '}', '\\[': '[', '\\]': ']', '<': '\N{MATHEMATICAL LEFT ANGLE BRACKET}', @@ -974,7 +996,7 @@ class DejaVuSansFontConstants(FontConstantsBase): } -def _get_font_constant_set(state): +def _get_font_constant_set(state: ParserState) -> type[FontConstantsBase]: constants = _font_constant_mapping.get( state.fontset._get_font(state.font).family_name, FontConstantsBase) # STIX sans isn't really its own fonts, just different code points @@ -988,57 +1010,58 @@ def _get_font_constant_set(state): class Node: """A node in the TeX box model.""" - def __init__(self): + def __init__(self) -> None: self.size = 0 - def __repr__(self): + def __repr__(self) -> str: return type(self).__name__ - def get_kerning(self, next): + def get_kerning(self, next: Node | None) -> float: return 0.0 - def shrink(self): + def shrink(self) -> None: """ Shrinks one level smaller. There are only three levels of sizes, after which things will no longer get smaller. """ self.size += 1 - def render(self, output, x, y): + def render(self, output: Output, x: float, y: float) -> None: """Render this node.""" class Box(Node): """A node with a physical location.""" - def __init__(self, width, height, depth): + def __init__(self, width: float, height: float, depth: float) -> None: super().__init__() self.width = width self.height = height self.depth = depth - def shrink(self): + def shrink(self) -> None: super().shrink() if self.size < NUM_SIZE_LEVELS: self.width *= SHRINK_FACTOR self.height *= SHRINK_FACTOR self.depth *= SHRINK_FACTOR - def render(self, output, x1, y1, x2, y2): + def render(self, output: Output, # type: ignore[override] + x1: float, y1: float, x2: float, y2: float) -> None: pass class Vbox(Box): """A box with only height (zero width).""" - def __init__(self, height, depth): + def __init__(self, height: float, depth: float): super().__init__(0., height, depth) class Hbox(Box): """A box with only width (zero height and depth).""" - def __init__(self, width): + def __init__(self, width: float): super().__init__(width, 0., 0.) @@ -1055,7 +1078,7 @@ class Char(Node): `Hlist`. """ - def __init__(self, c, state): + def __init__(self, c: str, state: ParserState): super().__init__() self.c = c self.fontset = state.fontset @@ -1067,10 +1090,10 @@ def __init__(self, c, state): # pack phase, after we know the real fontsize self._update_metrics() - def __repr__(self): + def __repr__(self) -> str: return '`%s`' % self.c - def _update_metrics(self): + def _update_metrics(self) -> None: metrics = self._metrics = self.fontset.get_metrics( self.font, self.font_class, self.c, self.fontsize, self.dpi) if self.c == ' ': @@ -1080,10 +1103,10 @@ def _update_metrics(self): self.height = metrics.iceberg self.depth = -(metrics.iceberg - metrics.height) - def is_slanted(self): + def is_slanted(self) -> bool: return self._metrics.slanted - def get_kerning(self, next): + def get_kerning(self, next: Node | None) -> float: """ Return the amount of kerning between this and the given character. @@ -1099,12 +1122,12 @@ def get_kerning(self, next): self.dpi) return advance + kern - def render(self, output, x, y): + def render(self, output: Output, x: float, y: float) -> None: self.fontset.render_glyph( output, x, y, self.font, self.font_class, self.c, self.fontsize, self.dpi) - def shrink(self): + def shrink(self) -> None: super().shrink() if self.size < NUM_SIZE_LEVELS: self.fontsize *= SHRINK_FACTOR @@ -1119,18 +1142,18 @@ class Accent(Char): since they are already offset correctly from the baseline in TrueType fonts. """ - def _update_metrics(self): + def _update_metrics(self) -> None: metrics = self._metrics = self.fontset.get_metrics( self.font, self.font_class, self.c, self.fontsize, self.dpi) self.width = metrics.xmax - metrics.xmin self.height = metrics.ymax - metrics.ymin self.depth = 0 - def shrink(self): + def shrink(self) -> None: super().shrink() self._update_metrics() - def render(self, output, x, y): + def render(self, output: Output, x: float, y: float) -> None: self.fontset.render_glyph( output, x - self._metrics.xmin, y + self._metrics.ymin, self.font, self.font_class, self.c, self.fontsize, self.dpi) @@ -1139,23 +1162,24 @@ def render(self, output, x, y): class List(Box): """A list of nodes (either horizontal or vertical).""" - def __init__(self, elements): + def __init__(self, elements: T.Sequence[Node]): super().__init__(0., 0., 0.) self.shift_amount = 0. # An arbitrary offset - self.children = elements # The child nodes of this list + self.children = [*elements] # The child nodes of this list # The following parameters are set in the vpack and hpack functions self.glue_set = 0. # The glue setting of this list self.glue_sign = 0 # 0: normal, -1: shrinking, 1: stretching self.glue_order = 0 # The order of infinity (0 - 3) for the glue - def __repr__(self): + def __repr__(self) -> str: return '{}[{}]'.format( super().__repr__(), self.width, self.height, self.depth, self.shift_amount, ', '.join([repr(x) for x in self.children])) - def _set_glue(self, x, sign, totals, error_type): + def _set_glue(self, x: float, sign: int, totals: list[float], + error_type: str) -> None: self.glue_order = o = next( # Highest order of glue used by the members of this list. (i for i in range(len(totals))[::-1] if totals[i] != 0), 0) @@ -1170,7 +1194,7 @@ def _set_glue(self, x, sign, totals, error_type): _log.warning("%s %s: %r", error_type, type(self).__name__, self) - def shrink(self): + def shrink(self) -> None: for child in self.children: child.shrink() super().shrink() @@ -1182,13 +1206,15 @@ def shrink(self): class Hlist(List): """A horizontal list of boxes.""" - def __init__(self, elements, w=0., m='additional', do_kern=True): + def __init__(self, elements: T.Sequence[Node], w: float = 0.0, + m: T.Literal['additional', 'exactly'] = 'additional', + do_kern: bool = True): super().__init__(elements) if do_kern: self.kern() self.hpack(w=w, m=m) - def kern(self): + def kern(self) -> None: """ Insert `Kern` nodes between `Char` nodes to set kerning. @@ -1213,21 +1239,8 @@ def kern(self): new_children.append(kern) self.children = new_children - # This is a failed experiment to fake cross-font kerning. -# def get_kerning(self, next): -# if len(self.children) >= 2 and isinstance(self.children[-2], Char): -# if isinstance(next, Char): -# print "CASE A" -# return self.children[-2].get_kerning(next) -# elif (isinstance(next, Hlist) and len(next.children) -# and isinstance(next.children[0], Char)): -# print "CASE B" -# result = self.children[-2].get_kerning(next.children[0]) -# print result -# return result -# return 0.0 - - def hpack(self, w=0., m='additional'): + def hpack(self, w: float = 0.0, + m: T.Literal['additional', 'exactly'] = 'additional') -> None: r""" Compute the dimensions of the resulting boxes, and adjust the glue if one of those dimensions is pre-specified. The computed sizes normally @@ -1295,11 +1308,14 @@ def hpack(self, w=0., m='additional'): class Vlist(List): """A vertical list of boxes.""" - def __init__(self, elements, h=0., m='additional'): + def __init__(self, elements: T.Sequence[Node], h: float = 0.0, + m: T.Literal['additional', 'exactly'] = 'additional'): super().__init__(elements) self.vpack(h=h, m=m) - def vpack(self, h=0., m='additional', l=np.inf): + def vpack(self, h: float = 0.0, + m: T.Literal['additional', 'exactly'] = 'additional', + l: float = np.inf) -> None: """ Compute the dimensions of the resulting boxes, and to adjust the glue if one of those dimensions is pre-specified. @@ -1382,18 +1398,19 @@ class Rule(Box): running in an `Hlist`; the height and depth are never running in a `Vlist`. """ - def __init__(self, width, height, depth, state): + def __init__(self, width: float, height: float, depth: float, state: ParserState): super().__init__(width, height, depth) self.fontset = state.fontset - def render(self, output, x, y, w, h): + def render(self, output: Output, # type: ignore[override] + x: float, y: float, w: float, h: float) -> None: self.fontset.render_rect_filled(output, x, y, x + w, y + h) class Hrule(Rule): """Convenience class to create a horizontal rule.""" - def __init__(self, state, thickness=None): + def __init__(self, state: ParserState, thickness: float | None = None): if thickness is None: thickness = state.get_current_underline_thickness() height = depth = thickness * 0.5 @@ -1403,7 +1420,7 @@ def __init__(self, state, thickness=None): class Vrule(Rule): """Convenience class to create a vertical rule.""" - def __init__(self, state): + def __init__(self, state: ParserState): thickness = state.get_current_underline_thickness() super().__init__(thickness, np.inf, np.inf, state) @@ -1436,7 +1453,10 @@ class Glue(Node): it's easier to stick to what TeX does.) """ - def __init__(self, glue_type): + def __init__(self, + glue_type: _GlueSpec | T.Literal["fil", "fill", "filll", + "neg_fil", "neg_fill", "neg_filll", + "empty", "ss"]): super().__init__() if isinstance(glue_type, str): glue_spec = _GlueSpec._named[glue_type] # type: ignore[attr-defined] @@ -1446,7 +1466,7 @@ def __init__(self, glue_type): raise ValueError("glue_type must be a glue spec name or instance") self.glue_spec = glue_spec - def shrink(self): + def shrink(self) -> None: super().shrink() if self.size < NUM_SIZE_LEVELS: g = self.glue_spec @@ -1459,7 +1479,7 @@ class HCentered(Hlist): centered within its enclosing box. """ - def __init__(self, elements): + def __init__(self, elements: list[Node]): super().__init__([Glue('ss'), *elements, Glue('ss')], do_kern=False) @@ -1469,7 +1489,7 @@ class VCentered(Vlist): centered within its enclosing box. """ - def __init__(self, elements): + def __init__(self, elements: list[Node]): super().__init__([Glue('ss'), *elements, Glue('ss')]) @@ -1487,14 +1507,14 @@ class Kern(Node): height = 0 depth = 0 - def __init__(self, width): + def __init__(self, width: float): super().__init__() self.width = width - def __repr__(self): + def __repr__(self) -> str: return "k%.02f" % self.width - def shrink(self): + def shrink(self) -> None: super().shrink() if self.size < NUM_SIZE_LEVELS: self.width *= SHRINK_FACTOR @@ -1509,7 +1529,8 @@ class AutoHeightChar(Hlist): always just return a scaled version of the glyph. """ - def __init__(self, c, height, depth, state, always=False, factor=None): + def __init__(self, c: str, height: float, depth: float, state: ParserState, + always: bool = False, factor: float | None = None): alternatives = state.fontset.get_sized_alternatives_for_symbol( state.font, c) @@ -1526,7 +1547,7 @@ def __init__(self, c, height, depth, state, always=False, factor=None): if char.height + char.depth >= target_total - 0.2 * xHeight: break - shift = 0 + shift = 0.0 if state.font != 0 or len(alternatives) == 1: if factor is None: factor = target_total / (char.height + char.depth) @@ -1548,7 +1569,8 @@ class AutoWidthChar(Hlist): always just return a scaled version of the glyph. """ - def __init__(self, c, width, state, always=False, char_class=Char): + def __init__(self, c: str, width: float, state: ParserState, always: bool = False, + char_class: type[Char] = Char): alternatives = state.fontset.get_sized_alternatives_for_symbol( state.font, c) @@ -1567,7 +1589,7 @@ def __init__(self, c, width, state, always=False, char_class=Char): self.width = char.width -def ship(box, xy=(0, 0)): +def ship(box: Box, xy: tuple[float, float] = (0, 0)) -> Output: """ Ship out *box* at offset *xy*, converting it to an `Output`. @@ -1584,10 +1606,10 @@ def ship(box, xy=(0, 0)): off_v = oy + box.height output = Output(box) - def clamp(value): + def clamp(value: float) -> float: return -1e9 if value < -1e9 else +1e9 if value > +1e9 else value - def hlist_out(box): + def hlist_out(box: Hlist) -> None: nonlocal cur_v, cur_h, off_h, off_v cur_g = 0 @@ -1612,9 +1634,11 @@ def hlist_out(box): cur_v = base_line + p.shift_amount if isinstance(p, Hlist): hlist_out(p) - else: + elif isinstance(p, Vlist): # p.vpack(box.height + box.depth, 'exactly') vlist_out(p) + else: + assert False, "unreachable code" cur_h = edge + p.width cur_v = base_line elif isinstance(p, Box): @@ -1648,7 +1672,7 @@ def hlist_out(box): rule_width += cur_g cur_h += rule_width - def vlist_out(box): + def vlist_out(box: Vlist) -> None: nonlocal cur_v, cur_h, off_h, off_v cur_g = 0 @@ -1672,8 +1696,10 @@ def vlist_out(box): p.width = box.width if isinstance(p, Hlist): hlist_out(p) - else: + elif isinstance(p, Vlist): vlist_out(p) + else: + assert False, "unreachable code" cur_v = save_v + p.depth cur_h = left_edge elif isinstance(p, Box): @@ -1705,6 +1731,7 @@ def vlist_out(box): raise RuntimeError( "Internal mathtext error: Char node found in vlist") + assert isinstance(box, Hlist) hlist_out(box) return output @@ -1713,9 +1740,9 @@ def vlist_out(box): # PARSER -def Error(msg): +def Error(msg: str) -> ParserElement: """Helper class to raise parser errors.""" - def raise_error(s, loc, toks): + def raise_error(s: str, loc: int, toks: ParseResults) -> T.Any: raise ParseFatalException(s, loc, msg) return Empty().setParseAction(raise_error) @@ -1732,33 +1759,34 @@ class ParserState: and popped accordingly. """ - def __init__(self, fontset, font, font_class, fontsize, dpi): + def __init__(self, fontset: Fonts, font: str, font_class: str, fontsize: float, + dpi: float): self.fontset = fontset self._font = font self.font_class = font_class self.fontsize = fontsize self.dpi = dpi - def copy(self): + def copy(self) -> ParserState: return copy.copy(self) @property - def font(self): + def font(self) -> str: return self._font @font.setter - def font(self, name): + def font(self, name: str) -> None: if name in ('rm', 'it', 'bf', 'bfit'): self.font_class = name self._font = name - def get_current_underline_thickness(self): + def get_current_underline_thickness(self) -> float: """Return the underline thickness for this state.""" return self.fontset.get_underline_thickness( self.font, self.fontsize, self.dpi) -def cmd(expr, args): +def cmd(expr: str, args: ParserElement) -> ParserElement: r""" Helper to define TeX commands. @@ -1770,7 +1798,7 @@ def cmd(expr, args): the error message. """ - def names(elt): + def names(elt: ParserElement) -> T.Generator[str, None, None]: if isinstance(elt, ParseExpression): for expr in elt.exprs: yield from names(expr) @@ -1943,10 +1971,10 @@ class _MathStyle(enum.Enum): ord('\N{GREEK SMALL LETTER OMEGA}') + 1)]) _latin_alphabets = set(string.ascii_letters) - def __init__(self): + def __init__(self) -> None: p = types.SimpleNamespace() - def set_names_and_parse_actions(): + def set_names_and_parse_actions() -> None: for key, val in vars(p).items(): if not key.startswith('_'): # Set names on (almost) everything -- very useful for debugging @@ -1961,7 +1989,7 @@ def set_names_and_parse_actions(): # Root definitions. # In TeX parlance, a csname is a control sequence name (a "\foo"). - def csnames(group, names): + def csnames(group: str, names: Iterable[str]) -> Regex: ends_with_alpha = [] ends_with_nonalpha = [] for name in names: @@ -2120,7 +2148,7 @@ def csnames(group, names): # To add space to nucleus operators after sub/superscripts self._in_subscript_or_superscript = False - def parse(self, s, fonts_object, fontsize, dpi): + def parse(self, s: str, fonts_object: Fonts, fontsize: float, dpi: float) -> Hlist: """ Parse expression *s* using the given *fonts_object* for output, at the given *fontsize* and *dpi*. @@ -2129,43 +2157,43 @@ def parse(self, s, fonts_object, fontsize, dpi): """ self._state_stack = [ ParserState(fonts_object, 'default', 'rm', fontsize, dpi)] - self._em_width_cache = {} + self._em_width_cache: dict[tuple[str, float, float], float] = {} try: result = self._expression.parseString(s) except ParseBaseException as err: # explain becomes a plain method on pyparsing 3 (err.explain(0)). raise ValueError("\n" + ParseException.explain(err, 0)) from None - self._state_stack = None + self._state_stack = [] self._in_subscript_or_superscript = False # prevent operator spacing from leaking into a new expression self._em_width_cache = {} ParserElement.resetCache() - return result[0] + return T.cast(Hlist, result[0]) # Known return type from main. - def get_state(self): + def get_state(self) -> ParserState: """Get the current `State` of the parser.""" return self._state_stack[-1] - def pop_state(self): + def pop_state(self) -> None: """Pop a `State` off of the stack.""" self._state_stack.pop() - def push_state(self): + def push_state(self) -> None: """Push a new `State` onto the stack, copying the current state.""" self._state_stack.append(self.get_state().copy()) - def main(self, toks): - return [Hlist(toks)] + def main(self, toks: ParseResults) -> list[Hlist]: + return [Hlist(toks.asList())] - def math_string(self, toks): + def math_string(self, toks: ParseResults) -> ParseResults: return self._math_expression.parseString(toks[0][1:-1], parseAll=True) - def math(self, toks): - hlist = Hlist(toks) + def math(self, toks: ParseResults) -> T.Any: + hlist = Hlist(toks.asList()) self.pop_state() return [hlist] - def non_math(self, toks): + def non_math(self, toks: ParseResults) -> T.Any: s = toks[0].replace(r'\$', '$') symbols = [Char(c, self.get_state()) for c in s] hlist = Hlist(symbols) @@ -2176,7 +2204,7 @@ def non_math(self, toks): float_literal = staticmethod(pyparsing_common.convertToFloat) - def text(self, toks): + def text(self, toks: ParseResults) -> T.Any: self.push_state() state = self.get_state() state.font = 'rm' @@ -2184,7 +2212,7 @@ def text(self, toks): self.pop_state() return [hlist] - def _make_space(self, percentage): + def _make_space(self, percentage: float) -> Kern: # In TeX, an em (the unit usually used to measure horizontal lengths) # is not the width of the character 'm'; it is the same in different # font styles (e.g. roman or italic). Mathtext, however, uses 'm' in @@ -2216,15 +2244,16 @@ def _make_space(self, percentage): r'\!': -0.16667, # -3/18 em = -3 mu } - def space(self, toks): + def space(self, toks: ParseResults) -> T.Any: num = self._space_widths[toks["space"]] box = self._make_space(num) return [box] - def customspace(self, toks): + def customspace(self, toks: ParseResults) -> T.Any: return [self._make_space(toks["space"])] - def symbol(self, s, loc, toks): + def symbol(self, s: str, loc: int, + toks: ParseResults | dict[str, str]) -> T.Any: c = toks["sym"] if c == "-": # "U+2212 minus sign is the preferred representation of the unary @@ -2271,7 +2300,7 @@ def symbol(self, s, loc, toks): return [Hlist([char, self._make_space(0.2)], do_kern=True)] return [char] - def unknown_symbol(self, s, loc, toks): + def unknown_symbol(self, s: str, loc: int, toks: ParseResults) -> T.Any: raise ParseFatalException(s, loc, f"Unknown symbol: {toks['name']}") _accent_map = { @@ -2299,11 +2328,12 @@ def unknown_symbol(self, s, loc, toks): _wide_accents = set(r"widehat widetilde widebar".split()) - def accent(self, toks): + def accent(self, toks: ParseResults) -> T.Any: state = self.get_state() thickness = state.get_current_underline_thickness() accent = toks["accent"] sym = toks["sym"] + accent_box: Node if accent in self._wide_accents: accent_box = AutoWidthChar( '\\' + accent, sym.width, state, char_class=Accent) @@ -2320,16 +2350,16 @@ def accent(self, toks): Hlist([sym]) ]) - def function(self, s, loc, toks): + def function(self, s: str, loc: int, toks: ParseResults) -> T.Any: hlist = self.operatorname(s, loc, toks) hlist.function_name = toks["name"] return hlist - def operatorname(self, s, loc, toks): + def operatorname(self, s: str, loc: int, toks: ParseResults) -> T.Any: self.push_state() state = self.get_state() state.font = 'rm' - hlist_list = [] + hlist_list: list[Node] = [] # Change the font of Chars, but leave Kerns alone name = toks["name"] for c in name: @@ -2360,51 +2390,51 @@ def operatorname(self, s, loc, toks): return Hlist(hlist_list) - def start_group(self, toks): + def start_group(self, toks: ParseResults) -> T.Any: self.push_state() # Deal with LaTeX-style font tokens if toks.get("font"): self.get_state().font = toks.get("font") return [] - def group(self, toks): + def group(self, toks: ParseResults) -> T.Any: grp = Hlist(toks.get("group", [])) return [grp] - def required_group(self, toks): + def required_group(self, toks: ParseResults) -> T.Any: return Hlist(toks.get("group", [])) optional_group = required_group - def end_group(self): + def end_group(self) -> T.Any: self.pop_state() return [] - def unclosed_group(self, s, loc, toks): + def unclosed_group(self, s: str, loc: int, toks: ParseResults) -> T.Any: raise ParseFatalException(s, len(s), "Expected '}'") - def font(self, toks): + def font(self, toks: ParseResults) -> T.Any: self.get_state().font = toks["font"] return [] - def is_overunder(self, nucleus): + def is_overunder(self, nucleus: Node) -> bool: if isinstance(nucleus, Char): return nucleus.c in self._overunder_symbols elif isinstance(nucleus, Hlist) and hasattr(nucleus, 'function_name'): return nucleus.function_name in self._overunder_functions return False - def is_dropsub(self, nucleus): + def is_dropsub(self, nucleus: Node) -> bool: if isinstance(nucleus, Char): return nucleus.c in self._dropsub_symbols return False - def is_slanted(self, nucleus): + def is_slanted(self, nucleus: Node) -> bool: if isinstance(nucleus, Char): return nucleus.is_slanted() return False - def subsuper(self, s, loc, toks): + def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any: nucleus = toks.get("nucleus", Hbox(0)) subsuper = toks.get("subsuper", []) napostrophes = len(toks.get("apostrophes", [])) @@ -2517,9 +2547,13 @@ def subsuper(self, s, loc, toks): else: subkern = 0 + x: List if super is None: # node757 - x = Hlist([Kern(subkern), sub]) + # Note: One of super or sub must be a Node if we're in this function, but + # mypy can't know this, since it can't interpret pyparsing expressions, + # hence the cast. + x = Hlist([Kern(subkern), T.cast(Node, sub)]) x.shrink() if self.is_dropsub(last_char): shift_down = lc_baseline + constants.subdrop * xHeight @@ -2566,7 +2600,8 @@ def subsuper(self, s, loc, toks): result = Hlist(spaced_nucleus) return [result] - def _genfrac(self, ldelim, rdelim, rule, style, num, den): + def _genfrac(self, ldelim: str, rdelim: str, rule: float | None, style: _MathStyle, + num: Hlist, den: Hlist) -> T.Any: state = self.get_state() thickness = state.get_current_underline_thickness() @@ -2601,34 +2636,37 @@ def _genfrac(self, ldelim, rdelim, rule, style, num, den): ldelim = '.' if rdelim == '': rdelim = '.' - return self._auto_sized_delimiter(ldelim, result, rdelim) + return self._auto_sized_delimiter(ldelim, + T.cast(list[T.Union[Box, Char, str]], + result), + rdelim) return result - def style_literal(self, toks): + def style_literal(self, toks: ParseResults) -> T.Any: return self._MathStyle(int(toks["style_literal"])) - def genfrac(self, toks): + def genfrac(self, toks: ParseResults) -> T.Any: return self._genfrac( toks.get("ldelim", ""), toks.get("rdelim", ""), toks["rulesize"], toks.get("style", self._MathStyle.TEXTSTYLE), toks["num"], toks["den"]) - def frac(self, toks): + def frac(self, toks: ParseResults) -> T.Any: return self._genfrac( "", "", self.get_state().get_current_underline_thickness(), self._MathStyle.TEXTSTYLE, toks["num"], toks["den"]) - def dfrac(self, toks): + def dfrac(self, toks: ParseResults) -> T.Any: return self._genfrac( "", "", self.get_state().get_current_underline_thickness(), self._MathStyle.DISPLAYSTYLE, toks["num"], toks["den"]) - def binom(self, toks): + def binom(self, toks: ParseResults) -> T.Any: return self._genfrac( "(", ")", 0, self._MathStyle.TEXTSTYLE, toks["num"], toks["den"]) - def _genset(self, s, loc, toks): + def _genset(self, s: str, loc: int, toks: ParseResults) -> T.Any: annotation = toks["annotation"] body = toks["body"] thickness = self.get_state().get_current_underline_thickness() @@ -2660,7 +2698,7 @@ def _genset(self, s, loc, toks): overset = underset = _genset - def sqrt(self, toks): + def sqrt(self, toks: ParseResults) -> T.Any: root = toks.get("root") body = toks["value"] state = self.get_state() @@ -2700,7 +2738,7 @@ def sqrt(self, toks): rightside]) # Body return [hlist] - def overline(self, toks): + def overline(self, toks: ParseResults) -> T.Any: body = toks["body"] state = self.get_state() @@ -2719,7 +2757,9 @@ def overline(self, toks): hlist = Hlist([rightside]) return [hlist] - def _auto_sized_delimiter(self, front, middle, back): + def _auto_sized_delimiter(self, front: str, + middle: list[Box | Char | str], + back: str) -> T.Any: state = self.get_state() if len(middle): height = max([x.height for x in middle if not isinstance(x, str)]) @@ -2727,41 +2767,45 @@ def _auto_sized_delimiter(self, front, middle, back): factor = None for idx, el in enumerate(middle): if isinstance(el, str) and el == '\\middle': - c = middle[idx + 1] + c = T.cast(str, middle[idx + 1]) # Should be one of p.delims. if c != '.': middle[idx + 1] = AutoHeightChar( c, height, depth, state, factor=factor) else: middle.remove(c) del middle[idx] + # There should only be \middle and its delimiter as str, which have + # just been removed. + middle_part = T.cast(list[T.Union[Box, Char]], middle) else: height = 0 depth = 0 factor = 1.0 + middle_part = [] - parts = [] + parts: list[Node] = [] # \left. and \right. aren't supposed to produce any symbols if front != '.': parts.append( AutoHeightChar(front, height, depth, state, factor=factor)) - parts.extend(middle) + parts.extend(middle_part) if back != '.': parts.append( AutoHeightChar(back, height, depth, state, factor=factor)) hlist = Hlist(parts) return hlist - def auto_delim(self, toks): + def auto_delim(self, toks: ParseResults) -> T.Any: return self._auto_sized_delimiter( toks["left"], # if "mid" in toks ... can be removed when requiring pyparsing 3. toks["mid"].asList() if "mid" in toks else [], toks["right"]) - def boldsymbol(self, toks): + def boldsymbol(self, toks: ParseResults) -> T.Any: self.push_state() state = self.get_state() - hlist = [] + hlist: list[Node] = [] name = toks["value"] for c in name: if isinstance(c, Hlist): @@ -2784,7 +2828,7 @@ def boldsymbol(self, toks): return Hlist(hlist) - def substack(self, toks): + def substack(self, toks: ParseResults) -> T.Any: parts = toks["parts"] state = self.get_state() thickness = state.get_current_underline_thickness() diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index d37cdff22e57..8f413b2a1673 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -2,6 +2,9 @@ font data tables for truetype and afm computer modern fonts """ +from __future__ import annotations + + latex_to_bakoma = { '\\__sqrt__' : ('cmex10', 0x70), '\\bigcap' : ('cmex10', 0x5c), @@ -1099,7 +1102,8 @@ # Each element is a 4-tuple of the form: # src_start, src_end, dst_font, dst_start # -stix_virtual_fonts = { +stix_virtual_fonts: dict[str, dict[str, list[tuple[int, int, str, int]]] | + list[tuple[int, int, str, int]]] = { 'bb': { 'rm': From 2293d6595798ee6448790d70b02a93abdc4bed99 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Aug 2023 07:08:04 -0400 Subject: [PATCH 0090/1120] TYP: Add overloads for MathTextParser return types --- lib/matplotlib/backends/backend_agg.py | 2 +- lib/matplotlib/mathtext.pyi | 15 ++++++++++----- lib/matplotlib/textpath.pyi | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 9008096253c6..470ce9d925ad 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -71,7 +71,7 @@ def __init__(self, width, height, dpi): self._filter_renderers = [] self._update_methods() - self.mathtext_parser = MathTextParser('Agg') + self.mathtext_parser = MathTextParser('agg') self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) diff --git a/lib/matplotlib/mathtext.pyi b/lib/matplotlib/mathtext.pyi index 706f83a6b168..607501a275c6 100644 --- a/lib/matplotlib/mathtext.pyi +++ b/lib/matplotlib/mathtext.pyi @@ -1,5 +1,8 @@ import os +from typing import Generic, IO, Literal, TypeVar, overload + from matplotlib.font_manager import FontProperties +from matplotlib.typing import ColorType # Re-exported API from _mathtext. from ._mathtext import ( @@ -8,14 +11,16 @@ from ._mathtext import ( get_unicode_index as get_unicode_index, ) -from typing import IO, Literal -from matplotlib.typing import ColorType +_ParseType = TypeVar("_ParseType", RasterParse, VectorParse) -class MathTextParser: - def __init__(self, output: Literal["path", "agg", "raster", "macosx"]) -> None: ... +class MathTextParser(Generic[_ParseType]): + @overload + def __init__(self: MathTextParser[VectorParse], output: Literal["path"]) -> None: ... + @overload + def __init__(self: MathTextParser[RasterParse], output: Literal["agg", "raster", "macosx"]) -> None: ... def parse( self, s: str, dpi: float = ..., prop: FontProperties | None = ..., *, antialiased: bool | None = ... - ) -> RasterParse | VectorParse: ... + ) -> _ParseType: ... def math_to_image( s: str, diff --git a/lib/matplotlib/textpath.pyi b/lib/matplotlib/textpath.pyi index 6e49a3e8092d..34d4e92ac47e 100644 --- a/lib/matplotlib/textpath.pyi +++ b/lib/matplotlib/textpath.pyi @@ -1,6 +1,6 @@ from matplotlib.font_manager import FontProperties from matplotlib.ft2font import FT2Font -from matplotlib.mathtext import MathTextParser +from matplotlib.mathtext import MathTextParser, VectorParse from matplotlib.path import Path import numpy as np @@ -10,7 +10,7 @@ from typing import Literal class TextToPath: FONT_SCALE: float DPI: float - mathtext_parser: MathTextParser + mathtext_parser: MathTextParser[VectorParse] def __init__(self) -> None: ... def get_text_width_height_descent( self, s: str, prop: FontProperties, ismath: bool | Literal["TeX"] From 7e5cc665d9d8e67732fbd41cc654a614f2fb45bd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 16:51:01 -0400 Subject: [PATCH 0091/1120] TYP: Fix types of errorbar *lims arguments --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/axes/_axes.pyi | 8 ++++---- lib/matplotlib/pyplot.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4d5907503f09..db0ffe0aaabd 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3436,7 +3436,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, If True, will plot the errorbars above the plot symbols. Default is below. - lolims, uplims, xlolims, xuplims : bool, default: False + lolims, uplims, xlolims, xuplims : bool or array-like, default: False These arguments can be used to indicate that a value gives only upper/lower limits. In that case a caret symbol is used to indicate this. *lims*-arguments may be scalars, or array-likes of diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 30c0622b89f5..6eeec5cae7fc 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -332,10 +332,10 @@ class Axes(_AxesBase): elinewidth: float | None = ..., capsize: float | None = ..., barsabove: bool = ..., - lolims: bool = ..., - uplims: bool = ..., - xlolims: bool = ..., - xuplims: bool = ..., + lolims: bool | ArrayLike = ..., + uplims: bool | ArrayLike = ..., + xlolims: bool | ArrayLike = ..., + xuplims: bool | ArrayLike = ..., errorevery: int | tuple[int, int] = ..., capthick: float | None = ..., *, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 04fdcbc744c9..95ddd9bd7045 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2961,10 +2961,10 @@ def errorbar( elinewidth: float | None = None, capsize: float | None = None, barsabove: bool = False, - lolims: bool = False, - uplims: bool = False, - xlolims: bool = False, - xuplims: bool = False, + lolims: bool | ArrayLike = False, + uplims: bool | ArrayLike = False, + xlolims: bool | ArrayLike = False, + xuplims: bool | ArrayLike = False, errorevery: int | tuple[int, int] = 1, capthick: float | None = None, *, From fa2304c681bc3e595f6040e9b66a0238c520908c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 20:00:53 -0400 Subject: [PATCH 0092/1120] TYP: Fix return types of Gcf getters Some return None when there is no figure available. --- lib/matplotlib/_pylab_helpers.pyi | 6 ++++-- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.pyi b/lib/matplotlib/_pylab_helpers.pyi index 6e1c8889833f..bdd8cfba3173 100644 --- a/lib/matplotlib/_pylab_helpers.pyi +++ b/lib/matplotlib/_pylab_helpers.pyi @@ -6,7 +6,7 @@ from matplotlib.figure import Figure class Gcf: figs: OrderedDict[int, FigureManagerBase] @classmethod - def get_fig_manager(cls, num: int) -> FigureManagerBase: ... + def get_fig_manager(cls, num: int) -> FigureManagerBase | None: ... @classmethod def destroy(cls, num: int | FigureManagerBase) -> None: ... @classmethod @@ -20,7 +20,9 @@ class Gcf: @classmethod def get_num_fig_managers(cls) -> int: ... @classmethod - def get_active(cls) -> FigureManagerBase: ... + def get_active(cls) -> FigureManagerBase | None: ... + @classmethod + def _set_new_active_manager(cls, manager: FigureManagerBase) -> None: ... @classmethod def set_active(cls, manager: FigureManagerBase) -> None: ... @classmethod diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 95ddd9bd7045..976e997a472b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -938,7 +938,7 @@ def figure( for hookspecs in rcParams["figure.hooks"]: module_name, dotted_name = hookspecs.split(":") - obj = importlib.import_module(module_name) + obj: Any = importlib.import_module(module_name) for part in dotted_name.split("."): obj = getattr(obj, part) obj(fig) From 6e86bdfa2d3b787c5436db312ac23c67bbeceb69 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 20:58:18 -0400 Subject: [PATCH 0093/1120] TYP: Accept tuples in Legend handles list Fixes #26639 --- lib/matplotlib/axes/_axes.py | 5 ++++- lib/matplotlib/axes/_axes.pyi | 4 ++-- lib/matplotlib/legend.py | 4 ++-- lib/matplotlib/legend.pyi | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index db0ffe0aaabd..c1ad5956e2cb 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -280,7 +280,7 @@ def legend(self, *args, **kwargs): Parameters ---------- - handles : sequence of `.Artist`, optional + handles : sequence of (`.Artist` or tuple of `.Artist`), optional A list of Artists (lines, patches) to be added to the legend. Use this together with *labels*, if you need full control on what is shown in the legend and the automatic mechanism described above @@ -289,6 +289,9 @@ def legend(self, *args, **kwargs): The length of handles and labels should be the same in this case. If they are not, they are truncated to the smaller length. + If an entry contains a tuple, then the legend handler for all Artists in the + tuple will be placed alongside a single label. + labels : list of str, optional A list of labels to show next to the artists. Use this together with *handles*, if you need full control on what diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 6eeec5cae7fc..96ea087f7eb9 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -57,9 +57,9 @@ class Axes(_AxesBase): @overload def legend(self) -> Legend: ... @overload - def legend(self, handles: Sequence[Artist], labels: Sequence[str], **kwargs) -> Legend: ... + def legend(self, handles: Sequence[Artist | tuple[Artist, ...]], labels: Sequence[str], **kwargs) -> Legend: ... @overload - def legend(self, *, handles: Sequence[Artist], **kwargs) -> Legend: ... + def legend(self, *, handles: Sequence[Artist | tuple[Artist, ...]], **kwargs) -> Legend: ... @overload def legend(self, labels: Sequence[str], **kwargs) -> Legend: ... @overload diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 18d0ee913237..bdabacdfedbf 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -401,7 +401,7 @@ def __init__( parent : `~matplotlib.axes.Axes` or `.Figure` The artist that contains the legend. - handles : list of `.Artist` + handles : list of (`.Artist` or tuple of `.Artist`) A list of Artists (lines, patches) to be added to the legend. labels : list of str @@ -1322,7 +1322,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): Returns ------- - handles : list of `.Artist` + handles : list of (`.Artist` or tuple of `.Artist`) The legend handles. labels : list of str The legend labels. diff --git a/lib/matplotlib/legend.pyi b/lib/matplotlib/legend.pyi index 077aaf672d39..d559b06c5d5d 100644 --- a/lib/matplotlib/legend.pyi +++ b/lib/matplotlib/legend.pyi @@ -52,7 +52,7 @@ class Legend(Artist): def __init__( self, parent: Axes | Figure, - handles: Iterable[Artist], + handles: Iterable[Artist | tuple[Artist, ...]], labels: Iterable[str], *, loc: str | tuple[float, float] | int | None = ..., From af6fbefcfdaf04cb28c0626b4a3e620f50c7216f Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 8 Aug 2023 05:42:50 -0400 Subject: [PATCH 0094/1120] restructured explanation and turned notes in special characters section Co-authored-by: Elliott Sales de Andrade Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- galleries/users_explain/text/mathtext.py | 719 +++++++++++------------ 1 file changed, 358 insertions(+), 361 deletions(-) diff --git a/galleries/users_explain/text/mathtext.py b/galleries/users_explain/text/mathtext.py index 0b786e3e7ed0..09c049d4a833 100644 --- a/galleries/users_explain/text/mathtext.py +++ b/galleries/users_explain/text/mathtext.py @@ -7,365 +7,362 @@ Writing mathematical expressions ================================ -You can use a subset of TeX markup in any Matplotlib text string by placing it -inside a pair of dollar signs ($). - -Note that you do not need to have TeX installed, since Matplotlib ships -its own TeX expression parser, layout engine, and fonts. The layout engine -is a fairly direct adaptation of the layout algorithms in Donald Knuth's -TeX, so the quality is quite good (Matplotlib also provides a ``usetex`` -option for those who do want to call out to TeX to generate their text; see -:ref:`usetex`). - -Any text element can use math text. You should use raw strings (precede the -quotes with an ``'r'``), and surround the math text with dollar signs ($), as -in TeX. Regular text and mathtext can be interleaved within the same string. -Mathtext can use DejaVu Sans (default), DejaVu Serif, the Computer Modern fonts -(from (La)TeX), `STIX `_ fonts (which are designed -to blend well with Times), or a Unicode font that you provide. The mathtext -font can be selected via :rc:`mathtext.fontset` (see -:ref:`customizing`) - -Here is a simple example:: - - # plain text - plt.title('alpha > beta') - -produces "alpha > beta". - -Whereas this:: - - # math text - plt.title(r'$\alpha > \beta$') - -produces ":mathmpl:`\alpha > \beta`". - -.. note:: - Mathtext should be placed between a pair of dollar signs ($). To make it - easy to display monetary values, e.g., "$100.00", if a single dollar sign - is present in the entire string, it will be displayed verbatim as a dollar - sign. This is a small change from regular TeX, where the dollar sign in - non-math text would have to be escaped ('\\\$'). - -.. note:: - While the syntax inside the pair of dollar signs ($) aims to be TeX-like, - the text outside does not. In particular, characters such as:: - - # $ % & ~ _ ^ \ { } \( \) \[ \] - - have special meaning outside of math mode in TeX. Therefore, these - characters will behave differently depending on :rc:`text.usetex`. See the - :ref:`usetex tutorial ` for more information. - -.. note:: - To generate html output in documentation that will exactly match the output - generated by ``mathtext``, use the `matplotlib.sphinxext.mathmpl` Sphinx - extension. - -Subscripts and superscripts ---------------------------- -To make subscripts and superscripts, use the ``'_'`` and ``'^'`` symbols:: - - r'$\alpha_i > \beta_i$' - -.. math:: - - \alpha_i > \beta_i - -To display multi-letter subscripts or superscripts correctly, -you should put them in curly braces ``{...}``:: - - r'$\alpha^{ic} > \beta_{ic}$' - -.. math:: - - \alpha^{ic} > \beta_{ic} - -Some symbols automatically put their sub/superscripts under and over the -operator. For example, to write the sum of :mathmpl:`x_i` from :mathmpl:`0` to -:mathmpl:`\infty`, you could do:: - - r'$\sum_{i=0}^\infty x_i$' - -.. math:: - - \sum_{i=0}^\infty x_i - -Fractions, binomials, and stacked numbers ------------------------------------------ -Fractions, binomials, and stacked numbers can be created with the -``\frac{}{}``, ``\binom{}{}`` and ``\genfrac{}{}{}{}{}{}`` commands, -respectively:: - - r'$\frac{3}{4} \binom{3}{4} \genfrac{}{}{0}{}{3}{4}$' - -produces - -.. math:: - - \frac{3}{4} \binom{3}{4} \genfrac{}{}{0pt}{}{3}{4} - -Fractions can be arbitrarily nested:: - - r'$\frac{5 - \frac{1}{x}}{4}$' - -produces - -.. math:: - - \frac{5 - \frac{1}{x}}{4} - -Note that special care needs to be taken to place parentheses and brackets -around fractions. Doing things the obvious way produces brackets that are too -small:: - - r'$(\frac{5 - \frac{1}{x}}{4})$' - -.. math:: - - (\frac{5 - \frac{1}{x}}{4}) - -The solution is to precede the bracket with ``\left`` and ``\right`` to inform -the parser that those brackets encompass the entire object.:: - - r'$\left(\frac{5 - \frac{1}{x}}{4}\right)$' - -.. math:: - - \left(\frac{5 - \frac{1}{x}}{4}\right) - -Radicals --------- -Radicals can be produced with the ``\sqrt[]{}`` command. For example:: - - r'$\sqrt{2}$' - -.. math:: - - \sqrt{2} - -Any base can (optionally) be provided inside square brackets. Note that the -base must be a simple expression, and cannot contain layout commands such as -fractions or sub/superscripts:: - - r'$\sqrt[3]{x}$' - -.. math:: - - \sqrt[3]{x} - -.. _mathtext-fonts: - -Fonts ------ -The default font is *italics* for mathematical symbols. - -.. note:: - - This default can be changed using :rc:`mathtext.default`. This is - useful, for example, to use the same font as regular non-math text for math - text, by setting it to ``regular``. - -To change fonts, e.g., to write "sin" in a Roman font, enclose the text in a -font command:: - - r'$s(t) = \mathcal{A}\mathrm{sin}(2 \omega t)$' - -.. math:: - - s(t) = \mathcal{A}\mathrm{sin}(2 \omega t) - -More conveniently, many commonly used function names that are typeset in -a Roman font have shortcuts. So the expression above could be written as -follows:: - - r'$s(t) = \mathcal{A}\sin(2 \omega t)$' - -.. math:: - - s(t) = \mathcal{A}\sin(2 \omega t) - -Here "s" and "t" are variable in italics font (default), "sin" is in Roman -font, and the amplitude "A" is in calligraphy font. Note in the example above -the calligraphy ``A`` is squished into the ``sin``. You can use a spacing -command to add a little whitespace between them:: - - r's(t) = \mathcal{A}\/\sin(2 \omega t)' - -.. Here we cheat a bit: for HTML math rendering, Sphinx relies on MathJax which - doesn't actually support the italic correction (\/); instead, use a thin - space (\,) which is supported. - -.. math:: - - s(t) = \mathcal{A}\,\sin(2 \omega t) - -The choices available with all fonts are: - -========================= ================================ -Command Result -========================= ================================ -``\mathrm{Roman}`` :mathmpl:`\mathrm{Roman}` -``\mathit{Italic}`` :mathmpl:`\mathit{Italic}` -``\mathtt{Typewriter}`` :mathmpl:`\mathtt{Typewriter}` -``\mathcal{CALLIGRAPHY}`` :mathmpl:`\mathcal{CALLIGRAPHY}` -========================= ================================ - -.. role:: math-stix(mathmpl) - :fontset: stix - -When using the `STIX `_ fonts, you also have the -choice of: - -================================ ========================================= -Command Result -================================ ========================================= -``\mathbb{blackboard}`` :math-stix:`\mathbb{blackboard}` -``\mathrm{\mathbb{blackboard}}`` :math-stix:`\mathrm{\mathbb{blackboard}}` -``\mathfrak{Fraktur}`` :math-stix:`\mathfrak{Fraktur}` -``\mathsf{sansserif}`` :math-stix:`\mathsf{sansserif}` -``\mathrm{\mathsf{sansserif}}`` :math-stix:`\mathrm{\mathsf{sansserif}}` -``\mathbfit{bolditalic}`` :math-stix:`\mathbfit{bolditalic}` -================================ ========================================= - -There are also five global "font sets" to choose from, which are -selected using the ``mathtext.fontset`` parameter in :ref:`matplotlibrc -`. - -``dejavusans``: DejaVu Sans - .. mathmpl:: - :fontset: dejavusans - - \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) - -``dejavuserif``: DejaVu Serif - .. mathmpl:: - :fontset: dejavuserif - - \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) - -``cm``: Computer Modern (TeX) - .. mathmpl:: - :fontset: cm - - \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) - -``stix``: STIX (designed to blend well with Times) - .. mathmpl:: - :fontset: stix - - \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) - -``stixsans``: STIX sans-serif - .. mathmpl:: - :fontset: stixsans - - \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) - -Additionally, you can use ``\mathdefault{...}`` or its alias -``\mathregular{...}`` to use the font used for regular text outside of -mathtext. There are a number of limitations to this approach, most notably -that far fewer symbols will be available, but it can be useful to make math -expressions blend well with other text in the plot. - -For compatibility with popular packages, ``\text{...}`` is available and uses the -``\mathrm{...}`` font, but otherwise retains spaces and renders - as a dash -(not minus). - -Custom fonts -~~~~~~~~~~~~ -mathtext also provides a way to use custom fonts for math. This method is -fairly tricky to use, and should be considered an experimental feature for -patient users only. By setting :rc:`mathtext.fontset` to ``custom``, -you can then set the following parameters, which control which font file to use -for a particular set of math characters. - -============================== ================================= -Parameter Corresponds to -============================== ================================= -``mathtext.it`` ``\mathit{}`` or default italic -``mathtext.rm`` ``\mathrm{}`` Roman (upright) -``mathtext.tt`` ``\mathtt{}`` Typewriter (monospace) -``mathtext.bf`` ``\mathbf{}`` bold -``mathtext.bfit`` ``\mathbfit{}`` bold italic -``mathtext.cal`` ``\mathcal{}`` calligraphic -``mathtext.sf`` ``\mathsf{}`` sans-serif -============================== ================================= - -Each parameter should be set to a fontconfig font descriptor (as defined in the -yet-to-be-written font chapter). - -.. TODO: Link to font chapter - -The fonts used should have a Unicode mapping in order to find any -non-Latin characters, such as Greek. If you want to use a math symbol -that is not contained in your custom fonts, you can set -:rc:`mathtext.fallback` to either ``'cm'``, ``'stix'`` or ``'stixsans'`` -which will cause the mathtext system to use -characters from an alternative font whenever a particular -character cannot be found in the custom font. - -Note that the math glyphs specified in Unicode have evolved over time, and many -fonts may not have glyphs in the correct place for mathtext. - -Accents -------- -An accent command may precede any symbol to add an accent above it. There are -long and short forms for some of them. - -============================== ================================= -Command Result -============================== ================================= -``\acute a`` or ``\'a`` :mathmpl:`\acute a` -``\bar a`` :mathmpl:`\bar a` -``\breve a`` :mathmpl:`\breve a` -``\dot a`` or ``\.a`` :mathmpl:`\dot a` -``\ddot a`` or ``\''a`` :mathmpl:`\ddot a` -``\dddot a`` :mathmpl:`\dddot a` -``\ddddot a`` :mathmpl:`\ddddot a` -``\grave a`` or ``\`a`` :mathmpl:`\grave a` -``\hat a`` or ``\^a`` :mathmpl:`\hat a` -``\tilde a`` or ``\~a`` :mathmpl:`\tilde a` -``\vec a`` :mathmpl:`\vec a` -``\overline{abc}`` :mathmpl:`\overline{abc}` -============================== ================================= - -In addition, there are two special accents that automatically adjust to the -width of the symbols below: - -============================== ================================= -Command Result -============================== ================================= -``\widehat{xyz}`` :mathmpl:`\widehat{xyz}` -``\widetilde{xyz}`` :mathmpl:`\widetilde{xyz}` -============================== ================================= - -Care should be taken when putting accents on lower-case i's and j's. Note that -in the following ``\imath`` is used to avoid the extra dot over the i:: - - r"$\hat i\ \ \hat \imath$" - -.. math:: - - \hat i\ \ \hat \imath - -Symbols -------- -You can also use a large number of the TeX symbols, as in ``\infty``, -``\leftarrow``, ``\sum``, ``\int``. - -.. math_symbol_table:: - -If a particular symbol does not have a name (as is true of many of the more -obscure symbols in the STIX fonts), Unicode characters can also be used:: - - r'$\u23ce$' - -Example -------- -Here is an example illustrating many of these features in context. - -.. figure:: /gallery/text_labels_and_annotations/images/sphx_glr_mathtext_demo_001.png - :target: /gallery/text_labels_and_annotations/mathtext_demo.html - :align: center +Matplotlib implements a lightweight TeX expression parser and layout engine and +*Mathtext* is the subset of Tex markup that this engine supports. Any string can +be processed as Mathtext by placing the string inside a pair of dollar signs +``'$'``. Mathtext often contains many backslashes ``'\'``; so that the backslashes +do not need to be escaped, Mathtext is often written using raw strings. For +example: """ + +import matplotlib.pyplot as plt + +fig = plt.figure(figsize=(3, 3), linewidth=1, edgecolor='black') +fig.text(.2, .7, "plain text: alpha > beta") +fig.text(.2, .5, "Mathtext: $\\alpha > \\beta$") +fig.text(.2, .3, r"raw string Mathtext: $\alpha > \beta$") + +# %% +# .. seealso:: +# +# :doc:`Mathtext example ` +# +# TeX does *not* need to be installed to use Mathtext because Matplotlib ships +# with the Mathtext parser and engine. The Mathtext layout engine is a fairly +# direct adaptation of the layout algorithms in Donald Knuth's TeX. To render +# mathematical text using a different TeX engine, see :ref:`usetex`. +# +# .. note:: +# To generate html output in documentation that will exactly match the output +# generated by ``mathtext``, use the `matplotlib.sphinxext.mathmpl` Sphinx +# extension. +# +# +# Special characters +# ------------------ +# +# Mathtext must be placed between a pair of (US) dollar signs ``'$'``. A literal +# dollar symbol ``'$'`` in a string containing Mathtext must be escaped using a +# backslash: ``'\$'``. A string may contain multiple pairs of dollar signs, +# resulting in multiple Mathtext expressions. Strings with an odd number of +# dollar signs are rendered solely as plain text. + +fig = plt.figure(figsize=(3, 3), linewidth=1, edgecolor='black') +fig.suptitle("Number of unescaped $") +fig.text(.1, .7, r"odd: $ \alpha $ = $1") +fig.text(.1, .5, r"even: $ \beta $= $ 2 $") +fig.text(.1, .3, r'odd: $ \gamma $= \$3 $') +fig.text(.1, .1, r'even: $ \delta $ = $ \$4 $') + +# %% +# While Mathtext aims for compatibility with regular TeX, it diverges on when +# special characters need to be escaped. In TeX the dollar sign must be escaped +# ``'\$'`` in non-math text, while in Matplotlib the dollar sign must be +# escaped when writing Mathtext. +# +# These other special characters are also escaped in non-math TeX, while in +# Matplotlib their behavior is dependent on how :rc:`text.usetex` is set:: +# +# # $ % & ~ _ ^ \ { } \( \) \[ \] +# +# See the :ref:`usetex tutorial ` for more information. +# +# +# Subscripts and superscripts +# --------------------------- +# To make subscripts and superscripts, use the ``'_'`` and ``'^'`` symbols:: +# +# r'$\alpha_i > \beta_i$' +# +# .. math:: +# +# \alpha_i > \beta_i +# +# To display multi-letter subscripts or superscripts correctly, +# you should put them in curly braces ``{...}``:: +# +# r'$\alpha^{ic} > \beta_{ic}$' +# +# .. math:: +# +# \alpha^{ic} > \beta_{ic} +# +# Some symbols automatically put their sub/superscripts under and over the +# operator. For example, to write the sum of :mathmpl:`x_i` from :mathmpl:`0` to +# :mathmpl:`\infty`, you could do:: +# +# r'$\sum_{i=0}^\infty x_i$' +# +# .. math:: +# +# \sum_{i=0}^\infty x_i +# +# Fractions, binomials, and stacked numbers +# ----------------------------------------- +# Fractions, binomials, and stacked numbers can be created with the +# ``\frac{}{}``, ``\binom{}{}`` and ``\genfrac{}{}{}{}{}{}`` commands, +# respectively:: +# +# r'$\frac{3}{4} \binom{3}{4} \genfrac{}{}{0}{}{3}{4}$' +# +# produces +# +# .. math:: +# +# \frac{3}{4} \binom{3}{4} \genfrac{}{}{0pt}{}{3}{4} +# +# Fractions can be arbitrarily nested:: +# +# r'$\frac{5 - \frac{1}{x}}{4}$' +# +# produces +# +# .. math:: +# +# \frac{5 - \frac{1}{x}}{4} +# +# Note that special care needs to be taken to place parentheses and brackets +# around fractions. Doing things the obvious way produces brackets that are too +# small:: +# +# r'$(\frac{5 - \frac{1}{x}}{4})$' +# +# .. math:: +# +# (\frac{5 - \frac{1}{x}}{4}) +# +# The solution is to precede the bracket with ``\left`` and ``\right`` to inform +# the parser that those brackets encompass the entire object.:: +# +# r'$\left(\frac{5 - \frac{1}{x}}{4}\right)$' +# +# .. math:: +# +# \left(\frac{5 - \frac{1}{x}}{4}\right) +# +# Radicals +# -------- +# Radicals can be produced with the ``\sqrt[]{}`` command. For example:: +# +# r'$\sqrt{2}$' +# +# .. math:: +# +# \sqrt{2} +# +# Any base can (optionally) be provided inside square brackets. Note that the +# base must be a simple expression, and cannot contain layout commands such as +# fractions or sub/superscripts:: +# +# r'$\sqrt[3]{x}$' +# +# .. math:: +# +# \sqrt[3]{x} +# +# .. _mathtext-fonts: +# +# Fonts +# ----- +# +# The default font is *italics* for mathematical symbols. +# +# This default can be changed using :rc:`mathtext.default`. For setting rcParams, +# see :ref:`customizing`. For example, setting the default to ``regular`` allows +# you to use the same font for math text and regular non-math text. +# +# To change fonts, e.g., to write "sin" in a Roman font, enclose the text in a +# font command:: +# +# r'$s(t) = \mathcal{A}\mathrm{sin}(2 \omega t)$' +# +# .. math:: +# +# s(t) = \mathcal{A}\mathrm{sin}(2 \omega t) +# +# More conveniently, many commonly used function names that are typeset in +# a Roman font have shortcuts. So the expression above could be written as +# follows:: +# +# r'$s(t) = \mathcal{A}\sin(2 \omega t)$' +# +# .. math:: +# +# s(t) = \mathcal{A}\sin(2 \omega t) +# +# Here "s" and "t" are variable in italics font (default), "sin" is in Roman +# font, and the amplitude "A" is in calligraphy font. Note in the example above +# the calligraphy ``A`` is squished into the ``sin``. You can use a spacing +# command to add a little whitespace between them:: +# +# r's(t) = \mathcal{A}\/\sin(2 \omega t)' +# +# .. Here we cheat a bit: for HTML math rendering, Sphinx relies on MathJax which +# doesn't actually support the italic correction (\/); instead, use a thin +# space (\,) which is supported. +# +# .. math:: +# +# s(t) = \mathcal{A}\,\sin(2 \omega t) +# +# Mathtext can use DejaVu Sans (default), DejaVu Serif, Computer Modern fonts +# from (La)TeX, `STIX `_ fonts which are designed +# to blend well with Times, or a Unicode font that you provide. The Mathtext +# font can be selected via :rc:`mathtext.fontset`. +# +# The choices available with all fonts are: +# +# ========================= ================================ +# Command Result +# ========================= ================================ +# ``\mathrm{Roman}`` :mathmpl:`\mathrm{Roman}` +# ``\mathit{Italic}`` :mathmpl:`\mathit{Italic}` +# ``\mathtt{Typewriter}`` :mathmpl:`\mathtt{Typewriter}` +# ``\mathcal{CALLIGRAPHY}`` :mathmpl:`\mathcal{CALLIGRAPHY}` +# ========================= ================================ +# +# .. role:: math-stix(mathmpl) +# :fontset: stix +# +# When using the `STIX `_ fonts, you also have the +# choice of: +# +# ================================ ========================================= +# Command Result +# ================================ ========================================= +# ``\mathbb{blackboard}`` :math-stix:`\mathbb{blackboard}` +# ``\mathrm{\mathbb{blackboard}}`` :math-stix:`\mathrm{\mathbb{blackboard}}` +# ``\mathfrak{Fraktur}`` :math-stix:`\mathfrak{Fraktur}` +# ``\mathsf{sansserif}`` :math-stix:`\mathsf{sansserif}` +# ``\mathrm{\mathsf{sansserif}}`` :math-stix:`\mathrm{\mathsf{sansserif}}` +# ``\mathbfit{bolditalic}`` :math-stix:`\mathbfit{bolditalic}` +# ================================ ========================================= +# +# There are also five global "font sets" to choose from, which are +# selected using the ``mathtext.fontset`` parameter in :ref:`matplotlibrc +# `. +# +# ``dejavusans``: DejaVu Sans +# .. mathmpl:: +# :fontset: dejavusans +# +# \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) +# +# ``dejavuserif``: DejaVu Serif +# .. mathmpl:: +# :fontset: dejavuserif +# +# \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) +# +# ``cm``: Computer Modern (TeX) +# .. mathmpl:: +# :fontset: cm +# +# \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) +# +# ``stix``: STIX (designed to blend well with Times) +# .. mathmpl:: +# :fontset: stix +# +# \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) +# +# ``stixsans``: STIX sans-serif +# .. mathmpl:: +# :fontset: stixsans +# +# \mathcal{R} \prod_{i=\alpha}^{\infty} a_i \sin\left(2\pi fx_i\right) +# +# Additionally, you can use ``\mathdefault{...}`` or its alias +# ``\mathregular{...}`` to use the font used for regular text outside of +# Mathtext. There are a number of limitations to this approach, most notably +# that far fewer symbols will be available, but it can be useful to make math +# expressions blend well with other text in the plot. +# +# For compatibility with popular packages, ``\text{...}`` is available and uses the +# ``\mathrm{...}`` font, but otherwise retains spaces and renders - as a dash +# (not minus). +# +# Custom fonts +# ~~~~~~~~~~~~ +# Mathtext also provides a way to use custom fonts for math. This method is +# fairly tricky to use, and should be considered an experimental feature for +# patient users only. By setting :rc:`mathtext.fontset` to ``custom``, +# you can then set the following parameters, which control which font file to use +# for a particular set of math characters. +# +# ============================== ================================= +# Parameter Corresponds to +# ============================== ================================= +# ``mathtext.it`` ``\mathit{}`` or default italic +# ``mathtext.rm`` ``\mathrm{}`` Roman (upright) +# ``mathtext.tt`` ``\mathtt{}`` Typewriter (monospace) +# ``mathtext.bf`` ``\mathbf{}`` bold +# ``mathtext.bfit`` ``\mathbfit{}`` bold italic +# ``mathtext.cal`` ``\mathcal{}`` calligraphic +# ``mathtext.sf`` ``\mathsf{}`` sans-serif +# ============================== ================================= +# +# Each parameter should be set to a fontconfig font descriptor, as defined in +# :ref:`fonts`. The fonts used should have a Unicode mapping in order to find +# any non-Latin characters, such as Greek. If you want to use a math symbol +# that is not contained in your custom fonts, you can set +# :rc:`mathtext.fallback` to either ``'cm'``, ``'stix'`` or ``'stixsans'`` +# which will cause the Mathtext system to use +# characters from an alternative font whenever a particular +# character cannot be found in the custom font. +# +# Note that the math glyphs specified in Unicode have evolved over time, and +# many fonts may not have glyphs in the correct place for Mathtext. +# +# Accents +# ------- +# An accent command may precede any symbol to add an accent above it. There are +# long and short forms for some of them. +# +# ============================== ================================= +# Command Result +# ============================== ================================= +# ``\acute a`` or ``\'a`` :mathmpl:`\acute a` +# ``\bar a`` :mathmpl:`\bar a` +# ``\breve a`` :mathmpl:`\breve a` +# ``\dot a`` or ``\.a`` :mathmpl:`\dot a` +# ``\ddot a`` or ``\''a`` :mathmpl:`\ddot a` +# ``\dddot a`` :mathmpl:`\dddot a` +# ``\ddddot a`` :mathmpl:`\ddddot a` +# ``\grave a`` or ``\`a`` :mathmpl:`\grave a` +# ``\hat a`` or ``\^a`` :mathmpl:`\hat a` +# ``\tilde a`` or ``\~a`` :mathmpl:`\tilde a` +# ``\vec a`` :mathmpl:`\vec a` +# ``\overline{abc}`` :mathmpl:`\overline{abc}` +# ============================== ================================= +# +# In addition, there are two special accents that automatically adjust to the +# width of the symbols below: +# +# ============================== ================================= +# Command Result +# ============================== ================================= +# ``\widehat{xyz}`` :mathmpl:`\widehat{xyz}` +# ``\widetilde{xyz}`` :mathmpl:`\widetilde{xyz}` +# ============================== ================================= +# +# Care should be taken when putting accents on lower-case i's and j's. Note +# that in the following ``\imath`` is used to avoid the extra dot over the i:: +# +# r"$\hat i\ \ \hat \imath$" +# +# .. math:: +# +# \hat i\ \ \hat \imath +# +# Symbols +# ------- +# You can also use a large number of the TeX symbols, as in ``\infty``, +# ``\leftarrow``, ``\sum``, ``\int``. +# +# .. math_symbol_table:: +# +# If a particular symbol does not have a name (as is true of many of the more +# obscure symbols in the STIX fonts), Unicode characters can also be used:: +# +# r'$\u23ce$' From d3afa558a02b972d7e6bd863482053bb9d64e6d6 Mon Sep 17 00:00:00 2001 From: Gautam Sagar <47146980+gautamsagar99@users.noreply.github.com> Date: Thu, 31 Aug 2023 02:16:38 -0500 Subject: [PATCH 0095/1120] Removed unnecessary origin keywords --- .../contourf_demo.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/contourf_demo.py b/galleries/examples/images_contours_and_fields/contourf_demo.py index edf72d928270..776d2535a872 100644 --- a/galleries/examples/images_contours_and_fields/contourf_demo.py +++ b/galleries/examples/images_contours_and_fields/contourf_demo.py @@ -8,8 +8,6 @@ import matplotlib.pyplot as plt import numpy as np -origin = 'lower' - delta = 0.025 x = y = np.arange(-3.0, 3.01, delta) @@ -41,14 +39,14 @@ # for purposes of illustration. fig1, ax2 = plt.subplots(layout='constrained') -CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.bone, origin=origin) +CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.bone) # Note that in the following, we explicitly pass in a subset of the contour # levels used for the filled contours. Alternatively, we could pass in # additional levels to provide extra resolution, or leave out the *levels* # keyword argument to use all of the original levels. -CS2 = ax2.contour(CS, levels=CS.levels[::2], colors='r', origin=origin) +CS2 = ax2.contour(CS, levels=CS.levels[::2], colors='r') ax2.set_title('Nonsense (3 masked regions)') ax2.set_xlabel('word length anomaly') @@ -68,20 +66,14 @@ fig2, ax2 = plt.subplots(layout='constrained') levels = [-1.5, -1, -0.5, 0, 0.5, 1] -CS3 = ax2.contourf(X, Y, Z, levels, - colors=('r', 'g', 'b'), - origin=origin, - extend='both') +CS3 = ax2.contourf(X, Y, Z, levels, colors=('r', 'g', 'b'), extend='both') # Our data range extends outside the range of levels; make # data below the lowest contour level yellow, and above the # highest level cyan: CS3.cmap.set_under('yellow') CS3.cmap.set_over('cyan') -CS4 = ax2.contour(X, Y, Z, levels, - colors=('k',), - linewidths=(3,), - origin=origin) +CS4 = ax2.contour(X, Y, Z, levels, colors=('k',), linewidths=(3,)) ax2.set_title('Listed colors (3 masked regions)') ax2.clabel(CS4, fmt='%2.1f', colors='w', fontsize=14) @@ -104,13 +96,31 @@ fig, axs = plt.subplots(2, 2, layout="constrained") for ax, extend in zip(axs.flat, extends): - cs = ax.contourf(X, Y, Z, levels, cmap=cmap, extend=extend, origin=origin) + cs = ax.contourf(X, Y, Z, levels, cmap=cmap, extend=extend) fig.colorbar(cs, ax=ax, shrink=0.9) ax.set_title("extend = %s" % extend) ax.locator_params(nbins=4) plt.show() +# %% +# Orient contour plots using the origin keyword +# --------------------------------------------- +# This code demonstrates orienting contour plot data using the "origin" keyword + +x = np.arange(1, 10) +y = x.reshape(-1, 1) +h = x * y + +fig, (ax1, ax2) = plt.subplots(ncols=2) + +ax1.set_title("origin='upper'") +ax2.set_title("origin='lower'") +ax1.contourf(h, levels=np.arange(5, 70, 5), extend='both', origin="upper") +ax2.contourf(h, levels=np.arange(5, 70, 5), extend='both', origin="lower") + +plt.show() + # %% # # .. admonition:: References From babd065c2e7b11c29d8e9e2e21a9336f2f3d2a2f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 17:09:39 -0400 Subject: [PATCH 0096/1120] DOC: Fix override of Axes setter in example `set_axes` was dropped from `Artist` a long time ago, so the version in the subclass did nothing. --- .../text_labels_and_annotations/line_with_text.py | 8 +++++--- lib/matplotlib/legend.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/galleries/examples/text_labels_and_annotations/line_with_text.py b/galleries/examples/text_labels_and_annotations/line_with_text.py index 8a62733b13ce..389554bd5ae1 100644 --- a/galleries/examples/text_labels_and_annotations/line_with_text.py +++ b/galleries/examples/text_labels_and_annotations/line_with_text.py @@ -28,9 +28,11 @@ def set_figure(self, figure): self.text.set_figure(figure) super().set_figure(figure) - def set_axes(self, axes): - self.text.set_axes(axes) - super().set_axes(axes) + # Override the axes property setter to set Axes on our children as well. + @lines.Line2D.axes.setter + def axes(self, new_axes): + self.text.axes = new_axes + lines.Line2D.axes.fset(self, new_axes) # Call the superclass property setter. def set_transform(self, transform): # 2 pixel offset diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index bdabacdfedbf..fcd7850bb473 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -642,7 +642,6 @@ def _set_artist_props(self, a): """ a.set_figure(self.figure) if self.isaxes: - # a.set_axes(self.axes) a.axes = self.axes a.set_transform(self.get_transform()) From 75441cee35c51f27f07952c1af3775ce160be71d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 19:29:07 -0400 Subject: [PATCH 0097/1120] DOC: Remove extra imports from paths tutorial --- galleries/users_explain/artists/paths.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/galleries/users_explain/artists/paths.py b/galleries/users_explain/artists/paths.py index d505711fd1c0..b096d05d0751 100644 --- a/galleries/users_explain/artists/paths.py +++ b/galleries/users_explain/artists/paths.py @@ -18,6 +18,8 @@ could use this code: """ +import numpy as np + import matplotlib.pyplot as plt import matplotlib.patches as patches @@ -191,11 +193,6 @@ # edgecolor='yellow', alpha=0.5) # ax.add_patch(patch) -import numpy as np - -import matplotlib.patches as patches -import matplotlib.path as path - fig, ax = plt.subplots() # Fixing random state for reproducibility np.random.seed(19680801) @@ -213,9 +210,9 @@ nverts = nrects*(1+3+1) verts = np.zeros((nverts, 2)) -codes = np.ones(nverts, int) * path.Path.LINETO -codes[0::5] = path.Path.MOVETO -codes[4::5] = path.Path.CLOSEPOLY +codes = np.full(nverts, Path.LINETO, dtype=int) +codes[0::5] = Path.MOVETO +codes[4::5] = Path.CLOSEPOLY verts[0::5, 0] = left verts[0::5, 1] = bottom verts[1::5, 0] = left @@ -225,7 +222,7 @@ verts[3::5, 0] = right verts[3::5, 1] = bottom -barpath = path.Path(verts, codes) +barpath = Path(verts, codes) patch = patches.PathPatch(barpath, facecolor='green', edgecolor='yellow', alpha=0.5) ax.add_patch(patch) From 4b0084abda9abc0a4b1486fad2174fee4306c0c6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 19:51:02 -0400 Subject: [PATCH 0098/1120] DOC: Use dict.items to iterate subplot_mosaic result --- galleries/users_explain/axes/arranging_axes.py | 16 ++++++++-------- galleries/users_explain/axes/axes_scales.py | 17 +++++++---------- galleries/users_explain/figure/figure_intro.rst | 4 ++-- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/galleries/users_explain/axes/arranging_axes.py b/galleries/users_explain/axes/arranging_axes.py index 9288eede9fd5..79b69f4bf3dd 100644 --- a/galleries/users_explain/axes/arranging_axes.py +++ b/galleries/users_explain/axes/arranging_axes.py @@ -152,8 +152,8 @@ def annotate_axes(ax, text, fontsize=18): fig, axd = plt.subplot_mosaic([['upper left', 'upper right'], ['lower left', 'lower right']], figsize=(5.5, 3.5), layout="constrained") -for k in axd: - annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) +for k, ax in axd.items(): + annotate_axes(ax, f'axd[{k!r}]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') # %% @@ -200,8 +200,8 @@ def annotate_axes(ax, text, fontsize=18): fig, axd = plt.subplot_mosaic([['upper left', 'right'], ['lower left', 'right']], figsize=(5.5, 3.5), layout="constrained") -for k in axd: - annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) +for k, ax in axd.items(): + annotate_axes(ax, f'axd[{k!r}]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') # %% @@ -223,8 +223,8 @@ def annotate_axes(ax, text, fontsize=18): ['lower left', 'right']], gridspec_kw=gs_kw, figsize=(5.5, 3.5), layout="constrained") -for k in axd: - annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) +for k, ax in axd.items(): + annotate_axes(ax, f'axd[{k!r}]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') # %% @@ -262,8 +262,8 @@ def annotate_axes(ax, text, fontsize=18): ['lower left', 'lower right']] fig, axd = plt.subplot_mosaic(outer, layout="constrained") -for k in axd: - annotate_axes(axd[k], f'axd["{k}"]') +for k, ax in axd.items(): + annotate_axes(ax, f'axd[{k!r}]') # %% # Low-level and advanced grid methods diff --git a/galleries/users_explain/axes/axes_scales.py b/galleries/users_explain/axes/axes_scales.py index 567f3c5762ed..6b163835070c 100644 --- a/galleries/users_explain/axes/axes_scales.py +++ b/galleries/users_explain/axes/axes_scales.py @@ -98,25 +98,23 @@ # %% # -todo = ['asinh', 'symlog', 'log', 'logit', ] fig, axs = plt.subplot_mosaic([['asinh', 'symlog'], ['log', 'logit']], layout='constrained') x = np.arange(0, 1000) -for td in todo: - ax = axs[td] - if td in ['asinh', 'symlog']: +for name, ax in axs.items(): + if name in ['asinh', 'symlog']: yy = x - np.mean(x) - elif td in ['logit']: + elif name in ['logit']: yy = (x-np.min(x)) yy = yy / np.max(np.abs(yy)) else: yy = x ax.plot(yy, yy) - ax.set_yscale(td) - ax.set_title(td) + ax.set_yscale(name) + ax.set_title(name) # %% # Optional arguments for scales @@ -131,9 +129,8 @@ fig, axs = plt.subplot_mosaic([['log', 'symlog']], layout='constrained', figsize=(6.4, 3)) -for td in axs: - ax = axs[td] - if td in ['log']: +for name, ax in axs.items(): + if name in ['log']: ax.plot(x, x) ax.set_yscale('log', base=2) ax.set_title('log base=2') diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index 87bec6236d2a..745b01566427 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -139,8 +139,8 @@ More complex grids can be achieved with `.pyplot.subplot_mosaic` (which wraps fig, axs = plt.subplot_mosaic([['A', 'right'], ['B', 'right']], figsize=(4, 3), layout='constrained') - for ax_name in axs: - axs[ax_name].text(0.5, 0.5, ax_name, ha='center', va='center') + for ax_name, ax in axs.items(): + ax.text(0.5, 0.5, ax_name, ha='center', va='center') Sometimes we want to have a nested layout in a Figure, with two or more sets of Axes that do not share the same subplot grid. From f403f2e306109ca962146684ebea61f1d6521519 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 20:21:23 -0400 Subject: [PATCH 0099/1120] DOC: Remove usage of discouraged layout engine API Fixes #26637 --- galleries/examples/userdemo/connectionstyle_demo.py | 2 +- lib/matplotlib/figure.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/galleries/examples/userdemo/connectionstyle_demo.py b/galleries/examples/userdemo/connectionstyle_demo.py index 50ca108fc92b..e34c63a5708b 100644 --- a/galleries/examples/userdemo/connectionstyle_demo.py +++ b/galleries/examples/userdemo/connectionstyle_demo.py @@ -48,7 +48,7 @@ def demo_con_style(ax, connectionstyle): for ax in axs.flat: ax.set(xlim=(0, 1), ylim=(0, 1.25), xticks=[], yticks=[], aspect=1.25) -fig.set_constrained_layout_pads(wspace=0, hspace=0, w_pad=0, h_pad=0) +fig.get_layout_engine().set(wspace=0, hspace=0, w_pad=0, h_pad=0) plt.show() diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index da03778b92ea..fe997d35372c 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2466,8 +2466,7 @@ def __init__(self, to avoid overlapping axes decorations. Can handle complex plot layouts and colorbars, and is thus recommended. - See :ref:`constrainedlayout_guide` - for examples. + See :ref:`constrainedlayout_guide` for examples. - 'compressed': uses the same algorithm as 'constrained', but removes extra space between fixed-aspect-ratio Axes. Best for @@ -2475,8 +2474,9 @@ def __init__(self, - 'tight': Use the tight layout mechanism. This is a relatively simple algorithm that adjusts the subplot parameters so that - decorations do not overlap. See `.set_tight_layout` for - further details. + decorations do not overlap. + + See :ref:`tight_layout_guide` for examples. - 'none': Do not use a layout engine. From 1d8659586031ec98a733bde94b42e1ca87ddaf0d Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Mon, 26 Jun 2023 20:24:00 +0530 Subject: [PATCH 0100/1120] Add sorted tex2uni data table --- lib/matplotlib/_mathtext_data.py | 1277 +++++++++++++++--------------- 1 file changed, 644 insertions(+), 633 deletions(-) diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index d37cdff22e57..1a12d1ec08ce 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -460,640 +460,651 @@ uni2type1 = {v: k for k, v in type12uni.items()} +# The script below is to sort and format the tex2uni dict + +## For decimal values: int(hex(v), 16) +# newtex = {k: hex(v) for k, v in tex2uni.items()} +# sd = dict(sorted(newtex.items(), key=lambda item: item[0])) +# +## For formatting the sorted dictionary with proper spacing +## the value '24' comes from finding the longest string in +## the newtex keys with len(max(newtex, key=len)) +# for key in sd: +# print("{0:24} : {1: Date: Thu, 31 Aug 2023 12:17:38 +0200 Subject: [PATCH 0101/1120] Clarify loading of backend FigureCanvas and show(). - FigureCanvas is now required to exist on backend modules (since the deprecation elapsed in 3.8) and we indeed already access that attribute directly when checking required_interactive_framework, so remove redundant getattrs. - Clarify the error message when manager_class is None *and* there's no global show(). (Previously an AttributeError would be raised.) --- lib/matplotlib/pyplot.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 415d5c042241..6c8294d31a3f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -339,8 +339,9 @@ def switch_backend(newbackend: str) -> None: old_backend = dict.__getitem__(rcParams, 'backend') module = importlib.import_module(cbook._backend_module_name(newbackend)) + canvas_class = module.FigureCanvas - required_framework = module.FigureCanvas.required_interactive_framework + required_framework = canvas_class.required_interactive_framework if required_framework is not None: current_framework = cbook._get_running_interactive_framework() if (current_framework and required_framework @@ -369,8 +370,6 @@ class backend_mod(matplotlib.backend_bases._Backend): # update backend_mod accordingly; also, per-backend customization of # draw_if_interactive is disabled. if new_figure_manager is None: - # Only try to get the canvas class if have opted into the new scheme. - canvas_class = backend_mod.FigureCanvas def new_figure_manager_given_figure(num, figure): return canvas_class.new_manager(figure, num) @@ -394,8 +393,7 @@ def draw_if_interactive() -> None: # If the manager explicitly overrides pyplot_show, use it even if a global # show is already present, as the latter may be here for backcompat. - manager_class = getattr(getattr(backend_mod, "FigureCanvas", None), - "manager_class", None) + manager_class = getattr(canvas_class, "manager_class", None) # We can't compare directly manager_class.pyplot_show and FMB.pyplot_show because # pyplot_show is a classmethod so the above constructs are bound classmethods, and # thus always different (being bound to different classes). We also have to use @@ -405,6 +403,10 @@ def draw_if_interactive() -> None: if (show is None or (manager_pyplot_show is not None and manager_pyplot_show != base_pyplot_show)): + if not manager_pyplot_show: + raise ValueError( + f"Backend {newbackend} defines neither FigureCanvas.manager_class nor " + f"a toplevel show function") _pyplot_show = cast('Any', manager_class).pyplot_show backend_mod.show = _pyplot_show # type: ignore[method-assign] From 219323a1cb6fe5840b4a503d7dc8962a236d7664 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:47:20 +0200 Subject: [PATCH 0102/1120] [MNT] Move SubplotParams from figure to gridspec Logically, this belongs more to the gridspec layout than to the figure. There is no API breakage involved, because `matplotlib.figure` reimports `SubplotParams`. Do we nevertheless want an API change note? --- doc/api/gridspec_api.rst | 1 + .../next_api_changes/behavior/26634-TH.rst | 5 ++ .../auto_subplots_adjust.py | 2 +- lib/matplotlib/figure.py | 67 +----------------- lib/matplotlib/figure.pyi | 28 +------- lib/matplotlib/gridspec.py | 70 +++++++++++++++++-- lib/matplotlib/gridspec.pyi | 28 +++++++- 7 files changed, 102 insertions(+), 99 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26634-TH.rst diff --git a/doc/api/gridspec_api.rst b/doc/api/gridspec_api.rst index 2b9f5a67784c..fe1137d94113 100644 --- a/doc/api/gridspec_api.rst +++ b/doc/api/gridspec_api.rst @@ -19,3 +19,4 @@ Classes SubplotSpec GridSpecBase GridSpecFromSubplotSpec + SubplotParams diff --git a/doc/api/next_api_changes/behavior/26634-TH.rst b/doc/api/next_api_changes/behavior/26634-TH.rst new file mode 100644 index 000000000000..4961722078d6 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26634-TH.rst @@ -0,0 +1,5 @@ +``SubplotParams`` has been moved from ``matplotlib.figure`` to ``matplotlib.gridspec`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is still importable from ``matplotlib.figure``, so does not require any changes to +existing code. diff --git a/galleries/examples/subplots_axes_and_figures/auto_subplots_adjust.py b/galleries/examples/subplots_axes_and_figures/auto_subplots_adjust.py index 3bd0d3f2bec1..e0a8c76a0e61 100644 --- a/galleries/examples/subplots_axes_and_figures/auto_subplots_adjust.py +++ b/galleries/examples/subplots_axes_and_figures/auto_subplots_adjust.py @@ -83,5 +83,5 @@ def on_draw(event): # - `matplotlib.transforms.BboxBase.union` # - `matplotlib.transforms.Transform.inverted` # - `matplotlib.figure.Figure.subplots_adjust` -# - `matplotlib.figure.SubplotParams` +# - `matplotlib.gridspec.SubplotParams` # - `matplotlib.backend_bases.FigureCanvasBase.mpl_connect` diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4361ef655c81..c28ba98edec8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -10,9 +10,6 @@ `SubFigure`) with `Figure.add_subfigure` or `Figure.subfigures` methods (provisional API v3.4). -`SubplotParams` - Control the default spacing between subplots. - Figures are typically created using pyplot methods `~.pyplot.figure`, `~.pyplot.subplots`, and `~.pyplot.subplot_mosaic`. @@ -51,7 +48,7 @@ import matplotlib.image as mimage from matplotlib.axes import Axes -from matplotlib.gridspec import GridSpec +from matplotlib.gridspec import GridSpec, SubplotParams from matplotlib.layout_engine import ( ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine, PlaceHolderLayoutEngine @@ -118,66 +115,6 @@ def __setstate__(self, state): self._counter = itertools.count(next_counter) -class SubplotParams: - """ - A class to hold the parameters for a subplot. - """ - - def __init__(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): - """ - Defaults are given by :rc:`figure.subplot.[name]`. - - Parameters - ---------- - left : float - The position of the left edge of the subplots, - as a fraction of the figure width. - right : float - The position of the right edge of the subplots, - as a fraction of the figure width. - bottom : float - The position of the bottom edge of the subplots, - as a fraction of the figure height. - top : float - The position of the top edge of the subplots, - as a fraction of the figure height. - wspace : float - The width of the padding between subplots, - as a fraction of the average Axes width. - hspace : float - The height of the padding between subplots, - as a fraction of the average Axes height. - """ - for key in ["left", "bottom", "right", "top", "wspace", "hspace"]: - setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"]) - self.update(left, bottom, right, top, wspace, hspace) - - def update(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): - """ - Update the dimensions of the passed parameters. *None* means unchanged. - """ - if ((left if left is not None else self.left) - >= (right if right is not None else self.right)): - raise ValueError('left cannot be >= right') - if ((bottom if bottom is not None else self.bottom) - >= (top if top is not None else self.top)): - raise ValueError('bottom cannot be >= top') - if left is not None: - self.left = left - if right is not None: - self.right = right - if bottom is not None: - self.bottom = bottom - if top is not None: - self.top = top - if wspace is not None: - self.wspace = wspace - if hspace is not None: - self.hspace = hspace - - class FigureBase(Artist): """ Base class for `.Figure` and `.SubFigure` containing the methods that add @@ -2435,7 +2372,7 @@ def __init__(self, frameon : bool, default: :rc:`figure.frameon` If ``False``, suppress drawing the figure background patch. - subplotpars : `SubplotParams` + subplotpars : `~matplotlib.gridspec.SubplotParams` Subplot parameters. If not given, the default subplot parameters :rc:`figure.subplot.*` are used. diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index 40e8fce0321f..f53feb78014d 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -11,7 +11,7 @@ from matplotlib.backend_bases import ( from matplotlib.colors import Colormap, Normalize from matplotlib.colorbar import Colorbar from matplotlib.cm import ScalarMappable -from matplotlib.gridspec import GridSpec, SubplotSpec +from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams from matplotlib.image import _ImageBase, FigureImage from matplotlib.layout_engine import LayoutEngine from matplotlib.legend import Legend @@ -27,32 +27,6 @@ from collections.abc import Callable, Iterable from typing import Any, IO, Literal, overload from .typing import ColorType, HashableList -class SubplotParams: - def __init__( - self, - left: float | None = ..., - bottom: float | None = ..., - right: float | None = ..., - top: float | None = ..., - wspace: float | None = ..., - hspace: float | None = ..., - ) -> None: ... - left: float - right: float - bottom: float - top: float - wspace: float - hspace: float - def update( - self, - left: float | None = ..., - bottom: float | None = ..., - right: float | None = ..., - top: float | None = ..., - wspace: float | None = ..., - hspace: float | None = ..., - ) -> None: ... - class FigureBase(Artist): artists: list[Artist] lines: list[Line2D] diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index b2f1b5d8ff2e..13a7d29ca563 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -320,7 +320,7 @@ class GridSpec(GridSpecBase): A grid layout to place subplots within a figure. The location of the grid cells is determined in a similar way to - `~.figure.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace* + `.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace* and *hspace*. Indexing a GridSpec instance returns a `.SubplotSpec`. @@ -424,7 +424,7 @@ def get_subplot_params(self, figure=None): if figure is None: kw = {k: mpl.rcParams["figure.subplot."+k] for k in self._AllowedKeys} - subplotpars = mpl.figure.SubplotParams(**kw) + subplotpars = SubplotParams(**kw) else: subplotpars = copy.copy(figure.subplotpars) @@ -517,9 +517,9 @@ def get_subplot_params(self, figure=None): figbox = self._subplot_spec.get_position(figure) left, bottom, right, top = figbox.extents - return mpl.figure.SubplotParams(left=left, right=right, - bottom=bottom, top=top, - wspace=wspace, hspace=hspace) + return SubplotParams(left=left, right=right, + bottom=bottom, top=top, + wspace=wspace, hspace=hspace) def get_topmost_subplotspec(self): """ @@ -736,3 +736,63 @@ def subgridspec(self, nrows, ncols, **kwargs): fig.add_subplot(gssub[0, i]) """ return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs) + + +class SubplotParams: + """ + Parameters defining the positioning of a subplots grid in a figure. + """ + + def __init__(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Defaults are given by :rc:`figure.subplot.[name]`. + + Parameters + ---------- + left : float + The position of the left edge of the subplots, + as a fraction of the figure width. + right : float + The position of the right edge of the subplots, + as a fraction of the figure width. + bottom : float + The position of the bottom edge of the subplots, + as a fraction of the figure height. + top : float + The position of the top edge of the subplots, + as a fraction of the figure height. + wspace : float + The width of the padding between subplots, + as a fraction of the average Axes width. + hspace : float + The height of the padding between subplots, + as a fraction of the average Axes height. + """ + for key in ["left", "bottom", "right", "top", "wspace", "hspace"]: + setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"]) + self.update(left, bottom, right, top, wspace, hspace) + + def update(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Update the dimensions of the passed parameters. *None* means unchanged. + """ + if ((left if left is not None else self.left) + >= (right if right is not None else self.right)): + raise ValueError('left cannot be >= right') + if ((bottom if bottom is not None else self.bottom) + >= (top if top is not None else self.top)): + raise ValueError('bottom cannot be >= top') + if left is not None: + self.left = left + if right is not None: + self.right = right + if bottom is not None: + self.bottom = bottom + if top is not None: + self.top = top + if wspace is not None: + self.wspace = wspace + if hspace is not None: + self.hspace = hspace diff --git a/lib/matplotlib/gridspec.pyi b/lib/matplotlib/gridspec.pyi index 6e2273080b3b..6f314054a9ee 100644 --- a/lib/matplotlib/gridspec.pyi +++ b/lib/matplotlib/gridspec.pyi @@ -5,7 +5,7 @@ import numpy as np from matplotlib.axes import Axes, SubplotBase from matplotlib.backend_bases import RendererBase -from matplotlib.figure import Figure, SubplotParams +from matplotlib.figure import Figure from matplotlib.transforms import Bbox class GridSpecBase: @@ -132,3 +132,29 @@ class SubplotSpec: def subgridspec( self, nrows: int, ncols: int, **kwargs ) -> GridSpecFromSubplotSpec: ... + +class SubplotParams: + def __init__( + self, + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + ) -> None: ... + left: float + right: float + bottom: float + top: float + wspace: float + hspace: float + def update( + self, + left: float | None = ..., + bottom: float | None = ..., + right: float | None = ..., + top: float | None = ..., + wspace: float | None = ..., + hspace: float | None = ..., + ) -> None: ... From 88863e1bc62a203d606d19d0aa3e14de12ceeeb6 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:42:29 +0200 Subject: [PATCH 0103/1120] [DOC] Enhance API reference index Co-authored-by: Jody Klymak --- doc/_static/mpl.css | 5 ++++ doc/api/index.rst | 56 +++++++++++++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index e9d7d65f3e01..45ecb21d5511 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -122,6 +122,11 @@ table.property-table td { margin: 0 0.5em; } +/* Make the code examples in the API reference index the same height. */ +.api-interface-example pre { + min-height: 6.5rem; +} + /* Make inheritance images have a scroll bar if necessary. */ div.graphviz { border: 1px solid lightgrey; diff --git a/doc/api/index.rst b/doc/api/index.rst index 1b098009faed..e55a0ed3c5b2 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -4,25 +4,23 @@ API Reference Matplotlib interfaces --------------------- -Matplotlib provides two different interfaces: - -- **Axes interface**: create a `.Figure` and one or more `~.axes.Axes` objects - (typically using `.pyplot.subplots`), then *explicitly* use methods on these objects - to add data, configure limits, set labels etc. -- **pyplot interface**: consists of functions in the `.pyplot` module. Figure and Axes - are manipulated through these functions and are only *implicitly* present in the - background. - -See :ref:`api_interfaces` for a more detailed description of both and their recommended -use cases. +Matplotlib has two interfaces. See :ref:`api_interfaces` for a more detailed +description of both and their recommended use cases. .. grid:: 1 1 2 2 - :padding: 0 0 1 1 + :padding: 0 + :gutter: 2 .. grid-item-card:: + :shadow: none + :class-footer: api-interface-footer **Axes interface** (object-based, explicit) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + create a `.Figure` and one or more `~.axes.Axes` objects, then *explicitly* use + methods on these objects to add data, configure limits, set labels etc. + + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ API: @@ -30,15 +28,45 @@ use cases. - :mod:`~matplotlib.axes`: add data, limits, labels etc. - `.Figure`: for figure-level methods + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + Example: + + .. code-block:: python + :class: api-interface-example + + fig, ax = plt.subplots() + ax.plot(x, y) + ax.set_title("Sample plot") + plt.show() + + .. grid-item-card:: + :shadow: none + :class-footer: api-interface-footer **pyplot interface** (function-based, implicit) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + consists of functions in the `.pyplot` module. Figure and Axes are manipulated + through these functions and are only *implicitly* present in the background. + + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ API: - `matplotlib.pyplot` + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + Example: + + .. code-block:: python + :class: api-interface-example + + plt.plot(x, y) + plt.title("Sample plot") + plt.show() + .. _api-index: From 2bf23975ba0c8287ca3ea507f3b2aa618c4b997e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:55:34 +0200 Subject: [PATCH 0104/1120] [DOC] Slightly improve the LineCollection docstring --- lib/matplotlib/collections.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 9db9a066fb65..81db24d0c026 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1404,11 +1404,12 @@ def __init__(self, segments, # Can be None. Parameters ---------- segments : list of array-like - A sequence of (*line0*, *line1*, *line2*), where:: + A sequence (*line0*, *line1*, *line2*) of lines, where each line is a list + of points:: - linen = (x0, y0), (x1, y1), ... (xm, ym) + lineN = [(x0, y0), (x1, y1), ... (xm, ym)] - or the equivalent numpy array with two columns. Each line + or the equivalent Mx2 numpy array with two columns. Each line can have a different number of segments. linewidths : float or list of float, default: :rc:`lines.linewidth` The width of each line in points. From 5fb45b8f3f3edf6dff9105c784022dc56b4dbab2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 6 Jun 2023 22:39:43 -0400 Subject: [PATCH 0105/1120] TST: Move locale comma test to a subprocess On some systems/pytest versions, the skip in an exception handler does not skip, but is treated as an exception. Namely, the ARM test machine in Cirrus and on my WSL Ubuntu. --- lib/matplotlib/tests/test_ticker.py | 40 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index c95526ecf987..9d08e335dbdd 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1640,22 +1640,36 @@ def test_latex(self, is_latex, usetex, expected): assert fmt.format_pct(50, 100) == expected -def test_locale_comma(): - currentLocale = locale.getlocale() +def _impl_locale_comma(): try: locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8') - ticks = mticker.ScalarFormatter(useMathText=True, useLocale=True) - fmt = '$\\mathdefault{%1.1f}$' - x = ticks._format_maybe_minus_and_locale(fmt, 0.5) - assert x == '$\\mathdefault{0{,}5}$' - # Do not change , in the format string - fmt = ',$\\mathdefault{,%1.1f},$' - x = ticks._format_maybe_minus_and_locale(fmt, 0.5) - assert x == ',$\\mathdefault{,0{,}5},$' except locale.Error: - pytest.skip("Locale de_DE.UTF-8 is not supported on this machine") - finally: - locale.setlocale(locale.LC_ALL, currentLocale) + print('SKIP: Locale de_DE.UTF-8 is not supported on this machine') + return + ticks = mticker.ScalarFormatter(useMathText=True, useLocale=True) + fmt = '$\\mathdefault{%1.1f}$' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == '$\\mathdefault{0{,}5}$' + # Do not change , in the format string + fmt = ',$\\mathdefault{,%1.1f},$' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == ',$\\mathdefault{,0{,}5},$' + + +def test_locale_comma(): + # On some systems/pytest versions, `pytest.skip` in an exception handler + # does not skip, but is treated as an exception, so directly running this + # test can incorrectly fail instead of skip. + # Instead, run this test in a subprocess, which avoids the problem, and the + # need to fix the locale after. + proc = mpl.testing.subprocess_run_helper(_impl_locale_comma, timeout=60, + extra_env={'MPLBACKEND': 'Agg'}) + skip_msg = next((line[len('SKIP:'):].strip() + for line in proc.stdout.splitlines() + if line.startswith('SKIP:')), + '') + if skip_msg: + pytest.skip(skip_msg) def test_majformatter_type(): From 4bb4dc7d9d47fb483fcbc5a7e5ba7271153bdc36 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 31 Jul 2023 23:49:42 -0400 Subject: [PATCH 0106/1120] Use Agg backend in pickle test subprocess The main parent process is using Agg, so the subprocess should as well, to be able to unpickle correctly. --- lib/matplotlib/tests/test_pickle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 8d1e20b4cb93..e222a495e437 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -143,7 +143,7 @@ def test_pickle_load_from_subprocess(fig_test, fig_ref, tmp_path): proc = subprocess_run_helper( _pickle_load_subprocess, timeout=60, - extra_env={'PICKLE_FILE_PATH': str(fp)} + extra_env={'PICKLE_FILE_PATH': str(fp), 'MPLBACKEND': 'Agg'} ) loaded_fig = pickle.loads(ast.literal_eval(proc.stdout)) From 8530b1f2d6fd9fae1baf25b2c9d79471dd91f690 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Aug 2023 19:12:09 -0400 Subject: [PATCH 0107/1120] TST: Mark macosx backend in leak test as xfail --- lib/matplotlib/tests/test_backends_interactive.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index dbc0f0928b74..e15e591b1b10 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -660,7 +660,7 @@ def _test_figure_leak(): reason="appveyor tests fail; gh-22988 suggests reworking") @pytest.mark.parametrize("env", _get_testable_interactive_backends()) @pytest.mark.parametrize("time_mem", [(0.0, 2_000_000), (0.1, 30_000_000)]) -def test_figure_leak_20490(env, time_mem): +def test_figure_leak_20490(env, time_mem, request): pytest.importorskip("psutil", reason="psutil needed to run this test") # We haven't yet directly identified the leaks so test with a memory growth @@ -669,9 +669,10 @@ def test_figure_leak_20490(env, time_mem): if env["MPLBACKEND"] == "wx": pytest.skip("wx backend is deprecated; tests failed on appveyor") - if env["MPLBACKEND"] == "macosx" or ( - env["MPLBACKEND"] == "tkagg" and sys.platform == 'darwin' - ): + if env["MPLBACKEND"] == "macosx": + request.node.add_marker(pytest.mark.xfail(reason="macosx backend is leaky")) + + if env["MPLBACKEND"] == "tkagg" and sys.platform == "darwin": acceptable_memory_leakage += 11_000_000 result = _run_helper( From 02263d2654183df374e467eaad6066e66daf6873 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Aug 2023 21:00:57 -0400 Subject: [PATCH 0108/1120] TST: Mark test_other_signal_before_sigint mac as xfail --- lib/matplotlib/tests/test_backends_interactive.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index e15e591b1b10..6048b2647b17 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -816,12 +816,12 @@ def custom_signal_handler(signum, frame): ('show', {'block': True}), ('pause', {'interval': 10}) ]) -def test_other_signal_before_sigint(env, target, kwargs): +def test_other_signal_before_sigint(env, target, kwargs, request): backend = env.get("MPLBACKEND") if not backend.startswith(("qt", "macosx")): pytest.skip("SIGINT currently only tested on qt and macosx") - if backend == "macosx" and target == "show": - pytest.xfail("test currently failing for macosx + show()") + if backend == "macosx": + request.node.add_marker(pytest.mark.xfail(reason="macosx backend is buggy")) proc = _WaitForStringPopen( [sys.executable, "-c", inspect.getsource(_test_other_signal_before_sigint_impl) + From 3d116a7d58b8ba3cc1d8baf95198b07ff37fcd10 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 3 Sep 2023 15:58:51 +0100 Subject: [PATCH 0109/1120] Update PR template doc links [skip ci] --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fa84d5cac9f5..c91ba742fee3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,9 +5,9 @@ - [ ] "closes #0000" is in the body of the PR description to [link the related issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) - [ ] new and changed code is [tested](https://matplotlib.org/devdocs/devel/testing.html) -- [ ] *Plotting related* features are demonstrated in an [example](https://matplotlib.org/devdocs/devel/documenting_mpl.html#writing-examples-and-tutorials) +- [ ] *Plotting related* features are demonstrated in an [example](https://matplotlib.org/devdocs/devel/document.html#write-examples-and-tutorials) - [ ] *New Features* and *API Changes* are noted with a [directive and release note](https://matplotlib.org/devdocs/devel/coding_guide.html#new-features-and-api-changes) -- [ ] Documentation complies with [general](https://matplotlib.org/devdocs/devel/documenting_mpl.html#writing-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/documenting_mpl.html#writing-docstrings) guidelines +- [ ] Documentation complies with [general](https://matplotlib.org/devdocs/devel/document.html#write-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/document.html#write-docstrings) guidelines +## PR summary + ## PR checklist @@ -9,32 +20,8 @@ - [ ] *New Features* and *API Changes* are noted with a [directive and release note](https://matplotlib.org/devdocs/devel/coding_guide.html#new-features-and-api-changes) - [ ] Documentation complies with [general](https://matplotlib.org/devdocs/devel/documenting_mpl.html#writing-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/documenting_mpl.html#writing-docstrings) guidelines - +back on your PR.--> From 9398d8a6b2470455a5c623699f05d8766b9f5802 Mon Sep 17 00:00:00 2001 From: hannah Date: Sun, 3 Sep 2023 15:00:34 -0400 Subject: [PATCH 0111/1120] Added sanitize_sequence to kwargs in _preprocess_data (#26670) Added a simple test for using errorbar with mapview kwarg Changed errorbar kwarg test to be more explicit Co-authored-by: jovianw --- lib/matplotlib/__init__.py | 5 ++++- lib/matplotlib/tests/test_axes.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 1e3dab1b336b..d0212cfe0276 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1462,7 +1462,10 @@ def func(foo, label=None): ... @functools.wraps(func) def inner(ax, *args, data=None, **kwargs): if data is None: - return func(ax, *map(sanitize_sequence, args), **kwargs) + return func( + ax, + *map(sanitize_sequence, args), + **{k: sanitize_sequence(v) for k, v in kwargs.items()}) bound = new_sig.bind(ax, *args, **kwargs) auto_label = (bound.arguments.get(label_namer) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index e6251948ebee..3b5f9019ead2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -835,6 +835,12 @@ def test_errorbar_dashes(fig_test, fig_ref): ax_test.errorbar(x, y, xerr=np.abs(y), yerr=np.abs(y), dashes=[2, 2]) +def test_errorbar_mapview_kwarg(): + D = {ii: ii for ii in range(10)} + fig, ax = plt.subplots() + ax.errorbar(x=D.keys(), y=D.values(), xerr=D.values()) + + @image_comparison(['single_point', 'single_point']) def test_single_point(): # Issue #1796: don't let lines.marker affect the grid From 7ebce14d93327dc8d10879c7bbdf091267cf60b5 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 4 Sep 2023 09:23:11 +0200 Subject: [PATCH 0112/1120] Make generated pgf code more robust against later changes of tex engine. By moving the conditionals to tex itself, the generated tex code becomes compilable with engines different from the one originally configured; this is similar to the `\ifdefined\pdfpagewidth` test in PdfPages.savefig. --- lib/matplotlib/backends/backend_pgf.py | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 734c188b0048..7d60249d2477 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -34,7 +34,7 @@ def _get_preamble(): """Prepare a LaTeX preamble based on the rcParams configuration.""" - preamble = [ + return "\n".join([ # Remove Matplotlib's custom command \mathdefault. (Not using # \mathnormal instead since this looks odd with Computer Modern.) r"\def\mathdefault#1{#1}", @@ -42,21 +42,21 @@ def _get_preamble(): r"\everymath=\expandafter{\the\everymath\displaystyle}", # Allow pgf.preamble to override the above definitions. mpl.rcParams["pgf.preamble"], - ] - if mpl.rcParams["pgf.texsystem"] != "pdflatex": - preamble.append("\\usepackage{fontspec}") - if mpl.rcParams["pgf.rcfonts"]: - families = ["serif", "sans\\-serif", "monospace"] - commands = ["setmainfont", "setsansfont", "setmonofont"] - for family, command in zip(families, commands): - # 1) Forward slashes also work on Windows, so don't mess with - # backslashes. 2) The dirname needs to include a separator. - path = pathlib.Path(fm.findfont(family)) - preamble.append(r"\%s{%s}[Path=\detokenize{%s/}]" % ( - command, path.name, path.parent.as_posix())) - preamble.append(mpl.texmanager._usepackage_if_not_loaded( - "underscore", option="strings")) # Documented as "must come last". - return "\n".join(preamble) + r"\ifdefined\pdftexversion\else % non-pdftex case.", + r" \usepackage{fontspec}", + *([ + r" \%s{%s}[Path=\detokenize{%s/}]" + % (command, path.name, path.parent.as_posix()) + for command, path in zip( + ["setmainfont", "setsansfont", "setmonofont"], + [pathlib.Path(fm.findfont(family)) + for family in ["serif", "sans\\-serif", "monospace"]] + ) + ] if mpl.rcParams["pgf.rcfonts"] else []), + r"\fi", + # Documented as "must come last". + mpl.texmanager._usepackage_if_not_loaded("underscore", option="strings"), + ]) # It's better to use only one unit for all coordinates, since the @@ -94,9 +94,9 @@ def _escape_and_apply_props(s, prop): family = prop.get_family()[0] if family in families: commands.append(families[family]) - elif (any(font.name == family for font in fm.fontManager.ttflist) - and mpl.rcParams["pgf.texsystem"] != "pdflatex"): - commands.append(r"\setmainfont{%s}\rmfamily" % family) + elif any(font.name == family for font in fm.fontManager.ttflist): + commands.append( + r"\ifdefined\pdftexversion\else\setmainfont{%s}\rmfamily\fi" % family) else: _log.warning("Ignoring unknown font: %s", family) From 4ceb02f6fcab7c0ad4f366333eca634aa6c49bce Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 4 Sep 2023 07:57:44 +0200 Subject: [PATCH 0113/1120] Fix error generation for missing pgf.texsystem. If pgf.texsystem isn't installed, then self.latex will not be created *at all* by _setup_latex_process, and we thus cannot access self.latex.args[0], but must instead read the rc value again. To make this clearer, move the error handling into _setup_latex_process, instead of having it outside. No test because that'd require setting up a test env where pgf.texsystem is explicitly *missing* (or go through lots of mocks), which seems not worth it. --- lib/matplotlib/backends/backend_pgf.py | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 734c188b0048..ccf4b800a614 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -241,16 +241,7 @@ def __init__(self): self._finalize_tmpdir = weakref.finalize(self, self._tmpdir.cleanup) # test the LaTeX setup to ensure a clean startup of the subprocess - try: - self._setup_latex_process(expect_reply=False) - except FileNotFoundError as err: - raise RuntimeError( - f"{self.latex.args[0]!r} not found. Install it or change " - f"rcParams['pgf.texsystem'] to an available TeX " - f"implementation.") from err - except OSError as err: - raise RuntimeError( - f"Error starting process {self.latex.args[0]!r}") from err + self._setup_latex_process(expect_reply=False) stdout, stderr = self.latex.communicate("\n\\makeatletter\\@@end\n") if self.latex.returncode != 0: raise LatexError( @@ -258,7 +249,6 @@ def __init__(self): f"while processing the following input:\n" f"{self._build_latex_header()}", stdout) - self.latex = None # Will be set up on first use. # Per-instance cache. self._get_box_metrics = functools.lru_cache(self._get_box_metrics) @@ -268,10 +258,19 @@ def _setup_latex_process(self, *, expect_reply=True): # Windows, we must ensure that the subprocess has quit before being # able to delete the tmpdir in which it runs; in order to do so, we # must first `kill()` it, and then `communicate()` with it. - self.latex = subprocess.Popen( - [mpl.rcParams["pgf.texsystem"], "-halt-on-error"], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - encoding="utf-8", cwd=self.tmpdir) + try: + self.latex = subprocess.Popen( + [mpl.rcParams["pgf.texsystem"], "-halt-on-error"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + encoding="utf-8", cwd=self.tmpdir) + except FileNotFoundError as err: + raise RuntimeError( + f"{mpl.rcParams['pgf.texsystem']!r} not found; install it or change " + f"rcParams['pgf.texsystem'] to an available TeX implementation" + ) from err + except OSError as err: + raise RuntimeError( + f"Error starting {mpl.rcParams['pgf.texsystem']!r}") from err def finalize_latex(latex): latex.kill() From c20554d1cb96ac254560e8db1e1df5b19de799a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 19:33:56 +0000 Subject: [PATCH 0114/1120] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 2 +- .github/workflows/circleci.yml | 2 +- .github/workflows/clean_pr.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/cygwin.yml | 2 +- .github/workflows/mypy-stubtest.yml | 2 +- .github/workflows/reviewdog.yml | 6 +++--- .github/workflows/tests.yml | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 8c45ef95990b..54b68c6bc56b 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -39,7 +39,7 @@ jobs: SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index ab8362bed0da..8f9e3190c5e2 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest name: Post warnings/errors as review steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Fetch result artifacts id: fetch-artifacts diff --git a/.github/workflows/clean_pr.yml b/.github/workflows/clean_pr.yml index e1fc4c1530ff..f3ccb3195d77 100644 --- a/.github/workflows/clean_pr.yml +++ b/.github/workflows/clean_pr.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' - name: Check for added-and-deleted files diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c10336410178..d31973e954d5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index b4b30b57dab1..11ea1377b36d 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -78,7 +78,7 @@ jobs: - name: Fix line endings run: git config --global core.autocrlf input - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml index f8897d0697fe..6da6f607642c 100644 --- a/.github/workflows/mypy-stubtest.yml +++ b/.github/workflows/mypy-stubtest.yml @@ -11,7 +11,7 @@ jobs: name: mypy-stubtest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3 uses: actions/setup-python@v4 diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index f1d57141602d..dafc331a11eb 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -12,7 +12,7 @@ jobs: name: flake8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3 uses: actions/setup-python@v4 @@ -42,7 +42,7 @@ jobs: name: mypy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3 uses: actions/setup-python@v4 @@ -77,7 +77,7 @@ jobs: name: eslint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: eslint uses: reviewdog/action-eslint@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b48c47907d09..adff7f1ce292 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,7 +88,7 @@ jobs: pyside6-ver: '!=6.5.1' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 From e908ac1344fd90f72fe7a43962db61bec170abfc Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 5 Sep 2023 12:11:20 -0500 Subject: [PATCH 0115/1120] Add changed callback after setting norm values --- lib/matplotlib/contour.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index aa3041ddce9b..7de45b54de38 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -892,7 +892,8 @@ def __init__(self, ax, *args, self.norm.vmin = vmin if vmax is not None: self.norm.vmax = vmax - self._process_colors() + self.norm._changed() + self._process_colors() if self._paths is None: self._paths = self._make_paths_from_contour_generator() From e5a554aecbb1d5b5690f5376fda6c260e84e8f77 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 5 Sep 2023 19:53:54 +0200 Subject: [PATCH 0116/1120] Improve naming of cibuildwheel jobs --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 8c45ef95990b..366dece1a23e 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -87,7 +87,7 @@ jobs: 'CI: Run cibuildwheel') ) needs: build_sdist - name: Build wheels on ${{ matrix.os }} + name: Build wheels on ${{ matrix.os }} for ${{ matrix.cibw_archs }} runs-on: ${{ matrix.os }} env: CIBW_BEFORE_BUILD: >- From 414d3825dac72bb5e870d592d75eed1edd44866a Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 5 Sep 2023 20:38:35 +0200 Subject: [PATCH 0117/1120] Check type for set_clip_box --- lib/matplotlib/artist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 5c3f6dc5952f..04eaa6cf75df 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -15,7 +15,7 @@ from .colors import BoundaryNorm from .cm import ScalarMappable from .path import Path -from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox, +from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox, TransformedPatchPath, TransformedPath) _log = logging.getLogger(__name__) @@ -763,6 +763,7 @@ def set_clip_box(self, clipbox): clipping for an artist added to an Axes. """ + _api.check_isinstance((BboxBase, None), clipbox=clipbox) if clipbox != self.clipbox: self.clipbox = clipbox self.pchanged() From 58d5aad7ba8bec31cf1ff5dc4a758fabc9b7fe16 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2023 03:52:43 -0400 Subject: [PATCH 0118/1120] TYP: Add common type overloads of subplot_mosaic I'll assert, without proof, that passing a single string, a mosaic list of strings, or a mosaic list all of the same type is more common than passing arbitrary unrelated hashables. Thus it is somewhat convenient if the return type stipulates that the resulting dictionary is also keyed with strings or the common type. This also fixes the type of the `per_subplot_kw` argument, which also allows dictionary keys of tuples of the entries. --- doc/missing-references.json | 6 ++++ lib/matplotlib/figure.py | 8 ++--- lib/matplotlib/figure.pyi | 52 +++++++++++++++++++++------- lib/matplotlib/pyplot.py | 68 +++++++++++++++++++++++++++++++++---- lib/matplotlib/typing.py | 5 +-- 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/doc/missing-references.json b/doc/missing-references.json index 1061b08b7fe6..364885ab3a74 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -152,6 +152,9 @@ "HashableList": [ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.subplot_mosaic:1" ], + "HashableList[_HT]": [ + "doc/docstring of builtins.list:17" + ], "LineStyleType": [ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.eventplot:1", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hlines:1", @@ -701,6 +704,9 @@ "matplotlib.animation.TimedAnimation.to_jshtml": [ "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], + "matplotlib.typing._HT": [ + "doc/docstring of builtins.list:17" + ], "mpl_toolkits.axislines.Axes": [ "lib/mpl_toolkits/axisartist/axis_artist.py:docstring of mpl_toolkits.axisartist.axis_artist:7" ], diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 9d929acf688c..d37dda204278 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1749,15 +1749,11 @@ def _norm_per_subplot_kw(per_subplot_kw): if isinstance(k, tuple): for sub_key in k: if sub_key in expanded: - raise ValueError( - f'The key {sub_key!r} appears multiple times.' - ) + raise ValueError(f'The key {sub_key!r} appears multiple times.') expanded[sub_key] = v else: if k in expanded: - raise ValueError( - f'The key {k!r} appears multiple times.' - ) + raise ValueError(f'The key {k!r} appears multiple times.') expanded[k] = v return expanded diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index f53feb78014d..687ae9e500d0 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -1,4 +1,9 @@ +from collections.abc import Callable, Hashable, Iterable import os +from typing import Any, IO, Literal, TypeVar, overload + +import numpy as np +from numpy.typing import ArrayLike from matplotlib.artist import Artist from matplotlib.axes import Axes, SubplotBase @@ -19,14 +24,10 @@ from matplotlib.lines import Line2D from matplotlib.patches import Rectangle, Patch from matplotlib.text import Text from matplotlib.transforms import Affine2D, Bbox, BboxBase, Transform - -import numpy as np -from numpy.typing import ArrayLike - -from collections.abc import Callable, Iterable -from typing import Any, IO, Literal, overload from .typing import ColorType, HashableList +_T = TypeVar("_T") + class FigureBase(Artist): artists: list[Artist] lines: list[Line2D] @@ -200,11 +201,38 @@ class FigureBase(Artist): *, bbox_extra_artists: Iterable[Artist] | None = ..., ) -> Bbox: ... - - # Any in list of list is recursive list[list[Hashable | list[Hashable | ...]]] but that can't really be type checked + @overload def subplot_mosaic( self, - mosaic: str | HashableList, + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[str, Axes]: ... + @overload + def subplot_mosaic( + self, + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[_T, Axes]: ... + @overload + def subplot_mosaic( + self, + mosaic: list[HashableList[Hashable]], *, sharex: bool = ..., sharey: bool = ..., @@ -212,9 +240,9 @@ class FigureBase(Artist): height_ratios: ArrayLike | None = ..., empty_sentinel: Any = ..., subplot_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[Any, dict[str, Any]] | None = ..., - gridspec_kw: dict[str, Any] | None = ... - ) -> dict[Any, Axes]: ... + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> dict[Hashable, Axes]: ... class SubFigure(FigureBase): figure: Figure diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index cc18b6b21bf0..00e5dea071a4 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -125,6 +125,7 @@ _P = ParamSpec('_P') _R = TypeVar('_R') + _T = TypeVar('_T') # We may not need the following imports here: @@ -1602,8 +1603,56 @@ def subplots( return fig, axs +@overload +def subplot_mosaic( + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[str, matplotlib.axes.Axes]]: ... + + +@overload +def subplot_mosaic( + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[_T, matplotlib.axes.Axes]]: ... + + +@overload +def subplot_mosaic( + mosaic: list[HashableList[Hashable]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: Any = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + **fig_kw: Any +) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: ... + + def subplot_mosaic( - mosaic: str | HashableList, + mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], *, sharex: bool = False, sharey: bool = False, @@ -1612,9 +1661,13 @@ def subplot_mosaic( empty_sentinel: Any = '.', subplot_kw: dict[str, Any] | None = None, gridspec_kw: dict[str, Any] | None = None, - per_subplot_kw: dict[Hashable, dict[str, Any]] | None = None, - **fig_kw -) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | + dict[_T | tuple[_T, ...], dict[str, Any]] | + dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, + **fig_kw: Any +) -> tuple[Figure, dict[str, matplotlib.axes.Axes]] | \ + tuple[Figure, dict[_T, matplotlib.axes.Axes]] | \ + tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: """ Build a layout of Axes based on ASCII art or nested lists. @@ -1716,12 +1769,13 @@ def subplot_mosaic( """ fig = figure(**fig_kw) - ax_dict = fig.subplot_mosaic( - mosaic, sharex=sharex, sharey=sharey, + ax_dict = fig.subplot_mosaic( # type: ignore[misc] + mosaic, # type: ignore[arg-type] + sharex=sharex, sharey=sharey, height_ratios=height_ratios, width_ratios=width_ratios, subplot_kw=subplot_kw, gridspec_kw=gridspec_kw, empty_sentinel=empty_sentinel, - per_subplot_kw=per_subplot_kw, + per_subplot_kw=per_subplot_kw, # type: ignore[arg-type] ) return fig, ax_dict diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index e6b9ada7d8a2..02059be94ba2 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -11,7 +11,7 @@ """ from collections.abc import Hashable, Sequence import pathlib -from typing import Any, Literal, Union +from typing import Any, Literal, TypeVar, Union from . import path from ._enums import JoinStyle, CapStyle @@ -55,5 +55,6 @@ Sequence[Union[str, pathlib.Path, dict[str, Any]]], ] -HashableList = list[Union[Hashable, "HashableList"]] +_HT = TypeVar("_HT", bound=Hashable) +HashableList = list[Union[_HT, "HashableList[_HT]"]] """A nested list of Hashable values.""" From dc3c81a92b7a7744e2a022e8e6d5a28aa0de854d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 6 Sep 2023 15:55:55 -0500 Subject: [PATCH 0119/1120] DOC: consistency in docstrings of formatting of array-like --- lib/matplotlib/axis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 56d4e6b77dc6..9adfb2cde7d3 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -2097,7 +2097,7 @@ def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs): Parameters ---------- - ticks : 1D ArrayLike + ticks : 1D array-like Array of tick locations. The axis `.Locator` is replaced by a `~.ticker.FixedLocator`. From b62bfb0e27d2bf586cf0adb4958a3307a8330cee Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 6 Sep 2023 02:54:47 -0400 Subject: [PATCH 0120/1120] converted coc to rst and added md forward rule --- CODE_OF_CONDUCT.md | 139 +----------------------- doc/users/project/code_of_conduct.rst | 147 ++++++++++++++++++++++++++ doc/users/project/index.rst | 1 + 3 files changed, 152 insertions(+), 135 deletions(-) create mode 100644 doc/users/project/code_of_conduct.rst diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1e35beeaf357..d43f37214254 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,136 +1,5 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[matplotlib-coc@numfocus.org](mailto:matplotlib-coc@numfocus.org) -(monitored by the [CoC subcommittee](https://matplotlib.org/governance/people.html#coc-subcommittee)) or a -report can be made using the [NumFOCUS Code of Conduct report form][numfocus -form]. If community leaders cannot come to a resolution about enforcement, -reports will be escalated to the NumFocus Code of Conduct committee -(conduct@numfocus.org). All complaints will be reviewed and investigated -promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -[numfocus form]: https://numfocus.typeform.com/to/ynjGdT - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +--- +layout: forward +target: https://matplotlib.org/stable/users/project/code_of_conduct.html +--- diff --git a/doc/users/project/code_of_conduct.rst b/doc/users/project/code_of_conduct.rst new file mode 100644 index 000000000000..8deda909bd4c --- /dev/null +++ b/doc/users/project/code_of_conduct.rst @@ -0,0 +1,147 @@ +.. code_of_conduct + +==================================== +Contributor Covenant Code of Conduct +==================================== + +Our Pledge +========== + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +============= + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +============================ + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +===== + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +=========== + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +`matplotlib-coc@numfocus.org `_ which is +monitored by the `CoC subcommittee `_ or a +report can be made using the `NumFOCUS Code of Conduct report form `_. +If community leaders cannot come to a resolution about enforcement, +reports will be escalated to the NumFocus Code of Conduct committee +(conduct@numfocus.org). All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +====================== + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +------------- + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +---------- + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +3. Temporary Ban +---------------- + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +---------------- + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +Attribution +=========== + +This Code of Conduct is adapted from the `Contributor Covenant `_, +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct +enforcement ladder `_. + + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/doc/users/project/index.rst b/doc/users/project/index.rst index 27f60d166abb..80dd6e27bc3d 100644 --- a/doc/users/project/index.rst +++ b/doc/users/project/index.rst @@ -8,6 +8,7 @@ Project information mission.rst history.rst + Code of Conduct citing.rst license.rst credits.rst From cb84a6b50f4440d9f27b8a7b4d234b83a29f037b Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 6 Sep 2023 10:03:58 -0400 Subject: [PATCH 0121/1120] put placeholder text with links out in code_of_conduct.md --- CODE_OF_CONDUCT.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d43f37214254..657eb14336b9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,6 @@ + ---- -layout: forward -target: https://matplotlib.org/stable/users/project/code_of_conduct.html ---- +Our Code of Conduct is at +https://matplotlib.org/stable/users/project/code_of_conduct.html + +It is rendered from `doc/users/project/code_of_conduct.rst` From 41e38c5b1584632d2dac27bdb620b076b6947084 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Fri, 8 Sep 2023 09:18:31 +0200 Subject: [PATCH 0122/1120] Fix issue with missing attribute in Path3DCollection --- lib/mpl_toolkits/mplot3d/art3d.py | 1 + lib/mpl_toolkits/mplot3d/tests/test_art3d.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ac6e841f5019..d2d782123f6e 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -833,6 +833,7 @@ def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): """ if isinstance(col, PathCollection): col.__class__ = Path3DCollection + col._offset_zordered = None elif isinstance(col, PatchCollection): col.__class__ = Patch3DCollection col._depthshade = depthshade diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py index 02d35aad0e4b..4ed48aae4685 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -1,6 +1,9 @@ +import numpy as np + import matplotlib.pyplot as plt from matplotlib.backend_bases import MouseEvent +from mpl_toolkits.mplot3d.art3d import Line3DCollection def test_scatter_3d_projection_conservation(): @@ -36,3 +39,18 @@ def test_scatter_3d_projection_conservation(): assert contains is True assert len(ind["ind"]) == 1 assert ind["ind"][0] == i + + +def test_zordered_error(): + # Smoke test for https://github.com/matplotlib/matplotlib/issues/26497 + lc = [(np.fromiter([0.0, 0.0, 0.0], dtype="float"), + np.fromiter([1.0, 1.0, 1.0], dtype="float"))] + pc = [np.fromiter([0.0, 0.0], dtype="float"), + np.fromiter([0.0, 1.0], dtype="float"), + np.fromiter([1.0, 1.0], dtype="float")] + + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + ax.add_collection(Line3DCollection(lc)) + ax.scatter(*pc, visible=False) + plt.draw() From fa80f34333df2033ac219beb11486bdbac1dff22 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 8 Sep 2023 05:30:32 -0400 Subject: [PATCH 0123/1120] Add a Python 3.12 classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index de8976cb70d2..e9c5df01eb68 100644 --- a/setup.py +++ b/setup.py @@ -305,6 +305,7 @@ def make_release_tree(self, base_dir, files): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering :: Visualization', ], From 3fd6eecfe978a7b818964c365eb6ca45f106f9a0 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 6 Sep 2023 10:09:05 +0200 Subject: [PATCH 0124/1120] [Doc] Small fixes found by velin --- lib/matplotlib/_constrained_layout.py | 61 ++++++++++++-------- lib/matplotlib/backend_bases.py | 5 ++ lib/matplotlib/backends/_backend_pdf_ps.py | 2 +- lib/matplotlib/backends/backend_svg.py | 1 + lib/matplotlib/colors.py | 32 +++++++--- lib/matplotlib/legend_handler.py | 2 +- lib/matplotlib/patches.py | 7 ++- lib/mpl_toolkits/axes_grid1/axes_divider.py | 16 ++++- lib/mpl_toolkits/axes_grid1/axes_rgb.py | 4 +- lib/mpl_toolkits/axes_grid1/parasite_axes.py | 2 +- lib/mpl_toolkits/axisartist/axis_artist.py | 8 +-- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/mplot3d/axis3d.py | 4 +- 13 files changed, 94 insertions(+), 52 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index a562e44a44af..907e7a24976e 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -69,11 +69,8 @@ def do_constrained_layout(fig, h_pad, w_pad, Parameters ---------- - fig : Figure - ``Figure`` instance to do the layout in. - - renderer : Renderer - Renderer to use. + fig : `~matplotlib.figure.Figure` + `.Figure` instance to do the layout in. h_pad, w_pad : float Padding around the axes elements in figure-normalized units. @@ -274,7 +271,7 @@ def compress_fixed_aspect(layoutgrids, fig): extrah = np.zeros(gs.nrows) elif _gs != gs: raise ValueError('Cannot do compressed layout if axes are not' - 'all from the same gridspec') + 'all from the same gridspec') orig = ax.get_position(original=True) actual = ax.get_position(original=False) dw = orig.width - actual.width @@ -343,6 +340,19 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, decorations on the axis. Then make room for colorbars. + + Parameters + ---------- + layoutgrids : dict + fig : `~matplotlib.figure.Figure` + `.Figure` instance to do the layout in. + renderer : `~matplotlib.backend_bases.RendererBase` subclass. + The renderer to use. + w_pad, h_pad : float, default: 0 + Width and height padding (in fraction of figure). + hspace, wspace : float, default: 0 + Width and height padding as fraction of figure size divided by + number of columns or rows. """ for sfig in fig.subfigs: # recursively make child panel margins ss = sfig._subplotspec @@ -448,7 +458,7 @@ def make_margin_suptitles(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0): # get the h_pad and w_pad as distances in the local subfigure coordinates: padbox = mtransforms.Bbox([[0, 0], [w_pad, h_pad]]) padbox = (fig.transFigure - - fig.transSubfigure).transform_bbox(padbox) + fig.transSubfigure).transform_bbox(padbox) h_pad_local = padbox.height w_pad_local = padbox.width @@ -578,7 +588,12 @@ def match_submerged_margins(layoutgrids, fig): def get_cb_parent_spans(cbax): """ - Figure out which subplotspecs this colorbar belongs to: + Figure out which subplotspecs this colorbar belongs to. + + Parameters + ---------- + cbax : `~matplotlib.axes.Axes` + Axes for the colorbar. """ rowstart = np.inf rowstop = -np.inf @@ -602,14 +617,14 @@ def get_pos_and_bbox(ax, renderer): Parameters ---------- - ax - renderer + ax : `~matplotlib.axes.Axes` + renderer : `~matplotlib.backend_bases.RendererBase` subclass. Returns ------- - pos : Bbox + pos : `~matplotlib.transforms.Bbox` Position in figure coordinates. - bbox : Bbox + bbox : `~matplotlib.transforms.Bbox` Tight bounding box in figure coordinates. """ fig = ax.figure @@ -672,18 +687,14 @@ def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None): Parameters ---------- - cbax : Axes - Axes for the colorbar - - renderer : - w_pad, h_pad : float - width and height padding (in fraction of figure) - hspace, wspace : float - width and height padding as fraction of figure size divided by - number of columns or rows - margin : array-like - offset the colorbar needs to be pushed to in order to - account for multiple colorbars + layoutgrids : dict + cbax : `~matplotlib.axes.Axes` + Axes for the colorbar. + renderer : `~matplotlib.backend_bases.RendererBase` subclass. + The renderer to use. + offset : array-like + Offset the colorbar needs to be pushed to in order to + account for multiple colorbars. """ parents = cbax._colorbar_info['parents'] @@ -753,7 +764,7 @@ def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None): def reset_margins(layoutgrids, fig): """ - Reset the margins in the layoutboxes of fig. + Reset the margins in the layoutboxes of *fig*. Margins are usually set as a minimum, so if the figure gets smaller the minimum needs to be zero in order for it to grow again. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index b960ca72bae6..49e067b02bfb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -208,10 +208,15 @@ def draw_markers(self, gc, marker_path, marker_trans, path, ---------- gc : `.GraphicsContextBase` The graphics context. + marker_path : `~matplotlib.path.Path` + The path for the marker. marker_trans : `~matplotlib.transforms.Transform` An affine transform applied to the marker. + path : `~matplotlib.path.Path` + The locations to draw the markers. trans : `~matplotlib.transforms.Transform` An affine transform applied to the path. + rgbFace : color, optional """ for vertices, codes in path.iter_segments(trans, simplify=False): if len(vertices): diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 7a4c2e6a3966..ce55df523d9d 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -28,7 +28,7 @@ def get_glyphs_subset(fontfile, characters): Parameters ---------- - symbol : str + fontfile : str Path to the font file characters : str Continuous set of characters to include in subset diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 4bac6bcb4427..f62152ed1f0a 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -200,6 +200,7 @@ def end(self, tag=None, indent=True): tag Element tag. If given, the tag must match the start tag. If omitted, the current element is closed. + indent : bool, default: True """ if tag: assert self.__tags, f"unbalanced end({tag})" diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 43eab9583e90..d0d20806666a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -2300,6 +2300,18 @@ def __init__(self, azdeg=315, altdeg=45, hsv_min_val=0, hsv_max_val=1, altdeg : float, default: 45 degrees The altitude (0-90, degrees up from horizontal) of the light source. + hsv_min_val : number, default: 0 + The minimum value ("v" in "hsv") that the *intensity* map can shift the + output image to. + hsv_max_val : number, default: 1 + The maximum value ("v" in "hsv") that the *intensity* map can shift the + output image to. + hsv_min_sat : number, default: 1 + The minimum saturation value that the *intensity* map can shift the output + image to. + hsv_max_sat : number, default: 0 + The maximum saturation value that the *intensity* map can shift the output + image to. Notes ----- @@ -2599,18 +2611,20 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None, An (M, N, 3) RGB array of floats ranging from 0 to 1 (color image). intensity : `~numpy.ndarray` An (M, N, 1) array of floats ranging from 0 to 1 (grayscale image). - hsv_max_sat : number, default: 1 - The maximum saturation value that the *intensity* map can shift the - output image to. + hsv_max_sat : number, optional + The maximum saturation value that the *intensity* map can shift the output + image to. If not provided, use the value provided upon initialization. hsv_min_sat : number, optional - The minimum saturation value that the *intensity* map can shift the - output image to. Defaults to 0. + The minimum saturation value that the *intensity* map can shift the output + image to. If not provided, use the value provided upon initialization. hsv_max_val : number, optional - The maximum value ("v" in "hsv") that the *intensity* map can shift - the output image to. Defaults to 1. + The maximum value ("v" in "hsv") that the *intensity* map can shift the + output image to. If not provided, use the value provided upon + initialization. hsv_min_val : number, optional - The minimum value ("v" in "hsv") that the *intensity* map can shift - the output image to. Defaults to 0. + The minimum value ("v" in "hsv") that the *intensity* map can shift the + output image to. If not provided, use the value provided upon + initialization. Returns ------- diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index c72edf86a484..5a929070e32d 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -155,7 +155,7 @@ def create_artists(self, legend, orig_handle, fontsize : int The fontsize in pixels. The legend artists being created should be scaled according to the given fontsize. - trans : `~matplotlib.transforms.Transform` + trans : `~matplotlib.transforms.Transform` The transform that is applied to the legend artists being created. Typically from unit coordinates in the handler box to screen coordinates. diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 92dc55940b8a..f80df92c62fc 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -994,9 +994,10 @@ def __init__(self, values, edges, *, True or an array is passed to *baseline*, a closed path is drawn. - Other valid keyword arguments are: + **kwargs + `Patch` properties: - %(Patch:kwdoc)s + %(Patch:kwdoc)s """ self.orientation = orientation self._edges = np.asarray(edges) @@ -1112,7 +1113,7 @@ def set_closed(self, closed): Parameters ---------- closed : bool - True if the polygon is closed + True if the polygon is closed """ if self._closed == bool(closed): return diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index 097bf712d121..f6c38f35dbc4 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -37,11 +37,11 @@ def __init__(self, fig, pos, horizontal, vertical, Sizes for horizontal division. vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size` Sizes for vertical division. - aspect : bool + aspect : bool, optional Whether overall rectangular area is reduced so that the relative part of the horizontal and vertical scales have the same scale. anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ -'NW', 'W'} +'NW', 'W'}, default: 'C' Placement of the reduced rectangle, when *aspect* is True. """ @@ -294,7 +294,7 @@ def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None): ---------- use_axes : `~matplotlib.axes.Axes` or list of `~matplotlib.axes.Axes` The Axes whose decorations are taken into account. - pad : float, optional + pad : float, default: 0.1 Additional padding in inches. adjust_dirs : list of {"left", "right", "bottom", "top"}, optional The sides where padding is added; defaults to all four sides. @@ -377,6 +377,16 @@ def __init__(self, fig, *args, horizontal=None, vertical=None, If *nrows*, *ncols*, and *index* are all single digit numbers, then *args* can be passed as a single 3-digit number (e.g. 234 for (2, 3, 4)). + horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional + Sizes for horizontal division. + vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional + Sizes for vertical division. + aspect : bool, optional + Whether overall rectangular area is reduced so that the relative + part of the horizontal and vertical scales have the same scale. + anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \ +'NW', 'W'}, default: 'C' + Placement of the reduced rectangle, when *aspect* is True. """ self.figure = fig super().__init__(fig, [0, 0, 1, 1], diff --git a/lib/mpl_toolkits/axes_grid1/axes_rgb.py b/lib/mpl_toolkits/axes_grid1/axes_rgb.py index 499684a22d5e..52fd707e8704 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_rgb.py +++ b/lib/mpl_toolkits/axes_grid1/axes_rgb.py @@ -17,7 +17,7 @@ def make_rgb_axes(ax, pad=0.01, axes_class=None, **kwargs): axes_class : `matplotlib.axes.Axes` or None, optional Axes class to use for the R, G, and B Axes. If None, use the same class as *ax*. - **kwargs : + **kwargs Forwarded to *axes_class* init for the R, G, and B Axes. """ @@ -130,7 +130,7 @@ def imshow_rgb(self, r, g, b, **kwargs): ---------- r, g, b : array-like The red, green, and blue arrays. - **kwargs : + **kwargs Forwarded to `~.Axes.imshow` calls for the four images. Returns diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index cafd06adba2e..2a2b5957e844 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -105,7 +105,7 @@ def get_aux_axes( axes_class : subclass type of `~matplotlib.axes.Axes`, optional The `~.axes.Axes` subclass that is instantiated. If None, the base class of the host axes is used. - kwargs + **kwargs Other parameters are forwarded to the parasite axes constructor. """ if axes_class is None: diff --git a/lib/mpl_toolkits/axisartist/axis_artist.py b/lib/mpl_toolkits/axisartist/axis_artist.py index 74df999ef24e..407ad07a3dc2 100644 --- a/lib/mpl_toolkits/axisartist/axis_artist.py +++ b/lib/mpl_toolkits/axisartist/axis_artist.py @@ -602,11 +602,11 @@ def __init__(self, *args, which="major", axis="both", **kwargs): Parameters ---------- which : {"major", "minor"} - Which grid to consider. + Which grid to consider. axis : {"both", "x", "y"} - Which axis to consider. - *args, **kwargs : - Passed to `.LineCollection`. + Which axis to consider. + *args, **kwargs + Passed to `.LineCollection`. """ self._which = which self._axis = axis diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ac6e841f5019..d60219b74fb9 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -202,7 +202,7 @@ def __init__(self, xs, ys, zs, *args, **kwargs): The y-data to be plotted. zs : array-like The z-data to be plotted. - *args, **kwargs : + *args, **kwargs Additional arguments are passed to `~matplotlib.lines.Line2D`. """ super().__init__([], [], *args, **kwargs) diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 58792deae963..4c5fa8a9c901 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -192,7 +192,7 @@ def set_ticks_position(self, position): Parameters ---------- - str : {'lower', 'upper', 'both', 'default', 'none'} + position : {'lower', 'upper', 'both', 'default', 'none'} The position of the bolded axis lines, ticks, and tick labels. """ if position in ['top', 'bottom']: @@ -221,7 +221,7 @@ def set_label_position(self, position): Parameters ---------- - str : {'lower', 'upper', 'both', 'default', 'none'} + position : {'lower', 'upper', 'both', 'default', 'none'} The position of the axis label. """ if position in ['top', 'bottom']: From d5a54d1eb10dc5cee3702f9f338add8276c5cd3b Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 29 Aug 2023 20:29:50 -0400 Subject: [PATCH 0125/1120] removed faq folder and added general faq link to user guide index, moved install related troubleshooting/faq to install docs new troubleshooting page specific to development consolidated information across install, install for dev, and dependencies Co-authored-by: Elliott Sales de Andrade --- doc/conf.py | 3 +- doc/devel/dependencies.rst | 82 ++- doc/devel/development_setup.rst | 6 + doc/devel/index.rst | 11 + doc/devel/troubleshooting.rst | 45 ++ doc/index.rst | 2 +- doc/missing-references.json | 667 +++++++++--------- doc/users/{faq/howto_faq.rst => faq.rst} | 98 ++- doc/users/faq/index.rst | 21 - doc/users/faq/troubleshooting_faq.rst | 177 ----- doc/users/index.rst | 6 + .../environment_variables_faq.rst | 11 +- doc/users/installing/index.rst | 126 +--- .../installing/troubleshooting_faq.inc.rst | 72 ++ 14 files changed, 675 insertions(+), 652 deletions(-) create mode 100644 doc/devel/troubleshooting.rst rename doc/users/{faq/howto_faq.rst => faq.rst} (75%) delete mode 100644 doc/users/faq/index.rst delete mode 100644 doc/users/faq/troubleshooting_faq.rst rename doc/users/{faq => installing}/environment_variables_faq.rst (95%) create mode 100644 doc/users/installing/troubleshooting_faq.inc.rst diff --git a/doc/conf.py b/doc/conf.py index 21da93ee6f58..95f7ea065f96 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -121,8 +121,7 @@ def _parse_skip_subdirs_file(): ] exclude_patterns = [ - 'api/prev_api_changes/api_changes_*/*' -] + 'api/prev_api_changes/api_changes_*/*', '**/*inc.rst'] exclude_patterns += skip_subdirs diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 3ad6ecc8dcb2..c840436397cc 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -38,7 +38,7 @@ The following packages and tools are not required but extend the capabilities of Matplotlib. Backends -~~~~~~~~ +^^^^^^^^ Matplotlib figures can be rendered to various user interfaces. See :ref:`what-is-a-backend` for more details on the optional Matplotlib backends @@ -73,14 +73,14 @@ and the capabilities they provide. .. _ipykernel: https://pypi.org/project/ipykernel/ Animations -~~~~~~~~~~ +^^^^^^^^^^ * `ffmpeg `_: for saving movies. * `ImageMagick `_: for saving animated gifs. Font handling and rendering -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * `LaTeX `_ (with `cm-super `__ and `underscore @@ -111,7 +111,7 @@ rasterize characters differently) and of Qhull. As an exception, Matplotlib defaults to the system version of FreeType on AIX. Use system libraries -~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^ To force Matplotlib to use a copy of FreeType or Qhull already installed in your system, create a :file:`mplsetup.cfg` file with the following contents: @@ -185,7 +185,7 @@ remember to clear your artifacts before re-building:: Manual Download -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ If the automatic download does not work (for example on air-gapped systems) it @@ -240,17 +240,65 @@ Setup dependencies C++ compiler ------------ -Matplotlib requires a C++ compiler that supports C++11. +Matplotlib requires a C++ compiler that supports C++11, and each platform has a +development environment that must be installed before a compiler can be installed. + +.. tab-set:: + + .. tab-item:: Linux + + On some Linux systems, you can install a meta-build package. For example, + on Ubuntu ``apt install build-essential`` + + Otherwise, use the system distribution's package manager to install + :ref:`gcc `. + + .. tab-item:: macOS + + Install `Xcode `_ for Apple platform development. + + .. tab-item:: Windows + + Install `Visual Studio Build Tools `_ + + Make sure "Desktop development with C++" is selected, and that the latest MSVC, + "C++ CMake tools for Windows," and a Windows SDK compatible with your version + of Windows are selected and installed. They should be selected by default under + the "Optional" subheading, but are required to build Matplotlib from source. + + Alternatively, you can install a Linux-like environment such as `CygWin `_ + or `Windows Subsystem for Linux `_. + + +We highly recommend that you install a compiler using your platform tool, i.e., +Xcode, VS Code or Linux package manager. Choose **one** compiler from this list: + +.. _compiler-table: + +.. list-table:: + :widths: 20 20 20 40 + :header-rows: 1 + + * - compiler + - minimum version + - platforms + - notes + * - GCC + - **4.8.1** + - Linux, macOS, Windows + - `gcc 4.8.1 `_, + `GCC: Binaries `_, + + For gcc <6.5 you will need to set ``$CFLAGS=-std=c++11`` to enable C++11 support. + * - Clang (LLVM) + - **3.3** + - Linux, macOS + - `clang 3.3 `_, `LLVM `_ + * - MSVC++ + - **14.0** + - Windows + - `Visual Studio 2015 C++ `_ -- `gcc 4.8.1 `_ or higher. For gcc <6.5 you will - need to set ``$CFLAGS=-std=c++11`` to enable C++11 support. - `Installing GCC: Binaries `_. -- `clang 3.3 `_ or higher. - `LLVM Download Page `_. -- `Visual Studio 2015 - `_ - (aka VS 14.0) or higher. A free version of Build Tools for Visual Studio is available for - `download `_. .. _test-dependencies: @@ -286,8 +334,8 @@ testing the following will be used if they are installed. fonts for testing font fallback and non-western fonts - xarray_ used to test compatibility with xarray -If any of these dependencies are not discovered the tests that rely on them -will be skipped by pytest. +If any of these dependencies are not discovered, then the tests that rely on +them will be skipped by pytest. .. note:: diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 4d277fca2ab4..8537e1229df4 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -148,6 +148,12 @@ The simplest way to do this is to use either Python's virtual environment Remember to activate the environment whenever you start working on Matplotlib. +Install Dependencies +==================== +Most Python dependencies will be installed when :ref:`setting up the environment ` +but non-Python dependencies like C++ compilers, LaTeX, and other system applications +must be installed separately. For a full list, see :ref:`dependencies`. + Install Matplotlib in editable mode =================================== diff --git a/doc/devel/index.rst b/doc/devel/index.rst index a49290dd8994..2b358595255b 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -78,6 +78,7 @@ Development environment .. grid:: 1 1 2 2 .. grid-item-card:: + :shadow: none **Install** ^^^ @@ -91,9 +92,11 @@ Development environment :maxdepth: 1 dependencies + ../users/installing/environment_variables_faq.rst .. grid-item-card:: + :shadow: none **Workflow** ^^^^ @@ -103,6 +106,10 @@ Development environment development_workflow + .. toctree:: + :maxdepth: 1 + + troubleshooting.rst .. _contribution_guideline: @@ -115,6 +122,7 @@ Policies and guidelines :gutter: 2 .. grid-item-card:: + :shadow: none **Code** ^^^ @@ -128,6 +136,7 @@ Policies and guidelines testing .. grid-item-card:: + :shadow: none **Documentation** ^^^ @@ -139,6 +148,7 @@ Policies and guidelines style_guide .. grid-item-card:: + :shadow: none **Triage** ^^^ @@ -148,6 +158,7 @@ Policies and guidelines | :ref:`triage_workflow` .. grid-item-card:: + :shadow: none **Maintenance** ^^^ diff --git a/doc/devel/troubleshooting.rst b/doc/devel/troubleshooting.rst new file mode 100644 index 000000000000..77c1b242399b --- /dev/null +++ b/doc/devel/troubleshooting.rst @@ -0,0 +1,45 @@ +.. _troubleshooting-faq: + +.. redirect-from:: /faq/troubleshooting_faq +.. redirect-from:: /users/faq/troubleshooting_faq + +=============== +Troubleshooting +=============== + +For guidance on debugging an installation, see :ref:`installing-faq`. + + +.. _git-trouble: + +Problems with git +================= + +First, make sure you have a clean build and install (see :ref:`clean-install`), +get the latest git update, install it and run a simple test script in debug +mode:: + + rm -rf /path/to/site-packages/matplotlib* + git clean -xfd + git pull + python -m pip install -v . > build.out + python -c "from pylab import *; set_loglevel('debug'); plot(); show()" > run.out + +and post :file:`build.out` and :file:`run.out` to the `matplotlib-devel +`_ +mailing list (please do not post git problems to the `users list +`_). + +Of course, you will want to clearly describe your problem, what you +are expecting and what you are getting, but often a clean build and +install will help. See also :ref:`reporting-problems`. + +Unlink of file ``*/_c_internal_utils.cp311-win_amd64.pyd`` failed +============================================================================ + +The DLL files may be loaded by multiple running instances of Matplotlib; therefore +check that Matplotlib is not running in any other application before trying to +unlink this file. Multiple versions of Matplotlib can be linked to the same DLL, +for example a development version installed in a development conda environment +and a stable version running in a Jupyter notebook. To resolve this error, fully +close all running instances of Matplotlib. diff --git a/doc/index.rst b/doc/index.rst index 8193c94aede9..4f42273d3dcc 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -56,7 +56,7 @@ Learn users/explain/quick_start User guide tutorials/index.rst - users/faq/index.rst + users/faq.rst .. grid-item-card:: :padding: 2 diff --git a/doc/missing-references.json b/doc/missing-references.json index 364885ab3a74..72de214df985 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -1,101 +1,126 @@ { "py:attr": { "cbar_axes": [ - "lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:41", - "lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:41" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:72", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:72" ], "eventson": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.CheckButtons.set_active:4", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.RadioButtons.set_active:4" ], "fmt_zdata": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.format_zdata:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.format_zdata:2" ], "height": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" ], "input_dims": [ - "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes:1", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:10", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:11", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:4" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform:8", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:8", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:14" ], "lines": [ - "lib/matplotlib/colorbar.py:docstring of matplotlib.colorbar:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\colorbar.py:docstring of matplotlib.colorbar.Colorbar.add_lines:4" ], "matplotlib.axes.Axes.patch": [ - "doc/tutorials/artists.rst:177", - "doc/tutorials/artists.rst:405" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:188", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:427" ], "matplotlib.axes.Axes.patches": [ - "doc/tutorials/artists.rst:443" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:465" ], "matplotlib.axes.Axes.transAxes": [ - "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows:4" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows:8" ], "matplotlib.axes.Axes.transData": [ - "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox:7", - "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse:4", - "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar:4" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox:11", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse:33", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar:8" ], "matplotlib.axes.Axes.xaxis": [ - "doc/tutorials/artists.rst:589", - "doc/users/explain/axes/index.rst:133" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:611", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/axes/axes_intro.rst:133" ], "matplotlib.axes.Axes.yaxis": [ - "doc/tutorials/artists.rst:589", - "doc/users/explain/axes/index.rst:133" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:611", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/axes/axes_intro.rst:133" ], "matplotlib.axis.Axis.label": [ - "doc/tutorials/artists.rst:636" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:658" ], "matplotlib.colors.Colormap.name": [ - "lib/matplotlib/cm.py:docstring of matplotlib.cm:10" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\cm.py:docstring of matplotlib.cm.register_cmap:14" ], "matplotlib.figure.Figure.patch": [ - "doc/tutorials/artists.rst:177", - "doc/tutorials/artists.rst:310" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:188", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:321" ], "matplotlib.figure.Figure.transFigure": [ - "doc/tutorials/artists.rst:359" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:370" ], "max": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.p1:4" ], "min": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.p0:4" ], "mpl_toolkits.mplot3d.axis3d._axinfo": [ - "doc/api/toolkits/mplot3d.rst:66" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/toolkits/mplot3d.rst:66" ], "name": [ - "lib/matplotlib/scale.py:docstring of matplotlib.scale:7" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\scale.py:docstring of matplotlib.scale.ScaleBase:8" ], "output_dims": [ - "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes:6", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:10", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:16", - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:17" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:21", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:21", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:20", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:14", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:21", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:20" ], "triangulation": [ - "lib/matplotlib/tri/_trirefine.py:docstring of matplotlib.tri._trirefine.UniformTriRefiner:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\tri\\_trirefine.py:docstring of matplotlib.tri._trirefine.UniformTriRefiner.refine_triangulation:2" ], "use_sticky_edges": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.margins:48" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.margins:53" ], "width": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" ], "xmax": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.x1:4" ], "xmin": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.x0:4" ], "ymax": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.y1:4" ], "ymin": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.y0:4" ] }, "py:class": { @@ -168,538 +193,539 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.twiny:1" ], "matplotlib.axes._base._AxesBase": [ - "doc/api/artist_api.rst:202" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202" ], "matplotlib.backend_bases.FigureCanvas": [ - "doc/tutorials/artists.rst:36", - "doc/tutorials/artists.rst:38", - "doc/tutorials/artists.rst:43" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:36", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:38", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:43" ], "matplotlib.backend_bases.Renderer": [ - "doc/tutorials/artists.rst:38", - "doc/tutorials/artists.rst:43" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:38", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:43" ], "matplotlib.backend_bases._Backend": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ShowBase:1" ], "matplotlib.backends._backend_pdf_ps.RendererPDFPSBase": [ - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf:1", - "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS:1" ], "matplotlib.backends._backend_tk.FigureCanvasTk": [ - "lib/matplotlib/backends/backend_tkagg.py:docstring of matplotlib.backends.backend_tkagg:1", - "lib/matplotlib/backends/backend_tkcairo.py:docstring of matplotlib.backends.backend_tkcairo:1" - ], - "matplotlib.backends.backend_webagg_core.FigureCanvasWebAggCore": [ - "lib/matplotlib/backends/backend_nbagg.py:docstring of matplotlib.backends.backend_nbagg:1", - "lib/matplotlib/backends/backend_webagg.py:docstring of matplotlib.backends.backend_webagg:1" - ], - "matplotlib.backends.backend_webagg_core.FigureManagerWebAgg": [ - "lib/matplotlib/backends/backend_nbagg.py:docstring of matplotlib.backends.backend_nbagg:1", - "lib/matplotlib/backends/backend_webagg.py:docstring of matplotlib.backends.backend_webagg:1" - ], - "matplotlib.backends.backend_webagg_core.NavigationToolbar2WebAgg": [ - "lib/matplotlib/backends/backend_nbagg.py:docstring of matplotlib.backends.backend_nbagg:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_tkagg.py:docstring of matplotlib.backends.backend_tkagg.FigureCanvasTkAgg:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_tkcairo.py:docstring of matplotlib.backends.backend_tkcairo.FigureCanvasTkCairo:1" ], "matplotlib.collections._CollectionWithSizes": [ - "doc/api/artist_api.rst:202", - "doc/api/collections_api.rst:13", - "lib/matplotlib/collections.py:docstring of matplotlib.collections:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/collections_api.rst:13", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.CircleCollection:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PathCollection:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PolyCollection:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.RegularPolyCollection:1" ], "matplotlib.collections._MeshData": [ - "doc/api/artist_api.rst:202", - "doc/api/collections_api.rst:13", - "lib/matplotlib/collections.py:docstring of matplotlib.collections:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/collections_api.rst:13", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PolyQuadMesh:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.QuadMesh:1" ], "matplotlib.image._ImageBase": [ - "doc/api/artist_api.rst:202", - "lib/matplotlib/image.py:docstring of matplotlib.image:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.AxesImage:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.BboxImage:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.FigureImage:1" ], "matplotlib.patches.ArrowStyle._Base": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Fancy:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Simple:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Wedge:1" ], "matplotlib.patches.ArrowStyle._Curve": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BarAB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketA:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketAB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketCurve:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Curve:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveA:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveAB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveBracket:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledA:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledAB:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledB:1" ], "matplotlib.patches.ConnectionStyle._Base": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle3:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc3:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Bar:1" ], "matplotlib.patches._Style": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle:1", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle:1", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle:1", - "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.BoxStyle:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" ], "matplotlib.projections.geo._GeoTransform": [ - "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform:1" ], "matplotlib.text._AnnotationBase": [ - "doc/api/artist_api.rst:202", - "lib/matplotlib/offsetbox.py:docstring of matplotlib.offsetbox:1", - "lib/matplotlib/text.py:docstring of matplotlib.text.Annotation:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\offsetbox.py:docstring of matplotlib.offsetbox.AnnotationBbox:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\text.py:docstring of matplotlib.text.Annotation:1" ], "matplotlib.transforms._BlendedMixin": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.BlendedAffine2D:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.BlendedGenericTransform:1" ], "matplotlib.widgets._SelectorWidget": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.LassoSelector:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.PolygonSelector:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.RectangleSelector:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.SpanSelector:1" ], "mpl_toolkits.axes_grid1.axes_size._Base": [ - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Add:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesX:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesY:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fixed:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fraction:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.MaxExtent:1", - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Scaled:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Add:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesX:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesY:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fixed:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fraction:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.MaxExtent:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Scaled:1" ], "mpl_toolkits.axes_grid1.parasite_axes.AxesHostAxes": [ - "doc/api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1", - "lib/mpl_toolkits/axes_grid1/parasite_axes.py:docstring of mpl_toolkits.axes_grid1.parasite_axes.AxesHostAxes:1" + ":1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1" ], "mpl_toolkits.axes_grid1.parasite_axes.AxesParasite": [ - "doc/api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1", - "lib/mpl_toolkits/axes_grid1/parasite_axes.py:docstring of mpl_toolkits.axes_grid1.parasite_axes.AxesParasite:1" + ":1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1" ], "mpl_toolkits.axisartist.Axes": [ - "doc/api/toolkits/axisartist.rst:6" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/toolkits/axisartist.rst:6" ], "mpl_toolkits.axisartist.axisline_style.AxislineStyle._Base": [ - "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle.SimpleArrow:1" ], "mpl_toolkits.axisartist.axisline_style._FancyAxislineStyle.FilledArrow": [ - "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" + ":1" ], "mpl_toolkits.axisartist.axisline_style._FancyAxislineStyle.SimpleArrow": [ - "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" + ":1" ], "mpl_toolkits.axisartist.axislines._FixedAxisArtistHelperBase": [ - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper:1", - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.FixedAxisArtistHelperRectilinear:1", - "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FixedAxisArtistHelper:1" + ":1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axislines.py:docstring of mpl_toolkits.axisartist.axislines.FixedAxisArtistHelperRectilinear:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FixedAxisArtistHelper:1" ], "mpl_toolkits.axisartist.axislines._FloatingAxisArtistHelperBase": [ - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper:1", - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.FloatingAxisArtistHelperRectilinear:1", - "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FloatingAxisArtistHelper:1" + ":1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axislines.py:docstring of mpl_toolkits.axisartist.axislines.FloatingAxisArtistHelperRectilinear:1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FloatingAxisArtistHelper:1" ], "mpl_toolkits.axisartist.floating_axes.FloatingAxesHostAxes": [ - "doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1", - "lib/mpl_toolkits/axisartist/floating_axes.py:docstring of mpl_toolkits.axisartist.floating_axes.FloatingAxesHostAxes:1" - ], - "np.ndarray": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.acorr:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.angle_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.cohere:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.csd:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist2d:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.imread:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.magnitude_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.phase_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.psd:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.specgram:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.xcorr:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.xticks:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.yticks:1" + ":1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1" ], "numpy.uint8": [ - "lib/matplotlib/path.py:docstring of matplotlib.path:1" + ":1" ] }, "py:data": { "matplotlib.axes.Axes.transAxes": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:238", - "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.add_artist:1", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:105", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:242", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:238" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:248", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:249", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:201", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:249", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:248" ] }, "py:meth": { "AbstractPathEffect._update_gc": [ - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:26", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:28", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:35", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:40", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:41" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.SimpleLineShadow:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.SimplePatchShadow:42", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.TickedStroke:57", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.withSimplePatchShadow:51", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.withTickedStroke:56" ], "IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook": [ - "doc/users/explain/figure/interactive_guide.rst:420" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:420" ], "_find_tails": [ - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:5" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:9" ], "_make_barbs": [ - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:5" - ], - "get_matrix": [ - "lib/matplotlib/transforms.py:docstring of matplotlib.transforms:12" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:9" ], "matplotlib.collections._CollectionWithSizes.set_sizes": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:171", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:77", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:113", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:113", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:201", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:173", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:207", - "lib/matplotlib/collections.py:docstring of matplotlib.collections.AsteriskPolygonCollection:22", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.barbs:171", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.broken_barh:77", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_between:113", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:113", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hexbin:201", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolor:173", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.quiver:207", - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:205", - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:38", - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver:244", - "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver:38", - "lib/mpl_toolkits/mplot3d/art3d.py:docstring of mpl_toolkits.mplot3d.art3d.Path3DCollection:39", - "lib/mpl_toolkits/mplot3d/art3d.py:docstring of mpl_toolkits.mplot3d.art3d.Poly3DCollection:37" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:176", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:82", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:118", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:118", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:206", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:178", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:212", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.AsteriskPolygonCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.BrokenBarHCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.CircleCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PathCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.RegularPolyCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.StarPolygonCollection.set:44", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.barbs:176", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.broken_barh:82", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.fill_between:118", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:118", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.hexbin:206", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.pcolor:178", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.quiver:212", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.artist.Barbs.set:45", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.artist.Quiver.set:45", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:209", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Quiver:248", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\art3d.py:docstring of matplotlib.artist.Path3DCollection.set:46", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\art3d.py:docstring of matplotlib.artist.Poly3DCollection.set:44" ], "matplotlib.collections._MeshData.set_array": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolormesh:155", - "lib/matplotlib/collections.py:docstring of matplotlib.collections.AsteriskPolygonCollection:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolormesh:155" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.pcolormesh:160", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:17", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.QuadMesh.set:17", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.pcolormesh:160" ] }, "py:obj": { "Artist.stale_callback": [ - "doc/users/explain/figure/interactive_guide.rst:323" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:323" ], "Artist.sticky_edges": [ - "doc/api/axes_api.rst:354::1", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.use_sticky_edges:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:356::1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes.Axes.use_sticky_edges:2" ], "Axes.dataLim": [ - "doc/api/axes_api.rst:293::1", - "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.update_datalim:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:293::1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.update_datalim:2" ], "AxesBase": [ - "doc/api/axes_api.rst:446::1", - "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.add_child_axes:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:448::1", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.add_child_axes:2" ], "Figure.stale_callback": [ - "doc/users/explain/figure/interactive_guide.rst:333" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:333" ], "Glyph": [ - "doc/gallery/misc/ftface_props.rst:28" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\gallery/misc/ftface_props.rst:28" ], "Image": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.gci:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.gci:4" ], "ImageComparisonFailure": [ - "lib/matplotlib/testing/decorators.py:docstring of matplotlib.testing.decorators:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\testing\\decorators.py:docstring of matplotlib.testing.decorators.image_comparison:2" ], "Line2D.pick": [ - "doc/users/explain/figure/event_handling.rst:568" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/event_handling.rst:568" ], "QuadContourSet.changed()": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.contour:147", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.contourf:147", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.contour:147", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.contourf:147" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.contour:152", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.contourf:152", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.contour:152", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.contourf:152" ], "Rectangle.contains": [ - "doc/users/explain/figure/event_handling.rst:280" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/event_handling.rst:280" ], "Size.from_any": [ - "lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:53", - "lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:53" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:84", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:84" ], "Timer": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:1", - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:13" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.new_timer:2", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:14" ], "ToolContainer": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:1", - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:19" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:2", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase:20" ], "_iter_collection": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:11", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.FigureCanvasPdf:1", - "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.FigureCanvasPS:1", - "lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.FigureCanvasSVG:1", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" ], "_iter_collection_raw_paths": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:11", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.FigureCanvasPdf:1", - "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.FigureCanvasPS:1", - "lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.FigureCanvasSVG:1", - "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.AbstractPathEffect:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" ], "_read": [ - "lib/matplotlib/dviread.py:docstring of matplotlib.dviread:19" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\dviread.py:docstring of matplotlib.dviread.Vf:20" ], "active": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.AxesWidget:15" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.AxesWidget:34" ], "ax.transAxes": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.indicate_inset:14", - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.inset_axes:6" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.indicate_inset:19", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.inset_axes:11" ], "axes.bbox": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:136", - "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure:69", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:3", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:140", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:136" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:145", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:97", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:145", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:144" ], "can_composite": [ - "lib/matplotlib/image.py:docstring of matplotlib.image:5" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.composite_images:9" ], "converter": [ - "lib/matplotlib/testing/compare.py:docstring of matplotlib.testing.compare:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\testing\\compare.py:docstring of matplotlib.testing.compare.compare_images:4" ], "draw_image": [ - "lib/matplotlib/backends/backend_agg.py:docstring of matplotlib.backends.backend_agg:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.option_scale_image:2" ], "figure.bbox": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:136", - "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure:69", - "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:3", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:140", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:136" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:145", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:97", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:145", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:144" ], "fmt_xdata": [ - "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.format_xdata:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.format_xdata:4" ], "fmt_ydata": [ - "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.format_ydata:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.format_ydata:4" ], "get_size": [ - "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size:1" - ], - "get_xbound": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_xlim:17" - ], - "get_ybound": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_ylim:17" - ], - "invert_xaxis": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_xlim:19" - ], - "invert_yaxis": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_ylim:19" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size:1" ], "ipykernel.pylab.backend_inline": [ - "doc/users/explain/figure/interactive.rst:264" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive.rst:340" ], "kde.covariance_factor": [ - "lib/matplotlib/mlab.py:docstring of matplotlib.mlab:40" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:41" ], "kde.factor": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.violinplot:41", - "lib/matplotlib/mlab.py:docstring of matplotlib.mlab:11", - "lib/matplotlib/mlab.py:docstring of matplotlib.mlab:44", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.violinplot:41" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.violinplot:46", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:12", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:45", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.violinplot:46" ], "make_image": [ - "lib/matplotlib/image.py:docstring of matplotlib.image:5" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.composite_images:9" ], "matplotlib.animation.ArtistAnimation.new_frame_seq": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.new_saved_frame_seq": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.pause": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.repeat": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:33::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:33::1" ], "matplotlib.animation.ArtistAnimation.resume": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.save": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.to_html5_video": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.to_jshtml": [ - "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.FFMpegFileWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.finish": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.frame_format": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.grab_frame": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.isAvailable": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.output_args": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.setup": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.finish": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.frame_size": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FFMpegWriter.grab_frame": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.isAvailable": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.output_args": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FFMpegWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.setup": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.supported_formats": [ - "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FileMovieWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" ], "matplotlib.animation.FileMovieWriter.isAvailable": [ - "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.supported_formats": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" ], "matplotlib.animation.FuncAnimation.pause": [ - "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.repeat": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.FuncAnimation.new_frame_seq:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FuncAnimation.new_frame_seq:1::1" ], "matplotlib.animation.FuncAnimation.resume": [ - "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.save": [ - "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.to_html5_video": [ - "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.to_jshtml": [ - "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.HTMLWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" ], "matplotlib.animation.HTMLWriter.frame_format": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" ], "matplotlib.animation.HTMLWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" ], "matplotlib.animation.HTMLWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.finish": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.frame_format": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickFileWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickFileWriter.grab_frame": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.isAvailable": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.setup": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.bin_path": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.finish": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickWriter.grab_frame": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.isAvailable": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.setup": [ - "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.supported_formats": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" ], "matplotlib.animation.MovieWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.MovieWriter.bin_path:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.MovieWriter.bin_path:1::1" ], "matplotlib.animation.MovieWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.MovieWriter.rst:27::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.MovieWriter.rst:27::1" ], "matplotlib.animation.PillowWriter.frame_size": [ - "lib/matplotlib/animation.py:docstring of matplotlib.animation.PillowWriter.finish:1::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.PillowWriter.finish:1::1" ], "matplotlib.animation.PillowWriter.saving": [ - "doc/api/_as_gen/matplotlib.animation.PillowWriter.rst:26::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.PillowWriter.rst:26::1" ], "matplotlib.animation.TimedAnimation.new_frame_seq": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.new_saved_frame_seq": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.pause": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.resume": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.save": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.to_html5_video": [ - "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.to_jshtml": [ "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" @@ -708,40 +734,31 @@ "doc/docstring of builtins.list:17" ], "mpl_toolkits.axislines.Axes": [ - "lib/mpl_toolkits/axisartist/axis_artist.py:docstring of mpl_toolkits.axisartist.axis_artist:7" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axis_artist.py:docstring of mpl_toolkits.axisartist.axis_artist:7" ], "next_whats_new": [ - "doc/users/next_whats_new/README.rst:6" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/next_whats_new/README.rst:6" ], "option_scale_image": [ - "lib/matplotlib/backends/backend_cairo.py:docstring of matplotlib.backends.backend_cairo.FigureCanvasCairo:1", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.FigureCanvasPdf:1", - "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.FigureCanvasPS:2", - "lib/matplotlib/backends/backend_template.py:docstring of matplotlib.backends.backend_template:18" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_cairo.py:docstring of matplotlib.backends.backend_cairo.RendererCairo.draw_image:22", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_image:22", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_image:22", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_template.py:docstring of matplotlib.backends.backend_template.RendererTemplate.draw_image:22" ], "print_xyz": [ - "lib/matplotlib/backends/backend_template.py:docstring of matplotlib.backends.backend_template:22" - ], - "set_xbound": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_xlim:17" - ], - "set_ybound": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_ylim:17" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_template.py:docstring of matplotlib.backends.backend_template:22" ], "toggled": [ - "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools:1" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.disable:4", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.enable:4", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.trigger:2", + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.ZoomPanBase.trigger:2" ], "tool_removed_event": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases:2" + "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:6" ], "whats_new.rst": [ - "doc/users/next_whats_new/README.rst:6" - ], - "xaxis_inverted": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_xlim:19" - ], - "yaxis_inverted": [ - "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_ylim:19" + "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/next_whats_new/README.rst:6" ] } } diff --git a/doc/users/faq/howto_faq.rst b/doc/users/faq.rst similarity index 75% rename from doc/users/faq/howto_faq.rst rename to doc/users/faq.rst index f43494ff4f12..46084ec4c6c6 100644 --- a/doc/users/faq/howto_faq.rst +++ b/doc/users/faq.rst @@ -1,14 +1,12 @@ .. _howto-faq: .. redirect-from:: /faq/howto_faq +.. redirect-from:: /users/faq/howto_faq +.. redirect-from:: /faq/index -****** -How-to -****** - -.. contents:: - :backlinks: none - +========================== +Frequently Asked Questions +========================== .. _how-to-too-many-ticks: @@ -199,14 +197,8 @@ different scales, you can often get ylabels that do not align vertically across the multiple subplots, which can be unattractive. By default, Matplotlib positions the x location of the ylabel so that it does not overlap any of the y ticks. You can override this default -behavior by specifying the coordinates of the label. The example -below shows the default behavior in the left subplots, and the manual -setting in the right subplots. - -.. figure:: ../../gallery/text_labels_and_annotations/images/sphx_glr_align_ylabels_001.png - :target: ../../gallery/text_labels_and_annotations/align_ylabels.html - :align: center - :scale: 50 +behavior by specifying the coordinates of the label. To learn how, see +:doc:`/gallery/text_labels_and_annotations/align_ylabels` .. _howto-set-zorder: @@ -307,3 +299,79 @@ artists. You may be able to work on separate figures from separate threads. However, you must in that case use a *non-interactive backend* (typically Agg), because most GUI backends *require* being run from the main thread as well. + +.. _reporting-problems: + +Get help +-------- + +There are a number of good resources for getting help with Matplotlib. +There is a good chance your question has already been asked: + +- The `mailing list archive + `_. + +- `GitHub issues `_. + +- Stackoverflow questions tagged `matplotlib + `_. + +If you are unable to find an answer to your question through search, please +provide the following information in your e-mail to the `mailing list +`_: + +* Your operating system (Linux/Unix users: post the output of ``uname -a``). + +* Matplotlib version:: + + python -c "import matplotlib; print(matplotlib.__version__)" + +* Where you obtained Matplotlib (e.g., your Linux distribution's packages, + GitHub, PyPI, or `Anaconda `_). + +* Any customizations to your ``matplotlibrc`` file (see + :ref:`customizing`). + +* If the problem is reproducible, please try to provide a *minimal*, standalone + Python script that demonstrates the problem. This is *the* critical step. + If you can't post a piece of code that we can run and reproduce your error, + the chances of getting help are significantly diminished. Very often, the + mere act of trying to minimize your code to the smallest bit that produces + the error will help you find a bug in *your* code that is causing the + problem. + +* Matplotlib provides debugging information through the `logging` library, and + a helper function to set the logging level: one can call :: + + plt.set_loglevel("info") # or "debug" for more info + + to obtain this debugging information. + + Standard functions from the `logging` module are also applicable; e.g. one + could call ``logging.basicConfig(level="DEBUG")`` even before importing + Matplotlib (this is in particular necessary to get the logging info emitted + during Matplotlib's import), or attach a custom handler to the "matplotlib" + logger. This may be useful if you use a custom logging configuration. + +If you compiled Matplotlib yourself, please also provide: + +* any changes you have made to ``setup.py`` or ``setupext.py``. +* the output of:: + + rm -rf build + python setup.py build + + The beginning of the build output contains lots of details about your + platform that are useful for the Matplotlib developers to diagnose your + problem. + +* your compiler version -- e.g., ``gcc --version``. + +Including this information in your first e-mail to the mailing list +will save a lot of time. + +You will likely get a faster response writing to the mailing list than +filing a bug in the bug tracker. Most developers check the bug +tracker only periodically. If your problem has been determined to be +a bug and cannot be quickly solved, you may be asked to file a bug in +the tracker so the issue doesn't get lost. diff --git a/doc/users/faq/index.rst b/doc/users/faq/index.rst deleted file mode 100644 index 636c90904aba..000000000000 --- a/doc/users/faq/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. _faq-index: - -.. redirect-from:: /faq/index - -########################## -How-to and troubleshooting -########################## - -.. only:: html - - :Release: |version| - :Date: |today| - - Frequently asked questions about Matplotlib: - -.. toctree:: - :maxdepth: 2 - - howto_faq.rst - troubleshooting_faq.rst - environment_variables_faq.rst diff --git a/doc/users/faq/troubleshooting_faq.rst b/doc/users/faq/troubleshooting_faq.rst deleted file mode 100644 index 851e18d1664d..000000000000 --- a/doc/users/faq/troubleshooting_faq.rst +++ /dev/null @@ -1,177 +0,0 @@ -.. _troubleshooting-faq: - -.. redirect-from:: /faq/troubleshooting_faq - -*************** -Troubleshooting -*************** - -.. contents:: - :backlinks: none - -.. _matplotlib-version: - -Obtaining Matplotlib version -============================ - -To find out your Matplotlib version number, import it and print the -``__version__`` attribute:: - - >>> import matplotlib - >>> matplotlib.__version__ - '0.98.0' - - -.. _locating-matplotlib-install: - -:file:`matplotlib` install location -=================================== - -You can find what directory Matplotlib is installed in by importing it -and printing the ``__file__`` attribute:: - - >>> import matplotlib - >>> matplotlib.__file__ - '/home/jdhunter/dev/lib64/python2.5/site-packages/matplotlib/__init__.pyc' - -.. _locating-matplotlib-config-dir: - -:file:`matplotlib` configuration and cache directory locations -============================================================== - -Each user has a Matplotlib configuration directory which may contain a -:ref:`matplotlibrc ` file. To -locate your :file:`matplotlib/` configuration directory, use -:func:`matplotlib.get_configdir`:: - - >>> import matplotlib as mpl - >>> mpl.get_configdir() - '/home/darren/.config/matplotlib' - -On Unix-like systems, this directory is generally located in your -:envvar:`HOME` directory under the :file:`.config/` directory. - -In addition, users have a cache directory. On Unix-like systems, this is -separate from the configuration directory by default. To locate your -:file:`.cache/` directory, use :func:`matplotlib.get_cachedir`:: - - >>> import matplotlib as mpl - >>> mpl.get_cachedir() - '/home/darren/.cache/matplotlib' - -On Windows, both the config directory and the cache directory are -the same and are in your :file:`Documents and Settings` or :file:`Users` -directory by default:: - - >>> import matplotlib as mpl - >>> mpl.get_configdir() - 'C:\\Documents and Settings\\jdhunter\\.matplotlib' - >>> mpl.get_cachedir() - 'C:\\Documents and Settings\\jdhunter\\.matplotlib' - -If you would like to use a different configuration directory, you can -do so by specifying the location in your :envvar:`MPLCONFIGDIR` -environment variable -- see -:ref:`setting-linux-osx-environment-variables`. Note that -:envvar:`MPLCONFIGDIR` sets the location of both the configuration -directory and the cache directory. - -.. _reporting-problems: - -Getting help -============ - -There are a number of good resources for getting help with Matplotlib. -There is a good chance your question has already been asked: - -- The `mailing list archive - `_. - -- `GitHub issues `_. - -- Stackoverflow questions tagged `matplotlib - `_. - -If you are unable to find an answer to your question through search, please -provide the following information in your e-mail to the `mailing list -`_: - -* Your operating system (Linux/Unix users: post the output of ``uname -a``). - -* Matplotlib version:: - - python -c "import matplotlib; print(matplotlib.__version__)" - -* Where you obtained Matplotlib (e.g., your Linux distribution's packages, - GitHub, PyPI, or `Anaconda `_). - -* Any customizations to your ``matplotlibrc`` file (see - :ref:`customizing`). - -* If the problem is reproducible, please try to provide a *minimal*, standalone - Python script that demonstrates the problem. This is *the* critical step. - If you can't post a piece of code that we can run and reproduce your error, - the chances of getting help are significantly diminished. Very often, the - mere act of trying to minimize your code to the smallest bit that produces - the error will help you find a bug in *your* code that is causing the - problem. - -* Matplotlib provides debugging information through the `logging` library, and - a helper function to set the logging level: one can call :: - - plt.set_loglevel("info") # or "debug" for more info - - to obtain this debugging information. - - Standard functions from the `logging` module are also applicable; e.g. one - could call ``logging.basicConfig(level="DEBUG")`` even before importing - Matplotlib (this is in particular necessary to get the logging info emitted - during Matplotlib's import), or attach a custom handler to the "matplotlib" - logger. This may be useful if you use a custom logging configuration. - -If you compiled Matplotlib yourself, please also provide: - -* any changes you have made to ``setup.py`` or ``setupext.py``. -* the output of:: - - rm -rf build - python setup.py build - - The beginning of the build output contains lots of details about your - platform that are useful for the Matplotlib developers to diagnose your - problem. - -* your compiler version -- e.g., ``gcc --version``. - -Including this information in your first e-mail to the mailing list -will save a lot of time. - -You will likely get a faster response writing to the mailing list than -filing a bug in the bug tracker. Most developers check the bug -tracker only periodically. If your problem has been determined to be -a bug and cannot be quickly solved, you may be asked to file a bug in -the tracker so the issue doesn't get lost. - -.. _git-trouble: - -Problems with recent git versions -================================= - -First, make sure you have a clean build and install (see :ref:`clean-install`), -get the latest git update, install it and run a simple test script in debug -mode:: - - rm -rf /path/to/site-packages/matplotlib* - git clean -xdf - git pull - python -m pip install -v . > build.out - python -c "from pylab import *; set_loglevel('debug'); plot(); show()" > run.out - -and post :file:`build.out` and :file:`run.out` to the `matplotlib-devel -`_ -mailing list (please do not post git problems to the `users list -`_). - -Of course, you will want to clearly describe your problem, what you -are expecting and what you are getting, but often a clean build and -install will help. See also :ref:`reporting-problems`. diff --git a/doc/users/index.rst b/doc/users/index.rst index 8081b353aeb5..64317fd61607 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -19,6 +19,11 @@ Using Matplotlib explain/quick_start + .. toctree:: + :maxdepth: 1 + + faq.rst + .. grid-item-card:: :padding: 2 @@ -92,6 +97,7 @@ Using Matplotlib explain/toolkits/index + .. toctree:: :hidden: diff --git a/doc/users/faq/environment_variables_faq.rst b/doc/users/installing/environment_variables_faq.rst similarity index 95% rename from doc/users/faq/environment_variables_faq.rst rename to doc/users/installing/environment_variables_faq.rst index fb9341db1147..e7a721026743 100644 --- a/doc/users/faq/environment_variables_faq.rst +++ b/doc/users/installing/environment_variables_faq.rst @@ -1,13 +1,12 @@ .. _environment-variables: -.. redirect-from:: /faq/environment_variables_faq -********************* -Environment variables -********************* +.. redirect-from:: /faq/installing_faq +.. redirect-from:: /users/faq/installing_faq -.. contents:: - :backlinks: none +===================== +Environment variables +===================== .. envvar:: HOME diff --git a/doc/users/installing/index.rst b/doc/users/installing/index.rst index 0a95d2088ce4..c8c9ba549775 100644 --- a/doc/users/installing/index.rst +++ b/doc/users/installing/index.rst @@ -1,12 +1,12 @@ .. redirect-from:: /users/installing -############ +============ Installation -############ +============ -============================== -Installing an official release -============================== + +Install an official release +=========================== Matplotlib releases are available as wheel packages for macOS, Windows and Linux on `PyPI `_. Install it using @@ -33,14 +33,15 @@ precompiled wheel for your OS and Python. animations and a larger selection of file formats, you can install :ref:`optional_dependencies`. -========================= + Third-party distributions ========================= Various third-parties provide Matplotlib for their environments. Conda packages -============== +-------------- + Matplotlib is available both via the *anaconda main channel* .. code-block:: sh @@ -54,7 +55,7 @@ as well as via the *conda-forge community channel* conda install -c conda-forge matplotlib Python distributions -==================== +-------------------- Matplotlib is part of major Python distributions: @@ -66,7 +67,7 @@ Matplotlib is part of major Python distributions: - `WinPython `_ Linux package manager -===================== +--------------------- If you are using the Python version that comes with your Linux distribution, you can install Matplotlib via your package manager, e.g.: @@ -80,9 +81,8 @@ you can install Matplotlib via your package manager, e.g.: .. _install_from_source: -========================== -Installing a nightly build -========================== +Install a nightly build +======================= Matplotlib makes nightly development build wheels available on the `scientific-python-nightly-wheels Anaconda Cloud organization @@ -99,82 +99,30 @@ scientific-python-nightly-wheels as the package index to query: --extra-index-url https://pypi.org/simple \ matplotlib -====================== -Installing from source -====================== - -If you are interested in contributing to Matplotlib development, -running the latest source code, or just like to build everything -yourself, it is not difficult to build Matplotlib from source. - -First you need to install the :ref:`dependencies`. - -A C compiler is required. Typically, on Linux, you will need ``gcc``, which -should be installed using your distribution's package manager; on macOS, you -will need xcode_; on Windows, you will need `Visual Studio`_ 2015 or later. - -For those using Visual Studio, make sure "Desktop development with C++" is -selected, and that the latest MSVC, "C++ CMake tools for Windows," and a -Windows SDK compatible with your version of Windows are selected and installed. -They should be selected by default under the "Optional" subheading, but are -required to build matplotlib from source. - -.. _xcode: https://guide.macports.org/chunked/installing.html#installing.xcode - -.. _Visual Studio: https://visualstudio.microsoft.com/downloads/ - -The easiest way to get the latest development version to start contributing -is to go to the git `repository `_ -and run:: - - git clone https://github.com/matplotlib/matplotlib.git - -or:: - git clone git@github.com:matplotlib/matplotlib.git +Install from source +=================== -If you're developing, it's better to do it in editable mode. The reason why -is that pytest's test discovery only works for Matplotlib -if installation is done this way. Also, editable mode allows your code changes -to be instantly propagated to your library code without reinstalling (though -you will have to restart your python process / kernel):: +.. admonition:: Installing for Development + :class: important - cd matplotlib - python -m pip install -e . + If you would like to contribute to Matplotlib or otherwise need to + install the latest development code, please follow the instructions in + :ref:`installing_for_devs`. -If you're not developing, it can be installed from the source directory with -a simple (just replace the last step):: +The following instructions are for installing from source for production use. +This is generally *not* recommended; please use prebuilt packages when possible. +Proceed with caution because these instructions may result in your +build producing unexpected behavior and/or causing local testing to fail. - python -m pip install . +Before trying to install Matplotlib, please install the :ref:`dependencies`. -To run the tests you will need to install some additional dependencies:: - - python -m pip install -r requirements/dev/dev-requirements.txt - -Then, if you want to update your Matplotlib at any time, just do:: - - git pull - -When you run ``git pull``, if the output shows that only Python files have -been updated, you are all set. If C files have changed, you need to run ``pip -install -e .`` again to compile them. - -There is more information on :ref:`using git ` in the developer -docs. - -.. warning:: - - The following instructions in this section are for very custom - installations of Matplotlib. Proceed with caution because these instructions - may result in your build producing unexpected behavior and/or causing - local testing to fail. - -If you would like to build from a tarball, grab the latest *tar.gz* release +To build from a tarball, download the latest *tar.gz* release file from `the PyPI files page `_. We provide a `mplsetup.cfg`_ file which you can use to customize the build process. For example, which default backend to use, whether some of the -optional libraries that Matplotlib ships with are installed, and so on. This +optional libraries that Matplotlib ships with are installed, and so on. This file will be particularly useful to those packaging Matplotlib. .. _mplsetup.cfg: https://raw.githubusercontent.com/matplotlib/matplotlib/main/mplsetup.cfg.template @@ -182,13 +130,16 @@ file will be particularly useful to those packaging Matplotlib. If you are building your own Matplotlib wheels (or sdists) on Windows, note that any DLLs that you copy into the source tree will be packaged too. -========================== -Installing for development -========================== -See :ref:`installing_for_devs`. -.. redirect-from:: /faq/installing_faq -.. redirect-from:: /users/faq/installing_faq +Configure build and behavior defaults +===================================== + +Aspects of the build and install process and some behaviorial defaults of the +library can be configured via :ref:`environment-variables`. Default plotting +appearance and behavior can be configured via the +:ref:`rcParams file ` + + .. _installing-faq: @@ -196,10 +147,6 @@ See :ref:`installing_for_devs`. Frequently asked questions ========================== -.. contents:: - :backlinks: none - :local: - Report a compilation problem ============================ @@ -326,3 +273,6 @@ Python.org Python, or check your homebrew or macports setup. Remember that the disk image installer only works for Python.org Python, and will not get picked up by other Pythons. If all these fail, please :ref:`let us know `. + + +.. include:: troubleshooting_faq.inc.rst diff --git a/doc/users/installing/troubleshooting_faq.inc.rst b/doc/users/installing/troubleshooting_faq.inc.rst new file mode 100644 index 000000000000..60bad7ccffc9 --- /dev/null +++ b/doc/users/installing/troubleshooting_faq.inc.rst @@ -0,0 +1,72 @@ +.. _troubleshooting-install: + +Troubleshooting +=============== + +.. _matplotlib-version: + +Obtaining Matplotlib version +---------------------------- + +To find out your Matplotlib version number, import it and print the +``__version__`` attribute:: + + >>> import matplotlib + >>> matplotlib.__version__ + '0.98.0' + + +.. _locating-matplotlib-install: + +:file:`matplotlib` install location +----------------------------------- + +You can find what directory Matplotlib is installed in by importing it +and printing the ``__file__`` attribute:: + + >>> import matplotlib + >>> matplotlib.__file__ + '/home/jdhunter/dev/lib64/python2.5/site-packages/matplotlib/__init__.pyc' + + +.. _locating-matplotlib-config-dir: + +:file:`matplotlib` configuration and cache directory locations +-------------------------------------------------------------- + +Each user has a Matplotlib configuration directory which may contain a +:ref:`matplotlibrc ` file. To +locate your :file:`matplotlib/` configuration directory, use +:func:`matplotlib.get_configdir`:: + + >>> import matplotlib as mpl + >>> mpl.get_configdir() + '/home/darren/.config/matplotlib' + +On Unix-like systems, this directory is generally located in your +:envvar:`HOME` directory under the :file:`.config/` directory. + +In addition, users have a cache directory. On Unix-like systems, this is +separate from the configuration directory by default. To locate your +:file:`.cache/` directory, use :func:`matplotlib.get_cachedir`:: + + >>> import matplotlib as mpl + >>> mpl.get_cachedir() + '/home/darren/.cache/matplotlib' + +On Windows, both the config directory and the cache directory are +the same and are in your :file:`Documents and Settings` or :file:`Users` +directory by default:: + + >>> import matplotlib as mpl + >>> mpl.get_configdir() + 'C:\\Documents and Settings\\jdhunter\\.matplotlib' + >>> mpl.get_cachedir() + 'C:\\Documents and Settings\\jdhunter\\.matplotlib' + +If you would like to use a different configuration directory, you can +do so by specifying the location in your :envvar:`MPLCONFIGDIR` +environment variable -- see +:ref:`setting-linux-osx-environment-variables`. Note that +:envvar:`MPLCONFIGDIR` sets the location of both the configuration +directory and the cache directory. From 41d196378101d120d001952795fd27f3f777e5c6 Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 11 Sep 2023 16:03:53 -0400 Subject: [PATCH 0126/1120] rstcheck config and precommit & fixed rst linting errors --- .pre-commit-config.yaml | 8 ++- doc/_templates/autofunctions.rst | 2 + doc/_templates/automodule.rst | 9 ++- doc/_templates/autosummary.rst | 5 +- doc/_templates/autosummary_class_only.rst | 1 + .../next_api_changes/behavior/25775-HZ.rst | 2 +- .../prev_api_changes/api_changes_2.1.2.rst | 2 +- .../api_changes_3.5.0/behaviour.rst | 4 +- .../api_changes_3.7.0/behaviour.rst | 4 +- doc/devel/MEP/MEP23.rst | 4 +- doc/devel/MEP/MEP28.rst | 1 + doc/devel/contribute.rst | 69 ++++++++++--------- doc/devel/document.rst | 2 +- .../antialiasing_text_annotation.rst | 4 +- doc/users/next_whats_new/boldsym_mathtext.rst | 2 +- .../next_whats_new/mathtext_features.rst | 4 +- doc/users/next_whats_new/updated_mathtext.rst | 4 +- .../prev_whats_new/dflt_style_changes.rst | 10 +-- .../prev_whats_new/github_stats_3.0.0.rst | 6 +- .../prev_whats_new/github_stats_3.2.0.rst | 6 +- .../prev_whats_new/github_stats_3.4.0.rst | 2 +- doc/users/prev_whats_new/whats_new_1.5.rst | 3 +- doc/users/prev_whats_new/whats_new_3.0.rst | 2 +- doc/users/prev_whats_new/whats_new_3.4.0.rst | 2 +- doc/users/prev_whats_new/whats_new_3.5.0.rst | 4 +- .../users_explain/toolkits/axisartist.rst | 69 +++++++++++++++---- pyproject.toml | 63 +++++++++++++++++ 27 files changed, 212 insertions(+), 82 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e213bf188f4c..18707e44e5d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,6 @@ repos: - id: no-commit-to-branch #default is master and main - id: trailing-whitespace exclude_types: [svg] - - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: @@ -44,10 +43,15 @@ repos: "--skip", "doc/users/project/credits.rst" ] - - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort name: isort (python) files: ^galleries/tutorials/|^galleries/examples/|^galleries/plot_types/ + - repo: https://github.com/rstcheck/rstcheck + rev: v6.2.0 + hooks: + - id: rstcheck + additional_dependencies: + - sphinx>=1.8.1 diff --git a/doc/_templates/autofunctions.rst b/doc/_templates/autofunctions.rst index 942731b46587..291b8eee2ede 100644 --- a/doc/_templates/autofunctions.rst +++ b/doc/_templates/autofunctions.rst @@ -14,7 +14,9 @@ Functions .. autosummary:: :template: autosummary.rst :toctree: + {% for item in functions %}{% if item not in ['plotting', 'colormaps'] %} {{ item }}{% endif %}{% endfor %} + {% endif %} {% endblock %} diff --git a/doc/_templates/automodule.rst b/doc/_templates/automodule.rst index 984c12e00d03..fef899a38a5e 100644 --- a/doc/_templates/automodule.rst +++ b/doc/_templates/automodule.rst @@ -1,8 +1,10 @@ {{ fullname | escape | underline}} {% if fullname in ['mpl_toolkits.axes_grid1.colorbar'] %} -.. To prevent problems with the autosummary for the colorbar doc - treat this separately (sphinx-doc/sphinx/issues/4874) + +.. To prevent problems with the autosummary for the colorbar doc treat this + separately (sphinx-doc/sphinx/issues/4874) + .. automodule:: {{ fullname }} :members: @@ -21,8 +23,10 @@ Classes .. autosummary:: :template: autosummary.rst :toctree: + {% for item in classes %}{% if item not in ['zip', 'map', 'reduce'] %} {{ item }}{% endif %}{% endfor %} + {% endif %} {% endblock %} @@ -38,6 +42,7 @@ Functions {% for item in functions %}{% if item not in ['zip', 'map', 'reduce'] %} {{ item }}{% endif %}{% endfor %} + {% endif %} {% endblock %} {% endif %} diff --git a/doc/_templates/autosummary.rst b/doc/_templates/autosummary.rst index c5f90e87f016..824dbe5b9a4b 100644 --- a/doc/_templates/autosummary.rst +++ b/doc/_templates/autosummary.rst @@ -5,6 +5,7 @@ {% if objtype in ['class'] %} + .. auto{{ objtype }}:: {{ objname }} :show-inheritance: :special-members: __call__ @@ -16,11 +17,13 @@ {% if objtype in ['class', 'method', 'function'] %} {% if objname in ['AxesGrid', 'Scalable', 'HostAxes', 'FloatingAxes', - 'ParasiteAxesAuxTrans', 'ParasiteAxes'] %} +'ParasiteAxesAuxTrans', 'ParasiteAxes'] %} + .. Filter out the above aliases to other classes, as sphinx gallery creates no example file for those (sphinx-gallery/sphinx-gallery#365) {% else %} + .. minigallery:: {{module}}.{{objname}} :add-heading: diff --git a/doc/_templates/autosummary_class_only.rst b/doc/_templates/autosummary_class_only.rst index 6611f04f5c0d..d10f1b375fd3 100644 --- a/doc/_templates/autosummary_class_only.rst +++ b/doc/_templates/autosummary_class_only.rst @@ -5,6 +5,7 @@ {% if objtype in ['class'] %} + .. auto{{ objtype }}:: {{ objname }} :no-members: diff --git a/doc/api/next_api_changes/behavior/25775-HZ.rst b/doc/api/next_api_changes/behavior/25775-HZ.rst index 118195811b98..badd2951e515 100644 --- a/doc/api/next_api_changes/behavior/25775-HZ.rst +++ b/doc/api/next_api_changes/behavior/25775-HZ.rst @@ -4,7 +4,7 @@ Default antialiasing behavior changes for ``Text`` and ``Annotation`` ``matplotlib.pyplot.annotate()`` and ``matplotlib.pyplot.text()`` now support parameter *antialiased* when initializing. Examples: -.. code-block:: +.. code-block:: python mpl.text.Text(.5, .5, "foo\nbar", antialiased=True) plt.text(0.5, 0.5, '6 inches x 2 inches', antialiased=True) diff --git a/doc/api/prev_api_changes/api_changes_2.1.2.rst b/doc/api/prev_api_changes/api_changes_2.1.2.rst index 5eb6658e263e..a043a99942bd 100644 --- a/doc/api/prev_api_changes/api_changes_2.1.2.rst +++ b/doc/api/prev_api_changes/api_changes_2.1.2.rst @@ -13,7 +13,7 @@ didn't handle linewidths or linestyles etc. This logic did not exist in `.axes.Axes.legend`. It was included (erroneously) in Matplotlib 2.1.1 when the legend argument parsing was unified -[#9324](https://github.com/matplotlib/matplotlib/pull/9324). This change +`#9324 `_. This change removes that check in `.axes.Axes.legend` again to restore the old behavior. This logic has also been dropped from `.Figure.legend`, where it diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst index 69e38270ca76..9045fbb62764 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst @@ -132,7 +132,7 @@ consistently exposes all the attributes and methods related to the line marker (:ghissue:`11358`). This makes it easy to change the marker features after instantiating a legend. -.. code:: +.. code-block:: python import matplotlib.pyplot as plt @@ -147,7 +147,7 @@ instantiating a legend. The former legend handler for Line2D objects has been renamed `.HandlerLine2DCompound`. To revert to the previous behaviour, one can use -.. code:: +.. code-block:: python import matplotlib.legend as mlegend from matplotlib.legend_handler import HandlerLine2DCompound diff --git a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst index 6057bfa9af4c..a4fd8b57e419 100644 --- a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst @@ -51,7 +51,7 @@ Now, the halfrange remains fixed when vcenter is changed, and **vmin** and For example, this is what the values were when changing vcenter previously. -.. code-block:: +.. code-block:: python norm = CenteredNorm(vcenter=0, halfrange=1) # Move vcenter up by one @@ -61,7 +61,7 @@ For example, this is what the values were when changing vcenter previously. and now, with that same example -.. code-block:: +.. code-block:: python norm = CenteredNorm(vcenter=0, halfrange=1) norm.vcenter = 1 diff --git a/doc/devel/MEP/MEP23.rst b/doc/devel/MEP/MEP23.rst index 11fd965c4816..1c06978eb8bc 100644 --- a/doc/devel/MEP/MEP23.rst +++ b/doc/devel/MEP/MEP23.rst @@ -35,7 +35,7 @@ most use cases. Sometimes when there are too many figures open at the same time, it is desirable to be able to group these under the same window -[see](https://github.com/matplotlib/matplotlib/issues/2194). +`see `_. The proposed solution modifies `.FigureManagerBase` to contain and manage more than one ``Canvas``. The settings parameter :rc:`backend.multifigure` control @@ -44,7 +44,7 @@ when the **MultiFigure** behaviour is desired. **Note** It is important to note, that the proposed solution, assumes that the -[MEP22](https://github.com/matplotlib/matplotlib/wiki/Mep22) is +`MEP22 `_. is already in place. This is simply because the actual implementation of the ``Toolbar`` makes it pretty hard to switch between canvases. diff --git a/doc/devel/MEP/MEP28.rst b/doc/devel/MEP/MEP28.rst index 0a215a9f19d0..7ae9f8e610d4 100644 --- a/doc/devel/MEP/MEP28.rst +++ b/doc/devel/MEP/MEP28.rst @@ -264,6 +264,7 @@ value of ``statfxn`` would be ``cbook.boxplot_stats``, but users could pass their own function. Then ``transform_in`` and ``transform_out`` would then be passed as elements of the ``statfxn_args`` parameter. +.. rstcheck: ignore-next-code-block .. code:: python def boxplot_stats(data, ..., transform_in=None, transform_out=None): diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 9ed1fe500fa5..9b53a80ab374 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -170,18 +170,18 @@ also welcome to post feature requests or pull requests. If you are reporting a bug, please do your best to include the following: -1. A short, top-level summary of the bug. In most cases, this should be 1-2 +#. A short, top-level summary of the bug. In most cases, this should be 1-2 sentences. -2. A short, self-contained code snippet to reproduce the bug, ideally allowing +#. A short, self-contained code snippet to reproduce the bug, ideally allowing a simple copy and paste to reproduce. Please do your best to reduce the code snippet to the minimum required. -3. The actual outcome of the code snippet. +#. The actual outcome of the code snippet. -4. The expected outcome of the code snippet. +#. The expected outcome of the code snippet. -5. The Matplotlib version, Python version and platform that you are using. You +#. The Matplotlib version, Python version and platform that you are using. You can grab the version with the following commands:: >>> import matplotlib @@ -229,52 +229,54 @@ contribute to Matplotlib. A brief overview of the workflow is as follows. -1. `Create an account `_ on GitHub if you do not +#. `Create an account `_ on GitHub if you do not already have one. -2. Fork the `project repository `_: - click on the 'Fork' button near the top of the page. This creates a copy of - the code under your account on the GitHub server. +#. Fork the `project repository `_ by + clicking on the :octicon:`repo-forked` **Fork** button near the top of the page. + This creates a copy of the code under your account on the GitHub server. -.. tab-set:: +#. Set up a development environment: - .. tab-item:: Local development + .. tab-set:: - 3. Clone this copy to your local disk:: + .. tab-item:: Local development - git clone https://github.com//matplotlib.git + Clone this copy to your local disk:: - .. tab-item:: Using GitHub Codespaces + git clone https://github.com//matplotlib.git - 3. Check out the Matplotlib repository and activate your development - environment: + .. tab-item:: Using GitHub Codespaces - * Open codespaces on your fork by clicking on the green "Code" button + Check out the Matplotlib repository and activate your development environment: + + #. Open codespaces on your fork by clicking on the green "Code" button on the GitHub web interface and selecting the "Codespaces" tab. - * Next, click on "Open codespaces on ". You will be + + #. Next, click on "Open codespaces on ". You will be able to change branches later, so you can select the default ``main`` branch. - * After the codespace is created, you will be taken to a new browser + + #. After the codespace is created, you will be taken to a new browser tab where you can use the terminal to activate a pre-defined conda environment called ``mpl-dev``:: - conda activate mpl-dev - + conda activate mpl-dev -4. Install the local version of Matplotlib with:: +#. Install the local version of Matplotlib with:: python -m pip install -e . See :ref:`installing_for_devs` for detailed instructions. -5. Create a branch to hold your changes:: +#. Create a branch to hold your changes:: git checkout -b my-feature origin/main and start making changes. Never work in the ``main`` branch! -6. Work on this task using Git to do the version control. Codespaces persist for +#. Work on this task using Git to do the version control. Codespaces persist for some time (check the `documentation for details `_) and can be managed on https://github.com/codespaces. When you're done editing @@ -301,11 +303,11 @@ GitHub Codespaces workflows `_. You can use it by connecting to this desktop via your web browser. To do this: - 1. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select + #. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select ``Ports: Focus on Ports View`` in the VSCode session to bring it into focus. Open the ports view in your tool, select the ``noVNC`` port, and click the Globe icon. - 2. In the browser that appears, click the Connect button and enter the desktop + #. In the browser that appears, click the Connect button and enter the desktop password (``vscode`` by default). Check the `GitHub instructions @@ -391,10 +393,11 @@ Rules Introducing ~~~~~~~~~~~ -1. Announce the deprecation in a new file +#. Announce the deprecation in a new file :file:`doc/api/next_api_changes/deprecations/99999-ABC.rst` where ``99999`` is the pull request number and ``ABC`` are the contributor's initials. -2. If possible, issue a `~matplotlib.MatplotlibDeprecationWarning` when the + +#. If possible, issue a `~matplotlib.MatplotlibDeprecationWarning` when the deprecated API is used. There are a number of helper tools for this: - Use ``_api.warn_deprecated()`` for general deprecation warnings @@ -411,7 +414,7 @@ Introducing You can use standard rst cross references in *alternative*. -3. Make appropriate changes to the type hints in the associated ``.pyi`` file. +#. Make appropriate changes to the type hints in the associated ``.pyi`` file. The general guideline is to match runtime reported behavior. - Items marked with ``@_api.deprecated`` or ``@_api.deprecate_privatize_attribute`` @@ -431,15 +434,17 @@ Introducing Expiring ~~~~~~~~ -1. Announce the API changes in a new file +#. Announce the API changes in a new file :file:`doc/api/next_api_changes/[kind]/99999-ABC.rst` where ``99999`` is the pull request number and ``ABC`` are the contributor's initials, and ``[kind]`` is one of the folders :file:`behavior`, :file:`development`, :file:`removals`. See :file:`doc/api/next_api_changes/README.rst` for more information. For the content, you can usually copy the deprecation notice and adapt it slightly. -2. Change the code functionality and remove any related deprecation warnings. -3. Make appropriate changes to the type hints in the associated ``.pyi`` file. + +#. Change the code functionality and remove any related deprecation warnings. + +#. Make appropriate changes to the type hints in the associated ``.pyi`` file. - Items marked with ``@_api.deprecated`` or ``@_api.deprecate_privatize_attribute`` are to be removed on expiry. diff --git a/doc/devel/document.rst b/doc/devel/document.rst index 68f2d3a674c9..ac92888b4052 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -668,7 +668,7 @@ in that case, they can be documented as an ``.. ACCEPTS:`` block, e.g. for .. code-block:: python - def set_xlim(self, ...): + def set_xlim(self, left=None, right=None): """ Set the x-axis view limits. diff --git a/doc/users/next_whats_new/antialiasing_text_annotation.rst b/doc/users/next_whats_new/antialiasing_text_annotation.rst index c771f6a1e19f..277b9ab05c92 100644 --- a/doc/users/next_whats_new/antialiasing_text_annotation.rst +++ b/doc/users/next_whats_new/antialiasing_text_annotation.rst @@ -6,7 +6,7 @@ When *antialiased* is set to ``False``, antialiasing will not be applied to the When *antialiased* is not specified, antialiasing will be set by :rc:`text.antialiased` at the creation time of ``Text`` and ``Annotation`` object. Examples: -.. code-block:: +.. code-block:: python mpl.text.Text(.5, .5, "foo\nbar", antialiased=True) plt.text(0.5, 0.5, '6 inches x 2 inches', antialiased=True) @@ -15,7 +15,7 @@ Examples: If the text contains math expression, *antialiased* applies to the whole text. Examples: -.. code-block:: +.. code-block:: python # no part will be antialiased for the text below plt.text(0.5, 0.25, r"$I'm \sqrt{x}$", antialiased=False) diff --git a/doc/users/next_whats_new/boldsym_mathtext.rst b/doc/users/next_whats_new/boldsym_mathtext.rst index d58532d5661e..80c8415b60cb 100644 --- a/doc/users/next_whats_new/boldsym_mathtext.rst +++ b/doc/users/next_whats_new/boldsym_mathtext.rst @@ -6,7 +6,7 @@ Supports using the ``\boldsymbol{}`` command in mathtext: To change symbols to bold enclose the text in a font command as shown: -.. code-block:: +.. code-block:: none r'$\boldsymbol{a+2+\alpha}$' diff --git a/doc/users/next_whats_new/mathtext_features.rst b/doc/users/next_whats_new/mathtext_features.rst index f62a8d453fee..57c4ec214aee 100644 --- a/doc/users/next_whats_new/mathtext_features.rst +++ b/doc/users/next_whats_new/mathtext_features.rst @@ -5,7 +5,7 @@ To use it to enclose the math in a substack command as shown: -.. code-block:: +.. code-block:: none r'$\sum_{\substack{1\leq i\leq 3\\ 1\leq j\leq 5}}$' @@ -23,7 +23,7 @@ The ``\middle`` delimiter has been added, and can now be used with the To use the middle command enclose it in between the ``\left`` and ``\right`` delimiter command as shown: -.. code-block:: +.. code-block:: none r'$\left( \frac{a}{b} \middle| q \right)$' diff --git a/doc/users/next_whats_new/updated_mathtext.rst b/doc/users/next_whats_new/updated_mathtext.rst index 3658b8db6be1..fb19bf0325b5 100644 --- a/doc/users/next_whats_new/updated_mathtext.rst +++ b/doc/users/next_whats_new/updated_mathtext.rst @@ -6,9 +6,9 @@ Supports use of bold-italic font style in mathtext using the ``\mathbfit{}`` com To change font to bold and italic enclose the text in a font command as shown: -.. code-block:: +.. code-block:: none - r'$\mathbfit{\eta \leq C(\delta(\eta))}$ + r'$\mathbfit{\eta \leq C(\delta(\eta))}$' .. math:: \mathbfit{\eta \leq C(\delta(\eta))} diff --git a/doc/users/prev_whats_new/dflt_style_changes.rst b/doc/users/prev_whats_new/dflt_style_changes.rst index e86e34c50e46..a833064b573b 100644 --- a/doc/users/prev_whats_new/dflt_style_changes.rst +++ b/doc/users/prev_whats_new/dflt_style_changes.rst @@ -13,7 +13,7 @@ are designed to work well in the most common cases. A 'classic' style sheet is provided so reverting to the 1.x default values is a single line of python -.. code:: +.. code-block:: python import matplotlib.style import matplotlib as mpl @@ -102,14 +102,14 @@ denote the first 10 colors in :rc:`axes.prop_cycle`. See To restore the old color cycle use -.. code:: +.. code-block:: python from cycler import cycler mpl.rcParams['axes.prop_cycle'] = cycler(color='bgrcmyk') or set -.. code:: +.. code-block:: cfg axes.prop_cycle : cycler('color', 'bgrcmyk') @@ -156,13 +156,13 @@ map. For details on all of the colormaps available in matplotlib see The previous default can be restored using -.. code:: +.. code-block:: python mpl.rcParams['image.cmap'] = 'jet' or setting -.. code:: +.. code-block:: cfg image.cmap : 'jet' diff --git a/doc/users/prev_whats_new/github_stats_3.0.0.rst b/doc/users/prev_whats_new/github_stats_3.0.0.rst index ab90e5e79e4e..0e9c4b3b588d 100644 --- a/doc/users/prev_whats_new/github_stats_3.0.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.0.0.rst @@ -128,7 +128,7 @@ The following 478 authors contributed 9809 commits. * Drew J. Sonne * Duncan Macleod * Dylan Evans -* E. G. Patrick Bos +* E\. G\. Patrick Bos * Egor Panfilov * Elijah Schutz * Elizabeth Seiver @@ -205,7 +205,7 @@ The following 478 authors contributed 9809 commits. * Isaac Slavitt * Ismo Toijala * J Alammar -* J. Goutin +* J\. Goutin * Jaap Versteegh * Jacob McDonald * jacob-on-github @@ -464,7 +464,7 @@ The following 478 authors contributed 9809 commits. * Tuan Dung Tran * u55 * ultra-andy -* V. R +* V\. R * vab9 * Valentin Schmidt * Vedant Nanda diff --git a/doc/users/prev_whats_new/github_stats_3.2.0.rst b/doc/users/prev_whats_new/github_stats_3.2.0.rst index 5fd75f7c57d0..3cb3fce5de52 100644 --- a/doc/users/prev_whats_new/github_stats_3.2.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.2.0.rst @@ -119,7 +119,7 @@ The following 164 authors contributed 3455 commits. * Nicolas Courtemanche * Nikita Kniazev * njwhite -* O. Castany +* O\. Castany * Oliver Natt * Olivier * Om Sitapara @@ -142,7 +142,7 @@ The following 164 authors contributed 3455 commits. * Richard Ji-Cathriner * RoryIAngus * Ryan May -* S. Fukuda +* S\. Fukuda * Samesh * Samesh Lakhotia * sasoripathos @@ -164,7 +164,7 @@ The following 164 authors contributed 3455 commits. * Tim Hoffmann * Tom Flannaghan * Travis CI -* V. Armando Solé +* V\. Armando Solé * Vincent L.M. Mazoyer * Viraj Mohile * Wafa Soofi diff --git a/doc/users/prev_whats_new/github_stats_3.4.0.rst b/doc/users/prev_whats_new/github_stats_3.4.0.rst index fe49e673a660..b2568058b455 100644 --- a/doc/users/prev_whats_new/github_stats_3.4.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.4.0.rst @@ -82,7 +82,7 @@ The following 177 authors contributed 3852 commits. * ImportanceOfBeingErnest * Isuru Fernando * ItsRLuo -* J. Scott Berg +* J\. Scott Berg * Jae-Joon Lee * Jakub Klus * Janakarajan Natarajan diff --git a/doc/users/prev_whats_new/whats_new_1.5.rst b/doc/users/prev_whats_new/whats_new_1.5.rst index 5ff5b0a97bbd..dd8e204aa957 100644 --- a/doc/users/prev_whats_new/whats_new_1.5.rst +++ b/doc/users/prev_whats_new/whats_new_1.5.rst @@ -517,7 +517,8 @@ also prevents unsafe usage by strictly defining the parameters that a user can set. To use, call ``set_params()`` on a `.Locator` instance with desired arguments: -:: + +.. code-block:: python loc = matplotlib.ticker.LogLocator() # Set given attributes for loc. diff --git a/doc/users/prev_whats_new/whats_new_3.0.rst b/doc/users/prev_whats_new/whats_new_3.0.rst index 2f268eaf0058..e3dd12c71a8e 100644 --- a/doc/users/prev_whats_new/whats_new_3.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.0.rst @@ -147,7 +147,7 @@ Add ``ax.get_gridspec`` to ``SubplotBase`` New method ``SubplotBase.get_gridspec`` is added so that users can easily get the gridspec that went into making an axes: -.. code:: +.. code-block:: python import matplotlib.pyplot as plt diff --git a/doc/users/prev_whats_new/whats_new_3.4.0.rst b/doc/users/prev_whats_new/whats_new_3.4.0.rst index f90a95c7f126..0cd57b4b8d45 100644 --- a/doc/users/prev_whats_new/whats_new_3.4.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.4.0.rst @@ -494,7 +494,7 @@ display an image of the colormap. .. only:: html - .. code-block:: + .. code-block:: ipython In[1]: cmap = plt.get_cmap('viridis').with_extremes(bad='r', under='g', over='b') diff --git a/doc/users/prev_whats_new/whats_new_3.5.0.rst b/doc/users/prev_whats_new/whats_new_3.5.0.rst index 5e69ab6be926..5a55cff17d02 100644 --- a/doc/users/prev_whats_new/whats_new_3.5.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.5.0.rst @@ -284,7 +284,7 @@ Simplifying the font setting for usetex mode Now the :rc:`font.family` accepts some font names as value for a more user-friendly setup. -.. code-block:: +.. code-block:: python plt.rcParams.update({ "text.usetex": True, @@ -561,7 +561,7 @@ to block callback signals from being processed by the ``CallbackRegistry``. The optional keyword, *signal*, can be used to block a specific signal from being processed and let all other signals pass. -.. code-block:: +.. code-block:: python import matplotlib.pyplot as plt diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst index 9246fb27271b..f27839956282 100644 --- a/galleries/users_explain/toolkits/axisartist.rst +++ b/galleries/users_explain/toolkits/axisartist.rst @@ -341,18 +341,63 @@ On the other hand, there is a concept of "axis_direction". This is a default setting of above properties for each, "bottom", "left", "top", and "right" axis. -========== =========== ========= ========== ========= ========== - ? ? left bottom right top ----------- ----------- --------- ---------- --------- ---------- -axislabel direction '-' '+' '+' '-' -axislabel rotation 180 0 0 180 -axislabel va center top center bottom -axislabel ha right center right center -ticklabel direction '-' '+' '+' '-' -ticklabels rotation 90 0 -90 180 -ticklabel ha right center right center -ticklabel va center baseline center baseline -========== =========== ========= ========== ========= ========== +.. list-table:: + + * - + - + - left + - bottom + - right + - top + * - axislabel + - direction + - '-' + - '+' + - '+' + - '-' + * - axislabel + - rotation + - 180 + - 0 + - 0 + - 180 + * - axislabel + - va + - center + - top + - center + - bottom + * - axislabel + - ha + - right + - center + - right + - center + * - ticklabel + - direction + - '-' + - '+' + - '+' + - '-' + * - ticklabels + - rotation + - 90 + - 0 + - -90 + - 180 + * - ticklabel + - ha + - right + - center + - right + - center + * - ticklabel + - va + - center + - baseline + - center + - baseline + And, 'set_axis_direction("top")' means to adjust the text rotation etc, for settings suitable for "top" axis. The concept of axis diff --git a/pyproject.toml b/pyproject.toml index d2e2f769b7ef..f52b96e1cd67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,3 +140,66 @@ ignore_missing_imports = true enable_incomplete_feature = [ "Unpack", ] + +[tool.rstcheck] +ignore_directives = [ + # matplotlib.sphinxext.mathmpl + "mathmpl", + # matplotlib.sphinxext.plot_directive + "plot", + # sphinxext.math_symbol_table + "math_symbol_table", + # sphinxext.redirect_from + "redirect-from", + # sphinx-design + "dropdown", + "grid", + "tab-set", + # sphinx-gallery + "minigallery", + "image-sg", + # sphinx.ext.autodoc + "automodule", + "autoclass", + "autofunction", + "autodata", + "automethod", + "autoattribute", + "autoproperty", + # sphinx.ext.autosummary + "autosummary", + # sphinx.ext.ifconfig + "ifconfig", + # sphinx.ext.inheritance_diagram + "inheritance-diagram", + # include directive is causing attribute errors + "include" +] +ignore_roles = [ + # sphinxext.custom_roles + "rc", + # matplotlib.sphinxext.mathmpl + "mathmpl", + "math-stix", + # sphinxext.github + "ghissue", + "ghpull", + "ghuser", + # sphinx-design + "octicon", +] +ignore_substitutions = [ + "Artist", + "Axes", + "Axis", + "Figure", + "release" +] + +ignore_messages = [ + "Hyperlink target \".*\" is not referenced.", + "Duplicate implicit target name: \".*\".", + "Duplicate explicit target name: \".*\".", + # error in role directive is `.. role:: math-stix(mathmpl) + "Error in \"role\" directive:", +] From eff63fb81311ac1a3e9297710718d648e473f016 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 13 Sep 2023 12:07:43 -0500 Subject: [PATCH 0127/1120] MNT: Numpy 2.0 removals from ndarray class xref numpy/numpy#24682 Only a handful of lines, and _mostly_ in tests/relatively unused utility functions like rgb_to_hsv, but still something that should be addressed. --- lib/matplotlib/cbook.py | 3 ++- lib/matplotlib/colors.py | 5 +++-- lib/matplotlib/tests/test_axes.py | 6 +++--- lib/matplotlib/tests/test_colors.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index bea97102006b..80ec1612688b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -734,7 +734,8 @@ def safe_masked_invalid(x, copy=False): if not x.dtype.isnative: # If we have already made a copy, do the byteswap in place, else make a # copy with the byte order swapped. - x = x.byteswap(inplace=copy).newbyteorder('N') # Swap to native order. + # Swap to native order. + x = x.byteswap(inplace=copy).view(x.dtype.newbyteorder('N')) try: xm = np.ma.masked_where(~(np.isfinite(x)), x, copy=False) except TypeError: diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index d0d20806666a..904b6ecfa04b 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -726,7 +726,8 @@ def __call__(self, X, alpha=None, bytes=False): xa = np.array(X, copy=True) if not xa.dtype.isnative: - xa = xa.byteswap().newbyteorder() # Native byteorder is faster. + # Native byteorder is faster. + xa = xa.byteswap().view(xa.dtype.newbyteorder()) if xa.dtype.kind == "f": xa *= self.N # xa == 1 (== N after multiplication) is not out of range. @@ -2161,7 +2162,7 @@ def rgb_to_hsv(arr): out = np.zeros_like(arr) arr_max = arr.max(-1) ipos = arr_max > 0 - delta = arr.ptp(-1) + delta = np.ptp(arr, -1) s = np.zeros_like(delta) s[ipos] = delta[ipos] / arr_max[ipos] ipos = delta > 0 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3b5f9019ead2..ab9dab03e543 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1333,7 +1333,7 @@ def test_pcolormesh(): Qz = np.sin(Y) + np.sin(X) Qx = (Qx + 1.1) Z = np.hypot(X, Y) / 5 - Z = (Z - Z.min()) / Z.ptp() + Z = (Z - Z.min()) / np.ptp(Z) # The color array can include masked values: Zm = ma.masked_where(np.abs(Qz) < 0.5 * np.max(Qz), Z) @@ -1354,7 +1354,7 @@ def test_pcolormesh_small(): Qz = np.sin(Y) + np.sin(X) Qx = (Qx + 1.1) Z = np.hypot(X, Y) / 5 - Z = (Z - Z.min()) / Z.ptp() + Z = (Z - Z.min()) / np.ptp(Z) Zm = ma.masked_where(np.abs(Qz) < 0.5 * np.max(Qz), Z) Zm2 = ma.masked_where(Qz < -0.5 * np.max(Qz), Z) @@ -1384,7 +1384,7 @@ def test_pcolormesh_alpha(): Qx = X Qy = Y + np.sin(X) Z = np.hypot(X, Y) / 5 - Z = (Z - Z.min()) / Z.ptp() + Z = (Z - Z.min()) / np.ptp(Z) vir = mpl.colormaps["viridis"].resampled(16) # make another colormap with varying alpha colors = vir(np.arange(16)) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index e8fc9baa1479..1c77f995fb53 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -216,7 +216,7 @@ def test_colormap_endian(): a = [-0.5, 0, 0.5, 1, 1.5, np.nan] for dt in ["f2", "f4", "f8"]: anative = np.ma.masked_invalid(np.array(a, dtype=dt)) - aforeign = anative.byteswap().newbyteorder() + aforeign = anative.byteswap().view(anative.dtype.newbyteorder()) assert_array_equal(cmap(anative), cmap(aforeign)) @@ -1126,7 +1126,7 @@ def alternative_hillshade(azimuth, elev, z): intensity = np.tensordot(normals, illum, axes=(2, 0)) intensity -= intensity.min() - intensity /= intensity.ptp() + intensity /= np.ptp(intensity) return intensity y, x = np.mgrid[5:0:-1, :5] From 43d4bd65d0a6c7a575e435cdaf6e132c59d7c03b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 13 Sep 2023 15:40:42 -0400 Subject: [PATCH 0128/1120] DOC: Add redirects for old gitwash files These were deleted in 3.7.0, and a bunch of redirects were added in #24629, but these two were missed. --- doc/devel/development_workflow.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index 0d3a4c590de2..c03dab47c759 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -1,5 +1,8 @@ .. highlight:: bash +.. redirect-from:: /devel/gitwash/development_workflow +.. redirect-from:: /devel/gitwash/maintainer_workflow + .. _development-workflow: #################### From 1d7f0945689342d16c4a370b3e355a46090fd6b7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 13 Sep 2023 18:17:44 -0400 Subject: [PATCH 0129/1120] Trim Gouraud triangles that contain NaN Agg enters an infinite loop if you give it points that are NaN, due to converting the values to fixed-point integers, and then oscillating between the large values that result from that conversion. NaN values may be introduced after transforming the input, so we need to trim those after transformation. This matches what is done in normal paths. --- lib/matplotlib/tests/test_transforms.py | 19 +++++++++++++++++++ src/_backend_agg.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index ee6754cb8da8..a9a92d33cff3 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -142,6 +142,25 @@ def test_pcolormesh_pre_transform_limits(): assert_almost_equal(expected, ax.dataLim.get_points()) +def test_pcolormesh_gouraud_nans(): + np.random.seed(19680801) + + values = np.linspace(0, 180, 3) + radii = np.linspace(100, 1000, 10) + z, y = np.meshgrid(values, radii) + x = np.radians(np.random.rand(*z.shape) * 100) + + fig = plt.figure() + ax = fig.add_subplot(111, projection="polar") + # Setting the limit to cause clipping of the r values causes NaN to be + # introduced; these should not crash but be ignored as in other path + # operations. + ax.set_rlim(101, 1000) + ax.pcolormesh(x, y, z, shading="gouraud") + + fig.canvas.draw() + + def test_Affine2D_from_values(): points = np.array([[0, 0], [10, 20], diff --git a/src/_backend_agg.h b/src/_backend_agg.h index f15fa05dd5fd..61c24232a866 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -1193,6 +1193,9 @@ inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, tpoints[i][j] = points(i, j); } trans.transform(&tpoints[i][0], &tpoints[i][1]); + if(std::isnan(tpoints[i][0]) || std::isnan(tpoints[i][1])) { + return; + } } span_alloc_t span_alloc; From 4c14bd0f25735174b16ccc78146276664a1ddad1 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 4 Sep 2023 02:50:12 +0200 Subject: [PATCH 0130/1120] Remove usage of numpy recarray Structured numpy arrays are more fundamental than recarrays and sufficient in all cases. Supersedes #26664. --- .../fill_between_alpha.py | 8 ++++---- .../lines_bars_and_markers/scatter_demo2.py | 8 ++++---- galleries/examples/misc/keyword_plotting.py | 13 +++++++------ .../examples/ticks/centered_ticklabels.py | 7 +++---- .../examples/ticks/date_index_formatter.py | 18 +++++++++--------- galleries/tutorials/pyplot.py | 6 ++++-- galleries/users_explain/quick_start.py | 9 ++++++--- 7 files changed, 37 insertions(+), 32 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/fill_between_alpha.py b/galleries/examples/lines_bars_and_markers/fill_between_alpha.py index 66822aaff0c5..3894d9d1d45c 100644 --- a/galleries/examples/lines_bars_and_markers/fill_between_alpha.py +++ b/galleries/examples/lines_bars_and_markers/fill_between_alpha.py @@ -18,14 +18,14 @@ import matplotlib.cbook as cbook # load up some sample financial data -r = cbook.get_sample_data('goog.npz')['price_data'].view(np.recarray) +r = cbook.get_sample_data('goog.npz')['price_data'] # create two subplots with the shared x and y axes fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True) -pricemin = r.close.min() +pricemin = r["close"].min() -ax1.plot(r.date, r.close, lw=2) -ax2.fill_between(r.date, pricemin, r.close, alpha=0.7) +ax1.plot(r["date"], r["close"], lw=2) +ax2.fill_between(r["date"], pricemin, r["close"], alpha=0.7) for ax in ax1, ax2: ax.grid(True) diff --git a/galleries/examples/lines_bars_and_markers/scatter_demo2.py b/galleries/examples/lines_bars_and_markers/scatter_demo2.py index 0a9eee4f2b53..c3d57c423d69 100644 --- a/galleries/examples/lines_bars_and_markers/scatter_demo2.py +++ b/galleries/examples/lines_bars_and_markers/scatter_demo2.py @@ -14,14 +14,14 @@ # low, close, volume, adj_close from the mpl-data/sample_data directory. The # record array stores the date as an np.datetime64 with a day unit ('D') in # the date column. -price_data = cbook.get_sample_data('goog.npz')['price_data'].view(np.recarray) +price_data = cbook.get_sample_data('goog.npz')['price_data'] price_data = price_data[-250:] # get the most recent 250 trading days -delta1 = np.diff(price_data.adj_close) / price_data.adj_close[:-1] +delta1 = np.diff(price_data["adj_close"]) / price_data["adj_close"][:-1] # Marker size in units of points^2 -volume = (15 * price_data.volume[:-2] / price_data.volume[0])**2 -close = 0.003 * price_data.close[:-2] / 0.003 * price_data.open[:-2] +volume = (15 * price_data["volume"][:-2] / price_data["volume"][0])**2 +close = 0.003 * price_data["close"][:-2] / 0.003 * price_data["open"][:-2] fig, ax = plt.subplots() ax.scatter(delta1[:-1], delta1[1:], c=close, s=volume, alpha=0.5) diff --git a/galleries/examples/misc/keyword_plotting.py b/galleries/examples/misc/keyword_plotting.py index 4e0f3e61037d..e8a2d944fe0d 100644 --- a/galleries/examples/misc/keyword_plotting.py +++ b/galleries/examples/misc/keyword_plotting.py @@ -3,13 +3,14 @@ Plotting with keywords ====================== -There are some instances where you have data in a format that lets you -access particular variables with strings: for example, with -`numpy.recarray` or `pandas.DataFrame`. +Some data structures, like dict, `structured numpy array +`_ +or `pandas.DataFrame` provide access to labelled data via string index access +``data[key]``. -Matplotlib allows you to provide such an object with the ``data`` keyword -argument. If provided, you may generate plots with the strings -corresponding to these variables. +For these data types, Matplotlib supports passing the whole datastructure via the +``data`` keyword argument, and using the string names as plot function parameters, +where you'd normally pass in your data. """ import matplotlib.pyplot as plt diff --git a/galleries/examples/ticks/centered_ticklabels.py b/galleries/examples/ticks/centered_ticklabels.py index 21c81e43d1f2..ab9e1b56c4e6 100644 --- a/galleries/examples/ticks/centered_ticklabels.py +++ b/galleries/examples/ticks/centered_ticklabels.py @@ -18,18 +18,17 @@ """ import matplotlib.pyplot as plt -import numpy as np import matplotlib.cbook as cbook import matplotlib.dates as dates import matplotlib.ticker as ticker # Load some financial data; Google's stock price -r = cbook.get_sample_data('goog.npz')['price_data'].view(np.recarray) +r = cbook.get_sample_data('goog.npz')['price_data'] r = r[-250:] # get the last 250 days fig, ax = plt.subplots() -ax.plot(r.date, r.adj_close) +ax.plot(r["date"], r["adj_close"]) ax.xaxis.set_major_locator(dates.MonthLocator()) # 16 is a slight approximation since months differ in number of days. @@ -45,5 +44,5 @@ for label in ax.get_xticklabels(minor=True): label.set_horizontalalignment('center') imid = len(r) // 2 -ax.set_xlabel(str(r.date[imid].item().year)) +ax.set_xlabel(str(r["date"][imid].item().year)) plt.show() diff --git a/galleries/examples/ticks/date_index_formatter.py b/galleries/examples/ticks/date_index_formatter.py index 4c48ac5ace7a..c5bc6abaf17f 100644 --- a/galleries/examples/ticks/date_index_formatter.py +++ b/galleries/examples/ticks/date_index_formatter.py @@ -21,23 +21,23 @@ import matplotlib.lines as ml from matplotlib.ticker import Formatter -# Load a numpy record array from yahoo csv data with fields date, open, high, +# Load a structured numpy array from yahoo csv data with fields date, open, high, # low, close, volume, adj_close from the mpl-data/sample_data directory. The # record array stores the date as an np.datetime64 with a day unit ('D') in -# the date column (``r.date``). -r = cbook.get_sample_data('goog.npz')['price_data'].view(np.recarray) +# the date column (``r['date']``). +r = cbook.get_sample_data('goog.npz')['price_data'] r = r[:9] # get the first 9 days fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 6), layout='constrained') fig.get_layout_engine().set(hspace=0.15) # First we'll do it the default way, with gaps on weekends -ax1.plot(r.date, r.adj_close, 'o-') +ax1.plot(r["date"], r["adj_close"], 'o-') # Highlight gaps in daily data -gaps = np.flatnonzero(np.diff(r.date) > np.timedelta64(1, 'D')) +gaps = np.flatnonzero(np.diff(r["date"]) > np.timedelta64(1, 'D')) for gap in r[['date', 'adj_close']][np.stack((gaps, gaps + 1)).T]: - ax1.plot(gap.date, gap.adj_close, 'w--', lw=2) + ax1.plot(gap['date'], gap['adj_close'], 'w--', lw=2) ax1.legend(handles=[ml.Line2D([], [], ls='--', label='Gaps in daily data')]) ax1.set_title("Plot y at x Coordinates") @@ -51,12 +51,12 @@ def format_date(x, _): try: # convert datetime64 to datetime, and use datetime's strftime: - return r.date[round(x)].item().strftime('%a') + return r["date"][round(x)].item().strftime('%a') except IndexError: pass # Create an index plot (x defaults to range(len(y)) if omitted) -ax2.plot(r.adj_close, 'o-') +ax2.plot(r["adj_close"], 'o-') ax2.set_title("Plot y at Index Coordinates Using Custom Formatter") ax2.xaxis.set_major_formatter(format_date) # internally creates FuncFormatter @@ -79,6 +79,6 @@ def __call__(self, x, pos=0): pass -ax2.xaxis.set_major_formatter(MyFormatter(r.date, '%a')) +ax2.xaxis.set_major_formatter(MyFormatter(r["date"], '%a')) plt.show() diff --git a/galleries/tutorials/pyplot.py b/galleries/tutorials/pyplot.py index b6e02041afa0..3c9f65a68c57 100644 --- a/galleries/tutorials/pyplot.py +++ b/galleries/tutorials/pyplot.py @@ -106,8 +106,10 @@ # ============================= # # There are some instances where you have data in a format that lets you -# access particular variables with strings. For example, with -# `numpy.recarray` or `pandas.DataFrame`. +# access particular variables with strings. For example, with `structured arrays`_ +# or `pandas.DataFrame`. +# +# .. _structured arrays: https://numpy.org/doc/stable/user/basics.rec.html#structured-arrays # # Matplotlib allows you to provide such an object with # the ``data`` keyword argument. If provided, then you may generate plots with diff --git a/galleries/users_explain/quick_start.py b/galleries/users_explain/quick_start.py index cf2d5850e6e5..16d0165ec7a6 100644 --- a/galleries/users_explain/quick_start.py +++ b/galleries/users_explain/quick_start.py @@ -126,10 +126,13 @@ # b = np.matrix([[1, 2], [3, 4]]) # b_asarray = np.asarray(b) # -# Most methods will also parse an addressable object like a *dict*, a -# `numpy.recarray`, or a `pandas.DataFrame`. Matplotlib allows you to -# provide the ``data`` keyword argument and generate plots passing the +# Most methods will also parse a string-indexable object like a *dict*, a +# `structured numpy array`_, or a `pandas.DataFrame`. Matplotlib allows you +# to provide the ``data`` keyword argument and generate plots passing the # strings corresponding to the *x* and *y* variables. +# +# .. _structured numpy array: `_ # noqa: E501 + np.random.seed(19680801) # seed the random number generator. data = {'a': np.arange(50), 'c': np.random.randint(0, 50, 50), From 448e9f6ffabdb44d3a1d0ddfa6d39908d550a2ca Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 31 Aug 2023 11:10:15 -0700 Subject: [PATCH 0131/1120] DOC: organize figure API [ci doc] --- doc/api/figure_api.rst | 303 +++++++++++++++++- .../prev_api_changes/api_changes_3.4.2.rst | 2 +- .../api_changes_3.5.0/behaviour.rst | 2 +- doc/users/prev_whats_new/whats_new_3.4.0.rst | 4 +- .../subplots_axes_and_figures/figure_title.py | 10 +- .../gridspec_nested.py | 2 +- .../users_explain/axes/arranging_axes.py | 2 + .../users_explain/figure/figure_intro.rst | 16 +- galleries/users_explain/figure/index.rst | 2 + lib/matplotlib/artist.py | 8 +- lib/matplotlib/figure.py | 24 +- 11 files changed, 344 insertions(+), 31 deletions(-) diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 1beb0a701b26..84d068f5c3af 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -5,5 +5,304 @@ .. currentmodule:: matplotlib.figure .. automodule:: matplotlib.figure - :members: - :inherited-members: + :no-members: + :no-undoc-members: + +The Figure class +================ +.. autosummary:: + :toctree: _as_gen + :template: autosummary_class_only.rst + :nosignatures: + + Figure + + +Adding Axes and SubFigures +========================== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.add_axes + Figure.add_subplot + Figure.subplots + Figure.subplot_mosaic + Figure.add_gridspec + Figure.get_axes + Figure.axes + Figure.delaxes + Figure.subfigures + Figure.add_subfigure + +Saving +====== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.savefig + + +Annotating +========== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.colorbar + Figure.legend + Figure.text + Figure.suptitle + Figure.get_suptitle + Figure.supxlabel + Figure.get_supxlabel + Figure.supylabel + Figure.get_supylabel + Figure.align_labels + Figure.align_xlabels + Figure.align_ylabels + Figure.autofmt_xdate + + +Figure geometry +=============== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.set_size_inches + Figure.get_size_inches + Figure.set_figheight + Figure.get_figheight + Figure.set_figwidth + Figure.get_figwidth + Figure.dpi + Figure.set_dpi + Figure.set_dpi + +Subplot layout +============== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.subplots_adjust + Figure.set_layout_engine + Figure.get_layout_engine + +Discouraged or deprecated +------------------------- + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.tight_layout + Figure.set_tight_layout + Figure.get_tight_layout + Figure.set_constrained_layout + Figure.get_constrained_layout + Figure.set_constrained_layout_pads + Figure.get_constrained_layout_pads + +Interactive +=========== + +.. seealso:: + + - :ref:`event-handling` + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.ginput + Figure.add_axobserver + Figure.waitforbuttonpress + Figure.pick + +Modifying appearance +==================== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.set_frameon + Figure.get_frameon + Figure.set_linewidth + Figure.get_linewidth + Figure.set_facecolor + Figure.get_facecolor + Figure.set_edgecolor + Figure.get_edgecolor + +Adding and getting Artists +========================== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.add_artist + Figure.get_children + Figure.figimage + +Getting and modifying state +=========================== + +.. seealso:: + + - :ref:`interactive_figures` + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + Figure.clear + Figure.gca + Figure.sca + Figure.get_tightbbox + Figure.get_window_extent + Figure.show + Figure.set_canvas + Figure.draw + Figure.draw_without_rendering + Figure.draw_artist + +.. _figure-api-subfigure: + +SubFigure +========= + +Matplotlib has the concept of a `~.SubFigure`, which is a logical figure inside +a parent `~.Figure`. It has many of the same methods as the parent. See +:ref:`nested_axes_layouts`. + +.. plot:: + + fig = plt.figure(layout='constrained', figsize=(4, 2.5), facecolor='lightgoldenrodyellow') + + # Make two subfigures, left ones more narrow than right ones: + sfigs = fig.subfigures(1, 2, width_ratios=[0.8, 1]) + sfigs[0].set_facecolor('khaki') + sfigs[1].set_facecolor('lightsalmon') + + # Add subplots to left subfigure: + lax = sfigs[0].subplots(2, 1) + sfigs[0].suptitle('Left subfigure') + + # Add subplots to right subfigure: + rax = sfigs[1].subplots(1, 2) + sfigs[1].suptitle('Right subfigure') + + # suptitle for the main figure: + fig.suptitle('Figure') + +SubFigure class +--------------- + +.. autosummary:: + :toctree: _as_gen + :template: autosummary_class_only.rst + :nosignatures: + + SubFigure + +Adding Axes and SubFigures +-------------------------- +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + SubFigure.add_axes + SubFigure.add_subplot + SubFigure.subplots + SubFigure.subplot_mosaic + SubFigure.add_gridspec + SubFigure.delaxes + SubFigure.add_subfigure + SubFigure.subfigures + +Annotating +---------- + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + SubFigure.colorbar + SubFigure.legend + SubFigure.text + SubFigure.suptitle + SubFigure.get_suptitle + SubFigure.supxlabel + SubFigure.get_supxlabel + SubFigure.supylabel + SubFigure.get_supylabel + SubFigure.align_labels + SubFigure.align_xlabels + SubFigure.align_ylabels + +Adding and getting Artists +-------------------------- + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + SubFigure.add_artist + SubFigure.get_children + +Modifying appearance +-------------------- + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + SubFigure.set_frameon + SubFigure.get_frameon + SubFigure.set_linewidth + SubFigure.get_linewidth + SubFigure.set_facecolor + SubFigure.get_facecolor + SubFigure.set_edgecolor + SubFigure.get_edgecolor + +Passthroughs +------------ + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + SubFigure.set_dpi + SubFigure.get_dpi + + +FigureBase parent class +======================= + +.. autoclass:: FigureBase diff --git a/doc/api/prev_api_changes/api_changes_3.4.2.rst b/doc/api/prev_api_changes/api_changes_3.4.2.rst index 2de543be37d6..34d760bf0941 100644 --- a/doc/api/prev_api_changes/api_changes_3.4.2.rst +++ b/doc/api/prev_api_changes/api_changes_3.4.2.rst @@ -7,7 +7,7 @@ Behaviour changes Rename first argument to ``subplot_mosaic`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Both `.FigureBase.subplot_mosaic`, and `.pyplot.subplot_mosaic` have had the +Both `.Figure.subplot_mosaic`, and `.pyplot.subplot_mosaic` have had the first position argument renamed from *layout* to *mosaic*. This is because we are considering to consolidate *constrained_layout* and *tight_layout* keyword arguments in the Figure creation functions of `.pyplot` into a single *layout* diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst index 69e38270ca76..278cad0a29c1 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst @@ -4,7 +4,7 @@ Behaviour changes First argument to ``subplot_mosaic`` renamed ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Both `.FigureBase.subplot_mosaic`, and `.pyplot.subplot_mosaic` have had the +Both `.Figure.subplot_mosaic`, and `.pyplot.subplot_mosaic` have had the first positional argument renamed from *layout* to *mosaic*. As we have consolidated the *constrained_layout* and *tight_layout* keyword arguments in the Figure creation functions of `.pyplot` into a single *layout* keyword diff --git a/doc/users/prev_whats_new/whats_new_3.4.0.rst b/doc/users/prev_whats_new/whats_new_3.4.0.rst index f90a95c7f126..707b43ab73b9 100644 --- a/doc/users/prev_whats_new/whats_new_3.4.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.4.0.rst @@ -634,8 +634,8 @@ supxlabel and supylabel ----------------------- It is possible to add x- and y-labels to a whole figure, analogous to -`.FigureBase.suptitle` using the new `.FigureBase.supxlabel` and -`.FigureBase.supylabel` methods. +`.Figure.suptitle` using the new `.Figure.supxlabel` and +`.Figure.supylabel` methods. .. plot:: diff --git a/galleries/examples/subplots_axes_and_figures/figure_title.py b/galleries/examples/subplots_axes_and_figures/figure_title.py index bc8a90366d8e..118157f0579c 100644 --- a/galleries/examples/subplots_axes_and_figures/figure_title.py +++ b/galleries/examples/subplots_axes_and_figures/figure_title.py @@ -5,10 +5,10 @@ Each axes can have a title (or actually three - one each with *loc* "left", "center", and "right"), but is sometimes desirable to give a whole figure -(or `.SubFigure`) an overall title, using `.FigureBase.suptitle`. +(or `.SubFigure`) an overall title, using `.Figure.suptitle`. -We can also add figure-level x- and y-labels using `.FigureBase.supxlabel` and -`.FigureBase.supylabel`. +We can also add figure-level x- and y-labels using `.Figure.supxlabel` and +`.Figure.supylabel`. """ import matplotlib.pyplot as plt @@ -31,8 +31,8 @@ fig.suptitle('Different types of oscillations', fontsize=16) # %% -# A global x- or y-label can be set using the `.FigureBase.supxlabel` and -# `.FigureBase.supylabel` methods. +# A global x- or y-label can be set using the `.Figure.supxlabel` and +# `.Figure.supylabel` methods. with get_sample_data('Stocks.csv') as file: diff --git a/galleries/examples/subplots_axes_and_figures/gridspec_nested.py b/galleries/examples/subplots_axes_and_figures/gridspec_nested.py index a2750a0ecb49..bfcb90cdfc4a 100644 --- a/galleries/examples/subplots_axes_and_figures/gridspec_nested.py +++ b/galleries/examples/subplots_axes_and_figures/gridspec_nested.py @@ -7,7 +7,7 @@ set the position for a nested grid of subplots. Note that the same functionality can be achieved more directly with -`~.FigureBase.subfigures`; see +`~.Figure.subfigures`; see :doc:`/gallery/subplots_axes_and_figures/subfigures`. """ diff --git a/galleries/users_explain/axes/arranging_axes.py b/galleries/users_explain/axes/arranging_axes.py index 79b69f4bf3dd..8068d3b92ad3 100644 --- a/galleries/users_explain/axes/arranging_axes.py +++ b/galleries/users_explain/axes/arranging_axes.py @@ -228,6 +228,8 @@ def annotate_axes(ax, text, fontsize=18): fig.suptitle('plt.subplot_mosaic()') # %% +# .. _nested_axes_layouts: +# # Nested Axes layouts # ------------------- # diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index 745b01566427..5f7db3d0202c 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -197,15 +197,15 @@ common cases. Adding Artists -------------- -The `~.FigureBase` class has a number of methods to add artists to a `~.Figure` or +The `~.Figure` class has a number of methods for adding artists to a `~.Figure` or a `~.SubFigure`. By far the most common are to add Axes of various configurations -(`~.FigureBase.add_axes`, `~.FigureBase.add_subplot`, `~.FigureBase.subplots`, -`~.FigureBase.subplot_mosaic`) and subfigures (`~.FigureBase.subfigures`). Colorbars -are added to Axes or group of Axes at the Figure level (`~.FigureBase.colorbar`). -It is also possible to have a Figure-level legend (`~.FigureBase.legend`). -Other Artists include figure-wide labels (`~.FigureBase.suptitle`, -`~.FigureBase.supxlabel`, `~.FigureBase.supylabel`) and text (`~.FigureBase.text`). -Finally, low-level Artists can be added directly using `~.FigureBase.add_artist` +(`~.Figure.add_axes`, `~.Figure.add_subplot`, `~.Figure.subplots`, +`~.Figure.subplot_mosaic`) and subfigures (`~.Figure.subfigures`). Colorbars +are added to Axes or group of Axes at the Figure level (`~.Figure.colorbar`). +It is also possible to have a Figure-level legend (`~.Figure.legend`). +Other Artists include figure-wide labels (`~.Figure.suptitle`, +`~.Figure.supxlabel`, `~.Figure.supylabel`) and text (`~.Figure.text`). +Finally, low-level Artists can be added directly using `~.Figure.add_artist` usually with care being taken to use the appropriate transform. Usually these include ``Figure.transFigure`` which ranges from 0 to 1 in each direction, and represents the fraction of the current Figure size, or ``Figure.dpi_scale_trans`` diff --git a/galleries/users_explain/figure/index.rst b/galleries/users_explain/figure/index.rst index 4433246e4074..753c24b62a5c 100644 --- a/galleries/users_explain/figure/index.rst +++ b/galleries/users_explain/figure/index.rst @@ -1,3 +1,5 @@ +.. _figures_and_backends: + ++++++++++++++++++++ Figures and backends ++++++++++++++++++++ diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 04eaa6cf75df..4a495531d900 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -506,7 +506,7 @@ def pickable(self): See Also -------- - set_picker, get_picker, pick + .Artist.set_picker, .Artist.get_picker, .Artist.pick """ return self.figure is not None and self._picker is not None @@ -519,7 +519,7 @@ def pick(self, mouseevent): See Also -------- - set_picker, get_picker, pickable + .Artist.set_picker, .Artist.get_picker, .Artist.pickable """ from .backend_bases import PickEvent # Circular import. # Pick self @@ -586,11 +586,11 @@ def get_picker(self): """ Return the picking behavior of the artist. - The possible values are described in `.set_picker`. + The possible values are described in `.Artist.set_picker`. See Also -------- - set_picker, pickable, pick + .Artist.set_picker, .Artist.pickable, .Artist.pick """ return self._picker diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d37dda204278..c750095aee18 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1438,8 +1438,12 @@ def align_labels(self, axs=None): def add_gridspec(self, nrows=1, ncols=1, **kwargs): """ - Return a `.GridSpec` that has this figure as a parent. This allows - complex layout of Axes in the figure. + Low-level API for creating a `.GridSpec` that has this figure as a parent. + + This is a low-level API, allowing you to create a gridspec and + subsequently add subplots based on the gridspec. Most users do + not need that freedom and should use the higher-level methods + `~.Figure.subplots` or `~.Figure.subplot_mosaic`. Parameters ---------- @@ -1663,6 +1667,9 @@ def _process_projection_requirements(self, *, axes_class=None, polar=False, return projection_class, kwargs def get_default_bbox_extra_artists(self): + """ + Return a list of Artists typically used in `.Figure.get_tightbbox`. + """ bbox_artists = [artist for artist in self.get_children() if (artist.get_visible() and artist.get_in_layout())] for ax in self.axes: @@ -2088,6 +2095,7 @@ class SubFigure(FigureBase): """ Logical figure that can be placed inside a figure. + See :ref:`figure-api-subfigure` for an index of methods on this class. Typically instantiated using `.Figure.add_subfigure` or `.SubFigure.add_subfigure`, or `.SubFigure.subfigures`. A subfigure has the same methods as a figure except for those particularly tied to the size @@ -2299,6 +2307,8 @@ class Figure(FigureBase): """ The top level container for all the plot elements. + See `matplotlib.figure` for an index of class methods. + Attributes ---------- patch @@ -2715,21 +2725,21 @@ def _set_dpi(self, dpi, forward=True): dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.") def get_tight_layout(self): - """Return whether `.tight_layout` is called when drawing.""" + """Return whether `.Figure.tight_layout` is called when drawing.""" return isinstance(self.get_layout_engine(), TightLayoutEngine) @_api.deprecated("3.6", alternative="set_layout_engine", pending=True) def set_tight_layout(self, tight): """ - Set whether and how `.tight_layout` is called when drawing. + Set whether and how `.Figure.tight_layout` is called when drawing. Parameters ---------- tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None - If a bool, sets whether to call `.tight_layout` upon drawing. + If a bool, sets whether to call `.Figure.tight_layout` upon drawing. If ``None``, use :rc:`figure.autolayout` instead. - If a dict, pass it as kwargs to `.tight_layout`, overriding the + If a dict, pass it as kwargs to `.Figure.tight_layout`, overriding the default paddings. """ if tight is None: @@ -3170,7 +3180,7 @@ def add_axobserver(self, func): def savefig(self, fname, *, transparent=None, **kwargs): """ - Save the current figure. + Save the current figure as an image or vector graphic to a file. Call signature:: From 55dee1230ce38e31f3db74f491241357f59f4566 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 14 Sep 2023 22:27:40 -0400 Subject: [PATCH 0132/1120] use inline to ignore role in file instead of ignoring error --- galleries/users_explain/text/mathtext.py | 1 + pyproject.toml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/galleries/users_explain/text/mathtext.py b/galleries/users_explain/text/mathtext.py index 09c049d4a833..067a332aa54a 100644 --- a/galleries/users_explain/text/mathtext.py +++ b/galleries/users_explain/text/mathtext.py @@ -221,6 +221,7 @@ # ``\mathcal{CALLIGRAPHY}`` :mathmpl:`\mathcal{CALLIGRAPHY}` # ========================= ================================ # +# .. rstcheck: ignore-directives=role # .. role:: math-stix(mathmpl) # :fontset: stix # diff --git a/pyproject.toml b/pyproject.toml index f52b96e1cd67..5c61dd9ff055 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,6 +200,4 @@ ignore_messages = [ "Hyperlink target \".*\" is not referenced.", "Duplicate implicit target name: \".*\".", "Duplicate explicit target name: \".*\".", - # error in role directive is `.. role:: math-stix(mathmpl) - "Error in \"role\" directive:", ] From e6805e67d60dbf79b016519d07cf0d5accb6e75e Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 14 Sep 2023 22:38:08 -0400 Subject: [PATCH 0133/1120] axis artist param table in simple table format --- .../users_explain/toolkits/axisartist.rst | 69 ++++--------------- 1 file changed, 12 insertions(+), 57 deletions(-) diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst index f27839956282..313a0fb5a9e9 100644 --- a/galleries/users_explain/toolkits/axisartist.rst +++ b/galleries/users_explain/toolkits/axisartist.rst @@ -341,63 +341,18 @@ On the other hand, there is a concept of "axis_direction". This is a default setting of above properties for each, "bottom", "left", "top", and "right" axis. -.. list-table:: - - * - - - - - left - - bottom - - right - - top - * - axislabel - - direction - - '-' - - '+' - - '+' - - '-' - * - axislabel - - rotation - - 180 - - 0 - - 0 - - 180 - * - axislabel - - va - - center - - top - - center - - bottom - * - axislabel - - ha - - right - - center - - right - - center - * - ticklabel - - direction - - '-' - - '+' - - '+' - - '-' - * - ticklabels - - rotation - - 90 - - 0 - - -90 - - 180 - * - ticklabel - - ha - - right - - center - - right - - center - * - ticklabel - - va - - center - - baseline - - center - - baseline - +========== ========== ========= ========== ========= ========== +label type parameter left bottom right top +========== ========== ========= ========== ========= ========== +axislabel direction '-' '+' '+' '-' +axislabel rotation 180 0 0 180 +axislabel va center top center bottom +axislabel ha right center right center +ticklabel direction '-' '+' '+' '-' +ticklabel rotation 90 0 -90 180 +ticklabel ha right center right center +ticklabel va center baseline center baseline +========== ========== ========= ========== ========= ========== And, 'set_axis_direction("top")' means to adjust the text rotation etc, for settings suitable for "top" axis. The concept of axis From 4f7d68f7aaadb65f8880d3c18de81f311e628235 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 14 Sep 2023 22:45:52 -0400 Subject: [PATCH 0134/1120] ghpull directive --- doc/api/prev_api_changes/api_changes_2.1.2.rst | 6 +++--- doc/devel/MEP/MEP23.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/prev_api_changes/api_changes_2.1.2.rst b/doc/api/prev_api_changes/api_changes_2.1.2.rst index a043a99942bd..92a72523443d 100644 --- a/doc/api/prev_api_changes/api_changes_2.1.2.rst +++ b/doc/api/prev_api_changes/api_changes_2.1.2.rst @@ -12,9 +12,9 @@ list of conditions was incomplete, didn't handle RGB tuples, didn't handle linewidths or linestyles etc. This logic did not exist in `.axes.Axes.legend`. It was included (erroneously) -in Matplotlib 2.1.1 when the legend argument parsing was unified -`#9324 `_. This change -removes that check in `.axes.Axes.legend` again to restore the old behavior. +in Matplotlib 2.1.1 when the legend argument parsing was unified :ghpull:`9324`. +This change removes that check in `.axes.Axes.legend` again to restore the old +behavior. This logic has also been dropped from `.Figure.legend`, where it was previously undocumented. Repeated diff --git a/doc/devel/MEP/MEP23.rst b/doc/devel/MEP/MEP23.rst index 1c06978eb8bc..d6b342877959 100644 --- a/doc/devel/MEP/MEP23.rst +++ b/doc/devel/MEP/MEP23.rst @@ -34,8 +34,8 @@ This is and may continue to be the desired method of operation for most use cases. Sometimes when there are too many figures open at the same time, it is -desirable to be able to group these under the same window -`see `_. +desirable to be able to group these under the same window. See :ghpull:`2194`. + The proposed solution modifies `.FigureManagerBase` to contain and manage more than one ``Canvas``. The settings parameter :rc:`backend.multifigure` control From e245ad90074cb1d890b0621496446bccf55fdccf Mon Sep 17 00:00:00 2001 From: katotaisei Date: Fri, 15 Sep 2023 14:23:54 +0900 Subject: [PATCH 0135/1120] fix Axes.errorbar docstring --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2b1f1bddfc6f..9997e660f40c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3382,7 +3382,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, Plot y versus x as lines and/or markers with attached errorbars. *x*, *y* define the data locations, *xerr*, *yerr* define the errorbar - sizes. By default, this draws the data markers/lines as well the + sizes. By default, this draws the data markers/lines as well as the errorbars. Use fmt='none' to draw errorbars without any data markers. .. versionadded:: 3.7 From 6811d37fab1b839c654189687fc9aeac7eeba85b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 15 Sep 2023 19:01:19 +0200 Subject: [PATCH 0136/1120] Remove unused Axis private init helpers. These were deprecated in c3f87d4 and later made unused, but not removed. --- lib/matplotlib/axis.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 56d4e6b77dc6..4c9bc8f064fe 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -265,21 +265,6 @@ def get_pad(self): """Get the value of the tick label pad in points.""" return self._base_pad - def _get_text1(self): - """Get the default Text 1 instance.""" - - def _get_text2(self): - """Get the default Text 2 instance.""" - - def _get_tick1line(self): - """Get the default `.Line2D` instance for tick1.""" - - def _get_tick2line(self): - """Get the default `.Line2D` instance for tick2.""" - - def _get_gridline(self): - """Get the default grid `.Line2D` instance for this tick.""" - def get_loc(self): """Return the tick location (data coords) as a scalar.""" return self._loc From c36c5a9a520ea3210b66c28f3c893b0a9fad076f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sun, 17 Sep 2023 12:05:10 +0200 Subject: [PATCH 0137/1120] Remove deprecated draw_gouraud_triangle --- .../next_api_changes/removals/26797-OG.rst | 17 +++++++ lib/matplotlib/backend_bases.py | 18 -------- lib/matplotlib/backend_bases.pyi | 7 --- lib/matplotlib/backends/backend_agg.py | 1 - lib/matplotlib/backends/backend_pdf.py | 4 -- lib/matplotlib/backends/backend_ps.py | 5 --- lib/matplotlib/backends/backend_svg.py | 4 -- src/_backend_agg.h | 20 --------- src/_backend_agg_wrapper.cpp | 44 ------------------- 9 files changed, 17 insertions(+), 103 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26797-OG.rst diff --git a/doc/api/next_api_changes/removals/26797-OG.rst b/doc/api/next_api_changes/removals/26797-OG.rst new file mode 100644 index 000000000000..680f69e01a96 --- /dev/null +++ b/doc/api/next_api_changes/removals/26797-OG.rst @@ -0,0 +1,17 @@ +``draw_gouraud_triangle`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. Use `~.RendererBase.draw_gouraud_triangles` instead. + +A ``draw_gouraud_triangle`` call in a custom `~matplotlib.artist.Artist` can readily be +replaced as:: + + self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)), + colors.reshape((1, 3, 4)), trans) + +A `~.RendererBase.draw_gouraud_triangles` method can be implemented from an +existing ``draw_gouraud_triangle`` method as:: + + transform = transform.frozen() + for tri, col in zip(triangles_array, colors_array): + self.draw_gouraud_triangle(gc, tri, col, transform) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 49e067b02bfb..c07ad692451e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -292,24 +292,6 @@ def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, gc, master_transform, paths, [], offsets, offsetTrans, facecolors, edgecolors, linewidths, [], [antialiased], [None], 'screen') - @_api.deprecated("3.7", alternative="draw_gouraud_triangles") - def draw_gouraud_triangle(self, gc, points, colors, transform): - """ - Draw a Gouraud-shaded triangle. - - Parameters - ---------- - gc : `.GraphicsContextBase` - The graphics context. - points : (3, 2) array-like - Array of (x, y) points for the triangle. - colors : (3, 4) array-like - RGBA colors for each point of the triangle. - transform : `~matplotlib.transforms.Transform` - An affine transform to apply to the points. - """ - raise NotImplementedError - def draw_gouraud_triangles(self, gc, triangles_array, colors_array, transform): """ diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index 0ae88cf18a42..075d87a6edd8 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -77,13 +77,6 @@ class RendererBase: antialiased: bool, edgecolors: Sequence[ColorType] | ColorType | None, ) -> None: ... - def draw_gouraud_triangle( - self, - gc: GraphicsContextBase, - points: ArrayLike, - colors: ArrayLike, - transform: Transform, - ) -> None: ... def draw_gouraud_triangles( self, gc: GraphicsContextBase, diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 470ce9d925ad..92253c02c1b5 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -84,7 +84,6 @@ def __setstate__(self, state): self.__init__(state['width'], state['height'], state['dpi']) def _update_methods(self): - self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles self.draw_image = self._renderer.draw_image self.draw_markers = self._renderer.draw_markers diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index aa4883135d92..d66e199b25b2 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2132,10 +2132,6 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, lastx, lasty = x, y output(Op.grestore) - def draw_gouraud_triangle(self, gc, points, colors, trans): - self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)), - colors.reshape((1, 3, 4)), trans) - def draw_gouraud_triangles(self, gc, points, colors, trans): assert len(points) == len(colors) if len(points) == 0: diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index a757bcf8d3be..b59c571809d1 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -691,11 +691,6 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): self._pswriter.write(f"{ox} {oy} {w} {h} rectfill\n") self._pswriter.write("grestore\n") - @_log_if_debug_on - def draw_gouraud_triangle(self, gc, points, colors, trans): - self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)), - colors.reshape((1, 3, 4)), trans) - @_log_if_debug_on def draw_gouraud_triangles(self, gc, points, colors, trans): assert len(points) == len(colors) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index f62152ed1f0a..f95d5dbf6d9e 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -770,10 +770,6 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, self._path_collection_id += 1 - def draw_gouraud_triangle(self, gc, points, colors, trans): - # docstring inherited - self._draw_gouraud_triangle(gc, points, colors, trans) - def _draw_gouraud_triangle(self, gc, points, colors, trans): # This uses a method described here: # diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 61c24232a866..dd73aecde0f2 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -190,12 +190,6 @@ class RendererAgg bool antialiased, ColorArray &edgecolors); - template - void draw_gouraud_triangle(GCAgg &gc, - PointArray &points, - ColorArray &colors, - agg::trans_affine &trans); - template void draw_gouraud_triangles(GCAgg &gc, PointArray &points, @@ -1229,20 +1223,6 @@ inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, } } -template -inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc, - PointArray &points, - ColorArray &colors, - agg::trans_affine &trans) -{ - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); - - _draw_gouraud_triangle(points, colors, trans, has_clippath); -} - template inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, PointArray &points, diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index ee69729be7e5..02832f1c3e69 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -436,49 +436,6 @@ static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *arg Py_RETURN_NONE; } -static PyObject * -PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args) -{ - GCAgg gc; - numpy::array_view points; - numpy::array_view colors; - agg::trans_affine trans; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&|O:draw_gouraud_triangle", - &convert_gcagg, - &gc, - &points.converter, - &points, - &colors.converter, - &colors, - &convert_trans_affine, - &trans)) { - return NULL; - } - - if (points.dim(0) != 3 || points.dim(1) != 2) { - PyErr_Format(PyExc_ValueError, - "points must have shape (3, 2), " - "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")", - points.dim(0), points.dim(1)); - return NULL; - } - - if (colors.dim(0) != 3 || colors.dim(1) != 4) { - PyErr_Format(PyExc_ValueError, - "colors must have shape (3, 4), " - "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")", - colors.dim(0), colors.dim(1)); - return NULL; - } - - - CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans))); - - Py_RETURN_NONE; -} - static PyObject * PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args) { @@ -603,7 +560,6 @@ static PyTypeObject *PyRendererAgg_init_type() {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL}, {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL}, {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL}, - {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL}, {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL}, From 819e1734c41ba4e476899601acc67c42fa254aab Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sun, 17 Sep 2023 12:43:21 +0200 Subject: [PATCH 0138/1120] Minor refactoring for performance --- lib/matplotlib/backends/backend_svg.py | 75 +++++++++++++------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index f95d5dbf6d9e..d454a5b2fdb9 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -770,7 +770,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, self._path_collection_id += 1 - def _draw_gouraud_triangle(self, gc, points, colors, trans): + def _draw_gouraud_triangle(self, transformed_points, colors): # This uses a method described here: # # http://www.svgopen.org/2005/papers/Converting3DFaceToSVG/index.html @@ -782,43 +782,17 @@ def _draw_gouraud_triangle(self, gc, points, colors, trans): # opposite edge. Underlying these three gradients is a solid # triangle whose color is the average of all three points. - writer = self.writer - if not self._has_gouraud: - self._has_gouraud = True - writer.start( - 'filter', - id='colorAdd') - writer.element( - 'feComposite', - attrib={'in': 'SourceGraphic'}, - in2='BackgroundImage', - operator='arithmetic', - k2="1", k3="1") - writer.end('filter') - # feColorMatrix filter to correct opacity - writer.start( - 'filter', - id='colorMat') - writer.element( - 'feColorMatrix', - attrib={'type': 'matrix'}, - values='1 0 0 0 0 \n0 1 0 0 0 \n0 0 1 0 0' + - ' \n1 1 1 1 0 \n0 0 0 0 1 ') - writer.end('filter') - avg_color = np.average(colors, axis=0) if avg_color[-1] == 0: # Skip fully-transparent triangles return - trans_and_flip = self._make_flip_transform(trans) - tpoints = trans_and_flip.transform(points) - + writer = self.writer writer.start('defs') for i in range(3): - x1, y1 = tpoints[i] - x2, y2 = tpoints[(i + 1) % 3] - x3, y3 = tpoints[(i + 2) % 3] + x1, y1 = transformed_points[i] + x2, y2 = transformed_points[(i + 1) % 3] + x3, y3 = transformed_points[(i + 2) % 3] rgba_color = colors[i] if x2 == x3: @@ -858,9 +832,9 @@ def _draw_gouraud_triangle(self, gc, points, colors, trans): writer.end('defs') # triangle formation using "path" - dpath = "M " + _short_float_fmt(x1)+',' + _short_float_fmt(y1) - dpath += " L " + _short_float_fmt(x2) + ',' + _short_float_fmt(y2) - dpath += " " + _short_float_fmt(x3) + ',' + _short_float_fmt(y3) + " Z" + dpath = (f"M {_short_float_fmt(x1)},{_short_float_fmt(y1)}" + f" L {_short_float_fmt(x2)},{_short_float_fmt(y2)}" + f" {_short_float_fmt(x3)},{_short_float_fmt(y3)} Z") writer.element( 'path', @@ -902,11 +876,36 @@ def _draw_gouraud_triangle(self, gc, points, colors, trans): def draw_gouraud_triangles(self, gc, triangles_array, colors_array, transform): - self.writer.start('g', **self._get_clip_attrs(gc)) + writer = self.writer + writer.start('g', **self._get_clip_attrs(gc)) transform = transform.frozen() - for tri, col in zip(triangles_array, colors_array): - self._draw_gouraud_triangle(gc, tri, col, transform) - self.writer.end('g') + trans_and_flip = self._make_flip_transform(transform) + + if not self._has_gouraud: + self._has_gouraud = True + writer.start( + 'filter', + id='colorAdd') + writer.element( + 'feComposite', + attrib={'in': 'SourceGraphic'}, + in2='BackgroundImage', + operator='arithmetic', + k2="1", k3="1") + writer.end('filter') + # feColorMatrix filter to correct opacity + writer.start( + 'filter', + id='colorMat') + writer.element( + 'feColorMatrix', + attrib={'type': 'matrix'}, + values='1 0 0 0 0 \n0 1 0 0 0 \n0 0 1 0 0 \n1 1 1 1 0 \n0 0 0 0 1 ') + writer.end('filter') + + for points, colors in zip(triangles_array, colors_array): + self._draw_gouraud_triangle(trans_and_flip.transform(points), colors) + writer.end('g') def option_scale_image(self): # docstring inherited From 5be91befb451a655214e65c69d4aa0cfb652772f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sun, 17 Sep 2023 13:38:10 +0200 Subject: [PATCH 0139/1120] Update kiwisolver and pillow versions to be consistent with requirements --- environment.yml | 4 ++-- lib/matplotlib/__init__.py | 2 +- requirements/testing/mypy.txt | 4 ++-- setup.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/environment.yml b/environment.yml index c35b90e9acba..7b13735bb172 100644 --- a/environment.yml +++ b/environment.yml @@ -14,9 +14,9 @@ dependencies: - cycler>=0.10.0 - fonttools>=4.22.0 - importlib-resources>=3.2.0 - - kiwisolver>=1.0.1 + - kiwisolver>=1.3.1 - numpy>=1.21 - - pillow>=6.2 + - pillow>=8 - pybind11>=2.6.0 - pygobject - pyparsing>=2.3.1 diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index d0212cfe0276..3ffe046e6ba9 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -246,7 +246,7 @@ def _check_versions(): for modname, minver in [ ("cycler", "0.10"), ("dateutil", "2.7"), - ("kiwisolver", "1.0.1"), + ("kiwisolver", "1.3.1"), ("numpy", "1.21"), ("pyparsing", "2.3.1"), ]: diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt index 801601bcd6eb..f697772da5ea 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -17,10 +17,10 @@ sphinx contourpy>=1.0.1 cycler>=0.10 fonttools>=4.22.0 -kiwisolver>=1.0.1 +kiwisolver>=1.3.1 numpy>=1.19 packaging>=20.0 -pillow>=6.2.0 +pillow>=8 pyparsing>=2.3.1 python-dateutil>=2.7 setuptools_scm>=7 diff --git a/setup.py b/setup.py index e9c5df01eb68..98aed8ccc912 100644 --- a/setup.py +++ b/setup.py @@ -332,10 +332,10 @@ def make_release_tree(self, base_dir, files): "contourpy>=1.0.1", "cycler>=0.10", "fonttools>=4.22.0", - "kiwisolver>=1.0.1", + "kiwisolver>=1.3.1", "numpy>=1.21", "packaging>=20.0", - "pillow>=6.2.0", + "pillow>=8", "pyparsing>=2.3.1", "python-dateutil>=2.7", ] + ( From a4d1f4e99587deaeeae82db2715beda38a33fda7 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 12 Sep 2023 11:06:47 +0200 Subject: [PATCH 0140/1120] Use cbook methods for string checking --- lib/matplotlib/_mathtext.py | 2 +- lib/matplotlib/legend.py | 4 ++-- lib/matplotlib/rcsetup.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index b23cb67116ed..0d8842772a5b 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2766,7 +2766,7 @@ def _auto_sized_delimiter(self, front: str, depth = max([x.depth for x in middle if not isinstance(x, str)]) factor = None for idx, el in enumerate(middle): - if isinstance(el, str) and el == '\\middle': + if el == r'\middle': c = T.cast(str, middle[idx + 1]) # Should be one of p.delims. if c != '.': middle[idx + 1] = AutoHeightChar( diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index fcd7850bb473..14249cb73442 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -29,7 +29,7 @@ import numpy as np import matplotlib as mpl -from matplotlib import _api, _docstring, colors, offsetbox +from matplotlib import _api, _docstring, cbook, colors, offsetbox from matplotlib.artist import Artist, allow_rasterization from matplotlib.cbook import silent_list from matplotlib.font_manager import FontProperties @@ -622,7 +622,7 @@ def __init__( break except AttributeError: pass - elif isinstance(labelcolor, str) and labelcolor == 'none': + elif cbook._str_equal(labelcolor, 'none'): for text in self.texts: text.set_color(labelcolor) elif np.iterable(labelcolor): diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 276bb9f812a9..c289459b1874 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -179,7 +179,7 @@ def _make_type_validator(cls, *, allow_none=False): def validator(s): if (allow_none and - (s is None or isinstance(s, str) and s.lower() == "none")): + (s is None or cbook._str_lower_equal(s, "none"))): return None if cls is str and not isinstance(s, str): raise ValueError(f'Could not convert {s!r} to str') @@ -615,7 +615,7 @@ def _validate_minor_tick_ndivs(n): two major ticks. """ - if isinstance(n, str) and n.lower() == 'auto': + if cbook._str_lower_equal(n, 'auto'): return n try: n = _validate_int_greaterequal0(n) From 587a8e50c6407b9713b6b3d34edac873b1e22842 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 12 Sep 2023 11:04:57 +0200 Subject: [PATCH 0141/1120] Add typing for some private methods and modules --- lib/matplotlib/_c_internal_utils.pyi | 6 ++++++ lib/matplotlib/cbook.pyi | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/matplotlib/_c_internal_utils.pyi b/lib/matplotlib/_c_internal_utils.pyi index 3a211223be80..3efc81bc8332 100644 --- a/lib/matplotlib/_c_internal_utils.pyi +++ b/lib/matplotlib/_c_internal_utils.pyi @@ -1 +1,7 @@ def display_is_valid() -> bool: ... + +def Win32_GetForegroundWindow() -> int | None: ... +def Win32_SetForegroundWindow(hwnd: int) -> None: ... +def Win32_SetProcessDpiAwareness_max() -> None: ... +def Win32_SetCurrentProcessExplicitAppUserModelID(appid: str) -> None: ... +def Win32_GetCurrentProcessExplicitAppUserModelID() -> str | None: ... diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index 227a23df4168..a6ad1e54a51c 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -161,6 +161,10 @@ def normalize_kwargs( ) -> dict[str, Any]: ... def _lock_path(path: str | os.PathLike) -> contextlib.AbstractContextManager[None]: ... def _str_equal(obj: Any, s: str) -> bool: ... +def _str_lower_equal(obj: Any, s: str) -> bool: ... +def _array_perimeter(arr: np.ndarray) -> np.ndarray: ... +def _unfold(arr: np.ndarray, axis: int, size: int, step: int) -> np.ndarray: ... +def _array_patch_perimeters(x: np.ndarray, rstride: int, cstride: int) -> np.ndarray: ... def _setattr_cm(obj: Any, **kwargs) -> contextlib.AbstractContextManager[None]: ... class _OrderedSet(collections.abc.MutableSet): @@ -172,4 +176,8 @@ class _OrderedSet(collections.abc.MutableSet): def discard(self, key) -> None: ... def _backend_module_name(name: str) -> str: ... +def _setup_new_guiapp() -> None: ... def _format_approx(number: float, precision: int) -> str: ... +def _g_sig_digits(value: float, delta: float) -> int: ... +def _unikey_or_keysym_to_mplkey(unikey: str, keysym: str) -> str: ... +def _auto_format_str(fmt: str, value: Any) -> str: ... From c67d1c3a604686ae7ac826db609228005df9827a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pe=C3=A7anha?= Date: Sun, 17 Sep 2023 13:28:00 -0300 Subject: [PATCH 0142/1120] Changing the default value to None, adding docstring documenting and fixing extra logic. --- lib/matplotlib/axes/_base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f9c3c25bbff5..a8130e1910e3 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3229,7 +3229,7 @@ def grid(self, visible=None, which='major', axis='both', **kwargs): if axis in ['y', 'both']: self.yaxis.grid(visible, which=which, **kwargs) - def ticklabel_format(self, *, axis='both', style='', scilimits=None, + def ticklabel_format(self, *, axis='both', style=None, scilimits=None, useOffset=None, useLocale=None, useMathText=None): r""" Configure the `.ScalarFormatter` used by default for linear Axes. @@ -3245,6 +3245,7 @@ def ticklabel_format(self, *, axis='both', style='', scilimits=None, style : {'sci', 'scientific', 'plain'} Whether to use scientific notation. The formatter default is to use scientific notation. + Sci is equivalent to scientific. scilimits : pair of ints (m, n) Scientific notation is used only for numbers outside the range @@ -3283,8 +3284,11 @@ def ticklabel_format(self, *, axis='both', style='', scilimits=None, except (ValueError, TypeError) as err: raise ValueError("scilimits must be a sequence of 2 integers" ) from err - STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None} - is_sci_style = _api.check_getitem(STYLES, style=style) + STYLES = {'sci': True, 'scientific': True, 'plain': False} + if style == None: + is_sci_style = False + else: + is_sci_style = _api.check_getitem(STYLES, style=style) axis_map = {**{k: [v] for k, v in self._axis_map.items()}, 'both': list(self._axis_map.values())} axises = _api.check_getitem(axis_map, axis=axis) From b6607993610991f6b9dec153a42a2fd678df7bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pe=C3=A7anha?= Date: Sun, 17 Sep 2023 13:44:48 -0300 Subject: [PATCH 0143/1120] Update _base.py Fixing if condition. --- lib/matplotlib/axes/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index a8130e1910e3..8449ec066d52 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3285,7 +3285,7 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, raise ValueError("scilimits must be a sequence of 2 integers" ) from err STYLES = {'sci': True, 'scientific': True, 'plain': False} - if style == None: + if style is None: is_sci_style = False else: is_sci_style = _api.check_getitem(STYLES, style=style) From b31b17dcb7e0b116c93ee81347bbf06b6b09e5f5 Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Mon, 18 Sep 2023 07:53:47 +0200 Subject: [PATCH 0144/1120] feat: specify input range when image data must be clipped --- lib/matplotlib/image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 757f0ba3476e..3579bf57176f 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -700,7 +700,9 @@ def _normalize_image_array(A): if A.min() < 0 or high < A.max(): _log.warning( 'Clipping input data to the valid range for imshow with ' - 'RGB data ([0..1] for floats or [0..255] for integers).' + 'RGB data ([0..1] for floats or [0..255] for integers). ' + 'Got range [%s..%s].', + A.min(), A.max() ) A = np.clip(A, 0, high) # Cast unsupported integer types to uint8 From f923a8a91ec33313f2cc17e33d0f3f09e1460dd9 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Mon, 18 Sep 2023 09:31:12 +0200 Subject: [PATCH 0145/1120] Fix issue with locale comma when not using math text --- lib/matplotlib/tests/test_ticker.py | 5 +++++ lib/matplotlib/ticker.py | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 9d08e335dbdd..961daaa1d167 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1654,6 +1654,11 @@ def _impl_locale_comma(): fmt = ',$\\mathdefault{,%1.1f},$' x = ticks._format_maybe_minus_and_locale(fmt, 0.5) assert x == ',$\\mathdefault{,0{,}5},$' + # Make sure no brackets are added if not using math text + ticks = mticker.ScalarFormatter(useMathText=False, useLocale=True) + fmt = '%1.1f' + x = ticks._format_maybe_minus_and_locale(fmt, 0.5) + assert x == '0,5' def test_locale_comma(): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 767e6200cd2f..22cc5193504b 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -516,11 +516,11 @@ def _format_maybe_minus_and_locale(self, fmt, arg): Format *arg* with *fmt*, applying Unicode minus and locale if desired. """ return self.fix_minus( - # Escape commas introduced by format_string but not those present - # from the beginning in fmt. - ",".join(locale.format_string(part, (arg,), True) - .replace(",", "{,}") - for part in fmt.split(",")) + # Escape commas introduced by locale.format_string if using math text, + # but not those present from the beginning in fmt. + (",".join(locale.format_string(part, (arg,), True).replace(",", "{,}") + for part in fmt.split(",")) if self._useMathText + else locale.format_string(fmt, (arg,), True)) if self._useLocale else fmt % arg) From a75771b933b6c1a366e50871238e311ae4c40091 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Mon, 18 Sep 2023 13:37:10 +0200 Subject: [PATCH 0146/1120] Catch ValueError to support pytorch (and others) plotting --- lib/matplotlib/cbook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 80ec1612688b..f02486a0e280 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1685,7 +1685,7 @@ def safe_first_element(obj): def _safe_first_finite(obj, *, skip_nonfinite=True): """ Return the first finite element in *obj* if one is available and skip_nonfinite is - True. Otherwise return the first element. + True. Otherwise, return the first element. This is a method for internal use. @@ -1697,7 +1697,7 @@ def safe_isfinite(val): return False try: return math.isfinite(val) - except TypeError: + except (TypeError, ValueError): pass try: return np.isfinite(val) if np.isscalar(val) else True From 10d9f7bbc5b7d214064fd2efe68e85d3ac6ce0f7 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sun, 17 Sep 2023 13:14:41 +0200 Subject: [PATCH 0147/1120] Remove deprecated methods and attributed in Axes3D --- .../next_api_changes/removals/26798-OG.rst | 9 ++++++++ doc/api/toolkits/mplot3d/axes3d.rst | 3 --- lib/mpl_toolkits/mplot3d/axes3d.py | 22 ------------------- 3 files changed, 9 insertions(+), 25 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26798-OG.rst diff --git a/doc/api/next_api_changes/removals/26798-OG.rst b/doc/api/next_api_changes/removals/26798-OG.rst new file mode 100644 index 000000000000..0d7d0a11faf2 --- /dev/null +++ b/doc/api/next_api_changes/removals/26798-OG.rst @@ -0,0 +1,9 @@ +``unit_cube``, ``tunit_cube``, and ``tunit_edges`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... of `.Axes3D` are removed without replacements. + +``axes3d.vvec``, ``axes3d.eye``, ``axes3d.sx``, and ``axes3d.sy`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... are removed without replacement. diff --git a/doc/api/toolkits/mplot3d/axes3d.rst b/doc/api/toolkits/mplot3d/axes3d.rst index b581494e4883..a1051992f9a7 100644 --- a/doc/api/toolkits/mplot3d/axes3d.rst +++ b/doc/api/toolkits/mplot3d/axes3d.rst @@ -267,9 +267,6 @@ Aliases and deprecated methods set_zlim3d stem3D text3D - tunit_cube - tunit_edges - unit_cube Other diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index e7abdc0767b5..ac218cb9c952 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -58,11 +58,6 @@ class Axes3D(Axes): Axes._shared_axes["z"] = cbook.Grouper() Axes._shared_axes["view"] = cbook.Grouper() - vvec = _api.deprecate_privatize_attribute("3.7") - eye = _api.deprecate_privatize_attribute("3.7") - sx = _api.deprecate_privatize_attribute("3.7") - sy = _api.deprecate_privatize_attribute("3.7") - def __init__( self, fig, rect=None, *args, elev=30, azim=-60, roll=0, sharez=None, proj_type='persp', @@ -227,10 +222,6 @@ def get_zaxis(self): get_zgridlines = _axis_method_wrapper("zaxis", "get_gridlines") get_zticklines = _axis_method_wrapper("zaxis", "get_ticklines") - @_api.deprecated("3.7") - def unit_cube(self, vals=None): - return self._unit_cube(vals) - def _unit_cube(self, vals=None): minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims() return [(minx, miny, minz), @@ -242,10 +233,6 @@ def _unit_cube(self, vals=None): (maxx, maxy, maxz), (minx, maxy, maxz)] - @_api.deprecated("3.7") - def tunit_cube(self, vals=None, M=None): - return self._tunit_cube(vals, M) - def _tunit_cube(self, vals=None, M=None): if M is None: M = self.M @@ -253,10 +240,6 @@ def _tunit_cube(self, vals=None, M=None): tcube = proj3d._proj_points(xyzs, M) return tcube - @_api.deprecated("3.7") - def tunit_edges(self, vals=None, M=None): - return self._tunit_edges(vals, M) - def _tunit_edges(self, vals=None, M=None): tc = self._tunit_cube(vals, M) edges = [(tc[0], tc[1]), @@ -943,11 +926,6 @@ def get_proj(self): # towards the middle of the box of data from a distance: eye = R + self._dist * ps - # vvec, self._vvec and self._eye are unused, remove when deprecated - vvec = R - eye - self._eye = eye - self._vvec = vvec / np.linalg.norm(vvec) - # Calculate the viewing axes for the eye position u, v, w = self._calc_view_axes(eye) self._view_u = u # _view_u is towards the right of the screen From 2096ae720c80fdcb72d45b69a0bee20dd4ef4602 Mon Sep 17 00:00:00 2001 From: Pedro Date: Mon, 18 Sep 2023 12:12:23 -0300 Subject: [PATCH 0148/1120] Added backwards-compatibility comment, changing to a solo STYLES dictionary and added stub parameter alternative. --- lib/matplotlib/axes/_base.py | 13 ++++++------- lib/matplotlib/axes/_base.pyi | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 8449ec066d52..32a8747a50a8 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3242,10 +3242,11 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, axis : {'x', 'y', 'both'}, default: 'both' The axis to configure. Only major ticks are affected. - style : {'sci', 'scientific', 'plain'} + style : {'sci', 'scientific', 'plain', '', None}, default: None Whether to use scientific notation. The formatter default is to use scientific notation. Sci is equivalent to scientific. + The '' option is included solely for backwards-compatibility. scilimits : pair of ints (m, n) Scientific notation is used only for numbers outside the range @@ -3275,7 +3276,8 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, AttributeError If the current formatter is not a `.ScalarFormatter`. """ - style = style.lower() + if isinstance(style, str): + style = style.lower() axis = axis.lower() if scilimits is not None: try: @@ -3284,11 +3286,8 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, except (ValueError, TypeError) as err: raise ValueError("scilimits must be a sequence of 2 integers" ) from err - STYLES = {'sci': True, 'scientific': True, 'plain': False} - if style is None: - is_sci_style = False - else: - is_sci_style = _api.check_getitem(STYLES, style=style) + STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None, None: None} + is_sci_style = _api.check_getitem(STYLES, style=style) axis_map = {**{k: [v] for k, v in self._axis_map.items()}, 'both': list(self._axis_map.values())} axises = _api.check_getitem(axis_map, axis=axis) diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index e3644585296d..1f929b6c90c5 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -283,7 +283,7 @@ class _AxesBase(martist.Artist): self, *, axis: Literal["both", "x", "y"] = ..., - style: Literal["", "sci", "scientific", "plain"] = ..., + style: Literal["", "sci", "scientific", "plain"] | None = ..., scilimits: tuple[int, int] | None = ..., useOffset: bool | float | None = ..., useLocale: bool | None = ..., From c6fdf59dfa41755c16a4ba859dfab52bad1ddbb7 Mon Sep 17 00:00:00 2001 From: Pedro Date: Mon, 18 Sep 2023 13:04:32 -0300 Subject: [PATCH 0149/1120] Changing comments from the docstring area to inline comments. --- lib/matplotlib/axes/_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 32a8747a50a8..cb6d5ba7cb41 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3242,11 +3242,9 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, axis : {'x', 'y', 'both'}, default: 'both' The axis to configure. Only major ticks are affected. - style : {'sci', 'scientific', 'plain', '', None}, default: None + style : {'sci', 'scientific', 'plain'} or None, default: None Whether to use scientific notation. The formatter default is to use scientific notation. - Sci is equivalent to scientific. - The '' option is included solely for backwards-compatibility. scilimits : pair of ints (m, n) Scientific notation is used only for numbers outside the range @@ -3287,6 +3285,8 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, raise ValueError("scilimits must be a sequence of 2 integers" ) from err STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None, None: None} + # 'sci' is equivalent to 'scientific'. + # The '' option is included for backwards-compatibility. is_sci_style = _api.check_getitem(STYLES, style=style) axis_map = {**{k: [v] for k, v in self._axis_map.items()}, 'both': list(self._axis_map.values())} From 606925ab3644d9e08a54b8430bac01fc258c6e68 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 18 Sep 2023 12:42:24 -0500 Subject: [PATCH 0150/1120] Add overload for slice to Spines.__getitem__ Closes #26808 --- lib/matplotlib/spines.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/spines.pyi b/lib/matplotlib/spines.pyi index 0a5243776730..0f06a6d1ce2b 100644 --- a/lib/matplotlib/spines.pyi +++ b/lib/matplotlib/spines.pyi @@ -75,6 +75,8 @@ class Spines(MutableMapping[str, Spine]): def __getitem__(self, key: str) -> Spine: ... @overload def __getitem__(self, key: list[str]) -> SpinesProxy: ... + @overload + def __getitem__(self, key: slice) -> SpinesProxy: ... def __setitem__(self, key: str, value: Spine) -> None: ... def __delitem__(self, key: str) -> None: ... def __iter__(self) -> Iterator[str]: ... From 3b58aa7d1fff65e269f5d7c5af30336a2544920d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:33:25 +0000 Subject: [PATCH 0151/1120] Bump docker/setup-qemu-action from 2 to 3 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 3369b67122c9..63fd8e7d504d 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -125,7 +125,7 @@ jobs: steps: - name: Set up QEMU if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: arm64 From 18dd02a66457f45efccddcf832c77da1a65e5288 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:33:35 +0000 Subject: [PATCH 0152/1120] Bump pypa/cibuildwheel from 2.15.0 to 2.16.0 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.15.0 to 2.16.0. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d...a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 3369b67122c9..f4825081bf5b 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -136,7 +136,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -158,7 +158,7 @@ jobs: pip install --pre "numpy>=1.25" - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -166,7 +166,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -174,7 +174,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -182,7 +182,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@39a63b5912f086dd459cf6fcb13dcdd3fe3bc24d # v2.15.0 + uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From 6c3ca1c1e7282655b6e6d48ed090e4740faef8a2 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:21:33 +0200 Subject: [PATCH 0153/1120] Remove usage of plt.Axes from tests Axes is considered private in pyplot. See also #26812. --- lib/matplotlib/tests/test_bbox_tight.py | 3 +-- lib/matplotlib/tests/test_image.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py index 4d4624e13f1d..b191ee90a4f8 100644 --- a/lib/matplotlib/tests/test_bbox_tight.py +++ b/lib/matplotlib/tests/test_bbox_tight.py @@ -141,8 +141,7 @@ def test_noop_tight_bbox(): dpi = 100 # make the figure just the right size up front fig = plt.figure(frameon=False, dpi=dpi, figsize=(x_size/dpi, y_size/dpi)) - ax = plt.Axes(fig, [0., 0., 1., 1.]) - fig.add_axes(ax) + ax = fig.add_axes((0, 0, 1, 1)) ax.set_axis_off() ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index aeeebd136b65..c9c959e96115 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -800,10 +800,8 @@ def test_image_preserve_size2(): data = np.identity(n, float) fig = plt.figure(figsize=(n, n), frameon=False) - - ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0]) + ax = fig.add_axes((0.0, 0.0, 1.0, 1.0)) ax.set_axis_off() - fig.add_axes(ax) ax.imshow(data, interpolation='nearest', origin='lower', aspect='auto') buff = io.BytesIO() fig.savefig(buff, dpi=1) From 3766913d0d537a50f08c9a00b2f1c8f2b8b8faae Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:51:44 +0200 Subject: [PATCH 0154/1120] Fix doc build (alternative) Attempt to fix doc build failure introduced with #25272. I suspect this is sphinx scoping: #25272 reused methods like `Axes.set_xbounds` to generate documentation for Axes3D. However, a see also of invert_xaxis only searches within the same module (or class), and invert_xaxis is not documented for Axes3D. This PR attempt to explicitly document the missing methods for Axes3D. --- doc/api/toolkits/mplot3d/axes3d.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api/toolkits/mplot3d/axes3d.rst b/doc/api/toolkits/mplot3d/axes3d.rst index ca9d98cb0c88..877e47b7e93a 100644 --- a/doc/api/toolkits/mplot3d/axes3d.rst +++ b/doc/api/toolkits/mplot3d/axes3d.rst @@ -98,6 +98,10 @@ Axis limits and direction get_zlim set_zlim get_w_lims + invert_xaxis + xaxis_inverted + invert_yaxis + yaxis_inverted invert_zaxis zaxis_inverted get_xbound From 604704c65775426d14798c12e5971093162f5a18 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 19 Sep 2023 00:13:59 +0200 Subject: [PATCH 0155/1120] Reoder safe_first_element() and _safe_first_finite() code This does not change functionality. The code path for `safe_first_element` is `_safe_first_finite(skip_nonfinite=False)` which is separate code block and does not interact with the skip_nonfinite=True case. IMHO this is more readable. Also add a comment on the exception handling recently modified in #26806. --- lib/matplotlib/cbook.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index f02486a0e280..b56de559532d 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1679,10 +1679,20 @@ def safe_first_element(obj): This is a type-independent way of obtaining the first element, supporting both index access and the iterator protocol. """ - return _safe_first_finite(obj, skip_nonfinite=False) + if isinstance(obj, collections.abc.Iterator): + # needed to accept `array.flat` as input. + # np.flatiter reports as an instance of collections.Iterator but can still be + # indexed via []. This has the side effect of re-setting the iterator, but + # that is acceptable. + try: + return obj[0] + except TypeError: + pass + raise RuntimeError("matplotlib does not support generators as input") + return next(iter(obj)) -def _safe_first_finite(obj, *, skip_nonfinite=True): +def _safe_first_finite(obj): """ Return the first finite element in *obj* if one is available and skip_nonfinite is True. Otherwise, return the first element. @@ -1698,6 +1708,9 @@ def safe_isfinite(val): try: return math.isfinite(val) except (TypeError, ValueError): + # if the outer object is 2d, then val is a 1d array, and + # - math.isfinite(numpy.zeros(3)) raises TypeError + # - math.isfinite(torch.zeros(3)) raises ValueError pass try: return np.isfinite(val) if np.isscalar(val) else True @@ -1705,26 +1718,12 @@ def safe_isfinite(val): # This is something that NumPy cannot make heads or tails of, # assume "finite" return True - if skip_nonfinite is False: - if isinstance(obj, collections.abc.Iterator): - # needed to accept `array.flat` as input. - # np.flatiter reports as an instance of collections.Iterator - # but can still be indexed via []. - # This has the side effect of re-setting the iterator, but - # that is acceptable. - try: - return obj[0] - except TypeError: - pass - raise RuntimeError("matplotlib does not support generators " - "as input") - return next(iter(obj)) - elif isinstance(obj, np.flatiter): + + if isinstance(obj, np.flatiter): # TODO do the finite filtering on this return obj[0] elif isinstance(obj, collections.abc.Iterator): - raise RuntimeError("matplotlib does not " - "support generators as input") + raise RuntimeError("matplotlib does not support generators as input") else: for val in obj: if safe_isfinite(val): From 15218fa3572e506c4e3f04eb6de0907cf7b7dddd Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 19 Sep 2023 14:28:50 +0200 Subject: [PATCH 0156/1120] Fix issue with non-string labels and legend --- lib/matplotlib/container.py | 2 +- lib/matplotlib/tests/test_container.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index e11fea391871..0f082e298afc 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -19,7 +19,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, kl, label=None): self._callbacks = cbook.CallbackRegistry(signals=["pchanged"]) self._remove_method = None - self._label = label + self._label = str(label) if label is not None else None def remove(self): for c in cbook.flatten( diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index 8e894d9e9084..1e4577c518ae 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -1,3 +1,4 @@ +import numpy as np import matplotlib.pyplot as plt @@ -28,3 +29,9 @@ def test_errorbar_remove(): eb = ax.errorbar([1], [1], fmt='none') eb.remove() + + +def test_nonstring_label(): + # Test for #26824 + plt.bar(np.arange(10), np.random.rand(10), label=1) + plt.legend() From 4c3355d7d20ce4831b43224a8aebe2c178880bcb Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 19 Sep 2023 09:30:55 -0300 Subject: [PATCH 0157/1120] Reformatting pyplot.py and putting the sci equivalency in the docstring area. --- lib/matplotlib/axes/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index cb6d5ba7cb41..23b50971b732 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3245,6 +3245,7 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, style : {'sci', 'scientific', 'plain'} or None, default: None Whether to use scientific notation. The formatter default is to use scientific notation. + 'sci' is equivalent to 'scientific'. scilimits : pair of ints (m, n) Scientific notation is used only for numbers outside the range @@ -3285,7 +3286,6 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, raise ValueError("scilimits must be a sequence of 2 integers" ) from err STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None, None: None} - # 'sci' is equivalent to 'scientific'. # The '' option is included for backwards-compatibility. is_sci_style = _api.check_getitem(STYLES, style=style) axis_map = {**{k: [v] for k, v in self._axis_map.items()}, From 730a0bfb29742b0583230646ced4da39ce9986df Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 19 Sep 2023 14:39:32 +0200 Subject: [PATCH 0158/1120] [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in --- src/_path.h | 2 ++ src/mplutils.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_path.h b/src/_path.h index 61c4ed07d0d2..387a0a6737f4 100644 --- a/src/_path.h +++ b/src/_path.h @@ -21,6 +21,8 @@ #include "_backend_agg_basic_types.h" #include "numpy_cpp.h" +const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3 }; + struct XY { double x; diff --git a/src/mplutils.h b/src/mplutils.h index 6eb89899ca4a..186449e8f63c 100644 --- a/src/mplutils.h +++ b/src/mplutils.h @@ -49,8 +49,6 @@ enum { CLOSEPOLY = 0x4f }; -const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 }; - inline int prepare_and_add_type(PyTypeObject *type, PyObject *module) { if (PyType_Ready(type)) { From 558b2d843dcc27d5fb4718f4cfec28a93d431382 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 19 Sep 2023 10:05:45 -0300 Subject: [PATCH 0159/1120] Attempting to fix pyplot.py by hand, although this is not encouraged. --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 00e5dea071a4..06aef7ed686f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3953,7 +3953,7 @@ def tick_params(axis: Literal["both", "x", "y"] = "both", **kwargs) -> None: def ticklabel_format( *, axis: Literal["both", "x", "y"] = "both", - style: Literal["", "sci", "scientific", "plain"] = "", + style: Literal["", "sci", "scientific", "plain"] | None = None scilimits: tuple[int, int] | None = None, useOffset: bool | float | None = None, useLocale: bool | None = None, From 3cf0234d3646ef57264b8e19ddbaa6cd7d301bf7 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 19 Sep 2023 10:08:44 -0300 Subject: [PATCH 0160/1120] Adding a comma to pyplot.py. --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 06aef7ed686f..74a73725d5d9 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3953,7 +3953,7 @@ def tick_params(axis: Literal["both", "x", "y"] = "both", **kwargs) -> None: def ticklabel_format( *, axis: Literal["both", "x", "y"] = "both", - style: Literal["", "sci", "scientific", "plain"] | None = None + style: Literal["", "sci", "scientific", "plain"] | None = None, scilimits: tuple[int, int] | None = None, useOffset: bool | float | None = None, useLocale: bool | None = None, From 6bdc99f92ad27ba4705b0c3a4d7ee1adb74d6230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pe=C3=A7anha?= <60028123+pedrompecanha@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:22:21 -0300 Subject: [PATCH 0161/1120] Editing the docstring, making it more consistent to the other parameters that default to None. Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- lib/matplotlib/axes/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 23b50971b732..394dfc2ccafd 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3242,7 +3242,7 @@ def ticklabel_format(self, *, axis='both', style=None, scilimits=None, axis : {'x', 'y', 'both'}, default: 'both' The axis to configure. Only major ticks are affected. - style : {'sci', 'scientific', 'plain'} or None, default: None + style : {'sci', 'scientific', 'plain'} Whether to use scientific notation. The formatter default is to use scientific notation. 'sci' is equivalent to 'scientific'. From b6694f43e5be64b923a4ecb1eb060856af861bf4 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 15 Sep 2023 19:09:23 +0200 Subject: [PATCH 0162/1120] Fix axh{line,span} on polar axes. For axhline, set the underlying path's _interpolation_steps to GRIDLINE_INTERPOLATION_STEPS (180), which amounts to using the same logic to draw it as for drawing gridlines. This ensures that a polar axhline goes (due to interpolation) around the whole circle, rather than being a trivial line connecting a point to itself. Also update axvline for consistency. (Note that _interpolation_steps has is ignored for rectilinear transforms and thus the change doesn't slow down the common, rectilinear case.) Increase the number of interpolation steps of ax{v,h}span likewise to GRIDLINE_INTERPOLATION_STEPS (again for consistency), and more importantly switch them to using Rectangle rather than Polygon, so that a polar axhspan is drawn as an annulus. This is necessary due to a separate "bug", whereby the CLOSEPOLY step on a Polygon would generate (effectively) a segment that's unfortunately ignored by _interpolation_steps (as it doesn't appear explicitly), whereas Rectangle explicitly includes the closing (4th) segment before emitting a CLOSEPOLY. --- .../next_api_changes/behavior/26788-AL.rst | 6 +++++ doc/users/next_whats_new/polar-line-spans.rst | 5 ++++ lib/matplotlib/axes/_axes.py | 25 ++++++++++++++---- .../test_axes/axhvlinespan_interpolation.png | Bin 0 -> 28240 bytes lib/matplotlib/tests/test_axes.py | 12 +++++++++ lib/matplotlib/tests/test_path.py | 6 ++--- lib/matplotlib/transforms.py | 13 +++++++++ lib/matplotlib/widgets.py | 12 +++------ 8 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26788-AL.rst create mode 100644 doc/users/next_whats_new/polar-line-spans.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png diff --git a/doc/api/next_api_changes/behavior/26788-AL.rst b/doc/api/next_api_changes/behavior/26788-AL.rst new file mode 100644 index 000000000000..14573e870843 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26788-AL.rst @@ -0,0 +1,6 @@ +``axvspan`` and ``axhspan`` now return ``Rectangle``\s, not ``Polygons`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This change allows using `~.Axes.axhspan` to draw an annulus on polar axes. + +This change also affects other elements built via `~.Axes.axvspan` and +`~.Axes.axhspan`, such as ``Slider.poly``. diff --git a/doc/users/next_whats_new/polar-line-spans.rst b/doc/users/next_whats_new/polar-line-spans.rst new file mode 100644 index 000000000000..47bb382dbdbf --- /dev/null +++ b/doc/users/next_whats_new/polar-line-spans.rst @@ -0,0 +1,5 @@ +``axhline`` and ``axhspan`` on polar axes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... now draw circles and circular arcs (`~.Axes.axhline`) or annuli and wedges +(`~.Axes.axhspan`). diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9997e660f40c..0fcabac8c7c0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -783,6 +783,7 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs): trans = self.get_yaxis_transform(which='grid') l = mlines.Line2D([xmin, xmax], [y, y], transform=trans, **kwargs) self.add_line(l) + l.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS if scaley: self._request_autoscale_view("y") return l @@ -851,6 +852,7 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs): trans = self.get_xaxis_transform(which='grid') l = mlines.Line2D([x, x], [ymin, ymax], transform=trans, **kwargs) self.add_line(l) + l.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS if scalex: self._request_autoscale_view("x") return l @@ -978,10 +980,17 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): self._check_no_units([xmin, xmax], ['xmin', 'xmax']) (ymin, ymax), = self._process_unit_info([("y", [ymin, ymax])], kwargs) - verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin) - p = mpatches.Polygon(verts, **kwargs) + p = mpatches.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, **kwargs) p.set_transform(self.get_yaxis_transform(which="grid")) + # For Rectangles and non-separable transforms, add_patch can be buggy + # and update the x limits even though it shouldn't do so for an + # yaxis_transformed patch, so undo that update. + ix = self.dataLim.intervalx + mx = self.dataLim.minposx self.add_patch(p) + self.dataLim.intervalx = ix + self.dataLim.minposx = mx + p.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS self._request_autoscale_view("y") return p @@ -1034,11 +1043,17 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): self._check_no_units([ymin, ymax], ['ymin', 'ymax']) (xmin, xmax), = self._process_unit_info([("x", [xmin, xmax])], kwargs) - verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] - p = mpatches.Polygon(verts, **kwargs) + p = mpatches.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, **kwargs) p.set_transform(self.get_xaxis_transform(which="grid")) - p.get_path()._interpolation_steps = 100 + # For Rectangles and non-separable transforms, add_patch can be buggy + # and update the y limits even though it shouldn't do so for an + # xaxis_transformed patch, so undo that update. + iy = self.dataLim.intervaly.copy() + my = self.dataLim.minposy self.add_patch(p) + self.dataLim.intervaly = iy + self.dataLim.minposy = my + p.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS self._request_autoscale_view("x") return p diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png b/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png new file mode 100644 index 0000000000000000000000000000000000000000..3937cdf5b34c9a77d8003dd04967ee70ef4ab130 GIT binary patch literal 28240 zcmeFY^;cEh7dN^O4HD9gNJ@7jpma-jBhsA$a!@)2q(w@)LAq0rh6fLwf*_5Qcif)?Tyc{LHx`U#ZFCU{PQ}5QL+sAfpLENN@;(tzw{o{|HPj{sO=B zJZ1GgwOnjGeJtExL#h^@u8uCAj&_!`-ml#~>|C7rIE6X6IjvuN^N0xWvb}k2Da^~u zXUT6Zz-i6P&CACv!XwB|YwPLh>LJR-<@A4-bGo>{;To5@Gy$)`bX73$fFL}JhaXt6 zWRV>NIb|!#ywLXjyuUQ(VT{OPzC0WaR413sJmkOKJipE(&6`S1lc zGA%wnzQzX&h=God&Yl|^{_qq|80;hXwz!)cA^=|tkfBL|Z}ZT4|Nq7R-!Uwv;6aAg zA&#cfyqWUiN%Fj9beV{cIs(>zDcnmh2|A|}*lyI&b9Ujl)GeIz8bngTnwPlxqfGi7 zOtT|cjwDH!Z|X1wqcIzLd{RfTQtSqB# zk~k;+bA7HNhg8v~CSlN$FFCv+x^mcFceTN2wZXu=F$%whlHQkTmi@=i?W}(5_lhtg z*|WF@ zgfXQ$v5_^rb#$G(j8T&rFoziON$ggT-KTqg?JU8u-e;s}n{n_|n@Q6i!9iZ7=30 z*F0!nT^=;>5fNzYJg{i5)(?j*(~ezE6JuZsLyj-vjWdpa6w+murA&DVH}B8A+T0XOEpu@%XO|Yxu(17)>iGESnluh5TGq%~L?cT)-piDgF{A zurFR?wV7%vMJo&X$k13K-8{SE`LRlQ4LC_74y=kgxF0*y$3GftHN(>m2ko| zEJp6Sl&06Dl^6k>_$nDDMisF2xuSrsqEb_)to z{T!guV9d(`Y}@W|VyI2S`p!eHWG<4pT*HTO63XxFAMMhU6GHE4K0_fO_u3s-je38; zgdR4(L}vdtXU?f?b%{WOkq07}d~Oi^hP#EJ_spDXH*zmd(&6uXP2*^6x3`dJATC-5 z*0dKj+a!OXG^R=zA;x!JvAec@r(V~t4+&~qhR@HjmKgDMkbsv9Ec0cGuStF;r)P;F z=MsWZr})5-4^Gb5_5Q0qhHSp6r%r-eA}XQlfZS>_#eMx$@}oJPsh!?nH7;>>0cq`v z2U;3<{fH4C++sMtb9W{z&Ij6S5@CO9<_3>_(IrKEnkn{F>)$gZ$Y;|S2}v@VzKB9P z6=>z2+U$4jnyzlGj{HLn&f+xbMg4b|#n^Pd&zd{#rU5_i3lp z3{Wg58hF$y+`fPzm?RQIQh421b?oSE;Pwp>FKzNp0(v|HLzBuwYoJiwxCubnV5_N^ zl*sH4`Cwv7b#P99Za5=RKy+1bS-8e zNZ`oLc&eaS2}_c8pV++Jy#Px6{cVP8&h+s6k#EvWRh9vaanaEU)S2*>8{E&^ENqj#F^f6Bf@hlF;?yG4se-E-NCWFSQCaeGMjOXjb|4PJ2!m{k5|9gP^~NA zC?;wN7@I_`m(AhGXjvjBj9T^_FEf4Kb6H15(@3_27)tpjJ#o!O%Qt>ldOBFiXMPWR zIM>8lXvySH`yJ7b%1G(LAJt+1*mIV**s@VQb#4N;XITS^pFu>&COpU_l;$0ysr_jB zpW#c$`X-@4Rc^K!aWyk!9T^7l!kpfCMk-ji7niQ~Ij9ooJ_&?6gg{QB2|r4O#p>c7 z{Y|J?MJ)a5DhVdRu5aG;5pog4{Q5Kam*~f3lv+$$N_5_uo^ebm#1DVTve_>Za!_izn&TV^Y}$I8HNc5T#U8@ z(S84>r4qQUP)YR)3kzwY)CMC};rb#}=BtdR>CeSIz-sJ2mF_gPHAQk2A{t268vXZu z-J5Xy4NdAPZ}?SFT4$p!qewf-nqBUFc7x}ze8phuMVANrM6gNi%wR=?*GM_z@!T2r zOCF7v15QF4lexM;Tk8U_H9~=xn;|nn@ZiU6)a_cxU=?{^X6cGcEJh(yFV8=E_mBOt z2}I{1Wla-NC8##Lz)?qh1?h-rTIz&|Nl9@!V{T&i;BoU0}Z!Px?Xu`Qh z`ZDjoL@*-^L_k3h_4;T{|8aCEi$LmVjC`1(+qa@HIZbbr4iVZG<2!-3^=dq@4o-YD z2lmIM8F9+dnU|LKPG#&G>UD(UG#EKc4pm1>_yV{Odwm=$x%~Sd1?Py%%V{tDQ7oFp z>G$Xt{Cf9>tS0D*4pP2I`mMM!sbrVdS}O1=N?U5Q`Eyw1&r&n_w4t&~8^QNgZx9Ju zUTH+A->+|cJ&ThCP$GZ8flWjKn{^oG33&VbTm6`eSvb1-zDw0XEky>F<5+YTX$S6C zLDoR|*#0@}Z#c*m*H93bdFiqgvFdNvx8fm@Gr~}f#F5zbJkONDhhg#vdkkQt$yoio zm#+K~{Sj#gPTmH`dkT=h;SzC2o!G0tpkMjG$Mti_|LZI>Q>i-$P;VIE#2 zHeXDXS9>RE1+jJwML1Nf*#Y6KNuv&z0ro(<=!~Y@0f zA5_fmEc-szZ^te-B}PpxIiGEp`u;R$17j6a`^wq7$wpZP8UP{iDThT9EoGa5Us zbG+1QtG5ni5e0u=5v|IY5q=?a-ms-jKmn&AZXi=!TVi7~a>&N@xjy>u?)=XjCbCnVPRy-+u?&~U0 z5ZAn_NH65b0~k<{^sgSQ%gwd#OI{&C+*C)a|D>stsn3)vk^h=OY^gHGHhC^qyvSMd z!5#9Do^ro4eU{eo!HkYEUQ||(KQc|*tDOr%4IEH+uzn~|z;ZD)lw;?RWB)S061UNQ z_aj!n??cxfdCge73ABVKi@%F}A#Pg3%iB`9(bL1E79XBtFWQFt$W(Mb)Y#K@E!HOxMjJ3T{bLpFPLCs+Q7GFSe?m@ zDbLasYJ^9$%fBg@0J1fq<&}6Qp~%ng8fdO>V+Px8!+0~P>vz}gQCXKr7N4- zzTMU*?nx8id&n8mM>qC|+0sl1Gg;(B+b<8gp}iC=6^++~mVGWe#(`>QtWg*qg5$B~ zZ~j8w@t4kj7caAha!m3w(5yGsJG}(o(UXw`rA97YHq*44BpHZ;QgK zMCECr!%D*Y)_k8C-Dlu_$)oX z-;kcwHB0lr4g|XvP<_Q9^`f-*{CabCEIxT#eMA8Ps1d{+1QtBC`gkL{H4@c_7H+4; z`#!8WKh4dKGRMa=agPHp=>E&WumR|^e z>2@GZd91i)UY7=F!o@~sZS_bmo(HT{%-a?1NHhKZJIMip-;t2M^DQsCX(gcl+trud z_e`t8tr2!-ihnAe1#fM?>sfX#@uS6W;n8errhREqaf|}L<%+sD8-50qY@q-3NJTVy z&iC_%^@MJc%0~>Wz+e(-GZ9BcWo$;_i0nO=9QWJ zhCr#^LtNi+B0;k&YyE%2Ma}Du zP9M_&ze3|2ft%4-FW`tk6ReStI+ zlvXB}{E)it4w3J^)S9^SB=+o*I=AVWiA=_FO75a+Zh-8jMSXe=Wx1&-0`5_H5?mz_b@_Gt>Lk`4C@)V!0?fZ!A1_Ccq)SJJwX{7h986&3Ssl>0c*inw3Z@s9i zws|bRsk&3^chLR}vms3G$TVs;3{H5G;>6DlBzD!$I8w=Ip-ChjRE!+n68O4VP(uJX zjTU*c@ydfz|$cwRlmaicW$G-)Cnf39?<7j!k?TQ731Q6@x3c9!f5N-U#% zcf_lCOaiNNdE?MPpa$n!ov(dHar@t$pmQDw_LeTj z1?%uP2<#hTn{Oovbskb~lj7Nk;6j*3HZotf_IN~WBl5Ng<5-Z>e-wddFGr(2a!JQ@ z$K~i`VQ3x5r;;|8Y8EDl^NbL^vK`HF zge<7SCI6Y!!n>RcqSeEVu(-GRyOyNKJ$h2UZK5=S8R+m2k_5j+C>^gemO)tt%<7RY zp9899pcP=^!;bO4Q@IRL3cR6|N941xj033zSM-b&VLK#P@m@R;LZxAklT9n*h zLzR&7`PQBBKaI4m7v@JS2U||sLtf@rRWf+5x*Qe&FC<0gtOA06pE9|g#jlSblN7j$V0FS*z8ttY*GVjt@mTH%M@dWyDozAJ8~*t`3?-aogsANY%MQf)wOl z8$R8w?E^HXVXkAV%5lx*9}@RnT=BFnW+e&F%z{MED7B|g3vP&9KMViR*^k8!L)d#k z8s(}26DKxUo!4?9UBJhgigXJEYny>xuue+D5S*MAOzb@?>gwk?3xjrMs>n=|j~Rto zyANRJ;!h-py;_aa5%}hc>Pj*+qKi&H;2B4<=Tn93v$Ny*j2pztb22^Ceh?bg3S39 z32}#kA`f;H)0MZVSa}+ErQuc9XKQYbB77P3w0|sVPW;{3rg&Zn)%mxUqa?!T<@gE$ z9~ehVEIxn1aH2jw2MNnY&&rJ6FzKE6e%FV4-55{q8;yD`qGQ9rqm?Lxvj~d3?4%cR zoT29&J8)W@z%XnOiNeqE6W`&`<#R)t(hmiyhX+2?6&)@%aZo?pslaoS?9lCh4O{(n zk1d?}!{a%f!DrR=`h|{8am1t8$csYQQ|?u-)&AZr2o6>PseA&P(SyyFjAFhXb=y9M zj%Js2vC*-gotE2Ke#$job6HhfJyM4~BK@KjTRRKy4SH%iy!n`dRGE+eUqRZrm+!G{ zF83)&0NCYz7-M8^OMuNxq6Fhpq{gG;) zofm;PS<8+>G)%tF4-4&*4>zo?>P6O>KE4WNl$jRULC_{f!an}d1%=goC?i81>wLR6 zh^f-=DkM!v#V9`@el_@u;bx}^K8hg=;>dHyiitop^uD>Ne~*xHdO39cgjlxwQMl;j zK|OuC4%qbduNhvFcg!vF*l_JeiDnu(?u_7Rn*lcF7K=KvBlm0x4z+*WhFVXKR_~E~ z#bP>1G#Guw`WzO!{suI%XQA6jBrKN$kR?9Or`L|pN+4r^8PPjc^@8CMGVM&kZa3MO zi__J1$hbvvP(o-`@EqUCLc75Q=ISMvUkcZZWG1EXu}~PzX2a_(zGIq{k;8A!PFNxU zj5;$nz7mMA2CCLlm;Pf-T~7}N6`^JD%-Tq~VwoaWcCvLY?Gq&x9|9aB>_2Y$QY9un z*H!fO`YN&;e6L#*=@b|2hUN!U2Wu z<3m?ukkj4?x+*H?y>440BQNm2pt9T3B}}2S3y^4Lst?T51paCXBnJTEL4#I9rKyj^ z7^|*_`{2s=*uU!B7na5jP}S}nx}3TO88tXgzC~YzEWxj`WdD`>y1cQ}%sdmT@Ll^j z+vMeKL(R7q^51!+5{M;UPXS_<#kP)lCu5d$V>xbb8be@>Z9K7mgv@+Tyvm!S`&5I& zqdwvytk-2xfY)vHhho< zGOfTZ3AlJNh7Flh0ql?z&!@*S<)-$_J%N5Rd*kT-qSHXU^W&Kh6&feH8)$LUo z*aJK*JtgL)RDcY{2QuPNVPyPgV#mz1cCVH=^X0iBC_*RmwaG|--o1qsN7?o+vN(YVF*$EM@U1gI<86?u{k8T{ zy^b2HmMqZO(Po;Mm9(qpi?F1Tl9fIRko2^jH}Dz2^CgiN(GJWR>}3uFkC%B3V(fx$ zs7rQXjq0fCp#*8z)3SvUVT>Wsb=cGP>+lJL7j415PzTl@|*8Cxy3Q%q@IGMK<3fuoj244 z3WHe%GT$-bNmW1#({@W7-uL&f1gJqDVb95D*a5%?11l+rW$y#Q)d@Cuq>DgaEq8jK z{}{(Vg~7Z?ADQf@L(ASmX!FKk(UaIZLE2d@tm9X_R6XhO9&7+ZE?`Khcu}?M* zq2^C*=TX4744NU43^CeAY#*?DA1~L5{%XupjpEfvd=~Zo%{jWcAWIr1bMw=XAAqn@ zQ^6`H>I-jsb-oN^LBCk^Dp)R$-##%5)9Qq}{y}K5rZLC*^J20i9#Sgez;K39=Y=?& zry|LeF9&^rE}wPGcN|;2vdsCx6vOt9_vU%d>xs06NLwF6wBWVhF?RmO2iix{A2^Og z*)639`GrD09s@R%_Vf7#@aZjHPBOV{gjZ4uoh<|+ZTm^RYnAald`pAU5;yE3l$v-2 z*#S0=z-pfU0ucO{$1lUIps%Ip{r`dXWgO`JoHr9wSlhQ4$ZS&gH<>e==HKb{}8+}6*4^%%ZkLgeg-g0yMd zDaCi}_o>}KtgX85x;7h`~9`gw*xp)Rf6a$nwM=z(H0gbJgMI>iC`%V zSO6NnQK(u42%yQrXja&2zUVpjr@bF<$0O(KmytQS^bcOvI$1Eek~`%{j3CD|4-g2O zu{AVom017|Tj$D?4jUrdIn7;SD~U68+X&x#SyrhF>ZE2Tf&?D4>=d&viirMN0>wb( zRzw`=@W3eppC&j0S$WwY2-1v5WwW19+9xB9e#p|cFjPI4Qelp5LrJS}UtpxON~`D^ z#M@Lyb>WPXkW2g?Hvgb2M(UQ%94r+kc>uL2Wd#Xk-LB=*@T95a4X0u^Ybp|xw-vQ%aJ3o&ED5P%PdwFY;Ktan5 z^rCl>_J~!j_2=wbf-GLrgY+Lb06^g*bX z_Hr?IIOyWGZVSKoTO?3rwl=@%JkWTxafpBDluqZ7hnJYs@3bKL=$0)GDUkt)(=Bs| zYCv}#>PxC50;}Erq!z!!A>$@ycOyDld#MvrlS~HMlPpe}R={eau5Fx=Gb*jr%N|in zgnyRPtAdVmJ~u>#Wp4m-+IfxO?JLO(HsTm4RZ4cHy}!FabCTcWyHFh!>enf7LRrv6 ztK^gixzV`-#qBh|`8l766S2SRefg$hDREu64;&sM9?NdbpqU*u3VGZk((ocdV zZVzisjN-uH!Ni}M+^T+-qRAt&kH)Ffx|0)iC!~>t8eSSipnh(4VnWDfGrTa1Caj>L z1m(A0YM$o&*8w01;{07v(7=PB@BW!NgBbdt_W8onp0z)J?zVxhk$-d%bq* zw16K_J&X>zn&7!Q2XUIXgD1$eIwSr4s!&(1^JK0f&qecR?IKltYg9v@m@RE&gPK*b0Kx}?Pg-9qT?$g@N zBZ|*YC7G^}41QKdcPPEL3hnrNvd+XG3)U__tQ~b>08wu3TRM-i@N%(rwArv-QSm)b z*H+>J6aEZI=A&(YZRbqo6$Lw+rX}Q}(HMQv_*P^~G z#A@V&YNVFjghY#o-o#V{2dxU$(ib|u_)l5JhrTX;LVGe?(KUw~M|{A}yagS20+P%U zsuZDV0EI|}e}$Uf8mNgu6TB!*Mw|8WGH2hHSg)`Q7|txvMcZEynX@Dg*hIher$4YmXLRSA0( zBNhm}ti=>cN7q-&D^)35lILR-zvh z5ocSh+B50Wf?KYH4LA7?BWJXWD86HfTC@kyK*4_~RQ7+NjHu8dw!yaZ!{B>QvddLT zg{Bv!dIT5W2$z-@-x8D-=Jz{E*TpP#PRh~}v$Rt~uT(5AxbP>Py=YJiB@^l}IqG3D zsZh}P?d|oBs`h zx^(~za7HJkr+EYB7f~Jb;!lg(XV@-CAR$tCO(T3#2%fGEQ+2!{(~Mtt*!%uzIrdf` z<;2e1gU0qxsz=N^4y{t@Z{%%McMAm|a z9fZfdWTB($Ha9$*Nq5ST-MMc@$;+|f;3Qdire@oMl$3`I@;JPEF_8;JuTa3edNReQ zSz5qGCpVK`S&KBFSSJ-UB15=OCd8i=anRYbb4XhX#&5;oX?JHR z9k+-0_)C#R8+V1lNuc#@9COv5@^x9WiU0`;S(7(x?8*HFpG1&DI}b-m>yIG2bvT<{ ztyT0b83OX9-pU}OV~x#&oUY;hE(doPo~obTV#Xso5MGGa`*STSp;agmIkezCdMg%| z%($A_iT>rjbN2Tn?!#vPa5Qe38x4B4pU_Hz0mtN0ugi1Tt6>-=UuZ(du2e@rk^3oY z?n}tAmpcXSi83ey3-5HikeZ1H0<5bhhD-{CQj}o{|E3mTP#v-DJpF^@998{gvh%j! z#+u^AVa)JYJE>v4Uny^sA9I>>k({1Wb{e-M?fJFQT}aP7SW!*jZT2VpuEEcrORwS2 zR?N4Qvm+R0KNIE$@MXg$LzPx=pd5L8hXS8+WGeq8*hlZxB3km7S67`T|8>pRHw2!{ z)8!51*>z2cXk@yas%rE#|5)^XB)pm~U z;h@<6VuY`K_Hf)#j2xS%d`>Hz4bao(AGan`cPOn;=wJe=^;tfJ&Ii-(Mwx;GCTy z8!Lj0PIr*-EpT)s>78!N2YYXb7y6z`7uDiI`XBK8mQbSeV2|FALv{}E_I7mIDBhm+cYEy7Hs2_!C`{YIV^^1}1aQwyV zPNaAV41Gr8&7u}ifJ~u2I=e@bz}&sJK4@^D`G_FF*J<3YO`E6oCrp#TDV|eWNq9k& z1W`+LZKX_uLjm+?tMhTL%PEfAjp1`Unr9(xgrSGuVQ;1@@4d2n zkw8YEt=n%%7mZ6^$Ez(w1}T=BtCZjG9?fk!LJ0y$+dChpaq)F@1u;QgE%$F=1ufAb zxr1J0Pl88zZ6Br0#M1DhB*;uNDJqY4wcZM3YBRPOKyyL&wd7_0S&%6vwJMlf$_EZF z0yjCrUQ3*zXL&ZW>sPpxgwBo_YwRff)mO{azn;sY(z#nQPxLLX;Pj=jS>Fjn3PemWI&w9-quNKKIT6sJ7&+ zJ0~2@lce%kfqWYHfYHQ?Q8=`t&JH`VZpauK8x#CBZdQ9Z_*Xqv$sPIrzwL>|T>s8d z=KFhd!LN;R35_^JAd>6#SnKwK_k|A-))YW5EbZJp6F=j3%}gyoGUy{Xv@$cysU(bs z1Iv$P(_gp$9`0USX{KzvW>`HWtd|?V(bMp+)+Vrd5f$jho+U3>)ort;W=;Uuo1vg2 zZXxPqBV4{UMfs_|DHDY18Tw{il^eyq`Sv@BkILFtaUEGp%-*Zcc>bfvKAh}_`=1CQ z#?xTjGCv1qSBB)|I*H6bpsk=tIA?gzY;mQB2>vCvvk!rOTyg`Ck+91Jj`=xuP8pz1 ztggic$DczQzC$B7)|MsZn|JE3=%gUhQZe_M7{;9r$_fskKcjLlV<17`HAZD%9AZW`c z(#oa2uVvBu2Y-MYyj=N438pN5^eMZr)#I91e`SgabPo{=wgYKjq75J87tUKX%q(`q zJ-$xSs4P!~*f|sTEoP36ueKr&GIh8&P5MtavT6n4=r*EdPFi{k>YR{{>RD#_X)4%P zN+2*S)#-#Ek)ca8;UTHX<@i#1(Vx8|D3U9YyURjLmW1F19?7kw{V3n!_o(J}g|YRs zl3R;Xo-k5s z)cuiy@P37vCDDNA>UTqC?kD|PsD;zJW2oyaNTxMOHKHZJ$|cIf=k@cDecj<@z%Y~V zSVgw~Ju`T5M)L>rPfMIWQ@lBU?2Ij1ciLqK7!V!Ta+p=wIvO_{w|bt?e}~I;cNrwk z@|&iFi3ds~I*xfZw+^DQVu=9{($ik&@kNGG-i%5Mfto1J_~6$u>o01oeD+k$Po$+r z$$TpIGtj-m+Q+QmbtU2mPErHBYjB%NWS&AT`viqSulGK)l_u}d|ucC9WKs_seNq>MS<9#rf!9_6=7w%p4JsG_u6ZW|?4^PQ=+ zEeU1C`y;tFlKZr?k|3xtm>lT_3o0;dIP_Ili1v6%7-p8l`J<11*Bd=PntQZ177Rzk zbv5MZ-5h*dn&0x*({rCwdjwhVkmZ4K?hwG7FDnxoQ%Bx zoV2R#LY#%DDOKqcbYL-irK(l3vaQd*eOB_J0mB6-ohDTX;^D_Kvx>5Z;WOR;sN# zx}Gg}k9og871X<>TJ+|dsNr!PvShE>{xzFtQ|BgG@0ndqu+^!m`2lv$;k&F^YLN`* zm~GX2eUZ-MH;QM&r!s{sEt}S^1kUchr@n@`smxrJBo;r z4Z--KAI(oO4wDHl^PS9>8e2lL!CRWMfl`QLV3?!G0{G`q-3JX~mW{E?{Zp-Y-fCs! zS$9s|V-71Mf3RMR|DL^72`>+Sg*j{)73d-N8e#x>%-6w9*&@q21q^uU*T1X=NMWUe zq{C*blJCx*xH;X<|2%YLqBNy=i`hx_j}fkg}@`Rg%0TjVZ)$|8A~_?GoG?Gw-*TGPRrL%)I+l`(?I1 zHoU(h<3S$>NxrZa#6Y}|CN=pA+4rEUE6KI#e)Yz8i2Z|sart*~*|_&aqj6hC_XXN0 zO?O{r`*@C*+qt5vg=L>^?$i5M^uiTirWam+0=<-eULn=*%cAyr;#vVhRJvLgU&n&I zv#F^=HCi%#29@xv zk)6v_M>F_5+1n$Tj@4*ch?yr2I;_yHUZ`NDf{nPNe8d8^vvo!xeN`l$aBB?9s<=E< zB7rT0(`)Cp{=HXnfD88qNm^A}=_oiqDHWgHPwAVMQhLpX($!WdJ?)vM;&rr= zssv`i7>AuRHH%B}rOLF_#AGgZN|uX`?#7qHi!QlA4Kl}PiTr&w(`V&rX(V`D!?UCrxwu{Q1xz~_!sgEpvHi2;51LHb zi|jGJ^EDnsEMa-CXCn@t&KivVh?{INLd-{IXBuOBGbWs^sEtN;)byQ~+YoB2Zi-4F zfqb2yxJCc|O#;tVQAu8Y(uiU#EGDP1Vmh#?MSO=kcE z+#j(dgI!j`{W5g2b!IkpXRcnx8{raZS>3u&Y|^zW#rMI)Z}86tPu0^{M&~`2^U2b3 zi4=PZWZLA)iPXV=;t8`nz&#}J6Q|vu$ZZ>Cqhz3eJbVkSb!XFUi^b?H(3BUBTK?xO z5<64F|Gwt0eymM(%HW>ptbxjpxLPJ3ih2ik!{(;W42AshhV$5wAQWImD2BOKiF^eNH4f{XLM`e5jk?Qjl4|H}CNjkOO zt+|#y2k2R?rOkAKr#JYE`tIUQI9uQ&?Op5h7X&nM?YNIDcf`fL-<=Y6>6~cumTWww zf-&U1%^m)1&(ZF}E|Kqnoe6=wEO#Q)mRAlcILH72KM$+47GH{51@4IqO&50ge6*6z&`!QMRILh7Ww2B)F=c*c;w@oULw2DXd=n8y7}k$I*Gtj*H!#t z@-$fyMH;hTla~Y>L1}pTCT~Ynn>-&*ASwpIQ&K@~HF-P10QQ?;-VzAhO>erKP>YQP z#@7%i`m{{RcRiUH@`!Y~7n6~ZOPE1dOd}q0+Rb#b0rUW6Fz00vD-2dvhVj$$YJX)H&`v3rPyoSXFEdM5G4#A->mi^z`#*(j82(#M5?1E5!FWWthZ;=lp_0@vMo#;P4~^=wgsQ__nYqDM<#XuKCQ8&@ z3`OuACQ!b}9+&VYYvtMz2%0J^V2-eu)0M(m=V9gcS3gfaulI^RY_s+)15CGWU&Yol z6$s24zompP+L6)aEhP$EuiCRTM_T8q3$kDhcTJFhv%l2%RKcvwvI{YKzJ=g23c&Tt zT)fm>*K^|KAeL-g^#$D2E&Y_(1_1DG2tIQ8SW}X-$ZW=#WRl#Ov*sXr7fPgaK+a>RPen z0ZSTKt2pTLTnLl+uh12!VeIFVGGH`N@jnI+l!>hrJuIdM_9ALP!AD|4t)^p2Ln4WQp>T@LZ0YKOhnf>d2`hY%CCxJLzje zfJBa{zWEZDn7Mk&wS)<_oB2gR4|j-E*@^WNRWiM=mHqh%8-H?XaNe`ONMY5zz);12_G@!+QkL7Gf@_+w_J-(b z%5L-+_~dNu1}2nJi3WgnM-oB=G{pf5B-SLw7p;2Yc9n9Bo*|1EFJJunkBb?=RpIGrKvwYAAz;O8uNJj&t>lvdz zdyO(-i5yU%4rXJ1RsuK?pIjOt`-J{Oqw*Nq#~x|lu3<=_5mW#>QS|UB!T0=Usmo}P zHKCNJ;)D7u$fm*_HPws=uE4-3eiX3~Tv*9D{tq759PuZm4PQnVLV?#~0yG()cxcav z;na_E=pi`)tz;6AH!lGB?5Hrkb|7+PNn~w*VOG`V1+Oi%jfiM?q>Ke;;#=Ki0a~>} zc_i8d3vmW*W$ej?Jrs!D5hTC72-twMl)d!BIpEt`1QsGriiY=kd;ZaZJd>jU*niwU z$_aJ#$kn#|gdug!e+5umL=V9m5{8O*_*Da_RSKa`lo9buKbs&h z3Y+7DCXJ_y%DwyTlO=LwT5`E7(XNKTf<}$Lr7jtU4&09bSdYs~uF9_8#2g-Q=GY1qKdb#Qt!I zj-XJtRWUUJrFk%A;Qkj~wfm_haERQn`F_Ivo3{iaIz2D#6tfs0^E0d*CE?Pz2#WX& z!oUc2f}g^_wQ>H2&ZXpfs}JpKJdC4D*Zc2*!IHs}E8Xwf1U{6jj-2h(QYuTXL?0s# zIByP<-3D9Wmi-wfQfdUm)wIrkpZ2P`^a#wliRvE4Z^g%g)rt=T|PrsFL1f1ao<_vroj4oeCh| z8Od}$^bXFh8BKS-vPo>g$g_LfhgOm+Nu8a8{Jgfpl(RXe`iPM(Cvs_sXsC?# z{eR-sZxv1b6R&D5S41cNgw6Hsm|Z>OE+o=Umei}(`aPmj5C+vJJtEOmq?d?a-(+RZ zIDB=M4b~qJ>4Yim8W(EEjZ?0DT#%den`cro$-WRy>3&OR^!9^yd|O>G%zTZV)+&$= ze}>Bhouo4nVfq)oJ*@QY%OD-qaM#yoz}bS?eQgr8Og{$-Ur$EXB8~LcV5>55@{ouF zXAjd;;#$waxe37*_N)Jb1xY^()`2O2yN7ObjHe$A${*$tX$@Y$g-9anD@n#4G~&s$AL6<{V%8Eg2Y#Chz1o zv+%H?ZU-qxSs-W}XB!W+Y_7m}3zEYQegWe5Jj>~U=sN+#dwubo(GpT5viO0YP@)rv zey$tLvKLk$UCUfVfw7Su3xN%(T>IMsRK@?Ny|a9avWwdO3`j@{NT+~EH;A-S2B0V< zT_PzVG17>1iQI^^fKp0HGjxL@APpi6CEW;;@0$BQ-uHO_gy+NKSDZc9Tzg-8uWPOI z{H^vxV*$luB*4FhU;ftJ_mgVS&H((|(G3 zT9iiodr1V+btQ(VJ;o_k&`rIYtG8f4vD2xsbJ)hYpIf?4@EBy^NLtTV-Wf8Zthw;( zofqy6fvQi9tbOg?7Qg?6R!Pd(w7 z9T$E3PHA+MlBmVh8F*6lR-!JzeDwX<+NSTK8zH>QOR~O+ds6Q@!J8~kYD4`lg1x{TrY0t7NxggBYKY`lx*uaM zt>&f|!`}v*$F(ND2Y)GTjN>ohX$;bHEkThHgcBqVuv6B@=v@QU}=ZV zrKOZ4r}fQuf@i-hn+%#_13!N_<|wfI9rWA9dO7Nfqnaqiyf&^XQq1Kq`pgjY0k>a* z(G{bQ4?Ff?BPZ13bW&UGItFf;FBKEruYO1($5AYR==vD2?OA>0)skQoi9ce?W;A=r z`Rn+DQkBubaL+bxBO35-WxW0I58WvhM|`K(sk8p$qu{$(og5}mt{3bwU`5g&yWL|u zG>LR$dILL|jrube{VZti|{@`qhF z9G+ud)EW)b3T1HS#~2(kAA$#*jo+@FappvZ*+dRt`YiWn(5KT0{%eo91b+>RCVgv~ z@%$j6$8;()v>w#?EN(h+!lrRgt?Zvx%DE;TSu$rA6nFco>>_YMeeT~ zPc1}Re<4)GbxMXK#}D~_^}@_QVhJwC~=~HRCJFDH(YtKx5lM55i?^msr8o>WP@jtuj9*yK6DN99x>dJN3(y-;3ZY-6O(K_L+qy@&62eOreA8YgR_ zN4%e@Li*Z^hi5tDigiz0sdgr(4(gWdIYGaO@e(HcS0?%wlRyXuZ}5by2NVAq;n|*- z{`X}z(D@KvP@ONYUZscG z#Jqi19=%fW+?VRDAN}`Al1Rt4%!Xich}KSv$``>I1535p;^7vZ+Bw!=q@8f0xjN#a zmZ5Q%3D4~?YWxzM*}BDDPKkLft_EbR-+jZ#7v%$T>_bexT32E|W+u*5yoj!8TA{Yg z@gqpT>>GIjgr14 zJbUx+I;BpRmlty*^SMA#A*1O|7mU1+nMmP`7geL^Kuza=&kwyJna`-up=(J<{r$xE zos56CB6ieN$z5H{pjfpcaMbLt(*A z;GXuvc^}64yt?F4+-ON70a)fuyj7Mpz`%jjNSKz?qW`eNQzy1dCZBqK>4k=?SuJU6 zYer7Qfz?DnG@oxQqT!isud#lZnOH*5-3Ml0Y=6xe~XF_tuZd7|&y=_mAh)u>11W%H@P9{GE&V6Mu3d_5d)1_ zsc+fVTvAlK9-U5o>y$jOTe=n{d_=F5N|ldWeA5<2@TcL@G`^h{j6=Su;rx~2OJ1x| zAEI0&dykHU+#gZ0M_S@$;)ia}H!V{!h|kb`1@6wJJsIZ@YTrXBv(VVb|EVa?;l^!5 z1dfqmjD@+9#bdhcoR>(mSvfPy4iJrV_PdcLwkB@JW}n*}=Ce#DTs?987IyHSg7f~V zpaZ81=nYkn>2jBZa}Fi`Q`5U0F-!eeCqVc}(Y)lcS0Xg~sa;Zp)HA_*$hb4f#j7Pu z-?qu+7dl9s8>e$M4P8&{Q8snBl{j%}^(>0pkmJRQ-k=+N76-w!{3m;G^EJ2yrm&s` zltncu*(Z98zUoHEr7}Fa7mp`28<<^rO2wp{9JYhOt554B5>Z95vwGQ$ zEuFip9O?0>N=xlw!)1YDX%{TQWPMAg0m1{es8jx z?I7pHMhrL7{5nT&G;bOksz_feUHa0C2g>^QbbBLozRdb5;yI4&VKdh&(zg2*ZDhABd|OQMuj|f z*q^!NN~=gU1o8S0&avsb>BGpv`}E#N-)A0y)^%wQMF=ndFy?i?2kqRo-xHR#Q)oDN zPImQn!4G=vnz!)S;@i>cB3xR3N23Q~h5?oe1j$Fywiyhn-1OL~R#)cgadn%lT6T7y z`5X8fQ4bN=vhHdigd3xU|~E>6KvAxnuGgXPI_*R0bE zEX!UOtU2!TPB}bBR&U)8NEXZSeJZv!(-WU;?F22glxo2Loa;T#78z_hL z9V6%GTKs*&1=Y1#STC^+4o61$yr< z-)9DYw3>>hbb1FHH(onVp+C#xg)PJw<>SO>AScNwie;YQEaOg|#^NxC&R5nL%k#;P zNm@st@rOU?s_~t8_1%o(DMJ>1P^%0lAuvO3 zTWObjIl#D|z-hNZ3${nRPvfr=V?3JebcLxjXFomeX1pGul2wnNZeFk$7+Md^}aJ#^JV;yGXS%~!V@I; zg5@Jv(b~+1cR$n)ESAnC;UW=DNvrmt)R{MFg2i-30=i#EPZKz%gvwL%r=?^q)Ju6%7#g@Wa zoG_(cTc^#Nb03=%dt|5!SDSv)&eof0$30c7ON>1=0?#AVqw8MS(u3>a!{FC4zx);K zyl7pD=?SP_hhN=Q$K{K7Nfk(SxX>je zGSA98ZJ?l7x2QIa;8GC&bEV6H8q+t-Uhx?0?cWm;3J2vIyoDe9I`>mXLp{K~^O?w8 zs2Un;o+0UQ@snpiXK%m~0dQ3B2NxRQd_Tv19Zv7fT;R2kiofCNi77rfmtfiSW76ed zb$fP=imLc;!o3cYPAi&CbKH^r^psdL-hTpssDmA|>K~V$RGUQD!36Pq$Ygc$zMOX} z>z{ckB5dd{ke%H=%Gx$mP7EkMH`O|M%b*+j3HRBjd4|O6wl3u}?SZu0@tBPF!;b@} z3I-Nh?0vh(R=Fcko0TMYoxFhjmrG@iXnJ%!NwIa~gK5xheAtc+JH1cGMlpc}%c#>? zN()=um?$Eq3n3Ya(;K~_A~QeFA*^HY7bv(Jk!98|q)>uTjIY0ll`AMHY&~CdoG?sU z?iuErl`x4J*uea5_tyJjBON*_BzSY!ZEbaH2Ib_Quz6)71{T5FEN&F$!8OP`z*A#_ zE{RGl=x%(l8PwE|HS;FDB`7*WzB>v_QDXRK%{{;m&5GSdu#4?D`9O#3aJd$xVS1Wf zD&rgZN1pFj*_#C*HW=&QV$aT+vhM^da?M6;0hCyWB_Qjn_y26fXjKOyWPn^OGU$G{ z(}9Pz0__u#C|T0Lf`?VPjAZwgA4Qm?(Dwb2i!^0A#3L(b3b>QsuXaAzOqy*0|fFKQr~ z|FKZ>YsQnrTM7*{g9>7c!JY#$j~VXKQ?2L{?IZlKo&}p?RtyRsunwr-Jw(Yyz8%_& ztSHI{>PKJYGFg~zykXY%kV|J7`zC2MjLryO{Zj2*vd;#Bk^+5- zWy8E1K=v@heA+3rWcFeh+pWH$5o$qS_xuUozlSv@)(&b&`I%s*p3X3AgAB1rD4v3U z9=S>A%}m=3Vy&)9BO?JYts(uL{ubS#-acD9rkkWguV=W|pq)Rg6Na29C8W*Pk1xLD z-Z1+9gxI^0cu&FN_F$mPS% z{HCY0Er#xkAUTAPX=dP8`(%#3q5;~}>VxD3{3y<*?-~b)=j=z93 z@t(NM_F%kRNiyjK6?Fp`e|=xXx;?LmAXkjRANg4KwzIg?FWS#ZAtWVaV7chW8;%6J zeF!63{gLGi&t30o4sRVh(Uj8NX|}AIDP~=n%ONkg1FVM;;U#rv?*TVTv;cok5Q zfq6>)Cv(caN!lVy6bomNY)o(a^qVGb)QxFYSZ9SBJFi^5uU?kTsbWH#T6(5*ik*@=vr54 z6M@mep(KFvDHdU6Lnt4j5A7-NzuwUbXb24q%x$|&ALoFj_mF<`e|#2i_tTO6~T!#e?&FPnmA|TSWG3y24N(haA4badWcAryl z6K%C*YBH@zN-;rc4PH=1T`Y^AU*jU~KW6Jcr!#ZcU1Z6VoPP*bk*1c>gcmkQ1e!o< z2hNSj{;x)lDKYm)1LIFMP+tBrt*pb;SOCs{*5zv9#)~Qx9D$)|QkIL~+LQ^S1R1^4 z(4`RZt;w~25h;&PrV38|El;qM)H4-YeD9S4x>Rk_*yQqHL#<#8%6kz3<3U0+YqI*r z1;0%*`kbKQIw|RA+xC{lyN+utF6l16U6e+3wMhHL%@$t1+bA3&bl2MCFRIY! zy6aij7yFc)PM;wn?BwxRwck>m2;+CU`*{8Wt)6V-o{i%m~Q zReUv{9llqEFknvrvMov75umB5WCB=P?jBH*K2RbadIsFV(5GmIHEb$!ihDr*L4kb<#s zI~*N|t-U}G+5TyVL-+6kpz{sL0oHOh?dvB9!A=3E&R+DN?;_H*Y~;%o@#)v5JIvFlh`w_Q|D(+54@Il8bl^i zKLg`5M_NB%t$S{<;6H{KLKL-+L)L(ttKjjRp(%}VAejo7FA6Liz z5vniZHJ=Y7Bh@>@KewAK_ zmI5M-v+VG+Mg`OcQjXRqdsgJqK1PFgqak^-Bo92|!zuXNquHQ&2*UbDXL+YdwO4J- z;>LM%!Y2P91%RR^C~6{k@$-Eg%teuQLjn8aXV&gG$EFu>2)=rB|J~>ORShSDZg8J% zyJ_kgFsXGbSO257Qo_!EvVB6Sn{5jk?Y@}u#9Uo7ElH%hANO~wLTkW9d#msyjrazj zD}H!tn^M|ad=QdFD$}YkH`7((EBRZc zx1nM+L1?7TFg<{7fA`?+`Wr}UWtXxE3Oe8*u-w95nE(p+>D2;nG3OgaV4z9x5p}Of z_X7p?YiBK{Ji+DZ=bZ4gv_V$Av&(Vb=I&uUvz5LUJ!2Ndd(1kyMhs)`1l89yBm9e~ zY0!DgqrP80n=2#$C#L->u%+K~XT3homwUoJuc?g-4>@NtN&EJn1!~YR_-OK(-1rsk z;gp1{QK(Kl9b}GW-iBo*!1TmNth%*|mp= z+c_oQpwL9w!OQ`z1JDdz8 zP!@8>!h=;O7YqQ|W=g)?zJZl3gp*|1O3P=|x`a5VjK;9CtlzqKwMWy2;ZhwAPzi8u z*FU`6w1Tt7(zHlD7~?^1#^*CGH|r@GTNi&%oVu%{eaM@O%*t}oPczK_dt0EH(1$n-d06<_;;{V7m6xr~PbG)XLJMT_?LUVUW=a9wwqi2)rDSf5t9!NBY( zBfyawGS1}49jY7S7CnBvKSIt=n{w~f%`%NBZeA`$5!wd6Ss6Cf-){9=#sWU02nj<% zl#Z+F^N;#zP3hO#@MDQIzM2ATxwVuHHV?rR`e}t9-SKi^4N!W~qKC%^FgZ``?;bv= zA_s(SlXZT-+Y@;X%~1Wrhf$goQ+#NGC13s2qlcMklOT=(00-n4@DO_y)mx_=X`eUM z%8B7veggNaA@y-*M()V_RYjm%*LRBYIlbipcP%*#)6Yqcct7YK&AUH#b zzNJwtsD?dTyoflBMXqyDPuXPKNq_(TdG717dtNeG?9@x_mph5YGH}+?!nIMq{`@8eI|3!yy+J8*)q{Yb*zT}bYQ`Ca*nE?0+@Jd2e zmCW6GBzc3UFd{MfQNzE*&AeN1E{)ldu*QS{U>FKbvZ7-zA0@Dm^51Egd!XhSgWrIvrg9v9 zSS^`>)D?=HYE5M+a!L;v8yax=U>7rwp30kk&Vj7183@V)a)GiAY#32RZCe9l5I_U= zFtHJ}mj>Q7W1P2rk^3+II(*y=vnm;Ds|7oEKrwoDuukQLPAia94lO40ro{+=JKU|M zz)lZ~yx_KMJQk)SLVXv3NH*4$m7&aQNVc{k0}|(F6AF8L3D`504_d5f?2n{l!l-1v zjezTm43?)n1F;1M&s!Ff3iLJ*ecyUsc>{%|yq?QyxC%*xSK=s|?f&B;hxTb1vkoDY zugv%75e8NGZ8+EO9zZT9u<52&Q%w)Bvfxy1*>7pFeqTfGk{*ks9GT~ns}oGbRsKF)$Nv)GZfzmnBiQs*e<8DFv8 zp|#*kq1@5e|3AF>_aP-cJY0D_!4~vBQVeYt%1l$b6@x9;zkY1na|RZ4#(Z+KYw1|P z+j{oB3KE@#+tVJ`?;reumx{GO#f5vI>9_%b7lSj}7L)059hk(Q(x+NHv-1_L$n;Y# zu#5fF;7`eAhAm?CZ}L$)LnALZEVst@w?@45G!G~fAmyY*TJyp{Un?NUSYB3oh^VoQ zXq4dB9m$J8jebCv^n|TWFWuw4Z7iYpnS7J3Os<&&yxRDG0U~C7Fv!;dT?yqPVXO^oe4(p5qK7F*a+ntI9X2A95jE7zx zxZ`zWmxo4YTjEn%NP^< zpkS*JY%zCbzFio(nUr_9Yw(^sVCve-w6c#|^^bi##fRO-CI#d5Twb_Pnf5(%C^RHJ zT*Cs+A@C~N|iX|IZVAK!~%aQ8gC>-WqOI zNn>m4jGFdUSf(pcKw7V4YS$B6HT)<6>OnQH6O{9PvASV>^;#x3<_Bf74we$v{B)~M zQ~SaK@>UUhj2saWIEob0)bUarqYhN|yWb}L1kW07;%f4zVV_bg7cZ6fY*GUKSxTb+ zC9IWq8qt5JgYyhvV;ttu7<7@l+&P}KUzl%RcBlDvE-g7}`THc|%{wpmm1%&O4+@~l ziW&~s<}iUYYmNs^ZE&p#*lSG%B)##y*|l%dG?L09wopKy{3$g)8r48^Eq7rl{qks9 zdPc07bm|xb|GJ~cEbO{P+Z+X;MO7xXg6tOdJ7>`dSST1+XJj5P9*0J6@WMnm-UuPy zrTBd9*;*Hio#$L;AdTB~or8Q1Wvql-3)kSjl?yZ&`1Ez4@COr?BqCiWHL|MK)Vagt zrX-x)0qZcTu*0Lygv|ZexvnbQ{Uprp683}W!7$C-X{esauWfF%m@nj+aX?Zss<^}X z2~R{q@7~C!p`0Ys>Oz=^Rv`pZ?C^@!ew!$fMiTh+P6CV9Q?RWfTuKHb0mRC`D4ox9 zyDC9X;@`3fWvjYW>709s&`dLsc)XIUt^dhm0W9(WS+@hr=-X_VQzh=V<45&VcF;!&)FvTX*V63|Nw< zNFkteq|RQoy99IQb|2nXk}56~5zGOlt}5mjr_G=graELomd?Smbu=$GMmrpmi2xKI z#7HfUJ~FgFLb#~Un57W6BFXU$Y8vA62C%opcTikJREk>DjQ8@ibWnBPu%!}o@&Hxm zp7;$+<#XnwILahhDAe#faAI$cfh>BiFU43F`g}maM)iJt{G$V9QXtEQeE@+U2v2iv zxu^$I>QAU+7Jmf?J}7b`_KCo8J5B=?0W>pd_3~9N2XsRi2@u}&$EqnbD>u0X?OZ1N zF=ZSx&%x=;G2`B3jnST8hY0Z{*1$pXazI=+JBhi15lL}4A^WHB_0;5=6|)+9tpGIc z;Eja)BVyo=R?Op8EW7ur#iJOSvdF;y3n3O+PK|Ju4FVHp<~u@f#_lx%j)DO0;g%+~ ze~Ku!f%`H#G@bZmt`|`dq4oyE650NZgI4ujmKk9Rb+PC7LNB4ztByFMT;v$KUQCKf!Q<;V<278U}dtiuu4?S$7d ztNlCe))h8Yl6o>eA2Q;fe_Ehn5cJN0o{MI7-^qMiryY1q>pXrgo5 zN@rUxUQIkrjXw>&9%d3ZYCfLKAcQYpl*GyY7@qgMf+ssGBVA*v7L#F*MbUFZ5$Mu) zm&XdBI}x@=5p*gVBc3j_#wvI|bKyl~DQY3`#)zV7iEG5sUH0fzT}K+KWaI0q;we)jh46Ozh?AWI*1VPm(BQj2M?_my3issDs7~#K(fVmx z?aD;xCdNiBe6*e4$q3>D?L=u<;n5`I7Y5Eh&r_+xTY1Ky&!@x4f=AUe}fA z0kng8n_wdL^ag6M^j^zt*7wx4s*hZ>A0RJt2Wu(e=|<9Nl|QbGW>2Ob>$R?4v(~qF vb7ZHGfQ0*heY4cG|F?hE|ImhGi{}J4{iYpT@(4E(@b8X_rgG6OvzPw|wRLVF literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ab9dab03e543..564bf6a86b52 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8849,3 +8849,15 @@ def test_xylim_changed_shared(): axs[1].callbacks.connect("ylim_changed", events.append) axs[0].set(xlim=[1, 3], ylim=[2, 4]) assert events == [axs[1], axs[1]] + + +@image_comparison(["axhvlinespan_interpolation.png"], style="default") +def test_axhvlinespan_interpolation(): + ax = plt.figure().add_subplot(projection="polar") + ax.set_axis_off() + ax.axvline(.1, c="C0") + ax.axvspan(.2, .3, fc="C1") + ax.axvspan(.4, .5, .1, .2, fc="C2") + ax.axhline(1, c="C0", alpha=.5) + ax.axhspan(.8, .9, fc="C1", alpha=.5) + ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 0a1d6c6b5e52..8c0c32dc133b 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -142,11 +142,11 @@ def test_nonlinear_containment(): ax.set(xscale="log", ylim=(0, 1)) polygon = ax.axvspan(1, 10) assert polygon.get_path().contains_point( - ax.transData.transform((5, .5)), ax.transData) + ax.transData.transform((5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((.5, .5)), ax.transData) + ax.transData.transform((.5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((50, .5)), ax.transData) + ax.transData.transform((50, .5)), polygon.get_transform()) @image_comparison(['arrow_contains_point.png'], diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index d04b59afa9d7..5a7fd125a29b 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -671,6 +671,7 @@ def intersection(bbox1, bbox2): y1 = np.minimum(bbox1.ymax, bbox2.ymax) return Bbox([[x0, y0], [x1, y1]]) if x0 <= x1 and y0 <= y1 else None + _default_minpos = np.array([np.inf, np.inf]) @@ -1011,6 +1012,10 @@ def minpos(self): """ return self._minpos + @minpos.setter + def minpos(self, val): + self._minpos[:] = val + @property def minposx(self): """ @@ -1022,6 +1027,10 @@ def minposx(self): """ return self._minpos[0] + @minposx.setter + def minposx(self, val): + self._minpos[0] = val + @property def minposy(self): """ @@ -1033,6 +1042,10 @@ def minposy(self): """ return self._minpos[1] + @minposy.setter + def minposy(self, val): + self._minpos[1] = val + def get_points(self): """ Get the points of the bounding box as an array of the form diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 0a31a9dd2529..771cfd714b91 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -433,8 +433,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, Notes ----- Additional kwargs are passed on to ``self.poly`` which is the - `~matplotlib.patches.Polygon` that draws the slider knob. See the - `.Polygon` documentation for valid property names (``facecolor``, + `~matplotlib.patches.Rectangle` that draws the slider knob. See the + `.Rectangle` documentation for valid property names (``facecolor``, ``edgecolor``, ``alpha``, etc.). """ super().__init__(ax, orientation, closedmin, closedmax, @@ -577,16 +577,12 @@ def set_val(self, val): ---------- val : float """ - xy = self.poly.xy if self.orientation == 'vertical': - xy[1] = .25, val - xy[2] = .75, val + self.poly.set_height(val - self.poly.get_y()) self._handle.set_ydata([val]) else: - xy[2] = val, .75 - xy[3] = val, .25 + self.poly.set_width(val - self.poly.get_x()) self._handle.set_xdata([val]) - self.poly.xy = xy self.valtext.set_text(self._format(val)) if self.drawon: self.ax.figure.canvas.draw_idle() From e6af5a339c7cea73fa0aaa587e258ecb742d4bef Mon Sep 17 00:00:00 2001 From: Chiraag Balu <86278218+chiraagbalu@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:06:04 -0700 Subject: [PATCH 0163/1120] Fix Issue 26821: [Bug]: ValueError: The truth value... when an ndarray is passed to the color kwarg of axes3d.scatter (#26834) * FIX: [Bug]: ValueError: The truth value... when an ndarray is passed to the color kwarg of axes3d.scatter * added smoke test to ensure no value error is thrown * fixed smoke test to use fig canvas draw rather than plt show * added an extra line and reduced line length to comply with flake8 * removed trailing whitespace * Update lib/mpl_toolkits/mplot3d/tests/test_axes3d.py Co-authored-by: Kyle Sunden --------- Co-authored-by: Kyle Sunden --- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index c519bc656533..0c18bce8ebd3 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2672,7 +2672,7 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, xs, ys, zs, s, c, color = cbook.delete_masked_points( xs, ys, zs, s, c, kwargs.get('color', None) ) - if kwargs.get('color', None): + if kwargs.get("color") is not None: kwargs['color'] = color # For xs and ys, 2D scatter() will do the copying. diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 75f0e2261907..f3666fffc418 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2302,3 +2302,12 @@ def test_Poly3DCollection_init_value_error(): 'or both for shade to work.'): poly = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float) c = art3d.Poly3DCollection([poly], shade=True) + + +def test_ndarray_color_kwargs_value_error(): + # smoke test + # ensures ndarray can be passed to color in kwargs for 3d projection plot + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ax.scatter(1, 0, 0, color=np.array([0, 0, 0, 1])) + fig.canvas.draw() From 5e8f2b8a54479755de7c8dace03974fcd6d83206 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 20 Sep 2023 13:25:00 +0200 Subject: [PATCH 0164/1120] Reduce redundant information in _process_plot_var_args. _prop_keys is redundant with _cycler_items and not really needed anywhere. Removing it makes it less likely that a _process_plot_var_args gets in an invalid state if _cycler_items is updated while _prop_keys is not touched. --- lib/matplotlib/axes/_base.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f9c3c25bbff5..7093271f2872 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -228,7 +228,6 @@ def set_prop_cycle(self, cycler): cycler = mpl.rcParams['axes.prop_cycle'] self._idx = 0 self._cycler_items = [*cycler] - self._prop_keys = cycler.keys # This should make a copy def __call__(self, axes, *args, data=None, **kwargs): axes._process_unit_info(kwargs=kwargs) @@ -305,11 +304,12 @@ def __call__(self, axes, *args, data=None, **kwargs): def get_next_color(self): """Return the next color in the cycle.""" - if 'color' not in self._prop_keys: - return 'k' - c = self._cycler_items[self._idx]['color'] - self._idx = (self._idx + 1) % len(self._cycler_items) - return c + entry = self._cycler_items[self._idx] + if "color" in entry: + self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler. + return entry["color"] + else: + return "k" def _getdefaults(self, ignore, kw): """ @@ -318,17 +318,13 @@ def _getdefaults(self, ignore, kw): of the next entry in the property cycle, excluding keys in *ignore*. Otherwise, don't advance the property cycle, and return an empty dict. """ - prop_keys = self._prop_keys - ignore - if any(kw.get(k, None) is None for k in prop_keys): - # Need to copy this dictionary or else the next time around - # in the cycle, the dictionary could be missing entries. - default_dict = self._cycler_items[self._idx].copy() - self._idx = (self._idx + 1) % len(self._cycler_items) - for p in ignore: - default_dict.pop(p, None) + defaults = self._cycler_items[self._idx] + if any(kw.get(k, None) is None for k in {*defaults} - ignore): + self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler. + # Return a new dict to avoid exposing _cycler_items entries to mutation. + return {k: v for k, v in defaults.items() if k not in ignore} else: - default_dict = {} - return default_dict + return {} def _setdefaults(self, defaults, kw): """ From ddad97332fa3f76b7cd0225b1d430e3e54e12e49 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 20 Sep 2023 13:36:40 +0200 Subject: [PATCH 0165/1120] Minor style-ish changes in _process_plot_var_args. - Swap arguments on _getdefaults so that ignores can be made optional. - Inline some temporaries, and use more comprehensions. --- lib/matplotlib/axes/_base.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 7093271f2872..1d9a91d78991 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -311,7 +311,7 @@ def get_next_color(self): else: return "k" - def _getdefaults(self, ignore, kw): + def _getdefaults(self, kw, ignore=frozenset()): """ If some keys in the property cycle (excluding those in the set *ignore*) are absent or set to None in the dict *kw*, return a copy @@ -337,8 +337,7 @@ def _setdefaults(self, defaults, kw): def _makeline(self, axes, x, y, kw, kwargs): kw = {**kw, **kwargs} # Don't modify the original kw. - default_dict = self._getdefaults(set(), kw) - self._setdefaults(default_dict, kw) + self._setdefaults(self._getdefaults(kw), kw) seg = mlines.Line2D(x, y, **kw) return seg, kw @@ -358,18 +357,16 @@ def _makefill(self, axes, x, y, kw, kwargs): # *user* explicitly specifies a marker which should be an error. # We also want to prevent advancing the cycler if there are no # defaults needed after ignoring the given properties. - ignores = {'marker', 'markersize', 'markeredgecolor', - 'markerfacecolor', 'markeredgewidth'} - # Also ignore anything provided by *kwargs*. - for k, v in kwargs.items(): - if v is not None: - ignores.add(k) + ignores = ({'marker', 'markersize', 'markeredgecolor', + 'markerfacecolor', 'markeredgewidth'} + # Also ignore anything provided by *kwargs*. + | {k for k, v in kwargs.items() if v is not None}) # Only using the first dictionary to use as basis # for getting defaults for back-compat reasons. # Doing it with both seems to mess things up in # various places (probably due to logic bugs elsewhere). - default_dict = self._getdefaults(ignores, kw) + default_dict = self._getdefaults(kw, ignores) self._setdefaults(default_dict, kw) # Looks like we don't want "color" to be interpreted to From 89314c0289a74d55d8cfa70ab02d467b7cf34414 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:17:45 +0200 Subject: [PATCH 0166/1120] DOC: Use ax.xaxis rather ax.get_xaxis() get_xaxis() is discouraged. --- doc/users/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/faq.rst b/doc/users/faq.rst index 46084ec4c6c6..c4e133d56d73 100644 --- a/doc/users/faq.rst +++ b/doc/users/faq.rst @@ -123,7 +123,7 @@ The default formatter will use an offset to reduce the length of the ticklabels. To turn this feature off on a per-axis basis:: - ax.get_xaxis().get_major_formatter().set_useOffset(False) + ax.xaxis.get_major_formatter().set_useOffset(False) set :rc:`axes.formatter.useoffset`, or use a different formatter. See :mod:`~matplotlib.ticker` for details. From 3b643d4455947a0b71ce30761a738905efbf6351 Mon Sep 17 00:00:00 2001 From: Jorge Moraleda Date: Mon, 11 Sep 2023 20:33:29 -0400 Subject: [PATCH 0167/1120] Explicitly set foreground color to black in svg icons. make_icons script adds style="fill:black;" to monochromatic icons --- lib/matplotlib/mpl-data/images/back.svg | 2 +- lib/matplotlib/mpl-data/images/filesave.svg | 2 +- lib/matplotlib/mpl-data/images/forward.svg | 2 +- lib/matplotlib/mpl-data/images/help.svg | 2 +- lib/matplotlib/mpl-data/images/home.svg | 2 +- lib/matplotlib/mpl-data/images/move.svg | 2 +- .../mpl-data/images/qt4_editor_options.svg | 2 +- lib/matplotlib/mpl-data/images/subplots.svg | 2 +- .../mpl-data/images/zoom_to_rect.svg | 2 +- tools/make_icons.py | 18 ++++++++++++++---- 10 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/mpl-data/images/back.svg b/lib/matplotlib/mpl-data/images/back.svg index a933ef8cdf50..0c2d653cbe8f 100644 --- a/lib/matplotlib/mpl-data/images/back.svg +++ b/lib/matplotlib/mpl-data/images/back.svg @@ -40,7 +40,7 @@ L 33.991875 44.72875 L 60.703125 44.72875 C 63.43375 44.72875 65.144375 42.444375 65.144375 39.8625 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/filesave.svg b/lib/matplotlib/mpl-data/images/filesave.svg index ad8372d295de..856721b6b5e2 100644 --- a/lib/matplotlib/mpl-data/images/filesave.svg +++ b/lib/matplotlib/mpl-data/images/filesave.svg @@ -62,7 +62,7 @@ C 6.855625 64.95875 8.491875 66.584375 10.5 66.584375 L 61.5 66.584375 C 63.508125 66.584375 65.144375 64.95875 65.144375 62.94 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/forward.svg b/lib/matplotlib/mpl-data/images/forward.svg index 1f4071360680..08b6fe174602 100644 --- a/lib/matplotlib/mpl-data/images/forward.svg +++ b/lib/matplotlib/mpl-data/images/forward.svg @@ -40,7 +40,7 @@ C 30.645 66.4675 31.866875 66.99875 33.1525 66.99875 C 34.438125 66.99875 35.691875 66.4675 36.605625 65.59625 L 61.30875 40.893125 C 62.2225 39.979375 62.71125 38.725625 62.71125 37.44 -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/help.svg b/lib/matplotlib/mpl-data/images/help.svg index 484bdbcbf659..260528d607aa 100644 --- a/lib/matplotlib/mpl-data/images/help.svg +++ b/lib/matplotlib/mpl-data/images/help.svg @@ -46,7 +46,7 @@ C 42.486563 52.0175 43.134687 51.029375 43.134687 50.19 C 43.134687 49.095625 44.537188 46.47125 46.779063 45.185625 C 50.380938 43.166875 55.279063 40.404375 55.279063 33.19 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/home.svg b/lib/matplotlib/mpl-data/images/home.svg index 3c4ccce3ed1f..db140d43d156 100644 --- a/lib/matplotlib/mpl-data/images/home.svg +++ b/lib/matplotlib/mpl-data/images/home.svg @@ -53,7 +53,7 @@ C 62.482813 46.47125 62.748438 46.545625 63.056562 46.545625 C 63.088437 46.545625 63.130938 46.545625 63.173438 46.545625 C 63.470938 46.51375 63.779063 46.354375 63.970313 46.13125 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/move.svg b/lib/matplotlib/mpl-data/images/move.svg index aa7198cf72df..f7e23ab0451c 100644 --- a/lib/matplotlib/mpl-data/images/move.svg +++ b/lib/matplotlib/mpl-data/images/move.svg @@ -67,7 +67,7 @@ C 55.433125 48.49 56.5275 49.584375 57.855625 49.584375 C 58.50375 49.584375 59.109375 49.31875 59.56625 48.861875 L 69.2775 39.150625 C 69.734375 38.69375 70 38.088125 70 37.44 -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg index 0b46bf80923e..02adfbc4ae11 100644 --- a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg +++ b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg @@ -42,7 +42,7 @@ L 63.3275 27.123125 L 67.9175 31.713125 C 68.714375 32.51 70 31.93625 70 30.87375 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/subplots.svg b/lib/matplotlib/mpl-data/images/subplots.svg index e87d2c9b1b19..9a0fa90972ff 100644 --- a/lib/matplotlib/mpl-data/images/subplots.svg +++ b/lib/matplotlib/mpl-data/images/subplots.svg @@ -75,7 +75,7 @@ L 32.355625 18.0175 L 32.355625 22.873125 L 65.144375 22.873125 z -"/> +" style="fill:black;"/> diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg index f4b69b23c5eb..44e59a8a4fdc 100644 --- a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg +++ b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg @@ -34,7 +34,7 @@ C 36.525937 59.300937 41.838437 57.664687 46.279687 54.594062 L 59.295313 67.577812 C 60.166562 68.480937 61.420313 69.012187 62.716563 69.012187 C 65.372813 69.012187 67.572187 66.812812 67.572187 64.156562 -"/> +" style="fill:black;"/> diff --git a/tools/make_icons.py b/tools/make_icons.py index 0424c0d03dad..f09d40e92256 100755 --- a/tools/make_icons.py +++ b/tools/make_icons.py @@ -36,8 +36,18 @@ def get_fontawesome(): return cached_path -def save_icon(fig, dest_dir, name): - fig.savefig(dest_dir / (name + '.svg')) +def save_icon(fig, dest_dir, name, add_black_fg_color): + if add_black_fg_color: + # Add explicit black foreground color to monochromatic svg icons + # so it can be replaced by backends to add dark theme support + svg_bytes_io = BytesIO() + fig.savefig(svg_bytes_io, format='svg') + svg = svg_bytes_io.getvalue() + before, sep, after = svg.rpartition(b'\nz\n"') + svg = before + sep + b' style="fill:black;"' + after + (dest_dir / (name + '.svg')).write_bytes(svg) + else: + fig.savefig(dest_dir / (name + '.svg')) fig.savefig(dest_dir / (name + '.pdf')) for dpi, suffix in [(24, ''), (48, '_large')]: fig.savefig(dest_dir / (name + suffix + '.png'), dpi=dpi) @@ -102,9 +112,9 @@ def make_icons(): font_path = get_fontawesome() for name, ccode in icon_defs: fig = make_icon(font_path, ccode) - save_icon(fig, args.dest_dir, name) + save_icon(fig, args.dest_dir, name, True) fig = make_matplotlib_icon() - save_icon(fig, args.dest_dir, 'matplotlib') + save_icon(fig, args.dest_dir, 'matplotlib', False) if __name__ == "__main__": From 25308bb2b8702c3fa6dc61869156c8b4a84e1371 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 Sep 2023 18:22:03 -0400 Subject: [PATCH 0168/1120] ci: Use new name for WQY-ZenHei package on Circle --- .circleci/config.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5fc186a4143a..c914581f5211 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,24 +44,24 @@ commands: command: | sudo apt -qq update sudo apt install -y \ - inkscape \ - ffmpeg \ + cm-super \ dvipng \ + ffmpeg \ + fonts-crosextra-carlito \ + fonts-freefont-otf \ + fonts-humor-sans \ + fonts-noto-cjk \ + fonts-wqy-zenhei \ + graphviz \ + inkscape \ lmodern \ - cm-super \ + optipng \ + texlive-fonts-recommended \ texlive-latex-base \ texlive-latex-extra \ - texlive-fonts-recommended \ texlive-latex-recommended \ texlive-pictures \ - texlive-xetex \ - ttf-wqy-zenhei \ - graphviz \ - fonts-crosextra-carlito \ - fonts-freefont-otf \ - fonts-humor-sans \ - fonts-noto-cjk \ - optipng + texlive-xetex fonts-install: steps: From ac8ac991da1bcc28ae7d98e6be6d12629466ddd2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 Sep 2023 18:23:16 -0400 Subject: [PATCH 0169/1120] ci: Don't install recommended packages on Circle We don't need the optional packages and save a little download/install time. --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c914581f5211..60a1e8211e60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,8 +42,8 @@ commands: - run: name: Install apt packages command: | - sudo apt -qq update - sudo apt install -y \ + sudo apt-get -qq update + sudo apt-get install -yy --no-install-recommends \ cm-super \ dvipng \ ffmpeg \ From c70a52edf48a133ff9b7ea022d8a43c8efc58109 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 20 Sep 2023 17:48:00 -0500 Subject: [PATCH 0170/1120] Bump setuptools required version because of setuptools_scm v8 Technically could work with older setuptools and setuptools_scm==7, but encoding that matrix of dependencies is not something I think is worth worrying about, and setuptools 64 is over a year old (though not by a _whole_ lot). --- doc/api/next_api_changes/development/26849-KS.rst | 5 +++++ doc/devel/dependencies.rst | 2 +- pyproject.toml | 9 ++++++++- requirements/testing/mypy.txt | 1 + setup.py | 5 ++++- 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 doc/api/next_api_changes/development/26849-KS.rst diff --git a/doc/api/next_api_changes/development/26849-KS.rst b/doc/api/next_api_changes/development/26849-KS.rst new file mode 100644 index 000000000000..1a1deda40fca --- /dev/null +++ b/doc/api/next_api_changes/development/26849-KS.rst @@ -0,0 +1,5 @@ +Minimum version of setuptools bumped to 64 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To comply with requirements of ``setuptools_scm``, the minimum version of ``setuptools`` +has been increased from 42 to 64. diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index a865b80cab84..400bc63d42ac 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -228,7 +228,7 @@ Setup dependencies runtime dependency. - `PyBind11 `_ (>= 2.6). Used to connect C/C++ code with Python. -- `setuptools `_ (>= 42). +- `setuptools `_ (>= 64). - `setuptools_scm `_ (>= 7). Used to update the reported ``mpl.__version__`` based on the current git commit. Also a runtime dependency for editable installs. diff --git a/pyproject.toml b/pyproject.toml index 5c61dd9ff055..4d9ccce2b290 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,17 @@ requires = [ "certifi>=2020.06.20", "numpy>=1.25", "pybind11>=2.6", - "setuptools>=42", + "setuptools>=64", "setuptools_scm>=7", ] +[tool.setuptools_scm] +version_scheme = "release-branch-semver" +local_scheme = "node-and-date" +write_to = "lib/matplotlib/_version.py" +parentdir_prefix_version = "matplotlib-" +fallback_version = "0.0+UNKNOWN" + [tool.isort] known_pydata = "numpy, matplotlib.pyplot" known_firstparty = "matplotlib,mpl_toolkits" diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt index f697772da5ea..a5ca15cfbdad 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -24,5 +24,6 @@ pillow>=8 pyparsing>=2.3.1 python-dateutil>=2.7 setuptools_scm>=7 +setuptools>=64 importlib-resources>=3.2.0 ; python_version < "3.10" diff --git a/setup.py b/setup.py index 98aed8ccc912..899021a83254 100644 --- a/setup.py +++ b/setup.py @@ -340,7 +340,10 @@ def make_release_tree(self, base_dir, files): "python-dateutil>=2.7", ] + ( # Installing from a git checkout that is not producing a wheel. - ["setuptools_scm>=7"] if ( + # setuptools_scm warns with older setuptools, which turns into errors for our + # test suite. However setuptools_scm does not themselves pin the version of + # setuptools. + ["setuptools_scm>=7", "setuptools>=64"] if ( Path(__file__).with_name(".git").exists() and os.environ.get("CIBUILDWHEEL", "0") != "1" ) else [] From a9d4f4c692233c4c65e8b86e642ac74053cb16c3 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 21 Sep 2023 09:41:23 +0200 Subject: [PATCH 0171/1120] Remove Julian date support --- .../next_api_changes/removals/26852-OG.rst | 5 ++ lib/matplotlib/dates.py | 59 ------------------- lib/matplotlib/tests/test_dates.py | 17 ------ 3 files changed, 5 insertions(+), 76 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26852-OG.rst diff --git a/doc/api/next_api_changes/removals/26852-OG.rst b/doc/api/next_api_changes/removals/26852-OG.rst new file mode 100644 index 000000000000..dc7d595f575f --- /dev/null +++ b/doc/api/next_api_changes/removals/26852-OG.rst @@ -0,0 +1,5 @@ +``num2julian``, ``julian2num`` and ``JULIAN_OFFSET`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... of the `.dates` module are removed without replacements. These were +undocumented and not exported. diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index a11e0d4f1ef6..c12d9f31ba4b 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -205,18 +205,6 @@ UTC = datetime.timezone.utc -@_api.caching_module_getattr -class __getattr__: - JULIAN_OFFSET = _api.deprecated("3.7")(property(lambda self: 1721424.5)) - # Julian date at 0000-12-31 - # note that the Julian day epoch is achievable w/ - # np.datetime64('-4713-11-24T12:00:00'); datetime64 is proleptic - # Gregorian and BC has a one-year offset. So - # np.datetime64('0000-12-31') - np.datetime64('-4713-11-24T12:00') = - # 1721424.5 - # Ref: https://en.wikipedia.org/wiki/Julian_day - - def _get_tzinfo(tz=None): """ Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`. @@ -466,53 +454,6 @@ def date2num(d): return d if iterable else d[0] -@_api.deprecated("3.7") -def julian2num(j): - """ - Convert a Julian date (or sequence) to a Matplotlib date (or sequence). - - Parameters - ---------- - j : float or sequence of floats - Julian dates (days relative to 4713 BC Jan 1, 12:00:00 Julian - calendar or 4714 BC Nov 24, 12:00:00, proleptic Gregorian calendar). - - Returns - ------- - float or sequence of floats - Matplotlib dates (days relative to `.get_epoch`). - """ - ep = np.datetime64(get_epoch(), 'h').astype(float) / 24. - ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24. - # Julian offset defined above is relative to 0000-12-31, but we need - # relative to our current epoch: - dt = __getattr__("JULIAN_OFFSET") - ep0 + ep - return np.subtract(j, dt) # Handles both scalar & nonscalar j. - - -@_api.deprecated("3.7") -def num2julian(n): - """ - Convert a Matplotlib date (or sequence) to a Julian date (or sequence). - - Parameters - ---------- - n : float or sequence of floats - Matplotlib dates (days relative to `.get_epoch`). - - Returns - ------- - float or sequence of floats - Julian dates (days relative to 4713 BC Jan 1, 12:00:00). - """ - ep = np.datetime64(get_epoch(), 'h').astype(float) / 24. - ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24. - # Julian offset defined above is relative to 0000-12-31, but we need - # relative to our current epoch: - dt = __getattr__("JULIAN_OFFSET") - ep0 + ep - return np.add(n, dt) # Handles both scalar & nonscalar j. - - def num2date(x, tz=None): """ Convert Matplotlib dates to `~datetime.datetime` objects. diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 8995b9b35f09..4133524e0e1a 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -6,7 +6,6 @@ import numpy as np import pytest -import matplotlib as mpl from matplotlib import rc_context, style import matplotlib.dates as mdates import matplotlib.pyplot as plt @@ -1282,22 +1281,6 @@ def test_change_interval_multiples(): assert ax.get_xticklabels()[1].get_text() == 'Feb 01 2020' -def test_julian2num(): - mdates._reset_epoch_test_example() - mdates.set_epoch('0000-12-31') - with pytest.warns(mpl.MatplotlibDeprecationWarning): - # 2440587.5 is julian date for 1970-01-01T00:00:00 - # https://en.wikipedia.org/wiki/Julian_day - assert mdates.julian2num(2440588.5) == 719164.0 - assert mdates.num2julian(719165.0) == 2440589.5 - # set back to the default - mdates._reset_epoch_test_example() - mdates.set_epoch('1970-01-01T00:00:00') - with pytest.warns(mpl.MatplotlibDeprecationWarning): - assert mdates.julian2num(2440588.5) == 1.0 - assert mdates.num2julian(2.0) == 2440589.5 - - def test_DateLocator(): locator = mdates.DateLocator() # Test nonsingular From 6da5edcaff5a54be75ea05188221aca208980005 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2023 13:01:29 -0400 Subject: [PATCH 0172/1120] TST: skeleton of tests passing datetime into every method --- lib/matplotlib/tests/test_datetime.py | 384 ++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 lib/matplotlib/tests/test_datetime.py diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py new file mode 100644 index 000000000000..fa6cee3916ff --- /dev/null +++ b/lib/matplotlib/tests/test_datetime.py @@ -0,0 +1,384 @@ +import pytest + +import matplotlib.pyplot as plt +import matplotlib as mpl + + +class TestDatetimePlotting: + @pytest.mark.xfail(reason="Test for acorr not written yet") + @mpl.style.context("default") + def test_acorr(self): + fig, ax = plt.subplots() + ax.acorr(...) + + @pytest.mark.xfail(reason="Test for angle_spectrum not written yet") + @mpl.style.context("default") + def test_angle_spectrum(self): + fig, ax = plt.subplots() + ax.angle_spectrum(...) + + @pytest.mark.xfail(reason="Test for annotate not written yet") + @mpl.style.context("default") + def test_annotate(self): + fig, ax = plt.subplots() + ax.annotate(...) + + @pytest.mark.xfail(reason="Test for arrow not written yet") + @mpl.style.context("default") + def test_arrow(self): + fig, ax = plt.subplots() + ax.arrow(...) + + @pytest.mark.xfail(reason="Test for axhline not written yet") + @mpl.style.context("default") + def test_axhline(self): + fig, ax = plt.subplots() + ax.axhline(...) + + @pytest.mark.xfail(reason="Test for axhspan not written yet") + @mpl.style.context("default") + def test_axhspan(self): + fig, ax = plt.subplots() + ax.axhspan(...) + + @pytest.mark.xfail(reason="Test for axline not written yet") + @mpl.style.context("default") + def test_axline(self): + fig, ax = plt.subplots() + ax.axline(...) + + @pytest.mark.xfail(reason="Test for axvline not written yet") + @mpl.style.context("default") + def test_axvline(self): + fig, ax = plt.subplots() + ax.axvline(...) + + @pytest.mark.xfail(reason="Test for axvspan not written yet") + @mpl.style.context("default") + def test_axvspan(self): + fig, ax = plt.subplots() + ax.axvspan(...) + + @pytest.mark.xfail(reason="Test for bar not written yet") + @mpl.style.context("default") + def test_bar(self): + fig, ax = plt.subplots() + ax.bar(...) + + @pytest.mark.xfail(reason="Test for bar_label not written yet") + @mpl.style.context("default") + def test_bar_label(self): + fig, ax = plt.subplots() + ax.bar_label(...) + + @pytest.mark.xfail(reason="Test for barbs not written yet") + @mpl.style.context("default") + def test_barbs(self): + fig, ax = plt.subplots() + ax.barbs(...) + + @pytest.mark.xfail(reason="Test for barh not written yet") + @mpl.style.context("default") + def test_barh(self): + fig, ax = plt.subplots() + ax.barh(...) + + @pytest.mark.xfail(reason="Test for boxplot not written yet") + @mpl.style.context("default") + def test_boxplot(self): + fig, ax = plt.subplots() + ax.boxplot(...) + + @pytest.mark.xfail(reason="Test for broken_barh not written yet") + @mpl.style.context("default") + def test_broken_barh(self): + fig, ax = plt.subplots() + ax.broken_barh(...) + + @pytest.mark.xfail(reason="Test for bxp not written yet") + @mpl.style.context("default") + def test_bxp(self): + fig, ax = plt.subplots() + ax.bxp(...) + + @pytest.mark.xfail(reason="Test for clabel not written yet") + @mpl.style.context("default") + def test_clabel(self): + fig, ax = plt.subplots() + ax.clabel(...) + + @pytest.mark.xfail(reason="Test for cohere not written yet") + @mpl.style.context("default") + def test_cohere(self): + fig, ax = plt.subplots() + ax.cohere(...) + + @pytest.mark.xfail(reason="Test for contour not written yet") + @mpl.style.context("default") + def test_contour(self): + fig, ax = plt.subplots() + ax.contour(...) + + @pytest.mark.xfail(reason="Test for contourf not written yet") + @mpl.style.context("default") + def test_contourf(self): + fig, ax = plt.subplots() + ax.contourf(...) + + @pytest.mark.xfail(reason="Test for csd not written yet") + @mpl.style.context("default") + def test_csd(self): + fig, ax = plt.subplots() + ax.csd(...) + + @pytest.mark.xfail(reason="Test for errorbar not written yet") + @mpl.style.context("default") + def test_errorbar(self): + fig, ax = plt.subplots() + ax.errorbar(...) + + @pytest.mark.xfail(reason="Test for eventplot not written yet") + @mpl.style.context("default") + def test_eventplot(self): + fig, ax = plt.subplots() + ax.eventplot(...) + + @pytest.mark.xfail(reason="Test for fill not written yet") + @mpl.style.context("default") + def test_fill(self): + fig, ax = plt.subplots() + ax.fill(...) + + @pytest.mark.xfail(reason="Test for fill_between not written yet") + @mpl.style.context("default") + def test_fill_between(self): + fig, ax = plt.subplots() + ax.fill_between(...) + + @pytest.mark.xfail(reason="Test for fill_betweenx not written yet") + @mpl.style.context("default") + def test_fill_betweenx(self): + fig, ax = plt.subplots() + ax.fill_betweenx(...) + + @pytest.mark.xfail(reason="Test for hexbin not written yet") + @mpl.style.context("default") + def test_hexbin(self): + fig, ax = plt.subplots() + ax.hexbin(...) + + @pytest.mark.xfail(reason="Test for hist not written yet") + @mpl.style.context("default") + def test_hist(self): + fig, ax = plt.subplots() + ax.hist(...) + + @pytest.mark.xfail(reason="Test for hist2d not written yet") + @mpl.style.context("default") + def test_hist2d(self): + fig, ax = plt.subplots() + ax.hist2d(...) + + @pytest.mark.xfail(reason="Test for hlines not written yet") + @mpl.style.context("default") + def test_hlines(self): + fig, ax = plt.subplots() + ax.hlines(...) + + @pytest.mark.xfail(reason="Test for imshow not written yet") + @mpl.style.context("default") + def test_imshow(self): + fig, ax = plt.subplots() + ax.imshow(...) + + @pytest.mark.xfail(reason="Test for loglog not written yet") + @mpl.style.context("default") + def test_loglog(self): + fig, ax = plt.subplots() + ax.loglog(...) + + @pytest.mark.xfail(reason="Test for magnitude_spectrum not written yet") + @mpl.style.context("default") + def test_magnitude_spectrum(self): + fig, ax = plt.subplots() + ax.magnitude_spectrum(...) + + @pytest.mark.xfail(reason="Test for matshow not written yet") + @mpl.style.context("default") + def test_matshow(self): + fig, ax = plt.subplots() + ax.matshow(...) + + @pytest.mark.xfail(reason="Test for pcolor not written yet") + @mpl.style.context("default") + def test_pcolor(self): + fig, ax = plt.subplots() + ax.pcolor(...) + + @pytest.mark.xfail(reason="Test for pcolorfast not written yet") + @mpl.style.context("default") + def test_pcolorfast(self): + fig, ax = plt.subplots() + ax.pcolorfast(...) + + @pytest.mark.xfail(reason="Test for pcolormesh not written yet") + @mpl.style.context("default") + def test_pcolormesh(self): + fig, ax = plt.subplots() + ax.pcolormesh(...) + + @pytest.mark.xfail(reason="Test for phase_spectrum not written yet") + @mpl.style.context("default") + def test_phase_spectrum(self): + fig, ax = plt.subplots() + ax.phase_spectrum(...) + + @pytest.mark.xfail(reason="Test for plot not written yet") + @mpl.style.context("default") + def test_plot(self): + fig, ax = plt.subplots() + ax.plot(...) + + @pytest.mark.xfail(reason="Test for plot_date not written yet") + @mpl.style.context("default") + def test_plot_date(self): + fig, ax = plt.subplots() + ax.plot_date(...) + + @pytest.mark.xfail(reason="Test for psd not written yet") + @mpl.style.context("default") + def test_psd(self): + fig, ax = plt.subplots() + ax.psd(...) + + @pytest.mark.xfail(reason="Test for quiver not written yet") + @mpl.style.context("default") + def test_quiver(self): + fig, ax = plt.subplots() + ax.quiver(...) + + @pytest.mark.xfail(reason="Test for quiverkey not written yet") + @mpl.style.context("default") + def test_quiverkey(self): + fig, ax = plt.subplots() + ax.quiverkey(...) + + @pytest.mark.xfail(reason="Test for scatter not written yet") + @mpl.style.context("default") + def test_scatter(self): + fig, ax = plt.subplots() + ax.scatter(...) + + @pytest.mark.xfail(reason="Test for semilogx not written yet") + @mpl.style.context("default") + def test_semilogx(self): + fig, ax = plt.subplots() + ax.semilogx(...) + + @pytest.mark.xfail(reason="Test for semilogy not written yet") + @mpl.style.context("default") + def test_semilogy(self): + fig, ax = plt.subplots() + ax.semilogy(...) + + @pytest.mark.xfail(reason="Test for specgram not written yet") + @mpl.style.context("default") + def test_specgram(self): + fig, ax = plt.subplots() + ax.specgram(...) + + @pytest.mark.xfail(reason="Test for spy not written yet") + @mpl.style.context("default") + def test_spy(self): + fig, ax = plt.subplots() + ax.spy(...) + + @pytest.mark.xfail(reason="Test for stackplot not written yet") + @mpl.style.context("default") + def test_stackplot(self): + fig, ax = plt.subplots() + ax.stackplot(...) + + @pytest.mark.xfail(reason="Test for stairs not written yet") + @mpl.style.context("default") + def test_stairs(self): + fig, ax = plt.subplots() + ax.stairs(...) + + @pytest.mark.xfail(reason="Test for stem not written yet") + @mpl.style.context("default") + def test_stem(self): + fig, ax = plt.subplots() + ax.stem(...) + + @pytest.mark.xfail(reason="Test for step not written yet") + @mpl.style.context("default") + def test_step(self): + fig, ax = plt.subplots() + ax.step(...) + + @pytest.mark.xfail(reason="Test for streamplot not written yet") + @mpl.style.context("default") + def test_streamplot(self): + fig, ax = plt.subplots() + ax.streamplot(...) + + @pytest.mark.xfail(reason="Test for table not written yet") + @mpl.style.context("default") + def test_table(self): + fig, ax = plt.subplots() + ax.table(...) + + @pytest.mark.xfail(reason="Test for text not written yet") + @mpl.style.context("default") + def test_text(self): + fig, ax = plt.subplots() + ax.text(...) + + @pytest.mark.xfail(reason="Test for tricontour not written yet") + @mpl.style.context("default") + def test_tricontour(self): + fig, ax = plt.subplots() + ax.tricontour(...) + + @pytest.mark.xfail(reason="Test for tricontourf not written yet") + @mpl.style.context("default") + def test_tricontourf(self): + fig, ax = plt.subplots() + ax.tricontourf(...) + + @pytest.mark.xfail(reason="Test for tripcolor not written yet") + @mpl.style.context("default") + def test_tripcolor(self): + fig, ax = plt.subplots() + ax.tripcolor(...) + + @pytest.mark.xfail(reason="Test for triplot not written yet") + @mpl.style.context("default") + def test_triplot(self): + fig, ax = plt.subplots() + ax.triplot(...) + + @pytest.mark.xfail(reason="Test for violin not written yet") + @mpl.style.context("default") + def test_violin(self): + fig, ax = plt.subplots() + ax.violin(...) + + @pytest.mark.xfail(reason="Test for violinplot not written yet") + @mpl.style.context("default") + def test_violinplot(self): + fig, ax = plt.subplots() + ax.violinplot(...) + + @pytest.mark.xfail(reason="Test for vlines not written yet") + @mpl.style.context("default") + def test_vlines(self): + fig, ax = plt.subplots() + ax.vlines(...) + + @pytest.mark.xfail(reason="Test for xcorr not written yet") + @mpl.style.context("default") + def test_xcorr(self): + fig, ax = plt.subplots() + ax.xcorr(...) From 6b870e72eee153f856eced32435ad1c789e7e44b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2023 13:58:44 -0400 Subject: [PATCH 0173/1120] TST: put pie back timedeltas make sense --- lib/matplotlib/tests/test_datetime.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index fa6cee3916ff..e313c3bf9523 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -209,6 +209,12 @@ def test_matshow(self): fig, ax = plt.subplots() ax.matshow(...) + @pytest.mark.xfail(reason="Test for pie not written yet") + @mpl.style.context("default") + def test_pie(self): + fig, ax = plt.subplots() + ax.pcolor(...) + @pytest.mark.xfail(reason="Test for pcolor not written yet") @mpl.style.context("default") def test_pcolor(self): From de6919ca5f620e458416d857014f17125a8ef2b2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2023 13:06:40 -0400 Subject: [PATCH 0174/1120] TST: implement plot smoke test --- lib/matplotlib/tests/test_datetime.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index e313c3bf9523..53958229f174 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -1,3 +1,6 @@ +import datetime +import numpy as np + import pytest import matplotlib.pyplot as plt @@ -239,11 +242,15 @@ def test_phase_spectrum(self): fig, ax = plt.subplots() ax.phase_spectrum(...) - @pytest.mark.xfail(reason="Test for plot not written yet") @mpl.style.context("default") def test_plot(self): - fig, ax = plt.subplots() - ax.plot(...) + mpl.rcParams["date.converter"] = 'concise' + N = 6 + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + x = np.array([datetime.datetime(2023, 9, n) for n in range(1, N)]) + ax1.plot(x, range(1, N)) + ax2.plot(range(1, N), x) + ax3.plot(x, x) @pytest.mark.xfail(reason="Test for plot_date not written yet") @mpl.style.context("default") From 5b0feba7be1af643510a6fe61deb427343aa61da Mon Sep 17 00:00:00 2001 From: Pavel Liavonau Date: Thu, 21 Sep 2023 20:09:47 +0200 Subject: [PATCH 0175/1120] QT/NavidationToolbar2: configure subplots dialog should be modal. Fix for a case when parent windows is modal. Configure subplots hides behind parent modal windows in case of non-modal usage. Other dialogs of NavigationToolbarQT are modal, so it looks like a bug. tested on pyQT6 --- lib/matplotlib/backends/backend_qt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 4b3783bc87ca..2aa7874fbdb7 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -759,6 +759,7 @@ def configure_subplots(self): self.canvas.mpl_connect( "close_event", lambda e: self._subplot_dialog.reject()) self._subplot_dialog.update_from_current_subplotpars() + self._subplot_dialog.setModal(True) self._subplot_dialog.show() return self._subplot_dialog From 5e5378ec107674fcd4d8f48f24487964675f00f5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 Sep 2023 23:47:00 -0400 Subject: [PATCH 0176/1120] DOC: Fix missing-reference generation on Windows On Windows, the path is absolute and contains a colon after the drive letter, so splitting on colon results in trying to relativize 'c' (or whatever the drive is) to the base directory, which just makes the final path into base directory + file path. --- doc/missing-references.json | 743 +++++++++++++--------------- doc/sphinxext/missing_references.py | 14 +- 2 files changed, 344 insertions(+), 413 deletions(-) diff --git a/doc/missing-references.json b/doc/missing-references.json index 72de214df985..1b0a6f9ef226 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -1,731 +1,660 @@ { "py:attr": { "cbar_axes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:72", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:72" + "lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:72", + "lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:72" ], "eventson": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.CheckButtons.set_active:4", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.RadioButtons.set_active:4" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.CheckButtons.set_active:4", + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.RadioButtons.set_active:4" ], "fmt_zdata": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.format_zdata:2" + "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.format_zdata:2" ], "height": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" ], "input_dims": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform:8", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:8", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:14" + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:14", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:14", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform:8", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:15", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:14", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:15", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:14", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:8", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:15", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:14" ], "lines": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\colorbar.py:docstring of matplotlib.colorbar.Colorbar.add_lines:4" + "lib/matplotlib/colorbar.py:docstring of matplotlib.colorbar.Colorbar.add_lines:4" ], "matplotlib.axes.Axes.patch": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:188", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:427" + "doc/tutorials/artists.rst:188", + "doc/tutorials/artists.rst:427" ], "matplotlib.axes.Axes.patches": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:465" + "doc/tutorials/artists.rst:465" ], "matplotlib.axes.Axes.transAxes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows:8" + "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows:8" ], "matplotlib.axes.Axes.transData": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox:11", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse:33", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar:8" + "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox:11", + "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse:33", + "lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar:8" ], "matplotlib.axes.Axes.xaxis": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:611", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/axes/axes_intro.rst:133" + "doc/tutorials/artists.rst:611", + "doc/users/explain/axes/axes_intro.rst:133" ], "matplotlib.axes.Axes.yaxis": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:611", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/axes/axes_intro.rst:133" + "doc/tutorials/artists.rst:611", + "doc/users/explain/axes/axes_intro.rst:133" ], "matplotlib.axis.Axis.label": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:658" + "doc/tutorials/artists.rst:658" ], "matplotlib.colors.Colormap.name": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\cm.py:docstring of matplotlib.cm.register_cmap:14" + "lib/matplotlib/cm.py:docstring of matplotlib.cm.register_cmap:14" ], "matplotlib.figure.Figure.patch": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:188", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:321" + "doc/tutorials/artists.rst:188", + "doc/tutorials/artists.rst:321" ], "matplotlib.figure.Figure.transFigure": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:370" + "doc/tutorials/artists.rst:370" ], "max": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.p1:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.p1:4" ], "min": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.p0:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.p0:4" ], "mpl_toolkits.mplot3d.axis3d._axinfo": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/toolkits/mplot3d.rst:66" + "doc/api/toolkits/mplot3d.rst:66" ], "name": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\scale.py:docstring of matplotlib.scale.ScaleBase:8" + "lib/matplotlib/scale.py:docstring of matplotlib.scale.ScaleBase:8" ], "output_dims": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:21", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:21", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:20", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:14", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:21", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:20" + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform.transform_non_affine:20", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform.transform_non_affine:20", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform:14", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform_affine:21", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.AffineBase.transform_non_affine:20", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_affine:21", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.CompositeGenericTransform.transform_non_affine:20", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform:14", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_affine:21", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.IdentityTransform.transform_non_affine:20" ], "triangulation": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\tri\\_trirefine.py:docstring of matplotlib.tri._trirefine.UniformTriRefiner.refine_triangulation:2" + "lib/matplotlib/tri/_trirefine.py:docstring of matplotlib.tri._trirefine.UniformTriRefiner.refine_triangulation:2" ], "use_sticky_edges": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.margins:53" + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.margins:53" ], "width": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.bounds:2" ], "xmax": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.x1:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.x1:4" ], "xmin": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.x0:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.x0:4" ], "ymax": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.y1:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.y1:4" ], "ymin": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.Bbox.y0:4" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.y0:4" ] }, "py:class": { - "ArrayLike": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.acorr:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.angle_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.bar:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.bar_label:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.barh:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.boxplot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.clabel:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.csd:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.ecdf:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.errorbar:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.eventplot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figimage:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_between:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist2d:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hlines:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.imsave:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.imshow:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.magnitude_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.matshow:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolor:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolormesh:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.phase_spectrum:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pie:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.plot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.plot_date:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.psd:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.rgrids:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.scatter:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.specgram:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.spy:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.stairs:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.stem:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.step:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.subplot_mosaic:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.thetagrids:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.violinplot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.vlines:1" - ], - "ColorType": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.errorbar:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.eventplot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hlines:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pie:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.scatter:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.vlines:1" - ], - "HashableList": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.subplot_mosaic:1" - ], "HashableList[_HT]": [ "doc/docstring of builtins.list:17" ], - "LineStyleType": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.eventplot:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hlines:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.vlines:1" - ], - "MarkerType": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.scatter:1" - ], - "_AxesBase": [ - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.twinx:1", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.twiny:1" - ], "matplotlib.axes._base._AxesBase": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202" + "doc/api/artist_api.rst:202" ], "matplotlib.backend_bases.FigureCanvas": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:36", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:38", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:43" + "doc/tutorials/artists.rst:36", + "doc/tutorials/artists.rst:38", + "doc/tutorials/artists.rst:43" ], "matplotlib.backend_bases.Renderer": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:38", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\tutorials/artists.rst:43" + "doc/tutorials/artists.rst:38", + "doc/tutorials/artists.rst:43" ], "matplotlib.backend_bases._Backend": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ShowBase:1" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.ShowBase:1" ], "matplotlib.backends._backend_pdf_ps.RendererPDFPSBase": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS:1" + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf:1", + "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS:1" ], "matplotlib.backends._backend_tk.FigureCanvasTk": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_tkagg.py:docstring of matplotlib.backends.backend_tkagg.FigureCanvasTkAgg:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_tkcairo.py:docstring of matplotlib.backends.backend_tkcairo.FigureCanvasTkCairo:1" + "lib/matplotlib/backends/backend_tkagg.py:docstring of matplotlib.backends.backend_tkagg.FigureCanvasTkAgg:1", + "lib/matplotlib/backends/backend_tkcairo.py:docstring of matplotlib.backends.backend_tkcairo.FigureCanvasTkCairo:1" ], "matplotlib.collections._CollectionWithSizes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/collections_api.rst:13", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.CircleCollection:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PathCollection:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PolyCollection:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.RegularPolyCollection:1" + "doc/api/artist_api.rst:202", + "doc/api/collections_api.rst:13", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.CircleCollection:1", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.PathCollection:1", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.PolyCollection:1", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.RegularPolyCollection:1" ], "matplotlib.collections._MeshData": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/collections_api.rst:13", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.PolyQuadMesh:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.collections.QuadMesh:1" + "doc/api/artist_api.rst:202", + "doc/api/collections_api.rst:13", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.PolyQuadMesh:1", + "lib/matplotlib/collections.py:docstring of matplotlib.collections.QuadMesh:1" ], "matplotlib.image._ImageBase": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.AxesImage:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.BboxImage:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.FigureImage:1" + "doc/api/artist_api.rst:202", + "lib/matplotlib/image.py:docstring of matplotlib.image.AxesImage:1", + "lib/matplotlib/image.py:docstring of matplotlib.image.BboxImage:1", + "lib/matplotlib/image.py:docstring of matplotlib.image.FigureImage:1" ], "matplotlib.patches.ArrowStyle._Base": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Fancy:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Simple:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Wedge:1" + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Fancy:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Simple:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Wedge:1" ], "matplotlib.patches.ArrowStyle._Curve": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BarAB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketA:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketAB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.BracketCurve:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.Curve:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveA:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveAB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveBracket:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledA:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledAB:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledB:1" + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BarAB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketA:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketAB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketCurve:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Curve:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveA:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveAB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveBracket:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledA:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledAB:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledB:1" ], "matplotlib.patches.ConnectionStyle._Base": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle3:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc3:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle.Bar:1" + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle3:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc3:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Bar:1" ], "matplotlib.patches._Style": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ArrowStyle:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.BoxStyle:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patches.py:docstring of matplotlib.patches.ConnectionStyle:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle:1", + "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" ], "matplotlib.projections.geo._GeoTransform": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\projections\\geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform:1" + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.AitoffTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.AitoffAxes.InvertedAitoffTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.HammerTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.HammerAxes.InvertedHammerTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.InvertedLambertTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.LambertAxes.LambertTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.InvertedMollweideTransform:1", + "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform:1" ], "matplotlib.text._AnnotationBase": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/artist_api.rst:202", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\offsetbox.py:docstring of matplotlib.offsetbox.AnnotationBbox:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\text.py:docstring of matplotlib.text.Annotation:1" + "doc/api/artist_api.rst:202", + "lib/matplotlib/offsetbox.py:docstring of matplotlib.offsetbox.AnnotationBbox:1", + "lib/matplotlib/text.py:docstring of matplotlib.text.Annotation:1" ], "matplotlib.transforms._BlendedMixin": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.BlendedAffine2D:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\transforms.py:docstring of matplotlib.transforms.BlendedGenericTransform:1" + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.BlendedAffine2D:1", + "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.BlendedGenericTransform:1" ], "matplotlib.widgets._SelectorWidget": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.LassoSelector:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.PolygonSelector:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.RectangleSelector:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.SpanSelector:1" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.LassoSelector:1", + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.PolygonSelector:1", + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.RectangleSelector:1", + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.SpanSelector:1" ], "mpl_toolkits.axes_grid1.axes_size._Base": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Add:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesX:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesY:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fixed:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fraction:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.MaxExtent:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Scaled:1" + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Add:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesX:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.AxesY:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fixed:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Fraction:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.MaxExtent:1", + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size.Scaled:1" ], "mpl_toolkits.axes_grid1.parasite_axes.AxesHostAxes": [ - ":1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1" + "doc/api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:32::1" ], "mpl_toolkits.axes_grid1.parasite_axes.AxesParasite": [ - ":1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:30::1" + "doc/api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:32::1" ], "mpl_toolkits.axisartist.Axes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/toolkits/axisartist.rst:6" + "doc/api/toolkits/axisartist.rst:6" ], "mpl_toolkits.axisartist.axisline_style.AxislineStyle._Base": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle.SimpleArrow:1" + "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" ], "mpl_toolkits.axisartist.axisline_style._FancyAxislineStyle.FilledArrow": [ - ":1" + "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" ], "mpl_toolkits.axisartist.axisline_style._FancyAxislineStyle.SimpleArrow": [ - ":1" + "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle:1" ], "mpl_toolkits.axisartist.axislines._FixedAxisArtistHelperBase": [ - ":1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axislines.py:docstring of mpl_toolkits.axisartist.axislines.FixedAxisArtistHelperRectilinear:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FixedAxisArtistHelper:1" + "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.FixedAxisArtistHelperRectilinear:1", + "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FixedAxisArtistHelper:1" ], "mpl_toolkits.axisartist.axislines._FloatingAxisArtistHelperBase": [ - ":1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axislines.py:docstring of mpl_toolkits.axisartist.axislines.FloatingAxisArtistHelperRectilinear:1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FloatingAxisArtistHelper:1" + "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.FloatingAxisArtistHelperRectilinear:1", + "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FloatingAxisArtistHelper:1" ], "mpl_toolkits.axisartist.floating_axes.FloatingAxesHostAxes": [ - ":1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1" + "doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:34::1" ], "numpy.uint8": [ - ":1" + "lib/matplotlib/path.py:docstring of matplotlib.path:1" ] }, "py:data": { "matplotlib.axes.Axes.transAxes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:248", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:249", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:201", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:249", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:248" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:248", + "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:249", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:201", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:249", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:248" ] }, "py:meth": { "AbstractPathEffect._update_gc": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.SimpleLineShadow:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.SimplePatchShadow:42", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.TickedStroke:57", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.withSimplePatchShadow:51", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.withTickedStroke:56" + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimpleLineShadow:44", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.SimplePatchShadow:42", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.TickedStroke:57", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withSimplePatchShadow:51", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.withTickedStroke:56" ], "IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:420" + "doc/users/explain/figure/interactive_guide.rst:420" ], "_find_tails": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:9" + "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:9" ], "_make_barbs": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:9" + "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:9" ], "matplotlib.collections._CollectionWithSizes.set_sizes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:176", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:82", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:118", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:118", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:206", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:178", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:212", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.AsteriskPolygonCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.BrokenBarHCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.CircleCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PathCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.RegularPolyCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.StarPolygonCollection.set:44", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.barbs:176", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.broken_barh:82", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.fill_between:118", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:118", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.hexbin:206", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.pcolor:178", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.quiver:212", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.artist.Barbs.set:45", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.artist.Quiver.set:45", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Barbs:209", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\quiver.py:docstring of matplotlib.quiver.Quiver:248", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\art3d.py:docstring of matplotlib.artist.Path3DCollection.set:46", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\mplot3d\\art3d.py:docstring of matplotlib.artist.Poly3DCollection.set:44" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:176", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:82", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:118", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:118", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:206", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:178", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:212", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.AsteriskPolygonCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.BrokenBarHCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.CircleCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.PathCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.PolyCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.RegularPolyCollection.set:44", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.StarPolygonCollection.set:44", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.barbs:176", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.broken_barh:82", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_between:118", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:118", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hexbin:206", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolor:178", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.quiver:212", + "lib/matplotlib/quiver.py:docstring of matplotlib.artist.Barbs.set:45", + "lib/matplotlib/quiver.py:docstring of matplotlib.artist.Quiver.set:45", + "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:209", + "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver:248", + "lib/mpl_toolkits/mplot3d/art3d.py:docstring of matplotlib.artist.Path3DCollection.set:46", + "lib/mpl_toolkits/mplot3d/art3d.py:docstring of matplotlib.artist.Poly3DCollection.set:44" ], "matplotlib.collections._MeshData.set_array": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.pcolormesh:160", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:17", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\collections.py:docstring of matplotlib.artist.QuadMesh.set:17", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.pcolormesh:160" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolormesh:160", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.PolyQuadMesh.set:17", + "lib/matplotlib/collections.py:docstring of matplotlib.artist.QuadMesh.set:17", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolormesh:160" ] }, "py:obj": { "Artist.stale_callback": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:323" + "doc/users/explain/figure/interactive_guide.rst:323" ], "Artist.sticky_edges": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:356::1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes.Axes.use_sticky_edges:2" + "doc/api/axes_api.rst:356::1" ], "Axes.dataLim": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:293::1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.update_datalim:2" + "doc/api/axes_api.rst:293::1", + "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.update_datalim:2" ], "AxesBase": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/axes_api.rst:448::1", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.add_child_axes:2" + "doc/api/axes_api.rst:448::1", + "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.add_child_axes:2" ], "Figure.stale_callback": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive_guide.rst:333" + "doc/users/explain/figure/interactive_guide.rst:333" ], "Glyph": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\gallery/misc/ftface_props.rst:28" + "doc/gallery/misc/ftface_props.rst:28" ], "Image": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.gci:4" + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.gci:4" ], "ImageComparisonFailure": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\testing\\decorators.py:docstring of matplotlib.testing.decorators.image_comparison:2" + "lib/matplotlib/testing/decorators.py:docstring of matplotlib.testing.decorators.image_comparison:2" ], "Line2D.pick": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/event_handling.rst:568" + "doc/users/explain/figure/event_handling.rst:568" ], "QuadContourSet.changed()": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.contour:152", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.contourf:152", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.contour:152", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.contourf:152" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.contour:152", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.contourf:152", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.contour:152", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.contourf:152" ], "Rectangle.contains": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/event_handling.rst:280" + "doc/users/explain/figure/event_handling.rst:280" ], "Size.from_any": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:84", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:84" + "lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:84", + "lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:84" ], "Timer": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.new_timer:2", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:14" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.new_timer:2", + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:14" ], "ToolContainer": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:2", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase:20" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:2", + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase:20" ], "_iter_collection": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", + "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", + "lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" ], "_iter_collection_raw_paths": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:15", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:15", + "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:15", + "lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:15", + "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:15" ], "_read": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\dviread.py:docstring of matplotlib.dviread.Vf:20" + "lib/matplotlib/dviread.py:docstring of matplotlib.dviread.Vf:20" ], "active": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\widgets.py:docstring of matplotlib.widgets.AxesWidget:34" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.AxesWidget:34" ], "ax.transAxes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.indicate_inset:19", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.inset_axes:11" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.indicate_inset:19", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.inset_axes:11" ], "axes.bbox": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:145", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:97", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:145", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:144" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", + "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:145", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:97", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:145", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:144" ], "can_composite": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.composite_images:9" + "lib/matplotlib/image.py:docstring of matplotlib.image.composite_images:9" ], "converter": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\testing\\compare.py:docstring of matplotlib.testing.compare.compare_images:4" + "lib/matplotlib/testing/compare.py:docstring of matplotlib.testing.compare.compare_images:4" ], "draw_image": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.option_scale_image:2" + "lib/matplotlib/backends/backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.option_scale_image:2" ], "figure.bbox": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\figure.py:docstring of matplotlib.figure.FigureBase.legend:145", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\legend.py:docstring of matplotlib.legend.Legend:97", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.figlegend:145", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.legend:144" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:144", + "lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:145", + "lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:97", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:145", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:144" ], "fmt_xdata": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.format_xdata:4" + "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.format_xdata:4" ], "fmt_ydata": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_base.py:docstring of matplotlib.axes._base._AxesBase.format_ydata:4" + "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes._base._AxesBase.format_ydata:4" ], "get_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axes_grid1\\axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size:1" + "lib/mpl_toolkits/axes_grid1/axes_size.py:docstring of mpl_toolkits.axes_grid1.axes_size:1" ], "ipykernel.pylab.backend_inline": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/explain/figure/interactive.rst:340" + "doc/users/explain/figure/interactive.rst:340" ], "kde.covariance_factor": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:41" + "lib/matplotlib/mlab.py:docstring of matplotlib.mlab.GaussianKDE:41" ], "kde.factor": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\axes\\_axes.py:docstring of matplotlib.axes._axes.Axes.violinplot:46", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:12", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\mlab.py:docstring of matplotlib.mlab.GaussianKDE:45", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\pyplot.py:docstring of matplotlib.pyplot.violinplot:46" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.violinplot:46", + "lib/matplotlib/mlab.py:docstring of matplotlib.mlab.GaussianKDE:12", + "lib/matplotlib/mlab.py:docstring of matplotlib.mlab.GaussianKDE:45", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.violinplot:46" ], "make_image": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\image.py:docstring of matplotlib.image.composite_images:9" + "lib/matplotlib/image.py:docstring of matplotlib.image.composite_images:9" ], "matplotlib.animation.ArtistAnimation.new_frame_seq": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.new_saved_frame_seq": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.pause": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.repeat": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:33::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:33::1" ], "matplotlib.animation.ArtistAnimation.resume": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.save": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.to_html5_video": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.ArtistAnimation.to_jshtml": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.ArtistAnimation.rst:28::1" ], "matplotlib.animation.FFMpegFileWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.finish": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.frame_format": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.grab_frame": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.isAvailable": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.output_args": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FFMpegFileWriter.supported_formats:1::1" ], "matplotlib.animation.FFMpegFileWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegFileWriter.setup": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegFileWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.finish": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FFMpegWriter.grab_frame": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.isAvailable": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.output_args": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FFMpegWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.setup": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:27::1" ], "matplotlib.animation.FFMpegWriter.supported_formats": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" + "doc/api/_as_gen/matplotlib.animation.FFMpegWriter.rst:34::1" ], "matplotlib.animation.FileMovieWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" ], "matplotlib.animation.FileMovieWriter.isAvailable": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.FileMovieWriter.rst:27::1" ], "matplotlib.animation.FileMovieWriter.supported_formats": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FileMovieWriter.finish:1::1" ], "matplotlib.animation.FuncAnimation.pause": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.repeat": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.FuncAnimation.new_frame_seq:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.FuncAnimation.new_frame_seq:1::1" ], "matplotlib.animation.FuncAnimation.resume": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.save": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.to_html5_video": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.FuncAnimation.to_jshtml": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.FuncAnimation.rst:28::1" ], "matplotlib.animation.HTMLWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" ], "matplotlib.animation.HTMLWriter.frame_format": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" ], "matplotlib.animation.HTMLWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.HTMLWriter.finish:1::1" ], "matplotlib.animation.HTMLWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.HTMLWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.finish": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.frame_format": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickFileWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickFileWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickFileWriter.grab_frame": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.isAvailable": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickFileWriter.setup": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickFileWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.bin_path": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.finish": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" ], "matplotlib.animation.ImageMagickWriter.grab_frame": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.isAvailable": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.setup": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.ImageMagickWriter.rst:27::1" ], "matplotlib.animation.ImageMagickWriter.supported_formats": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.ImageMagickWriter.input_names:1::1" ], "matplotlib.animation.MovieWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.MovieWriter.bin_path:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.MovieWriter.bin_path:1::1" ], "matplotlib.animation.MovieWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.MovieWriter.rst:27::1" + "doc/api/_as_gen/matplotlib.animation.MovieWriter.rst:27::1" ], "matplotlib.animation.PillowWriter.frame_size": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\animation.py:docstring of matplotlib.animation.PillowWriter.finish:1::1" + "lib/matplotlib/animation.py:docstring of matplotlib.animation.PillowWriter.finish:1::1" ], "matplotlib.animation.PillowWriter.saving": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.PillowWriter.rst:26::1" + "doc/api/_as_gen/matplotlib.animation.PillowWriter.rst:26::1" ], "matplotlib.animation.TimedAnimation.new_frame_seq": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.new_saved_frame_seq": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.pause": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.resume": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.save": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.to_html5_video": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" + "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" ], "matplotlib.animation.TimedAnimation.to_jshtml": [ "doc/api/_as_gen/matplotlib.animation.TimedAnimation.rst:28::1" @@ -734,31 +663,31 @@ "doc/docstring of builtins.list:17" ], "mpl_toolkits.axislines.Axes": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\mpl_toolkits\\axisartist\\axis_artist.py:docstring of mpl_toolkits.axisartist.axis_artist:7" + "lib/mpl_toolkits/axisartist/axis_artist.py:docstring of mpl_toolkits.axisartist.axis_artist:7" ], "next_whats_new": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/next_whats_new/README.rst:6" + "doc/users/next_whats_new/README.rst:6" ], "option_scale_image": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_cairo.py:docstring of matplotlib.backends.backend_cairo.RendererCairo.draw_image:22", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_image:22", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_image:22", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_template.py:docstring of matplotlib.backends.backend_template.RendererTemplate.draw_image:22" + "lib/matplotlib/backends/backend_cairo.py:docstring of matplotlib.backends.backend_cairo.RendererCairo.draw_image:22", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_image:22", + "lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_image:22", + "lib/matplotlib/backends/backend_template.py:docstring of matplotlib.backends.backend_template.RendererTemplate.draw_image:22" ], "print_xyz": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backends\\backend_template.py:docstring of matplotlib.backends.backend_template:22" + "lib/matplotlib/backends/backend_template.py:docstring of matplotlib.backends.backend_template:22" ], "toggled": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.disable:4", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.enable:4", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.trigger:2", - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_tools.py:docstring of matplotlib.backend_tools.ZoomPanBase.trigger:2" + "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.disable:4", + "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.enable:4", + "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools.AxisScaleBase.trigger:2", + "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools.ZoomPanBase.trigger:2" ], "tool_removed_event": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\lib\\matplotlib\\backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:6" + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.ToolContainerBase.remove_toolitem:6" ], "whats_new.rst": [ - "doc/C:\\Users\\story\\Projects\\matplotlib\\doc\\users/next_whats_new/README.rst:6" + "doc/users/next_whats_new/README.rst:6" ] } } diff --git a/doc/sphinxext/missing_references.py b/doc/sphinxext/missing_references.py index cc0a3a8ea268..c621adb2c945 100644 --- a/doc/sphinxext/missing_references.py +++ b/doc/sphinxext/missing_references.py @@ -99,8 +99,12 @@ def get_location(node, app): if source: # 'source' can have the form '/some/path:docstring of some.api' but the # colons are forbidden on windows, but on posix just passes through. - path, *post = source.partition(':') - post = ''.join(post) + if ':docstring of' in source: + path, *post = source.rpartition(':docstring of') + post = ''.join(post) + else: + path = source + post = '' # We locate references relative to the parent of the doc # directory, which for matplotlib, will be the root of the # matplotlib repo. When matplotlib is not an editable install @@ -194,8 +198,7 @@ def save_missing_references_handler(app, exc): _warn_unused_missing_references(app) - json_path = (Path(app.confdir) / - app.config.missing_references_filename) + json_path = Path(app.confdir) / app.config.missing_references_filename references_warnings = getattr(app.env, 'missing_references_warnings', {}) @@ -264,8 +267,7 @@ def prepare_missing_references_handler(app): app.env.missing_references_ignored_references = {} - json_path = (Path(app.confdir) / - app.config.missing_references_filename) + json_path = Path(app.confdir) / app.config.missing_references_filename if not json_path.exists(): return From 705568a578d7275b32c6f9878924a8539f99978d Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 21 Sep 2023 10:04:23 +0200 Subject: [PATCH 0177/1120] Remove deprecations and make arguments keyword only --- ci/mypy-stubtest-allowlist.txt | 1 - .../next_api_changes/removals/26853-OG.rst | 26 +++ .../test_widgets/check_radio_buttons.png | Bin 24315 -> 23386 bytes lib/matplotlib/tests/test_widgets.py | 59 ------ lib/matplotlib/widgets.py | 174 +++--------------- lib/matplotlib/widgets.pyi | 9 +- 6 files changed, 54 insertions(+), 215 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26853-OG.rst diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 99216f5f75eb..64fae8d44562 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -54,7 +54,6 @@ matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap matplotlib.collections.PolyCollection.span_where matplotlib.gridspec.GridSpecBase.get_grid_positions -matplotlib.widgets.MultiCursor.needclear # 3.8 deprecations matplotlib.cbook.get_sample_data diff --git a/doc/api/next_api_changes/removals/26853-OG.rst b/doc/api/next_api_changes/removals/26853-OG.rst new file mode 100644 index 000000000000..dc5c37e38db5 --- /dev/null +++ b/doc/api/next_api_changes/removals/26853-OG.rst @@ -0,0 +1,26 @@ +Most arguments to widgets have been made keyword-only +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing all but the very few first arguments positionally in the constructors +of Widgets is now keyword-only. In general, all optional arguments are keyword-only. + +``RadioButtons.circles`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. (``RadioButtons`` now draws itself using `~.Axes.scatter`.) + +``CheckButtons.rectangles`` and ``CheckButtons.lines`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``CheckButtons.rectangles`` and ``CheckButtons.lines`` are removed. +(``CheckButtons`` now draws itself using `~.Axes.scatter`.) + +Remove unused parameter *x* to ``TextBox.begin_typing`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This parameter was unused in the method, but was a required argument. + +``MultiCursor.needclear`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png index d6e6004a1732e7f6c449ce3ce7cc87b70743acc7..f0d5023008cabd1296c3cbd30447476c7608cb0d 100644 GIT binary patch literal 23386 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%0ADJ)ar9_c zq2h5k=ltt2E!Y{{9Dh0xuKn^L&oS>)mKM?Wa$l zj!03<5G3tR!h`8N9y^Cr9BW|mDY}asL1?AmE)nSSo4}N&=p!n@jmJ>4u{+(sgqWiX zIRkv;))`n^Gswr#L5PqfouDBi&;)fE38R6lwC*5bX{`@AuH9>H<}?%;ELX@-q8nWv z;r{yVJU-Xo|920$j{?|?9Sd}gW*g{b>uFhbNM&m&WA_$Q!Ub#-N*ps*51PXvT%N7< z-&+dJ)(G9-PfJTn=^^{q2L+XesVcrOv&gkXUNow9jkKxGTQSWw;w`l_Eg2YgiR~`V z=&q@>zs{{F>Y^x!o0oXBiE* z37{0f+A6LRY)OX76tj(tuv2ui;T$q=JeEY7%(!&jA3c)k@beQ$DA2xAYRemN;Yz6$ zhhjW-aR~2`u4faR;Hf&-Jv$&6vVl8f!xIu#@%>g|e0E9QWqBJ5#qNCt3bY(IOP)DX zi~-mq`r#fF_5LSjd2zupLc!icEDR#die4I6f`P?wRYQ2Gv9EpG_NvUS`HoL+cr9Lw zQHj|W-t=p-X{Ie{MmWJh@x9!em5 z4crQ;t85e5YVsR;lg(Xc zl*0^nSVLF82b8-meYznG`iOqKN(un%FOEPPwIyVYI3HC;PWn@yvA&sBOT{r$TuEZBnWZZ|=%@~n~C zX>0M3IY8zT+l*N}etEVvQ-H-K(aW4L8yh-0^XF@5_GuOA#rcvxuSbM%?HwOB5T@99 zLF%dv+A~xx@X{SI99avAEV9xmYWjfGA#L-5%J*!Ube|HMi%dCIlDRExRO+V3V+{Ib7|K$s|B>6d0dO24k=J6z#6CFFcxGj&z+ zP2a4XFtOfo_A|4Jee{$-B1R4|()}LQW1fA_eJ8Ci1~J|CAql#BVYMPV0SPC(y1e&2 z=iI%#eq-Y0C0OJv4RT7c_qX0hM)oP1O0(UcZ;9fCmsOnFxe1vvDgm)S-pcuRHu?ONCSjs7s65DUd97+>a4$h9acH5# z9H~cxGh?L}ax)`pb0mRK!fF_NxiY zTLsB4IFDo*J`iWo3w0LVDIsbv2MyeEOWpS>;AY)lB?fJF4`iI3q0Ky+eDbDg%h>=t zW=B(4PxG>IX3K{uvBY;NTr}$PAk>*N-PXPU70@-p zrWSc3<*VBBE9$5Xg@aJW^kYBCum#H@&SgLrb3#S?>kVtM+D8TwODTM~3#q~$HPM)y zxNC6wyM%EZBriQS<~Z@Rr*DMBitY?ws~&7IWWeiEdlQqU785c$T>~PR%e=BpTHx-0DJ#E}QIl)6da$8)$0KJ%VM^kmfv9n5WLDN9KI;}QwtLG;sQL|cf zvAe=@;x0Zhpw6nBu7ytAwbAia)v13yZuDRvNll!$j+ew@Dr-;7O!s%S-HOr1-90?6 zFZl3wN*!Up+H5f{9js3`liz$fE-#~3EL~PhBHL^|*5Yg7xfnOX{b9p-mpu^;vh3_e zZjow_J)xzX{F7CdpI1+A^+hn~vtnLrED!Aka_(q^Fbn2(sZ8AV=l#fd(YrrMttn$e zce>9B^D8eTEs64u?k|sIe(Px665B_~BYpj0#n`89XDszg3lnS!p2_*-XhlrQeY%X> z=TKt9HeOl0z(9FPRXhF0-aMQIeZO~FZ)1L69Mjay_<8Ev`etz~O6PN=IV?Ed$@xEO zq37PbTTN>tx{P`$>bEmHsa0Vva=_G%N1o}vT=M)N?qGT=2m9DrTH|fAy>(+1166J# zag?ACBHA+_HnEw>`6$~uJ1^1uuxXqS<|HVGkJwZ-)BMo8?;b$9PkITg0P5^WLcKNF zm}^GePlnRv?=)5U!kPV-#Y?w14B9HP-_@RD;9Or8Kg@^2WTCr~+tO&TF*Z$YIoM$> zQsuFS>oo!!z-Y%l$v&^5C@~@tQ;bmxOxIWask&%qIP#IN4NojV!{LiX~KMD4u^Xu0V2aRxqM%SJ+ z-80q~YVNcT@5CJyWJ-IkNYOmVH7%>d{W0dphd6wezT>;fdJnCWnVfRF+>_<8fd>(5 zvDxJ6Tp#^gW+Qv{jPTC4h#zaQf4E9>JB#Wuh%y@g<%Wb1m+V0~N#a(jlD=5*{Azok z=ZD*v(OJ8BBbx+^b^og`u5IkP7I3vhurz7Mq-(0=U3$Lkv}UnLv~~@|EC*KTHT9ZU zi&U~S&bI{d@UGM{%+n2jITYDVyi|a5T)W7kuT+tR*_P=&k+bVHpp4$A)^l5-H0LMJ z=sJlqV8oaOJcPv1?h2npu1ux2+pJzPh$@$FFcU2Ti z%oO)8%^6nG!wqMfQbf)+gi{UBl;86r4mV14UK>n|J=B5dH1}3e`PqDdp7TTs z#Vt~iR=+o*v!~=7w2&V?Xl~hAE6oq#X>{VSXCjADD=$eGySBx)jweR*CU$-}6YFHu z?Ijvv_mGNky+ktN68kpM2YN9`R_@!}K6;~`R|-R{kwNWgxUR3~Z$&0`G;Z9gz0-NT zlYZToZ`W~Ml6Fr*xy)M?H@u~#cxY3_Xu13TgG)j-+4QMrE6txm{NLZ2qBeHo+zH!? zBK#_#%Ka;omx-u~1_!U%$bk<7yJOF(yAO+r-`bA%8uvCf1~N=T6+=3?@g;{{ zq>VI;ZzZqF>3UmLMliMX|JJ_t@ik-hB448q%{SB609RA6x-zpLCoh@~5QfI2SL!F4)@!coMFf6f3RV;n%_b8bl>d9>s1j9XM409YrTKJT-|*`W2zO&r3H#znqq94 zASTVA+P@ER1?x)E&>GjQg5DWv93s*Gw|%r`Ggjl?RqNGN zK00zqokR;VI3LWm`QFhptPk$J*7xw<%)V**4!AC;eO+UpR3mwmauQNf{CR#Ab)>yL zOUu@i<+xtBTpJ2v*+AjF-F-*k)Eu#*B+_?arnn1a+CJ~jr3;US+3buS8QPp*mmq9d zSnRo2Eb3mnAR?-?)MpgnP;Mhw<+g5IOqYCA|1es%87KVm0m?#tfw(8Cj3ssXe^@4=% z!qo1Jpzh?q)l0KC)>?#v5?dmo*f$rs1fN~rT(T~?-kZyTCsxsHI5>{^fY8oo#wYA| zp3b1udXRo|rdKiBaQH$?CWe3uETie!m{G;bbhd zoFFb!zEs>!f5&=S>6*%jv)1_h_Ec=w_Ubpcnv6uDZ_#lR+2V8Q>&_| zjJMQ2+RA-wWHkGU!q13Nr3(oCd*SkvK6_ho;vV#Qj=VLOHnQ9i%5R4ucO`PK0Z-)fk`?%_Xv zkNRAGU}KO%D62tE`{uaomt(WkxEpt8F$HqaN>@?DV%*S}uJG-e=z#XAZ=*)KRU5kV z>}bsic`sRh!sqSVE-wPz^SPXYG)|3t2F!pu_75}%`ZL)&wHZogrCvVVjV!^{tsbN+ zbd7(O$%!^OlDoBNTgzI$5_zzDE$G4Fwr{8IR;xf?plLg6!mU-^?G_=sq|UF^9_168 zhBd#Qxi550PDQkjJ-)ULlAHag@W}fbs{S2GuPUddq0(uf<+VpoUDp?pvVh1T^=XJ7 z@!kk_T20ybsVwr@2wDMBzgiP)jN_{s86Cw|tOiWPY_8jOUbUF%s3}lTiV(88L29fT z()pFk03#yiJU39u!O=xyTy40y80+DFuzQXNSsJqNY|DGazvpaZG~wtMDD=YN$#p}k zhZ2=;k3?Nsx5Zwc;H!#=;WII0bKXujhHoSf)Aynszh~3b-hN?kDpSvfugZO}T=*M}_jdA4#}9>REuX1J zYaX`b8+_xrGCM1CBfw9f>ZzJq0MDNVzBfMx?JJ#LzM>O|V=-oss*i@VSGi?Fi^M-! z&^DegEjAC(!T0G^bsux(t+Vo9%~5N7w$tMB`%BwoPv(=j(2%E2w9`|&JjSvn?Mgo| zbaAZ@XUj+Zm#Hj8oYl7m-k3Qyg;Bs8Vblm^`y%P%SCld<;2kg8hiiwQMRQrXP6yQB z1JZSvXX9P{^T)0F$JxsB`n$Sp7?s)S4C>lqFOC)%#_x%1<#FmW6h{W=6ka7vaB0zV zhbAYhPJDaCb(>f6z{Il+Q}`^Y1oE_`XG1)QeAmY(UFL~&+zva@xBHHnRR#=l<$@QT z*HTgxH0w0WF&NygvY%+{YmK@RFBa|Lt}Nlc7>vFpq>y+;r5D{SCmT8>(X;DW)7+pi!TN`l(6uyf zBO9l-^Da&Bj`vo~s`IB87=@G*bWb0C)UNrZeR}eMkp7A)z|_(ZXD~M+PADQSANott zow-(O<7Itk-aJw88@PokMFoy!;#syY-zWbZrt5`MfGj>g>mEg{Cp{UbUWOJ9M0QmM-{r-v?s`9y6?_n zsO}tYgwk}lEZjK%f~S?z%`;1FryhlK$`#aHMb)CIbKffLzXdXfveMmw5FXIy<(XEu|8=I8 zr&-jaynZ5!a`Z{e|EMn`O4X=TNM2l*Zo)ebePu+5s z$L?9DPA=@)blVK41z%He;i`C{kLO$*#yCay=2|?YUtP6d82FhHP`b&f!LK|2@WbyO z7Ooa;+{%KEu~%&bw_mxDt!K@n#%(v_6J~g9S6=y4+=A-auLnDktXlc8Y~VR|Xa7-; zNp9#_W_Ny#hHp){f7bh?_$^j)WJWK|=va*c7x<KeUW&B<Z&l3D|3h1f;T>FR zL?7Oim&!up5eBqPb`A8?;_%ezAmt+YAgU*+qx8lZRL-zluscvIz3g*Z8t7A>EOJBz zI*^euMwm<=(_H)dC)oW<~w_k}6 z#sE{gvFaiua{u1_Gk$zt?KJF&BP?Hzw{E`(9B45J&eU8hOC8;p;!g4t51`FB653jd za~`q6O%RxX8oF?h2S@7TR8`Y{Y0s`rVhUK8A6yiU7!v2ve_y+sZP#aF5;k-&UI-uX zu>x_qNE@748pLJn0^jV`U^-~Yr_ZDtc?TPMmHt_B{{8Z%sr;wu^b7@Y>;sML2j1Q< zrT#fJJ8=RN*@cUklA+=6CeW~TiEAQQHcdV}?Po^14>D1DNYs*7Y;!l%j{{O?zqHBE zpXNB7wqiH?&C43zgLP?b=Oyf!n>?r$-e>ppOnZAKy~;UV&(?Xt3#kO=hS>ZWA-N%> zY5D8M@&q3rxo`cprrgpI<>fe?y4=j27{45>E z!ER28$y=vJ235_7jRqT7^a8gRZ(ZDaRC#%Kx?d;x$+Msq zAnz}N0Nl@wTW$?b$;Ytx9Y|xJm@IMsEiCHRF|(Ve%ChYE)HpqyDoPM)TxSrus*!ow)NIO%VSiIA3x~59x-~oQOLaK% z9Gblm#NB=wiQ=Bq%#!x&;;OUwi5dRLtw(S0DWuauJNX5CmqMvy(52v9O^bE2R7~Yv z@(E|AM9g$lTNl$IJGTL+^MW>Z^K~Q0g~*zk1nFb@5-SU*c2?G@Vah0m-^{5PA%UOU z5?n1aBa3~@1FPN#HjM4AsUL4-8H!tcr6j#94{y35Du0=gKtrPdm=qX(AIE{MKU^C24nc(je4cS4`Z$T3#L=$C;WXm zx7zE@RyqtjpvhC*&?3Q2$8nj@2MQM7xt2tI`bo(G=svxgoG|tW**sm|orz2yTJ)i? zyu^T{H~5MrT>>XZ-lcf@lN=HI3;m1w5vhNqW8^E;#D-3wL;6dSM?bz?6kP6kZ6q|~ zKk4s@_d-RKP1_-((ubuB(yg-T`7We@Rn z%U<}3ap|@NAvNPA`fE+c)Hp0oEePm0oaSL;7Zfk;=(Co@8=&%{f9?sv%poZdu44!j zVv7Cz(h7UL8q2To3dv>c77)MCYMgv?YjZc*p|{j%BGJ&+1*gVa;&F#qOAJXni+Oi` zK_`jHd+$?I>2*KX-KB2VT})q{rIl}?NMQ87B7NVJI3B}Fx1Y|l(X+bi>+}b4+r>7=@zMwM zgkp-u`FKT<9A4wv?tTSxjX`xi7;Pz?7Zl{ym^bd@V&#(w-{;IS4TzXit@HD&@mkCE zcqb@5ny{OHPQ;pHYz{R5Vu|AkX(*+{q@TQ8yll(oy}Oj!TvG#>mDr^h%mfm9(?VNce!3o zxg~)9F3Ceo4UCga`6&NHG~7wPysye&RC3iTu(t%R$81-PudQ#X;hQ&|e*ZomFnm)J z!)TaVJwIYCX=^^DbaLC%=ZAr4hF*hBFm%j;%7M*W$He@yf12+s>N*>T<(^MB!4wId z8qprxt9j%i&b6)1zER2!UA*vBFm{7Tg($YvaCjH8;9bkw>91n6shONRyyp*6cVtWR zdvu?lqx`i;5Cxg6uf?@4cTw3YvOKa};`V+tv1Y%0cUt+dd*$})i<2OcCFrh)y)L0o z!ENmNf)3mDm5a7#`lkf_M&cdO)rR9cd%J^1IUvE5iPcI6^z9uw zjMHxF52#%sUKv>Za;oQ8_PE_J3&%CCdw$f{HRH!bEj5ZYlJlke(cmucH^@V#CRtL; z+%(vt7cei}yM1LsaP{q5`d`*bHF&&+L1iY$+u=aCwwcggwS9M5XY zJ8|t&x959M2p#;-#jSt7q;WhmWd}c<6Ove1Zucx*1@F~9_-_(hxQWvpHhraz{gL&> zape%pqdnbF45*$R@o4pM>PxBaPI4ggy8UPHuCJsV{oZJy=p~#sh|o&42Ca4|&?nuj zw5EDoMq)tmY%(HzVB<4usr7&+S9z{_j!a&6ssS~=a65pspnZw>uw9QDqD52U^BZ%NW~R+aDm#iO?02{rW31z3s`+d=!nInnNxp9+yR_ z@$EkTN?|#iR+`|xLdWncJkR|7;csFvLBYs9%N8zUwrCU@$UUJ5RWj!CXa2A)*syk2bT-bMOi z3xDFr8EV**Fx}o~SrAGOo)I6dyR@qfzpr{V!e^}SYrorm0mLIB@N)RZA5LRF%Qn!!yfIF4pA*HF{GfpAVGgU4azdRvIV&BEiLmO*1^o7(3TI_r@ORsIY~FfJG-r`NQI zntvJ8@J>0PFDp%r5>)G|E6(TCudw&6L)~re%hxmL*!Pa_4tQxjdv9WT-t{@CHTZN* zG#`GeAT75hvC_{ge460bfC)IJ1gdnpr0B(R-HOGf9`n?l~{qiRJeZm z+j~1GlfD%?cvMP`Mbwxe^N$SM)nDAzlcQQ{2`?0@O7R3dZ;4c3@&q9dYVn$u8k|KS zhq{i2V+j!nb(Itq-&NJpqb|(uyEj>eTlU_RxONy@e(#$zoD^d2IFD%4dmkFa@ZHlb zdst<6Wtz{(C`*>wqMJR-gMcYW6yvZSVi&Zz9_cg}_NZ=mQ{h9+!Crduz4Uv&X{{Go z^MHw!`yDw-AcQ=A#goSkv;4TA2(z|n96NNVM zPNQBw@(UgJiPbcuv4R4+`<8tXU0wcC6{ddh8hO{JzrRiYU+rA^R})zpEu;m6qc(!F zM!GvJA`ljVAR>Y^K?R+$g@6(W8dlk47m!6n86Z*CpoTWsECNa*5_Uqm5dt(wG@v0u z*c26lL=0h(CG*0ZnVxgzUznfj)OqJs)q8d7tM9wty|00?VnE}u+BpI-#*$T!dieOm3iqU4_nbP1kf5dZb4i%XuWD-!&;!w^txa18Neh-NQ zR<6L;K_UpyvR6D3>CCS{NkxbVkFVvHC9P@Kod?8|%Y(Xtuh0Da)QkcnBABWzM5A(b z3wg?Np#drwb3Dzp`I)j>5Upn~%D8S0 z&w1A%Wdi6J(Nz<|5KDb4kE}lf+I#-E!A6d&j*B-0+cWAxfzlEab*v$&)!g?VK*d|n znSE0TEPbjRGDfNKf}=sRuBE{iq@tpsMry>91Vny@(Y_#nzL3S{x>XozsAtghsj4%- zu8(JE!wz3cr-5q69cwXr7PEJ=(J>-0aG@_U4*Zu>4I=v>tC(uj-Vzfa#@lsW1F4SH zFgIyIX3zdEg|qy^i*pt)r!f16joWjQzx96FN65^r9(~%;_vsaTj*IFWImA*mO6l!o z#ym!!?7j^;Wq~e~9%6AwdN14wkM2kFVK8#0~}PTO>=_WjO7(shHH4g$cJ%Rh{&cNxsj&zsKgYqe@?O(V9$p7d1AWF7Ue z*N-<=sg#z*Q$yb>Q@uSK$40X(6KIMDcWdSV8K?T;uWxI`n;f|mw`x`1pB&ktS{&iZ z(sU8K{~ojdeMl)vZBnvBgSw^s<%A+*4I}dE>I!aV<`H2B0-K~0!$;GWZGwpfxff|C z{4@TR-+Ps+7*-%rif#cfU6=IH@mrfG1h$n~zeNSS<7bv8U)W&%@H+JF!ybRm{vC|9 zK<=CmkGoO*ptu0z6Q7yMBj!=^P7+aEQ`tB<<7iN2$@bh$zPwYM$rS#H7xriFJ&z z{c6=mTD)HOH0DKy!VHb##)7_U)Nn+pW0m$$=)`;aNozF=8?ZRl?g27#|H1+jZt_yj zH6j9tH<-;wVpWwa@0*(vEA{b<(WRv2dQRC$cv0-~OlE>68@8nfKiLOoy&`yFqzWj6)wSnzcXR)F$IrZ3GR~z0exaeDCDx*ywZEH4HgLzIX;WWBh1Rj= z?cyngZYm(2-Svo^yBoc7Wm2G__+#jZf7?euJ_GEiVP=i?#$x`$>)aSQDaeis0{+#j zaW^%UY&TyDt5Gaa?R0+cWQYmGXO7f9_h8J=aYtEeOP;t#*;*-v$s65x)AoVlAVcYV z(qnf*uR8a*1GR>OjIK1b{DRWgM*%8BwL6md1cYGRHYc(zsxoG%2>XF9w|r|SlWm!& zh>(I(`4N1?M0vP|GaYm6gK?sZ&r)Yjy8~<(@iWuRkk4`6Sku75;RFi%!wbSmO@=ru zol)vU5Z*}3!ipapr5ZhCSLH8G++hC1wzp@d7m$<9X4y5QMY0&v8urEzt~}4h^~)r$ zF96J0%i1@+C3K_=UGud)F0gV@#9?aRCTK{!E*oxP?KhW@@X?r0KUiG$?MK_j4GiZY z5>I!{^9Y|skmNc0pr)9|3-w&s8;ssHjMlbu`E*Uh@_Z`+5;Zc^c%>l9**tTr^r7v# z4I?c#gx$2j-FgLEdmAii1KbI*wPlWV-jJDY)3qA*U>GMFG$F^DV*C60nt+vInjq9n zE!*zc%0zuv2{NQ7g^KOt5y|PVq4mRdxPZBGY9O~Yn#Zhs6HNJIRd_-&jOC9&V+U?K< zX#;e08@|Ijo_%*TH}_(HMGaQ`mZBHm9N85Z-+J|xTv%>yvjBzxNdRxyVP!qOw24F1F8~?o9OjB05D4W z!c)fi_gp~m1gIY9R_JVlL6F_qW|0{%Hi8ZSyc!Jr+a0zMCM{P`G1t4WFT1PPLJCrL zToQ!qA!4W4a;bKRUpA3lHo$InYsBf)sVrb^Q0%p{2rz*o75@uZKHN^~7Xjx3K;TwZ zQR!B9)k$U5`7Xp04Q*L@ZhOd&fOG-OmYDZ7#ZCiHiJqJPGJ81plizk(S>f*EJEnw& z=dV4`$p-WGE(^=1jl(6@56~BrGoezxCG-GXDguHACWoLNyTlXIaj6slc6uj?+_u2= zgVWo{0lj}+VlkduJiMN_h5cflTwl6^^=AW5a^T)96UH7hY-gC}--goc)?C070d_o@FlOrLdB}5Q}L_uCg1wk-< z5d>ol9~X{X>mHhd|EN3N)^K`gXXfN$=wOQ6HFUDKv2(JqG-7r(b#Sz_dn~{$%+1Sf zV(QE%$S2BaZen68EFvmoB+Sok!q3Yuz{|(aca_<~$;saFIuDQSzhBO6=U~pGiltKm zk07v@*K|Y>GDGxVj4a6ymI$J!s33Dw)irr}*iGkU&jjw8d7u}KEV=SIa#f|F+jrY^ z?76r~1vjnA-|zn#+C5*w`Sjs09ZoK;>b#XZuj9=)-uqjG;%DO%a;LR3&lR*E9!~1u zM%xD3nkCF0?ziL3yxA3>ZGZ6V*MMFko5Go3_-iq0i5Ny$;4epm*jEPrC<8HGpeMfZ zB4;QlC^V^YrO?wv^@u#2H8G?{Lg4gOVk~Aj8~xl6VS}?py#N2={~HTdDCCS4tQ4)5 zds|u>^Bf_L34dZ?(k&So+!!{3?Ck6;&0_W`haDI3D2x|b2mt}X+Rl!Uvp{fg*2{^> z$?@rF(U2$ybCNWx3YVzTs=G%YdWv+pr@yZ4=c*bPzy-p zyEl1-8n5wx-=^~|Ey zf!NvE84LL-NqKsTRaI4m4->Pn@KeOFVT^fsw#6!ArAfatjK z^A2b05(H^U1Zg8y%C+)@jI6;}U$H6Rk9go9INGd zb{G}U=}KMIy0rTW^(^cul_4(tn)sopG#bnpp*ajWRq7SlFE95*qs|u6A6_iuR ze3*9aTG!Mg!~bB(n{X^GEg6`bn=hG>Q)s^P|2C?fS1y|-R)~lCQ+VKbr^u=Av|Oe> z4i7qYG7kFs)F*tXpD8~#+7pmCb+sH`VxRYZ|D4CJPn|bFuCLLhfAGTdW+lywHv}Hzr4_Ju-@0F+_!U9_EW??*2ca*JM-#8k5%_) z%ZFekf_5YH8L|-^PwBt{TF=6K_zsb*9xDnOnh;jG$gx-Y-e2c`eTa^aH?tRIxb7sS zqH;!+B0>X?27_++#-*VRc8(+c7n8XCHNYHMk!JKEo*pr#I98Lpfx9rbQg-+6K2 zmi_*Q>F{Pi0CqUT4N5Mp&r*jw%Z1L%TJkZf7K2j`qVJUw{nho{a7al>achN9OZ>KG zUeV5tD8>X3oRz5K%&+qQdw6owYJm-r!x+LQCu7FcTD~qLvuD`)($eC07LAdyG0CUd zrU<$$t&*G-H*iz1Z9c9UGR?pOHm!@V-+W!iXd-B6biH@wZ+&gLK{Zwq@5*ajtn`czASN)HU5^+qa< z(#KrBJuMw6zDG2@L{b8X@dbtG;IPH#r6aWAaF@EKruSVAlM@pKp8F0arl#ZVi2@>S z8*Do(!?_;S3bE{aOU0c|os~}W{@?>fgI{bDBz+`SMr)()CJ7mi)_BczXW$`e-hU4) ze!f$9`_y~$hj5wCi9|=LBpDSI72;b`a!q1y3`;3RjE3;}x4Qd503LhP$4aRKICUpS zvkwt045i-fDmO@?5f*+nM_*Vqd?ViDUOAGDp#A#mS8>;MRu-17FSgpc zdsx1Yhbu0Ki!*e0cUue;WiqZt!$h2Tj}q~UiF~IsEq}f5ul5i|nAzEJ^R!C7wZ?Js zc^x<*bqCvvg(lz6T-C3k$H2g_cX0UjQL{L$+X6x6=jRK)d%V^f z9Gjcd7~8+7*&+=QK#ACArhaJ#fyjVUcOQ|!O5>VYlGo6Vl3nbzUH^H9XI?j`wpMcH zM^X@ME%}uzR}kN7k6ojwmS}2N0`=(71j&Cv!8{B6YJD^>$)iAsj+f% zf9i5y=*>$qzlo0={oNg{=z}oQj#HoN##f1mn6*=fGf869v1w>t!y_jy#Alc|dp6C$%&XG%nSd%N@^I!9R4=W(3s(lKme?pyedvmLiPQ^b{X)$*kCE8RBP z)6>&mfOlWIpTf++L8z>(j9V=UZc_wq;|*@pYQq0fw-OglH!SA&Jqv52MTU_*j5XXY zOox5!&lXRJ;7$$GZ4_(U+t`S8?1Ri-on8ZD7ncN8~VrTQ!*gnxOFHQVR-alYR`vCi%57z2-hX>p&s5tSX0{V1HRa&;eA`!3TYK_3mEPqI zdQMJGL95>_X%5RvOI>A8CVvV>0ANceI_zakBo34FLYhTGRiCts~Wx#-)Zt|4mhqm z&H3g$%1s9gD05lW%WjjfA45LrRp)rrn0GRYxvr%(Oxq4uobSnbR1XkR2*Ji_v6loq zjpI=w1bQ69z1*sH3B;;IOGZ|9q9vMjZ~B$K{lS*yyUa`)oqIu~3@={3#Ew&Z&Z+*1 znVv^gd=mJb%k zX=RAU&(9CcjQrcTZyP$VqNDxxH)mOSdC7+>U7GD`|HkJpUAPrEwlUpSF$?Jx^$q8% zTAwZaz=4gI|CHP&op)aTbKZ9FOOJT|qg+b&-Ia(tuefQBBp_%Lep^%DPt-+TvJo(O zu3_l8O`kv7?37Fpv^*2ZC?1+5Y{w!Y!3diT0iEIK=!n<$kD#D+FL7aEA#R`JWD|*M zp4OvXF^Gnc=q2zPVoK~!opp6{d#hXN?ADqh=Eh%hd|<7VBt$aV6j2Y$K*7Y6`eBI4 zdk@29bp(eQ)A+-^H}+uES9L4StyXQ4&UB_mK~(BW`ywkNlXmkNE^e(Hxb7_9ktS|& z<@q5heNR6Kyhhs#J?P`6HmYcP+uDeJyuMc7UucZtzh1SwA><1pPbRQ!Y-|X*gt@s% z+g|a=si{#yM0^%|>AunGNOeVDlAxu3r^KPsZWXLXnd>^s7u&%=SlG$qzlZ&wEziR8 z1u=@dBgjHueiZxN*XOimg-1P~9qz790*F@}p3?$nc}6BtH}yUC?Sspqf5pJFg9{3H zZHCGU`yF9%QJ5Bmc;kIA|AAV>o|*k#vc^(537t^G`@2b9CD!Tzfr06_f=HjIpt%T4 zYyuDmML}F*YF}>MiYZZZK%KqyW~L{z9SjzNv}|l_X|R`wFT4*c`1qR}F1>F?Av1Ax zcc;`cmyyB#^Tjr4mc3`V;^{4S_v`5y8Qn#uZ7idskVEmJ=zh3dTJ;K?H;%AA)f6JO zhTdx-+fcA0ECfPCSI#5Ok2>XNeto!)hkVqpWxNxA^(&Ya1vxnaQLz2>H6q#wT;xSc z3aPcVwTAZWU->u5$%J_VTwElPk&!ql`a{z3@IvFurK3%dLGam+Nj)3kq@d_7rarzJ zHShx?nctKUmgAD=&VYHBRmtrXI@q!947m(2PuKa(S6iRzM@N}9XMWT}UQj!XNC%rjgP`@pT`{~AewOk4+Ds>C-r;EKmikY#I zVwGo4!yvX#iPbM`N8Jt1LEVTm!m+s^2g>=D?e zw2OCrAW8T8yL6CWo`rSN%A+hc@02w&Gkg0Koqg%!VHDplD4YSa3MhA+O*ijM$$FHl z=GT`j;YAA&+ypr@`MGleSy`85Zr`3JNP!S_V6a!LV@8FT^3y=HN6j}KEL@}5jHKe} zBCe#QBrv3m+hLU84gTC30G@SyeSKr@$;rvQrmg4DnOc?55>Z~2vaz`;CMGtvun>xc zM{3ZOcGK9l_4Qb%Xg1eh>B95# z*RR4Sld=F@bBqi(oCWv9&rn298|@X_@P7~9rFfXz1E>Ua#Env#S~|C9;!lZnnd2-e zzktA72y~Fau*{Ccse&iMu93>g$+>z^9q}kQI|~6~kIl@eO<}$;w-lspXlSS#7$}!9 z?9qv99IdTNa2N~@R%|=-?LW|}{1es3s{b)um4&_v4(15B7$C`!GC+cH7I@5e-Xmqw z;#CYb4lrQZ(o8buKvEcUHc--?(gO)W0s;c0vQ-xtpZ{2AW8s%0_Sq5x)`K|CwEHP$ z{uEEC4HBS;E;riC@VCcLz&E-O`R2`=!laurs_&_fvp{7)9* zKC0(%r4ZP$I!HJ1A(@4Jk%rXk@@4!{uU&>5^?bdYA~JdrU#p&+dCW1?vyB>p&%APh zG!+fa-rnAj<#V7b40*uPU@6%t+-ZjdXE_)EdM9`xzpYAwLqoqo_;?W)_h=#_?FQgz zhFlaT`7Zg&P`Q9vM{-D>c4@q&hvN#Djtc5&Mn)`Fzq2q9*MqGq?mJ5%;56e9&P}_d zf&H2j`@AD7*2Q*M_Lwzi-4?c8J;Ts;4r!UH-PL4LoAgBkxTTehH z92^xT2jR0fPa6+h3dJixlv_47AHQx7l|8`%C+!ixFE1};WyOJpjQbM%*m`<;7C>oJ ziXe{+&#yhE{q_ECJ!DF}uw0(G3O;+|cth15RKED(Z>~T61~H<*XWv10(7x@qAZKKZgvd7b#$|*LbtvWOuWNF+(c$u$Dhdc&oMPtgH9FzPFA;gjK;w&w zi^IfrA&kW2=jZ2w&#Dz6_u90@&-D`(zg0D zGwd@wT_J{T9>Qr%L_~zsT-Sx+#aBG~@u1dh&3{N;h}6{7RFI3Lk7iSdsX5xXqmZpS z<8}fWKs-GI!^ILUU7KNq4fzQfZ|293A6YtuZ(rO$R6qCdF&#zs=4Y+E&-S~cD$AOO z7e@yUGnqP?Xet$Cn^)J<6LrbPKBRhw`eET2O*DkOXbXFc2FgOqU+l@Ej<}Hb-~M;} z0|-YmzyE@NbHkOwC?^3tVHFdj7q%P0{-3h0K{4U%mEJr$h*cB}47w&BwI|0O=q3V( zmDSMD$fk@Zr-ED#QXaJdSt0HV%p~voHL`q4CSgXVeWfn#UTl0wT_F|v$_n33x7UaK1&oZm(J*x%Iz`QSsG|*Z@>CE^6jml3GgqCa)*m- z3g5u*cwJU>ww4CYmRR+0ti2xfJ|yuuTE}CQ^bY^{@oGR&kTF~e42Q<5?~`p8N{lue z*5ooxoiQG;#~OlrqM6b{Pj1jP>)tf&(#LC?o6ky1h0)BH;4J%`|0D2cPcJW$PtD)I z6Fhvc^cBXwRm&|j_=?%k)btH-?bD}EBET1b?7p0R{Ub>jFdq|+**|@H8+h7lK}!`2 zhMJn1y@lL-)OS_0Rd5&?8R2ajz!r(B*&*u%&<>P->-**n1DGfQ6BE<$+BJyck0FXv zn#8C!ymy=i+I%ZpRXS5;S`*(8KTj414hF1MElsr%}2mha;{^L;`g#Qc$I0^k{s0qAq_oo+*xtQ=pKkW!b<<-)5GW1zSRWevMlQc< zLxtpa_7~BGvr)lU3F?^=3Ar&rM9x8oeM}Tjr`AdXM`=+xGu;79>3uLtQ?rtJkD{}U zP#VjD2SZ!`8A=U9ub&R(hoLjRr$fVGXt}}ZP%IdF?{uhQ$aqwHgt8ruoC(K&m3Ujw zTZO zK_C5x`FlrE`QNIOLHjX4b!&2Q1n34S z^T7rdoz_A)FzcBf$)0ZDbJ(@k)~vxgixMp$6jE;=NU_J#6kjpHj$yk1y8-SLpwn?e zb%EvAH@H)xGL`}8g{x&!fRRD3UcEy1=UqLVdGjx=8*!QcA|~oG*s?$C8{BdZUiu<0 zh}kqdPoF$7L_QnPA75g52$&Y{e>NY~BA8$Nb96m{9y4j4?njXYEN2Kh{T3s>qv*`j z?FfT~j}Jc`ND2c_ccbVsv5yS;8YVZXkAtys>&(F!=q0`BA$)K-uMCAiCMT9p0@*g; z#MRP+M;zw^Nf|;L8f2d3-&InQ(a@ldiHT`_YYLG#YCP&P71r3v(w`C{aPzsrQl8#? z{ix?eRNtDL@oohYV<3=v8G#rpfA1c5sSt!vY+wdKkP#lfh8#K#$Uj=!Kp_Jd)GO^n zP{PjFGb!uIR-e~Vo|u?W>`9tlSs{h6P~9Q!yhM%S8bn5;^@+v;G$=zHVi6H3Yp~72 zPf1DHo6Csm0xlP3ur~FG7FB%RMnWCgvM@MJvU@_l# zW9s{hHpd&2 zc}2;{#DkM(8L-nJu`w~g1|xyJU0`Oe8(YlRs}6-g&vNBTc0ZDkoE#1e$Ox#vdz?7T zikL})KEHa07A49+V#Xo!MWsyHjGM5?Na_Kil8jpn~mdczw+pK_CHXLbEEz z86~CHf&{y(qjIXMs(w!)sU-v%;ezw(h%R^pOZqpyBZ~7KY*+@7zP`Q=tHYsDQFsWX zsB-z6a^ci|fZP}e(Bm*jo5z9ET8vbQqG=914|3%D!r@D>ghIA|m_beeq_1W@QfpIhG(f(}eMZ&MJ1mUm*7VHcNr*ub`jjU{--fXFL-@V<+%E}v1 z13Po}Y(8jhUF78B<5MKOUh{07Zvyh|^LLvH#8%z0Mc}Y%YHIjoWOCK++k{-^prV0BhlPr6;BFBR zmt@>w>3^oeO)=|2&vV+Zj))V}i;(m2@gWU0K6s?`QnMXzwzUN6i)LD$S?~(zlwS%6m2%+N}#HOCdzZav(JJIqH6KxeyYT6z(in) z%nD~#MykWIva-(Dvfq_4`2=BrxE{err6Lh1*QltdtbHTmse?s<^`aJjOb`B_<05+Z zwcyuJx|Icxbpn=VxNk3HsbmtO;?%Gxlg|+!Sax^W6Jym}HIj~wj*9c&IuAkETR%Qr zMTyt=_&83ABa~@?`7$#zBQ!KLXz>b?LSFL@`g#d$78ZfVH8~TJ#FqkmKk$EBU3;l` z5eiP=YfjCO`k^X}iXGNjN=maej)@>LrU_7&V8;Salf=ZY+jn9Dzz|qjS+Pb&Mjl)~ zx8I?ls3;9_7bRVwH9uR-A|^aMOu|GV9dye6mmg&Ii(@?w9q_XJgy`pX?J zLGg5>O2}#M!BCZ3@z8>Lwkl}#SznVxPC!yb2S9+<{Itx>>(_YMxy?M7=*1Eu8F!H4ikOv8`uTV~u0s}`M{tdlM@KM7Jj9wdl7Ve$9( z-}>`HR6yXYK~8YS#DmQX-)DhmDPm{j7>`*quqT zGg|D;%hId9j*Jc3)i#1O6xOHbL9#qpD(tY>9weLLC!5hNuAbW?YePbT0qZyYooyA{ z0rIxrx$2!`dR68FfBvwrv&-h^nLw(HRjvavQkQh6+tD5$1P)Yo1!9gCqyUuAz(Ff$ zk#>XU$`t~L=Jg?GFO5x1`1k4iFn|;ksFN}{;J!9?)26=w1L!W480tXhM)fk3djhDS zCDc#tpw$?h9REe{1_5H*eHCH@lh+mn=vn!F`%OlQm7hU^Fa})Ok4%yt48BTH&lDGb z<*#tlxx^`Qs6O!*6&0bSI&2)A`N-Xc9wIz4MgY@Je%qh z3ChC!r%_O*pz<09d7fco=y_^BBW$Q)5`DCW7=-HEP-)9_*$WE^xe4Jd<$+>jl7KlW z*uo0v%XUlb6q;AT!U&mZq7^e^5P6O`j?Q&ZW(j=HQBtx)-o+s4twTsm44fKjsz1Ly zC(4|gv#{J-8;?<9k45A`f{%t^p*FUCyWD=0RClyao^F(Q(IjO&n&2|y}~>Fh2oJJ9MCE|w9DT4_%~*C>F-Z6 z^U3gGHvWf4j6AQucVKv`HAgZ$Ss4;USLXv0! z!Q55VO`BWj$k4c)>;hnkCQ*w8pEWs~Zhf4XCi5 z*KCGr>-yfhF=Q1B!<(R5tglqAP5>j_1IONeEubJ{qR6&$MDd6^mL8KkoK0ch|L(mm zj_m7C7wTGD38D7s{sy`xQfJF`xaC0YL%>Gy?46tfs;kB6>FJ?*i4V#RBrJN`sBb(deXOgYsd+(6tg6KU^1J8o{*cd*f*V;y&qBR@chrZe_V|F)`{b|7h-6}L zJEW${jRNxsH5FA548HIWT2KqwP=laOPEQ{WP&1yJUn32vCxo2>DDwzD9-@wAzbmb% z7VL;JGbJd|Kk|7L=6Nd9YGl4B)Gpmu=K`{$~ zc8(xE<0b=;>Vm_=fA=_o$lz(32U0IuL4idNfQ!y+&AzfnD%mf=mOprS!;5@xZk7W9 zA`YZy^a0t%&5;#-Kn4Y&G@!s9s{nu%lb4sbwKHgUC+1}({uHqEicRpV%jMgr0ZBWG&R>LHj23N!~+h`5hx$DX*JV5|<#5+wEzGlwc&$e>)pqf7&l z1Y{rKz3all!e!;r%Y&-_z_GNZ2mgX4;W#4-q*iV2k}~Y8u*fl7RrsXbl~ztMwFFu2%{rHFxF@w zNrTL>8JbDLP}sO|;XT^sp>T@Cv{INC9 zOGYLTQaFBHb~*&O5r-~NKTp|OrkkDlo}Co58}rV zaG=lSqE)N}l==Jt{%&Qe%zOO^rx94&XyZY#1fPK5CREZxU4v@Lfb=|1{&mT+#Fo1%d zUgveG@(S7%1FRaPDGUUX!~iG^Qh*B7=&@RDKGjPReG$@KelK>nOE2K)TVNZwyA$GV z0OydLy4zj^&5y2S+5sXOrF#cJ4^;5%x8{_WhRV-^A_;~6Mo8J9NP;!&rl6pJ0_VMC z5e!h{tBwyA6QHg+wK3Gu`wWlF2mmiQEbLiO5FRcWV|X;H92yvbF*Wq$sy{Vd=`X~B zl3_F4ZESu%yU)W1@|3k+Z9CMdAu3*dUv;MvEV|_-@ZRjPUDs|U2YeAxQ5wjGP~8zi zhEuc0_QG>`6wB4CO1o&%gKAAkK~d2+w5DxXj|=+WTuHPOz;s87c-VM-&|=S*^~vV? z!9hBx;GtDAaE(l&9IzGGr)JPM(ZB>FCLw+|zf*{H>a!cEqVYQ1xeXXnVAMz$PR)l2 zHC~)vRGdTGlF)7|p(9gM)0>$phOl^OS!cv^^KSJB8HKVYWRU!<=e>8w&CfY!%>2KK zv>+rA_+m?;fvW}#&?uh|=e=jfV@Ro^ul%$s`T1?w=T3?F@+(jC)mPpkbmmL!(a$H* z9dRZdE57c$6bx+!hJe%w*Q_bgLx%pO}nlS(b+MTAHsdyeF;#>K<&<{a-#^X4+JyH|9jy{HMTAMiL>}SOsh|ORZ z(oRl-u;Y)G{zsg#*>mt+Rxj1L%(CTXZA!oO)i*dU2`{nZpoQ2$0Y zz5K%N0nN;D(F8;R9t>XYT!X6Q&`v}I&;W`rm1YNYb9CkFOG0@90~pG+J#jQy78f5L zTMP~+1`WxiM(H}z)Y$k|zt$U{kWd>~%V7bTPqUXK)ja2$$5Ls_RZ4*_nL5nc} zFofDg>BurW3;(%#rszb0-lXm6nvu}@=*{musI-}l~LHFuzF7ZqBeWCvz4yiY{M z{SpLcLN6Xzo_IiYwpVR1LjZD09=)rk=H`okMI}fPF)(?c?6D(sH=(-LiAn`vQ0QJj6Xdf$TWjODhHAX%fL?=C|0{Q7R^fk1dR^lc zGrvK|8lRfNh6EBav$&k9P@{hXhvlXV0)8 z`6j99`0<0}@#DuAa|D8v*l&T95h~U^JX|fa%o(hb9iWnZHMR&EVE}?v-4aZYt@ZVr zs;V#b=tQZShKF_YD}Bi+l-EIJ;Gp^sirj=U;QfRx4p3qFxgis64MBBELQpJK-B86` zSpuLgjt*5}(Q2ORK6)@3a>Z!VQ~el3X_@!{^cuMHY1?}AbK^bMA~96kqy%ZHg`Z~o z9y4?mlMCChx5aL{bfV%a2PNELn%@dhx_cJ~arM|FYL3Z#|6V#7kt2 zWP?eQn7wuMJRraT;JfKDu5vj{iUoP0knDR~)qpGw7~DUqMtDDedJ!yHyv%8JW5c70 zTy`0QjsLu}vvUDtSJ2+wN>a~6As#kZM^5p`0bcjCq@Jz_?*?5*5KmMUAe&J>?cC?M z5GcuVT2xTJ2sf2!L}i8?8CV*5CUimKu;EtoVX$0TXHc#0)@cv8C|tyU4wg8Y243Jw*1h=A~bYK+P_kQK2!L`w#$sDR$03T@a@&O$HZa4bqPIcKL7#(KHd4+n~& z6#BibwUt9xJGkW?tX}jLLj>Dbs{~uWTf?Ww=%Ql(bKMYHwV&X|z9U}N_OBBvTCTL5D zjW?S@M~VA8k0_iL$7$|`2MKaCdV-Pa!|i=vp4^m}8u@I6Q+47!zDz`jsiS+R*O`U? z3Ye2gPWuhS%oO9~fU1iZ5plsS+YlzN!q+Gz6|t`?D7u$Q+1LL1;y2Ao=ZQV*e2nq& zy5qlfah!EK%ThKUU6=pv`c1d_vC7GiO=aG}c?GI{SGj=wM1aWbHymjHR6P0;(y|4v z5Pd~p6>;79NbUVboG43Aw>^nY{0zPYa&%N|Xv!c?c(lj5_CpvN$l=ohk(78`r9@1p z1vY6N2Yzw}5-~4AohtmTWzLRVWh{%)nz5q$DcHCqXKDxgk-vvwDR)?MUZVT``%$E* zuc_g!39KSHlsQEw%j#^_0NPj1~ig}N6PSe7zBgpJhE(+MGV)fc_rXNEWu7TM6O z%_P+o8cV6f%-!}}%=7PD{!I`ZdZxB^4f8>*L{J!~6v*NLV>U}>FVH~2;>Z&FE{6LR zmw1U5Nu~-xoFdMvWKoxk(J?IUJ&fWU`$D|874G6NN}Th+mbjmLKG&zZ3gOI6Z!7m< zvUkExe$)2F%Pgn;_}CQsz~P^{l-yHSPLdfs-N!`6hX;1ySNK;M>*yclAgprn`ul{> zUC+sI^N3JWHq>V<{gE+k?dTwdPzmuN5siKPax>!aLB>Chtb+!sI0SVb0hH@up~g{; zHrn!|2Or_a8B@q#X=H02i_-R)OVMu2Qy$K~uD9b&qv&Cp*!xs+S%UVHb@2INbpwq} zL&P`J=W64uVd~`0??F?w`>sYJ-nIKT9!pe&_No*78kkLM?H9W@`~l3<|kS96^w8>J!&FLldrc zf3q6g53(uJPd_YB7yA&ylr%qWb1xPVTRQ$~{V3)m?6XAsndnNf%Gz#O?Gw8L@pd~& zre>ok!-3Sc){l%VcK}&s;z&FYA_HU7XRoUDo2qs;dEG}Xj=VQr!W|egJ^tSOgGFEc z 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 RQh=I=7#W%vR9tX;@NW~Cpx*!h diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 1ecb4b9a1df7..3e41712ba530 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -7,8 +7,6 @@ import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt -from matplotlib.patches import Rectangle -from matplotlib.lines import Line2D from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing.widgets import (click_and_drag, do_event, get_ax, mock_event, noop) @@ -1055,16 +1053,10 @@ def test_check_radio_buttons_image(): rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15)) rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) - with pytest.warns(DeprecationWarning, - match='The circles attribute was deprecated'): - rb1.circles # Trigger the old-style elliptic radiobuttons. rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15)) cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), (False, True, True)) - with pytest.warns(DeprecationWarning, - match='The rectangles attribute was deprecated'): - cb1.rectangles # Trigger old-style Rectangle check boxes rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15)) rb3 = widgets.RadioButtons( @@ -1164,57 +1156,6 @@ def test_check_button_props(fig_test, fig_ref): cb.set_check_props({**check_props, 's': (24 / 2)**2}) -@check_figures_equal(extensions=["png"]) -def test_check_buttons_rectangles(fig_test, fig_ref): - # Test should be removed once .rectangles is removed - cb = widgets.CheckButtons(fig_test.subplots(), ["", ""], - [False, False]) - with pytest.warns(DeprecationWarning, - match='The rectangles attribute was deprecated'): - cb.rectangles - ax = fig_ref.add_subplot(xticks=[], yticks=[]) - ys = [2/3, 1/3] - dy = 1/3 - w, h = dy / 2, dy / 2 - rectangles = [ - Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, - edgecolor="black", - facecolor="none", - transform=ax.transAxes - ) - for i, y in enumerate(ys) - ] - for rectangle in rectangles: - ax.add_patch(rectangle) - - -@check_figures_equal(extensions=["png"]) -def test_check_buttons_lines(fig_test, fig_ref): - # Test should be removed once .lines is removed - cb = widgets.CheckButtons(fig_test.subplots(), ["", ""], [True, True]) - with pytest.warns(DeprecationWarning, - match='The lines attribute was deprecated'): - cb.lines - for rectangle in cb._rectangles: - rectangle.set_visible(False) - ax = fig_ref.add_subplot(xticks=[], yticks=[]) - ys = [2/3, 1/3] - dy = 1/3 - w, h = dy / 2, dy / 2 - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': ax.transAxes, - 'solid_capstyle': 'butt'} - for i, y in enumerate(ys): - x, y = 0.05, y - h / 2 - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) - - l1.set_visible(True) - l2.set_visible(True) - ax.add_line(l1) - ax.add_line(l2) - - def test_slider_slidermin_slidermax_invalid(): fig, ax = plt.subplots() # test min/max with floats diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 0a31a9dd2529..f24be7904690 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -21,7 +21,7 @@ from . import (_api, _docstring, backend_tools, cbook, collections, colors, text as mtext, ticker, transforms) from .lines import Line2D -from .patches import Circle, Rectangle, Ellipse, Polygon +from .patches import Rectangle, Ellipse, Polygon from .transforms import TransformedPatchPath, Affine2D @@ -355,11 +355,10 @@ class Slider(SliderBase): Slider value. """ - @_api.make_keyword_only("3.7", name="valinit") - def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, + def __init__(self, ax, label, valmin, valmax, *, valinit=0.5, valfmt=None, closedmin=True, closedmax=True, slidermin=None, slidermax=None, dragging=True, valstep=None, - orientation='horizontal', *, initcolor='r', + orientation='horizontal', initcolor='r', track_color='lightgrey', handle_style=None, **kwargs): """ Parameters @@ -627,13 +626,13 @@ class RangeSlider(SliderBase): Slider value. """ - @_api.make_keyword_only("3.7", name="valinit") def __init__( self, ax, label, valmin, valmax, + *, valinit=None, valfmt=None, closedmin=True, @@ -1115,32 +1114,20 @@ def _clear(self, event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) - if hasattr(self, '_lines'): - for l1, l2 in self._lines: - self.ax.draw_artist(l1) - self.ax.draw_artist(l2) def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: return pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) distances = {} - if hasattr(self, "_rectangles"): - for i, (p, t) in enumerate(zip(self._rectangles, self.labels)): - x0, y0 = p.get_xy() - if (t.get_window_extent().contains(event.x, event.y) - or (x0 <= pclicked[0] <= x0 + p.get_width() - and y0 <= pclicked[1] <= y0 + p.get_height())): - distances[i] = np.linalg.norm(pclicked - p.get_center()) - else: - _, frame_inds = self._frames.contains(event) - coords = self._frames.get_offset_transform().transform( - self._frames.get_offsets() - ) - for i, t in enumerate(self.labels): - if (i in frame_inds["ind"] - or t.get_window_extent().contains(event.x, event.y)): - distances[i] = np.linalg.norm(pclicked - coords[i]) + _, frame_inds = self._frames.contains(event) + coords = self._frames.get_offset_transform().transform( + self._frames.get_offsets() + ) + for i, t in enumerate(self.labels): + if (i in frame_inds["ind"] + or t.get_window_extent().contains(event.x, event.y)): + distances[i] = np.linalg.norm(pclicked - coords[i]) if len(distances) > 0: closest = min(distances, key=distances.get) self.set_active(closest) @@ -1227,20 +1214,11 @@ def set_active(self, index): ) self._checks.set_facecolor(facecolors) - if hasattr(self, "_lines"): - l1, l2 = self._lines[index] - l1.set_visible(not l1.get_visible()) - l2.set_visible(not l2.get_visible()) - if self.drawon: if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) self.ax.draw_artist(self._checks) - if hasattr(self, "_lines"): - for l1, l2 in self._lines: - self.ax.draw_artist(l1) - self.ax.draw_artist(l2) self.canvas.blit(self.ax.bbox) else: self.canvas.draw() @@ -1283,60 +1261,6 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7", - addendum="Any custom property styling may be lost.") - @property - def rectangles(self): - if not hasattr(self, "_rectangles"): - ys = np.linspace(1, 0, len(self.labels)+2)[1:-1] - dy = 1. / (len(self.labels) + 1) - w, h = dy / 2, dy / 2 - rectangles = self._rectangles = [ - Rectangle(xy=(0.05, ys[i] - h / 2), width=w, height=h, - edgecolor="black", - facecolor="none", - transform=self.ax.transAxes - ) - for i, y in enumerate(ys) - ] - self._frames.set_visible(False) - for rectangle in rectangles: - self.ax.add_patch(rectangle) - if not hasattr(self, "_lines"): - with _api.suppress_matplotlib_deprecation_warning(): - _ = self.lines - return self._rectangles - - @_api.deprecated("3.7", - addendum="Any custom property styling may be lost.") - @property - def lines(self): - if not hasattr(self, "_lines"): - ys = np.linspace(1, 0, len(self.labels)+2)[1:-1] - self._checks.set_visible(False) - dy = 1. / (len(self.labels) + 1) - w, h = dy / 2, dy / 2 - self._lines = [] - current_status = self.get_status() - lineparams = {'color': 'k', 'linewidth': 1.25, - 'transform': self.ax.transAxes, - 'solid_capstyle': 'butt', - 'animated': self._useblit} - for i, y in enumerate(ys): - x, y = 0.05, y - h / 2 - l1 = Line2D([x, x + w], [y + h, y], **lineparams) - l2 = Line2D([x, x + w], [y, y + h], **lineparams) - - l1.set_visible(current_status[i]) - l2.set_visible(current_status[i]) - self._lines.append((l1, l2)) - self.ax.add_line(l1) - self.ax.add_line(l2) - if not hasattr(self, "_rectangles"): - with _api.suppress_matplotlib_deprecation_warning(): - _ = self.rectangles - return self._lines - class TextBox(AxesWidget): """ @@ -1361,8 +1285,7 @@ class TextBox(AxesWidget): The color of the text box when hovering. """ - @_api.make_keyword_only("3.7", name="color") - def __init__(self, ax, label, initial='', + def __init__(self, ax, label, initial='', *, color='.95', hovercolor='1', label_pad=.01, textalignment="left"): """ @@ -1513,8 +1436,7 @@ def set_val(self, val): self._observers.process('change', self.text) self._observers.process('submit', self.text) - @_api.delete_parameter("3.7", "x") - def begin_typing(self, x=None): + def begin_typing(self): self.capturekeystrokes = True # Disable keypress shortcuts, which may otherwise cause the figure to # be saved, closed, etc., until the user stops typing. The way to @@ -1730,9 +1652,6 @@ def _clear(self, event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) - if hasattr(self, "_circles"): - for circle in self._circles: - self.ax.draw_artist(circle) def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: @@ -1742,16 +1661,10 @@ def _clicked(self, event): coords = self._buttons.get_offset_transform().transform( self._buttons.get_offsets()) distances = {} - if hasattr(self, "_circles"): # Remove once circles is removed. - for i, (p, t) in enumerate(zip(self._circles, self.labels)): - if (t.get_window_extent().contains(event.x, event.y) - or np.linalg.norm(pclicked - p.center) < p.radius): - distances[i] = np.linalg.norm(pclicked - p.center) - else: - for i, t in enumerate(self.labels): - if (i in inds["ind"] - or t.get_window_extent().contains(event.x, event.y)): - distances[i] = np.linalg.norm(pclicked - coords[i]) + for i, t in enumerate(self.labels): + if (i in inds["ind"] + or t.get_window_extent().contains(event.x, event.y)): + distances[i] = np.linalg.norm(pclicked - coords[i]) if len(distances) > 0: closest = min(distances, key=distances.get) self.set_active(closest) @@ -1824,19 +1737,12 @@ def set_active(self, index): button_facecolors[:] = colors.to_rgba("none") button_facecolors[index] = colors.to_rgba(self._active_colors[index]) self._buttons.set_facecolor(button_facecolors) - if hasattr(self, "_circles"): # Remove once circles is removed. - for i, p in enumerate(self._circles): - p.set_facecolor(self.activecolor if i == index else "none") - if self.drawon and self._useblit: - self.ax.draw_artist(p) + if self.drawon: if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) self.ax.draw_artist(self._buttons) - if hasattr(self, "_circles"): - for p in self._circles: - self.ax.draw_artist(p) self.canvas.blit(self.ax.bbox) else: self.canvas.draw() @@ -1856,23 +1762,6 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7", - addendum="Any custom property styling may be lost.") - @property - def circles(self): - if not hasattr(self, "_circles"): - radius = min(.5 / (len(self.labels) + 1) - .01, .05) - circles = self._circles = [ - Circle(xy=self._buttons.get_offsets()[i], edgecolor="black", - facecolor=self._buttons.get_facecolor()[i], - radius=radius, transform=self.ax.transAxes, - animated=self._useblit) - for i in range(len(self.labels))] - self._buttons.set_visible(False) - for circle in circles: - self.ax.add_patch(circle) - return self._circles - class SubplotTool(Widget): """ @@ -1974,8 +1863,7 @@ class Cursor(AxesWidget): -------- See :doc:`/gallery/widgets/cursor`. """ - @_api.make_keyword_only("3.7", "horizOn") - def __init__(self, ax, horizOn=True, vertOn=True, useblit=False, + def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, **lineprops): super().__init__(ax) @@ -2109,8 +1997,6 @@ def __init__(self, canvas, axes, *, useblit=True, horizOn=False, vertOn=True, self.connect() - needclear = _api.deprecated("3.7")(lambda self: False) - def connect(self): """Connect events.""" for canvas, info in self._canvas_infos.items(): @@ -2613,8 +2499,7 @@ class SpanSelector(_SelectorWidget): See also: :doc:`/gallery/widgets/span_selector` """ - @_api.make_keyword_only("3.7", name="minspan") - def __init__(self, ax, onselect, direction, minspan=0, useblit=False, + def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, props=None, onmove_callback=None, interactive=False, button=None, handle_props=None, grab_range=10, state_modifier_keys=None, drag_from_anywhere=False, @@ -2957,8 +2842,7 @@ class ToolLineHandles: for details. """ - @_api.make_keyword_only("3.7", "line_props") - def __init__(self, ax, positions, direction, line_props=None, + def __init__(self, ax, positions, direction, *, line_props=None, useblit=True): self.ax = ax @@ -3068,8 +2952,7 @@ class ToolHandles: for details. """ - @_api.make_keyword_only("3.7", "marker") - def __init__(self, ax, x, y, marker='o', marker_props=None, useblit=True): + def __init__(self, ax, x, y, *, marker='o', marker_props=None, useblit=True): self.ax = ax props = {'marker': marker, 'markersize': 7, 'markerfacecolor': 'w', 'linestyle': 'none', 'alpha': 0.5, 'visible': False, @@ -3771,8 +3654,7 @@ def onselect(verts): which corresponds to all buttons. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, onselect, useblit=True, props=None, button=None): + def __init__(self, ax, onselect, *, useblit=True, props=None, button=None): super().__init__(ax, onselect, useblit=useblit, button=button) self.verts = None props = { @@ -3882,9 +3764,8 @@ class PolygonSelector(_SelectorWidget): point. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, onselect, useblit=False, - props=None, handle_props=None, grab_range=10, *, + def __init__(self, ax, onselect, *, useblit=False, + props=None, handle_props=None, grab_range=10, draw_bounding_box=False, box_handle_props=None, box_props=None): # The state modifiers 'move', 'square', and 'center' are expected by @@ -4199,8 +4080,7 @@ class Lasso(AxesWidget): for details. """ - @_api.make_keyword_only("3.7", name="useblit") - def __init__(self, ax, xy, callback, useblit=True): + def __init__(self, ax, xy, callback, *, useblit=True): super().__init__(ax) self.useblit = useblit and self.canvas.supports_blit diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index 00c2d0da8a7e..ca1e5a71a288 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -164,10 +164,6 @@ class CheckButtons(AxesWidget): def get_status(self) -> list[bool]: ... def on_clicked(self, func: Callable[[str], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... - @property - def lines(self) -> list[tuple[Line2D, Line2D]]: ... - @property - def rectangles(self) -> list[Rectangle]: ... class TextBox(AxesWidget): label: Text @@ -191,7 +187,7 @@ class TextBox(AxesWidget): @property def text(self) -> str: ... def set_val(self, val: str) -> None: ... - def begin_typing(self, x = ...) -> None: ... + def begin_typing(self) -> None: ... def stop_typing(self) -> None: ... def on_text_change(self, func: Callable[[str], Any]) -> int: ... def on_submit(self, func: Callable[[str], Any]) -> int: ... @@ -217,8 +213,6 @@ class RadioButtons(AxesWidget): def set_active(self, index: int) -> None: ... def on_clicked(self, func: Callable[[str], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... - @property - def circles(self) -> list[Circle]: ... class SubplotTool(Widget): figure: Figure @@ -253,7 +247,6 @@ class MultiCursor(Widget): vertOn: bool visible: bool useblit: bool - needclear: bool vlines: list[Line2D] hlines: list[Line2D] def __init__( From f49cf289ef8c90c47b2264b3731a9cd9739772b9 Mon Sep 17 00:00:00 2001 From: ananya314 Date: Fri, 22 Sep 2023 14:15:23 -0400 Subject: [PATCH 0178/1120] Removed deprecated code from animation.py --- doc/api/next_api_changes/removals/26872-AD.rst | 6 ++++++ lib/matplotlib/animation.py | 4 ---- lib/matplotlib/animation.pyi | 2 -- 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26872-AD.rst diff --git a/doc/api/next_api_changes/removals/26872-AD.rst b/doc/api/next_api_changes/removals/26872-AD.rst new file mode 100644 index 000000000000..2f678ffc3cfd --- /dev/null +++ b/doc/api/next_api_changes/removals/26872-AD.rst @@ -0,0 +1,6 @@ +``repeat`` +~~~~~~~~~~ +... of `.TimedAnimation` is removed without replacements. +``save_count`` +~~~~~~~~~~~~~~ +... of `.FuncAnimation` is removed without replacements. diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index e9b1c23658cd..e7d09e7d7847 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -1446,8 +1446,6 @@ def _step(self, *args): self.event_source.interval = self._interval return True - repeat = _api.deprecate_privatize_attribute("3.7") - class ArtistAnimation(TimedAnimation): """ @@ -1788,8 +1786,6 @@ def _draw_frame(self, framedata): for a in self._drawn_artists: a.set_animated(self._blit) - save_count = _api.deprecate_privatize_attribute("3.7") - def _validate_grabframe_kwargs(savefig_kwargs): if mpl.rcParams['savefig.bbox'] == 'tight': diff --git a/lib/matplotlib/animation.pyi b/lib/matplotlib/animation.pyi index de1b9926afd6..56c27a465b7f 100644 --- a/lib/matplotlib/animation.pyi +++ b/lib/matplotlib/animation.pyi @@ -188,7 +188,6 @@ class Animation: def resume(self) -> None: ... class TimedAnimation(Animation): - repeat: bool def __init__( self, fig: Figure, @@ -204,7 +203,6 @@ class ArtistAnimation(TimedAnimation): def __init__(self, fig: Figure, artists: Sequence[Collection[Artist]], *args, **kwargs) -> None: ... class FuncAnimation(TimedAnimation): - save_count: int def __init__( self, fig: Figure, From 27a0884db74972ad8efb13b130875afebfc92d74 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 22 Sep 2023 16:36:48 -0500 Subject: [PATCH 0179/1120] [TYP] Type changes from running against Pandas --- lib/matplotlib/axes/__init__.pyi | 6 ++++++ lib/matplotlib/axes/_base.pyi | 34 +++++++++++++++++++------------- lib/matplotlib/ticker.pyi | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/axes/__init__.pyi b/lib/matplotlib/axes/__init__.pyi index 0c27be62e370..d885677b63de 100644 --- a/lib/matplotlib/axes/__init__.pyi +++ b/lib/matplotlib/axes/__init__.pyi @@ -1,3 +1,9 @@ +__all__ = [ + 'Axes', + 'Subplot', + 'SubplotBase', +] + from typing import TypeVar from ._axes import * diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 1f929b6c90c5..a8a47f819446 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -6,6 +6,7 @@ from matplotlib import cbook from matplotlib.artist import Artist from matplotlib.axis import XAxis, YAxis, Tick from matplotlib.backend_bases import RendererBase, MouseButton, MouseEvent +from matplotlib.cbook import CallbackRegistry from matplotlib.container import Container from matplotlib.collections import Collection from matplotlib.cm import ScalarMappable @@ -25,9 +26,11 @@ from cycler import Cycler import numpy as np from numpy.typing import ArrayLike -from typing import Any, Literal, overload +from typing import Any, Literal, TypeVar, overload from matplotlib.typing import ColorType +_T = TypeVar("_T", bound=Artist) + class _axis_method_wrapper: attr_name: str method_name: str @@ -53,6 +56,9 @@ class _AxesBase(martist.Artist): transData: Transform ignore_existing_data_limits: bool axison: bool + containers: list[Container] + callbacks: CallbackRegistry + child_axes: list[_AxesBase] _projection_init: Any def __init__( @@ -126,7 +132,7 @@ class _AxesBase(martist.Artist): def cla(self) -> None: ... # Could be made generic, but comments indicate it may be temporary anyway - class ArtistList(Sequence[Artist]): + class ArtistList(Sequence[_T]): def __init__( self, axes: _AxesBase, @@ -135,40 +141,40 @@ class _AxesBase(martist.Artist): invalid_types: type | Iterable[type] | None = ..., ) -> None: ... def __len__(self) -> int: ... - def __iter__(self) -> Iterator[Artist]: ... + def __iter__(self) -> Iterator[_T]: ... @overload - def __getitem__(self, key: int) -> Artist: ... + def __getitem__(self, key: int) -> _T: ... @overload - def __getitem__(self, key: slice) -> list[Artist]: ... + def __getitem__(self, key: slice) -> list[_T]: ... @overload - def __add__(self, other: _AxesBase.ArtistList) -> list[Artist]: ... + def __add__(self, other: _AxesBase.ArtistList[_T]) -> list[_T]: ... @overload def __add__(self, other: list[Any]) -> list[Any]: ... @overload def __add__(self, other: tuple[Any]) -> tuple[Any]: ... @overload - def __radd__(self, other: _AxesBase.ArtistList) -> list[Artist]: ... + def __radd__(self, other: _AxesBase.ArtistList[_T]) -> list[_T]: ... @overload def __radd__(self, other: list[Any]) -> list[Any]: ... @overload def __radd__(self, other: tuple[Any]) -> tuple[Any]: ... @property - def artists(self) -> _AxesBase.ArtistList: ... + def artists(self) -> _AxesBase.ArtistList[Artist]: ... @property - def collections(self) -> _AxesBase.ArtistList: ... + def collections(self) -> _AxesBase.ArtistList[Collection]: ... @property - def images(self) -> _AxesBase.ArtistList: ... + def images(self) -> _AxesBase.ArtistList[AxesImage]: ... @property - def lines(self) -> _AxesBase.ArtistList: ... + def lines(self) -> _AxesBase.ArtistList[Line2D]: ... @property - def patches(self) -> _AxesBase.ArtistList: ... + def patches(self) -> _AxesBase.ArtistList[Patch]: ... @property - def tables(self) -> _AxesBase.ArtistList: ... + def tables(self) -> _AxesBase.ArtistList[Table]: ... @property - def texts(self) -> _AxesBase.ArtistList: ... + def texts(self) -> _AxesBase.ArtistList[Text]: ... def get_facecolor(self) -> ColorType: ... def set_facecolor(self, color: ColorType | None) -> None: ... @overload diff --git a/lib/matplotlib/ticker.pyi b/lib/matplotlib/ticker.pyi index 2ef1c9f53f1d..f026b4943c94 100644 --- a/lib/matplotlib/ticker.pyi +++ b/lib/matplotlib/ticker.pyi @@ -19,7 +19,7 @@ class _DummyAxis: class TickHelper: axis: None | Axis | _DummyAxis | _AxisWrapper - def set_axis(self, axis: Axis | _DummyAxis | None) -> None: ... + def set_axis(self, axis: Axis | _DummyAxis | _AxisWrapper | None) -> None: ... def create_dummy_axis(self, **kwargs) -> None: ... class Formatter(TickHelper): From 19f775e0d590b94af60fa19a4de2790ff05ca310 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 15 Sep 2023 20:05:10 +0200 Subject: [PATCH 0180/1120] Simplify Tk toolktip setup. ... switching everything to use closures. --- lib/matplotlib/backends/_backend_tk.py | 72 ++++++++++---------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 89c380f8c96b..698e84247d53 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -631,7 +631,7 @@ def __init__(self, canvas, window=None, *, pack_toolbar=True): command=getattr(self, callback), ) if tooltip_text is not None: - ToolTip.createToolTip(button, tooltip_text) + add_tooltip(button, tooltip_text) self._label_font = tkinter.font.Font(root=window, size=10) @@ -892,62 +892,44 @@ def set_history_buttons(self): state_map = {True: tk.NORMAL, False: tk.DISABLED} can_back = self._nav_stack._pos > 0 can_forward = self._nav_stack._pos < len(self._nav_stack) - 1 - if "Back" in self._buttons: self._buttons['Back']['state'] = state_map[can_back] - if "Forward" in self._buttons: self._buttons['Forward']['state'] = state_map[can_forward] -class ToolTip: - """ - Tooltip recipe from - http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387 - """ - @staticmethod - def createToolTip(widget, text): - toolTip = ToolTip(widget) - def enter(event): - toolTip.showtip(text) - def leave(event): - toolTip.hidetip() - widget.bind('', enter) - widget.bind('', leave) - - def __init__(self, widget): - self.widget = widget - self.tipwindow = None - self.id = None - self.x = self.y = 0 - - def showtip(self, text): +def add_tooltip(widget, text): + tipwindow = None + + def showtip(event): """Display text in tooltip window.""" - self.text = text - if self.tipwindow or not self.text: + nonlocal tipwindow + if tipwindow or not text: return - x, y, _, _ = self.widget.bbox("insert") - x = x + self.widget.winfo_rootx() + self.widget.winfo_width() - y = y + self.widget.winfo_rooty() - self.tipwindow = tw = tk.Toplevel(self.widget) - tw.wm_overrideredirect(1) - tw.wm_geometry("+%d+%d" % (x, y)) - try: - # For Mac OS - tw.tk.call("::tk::unsupported::MacWindowStyle", - "style", tw._w, - "help", "noActivates") + x, y, _, _ = widget.bbox("insert") + x = x + widget.winfo_rootx() + widget.winfo_width() + y = y + widget.winfo_rooty() + tipwindow = tk.Toplevel(widget) + tipwindow.overrideredirect(1) + tipwindow.geometry(f"+{x}+{y}") + try: # For Mac OS + tipwindow.tk.call("::tk::unsupported::MacWindowStyle", + "style", tipwindow._w, + "help", "noActivates") except tk.TclError: pass - label = tk.Label(tw, text=self.text, justify=tk.LEFT, + label = tk.Label(tipwindow, text=text, justify=tk.LEFT, relief=tk.SOLID, borderwidth=1) label.pack(ipadx=1) - def hidetip(self): - tw = self.tipwindow - self.tipwindow = None - if tw: - tw.destroy() + def hidetip(event): + nonlocal tipwindow + if tipwindow: + tipwindow.destroy() + tipwindow = None + + widget.bind("", showtip) + widget.bind("", hidetip) @backend_tools._register_tool_class(FigureCanvasTk) @@ -1002,7 +984,7 @@ def add_toolitem( lambda: self._button_click(name)) button.pack_configure(before=before) if description is not None: - ToolTip.createToolTip(button, description) + add_tooltip(button, description) self._toolitems.setdefault(name, []) self._toolitems[name].append(button) From ff552f45f2d30c76120fe0355306afac9a9dd2da Mon Sep 17 00:00:00 2001 From: agautam478 <72432016+agautam478@users.noreply.github.com> Date: Fri, 22 Sep 2023 19:37:29 -0700 Subject: [PATCH 0181/1120] Remove the 3.7 deprecated code from axis.py (#26871) --- doc/api/next_api_changes/removals/26871-AG.rst | 3 +++ lib/matplotlib/axis.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 doc/api/next_api_changes/removals/26871-AG.rst diff --git a/doc/api/next_api_changes/removals/26871-AG.rst b/doc/api/next_api_changes/removals/26871-AG.rst new file mode 100644 index 000000000000..9c24ac3215a1 --- /dev/null +++ b/doc/api/next_api_changes/removals/26871-AG.rst @@ -0,0 +1,3 @@ +``matplotlib.axis.Axis.set_ticklabels`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... a param was renamed to labels from ticklabels. diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 33d029028012..64aeac81c140 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1941,7 +1941,6 @@ def set_pickradius(self, pickradius): def _format_with_dict(tickd, x, pos): return tickd.get(x, "") - @_api.rename_parameter("3.7", "ticklabels", "labels") def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs): r""" [*Discouraged*] Set this Axis' tick labels with list of string labels. From 82ff2732958c5825e421ae2e28098f8fe014aa20 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 23 Sep 2023 14:31:44 +0200 Subject: [PATCH 0182/1120] Simplify CheckButtons and RadioButtons click handler. Shorten the code that determines which button has been clicked. Use Text.contains directly (it calls text.get_window_extent itself) rather than manually going through text.get_window_extent. The previous code for finding the closest click was actually wrong (transAxes.inverted().transform((event.x, event.y)) is in axes space, but get_offset_transform().transform(get_offsets()) is in screen space, so they cannot be subtracted from one another) but it didn't really matter because a click is, in practice, always contained by a single button anyways (idxs has length at most 1). Still, fix that. --- lib/matplotlib/widgets.py | 41 +++++++++++++++------------------------ 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index f24be7904690..45aa58d6fc19 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1118,19 +1118,14 @@ def _clear(self, event): def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: return - pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) - distances = {} - _, frame_inds = self._frames.contains(event) - coords = self._frames.get_offset_transform().transform( - self._frames.get_offsets() - ) - for i, t in enumerate(self.labels): - if (i in frame_inds["ind"] - or t.get_window_extent().contains(event.x, event.y)): - distances[i] = np.linalg.norm(pclicked - coords[i]) - if len(distances) > 0: - closest = min(distances, key=distances.get) - self.set_active(closest) + idxs = [ # Indices of frames and of texts that contain the event. + *self._frames.contains(event)[1]["ind"], + *[i for i, text in enumerate(self.labels) if text.contains(event)[0]]] + if idxs: + coords = self._frames.get_offset_transform().transform( + self._frames.get_offsets()) + self.set_active( # Closest index, only looking in idxs. + idxs[(((event.x, event.y) - coords[idxs]) ** 2).sum(-1).argmin()]) def set_label_props(self, props): """ @@ -1656,18 +1651,14 @@ def _clear(self, event): def _clicked(self, event): if self.ignore(event) or event.button != 1 or not self.ax.contains(event)[0]: return - pclicked = self.ax.transAxes.inverted().transform((event.x, event.y)) - _, inds = self._buttons.contains(event) - coords = self._buttons.get_offset_transform().transform( - self._buttons.get_offsets()) - distances = {} - for i, t in enumerate(self.labels): - if (i in inds["ind"] - or t.get_window_extent().contains(event.x, event.y)): - distances[i] = np.linalg.norm(pclicked - coords[i]) - if len(distances) > 0: - closest = min(distances, key=distances.get) - self.set_active(closest) + idxs = [ # Indices of buttons and of texts that contain the event. + *self._buttons.contains(event)[1]["ind"], + *[i for i, text in enumerate(self.labels) if text.contains(event)[0]]] + if idxs: + coords = self._buttons.get_offset_transform().transform( + self._buttons.get_offsets()) + self.set_active( # Closest index, only looking in idxs. + idxs[(((event.x, event.y) - coords[idxs]) ** 2).sum(-1).argmin()]) def set_label_props(self, props): """ From 96392c3a5ae485ddc605cd0e3b6025a5f3dac51d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 24 Sep 2023 12:01:04 +0100 Subject: [PATCH 0183/1120] add users/explain to default skip subdir --- doc/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index f369c7c4b89c..c2cdadf0ec24 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -47,8 +47,9 @@ def _parse_skip_subdirs_file(): but you can skip subdirectories of 'users'. Doing this can make partial builds very fast. """ - default_skip_subdirs = ['users/prev_whats_new/*', 'api/*', 'gallery/*', - 'tutorials/*', 'plot_types/*', 'devel/*'] + default_skip_subdirs = [ + 'users/prev_whats_new/*', 'users/explain/*', 'api/*', 'gallery/*', + 'tutorials/*', 'plot_types/*', 'devel/*'] try: with open(".mpl_skip_subdirs.yaml", 'r') as fin: print('Reading subdirectories to skip from', From a3bebed46725dc7203f459c62bc9eeb60a2a7338 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 7 Jul 2023 08:54:11 +0100 Subject: [PATCH 0184/1120] Use pybind11 in image module --- lib/matplotlib/tests/test_image.py | 15 +- setupext.py | 9 +- src/_image_wrapper.cpp | 329 +++++++++++------------------ src/py_converters_11.cpp | 20 ++ src/py_converters_11.h | 13 ++ 5 files changed, 179 insertions(+), 207 deletions(-) create mode 100644 src/py_converters_11.cpp create mode 100644 src/py_converters_11.h diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index c9c959e96115..4fb4e65137d4 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1468,17 +1468,26 @@ def test_str_norms(fig_test, fig_ref): def test__resample_valid_output(): resample = functools.partial(mpl._image.resample, transform=Affine2D()) - with pytest.raises(ValueError, match="must be a NumPy array"): + with pytest.raises(TypeError, match="incompatible function arguments"): resample(np.zeros((9, 9)), None) with pytest.raises(ValueError, match="different dimensionalities"): resample(np.zeros((9, 9)), np.zeros((9, 9, 4))) - with pytest.raises(ValueError, match="must be RGBA"): + with pytest.raises(ValueError, match="different dimensionalities"): + resample(np.zeros((9, 9, 4)), np.zeros((9, 9))) + with pytest.raises(ValueError, match="3D input array must be RGBA"): + resample(np.zeros((9, 9, 3)), np.zeros((9, 9, 4))) + with pytest.raises(ValueError, match="3D output array must be RGBA"): resample(np.zeros((9, 9, 4)), np.zeros((9, 9, 3))) - with pytest.raises(ValueError, match="Mismatched types"): + with pytest.raises(ValueError, match="mismatched types"): resample(np.zeros((9, 9), np.uint8), np.zeros((9, 9))) with pytest.raises(ValueError, match="must be C-contiguous"): resample(np.zeros((9, 9)), np.zeros((9, 9)).T) + out = np.zeros((9, 9)) + out.flags.writeable = False + with pytest.raises(ValueError, match="Output array must be writeable"): + resample(np.zeros((9, 9)), out) + def test_axesimage_get_shape(): # generate dummy image to test get_shape method diff --git a/setupext.py b/setupext.py index 9f78d88c87e8..049f46c65c7d 100644 --- a/setupext.py +++ b/setupext.py @@ -424,12 +424,13 @@ def get_extensions(self): add_libagg_flags(ext) yield ext # image - ext = Extension( + ext = Pybind11Extension( "matplotlib._image", [ "src/_image_wrapper.cpp", - "src/py_converters.cpp", - ]) - add_numpy_flags(ext) + "src/py_converters_11.cpp", + ], + cxx_std=11) + # Only need source code files agg_image_filters.cpp and agg_trans_affine.cpp add_libagg_flags_and_sources(ext) yield ext # path diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index ca6ae8b2226f..a63004ebb624 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -1,7 +1,8 @@ -#include "mplutils.h" +#include +#include + #include "_image_resample.h" -#include "numpy_cpp.h" -#include "py_converters.h" +#include "py_converters_11.h" /********************************************************************** @@ -49,8 +50,8 @@ const char* image_resample__doc__ = " The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; -static PyArrayObject * -_get_transform_mesh(PyObject *py_affine, npy_intp *dims) +static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, + const ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -58,240 +59,168 @@ _get_transform_mesh(PyObject *py_affine, npy_intp *dims) every pixel in the output image to the input image. This is used as a lookup table during the actual resampling. */ - PyObject *py_inverse = NULL; - npy_intp out_dims[3]; - - out_dims[0] = dims[0] * dims[1]; - out_dims[1] = 2; + // If attribute doesn't exist, raises Python AttributeError + auto inverse = transform.attr("inverted")(); - py_inverse = PyObject_CallMethod(py_affine, "inverted", NULL); - if (py_inverse == NULL) { - return NULL; - } + pybind11::array_t input_mesh({dims[0]*dims[1], 2L}); + auto p = input_mesh.mutable_data(); - numpy::array_view input_mesh(out_dims); - double *p = (double *)input_mesh.data(); - - for (npy_intp y = 0; y < dims[0]; ++y) { - for (npy_intp x = 0; x < dims[1]; ++x) { + for (auto y = 0; y < dims[0]; ++y) { + for (auto x = 0; x < dims[1]; ++x) { *p++ = (double)x; *p++ = (double)y; } } - PyObject *output_mesh = PyObject_CallMethod( - py_inverse, "transform", "O", input_mesh.pyobj_steal()); - - Py_DECREF(py_inverse); - - if (output_mesh == NULL) { - return NULL; - } - - PyArrayObject *output_mesh_array = - (PyArrayObject *)PyArray_ContiguousFromAny( - output_mesh, NPY_DOUBLE, 2, 2); + auto output_mesh = inverse.attr("transform")(input_mesh); - Py_DECREF(output_mesh); + auto output_mesh_array = + pybind11::array_t(output_mesh); - if (output_mesh_array == NULL) { - return NULL; - } + if (output_mesh_array.ndim() != 2) + throw std::runtime_error( + "Inverse transformed mesh array should be 2D not " + + std::to_string(output_mesh_array.ndim()) + "D"); return output_mesh_array; } -static PyObject * -image_resample(PyObject *self, PyObject* args, PyObject *kwargs) +// Using generic pybind::array for input and output arrays rather than the more usual +// pybind::array_t as function supports multiple array dtypes. +static void image_resample(pybind11::array input_array, + pybind11::array& output_array, + const pybind11::object& transform, + interpolation_e interpolation, + bool resample_, // Avoid name clash with resample() function + float alpha, + bool norm, + float radius) { - PyObject *py_input = NULL; - PyObject *py_output = NULL; - PyObject *py_transform = NULL; - resample_params_t params; + // Validate input_array + auto dtype = input_array.dtype(); // Validated when determine resampler below + auto ndim = input_array.ndim(); - PyArrayObject *input = NULL; - PyArrayObject *output = NULL; - PyArrayObject *transform_mesh = NULL; - int ndim; - int type; - - params.interpolation = NEAREST; - params.transform_mesh = NULL; - params.resample = false; - params.norm = false; - params.radius = 1.0; - params.alpha = 1.0; - - const char *kwlist[] = { - "input_array", "output_array", "transform", "interpolation", - "resample", "alpha", "norm", "radius", NULL }; - - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "OOO|iO&dO&d:resample", (char **)kwlist, - &py_input, &py_output, &py_transform, - ¶ms.interpolation, &convert_bool, ¶ms.resample, - ¶ms.alpha, &convert_bool, ¶ms.norm, ¶ms.radius)) { - return NULL; - } + if (ndim < 2 || ndim > 3) + throw std::invalid_argument("Input array must be a 2D or 3D array"); - if (params.interpolation < 0 || params.interpolation >= _n_interpolation) { - PyErr_Format(PyExc_ValueError, "Invalid interpolation value %d", - params.interpolation); - goto error; - } + if (ndim == 3 && input_array.shape(2) != 4) + throw std::invalid_argument( + "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + + std::to_string(ndim)); - input = (PyArrayObject *)PyArray_FromAny( - py_input, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL); - if (!input) { - goto error; - } - ndim = PyArray_NDIM(input); - type = PyArray_TYPE(input); + // Ensure input array is contiguous, regardless of dtype + input_array = pybind11::array::ensure(input_array, pybind11::array::c_style); - if (!PyArray_Check(py_output)) { - PyErr_SetString(PyExc_ValueError, "Output array must be a NumPy array"); - goto error; - } - output = (PyArrayObject *)py_output; - if (PyArray_NDIM(output) != ndim) { - PyErr_Format( - PyExc_ValueError, - "Input (%dD) and output (%dD) have different dimensionalities.", - ndim, PyArray_NDIM(output)); - goto error; - } - // PyArray_FromAny above checks that input is 2D or 3D. - if (ndim == 3 && (PyArray_DIM(input, 2) != 4 || PyArray_DIM(output, 2) != 4)) { - PyErr_Format( - PyExc_ValueError, - "If 3D, input and output arrays must be RGBA with shape (M, N, 4); " - "got trailing dimensions of %" NPY_INTP_FMT " and %" NPY_INTP_FMT - " respectively", PyArray_DIM(input, 2), PyArray_DIM(output, 2)); - goto error; - } - if (PyArray_TYPE(output) != type) { - PyErr_SetString(PyExc_ValueError, "Mismatched types"); - goto error; - } - if (!PyArray_IS_C_CONTIGUOUS(output)) { - PyErr_SetString(PyExc_ValueError, "Output array must be C-contiguous"); - goto error; - } + // Validate output array + auto out_ndim = output_array.ndim(); + + if (out_ndim != ndim) + throw std::invalid_argument( + "Input (" + std::to_string(ndim) + "D) and output (" + std::to_string(out_ndim) + + "D) arrays have different dimensionalities"); + + if (out_ndim == 3 && output_array.shape(2) != 4) + throw std::invalid_argument( + "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + + std::to_string(out_ndim)); - if (py_transform == NULL || py_transform == Py_None) { + if (!output_array.dtype().is(dtype)) + throw std::invalid_argument("Input and output arrays have mismatched types"); + + if ((output_array.flags() & pybind11::array::c_style) == 0) + throw std::invalid_argument("Output array must be C-contiguous"); + + if (!output_array.writeable()) + throw std::invalid_argument("Output array must be writeable"); + + resample_params_t params; + params.interpolation = interpolation; + params.transform_mesh = nullptr; + params.resample = resample_; + params.norm = norm; + params.radius = radius; + params.alpha = alpha; + + // Only used if transform is not affine. + // Need to keep it in scope for the duration of this function. + pybind11::array_t transform_mesh; + + // Validate transform + if (transform.is_none()) { params.is_affine = true; } else { - PyObject *py_is_affine; - int py_is_affine2; - py_is_affine = PyObject_GetAttrString(py_transform, "is_affine"); - if (!py_is_affine) { - goto error; - } + // Raises Python AttributeError if no such attribute or TypeError if cast fails + bool is_affine = pybind11::cast(transform.attr("is_affine")); - py_is_affine2 = PyObject_IsTrue(py_is_affine); - Py_DECREF(py_is_affine); - - if (py_is_affine2 == -1) { - goto error; - } else if (py_is_affine2) { - if (!convert_trans_affine(py_transform, ¶ms.affine)) { - goto error; - } + if (is_affine) { + convert_trans_affine(transform, params.affine); params.is_affine = true; } else { - transform_mesh = _get_transform_mesh( - py_transform, PyArray_DIMS(output)); - if (!transform_mesh) { - goto error; - } - params.transform_mesh = (double *)PyArray_DATA(transform_mesh); + transform_mesh = _get_transform_mesh(transform, output_array.shape()); + params.transform_mesh = transform_mesh.data(); params.is_affine = false; } } if (auto resampler = (ndim == 2) ? ( - (type == NPY_UINT8) ? resample : - (type == NPY_INT8) ? resample : - (type == NPY_UINT16) ? resample : - (type == NPY_INT16) ? resample : - (type == NPY_FLOAT32) ? resample : - (type == NPY_FLOAT64) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : nullptr) : ( // ndim == 3 - (type == NPY_UINT8) ? resample : - (type == NPY_INT8) ? resample : - (type == NPY_UINT16) ? resample : - (type == NPY_INT16) ? resample : - (type == NPY_FLOAT32) ? resample : - (type == NPY_FLOAT64) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : nullptr)) { Py_BEGIN_ALLOW_THREADS resampler( - PyArray_DATA(input), PyArray_DIM(input, 1), PyArray_DIM(input, 0), - PyArray_DATA(output), PyArray_DIM(output, 1), PyArray_DIM(output, 0), + input_array.data(), input_array.shape(1), input_array.shape(0), + output_array.mutable_data(), output_array.shape(1), output_array.shape(0), params); Py_END_ALLOW_THREADS - } else { - PyErr_SetString( - PyExc_ValueError, - "arrays must be of dtype byte, short, float32 or float64"); - goto error; - } - - Py_DECREF(input); - Py_XDECREF(transform_mesh); - Py_RETURN_NONE; - - error: - Py_XDECREF(input); - Py_XDECREF(transform_mesh); - return NULL; + } else + throw std::invalid_argument("arrays must be of dtype byte, short, float32 or float64"); } -static PyMethodDef module_functions[] = { - {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__}, - {NULL} -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, "_image", NULL, 0, module_functions, -}; - -PyMODINIT_FUNC PyInit__image(void) -{ - PyObject *m; - - import_array(); - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - if (PyModule_AddIntConstant(m, "NEAREST", NEAREST) || - PyModule_AddIntConstant(m, "BILINEAR", BILINEAR) || - PyModule_AddIntConstant(m, "BICUBIC", BICUBIC) || - PyModule_AddIntConstant(m, "SPLINE16", SPLINE16) || - PyModule_AddIntConstant(m, "SPLINE36", SPLINE36) || - PyModule_AddIntConstant(m, "HANNING", HANNING) || - PyModule_AddIntConstant(m, "HAMMING", HAMMING) || - PyModule_AddIntConstant(m, "HERMITE", HERMITE) || - PyModule_AddIntConstant(m, "KAISER", KAISER) || - PyModule_AddIntConstant(m, "QUADRIC", QUADRIC) || - PyModule_AddIntConstant(m, "CATROM", CATROM) || - PyModule_AddIntConstant(m, "GAUSSIAN", GAUSSIAN) || - PyModule_AddIntConstant(m, "BESSEL", BESSEL) || - PyModule_AddIntConstant(m, "MITCHELL", MITCHELL) || - PyModule_AddIntConstant(m, "SINC", SINC) || - PyModule_AddIntConstant(m, "LANCZOS", LANCZOS) || - PyModule_AddIntConstant(m, "BLACKMAN", BLACKMAN) || - PyModule_AddIntConstant(m, "_n_interpolation", _n_interpolation)) { - Py_DECREF(m); - return NULL; - } - return m; +PYBIND11_MODULE(_image, m) { + pybind11::enum_(m, "interpolation_e") + .value("NEAREST", NEAREST) + .value("BILINEAR", BILINEAR) + .value("BICUBIC", BICUBIC) + .value("SPLINE16", SPLINE16) + .value("SPLINE36", SPLINE36) + .value("HANNING", HANNING) + .value("HAMMING", HAMMING) + .value("HERMITE", HERMITE) + .value("KAISER", KAISER) + .value("QUADRIC", QUADRIC) + .value("CATROM", CATROM) + .value("GAUSSIAN", GAUSSIAN) + .value("BESSEL", BESSEL) + .value("MITCHELL", MITCHELL) + .value("SINC", SINC) + .value("LANCZOS", LANCZOS) + .value("BLACKMAN", BLACKMAN) + .value("_n_interpolation", _n_interpolation) + .export_values(); + + m.def("resample", &image_resample, + pybind11::arg("input_array"), + pybind11::arg("output_array"), + pybind11::arg("transform"), + pybind11::arg("interpolation") = interpolation_e::NEAREST, + pybind11::arg("resample") = false, + pybind11::arg("alpha") = 1, + pybind11::arg("norm") = false, + pybind11::arg("radius") = 1, + image_resample__doc__); } diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp new file mode 100644 index 000000000000..fbcc8f809f44 --- /dev/null +++ b/src/py_converters_11.cpp @@ -0,0 +1,20 @@ +#include "py_converters_11.h" + +void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) +{ + // If None assume identity transform so leave affine unchanged + if (transform.is(pybind11::none())) + return; + + auto array = pybind11::array_t::ensure(transform); + if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) + throw std::invalid_argument("Invalid affine transformation matrix"); + + auto buffer = array.data(); + affine.sx = buffer[0]; + affine.shx = buffer[1]; + affine.tx = buffer[2]; + affine.shy = buffer[3]; + affine.sy = buffer[4]; + affine.ty = buffer[5]; +} diff --git a/src/py_converters_11.h b/src/py_converters_11.h new file mode 100644 index 000000000000..1017b2a3e5c1 --- /dev/null +++ b/src/py_converters_11.h @@ -0,0 +1,13 @@ +#ifndef MPL_PY_CONVERTERS_11_H +#define MPL_PY_CONVERTERS_11_H + +// pybind11 equivalent of py_converters.h + +#include +#include + +#include "agg_trans_affine.h" + +void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine); + +#endif From c14ad96e31898726f5841d9804363d9d4d4ba409 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 7 Jul 2023 11:32:18 +0100 Subject: [PATCH 0185/1120] Be explicit with array dimension types --- src/_image_wrapper.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index a63004ebb624..5167e196c9e5 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -51,7 +51,7 @@ const char* image_resample__doc__ = static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, - const ssize_t *dims) + const pybind11::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -62,7 +62,8 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // If attribute doesn't exist, raises Python AttributeError auto inverse = transform.attr("inverted")(); - pybind11::array_t input_mesh({dims[0]*dims[1], 2L}); + pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[2], 2}; + pybind11::array_t input_mesh(mesh_dims); auto p = input_mesh.mutable_data(); for (auto y = 0; y < dims[0]; ++y) { From 1a3c723dd8685cf6e8bd3f9f8bd9b15a77b94dd8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 17 Jul 2023 09:48:19 +0100 Subject: [PATCH 0186/1120] Review comments --- src/_image_resample.h | 2 -- src/_image_wrapper.cpp | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/_image_resample.h b/src/_image_resample.h index 10763fb01d37..60f5e66d4539 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -496,7 +496,6 @@ typedef enum { SINC, LANCZOS, BLACKMAN, - _n_interpolation } interpolation_e; @@ -629,7 +628,6 @@ static void get_filter(const resample_params_t ¶ms, { switch (params.interpolation) { case NEAREST: - case _n_interpolation: // Never should get here. Here to silence compiler warnings. break; diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 5167e196c9e5..13178e734d36 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -62,7 +62,7 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // If attribute doesn't exist, raises Python AttributeError auto inverse = transform.attr("inverted")(); - pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[2], 2}; + pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[1], 2}; pybind11::array_t input_mesh(mesh_dims); auto p = input_mesh.mutable_data(); @@ -102,7 +102,7 @@ static void image_resample(pybind11::array input_array, auto dtype = input_array.dtype(); // Validated when determine resampler below auto ndim = input_array.ndim(); - if (ndim < 2 || ndim > 3) + if (ndim != 2 && ndim != 3) throw std::invalid_argument("Input array must be a 2D or 3D array"); if (ndim == 3 && input_array.shape(2) != 4) @@ -211,7 +211,6 @@ PYBIND11_MODULE(_image, m) { .value("SINC", SINC) .value("LANCZOS", LANCZOS) .value("BLACKMAN", BLACKMAN) - .value("_n_interpolation", _n_interpolation) .export_values(); m.def("resample", &image_resample, From bfaf1a7d406f115b71ed25676825cbc4d1eb9c22 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:08:37 +0100 Subject: [PATCH 0187/1120] Added curly braces around one-liners --- src/_image_wrapper.cpp | 27 ++++++++++++++++++--------- src/py_converters_11.cpp | 6 ++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 13178e734d36..93904d52bcc6 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -78,10 +78,11 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra auto output_mesh_array = pybind11::array_t(output_mesh); - if (output_mesh_array.ndim() != 2) + if (output_mesh_array.ndim() != 2) { throw std::runtime_error( "Inverse transformed mesh array should be 2D not " + std::to_string(output_mesh_array.ndim()) + "D"); + } return output_mesh_array; } @@ -102,13 +103,15 @@ static void image_resample(pybind11::array input_array, auto dtype = input_array.dtype(); // Validated when determine resampler below auto ndim = input_array.ndim(); - if (ndim != 2 && ndim != 3) + if (ndim != 2 && ndim != 3) { throw std::invalid_argument("Input array must be a 2D or 3D array"); + } - if (ndim == 3 && input_array.shape(2) != 4) + if (ndim == 3 && input_array.shape(2) != 4) { throw std::invalid_argument( "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + std::to_string(ndim)); + } // Ensure input array is contiguous, regardless of dtype input_array = pybind11::array::ensure(input_array, pybind11::array::c_style); @@ -116,24 +119,29 @@ static void image_resample(pybind11::array input_array, // Validate output array auto out_ndim = output_array.ndim(); - if (out_ndim != ndim) + if (out_ndim != ndim) { throw std::invalid_argument( "Input (" + std::to_string(ndim) + "D) and output (" + std::to_string(out_ndim) + "D) arrays have different dimensionalities"); + } - if (out_ndim == 3 && output_array.shape(2) != 4) + if (out_ndim == 3 && output_array.shape(2) != 4) { throw std::invalid_argument( "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + std::to_string(out_ndim)); + } - if (!output_array.dtype().is(dtype)) + if (!output_array.dtype().is(dtype)) { throw std::invalid_argument("Input and output arrays have mismatched types"); + } - if ((output_array.flags() & pybind11::array::c_style) == 0) + if ((output_array.flags() & pybind11::array::c_style) == 0) { throw std::invalid_argument("Output array must be C-contiguous"); + } - if (!output_array.writeable()) + if (!output_array.writeable()) { throw std::invalid_argument("Output array must be writeable"); + } resample_params_t params; params.interpolation = interpolation; @@ -187,8 +195,9 @@ static void image_resample(pybind11::array input_array, output_array.mutable_data(), output_array.shape(1), output_array.shape(0), params); Py_END_ALLOW_THREADS - } else + } else { throw std::invalid_argument("arrays must be of dtype byte, short, float32 or float64"); + } } diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp index fbcc8f809f44..47a5ec5b5a2c 100644 --- a/src/py_converters_11.cpp +++ b/src/py_converters_11.cpp @@ -3,12 +3,14 @@ void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) { // If None assume identity transform so leave affine unchanged - if (transform.is(pybind11::none())) + if (transform.is(pybind11::none())) { return; + } auto array = pybind11::array_t::ensure(transform); - if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) + if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) { throw std::invalid_argument("Invalid affine transformation matrix"); + } auto buffer = array.data(); affine.sx = buffer[0]; From c417369d21d2d5c671abb420513401492b2e20a2 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:20:18 +0100 Subject: [PATCH 0188/1120] Export _InterpolationType rather than interpolation_e --- src/_image_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 93904d52bcc6..a41b803f7146 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -202,7 +202,7 @@ static void image_resample(pybind11::array input_array, PYBIND11_MODULE(_image, m) { - pybind11::enum_(m, "interpolation_e") + pybind11::enum_(m, "_InterpolationType") .value("NEAREST", NEAREST) .value("BILINEAR", BILINEAR) .value("BICUBIC", BICUBIC) From 5d90e482eace4312ad696a9cdf3aa85d3cba4fc8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:28:57 +0100 Subject: [PATCH 0189/1120] Miscellaneous review comments --- src/_image_wrapper.cpp | 28 +++++++++++++--------------- src/py_converters_11.cpp | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index a41b803f7146..179f8d1301f7 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -10,9 +10,6 @@ * */ const char* image_resample__doc__ = -"resample(input_array, output_array, transform, interpolation=NEAREST, resample=False, alpha=1.0, norm=False, radius=1.0)\n" -"--\n\n" - "Resample input_array, blending it in-place into output_array, using an\n" "affine transformation.\n\n" @@ -50,8 +47,8 @@ const char* image_resample__doc__ = " The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; -static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, - const pybind11::ssize_t *dims) +static pybind11::array_t +_get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -90,14 +87,15 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // Using generic pybind::array for input and output arrays rather than the more usual // pybind::array_t as function supports multiple array dtypes. -static void image_resample(pybind11::array input_array, - pybind11::array& output_array, - const pybind11::object& transform, - interpolation_e interpolation, - bool resample_, // Avoid name clash with resample() function - float alpha, - bool norm, - float radius) +static void +image_resample(pybind11::array input_array, + pybind11::array& output_array, + const pybind11::object& transform, + interpolation_e interpolation, + bool resample_, // Avoid name clash with resample() function + float alpha, + bool norm, + float radius) { // Validate input_array auto dtype = input_array.dtype(); // Validated when determine resampler below @@ -110,7 +108,7 @@ static void image_resample(pybind11::array input_array, if (ndim == 3 && input_array.shape(2) != 4) { throw std::invalid_argument( "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + - std::to_string(ndim)); + std::to_string(input_array.shape(2))); } // Ensure input array is contiguous, regardless of dtype @@ -128,7 +126,7 @@ static void image_resample(pybind11::array input_array, if (out_ndim == 3 && output_array.shape(2) != 4) { throw std::invalid_argument( "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + - std::to_string(out_ndim)); + std::to_string(output_array.shape(2))); } if (!output_array.dtype().is(dtype)) { diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp index 47a5ec5b5a2c..982cc9ac6c46 100644 --- a/src/py_converters_11.cpp +++ b/src/py_converters_11.cpp @@ -3,7 +3,7 @@ void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) { // If None assume identity transform so leave affine unchanged - if (transform.is(pybind11::none())) { + if (transform.is_none()) { return; } From 1c054c482388607586cad067af70bf891053ee6e Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 24 Sep 2023 09:32:36 +0100 Subject: [PATCH 0190/1120] MNT: only account for Artists once in fig.get_tightbbox --- lib/matplotlib/figure.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c750095aee18..2b83a7ae4a73 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1710,7 +1710,9 @@ def get_tightbbox(self, renderer=None, bbox_extra_artists=None): bb = [] if bbox_extra_artists is None: - artists = self.get_default_bbox_extra_artists() + artists = [artist for artist in self.get_children() + if (artist not in self.axes and artist.get_visible() + and artist.get_in_layout())] else: artists = bbox_extra_artists From 96b094484b7b21c31bd64076220ad3eaa0583839 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:32:20 +0100 Subject: [PATCH 0191/1120] allsegs and allkinds return individual segments --- lib/matplotlib/contour.py | 6 ++++-- lib/matplotlib/tests/test_contour.py | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index efea024dc102..2725dd6340a4 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -935,9 +935,11 @@ def __init__(self, ax, *args, ) allsegs = _api.deprecated("3.8", pending=True)(property(lambda self: [ - p.vertices for c in self.collections for p in c.get_paths()])) + [subp.vertices for subp in p._iter_connected_components()] + for p in self.get_paths()])) allkinds = _api.deprecated("3.8", pending=True)(property(lambda self: [ - p.codes for c in self.collections for p in c.get_paths()])) + [subp.codes for subp in p._iter_connected_components()] + for p in self.get_paths()])) tcolors = _api.deprecated("3.8")(property(lambda self: [ (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) tlinewidths = _api.deprecated("3.8")(property(lambda self: [ diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 4a32fdc6ce32..c911d499ea96 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -819,14 +819,24 @@ def test_all_nan(): 2.4e-14, 5e-14, 7.5e-14, 1e-13]) +def test_allsegs_allkinds(): + x, y = np.meshgrid(np.arange(0, 10, 2), np.arange(0, 10, 2)) + z = np.sin(x) * np.cos(y) + + cs = plt.contour(x, y, z, levels=[0, 0.5]) + + # Expect two levels, first with 5 segments and the second with 4. + with pytest.warns(PendingDeprecationWarning, match="all"): + for result in [cs.allsegs, cs.allkinds]: + assert len(result) == 2 + assert len(result[0]) == 5 + assert len(result[1]) == 4 + + def test_deprecated_apis(): cs = plt.contour(np.arange(16).reshape((4, 4))) with pytest.warns(mpl.MatplotlibDeprecationWarning, match="collections"): colls = cs.collections - with pytest.warns(PendingDeprecationWarning, match="allsegs"): - assert cs.allsegs == [p.vertices for c in colls for p in c.get_paths()] - with pytest.warns(PendingDeprecationWarning, match="allkinds"): - assert cs.allkinds == [p.codes for c in colls for p in c.get_paths()] with pytest.warns(mpl.MatplotlibDeprecationWarning, match="tcolors"): assert_array_equal(cs.tcolors, [c.get_edgecolor() for c in colls]) with pytest.warns(mpl.MatplotlibDeprecationWarning, match="tlinewidths"): From 0c6a3b0d2cb5de809a928dc6e3b0fb4f713e8fb0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 22 Sep 2023 17:23:02 -0500 Subject: [PATCH 0192/1120] Rework axes/__init__.pyi to not use __all__ --- lib/matplotlib/axes/__init__.pyi | 13 +++++-------- lib/matplotlib/axes/_base.pyi | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/axes/__init__.pyi b/lib/matplotlib/axes/__init__.pyi index d885677b63de..7df38b8bde9e 100644 --- a/lib/matplotlib/axes/__init__.pyi +++ b/lib/matplotlib/axes/__init__.pyi @@ -1,16 +1,13 @@ -__all__ = [ - 'Axes', - 'Subplot', - 'SubplotBase', -] - from typing import TypeVar -from ._axes import * -from ._axes import Axes as Subplot +from ._axes import Axes as Axes + _T = TypeVar("_T") +# Backcompat. +Subplot = Axes + class _SubplotBaseMeta(type): def __instancecheck__(self, obj) -> bool: ... diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index a8a47f819446..cac66d354245 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -131,7 +131,6 @@ class _AxesBase(martist.Artist): def clear(self) -> None: ... def cla(self) -> None: ... - # Could be made generic, but comments indicate it may be temporary anyway class ArtistList(Sequence[_T]): def __init__( self, From 84915614d61840af07d286ebcf930f940030bf96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Andrade?= <49215007+jpgianfaldoni@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:15:10 -0300 Subject: [PATCH 0193/1120] Removed the deprecated code from offsetbox.py (#26910) * Removed the deprecated code from offsetbox.py * fix spacing * add rst file * fix title underline * fix rst file * make lowercase * Update doc/api/next_api_changes/removals/26910-JP.rst Co-authored-by: Kyle Sunden * Update doc/api/next_api_changes/removals/26910-JP.rst Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --------- Co-authored-by: Kyle Sunden Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../next_api_changes/removals/26910-JP.rst | 13 ++++++++ lib/matplotlib/offsetbox.py | 32 ------------------- lib/matplotlib/offsetbox.pyi | 7 ---- 3 files changed, 13 insertions(+), 39 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26910-JP.rst diff --git a/doc/api/next_api_changes/removals/26910-JP.rst b/doc/api/next_api_changes/removals/26910-JP.rst new file mode 100644 index 000000000000..0de12cd89ad5 --- /dev/null +++ b/doc/api/next_api_changes/removals/26910-JP.rst @@ -0,0 +1,13 @@ +``offsetbox.bbox_artist`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed. This was just a wrapper to call `.patches.bbox_artist` if a flag is set in the file, so use that directly if you need the behavior. + +``offsetBox.get_extent_offsets`` and ``offsetBox.get_extent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... are removed; these methods are also removed on all subclasses of `.OffsetBox`. + +... To get the offsetbox extents, instead of ``get_extent``, use `.OffsetBox.get_bbox`, which directly returns a `.Bbox` instance. + +... To also get the child offsets, instead of ``get_extent_offsets``, separately call `~.OffsetBox.get_offset` on each children after triggering a draw. diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index bb117c38cece..acf93f5a34d9 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -61,12 +61,6 @@ def get_offset(self, *args, **kwargs): return get_offset -@_api.deprecated("3.7", alternative='patches.bbox_artist') -def bbox_artist(*args, **kwargs): - if DEBUG: - mbbox_artist(*args, **kwargs) - - # for debugging use def _bbox_artist(*args, **kwargs): if DEBUG: @@ -366,32 +360,6 @@ def get_bbox(self, renderer): bbox, offsets = self._get_bbox_and_child_offsets(renderer) return bbox - @_api.deprecated("3.7", alternative="get_bbox and child.get_offset") - def get_extent_offsets(self, renderer): - """ - Update offset of the children and return the extent of the box. - - Parameters - ---------- - renderer : `.RendererBase` subclass - - Returns - ------- - width - height - xdescent - ydescent - list of (xoffset, yoffset) pairs - """ - bbox, offsets = self._get_bbox_and_child_offsets(renderer) - return bbox.width, bbox.height, -bbox.x0, -bbox.y0, offsets - - @_api.deprecated("3.7", alternative="get_bbox") - def get_extent(self, renderer): - """Return a tuple ``width, height, xdescent, ydescent`` of the box.""" - bbox = self.get_bbox(renderer) - return bbox.width, bbox.height, -bbox.x0, -bbox.y0 - def get_window_extent(self, renderer=None): # docstring inherited if renderer is None: diff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi index 7d7f4d8f67ec..09f89aed2bc8 100644 --- a/lib/matplotlib/offsetbox.pyi +++ b/lib/matplotlib/offsetbox.pyi @@ -15,7 +15,6 @@ from typing import Any, Literal, overload DEBUG: bool -def bbox_artist(*args, **kwargs) -> None: ... def _get_packed_offsets( widths: Sequence[float], total: float | None, @@ -51,12 +50,6 @@ class OffsetBox(martist.Artist): def get_visible_children(self) -> list[martist.Artist]: ... def get_children(self) -> list[martist.Artist]: ... def get_bbox(self, renderer: RendererBase) -> Bbox: ... - def get_extent_offsets( - self, renderer: RendererBase - ) -> tuple[float, float, float, float, list[tuple[float, float]]]: ... - def get_extent( - self, renderer: RendererBase - ) -> tuple[float, float, float, float]: ... def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ... class PackerBase(OffsetBox): From a6d0029803dcbd74ad46f38f8520bc08db5d05d4 Mon Sep 17 00:00:00 2001 From: Ananya Devarakonda <57040724+ananya314@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:36:11 -0400 Subject: [PATCH 0194/1120] removed deprecated code from gridspec.py Co-Authored-By: Elliott Sales de Andrade --- ci/mypy-stubtest-allowlist.txt | 1 - .../next_api_changes/removals/26885-AD.rst | 4 +++ lib/matplotlib/gridspec.py | 31 +++++-------------- lib/matplotlib/gridspec.pyi | 2 +- 4 files changed, 13 insertions(+), 25 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26885-AD.rst diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 64fae8d44562..e0890b3f7117 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -53,7 +53,6 @@ matplotlib.figure.Figure.set_tight_layout matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap matplotlib.collections.PolyCollection.span_where -matplotlib.gridspec.GridSpecBase.get_grid_positions # 3.8 deprecations matplotlib.cbook.get_sample_data diff --git a/doc/api/next_api_changes/removals/26885-AD.rst b/doc/api/next_api_changes/removals/26885-AD.rst new file mode 100644 index 000000000000..c617f10d07ed --- /dev/null +++ b/doc/api/next_api_changes/removals/26885-AD.rst @@ -0,0 +1,4 @@ +``raw`` parameter +~~~~~~~~~~~~~~~~~ + +... of `.GridSpecBase.get_grid_positions` is removed without replacements. diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 13a7d29ca563..0b93ea93dd3d 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -141,8 +141,7 @@ def get_height_ratios(self): """ return self._row_height_ratios - @_api.delete_parameter("3.7", "raw") - def get_grid_positions(self, fig, raw=False): + def get_grid_positions(self, fig): """ Return the positions of the grid cells in figure coordinates. @@ -151,11 +150,6 @@ def get_grid_positions(self, fig, raw=False): fig : `~matplotlib.figure.Figure` The figure the grid should be applied to. The subplot parameters (margins and spacing between subplots) are taken from *fig*. - raw : bool, default: False - If *True*, the subplot parameters of the figure are not taken - into account. The grid spans the range [0, 1] in both directions - without margins and there is no space between grid cells. This is - used for constrained_layout. Returns ------- @@ -164,22 +158,13 @@ def get_grid_positions(self, fig, raw=False): figure coordinates. """ nrows, ncols = self.get_geometry() - - if raw: - left = 0. - right = 1. - bottom = 0. - top = 1. - wspace = 0. - hspace = 0. - else: - subplot_params = self.get_subplot_params(fig) - left = subplot_params.left - right = subplot_params.right - bottom = subplot_params.bottom - top = subplot_params.top - wspace = subplot_params.wspace - hspace = subplot_params.hspace + subplot_params = self.get_subplot_params(fig) + left = subplot_params.left + right = subplot_params.right + bottom = subplot_params.bottom + top = subplot_params.top + wspace = subplot_params.wspace + hspace = subplot_params.hspace tot_width = right - left tot_height = top - bottom diff --git a/lib/matplotlib/gridspec.pyi b/lib/matplotlib/gridspec.pyi index 6f314054a9ee..1ac1bb0b40e7 100644 --- a/lib/matplotlib/gridspec.pyi +++ b/lib/matplotlib/gridspec.pyi @@ -30,7 +30,7 @@ class GridSpecBase: def set_height_ratios(self, height_ratios: ArrayLike | None) -> None: ... def get_height_ratios(self) -> ArrayLike: ... def get_grid_positions( - self, fig: Figure, raw: bool = ... + self, fig: Figure ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: ... @staticmethod def _check_gridspec_exists(figure: Figure, nrows: int, ncols: int) -> GridSpec: ... From e64af30e42dd1f2af7584021ee1f6bdd595cce59 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 25 Sep 2023 23:02:03 -0500 Subject: [PATCH 0195/1120] Add additional public attrs --- lib/matplotlib/axes/_base.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index cac66d354245..c63d81992389 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -59,6 +59,8 @@ class _AxesBase(martist.Artist): containers: list[Container] callbacks: CallbackRegistry child_axes: list[_AxesBase] + legend_: Legend | None + title: Text _projection_init: Any def __init__( From a9523f5fbc9552d088f7759f918d0c1ab766d236 Mon Sep 17 00:00:00 2001 From: rsp2210 Date: Mon, 25 Sep 2023 22:46:28 -0700 Subject: [PATCH 0196/1120] Resolved squash conflict and applied changes --- doc/api/next_api_changes/behavior/26902-RP.rst | 5 +++++ lib/matplotlib/lines.py | 18 ++---------------- lib/matplotlib/tests/test_lines.py | 7 ++----- 3 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26902-RP.rst diff --git a/doc/api/next_api_changes/behavior/26902-RP.rst b/doc/api/next_api_changes/behavior/26902-RP.rst new file mode 100644 index 000000000000..3106de94fbd5 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26902-RP.rst @@ -0,0 +1,5 @@ +``Line2D`` +~~~~~~~~~~ + +When creating a Line2D or using `.Line2D.set_xdata` and `.Line2D.set_ydata`, +passing x/y data as non sequence is now an error. diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 92d55a3fe6ae..31b931a52c82 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1276,14 +1276,7 @@ def set_xdata(self, x): x : 1D array """ if not np.iterable(x): - # When deprecation cycle is completed - # raise RuntimeError('x must be a sequence') - _api.warn_deprecated( - since="3.7", - message="Setting data with a non sequence type " - "is deprecated since %(since)s and will be " - "remove %(removal)s") - x = [x, ] + raise RuntimeError('x must be a sequence') self._xorig = copy.copy(x) self._invalidx = True self.stale = True @@ -1297,14 +1290,7 @@ def set_ydata(self, y): y : 1D array """ if not np.iterable(y): - # When deprecation cycle is completed - # raise RuntimeError('y must be a sequence') - _api.warn_deprecated( - since="3.7", - message="Setting data with a non sequence type " - "is deprecated since %(since)s and will be " - "remove %(removal)s") - y = [y, ] + raise RuntimeError('y must be a sequence') self._yorig = copy.copy(y) self._invalidy = True self.stale = True diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 4f23e6969b0b..68e378a20f88 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -92,12 +92,9 @@ def test_invalid_line_data(): mlines.Line2D([], 1) line = mlines.Line2D([], []) - # when deprecation cycle is completed - # with pytest.raises(RuntimeError, match='x must be'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): + with pytest.raises(RuntimeError, match='x must be'): line.set_xdata(0) - # with pytest.raises(RuntimeError, match='y must be'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): + with pytest.raises(RuntimeError, match='y must be'): line.set_ydata(0) From a711b12b0866a721686be49b7a694bae3abd29cc Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 26 Sep 2023 15:26:11 -0500 Subject: [PATCH 0197/1120] Add tool for running stubtest Walks the ast to find delete_parameter calls and automatically ignores them as their signature is modified at runtime Could potentially be further modified to respect the alias behavior, which is the majority of the current allowlist Makes running stubtest easier as now it is just 'python tools/stubtest.py', rather than a long incantation with multiple arguments --- .github/workflows/mypy-stubtest.yml | 5 +-- ci/mypy-stubtest-allowlist.txt | 3 -- doc/devel/contribute.rst | 16 ++++----- tools/stubtest.py | 53 +++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 tools/stubtest.py diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml index 6da6f607642c..4cb225258f4f 100644 --- a/.github/workflows/mypy-stubtest.yml +++ b/.github/workflows/mypy-stubtest.yml @@ -37,10 +37,7 @@ jobs: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -o pipefail - MPLBACKEND=agg python -m mypy.stubtest \ - --mypy-config-file pyproject.toml \ - --allowlist ci/mypy-stubtest-allowlist.txt \ - matplotlib | \ + MPLBACKEND=agg python tools/stubtest.py | \ reviewdog \ -efm '%Eerror: %m' \ -efm '%CStub: in file %f:%l' \ diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e0890b3f7117..5474d6fc5087 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -55,13 +55,10 @@ matplotlib.cm.unregister_cmap matplotlib.collections.PolyCollection.span_where # 3.8 deprecations -matplotlib.cbook.get_sample_data matplotlib.contour.ContourSet.allkinds matplotlib.contour.ContourSet.allsegs matplotlib.contour.ContourSet.tcolors matplotlib.contour.ContourSet.tlinewidths -matplotlib.ticker.LogLocator.__init__ -matplotlib.ticker.LogLocator.set_params # positional-only argument name lacking leading underscores matplotlib.axes._base._AxesBase.axis diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 9b53a80ab374..85c061b7ce8f 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -425,11 +425,7 @@ Introducing updated on introduction. - Items decorated with ``@_api.delete_parameter`` should include a default value hint for the deleted parameter, even if it did not previously have one (e.g. - ``param: = ...``). Even so, the decorator changes the default value to a - sentinel value which should not be included in the type stub. Thus, Mypy Stubtest - needs to be informed of the inconsistency by placing the method into - :file:`ci/mypy-stubtest-allowlist.txt` under a heading indicating the deprecation - version number. + ``param: = ...``). Expiring ~~~~~~~~ @@ -452,11 +448,11 @@ Expiring will have been updated at introduction, and require no change now. - Items decorated with ``@_api.delete_parameter`` will need to be updated to the final signature, in the same way as the ``.py`` file signature is updated. - The entry in :file:`ci/mypy-stubtest-allowlist.txt` should be removed. - - Any other entries in :file:`ci/mypy-stubtest-allowlist.txt` under a version's - deprecations should be double checked, as only ``delete_parameter`` should normally - require that mechanism for deprecation. For removed items that were not in the stub - file, only deleting from the allowlist is required. + - Any entries in :file:`ci/mypy-stubtest-allowlist.txt` which indicate a deprecation + version should be double checked. In most cases this is not needed, though some + items were never type hinted in the first place and were added to this file + instead. For removed items that were not in the stub file, only deleting from the + allowlist is required. Adding new API -------------- diff --git a/tools/stubtest.py b/tools/stubtest.py new file mode 100644 index 000000000000..a68f3ab5c3b3 --- /dev/null +++ b/tools/stubtest.py @@ -0,0 +1,53 @@ +import ast +import pathlib +import subprocess +import sys +import tempfile + +root = pathlib.Path(__file__).parent.parent + +lib = root / "lib" +mpl = lib / "matplotlib" + + +class Visitor(ast.NodeVisitor): + def __init__(self, filepath, output): + self.filepath = filepath + self.context = list(filepath.with_suffix("").relative_to(lib).parts) + self.output = output + + def visit_FunctionDef(self, node): + if any("delete_parameter" in ast.unparse(line) for line in node.decorator_list): + parents = [] + if hasattr(node, "parent"): + parent = node.parent + while hasattr(parent, "parent") and not isinstance(parent, ast.Module): + parents.append(parent.name) + parent = parent.parent + self.output.write(f"{'.'.join(self.context + parents)}.{node.name}\n") + + +with tempfile.NamedTemporaryFile("wt") as f: + for path in mpl.glob("**/*.py"): + v = Visitor(path, f) + tree = ast.parse(path.read_text()) + + # Assign parents to tree so they can be backtraced + for node in ast.walk(tree): + for child in ast.iter_child_nodes(node): + child.parent = node + + v.visit(tree) + f.flush() + proc = subprocess.run( + [ + "stubtest", + "--mypy-config-file=pyproject.toml", + "--allowlist=ci/mypy-stubtest-allowlist.txt", + f"--allowlist={f.name}", + "matplotlib", + ], + cwd=root, + ) + +sys.exit(proc.returncode) From a59c2680dc176744282be1173f6a90cfb475d909 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 26 Sep 2023 17:47:27 -0500 Subject: [PATCH 0198/1120] Add alias decorator handling to the generated allowlist --- ci/mypy-stubtest-allowlist.txt | 82 ---------------------------------- tools/stubtest.py | 28 +++++++++++- 2 files changed, 27 insertions(+), 83 deletions(-) diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 5474d6fc5087..4cbbcac94dcf 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -63,88 +63,6 @@ matplotlib.contour.ContourSet.tlinewidths # positional-only argument name lacking leading underscores matplotlib.axes._base._AxesBase.axis -# Aliases (dynamically generated, not type hinted) -matplotlib.collections.Collection.get_aa -matplotlib.collections.Collection.get_antialiaseds -matplotlib.collections.Collection.get_dashes -matplotlib.collections.Collection.get_ec -matplotlib.collections.Collection.get_edgecolors -matplotlib.collections.Collection.get_facecolors -matplotlib.collections.Collection.get_fc -matplotlib.collections.Collection.get_linestyles -matplotlib.collections.Collection.get_linewidths -matplotlib.collections.Collection.get_ls -matplotlib.collections.Collection.get_lw -matplotlib.collections.Collection.get_transOffset -matplotlib.collections.Collection.set_aa -matplotlib.collections.Collection.set_antialiaseds -matplotlib.collections.Collection.set_dashes -matplotlib.collections.Collection.set_ec -matplotlib.collections.Collection.set_edgecolors -matplotlib.collections.Collection.set_facecolors -matplotlib.collections.Collection.set_fc -matplotlib.collections.Collection.set_linestyles -matplotlib.collections.Collection.set_linewidths -matplotlib.collections.Collection.set_ls -matplotlib.collections.Collection.set_lw -matplotlib.collections.Collection.set_transOffset -matplotlib.lines.Line2D.get_aa -matplotlib.lines.Line2D.get_c -matplotlib.lines.Line2D.get_ds -matplotlib.lines.Line2D.get_ls -matplotlib.lines.Line2D.get_lw -matplotlib.lines.Line2D.get_mec -matplotlib.lines.Line2D.get_mew -matplotlib.lines.Line2D.get_mfc -matplotlib.lines.Line2D.get_mfcalt -matplotlib.lines.Line2D.get_ms -matplotlib.lines.Line2D.set_aa -matplotlib.lines.Line2D.set_c -matplotlib.lines.Line2D.set_ds -matplotlib.lines.Line2D.set_ls -matplotlib.lines.Line2D.set_lw -matplotlib.lines.Line2D.set_mec -matplotlib.lines.Line2D.set_mew -matplotlib.lines.Line2D.set_mfc -matplotlib.lines.Line2D.set_mfcalt -matplotlib.lines.Line2D.set_ms -matplotlib.patches.Patch.get_aa -matplotlib.patches.Patch.get_ec -matplotlib.patches.Patch.get_fc -matplotlib.patches.Patch.get_ls -matplotlib.patches.Patch.get_lw -matplotlib.patches.Patch.set_aa -matplotlib.patches.Patch.set_ec -matplotlib.patches.Patch.set_fc -matplotlib.patches.Patch.set_ls -matplotlib.patches.Patch.set_lw -matplotlib.text.Text.get_c -matplotlib.text.Text.get_family -matplotlib.text.Text.get_font -matplotlib.text.Text.get_font_properties -matplotlib.text.Text.get_ha -matplotlib.text.Text.get_name -matplotlib.text.Text.get_size -matplotlib.text.Text.get_style -matplotlib.text.Text.get_va -matplotlib.text.Text.get_variant -matplotlib.text.Text.get_weight -matplotlib.text.Text.set_c -matplotlib.text.Text.set_family -matplotlib.text.Text.set_font -matplotlib.text.Text.set_font_properties -matplotlib.text.Text.set_ha -matplotlib.text.Text.set_ma -matplotlib.text.Text.set_name -matplotlib.text.Text.set_size -matplotlib.text.Text.set_stretch -matplotlib.text.Text.set_style -matplotlib.text.Text.set_va -matplotlib.text.Text.set_variant -matplotlib.text.Text.set_weight -matplotlib.axes._base._AxesBase.get_fc -matplotlib.axes._base._AxesBase.set_fc - # Other dynamic python behaviors not type hinted matplotlib.rcsetup.defaultParams diff --git a/tools/stubtest.py b/tools/stubtest.py index a68f3ab5c3b3..3f3c6931255f 100644 --- a/tools/stubtest.py +++ b/tools/stubtest.py @@ -1,4 +1,5 @@ import ast +import os import pathlib import subprocess import sys @@ -22,10 +23,34 @@ def visit_FunctionDef(self, node): if hasattr(node, "parent"): parent = node.parent while hasattr(parent, "parent") and not isinstance(parent, ast.Module): - parents.append(parent.name) + parents.insert(0, parent.name) parent = parent.parent self.output.write(f"{'.'.join(self.context + parents)}.{node.name}\n") + def visit_ClassDef(self, node): + for dec in node.decorator_list: + if "define_aliases" in ast.unparse(dec): + parents = [] + if hasattr(node, "parent"): + parent = node.parent + while hasattr(parent, "parent") and not isinstance( + parent, ast.Module + ): + parents.insert(0, parent.name) + parent = parent.parent + aliases = ast.literal_eval(dec.args[0]) + # Written as a regex rather than two lines to avoid unused entries + # for setters on items with only a getter + for substitutions in aliases.values(): + parts = self.context + parents + [node.name] + self.output.write( + "\n".join( + f"{'.'.join(parts)}.[gs]et_{a}\n" for a in substitutions + ) + ) + for child in ast.iter_child_nodes(node): + self.visit(child) + with tempfile.NamedTemporaryFile("wt") as f: for path in mpl.glob("**/*.py"): @@ -48,6 +73,7 @@ def visit_FunctionDef(self, node): "matplotlib", ], cwd=root, + env=os.environ | {"MPLBACKEND": "agg"}, ) sys.exit(proc.returncode) From a16721f0771be2816114172574af2c2a36b8fbe2 Mon Sep 17 00:00:00 2001 From: ananya314 <57040724+ananya314@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:36:02 -0400 Subject: [PATCH 0199/1120] Fix 26872-AD.rst --- doc/api/next_api_changes/removals/26872-AD.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/api/next_api_changes/removals/26872-AD.rst b/doc/api/next_api_changes/removals/26872-AD.rst index 2f678ffc3cfd..411359813e51 100644 --- a/doc/api/next_api_changes/removals/26872-AD.rst +++ b/doc/api/next_api_changes/removals/26872-AD.rst @@ -1,6 +1,5 @@ -``repeat`` -~~~~~~~~~~ -... of `.TimedAnimation` is removed without replacements. -``save_count`` -~~~~~~~~~~~~~~ -... of `.FuncAnimation` is removed without replacements. +``Animation`` attributes +~~~~~~~~~~~~~~~~~~~~~~~~ + +The attributes ``repeat`` of `.TimedAnimation` and subclasses and +``save_count`` of `.FuncAnimation` are considered private and removed. From 4d02b5f1f13b75e4f51f858ac0f51a8ac3921a9e Mon Sep 17 00:00:00 2001 From: Hugues Hoppe Date: Tue, 26 Sep 2023 23:50:28 -0700 Subject: [PATCH 0200/1120] Update pyplot.py Added type signature for `show`. --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 74a73725d5d9..ce7d302ae9e7 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -479,7 +479,7 @@ def draw_if_interactive(*args, **kwargs): # This function's signature is rewritten upon backend-load by switch_backend. -def show(*args, **kwargs): +def show(*args, **kwargs) -> None: """ Display all open figures. From c60622a3fd8bb010d6fe6f1e71651e78e3775e4c Mon Sep 17 00:00:00 2001 From: wemi3 <62965919+wemi3@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:36:47 -0700 Subject: [PATCH 0201/1120] 26865 Removed deprecations from quiver.py (#26918) * 26865 Removed deprecations from quiver.py * Added rst file * updated quiver.pyi * removed extra lines * removed code stubs, edited rst file * Update lib/matplotlib/quiver.py Co-authored-by: Elliott Sales de Andrade * Update doc/api/next_api_changes/removals/26918-EW.rst Co-authored-by: Oscar Gustafsson --------- Co-authored-by: Elliott Sales de Andrade Co-authored-by: Oscar Gustafsson --- doc/api/next_api_changes/removals/26918-EW.rst | 3 +++ lib/matplotlib/quiver.py | 4 ---- lib/matplotlib/quiver.pyi | 4 ---- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26918-EW.rst diff --git a/doc/api/next_api_changes/removals/26918-EW.rst b/doc/api/next_api_changes/removals/26918-EW.rst new file mode 100644 index 000000000000..454f35d5e200 --- /dev/null +++ b/doc/api/next_api_changes/removals/26918-EW.rst @@ -0,0 +1,3 @@ +``Quiver.quiver_doc`` and ``Barbs.barbs_doc`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... are removed. These are the doc-string and should not be accessible as a named class member. diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index c8f8ba566106..52f56deb40c0 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -721,8 +721,6 @@ def _h_arrows(self, length): # Mask handling is deferred to the caller, _make_verts. return X, Y - quiver_doc = _api.deprecated("3.7")(property(lambda self: _quiver_doc)) - _barbs_doc = r""" Plot a 2D field of barbs. @@ -1177,5 +1175,3 @@ def set_offsets(self, xy): xy = np.column_stack((x, y)) super().set_offsets(xy) self.stale = True - - barbs_doc = _api.deprecated("3.7")(property(lambda self: _barbs_doc)) diff --git a/lib/matplotlib/quiver.pyi b/lib/matplotlib/quiver.pyi index c673c5dd3aff..2a043a92b4b5 100644 --- a/lib/matplotlib/quiver.pyi +++ b/lib/matplotlib/quiver.pyi @@ -125,8 +125,6 @@ class Quiver(mcollections.PolyCollection): def set_UVC( self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... ) -> None: ... - @property - def quiver_doc(self) -> str: ... class Barbs(mcollections.PolyCollection): sizes: dict[str, float] @@ -183,5 +181,3 @@ class Barbs(mcollections.PolyCollection): self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... ) -> None: ... def set_offsets(self, xy: ArrayLike) -> None: ... - @property - def barbs_doc(self) -> str: ... From d69ace945fd80ae8356227b055a4b94142f32f62 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 22 Sep 2023 18:12:51 -0700 Subject: [PATCH 0202/1120] DOC: improve removal for julian dates [ci doc] --- doc/api/next_api_changes/removals/26852-OG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/api/next_api_changes/removals/26852-OG.rst b/doc/api/next_api_changes/removals/26852-OG.rst index dc7d595f575f..08ad0105b70a 100644 --- a/doc/api/next_api_changes/removals/26852-OG.rst +++ b/doc/api/next_api_changes/removals/26852-OG.rst @@ -3,3 +3,10 @@ ... of the `.dates` module are removed without replacements. These were undocumented and not exported. + +Julian dates in Matplotlib were calculated from a Julian date epoch: ``jdate = +(date - np.datetime64(EPOCH)) / np.timedelta64(1, 'D')``. Conversely, a Julian +date was converted to datetime as ``date = np.timedelta64(int(jdate * 24 * +3600), 's') + np.datetime64(EPOCH)``. Matplotlib was using +``EPOCH='-4713-11-24T12:00'`` so that 2000-01-01 at 12:00 is 2_451_545.0 (see +`). From ab94dd766af49d996f8c48ee88e75944277ef574 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 25 Sep 2023 11:57:32 -0700 Subject: [PATCH 0203/1120] DOC: add a couple more placement examples, crosslink axes_grid [ci doc] Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../demo_colorbar_with_axes_divider.py | 6 + .../demo_colorbar_with_inset_locator.py | 10 +- .../users_explain/axes/colorbar_placement.py | 113 ++++++++++++++++-- .../axes/constrainedlayout_guide.py | 4 +- 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py index e314c2dcea21..9e4611c65bb7 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py @@ -1,4 +1,6 @@ """ +.. _demo-colorbar-with-axes-divider: + ========================= Colorbar with AxesDivider ========================= @@ -8,6 +10,10 @@ method of the `.AxesDivider` can then be used to create a new axes on a given side ("top", "right", "bottom", or "left") of the original axes. This example uses `.append_axes` to add colorbars next to axes. + +Users should consider simply passing the main axes to the *ax* keyword argument of +`~.Figure.colorbar` instead of creating a locatable axes manually like this. +See :ref:`colorbar_placement`. """ import matplotlib.pyplot as plt diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py index 0c8d48e23101..8ec7d0e7271b 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py @@ -1,15 +1,21 @@ """ +.. _demo-colorbar-with-inset-locator: + ============================================================== Controlling the position and size of colorbars with Inset Axes ============================================================== -This example shows how to control the position, height, and width of -colorbars using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. +This example shows how to control the position, height, and width of colorbars +using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. Inset axes placement is controlled as for legends: either by providing a *loc* option ("upper right", "best", ...), or by providing a locator with respect to the parent bbox. Parameters such as *bbox_to_anchor* and *borderpad* likewise work in the same way, and are also demonstrated here. + +Users should consider using `.Axes.inset_axes` instead (see +:ref:`colorbar_placement`). + """ import matplotlib.pyplot as plt diff --git a/galleries/users_explain/axes/colorbar_placement.py b/galleries/users_explain/axes/colorbar_placement.py index de767a4fa130..1e43d4940a98 100644 --- a/galleries/users_explain/axes/colorbar_placement.py +++ b/galleries/users_explain/axes/colorbar_placement.py @@ -10,7 +10,11 @@ Colorbars indicate the quantitative extent of image data. Placing in a figure is non-trivial because room needs to be made for them. -The simplest case is just attaching a colorbar to each axes: +Automatic placement of colorbars +================================ + +The simplest case is just attaching a colorbar to each axes. Note in this +example that the colorbars steal some space from the parent axes. """ import matplotlib.pyplot as plt import numpy as np @@ -28,9 +32,9 @@ fig.colorbar(pcm, ax=ax) # %% -# The first column has the same type of data in both rows, so it may -# be desirable to combine the colorbar which we do by calling -# `.Figure.colorbar` with a list of axes instead of a single axes. +# The first column has the same type of data in both rows, so it may be +# desirable to have just one colorbar. We do this by passing `.Figure.colorbar` +# a list of axes with the *ax* kwarg. fig, axs = plt.subplots(2, 2) cmaps = ['RdBu_r', 'viridis'] @@ -41,6 +45,27 @@ cmap=cmaps[col]) fig.colorbar(pcm, ax=axs[:, col], shrink=0.6) +# %% +# The stolen space can lead to axes in the same subplot layout +# being different sizes, which is often undesired if the the +# x-axis on each plot is meant to be comparable as in the following: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True) +X = np.random.randn(20, 20) +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + +# %% +# This is usually undesired, and can be worked around in various ways, e.g. +# adding a colorbar to the other axes and then removing it. However, the most +# straightforward is to use :ref:`constrained layout `: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True, layout='constrained') +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + # %% # Relatively complicated colorbar layouts are possible using this # paradigm. Note that this example works far better with @@ -56,8 +81,67 @@ fig.colorbar(pcm, ax=[axs[2, 1]], location='left') # %% -# Colorbars with fixed-aspect-ratio axes -# ====================================== +# Adjusting the spacing between colorbars and parent axes +# ======================================================= +# +# The distance a colorbar is from the parent axes can be adjusted with the +# *pad* keyword argument. This is in units of fraction of the parent axes +# width, and the default for a vertical axes is 0.05 (or 0.15 for a horizontal +# axes). + +fig, axs = plt.subplots(3, 1, layout='constrained', figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("layout='constrained'") + +# %% +# Note that if you do not use constrained layout, the pad command makes the +# parent axes shrink: + +fig, axs = plt.subplots(3, 1, figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("No layout manager") + +# %% +# Manual placement of colorbars +# ============================= +# +# Sometimes the automatic placement provided by ``colorbar`` does not +# give the desired effect. We can manually create an axes and tell +# ``colorbar`` to use that axes by passing the axes to the *cax* keyword +# argument. +# +# Using ``inset_axes`` +# -------------------- +# +# We can manually create any type of axes for the colorbar to use, but an +# `.Axes.inset_axes` is useful because it is a child of the parent axes and can +# be positioned relative to the parent. Here we add a colorbar centered near +# the bottom of the parent axes. + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([0.3, 0.07, 0.4, 0.04]) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# `.Axes.inset_axes` can also specify its position in data coordinates +# using the *transform* keyword argument if you want your axes at a +# certain data position on the graph: + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([7.5, -1.7, 5, 1.2], transform=ax.transData) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# Colorbars attached to fixed-aspect-ratio axes +# --------------------------------------------- # # Placing colorbars for axes with a fixed aspect ratio pose a particular # challenge as the parent axes changes size depending on the data view. @@ -77,9 +161,10 @@ fig.colorbar(pcm, ax=ax, shrink=0.6) # %% -# One way around this issue is to use an `.Axes.inset_axes` to locate the -# axes in axes coordinates. Note that if you zoom in on the axes, and -# change the shape of the axes, the colorbar will also change position. +# We solve this problem using `.Axes.inset_axes` to locate the axes in "axes +# coordinates" (see :ref:`transforms_tutorial`). Note that if you zoom in on +# the parent axes, and thus change the shape of it, the colorbar will also +# change position. fig, axs = plt.subplots(2, 2, layout='constrained') cmaps = ['RdBu_r', 'viridis'] @@ -94,6 +179,12 @@ ax.set_aspect(1/2) if row == 1: cax = ax.inset_axes([1.04, 0.2, 0.05, 0.6]) - fig.colorbar(pcm, ax=ax, cax=cax) + fig.colorbar(pcm, cax=cax) -plt.show() +# %% +# .. seealso:: +# +# :ref:`axes_grid` has methods for manually creating colorbar axes as well: +# +# - :ref:`demo-colorbar-with-inset-locator` +# - :ref:`demo-colorbar-with-axes-divider` diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py index 0a2752674c6a..260a4f76bf71 100644 --- a/galleries/users_explain/axes/constrainedlayout_guide.py +++ b/galleries/users_explain/axes/constrainedlayout_guide.py @@ -4,9 +4,9 @@ .. _constrainedlayout_guide: -================================ +======================== Constrained Layout Guide -================================ +======================== Use *constrained layout* to fit plots within your figure cleanly. From afcc560f3ea70dc9f3964dabf2b7632118f6085d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 26 Sep 2023 15:05:25 -0500 Subject: [PATCH 0204/1120] [TYP] Remove some stubtest allowlist entries Mark the expected interface for some things such as Transform.input_dims as read-only via a property and remove redundant versions in subclasses Same for some offsetbox code Fix rcsetup ignores --- ci/mypy-stubtest-allowlist.txt | 18 ------------------ lib/matplotlib/offsetbox.pyi | 8 ++++++-- lib/matplotlib/rcsetup.pyi | 9 ++++++--- lib/matplotlib/transforms.pyi | 20 +++++++++----------- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e0890b3f7117..b92ef9c67688 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -32,18 +32,6 @@ matplotlib.ticker.LogitLocator.nonsingular matplotlib.backend_bases._Mode.__new__ matplotlib.units.Number.__hash__ -# Property read-write vs read-only weirdness, fix if possible -matplotlib.offsetbox.DraggableBase.canvas -matplotlib.offsetbox.DraggableBase.cids -matplotlib.transforms.BboxTransform.is_separable -matplotlib.transforms.BboxTransformFrom.is_separable -matplotlib.transforms.BboxTransformTo.is_separable -matplotlib.transforms.BlendedAffine2D.is_separable -matplotlib.transforms.CompositeGenericTransform.is_separable -matplotlib.transforms.TransformWrapper.input_dims -matplotlib.transforms.TransformWrapper.is_separable -matplotlib.transforms.TransformWrapper.output_dims - # 3.6 Pending deprecations matplotlib.figure.Figure.set_constrained_layout matplotlib.figure.Figure.set_constrained_layout_pads @@ -148,16 +136,10 @@ matplotlib.text.Text.set_weight matplotlib.axes._base._AxesBase.get_fc matplotlib.axes._base._AxesBase.set_fc -# Other dynamic python behaviors not type hinted -matplotlib.rcsetup.defaultParams - # Maybe should be abstractmethods, required for subclasses, stubs define once matplotlib.tri.*TriInterpolator.__call__ matplotlib.tri.*TriInterpolator.gradient -# Functionally a method call, but actually a class instance, type hinted as former -matplotlib.rcsetup.validate_fillstyle - # TypeVar used only in type hints matplotlib.backend_bases.FigureCanvasBase._T matplotlib.backend_managers.ToolManager._T diff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi index 09f89aed2bc8..c222a9b2973e 100644 --- a/lib/matplotlib/offsetbox.pyi +++ b/lib/matplotlib/offsetbox.pyi @@ -280,11 +280,15 @@ class AnnotationBbox(martist.Artist, mtext._AnnotationBase): class DraggableBase: ref_artist: martist.Artist got_artist: bool - canvas: FigureCanvasBase - cids: list[int] mouse_x: int mouse_y: int background: Any + + @property + def canvas(self) -> FigureCanvasBase: ... + @property + def cids(self) -> list[int]: ... + def __init__(self, ref_artist: martist.Artist, use_blit: bool = ...) -> None: ... def on_motion(self, evt: Event) -> None: ... def on_pick(self, evt: Event) -> None: ... diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 8a8a9e71d666..70e94a7694a9 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -129,9 +129,9 @@ def validate_fontstretch( def validate_font_properties(s: Any) -> dict[str, Any]: ... def validate_whiskers(s: Any) -> list[float] | float: ... def validate_ps_distiller(s: Any) -> None | Literal["ghostscript", "xpdf"]: ... -def validate_fillstyle( - s: Any, -) -> Literal["full", "left", "right", "bottom", "top", "none"]: ... + +validate_fillstyle: ValidateInStrings + def validate_fillstylelist( s: Any, ) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ... @@ -152,3 +152,6 @@ def validate_hist_bins( ) -> Literal["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"] | int | list[ float ]: ... + +# At runtime is added in __init__.py +defaultParams: dict[str, Any] diff --git a/lib/matplotlib/transforms.pyi b/lib/matplotlib/transforms.pyi index 68e55612b7f1..90a527e5bfc5 100644 --- a/lib/matplotlib/transforms.pyi +++ b/lib/matplotlib/transforms.pyi @@ -175,12 +175,17 @@ class LockableBbox(BboxBase): def locked_y1(self, y1: float | None) -> None: ... class Transform(TransformNode): - input_dims: int | None - output_dims: int | None - is_separable: bool - # Implemented as a standard attr in base class, but functionally readonly and some subclasses implement as such + + # Implemented as a standard attrs in base class, but functionally readonly and some subclasses implement as such + @property + def input_dims(self) -> int | None: ... + @property + def output_dims(self) -> int | None: ... + @property + def is_separable(self) -> bool: ... @property def has_inverse(self) -> bool: ... + def __add__(self, other: Transform) -> Transform: ... @property def depth(self) -> int: ... @@ -225,8 +230,6 @@ class Affine2DBase(AffineBase): input_dims: Literal[2] output_dims: Literal[2] def frozen(self) -> Affine2D: ... - @property - def is_separable(self): ... def to_values(self) -> tuple[float, float, float, float, float, float]: ... class Affine2D(Affine2DBase): @@ -255,7 +258,6 @@ class _BlendedMixin: class BlendedGenericTransform(_BlendedMixin, Transform): input_dims: Literal[2] output_dims: Literal[2] - is_separable: bool pass_through: bool def __init__( self, x_transform: Transform, y_transform: Transform, **kwargs @@ -265,8 +267,6 @@ class BlendedGenericTransform(_BlendedMixin, Transform): def contains_branch(self, other: Transform) -> Literal[False]: ... @property def is_affine(self) -> bool: ... - @property - def has_inverse(self) -> bool: ... class BlendedAffine2D(_BlendedMixin, Affine2DBase): def __init__( @@ -279,8 +279,6 @@ def blended_transform_factory( class CompositeGenericTransform(Transform): pass_through: bool - input_dims: int | None - output_dims: int | None def __init__(self, a: Transform, b: Transform, **kwargs) -> None: ... class CompositeAffine2D(Affine2DBase): From e8f21d271a498fb6bcee936eae239ce7298e8a9e Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 27 Sep 2023 11:53:16 -0500 Subject: [PATCH 0205/1120] add tomli to rstcheck extras required for reading config from pyproject.toml on py<3.11 --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18707e44e5d0..05a14a1a08cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,3 +55,4 @@ repos: - id: rstcheck additional_dependencies: - sphinx>=1.8.1 + - tomli From 6dadeca73c5d5a60999e68351018fa3928db55a1 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 27 Sep 2023 13:07:30 -0500 Subject: [PATCH 0206/1120] Add ArrayLike to scatter c arg type hint Closes #26936 --- lib/matplotlib/axes/_axes.pyi | 2 +- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 96ea087f7eb9..9602db3b950c 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -402,7 +402,7 @@ class Axes(_AxesBase): x: float | ArrayLike, y: float | ArrayLike, s: float | ArrayLike | None = ..., - c: Sequence[ColorType] | ColorType | None = ..., + c: ArrayLike | Sequence[ColorType] | ColorType | None = ..., marker: MarkerType | None = ..., cmap: str | Colormap | None = ..., norm: str | Normalize | None = ..., diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 74a73725d5d9..f48ca312e58e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3670,7 +3670,7 @@ def scatter( x: float | ArrayLike, y: float | ArrayLike, s: float | ArrayLike | None = None, - c: Sequence[ColorType] | ColorType | None = None, + c: ArrayLike | Sequence[ColorType] | ColorType | None = None, marker: MarkerType | None = None, cmap: str | Colormap | None = None, norm: str | Normalize | None = None, From 3f72eca907dbe6ebedd4e4437139ee6393b12947 Mon Sep 17 00:00:00 2001 From: Aditi Gautam Date: Fri, 22 Sep 2023 11:34:20 -0700 Subject: [PATCH 0207/1120] Cleaned up the span_where class method from Polycollections. --- ci/mypy-stubtest-allowlist.txt | 1 - doc/api/artist_api.rst | 2 +- .../next_api_changes/removals/26874-AG.rst | 4 ++ doc/users/prev_whats_new/whats_new_1.4.rst | 2 +- lib/matplotlib/axes/_axes.pyi | 3 +- lib/matplotlib/collections.py | 45 ------------------- lib/matplotlib/collections.pyi | 12 ----- lib/matplotlib/pyplot.py | 3 +- 8 files changed, 8 insertions(+), 64 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26874-AG.rst diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e0890b3f7117..78f269a5f5bf 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -52,7 +52,6 @@ matplotlib.figure.Figure.set_tight_layout # 3.7 deprecations matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap -matplotlib.collections.PolyCollection.span_where # 3.8 deprecations matplotlib.cbook.get_sample_data diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index 3903bbd5924d..df7a6e8b1c04 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -11,7 +11,7 @@ Inheritance Diagrams ==================== -.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.BrokenBarHCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet +.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet :parts: 1 :private-bases: diff --git a/doc/api/next_api_changes/removals/26874-AG.rst b/doc/api/next_api_changes/removals/26874-AG.rst new file mode 100644 index 000000000000..ad305cf9d96c --- /dev/null +++ b/doc/api/next_api_changes/removals/26874-AG.rst @@ -0,0 +1,4 @@ +``collections.PolyCollection.span_where`` and ``collections.BrokenBarHCollection`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... removed as it was deprecated during 3.7. Use ``fill_between`` instead diff --git a/doc/users/prev_whats_new/whats_new_1.4.rst b/doc/users/prev_whats_new/whats_new_1.4.rst index 39eefa81b168..eb0e93fd8883 100644 --- a/doc/users/prev_whats_new/whats_new_1.4.rst +++ b/doc/users/prev_whats_new/whats_new_1.4.rst @@ -221,7 +221,7 @@ Added size related functions to specialized `.Collection`\s Added the ``get_size`` and ``set_size`` functions to control the size of elements of specialized collections ( :class:`~matplotlib.collections.AsteriskPolygonCollection` -:class:`~matplotlib.collections.BrokenBarHCollection` +``matplotlib.collections.BrokenBarHCollection`` :class:`~matplotlib.collections.CircleCollection` :class:`~matplotlib.collections.PathCollection` :class:`~matplotlib.collections.PolyCollection` diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 96ea087f7eb9..5dff356be4a9 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -6,7 +6,6 @@ from matplotlib.backend_bases import RendererBase from matplotlib.collections import ( Collection, LineCollection, - BrokenBarHCollection, PathCollection, PolyCollection, EventCollection, @@ -282,7 +281,7 @@ class Axes(_AxesBase): *, data=..., **kwargs - ) -> BrokenBarHCollection: ... + ) -> PolyCollection: ... def stem( self, *args: ArrayLike | str, diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 81db24d0c026..cc20e5cebc1b 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1253,51 +1253,6 @@ def set_verts_and_codes(self, verts, codes): for xy, cds in zip(verts, codes)] self.stale = True - @classmethod - @_api.deprecated("3.7", alternative="fill_between") - def span_where(cls, x, ymin, ymax, where, **kwargs): - """ - Return a `.BrokenBarHCollection` that plots horizontal bars from - over the regions in *x* where *where* is True. The bars range - on the y-axis from *ymin* to *ymax* - - *kwargs* are passed on to the collection. - """ - xranges = [] - for ind0, ind1 in cbook.contiguous_regions(where): - xslice = x[ind0:ind1] - if not len(xslice): - continue - xranges.append((xslice[0], xslice[-1] - xslice[0])) - return BrokenBarHCollection(xranges, [ymin, ymax - ymin], **kwargs) - - -@_api.deprecated("3.7") -class BrokenBarHCollection(PolyCollection): - """ - A collection of horizontal bars spanning *yrange* with a sequence of - *xranges*. - """ - def __init__(self, xranges, yrange, **kwargs): - """ - Parameters - ---------- - xranges : list of (float, float) - The sequence of (left-edge-position, width) pairs for each bar. - yrange : (float, float) - The (lower-edge, height) common to all bars. - **kwargs - Forwarded to `.Collection`. - """ - ymin, ywidth = yrange - ymax = ymin + ywidth - verts = [[(xmin, ymin), - (xmin, ymax), - (xmin + xwidth, ymax), - (xmin + xwidth, ymin), - (xmin, ymin)] for xmin, xwidth in xranges] - super().__init__(verts, **kwargs) - class RegularPolyCollection(_CollectionWithSizes): """A collection of n-sided regular polygons.""" diff --git a/lib/matplotlib/collections.pyi b/lib/matplotlib/collections.pyi index 01682a55b374..7162c4687dfa 100644 --- a/lib/matplotlib/collections.pyi +++ b/lib/matplotlib/collections.pyi @@ -106,18 +106,6 @@ class PolyCollection(_CollectionWithSizes): self, verts: Sequence[ArrayLike | Path], codes: Sequence[int] ) -> None: ... -class BrokenBarHCollection(PolyCollection): - def __init__( - self, - xranges: Iterable[tuple[float, float]], - yrange: tuple[float, float], - **kwargs - ) -> None: ... - @classmethod - def span_where( - cls, x: ArrayLike, ymin: float, ymax: float, where: ArrayLike, **kwargs - ) -> BrokenBarHCollection: ... - class RegularPolyCollection(_CollectionWithSizes): def __init__( self, numsides: int, *, rotation: float = ..., sizes: ArrayLike = ..., **kwargs diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 74a73725d5d9..d5c100089993 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -99,7 +99,6 @@ from matplotlib.collections import ( Collection, LineCollection, - BrokenBarHCollection, PolyCollection, PathCollection, EventCollection, @@ -2873,7 +2872,7 @@ def broken_barh( *, data=None, **kwargs, -) -> BrokenBarHCollection: +) -> PolyCollection: return gca().broken_barh( xranges, yrange, **({"data": data} if data is not None else {}), **kwargs ) From 61661f1c40fcaafe19b1f230da83d569a05f4ff9 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 27 Sep 2023 17:18:49 -0500 Subject: [PATCH 0208/1120] Close file so it hopefully works on windows (but not tested) --- tools/stubtest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/stubtest.py b/tools/stubtest.py index 3f3c6931255f..71de6bda20c8 100644 --- a/tools/stubtest.py +++ b/tools/stubtest.py @@ -52,7 +52,7 @@ def visit_ClassDef(self, node): self.visit(child) -with tempfile.NamedTemporaryFile("wt") as f: +with tempfile.NamedTemporaryFile("wt", delete=False) as f: for path in mpl.glob("**/*.py"): v = Visitor(path, f) tree = ast.parse(path.read_text()) @@ -64,6 +64,7 @@ def visit_ClassDef(self, node): v.visit(tree) f.flush() + f.close() proc = subprocess.run( [ "stubtest", @@ -75,5 +76,9 @@ def visit_ClassDef(self, node): cwd=root, env=os.environ | {"MPLBACKEND": "agg"}, ) + try: + os.unlink(f.name) + except OSError: + pass sys.exit(proc.returncode) From 93cab2e642521f86f10b6d65824776a1f623ec72 Mon Sep 17 00:00:00 2001 From: Haoying Zhang <38875181+stevezhang1999@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:43:26 -0400 Subject: [PATCH 0209/1120] DOC: Clarify description and add examples in colors.Normalize (#26915) Co-authored-by: Zihao Yang zihaoyng@gmail.com --- lib/matplotlib/colors.py | 64 ++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 904b6ecfa04b..584adba8eace 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1215,8 +1215,24 @@ def reversed(self, name=None): class Normalize: """ - A class which, when called, linearly normalizes data into the - ``[0.0, 1.0]`` interval. + A class which, when called, maps values within the interval + ``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of + values outside ``[vmin, vmax]`` depends on *clip*. + + Examples + -------- + :: + + x = [-2, -1, 0, 1, 2] + + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=False) + norm(x) # [-0.5, 0., 0.5, 1., 1.5] + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=True) + norm(x) # [0., 0., 0.5, 1., 1.] + + See Also + -------- + :ref:`colormapnorms` """ def __init__(self, vmin=None, vmax=None, clip=False): @@ -1224,27 +1240,30 @@ def __init__(self, vmin=None, vmax=None, clip=False): Parameters ---------- vmin, vmax : float or None - If *vmin* and/or *vmax* is not given, they are initialized from the - minimum and maximum value, respectively, of the first input - processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``. + Values within the range ``[vmin, vmax]`` from the input data will be + linearly mapped to ``[0, 1]``. If either *vmin* or *vmax* is not + provided, they default to the minimum and maximum values of the input, + respectively. + clip : bool, default: False Determines the behavior for mapping values outside the range ``[vmin, vmax]``. - If clipping is off, values outside the range ``[vmin, vmax]`` are also - transformed linearly, resulting in values outside ``[0, 1]``. For a - standard use with colormaps, this behavior is desired because colormaps - mark these outside values with specific colors for *over* or *under*. + If *clip* is ``False``, values outside ``[vmin, vmax]`` are also transformed + linearly, leading to results outside ``[0, 1]``. For a standard use with + colormaps, this behavior is desired because colormaps mark these outside + values with specific colors for over or under. - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer. This makes these values + If *clip* is ``True``, values outside ``[vmin, vmax]`` are set to 0 or 1, + depending on which boundary they're closer to. This makes these values indistinguishable from regular boundary values and can lead to misinterpretation of the data. + Notes ----- - Returns 0 if ``vmin == vmax``. + If ``vmin == vmax``, input data will be mapped to 0. """ self._vmin = _sanitize_extrema(vmin) self._vmax = _sanitize_extrema(vmax) @@ -1298,6 +1317,11 @@ def process_value(value): *value* can be a scalar or sequence. + Parameters + ---------- + value + Data to normalize. + Returns ------- result : masked array @@ -1328,8 +1352,7 @@ def process_value(value): def __call__(self, value, clip=None): """ - Normalize *value* data in the ``[vmin, vmax]`` interval into the - ``[0.0, 1.0]`` interval and return it. + Normalize the data and return the normalized data. Parameters ---------- @@ -1375,6 +1398,15 @@ def __call__(self, value, clip=None): return result def inverse(self, value): + """ + Maps the normalized value (i.e., index in the colormap) back to image + data value. + + Parameters + ---------- + value + Normalized value. + """ if not self.scaled(): raise ValueError("Not invertible until both vmin and vmax are set") (vmin,), _ = self.process_value(self.vmin) @@ -1396,7 +1428,7 @@ def autoscale(self, A): self._changed() def autoscale_None(self, A): - """If vmin or vmax are not set, use the min/max of *A* to set them.""" + """If *vmin* or *vmax* are not set, use the min/max of *A* to set them.""" A = np.asanyarray(A) if isinstance(A, np.ma.MaskedArray): @@ -1410,7 +1442,7 @@ def autoscale_None(self, A): self.vmax = A.max() def scaled(self): - """Return whether vmin and vmax are set.""" + """Return whether *vmin* and *vmax* are both set.""" return self.vmin is not None and self.vmax is not None From 890d4b38a3387a3616069ed4f5f159d7f6b70e15 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 28 Sep 2023 10:17:35 +0200 Subject: [PATCH 0210/1120] Inline Cursor._update into its sole caller. ... to make the Cursor redraw logic easier to follow. Also remove the unused return value from _update. --- lib/matplotlib/widgets.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 9fde066afb5c..a1974a43fe12 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1886,23 +1886,19 @@ def onmove(self, event): if not self.ax.contains(event)[0]: self.linev.set_visible(False) self.lineh.set_visible(False) - if self.needclear: self.canvas.draw() self.needclear = False return self.needclear = True - xdata, ydata = self._get_data_coords(event) self.linev.set_xdata((xdata, xdata)) self.linev.set_visible(self.visible and self.vertOn) self.lineh.set_ydata((ydata, ydata)) self.lineh.set_visible(self.visible and self.horizOn) - - if self.visible and (self.vertOn or self.horizOn): - self._update() - - def _update(self): + if not (self.visible and (self.vertOn or self.horizOn)): + return + # Redraw. if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) @@ -1911,7 +1907,6 @@ def _update(self): self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() - return False class MultiCursor(Widget): @@ -2026,10 +2021,9 @@ def onmove(self, event): for line in self.hlines: line.set_ydata((ydata, ydata)) line.set_visible(self.visible and self.horizOn) - if self.visible and (self.vertOn or self.horizOn): - self._update() - - def _update(self): + if not (self.visible and (self.vertOn or self.horizOn)): + return + # Redraw. if self.useblit: for canvas, info in self._canvas_infos.items(): if info["background"]: From 1e6993831473efc56bf6a0fce6eeb17769eb9d13 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 28 Sep 2023 23:35:23 +0200 Subject: [PATCH 0211/1120] Fix incorrect skip check in test_backend_ps. There was a typo in `elif rcParams.get("ps.userdistiller") == "xpdf"` which effectively always returned False (the correct spelling is "ps.usedistiller", so the get() call always returned None), which made the skip check for xpdf never run. Thus, running the test on a machine without pdftops installed would eventually result in an ExecutableNotFoundError when matplotlib actually tries to call the distiller. To avoid this kind of problems (dict.get hiding typos), directly update the test-specific settings into the main rcParams, and use normal brackets to access rcParams entries, as rcParams keys are a fixed set anyways. --- lib/matplotlib/backends/backend_macosx.py | 2 +- lib/matplotlib/tests/test_backend_ps.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 1d92ec602d3b..ecf21b07aef4 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -147,7 +147,7 @@ def __init__(self, canvas, num): icon_path = str(cbook._get_data_path('images/matplotlib.pdf')) _macosx.FigureManager.set_icon(icon_path) FigureManagerBase.__init__(self, canvas, num) - self._set_window_mode(mpl.rcParams.get("macosx.window_mode", "system")) + self._set_window_mode(mpl.rcParams["macosx.window_mode"]) if self.toolbar is not None: self.toolbar.update() if mpl.is_interactive(): diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 954d0955a760..cbf33ccc5a1b 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -40,20 +40,19 @@ 'eps with usetex' ]) def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize): - if rcParams.get("ps.usedistiller") == "ghostscript": + mpl.rcParams.update(rcParams) + if mpl.rcParams["ps.usedistiller"] == "ghostscript": try: mpl._get_executable_info("gs") except mpl.ExecutableNotFoundError as exc: pytest.skip(str(exc)) - elif rcParams.get("ps.userdistiller") == "xpdf": + elif mpl.rcParams["ps.usedistiller"] == "xpdf": try: mpl._get_executable_info("gs") # Effectively checks for ps2pdf. mpl._get_executable_info("pdftops") except mpl.ExecutableNotFoundError as exc: pytest.skip(str(exc)) - mpl.rcParams.update(rcParams) - fig, ax = plt.subplots() with io.StringIO() as s_buf, io.BytesIO() as b_buf: @@ -67,9 +66,9 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize): title += " \N{MINUS SIGN}\N{EURO SIGN}" ax.set_title(title) allowable_exceptions = [] - if rcParams.get("text.usetex"): + if mpl.rcParams["text.usetex"]: allowable_exceptions.append(RuntimeError) - if rcParams.get("ps.useafm"): + if mpl.rcParams["ps.useafm"]: allowable_exceptions.append(mpl.MatplotlibDeprecationWarning) try: fig.savefig(s_buf, format=format, orientation=orientation, @@ -87,14 +86,14 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize): if format == 'ps': # Default figsize = (8, 6) inches = (576, 432) points = (203.2, 152.4) mm. # Landscape orientation will swap dimensions. - if rcParams.get("ps.usedistiller") == "xpdf": + if mpl.rcParams["ps.usedistiller"] == "xpdf": # Some versions specifically show letter/203x152, but not all, # so we can only use this simpler test. if papersize == 'figure': assert b'letter' not in s_val.lower() else: assert b'letter' in s_val.lower() - elif rcParams.get("ps.usedistiller") or rcParams.get("text.usetex"): + elif mpl.rcParams["ps.usedistiller"] or mpl.rcParams["text.usetex"]: width = b'432.0' if orientation == 'landscape' else b'576.0' wanted = (b'-dDEVICEWIDTHPOINTS=' + width if papersize == 'figure' else b'-sPAPERSIZE') From 72bf994e82553bae4c2075c57db74f44c158c872 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:33:48 +0100 Subject: [PATCH 0212/1120] FIX 2-tuple of colors in to_rgba_array --- lib/matplotlib/colors.py | 2 +- lib/matplotlib/tests/test_colors.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 584adba8eace..b9866974d8a3 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -435,7 +435,7 @@ def to_rgba_array(c, alpha=None): (n, 4) array of RGBA colors, where each channel (red, green, blue, alpha) can assume values between 0 and 1. """ - if isinstance(c, tuple) and len(c) == 2: + if isinstance(c, tuple) and len(c) == 2 and isinstance(c[1], Real): if alpha is None: c, alpha = c else: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 1c77f995fb53..139efbe17407 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1298,6 +1298,11 @@ def test_to_rgba_array_single_str(): array = mcolors.to_rgba_array("rgb") +def test_to_rgba_array_2tuple_str(): + expected = np.array([[0, 0, 0, 1], [1, 1, 1, 1]]) + assert_array_equal(mcolors.to_rgba_array(("k", "w")), expected) + + def test_to_rgba_array_alpha_array(): with pytest.raises(ValueError, match="The number of colors must match"): mcolors.to_rgba_array(np.ones((5, 3), float), alpha=np.ones((2,))) From 6245534a75f3469f2e421510299610ff0b06eb46 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 29 Sep 2023 14:16:44 +0200 Subject: [PATCH 0213/1120] Deprecate backend_ps.get_bbox_header, and split it for internal use. It's clearly an internal helper, and the two parts (the actual bbox header and the rotation command) don't really benefit from being smushed together, so make the function private and split the two parts. --- .../deprecations/26960-AL.rst | 3 ++ lib/matplotlib/backends/backend_ps.py | 36 +++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/26960-AL.rst diff --git a/doc/api/next_api_changes/deprecations/26960-AL.rst b/doc/api/next_api_changes/deprecations/26960-AL.rst new file mode 100644 index 000000000000..cbde4cbba424 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/26960-AL.rst @@ -0,0 +1,3 @@ +``backend_ps.get_bbox_header`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated, as it is considered an internal helper. diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index b59c571809d1..e1e5bd9c091b 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -9,6 +9,7 @@ from io import StringIO import itertools import logging +import math import os import pathlib import shutil @@ -915,7 +916,7 @@ def print_figure_impl(fh): print(f"%%LanguageLevel: 3\n" f"{dsc_comments}\n" f"%%Orientation: {orientation.name}\n" - f"{get_bbox_header(bbox)[0]}\n" + f"{_get_bbox_header(bbox)}\n" f"%%EndComments\n", end="", file=fh) @@ -1024,7 +1025,7 @@ def _print_figure_tex( %!PS-Adobe-3.0 EPSF-3.0 %%LanguageLevel: 3 {dsc_comments} -{get_bbox_header(bbox)[0]} +{_get_bbox_header(bbox)} %%EndComments %%BeginProlog /mpldict {len(_psDefs)} dict def @@ -1219,21 +1220,26 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): pstoeps(tmpfile) +@_api.deprecated("3.9") def get_bbox_header(lbrt, rotated=False): """ Return a postscript header string for the given bbox lbrt=(l, b, r, t). Optionally, return rotate command. """ + return _get_bbox_header(lbrt), (_get_rotate_command(lbrt) if rotated else "") + +def _get_bbox_header(lbrt): + """Return a PostScript header string for bounding box *lbrt*=(l, b, r, t).""" l, b, r, t = lbrt - if rotated: - rotate = f"{l+r:.2f} {0:.2f} translate\n90 rotate" - else: - rotate = "" - bbox_info = '%%%%BoundingBox: %d %d %d %d' % (l, b, np.ceil(r), np.ceil(t)) - hires_bbox_info = f'%%HiResBoundingBox: {l:.6f} {b:.6f} {r:.6f} {t:.6f}' + return (f"%%BoundingBox: {int(l)} {int(b)} {math.ceil(r)} {math.ceil(t)}\n" + f"%%HiResBoundingBox: {l:.6f} {b:.6f} {r:.6f} {t:.6f}") + - return '\n'.join([bbox_info, hires_bbox_info]), rotate +def _get_rotate_command(lbrt): + """Return a PostScript 90° rotation command for bounding box *lbrt*=(l, b, r, t).""" + l, b, r, t = lbrt + return f"{l+r:.2f} {0:.2f} translate\n90 rotate" def pstoeps(tmpfile, bbox=None, rotated=False): @@ -1243,12 +1249,6 @@ def pstoeps(tmpfile, bbox=None, rotated=False): None, original bbox will be used. """ - # if rotated==True, the output eps file need to be rotated - if bbox: - bbox_info, rotate = get_bbox_header(bbox, rotated=rotated) - else: - bbox_info, rotate = None, None - epsfile = tmpfile + '.eps' with open(epsfile, 'wb') as epsh, open(tmpfile, 'rb') as tmph: write = epsh.write @@ -1257,7 +1257,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): if line.startswith(b'%!PS'): write(b"%!PS-Adobe-3.0 EPSF-3.0\n") if bbox: - write(bbox_info.encode('ascii') + b'\n') + write(_get_bbox_header(bbox).encode('ascii') + b'\n') elif line.startswith(b'%%EndComments'): write(line) write(b'%%BeginProlog\n' @@ -1269,8 +1269,8 @@ def pstoeps(tmpfile, bbox=None, rotated=False): b'/setpagedevice {pop} def\n' b'%%EndProlog\n' b'%%Page 1 1\n') - if rotate: - write(rotate.encode('ascii') + b'\n') + if rotated: # The output eps file need to be rotated. + write(_get_rotate_command(bbox).encode('ascii') + b'\n') break elif bbox and line.startswith((b'%%Bound', b'%%HiResBound', b'%%DocumentMedia', b'%%Pages')): From 0334c9489a5cc38f83e279ccc2bfcffb587bd02b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 29 Sep 2023 12:03:35 +0200 Subject: [PATCH 0214/1120] Move papersize="auto" deprecation to backend_bases. This makes it easier for mplcairo (which also outputs PostScript natively) to inherit the deprecation and be able to run the matplotlib test suite both with matplotlib 3.7 (where there should be no warning) and with matplotlib 3.8 (where there should be a warning). --- lib/matplotlib/backend_bases.py | 6 ++++++ lib/matplotlib/backends/backend_ps.py | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c07ad692451e..54e974991854 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2105,6 +2105,12 @@ def print_figure( if dpi == 'figure': dpi = getattr(self.figure, '_original_dpi', self.figure.dpi) + if kwargs.get("papertype") == 'auto': + # When deprecation elapses, remove backend_ps._get_papertype & its callers. + _api.warn_deprecated( + "3.8", name="papertype='auto'", addendum="Pass an explicit paper type, " + "'figure', or omit the *papertype* argument entirely.") + # Remove the figure manager, if any, to avoid resizing the GUI widget. with cbook._setattr_cm(self, manager=None), \ self._switch_canvas_and_return_print_method(format, backend) \ diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index b59c571809d1..8d09fc1fe733 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -867,9 +867,6 @@ def _print_figure( # find the appropriate papertype width, height = self.figure.get_size_inches() if papertype == 'auto': - _api.warn_deprecated("3.8", name="papertype='auto'", - addendum="Pass an explicit paper type, 'figure', or " - "omit the *papertype* argument entirely.") papertype = _get_papertype(*orientation.swap_if_landscape((width, height))) if is_eps or papertype == 'figure': @@ -1053,9 +1050,6 @@ def _print_figure_tex( self.figure.get_size_inches()) else: if papertype == 'auto': - _api.warn_deprecated("3.8", name="papertype='auto'", - addendum="Pass an explicit paper type, or " - "omit the *papertype* argument entirely.") papertype = _get_papertype(width, height) paper_width, paper_height = papersize[papertype] From 17f4a9aabc14b20e3f76aed7bab2bdbd90429b2a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 25 Sep 2023 23:34:35 +0200 Subject: [PATCH 0215/1120] Deprecate ContourLabeler.add_label_clabeltext. We can just dispatch add_label based on the value of the internal _use_clabeltext flag. This change is also preparing for a possible future change where we always have use_clabeltext=True (which is needed for contour labels to be properly rotated after changes of axes aspect ratio, and should have no drawback except for a tiny(?) performance cost). --- .../next_api_changes/behavior/26917-AL.rst | 3 +++ .../deprecations/26917-AL.rst | 3 +++ lib/matplotlib/contour.py | 23 ++++++++----------- 3 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26917-AL.rst create mode 100644 doc/api/next_api_changes/deprecations/26917-AL.rst diff --git a/doc/api/next_api_changes/behavior/26917-AL.rst b/doc/api/next_api_changes/behavior/26917-AL.rst new file mode 100644 index 000000000000..7872caf3204d --- /dev/null +++ b/doc/api/next_api_changes/behavior/26917-AL.rst @@ -0,0 +1,3 @@ +``ContourLabeler.add_label`` now respects *use_clabeltext* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... and sets `.Text.set_transform_rotates_text` accordingly. diff --git a/doc/api/next_api_changes/deprecations/26917-AL.rst b/doc/api/next_api_changes/deprecations/26917-AL.rst new file mode 100644 index 000000000000..d3cf16f5c511 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/26917-AL.rst @@ -0,0 +1,3 @@ +``ContourLabeler.add_label_clabeltext`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index efea024dc102..941ebf7aed39 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -527,7 +527,7 @@ def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): return rotation, nlc def add_label(self, x, y, rotation, lev, cvalue): - """Add contour label without `.Text.set_transform_rotates_text`.""" + """Add a contour label, respecting whether *use_clabeltext* was set.""" data_x, data_y = self.axes.transData.inverted().transform((x, y)) t = Text( data_x, data_y, @@ -538,20 +538,21 @@ def add_label(self, x, y, rotation, lev, cvalue): color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()), fontproperties=self._label_font_props, clip_box=self.axes.bbox) + if self._use_clabeltext: + data_rotation, = self.axes.transData.inverted().transform_angles( + [rotation], [[x, y]]) + t.set(rotation=data_rotation, transform_rotates_text=True) self.labelTexts.append(t) self.labelCValues.append(cvalue) self.labelXYs.append((x, y)) # Add label to plot here - useful for manual mode label selection self.axes.add_artist(t) + @_api.deprecated("3.8", alternative="add_label") def add_label_clabeltext(self, x, y, rotation, lev, cvalue): """Add contour label with `.Text.set_transform_rotates_text`.""" - self.add_label(x, y, rotation, lev, cvalue) - # Grab the last added text, and reconfigure its rotation. - t = self.labelTexts[-1] - data_rotation, = self.axes.transData.inverted().transform_angles( - [rotation], [[x, y]]) - t.set(rotation=data_rotation, transform_rotates_text=True) + with cbook._setattr_cm(self, _use_clabeltext=True): + self.add_label(x, y, rotation, lev, cvalue) def add_label_near(self, x, y, inline=True, inline_spacing=5, transform=None): @@ -600,12 +601,6 @@ def pop_label(self, index=-1): t.remove() def labels(self, inline, inline_spacing): - - if self._use_clabeltext: - add_label = self.add_label_clabeltext - else: - add_label = self.add_label - for idx, (icon, lev, cvalue) in enumerate(zip( self.labelIndiceList, self.labelLevelList, @@ -622,7 +617,7 @@ def labels(self, inline, inline_spacing): rotation, path = self._split_path_and_get_label_rotation( subpath, idx, (x, y), label_width, inline_spacing) - add_label(x, y, rotation, lev, cvalue) # Really add label. + self.add_label(x, y, rotation, lev, cvalue) # Really add label. if inline: # If inline, add new contours additions.append(path) else: # If not adding label, keep old path From 8f52c28bcca3b5fd90c1c531929d9557304839e8 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 29 Sep 2023 10:46:29 -0400 Subject: [PATCH 0216/1120] turned send to matplotlib into a section and added some more info so it would stand out Co-authored-by: Elliott Sales de Andrade --- doc/devel/contribute.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 9b53a80ab374..aab1e73f955c 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -227,6 +227,9 @@ Maplotlib repository to your own computer, or alternatively using in-browser development environment that comes with the appropriated setup to contribute to Matplotlib. +Workflow overview +^^^^^^^^^^^^^^^^^ + A brief overview of the workflow is as follows. #. `Create an account `_ on GitHub if you do not @@ -289,8 +292,13 @@ A brief overview of the workflow is as follows. git push -u origin my-feature -Finally, go to the web page of your fork of the Matplotlib repo, and click -'Pull request' to send your changes to the maintainers for review. +Open a pull request on Matplotlib +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Finally, go to the web page of *your fork* of the Matplotlib repo, and click +**Compare & pull request** to send your changes to the maintainers for review. +The base repository is ``matplotlib/matplotlib`` and the base branch is +generally ``main``. For more guidance, see GitHub's `pull request tutorial +`_. For more detailed instructions on how to set up Matplotlib for development and best practices for contribution, see :ref:`installing_for_devs`. From 754ec4518eef8e81ebc131c81a2da475b03b7be1 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 29 Sep 2023 10:48:21 -0400 Subject: [PATCH 0217/1120] changed title level to documented leveling format --- doc/devel/contribute.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index aab1e73f955c..a9bfb0f816dd 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -304,7 +304,7 @@ For more detailed instructions on how to set up Matplotlib for development and best practices for contribution, see :ref:`installing_for_devs`. GitHub Codespaces workflows -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * If you need to open a GUI window with Matplotlib output on Codespaces, our configuration includes a `light-weight Fluxbox-based desktop @@ -386,7 +386,7 @@ This ensures that users are notified before the change will take effect and thus prevents unexpected breaking of code. Rules -~~~~~ +^^^^^ - Deprecations are targeted at the next point.release (e.g. 3.x) - Deprecated API is generally removed two point-releases after introduction @@ -399,7 +399,7 @@ Rules API consistency lead developer Introducing -~~~~~~~~~~~ +^^^^^^^^^^^ #. Announce the deprecation in a new file :file:`doc/api/next_api_changes/deprecations/99999-ABC.rst` where ``99999`` @@ -440,7 +440,7 @@ Introducing version number. Expiring -~~~~~~~~ +^^^^^^^^ #. Announce the API changes in a new file :file:`doc/api/next_api_changes/[kind]/99999-ABC.rst` where ``99999`` @@ -621,7 +621,7 @@ example, use ``_log.error('hello %s', 'world')`` rather than ``_log.error('hell {}'.format('world'))`` or ``_log.error(f'hello {s}')``. Which logging level to use? -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are five levels at which you can emit messages. From c486c95f3bb8eb0852fd7202345e635ae127c439 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 26 Sep 2023 03:18:05 -0400 Subject: [PATCH 0218/1120] clarify that path.sketch/validate_sketch is looking for string list of three elements --- lib/matplotlib/mpl-data/matplotlibrc | 16 ++++++++-------- lib/matplotlib/rcsetup.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index a097c98429d1..f606bab8c304 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -669,14 +669,14 @@ #path.snap: True # When True, rectilinear axis-aligned paths will be snapped # to the nearest pixel when certain criteria are met. # When False, paths will never be snapped. -#path.sketch: None # May be None, or a 3-tuple of the form: - # (scale, length, randomness). - # - *scale* is the amplitude of the wiggle - # perpendicular to the line (in pixels). - # - *length* is the length of the wiggle along the - # line (in pixels). - # - *randomness* is the factor by which the length is - # randomly scaled. +#path.sketch: None # May be None, or a triplet of the form: + # path.sketch: scale, length, randomness + # - *scale* is the amplitude of the wiggle + # perpendicular to the line (in pixels). + # - *length* is the length of the wiggle along the + # line (in pixels). + # - *randomness* is the factor by which the length is + # randomly scaled. #path.effects: diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index fa18677f5895..57a3859a90e7 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -562,7 +562,7 @@ def validate_sketch(s): try: return tuple(_listify_validator(validate_float, n=3)(s)) except ValueError: - raise ValueError("Expected a (scale, length, randomness) triplet") + raise ValueError("Expected a 'scale, length, randomness' triplet") def _validate_greaterthan_minushalf(s): From 36a3f7f63253a54786374f39f7e2955e8c8ee793 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 26 Sep 2023 15:54:34 -0400 Subject: [PATCH 0219/1120] added tests for the sketch param validator --- lib/matplotlib/tests/test_rcparams.py | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 515068c462d4..37dd107382e0 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -27,6 +27,7 @@ validate_int, validate_markevery, validate_stringlist, + validate_sketch, _validate_linestyle, _listify_validator) @@ -628,3 +629,38 @@ def test_rcparams_legend_loc_from_file(tmpdir, value): with mpl.rc_context(fname=rc_path): assert mpl.rcParams["legend.loc"] == value + + +@pytest.mark.parametrize("value", [(1, 2, 3), '1, 2, 3']) +def test_validate_sketch(value): + mpl.rcParams["path.sketch"] = value + assert mpl.rcParams["path.sketch"] == (1, 2, 3) + assert validate_sketch(value) == (1, 2, 3) + + +@pytest.mark.parametrize("value", [1, '1', '(1, 2, 3)']) +def test_validate_sketch_error(value): + with pytest.raises(ValueError, match="'scale, length, randomness'"): + validate_sketch(value) + with pytest.raises(ValueError, match="'scale, length, randomness'"): + mpl.rcParams["path.sketch"] = value + + +def test_rcparams_path_sketch_from_file(tmpdir): + rc_path = tmpdir.join("matplotlibrc") + rc_path.write("path.sketch: 1, 2, 3") + + with mpl.rc_context(fname=rc_path): + assert mpl.rcParams["path.sketch"] == (1, 2, 3) + + +def test_rcparams_path_sketch_from_file_error(tmpdir, caplog): + # rcParams parser doesn't read a tuple rcfile entry + rc_path = tmpdir.join("matplotlibrc") + rc_path.write("path.sketch: (1, 2, 3)") + + with mpl.rc_context(fname=rc_path): + with caplog.at_level("WARNING"): + assert mpl.rcParams['path.sketch'] is None + assert ("Expected a 'scale, length, randomness' triplet" + in caplog.text) From 3a86726bd44395c07442b17cd02231e90c75ba7d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 8 Aug 2023 14:56:37 +0200 Subject: [PATCH 0220/1120] Minor cleanups. Re: the rewriting of AsinhLocator formulas: The previous implementation added 1e-6 to the logs in the case where xs=0 in order to avoid a warning with log(0), but the result of the log was then zeroed out by multiplication by xs anyways. Instead, here we just rely on `10**log(0) = 10**-inf = 0`. --- lib/matplotlib/axis.py | 2 +- lib/matplotlib/backend_bases.py | 54 +++++++--------------- lib/matplotlib/font_manager.py | 3 +- lib/matplotlib/projections/polar.py | 26 ++++------- lib/matplotlib/quiver.py | 4 +- lib/matplotlib/ticker.py | 51 +++++++------------- lib/mpl_toolkits/axisartist/grid_finder.py | 4 +- 7 files changed, 48 insertions(+), 96 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 64aeac81c140..fa28597a9d08 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -208,7 +208,7 @@ def _apply_tickdir(self, tickdir): """Set tick direction. Valid values are 'out', 'in', 'inout'.""" # This method is responsible for updating `_pad`, and, in subclasses, # for setting the tick{1,2}line markers as well. From the user - # perspective this should always be called though _apply_params, which + # perspective this should always be called through _apply_params, which # further updates ticklabel positions using the new pads. if tickdir is None: tickdir = mpl.rcParams[f'{self.__name__}.direction'] diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c07ad692451e..484a1be4ec90 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2385,8 +2385,6 @@ def key_press_handler(event, canvas=None, toolbar=None): back-compatibility, but, if set, should always be equal to ``event.canvas.toolbar``. """ - # these bindings happen whether you are over an Axes or not - if event.key is None: return if canvas is None: @@ -2394,55 +2392,40 @@ def key_press_handler(event, canvas=None, toolbar=None): if toolbar is None: toolbar = canvas.toolbar - # Load key-mappings from rcParams. - fullscreen_keys = rcParams['keymap.fullscreen'] - home_keys = rcParams['keymap.home'] - back_keys = rcParams['keymap.back'] - forward_keys = rcParams['keymap.forward'] - pan_keys = rcParams['keymap.pan'] - zoom_keys = rcParams['keymap.zoom'] - save_keys = rcParams['keymap.save'] - quit_keys = rcParams['keymap.quit'] - quit_all_keys = rcParams['keymap.quit_all'] - grid_keys = rcParams['keymap.grid'] - grid_minor_keys = rcParams['keymap.grid_minor'] - toggle_yscale_keys = rcParams['keymap.yscale'] - toggle_xscale_keys = rcParams['keymap.xscale'] - - # toggle fullscreen mode ('f', 'ctrl + f') - if event.key in fullscreen_keys: + # toggle fullscreen mode (default key 'f', 'ctrl + f') + if event.key in rcParams['keymap.fullscreen']: try: canvas.manager.full_screen_toggle() except AttributeError: pass # quit the figure (default key 'ctrl+w') - if event.key in quit_keys: + if event.key in rcParams['keymap.quit']: Gcf.destroy_fig(canvas.figure) - if event.key in quit_all_keys: + if event.key in rcParams['keymap.quit_all']: Gcf.destroy_all() if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') - if event.key in home_keys: + if event.key in rcParams['keymap.home']: toolbar.home() # forward / backward keys to enable left handed quick navigation # (default key for backward: 'left', 'backspace' and 'c') - elif event.key in back_keys: + elif event.key in rcParams['keymap.back']: toolbar.back() # (default key for forward: 'right' and 'v') - elif event.key in forward_keys: + elif event.key in rcParams['keymap.forward']: toolbar.forward() # pan mnemonic (default key 'p') - elif event.key in pan_keys: + elif event.key in rcParams['keymap.pan']: toolbar.pan() toolbar._update_cursor(event) # zoom mnemonic (default key 'o') - elif event.key in zoom_keys: + elif event.key in rcParams['keymap.zoom']: toolbar.zoom() toolbar._update_cursor(event) # saving current figure (default key 's') - elif event.key in save_keys: + elif event.key in rcParams['keymap.save']: toolbar.save_figure() if event.inaxes is None: @@ -2452,19 +2435,16 @@ def key_press_handler(event, canvas=None, toolbar=None): def _get_uniform_gridstate(ticks): # Return True/False if all grid lines are on or off, None if they are # not all in the same state. - if all(tick.gridline.get_visible() for tick in ticks): - return True - elif not any(tick.gridline.get_visible() for tick in ticks): - return False - else: - return None + return (True if all(tick.gridline.get_visible() for tick in ticks) else + False if not any(tick.gridline.get_visible() for tick in ticks) else + None) ax = event.inaxes # toggle major grids in current Axes (default key 'g') # Both here and below (for 'G'), we do nothing if *any* grid (major or # minor, x or y) is not in a uniform state, to avoid messing up user # customization. - if (event.key in grid_keys + if (event.key in rcParams['keymap.grid'] # Exclude minor grids not in a uniform state. and None not in [_get_uniform_gridstate(ax.xaxis.minorTicks), _get_uniform_gridstate(ax.yaxis.minorTicks)]): @@ -2483,7 +2463,7 @@ def _get_uniform_gridstate(ticks): ax.grid(y_state, which="major" if y_state else "both", axis="y") canvas.draw_idle() # toggle major and minor grids in current Axes (default key 'G') - if (event.key in grid_minor_keys + if (event.key in rcParams['keymap.grid_minor'] # Exclude major grids not in a uniform state. and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks), _get_uniform_gridstate(ax.yaxis.majorTicks)]): @@ -2501,7 +2481,7 @@ def _get_uniform_gridstate(ticks): ax.grid(y_state, which="both", axis="y") canvas.draw_idle() # toggle scaling of y-axes between 'log and 'linear' (default key 'l') - elif event.key in toggle_yscale_keys: + elif event.key in rcParams['keymap.yscale']: scale = ax.get_yscale() if scale == 'log': ax.set_yscale('linear') @@ -2514,7 +2494,7 @@ def _get_uniform_gridstate(ticks): ax.set_yscale('linear') ax.figure.canvas.draw_idle() # toggle scaling of x-axes between 'log and 'linear' (default key 'k') - elif event.key in toggle_xscale_keys: + elif event.key in rcParams['keymap.xscale']: scalex = ax.get_xscale() if scalex == 'log': ax.set_xscale('linear') diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index a91ca4ba45df..68b22ccf7df1 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1448,7 +1448,8 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, # actually raised. return _ExceptionProxy( ValueError, - f"Failed to find font {prop}, and fallback to the default font was disabled" + f"Failed to find font {prop}, and fallback to the default font was " + f"disabled" ) else: _log.debug('findfont: Matching %s to %s (%r) with score of %f.', diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 0bff320e5728..f6fa0ea7b982 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -524,24 +524,16 @@ def get_matrix(self): if self._invalid: if self.mode == 'rlabel': angle = ( - np.deg2rad(self.axes.get_rlabel_position()) * - self.axes.get_theta_direction() + - self.axes.get_theta_offset() + np.deg2rad(self.axes.get_rlabel_position() + * self.axes.get_theta_direction()) + + self.axes.get_theta_offset() + - np.pi / 2 ) - else: - if self.mode == 'min': - angle = self.axes._realViewLim.xmin - elif self.mode == 'max': - angle = self.axes._realViewLim.xmax - - if self.mode in ('rlabel', 'min'): - padx = np.cos(angle - np.pi / 2) - pady = np.sin(angle - np.pi / 2) - else: - padx = np.cos(angle + np.pi / 2) - pady = np.sin(angle + np.pi / 2) - - self._t = (self.pad * padx / 72, self.pad * pady / 72) + elif self.mode == 'min': + angle = self.axes._realViewLim.xmin - np.pi / 2 + elif self.mode == 'max': + angle = self.axes._realViewLim.xmax + np.pi / 2 + self._t = (self.pad * np.cos(angle) / 72, self.pad * np.sin(angle) / 72) return super().get_matrix() diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index 52f56deb40c0..3089af5757dd 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -72,7 +72,7 @@ of these components (in data or in screen space) depends on *angles*. *U* and *V* must have the same number of elements, matching the number of - arrow locations in *X*, *Y*. *U* and *V* may be masked. Locations masked + arrow locations in *X*, *Y*. *U* and *V* may be masked. Locations masked in any of *U*, *V*, and *C* will not be drawn. C : 1D or 2D array-like, optional @@ -90,7 +90,7 @@ symbolize a quantity that is not based on *X*, *Y* data coordinates. If *U* == *V* the orientation of the arrow on the plot is 45 degrees - counter-clockwise from the horizontal axis (positive to the right). + counter-clockwise from the horizontal axis (positive to the right). - 'xy': Arrow direction in data coordinates, i.e. the arrows point from (x, y) to (x+u, y+v). Use this e.g. for plotting a gradient field. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 43a6126f6fcf..41114aafbf3e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2661,51 +2661,32 @@ def __call__(self): return self.tick_values(vmin, vmax) def tick_values(self, vmin, vmax): - # Construct a set of "on-screen" locations - # that are uniformly spaced: + # Construct a set of uniformly-spaced "on-screen" locations. ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax]) - / self.linear_width) + / self.linear_width) ys = np.linspace(ymin, ymax, self.numticks) - zero_dev = np.abs(ys / (ymax - ymin)) - if (ymin * ymax) < 0: - # Ensure that the zero tick-mark is included, - # if the axis straddles zero + zero_dev = abs(ys / (ymax - ymin)) + if ymin * ymax < 0: + # Ensure that the zero tick-mark is included, if the axis straddles zero. ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0]) # Transform the "on-screen" grid to the data space: xs = self.linear_width * np.sinh(ys / self.linear_width) zero_xs = (ys == 0) - # Round the data-space values to be intuitive base-n numbers, - # keeping track of positive and negative values separately, - # but giving careful treatment to the zero value: - if self.base > 1: - log_base = math.log(self.base) - powers = ( - np.where(zero_xs, 0, np.sign(xs)) * - np.power(self.base, - np.where(zero_xs, 0.0, - np.floor(np.log(np.abs(xs) + zero_xs*1e-6) - / log_base))) - ) - if self.subs: - qs = np.outer(powers, self.subs).flatten() - else: - qs = powers - else: - powers = ( - np.where(xs >= 0, 1, -1) * - np.power(10, np.where(zero_xs, 0.0, - np.floor(np.log10(np.abs(xs) - + zero_xs*1e-6)))) - ) - qs = powers * np.round(xs / powers) + # Round the data-space values to be intuitive base-n numbers, keeping track of + # positive and negative values separately and carefully treating the zero value. + with np.errstate(divide="ignore"): # base ** log(0) = base ** -inf = 0. + if self.base > 1: + pows = (np.sign(xs) + * self.base ** np.floor(np.log(abs(xs)) / math.log(self.base))) + qs = np.outer(pows, self.subs).flatten() if self.subs else pows + else: # No need to adjust sign(pows), as it cancels out when computing qs. + pows = np.where(zero_xs, 1, 10**np.floor(np.log10(abs(xs)))) + qs = pows * np.round(xs / pows) ticks = np.array(sorted(set(qs))) - if len(ticks) >= 2: - return ticks - else: - return np.linspace(vmin, vmax, self.numticks) + return ticks if len(ticks) >= 2 else np.linspace(vmin, vmax, self.numticks) class LogitLocator(MaxNLocator): diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index 18a48b8e8ccf..ff61887f9ef2 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -188,9 +188,7 @@ def get_grid_info(self, x1, y1, x2, y2): lon_min, lon_max, lat_min, lat_max) - ddx = (x2-x1)*1.e-10 - ddy = (y2-y1)*1.e-10 - bb = Bbox.from_extents(x1-ddx, y1-ddy, x2+ddx, y2+ddy) + bb = Bbox.from_extents(x1, y1, x2, y2).expanded(1 + 2e-10, 1 + 2e-10) grid_info = { "extremes": extremes, From df8eb6bbe4adad645a2d88c17bc02dfef530c9c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:34:57 +0000 Subject: [PATCH 0221/1120] Bump pypa/cibuildwheel from 2.16.0 to 2.16.1 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.16.0 to 2.16.1. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502...7da7df1efc530f07d1945c00934b8cfd34be0d50) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 5788c3e0bc0a..8c76303cbb85 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -136,7 +136,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 + uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -158,7 +158,7 @@ jobs: pip install --pre "numpy>=1.25" - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 + uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -166,7 +166,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 + uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -174,7 +174,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 + uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -182,7 +182,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 # v2.16.0 + uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From 5bf7e33ea01e320497cd54becdcf4ceb800dea55 Mon Sep 17 00:00:00 2001 From: vicky6 Date: Tue, 3 Oct 2023 02:38:37 +0530 Subject: [PATCH 0222/1120] deprecated api tri (#26909) * deprecated api tri * updated deprecated api tri * updated deprecated api tri * updated deprecated api tri * updated deprecated api tri * pre-commit.ci autofix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- doc/api/next_api_changes/removals/26909-VV.rst | 4 ++++ lib/matplotlib/tri/triangulation.py | 9 --------- lib/matplotlib/tri/tricontour.py | 9 --------- lib/matplotlib/tri/trifinder.py | 9 --------- lib/matplotlib/tri/triinterpolate.py | 9 --------- lib/matplotlib/tri/tripcolor.py | 9 --------- lib/matplotlib/tri/triplot.py | 9 --------- lib/matplotlib/tri/trirefine.py | 9 --------- lib/matplotlib/tri/tritools.py | 9 --------- 9 files changed, 4 insertions(+), 72 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26909-VV.rst delete mode 100644 lib/matplotlib/tri/triangulation.py delete mode 100644 lib/matplotlib/tri/tricontour.py delete mode 100644 lib/matplotlib/tri/trifinder.py delete mode 100644 lib/matplotlib/tri/triinterpolate.py delete mode 100644 lib/matplotlib/tri/tripcolor.py delete mode 100644 lib/matplotlib/tri/triplot.py delete mode 100644 lib/matplotlib/tri/trirefine.py delete mode 100644 lib/matplotlib/tri/tritools.py diff --git a/doc/api/next_api_changes/removals/26909-VV.rst b/doc/api/next_api_changes/removals/26909-VV.rst new file mode 100644 index 000000000000..bdb815eed322 --- /dev/null +++ b/doc/api/next_api_changes/removals/26909-VV.rst @@ -0,0 +1,4 @@ +``matplotlib.tri`` submodules are removed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``matplotlib.tri.*`` submodules are removed. All functionality is +available in ``matplotlib.tri`` directly and should be imported from there. diff --git a/lib/matplotlib/tri/triangulation.py b/lib/matplotlib/tri/triangulation.py deleted file mode 100644 index c48b09b280ff..000000000000 --- a/lib/matplotlib/tri/triangulation.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._triangulation import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/tricontour.py b/lib/matplotlib/tri/tricontour.py deleted file mode 100644 index 37406451d376..000000000000 --- a/lib/matplotlib/tri/tricontour.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._tricontour import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/trifinder.py b/lib/matplotlib/tri/trifinder.py deleted file mode 100644 index 1aff5c9d3280..000000000000 --- a/lib/matplotlib/tri/trifinder.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._trifinder import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/triinterpolate.py b/lib/matplotlib/tri/triinterpolate.py deleted file mode 100644 index 3112bd38e6c6..000000000000 --- a/lib/matplotlib/tri/triinterpolate.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._triinterpolate import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/tripcolor.py b/lib/matplotlib/tri/tripcolor.py deleted file mode 100644 index 0da87891810d..000000000000 --- a/lib/matplotlib/tri/tripcolor.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._tripcolor import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/triplot.py b/lib/matplotlib/tri/triplot.py deleted file mode 100644 index 7c012b1a59e7..000000000000 --- a/lib/matplotlib/tri/triplot.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._triplot import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/trirefine.py b/lib/matplotlib/tri/trirefine.py deleted file mode 100644 index 6f22f9e8d203..000000000000 --- a/lib/matplotlib/tri/trirefine.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._trirefine import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") diff --git a/lib/matplotlib/tri/tritools.py b/lib/matplotlib/tri/tritools.py deleted file mode 100644 index 9c6839ca2049..000000000000 --- a/lib/matplotlib/tri/tritools.py +++ /dev/null @@ -1,9 +0,0 @@ -from ._tritools import * # noqa: F401, F403 -from matplotlib import _api - - -_api.warn_deprecated( - "3.7", - message=f"Importing {__name__} was deprecated in Matplotlib 3.7 and will " - f"be removed two minor releases later. All functionality is " - f"available via the top-level module matplotlib.tri") From eeda7055df59d07a5b10c491d1c93009a10e7bcb Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 28 Sep 2023 01:31:24 -0400 Subject: [PATCH 0223/1120] support tuple input for sketch Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/mpl-data/matplotlibrc | 4 ++-- lib/matplotlib/rcsetup.py | 9 ++++++--- lib/matplotlib/tests/test_rcparams.py | 26 +++++++------------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index f606bab8c304..9bd8a622092e 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -669,8 +669,8 @@ #path.snap: True # When True, rectilinear axis-aligned paths will be snapped # to the nearest pixel when certain criteria are met. # When False, paths will never be snapped. -#path.sketch: None # May be None, or a triplet of the form: - # path.sketch: scale, length, randomness +#path.sketch: None # May be None, or a tuple of the form: + # path.sketch: (scale, length, randomness) # - *scale* is the amplitude of the wiggle # perpendicular to the line (in pixels). # - *length* is the length of the wiggle along the diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 57a3859a90e7..38d4606024d3 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -555,14 +555,17 @@ def validate_bbox(s): def validate_sketch(s): + if isinstance(s, str): - s = s.lower() + s = s.lower().strip() + if s.startswith("(") and s.endswith(")"): + s = s[1:-1] if s == 'none' or s is None: return None try: return tuple(_listify_validator(validate_float, n=3)(s)) - except ValueError: - raise ValueError("Expected a 'scale, length, randomness' triplet") + except ValueError as exc: + raise ValueError("Expected a (scale, length, randomness) tuple") from exc def _validate_greaterthan_minushalf(s): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 37dd107382e0..65cd823f13a9 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -631,36 +631,24 @@ def test_rcparams_legend_loc_from_file(tmpdir, value): assert mpl.rcParams["legend.loc"] == value -@pytest.mark.parametrize("value", [(1, 2, 3), '1, 2, 3']) +@pytest.mark.parametrize("value", [(1, 2, 3), '1, 2, 3', '(1, 2, 3)']) def test_validate_sketch(value): mpl.rcParams["path.sketch"] = value assert mpl.rcParams["path.sketch"] == (1, 2, 3) assert validate_sketch(value) == (1, 2, 3) -@pytest.mark.parametrize("value", [1, '1', '(1, 2, 3)']) +@pytest.mark.parametrize("value", [1, '1', '1 2 3']) def test_validate_sketch_error(value): - with pytest.raises(ValueError, match="'scale, length, randomness'"): + with pytest.raises(ValueError, match="scale, length, randomness"): validate_sketch(value) - with pytest.raises(ValueError, match="'scale, length, randomness'"): + with pytest.raises(ValueError, match="scale, length, randomness"): mpl.rcParams["path.sketch"] = value -def test_rcparams_path_sketch_from_file(tmpdir): +@pytest.mark.parametrize("value", ['1, 2, 3', '(1,2,3)']) +def test_rcparams_path_sketch_from_file(tmpdir, value): rc_path = tmpdir.join("matplotlibrc") - rc_path.write("path.sketch: 1, 2, 3") - + rc_path.write(f"path.sketch: {value}") with mpl.rc_context(fname=rc_path): assert mpl.rcParams["path.sketch"] == (1, 2, 3) - - -def test_rcparams_path_sketch_from_file_error(tmpdir, caplog): - # rcParams parser doesn't read a tuple rcfile entry - rc_path = tmpdir.join("matplotlibrc") - rc_path.write("path.sketch: (1, 2, 3)") - - with mpl.rc_context(fname=rc_path): - with caplog.at_level("WARNING"): - assert mpl.rcParams['path.sketch'] is None - assert ("Expected a 'scale, length, randomness' triplet" - in caplog.text) From 06e47316ed527d10be9199774e43d55bcedc527d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 2 Oct 2023 11:37:19 -0500 Subject: [PATCH 0224/1120] Switch to tempdirectory for windows support and autocleanup --- tools/stubtest.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/stubtest.py b/tools/stubtest.py index 71de6bda20c8..7c93d2dae157 100644 --- a/tools/stubtest.py +++ b/tools/stubtest.py @@ -52,25 +52,25 @@ def visit_ClassDef(self, node): self.visit(child) -with tempfile.NamedTemporaryFile("wt", delete=False) as f: - for path in mpl.glob("**/*.py"): - v = Visitor(path, f) - tree = ast.parse(path.read_text()) +with tempfile.TemporaryDirectory() as d: + p = pathlib.Path(d) / "allowlist.txt" + with p.open("wt") as f: + for path in mpl.glob("**/*.py"): + v = Visitor(path, f) + tree = ast.parse(path.read_text()) - # Assign parents to tree so they can be backtraced - for node in ast.walk(tree): - for child in ast.iter_child_nodes(node): - child.parent = node + # Assign parents to tree so they can be backtraced + for node in ast.walk(tree): + for child in ast.iter_child_nodes(node): + child.parent = node - v.visit(tree) - f.flush() - f.close() + v.visit(tree) proc = subprocess.run( [ "stubtest", "--mypy-config-file=pyproject.toml", "--allowlist=ci/mypy-stubtest-allowlist.txt", - f"--allowlist={f.name}", + f"--allowlist={p}", "matplotlib", ], cwd=root, From de10600d7b8893d09264a2899a9a435a4f3af3b8 Mon Sep 17 00:00:00 2001 From: iamfaham Date: Wed, 4 Oct 2023 01:01:22 +0530 Subject: [PATCH 0225/1120] Reformatted docs under galleries/tutorials and galleries/users_explain/toolkits --- galleries/tutorials/index.rst | 12 ++++++------ galleries/users_explain/toolkits/axes_grid.rst | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/galleries/tutorials/index.rst b/galleries/tutorials/index.rst index e51c35a214cd..ace37dcb6f57 100644 --- a/galleries/tutorials/index.rst +++ b/galleries/tutorials/index.rst @@ -118,14 +118,14 @@ User guide tutorials Many of our tutorials were moved from this section to :ref:`users-guide-index`: Introductory -~~~~~~~~~~~~ +^^^^^^^^^^^^ - :ref:`quick_start` - :ref:`customizing` - :ref:`animations` Intermediate -~~~~~~~~~~~~ +^^^^^^^^^^^^ - :ref:`legend_guide` - :ref:`color_cycle` @@ -136,7 +136,7 @@ Intermediate - :ref:`imshow_extent` Advanced -~~~~~~~~ +^^^^^^^^ - :ref:`blitting` - :ref:`paths` @@ -144,16 +144,16 @@ Advanced - :ref:`transforms_tutorial` Colors -~~~~~~ +^^^^^^ See :ref:`tutorials-colors`. Text -~~~~ +^^^^ See :ref:`tutorials-text`. Toolkits -~~~~~~~~ +^^^^^^^^ See :ref:`tutorials-toolkits`. diff --git a/galleries/users_explain/toolkits/axes_grid.rst b/galleries/users_explain/toolkits/axes_grid.rst index a9c39bd55b10..ba37c4cf7d78 100644 --- a/galleries/users_explain/toolkits/axes_grid.rst +++ b/galleries/users_explain/toolkits/axes_grid.rst @@ -86,7 +86,7 @@ colorbar whose height (or width) is in sync with the main axes :align: center scatter_hist.py with AxesDivider -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :doc:`/gallery/lines_bars_and_markers/scatter_hist` example can be rewritten using `~.axes_grid1.axes_divider.make_axes_locatable`:: @@ -141,14 +141,14 @@ parasite axes. To create a host axes, you may use ``host_subplot`` or ``host_axes`` command. Example 1: twinx -~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ .. figure:: /gallery/axes_grid1/images/sphx_glr_parasite_simple_001.png :target: /gallery/axes_grid1/parasite_simple.html :align: center Example 2: twin -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ ``twin`` without a transform argument assumes that the parasite axes has the same data transform as the host. This can be useful when you want the From 6665b26c72e2b1ab8d4a3844dc15751b74af5462 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 7 Sep 2022 18:36:07 +0200 Subject: [PATCH 0226/1120] Start transitioning to pyproject.toml --- pyproject.toml | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 80 +-------------------------------------------- 2 files changed, 89 insertions(+), 79 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4d9ccce2b290..423d6352ae8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,73 @@ +[project] +name = "matplotlib" +authors = [ + {email = "matplotlib-users@python.org"}, + {name = "John D. Hunter, Michael Droettboom"} +] +description = "Python plotting package" +readme = "README.md" +license = { file = "LICENSE/LICENSE" } +dynamic = ["version"] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Matplotlib", + "Intended Audience :: Science/Research", + "Intended Audience :: Education", + "License :: OSI Approved :: Python Software Foundation License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Visualization", +] + +# When updating the list of dependencies, add an api_changes/development +# entry and also update the following places: +# - lib/matplotlib/__init__.py (matplotlib._check_versions()) +# - requirements/testing/minver.txt +# - doc/devel/dependencies.rst +# - .github/workflows/tests.yml +# - environment.yml +dependencies = [ + "contourpy >= 1.0.1", + "cycler >= 0.10", + "fonttools >= 4.22.0", + "kiwisolver >= 1.3.1", + "numpy >= 1.21", + "packaging >= 20.0", + "pillow >= 8", + "pyparsing >= 2.3.1", + "python-dateutil >= 2.7", + "setuptools >= 64", + "setuptools_scm >= 7", + "importlib-resources >= 3.2.0; python_version < '3.10'", +] +requires-python = ">=3.9" + +[project.optional-dependencies] +# Should be a copy of the build dependencies below. +dev = [ + "certifi>=2020.06.20", + "numpy>=1.25", + "pybind11>=2.6", + "setuptools>=64", + "setuptools_scm>=7", +] + +[project.urls] +"Homepage" = "https://matplotlib.org" +"Download" = "https://matplotlib.org/stable/users/installing/index.html" +"Documentation" = "https://matplotlib.org" +"Source Code" = "https://github.com/matplotlib/matplotlib" +"Bug Tracker" = "https://github.com/matplotlib/matplotlib/issues" +"Forum" = "https://discourse.matplotlib.org/" +"Donate" = "https://numfocus.org/donate-to-matplotlib" + [build-system] build-backend = "setuptools.build_meta" +# Also keep in sync with optional dependencies above. requires = [ "certifi>=2020.06.20", "numpy>=1.25", @@ -8,6 +76,26 @@ requires = [ "setuptools_scm>=7", ] +[tool.setuptools] +platforms = ["any"] +py-modules = ["pylab"] +license-files = ["LICENSE/*"] +namespace-packages = ["mpl_toolkits"] + +[tool.setuptools.packages.find] +where = ["lib"] +include = ["matplotlib*", "mpl_toolkits*"] +exclude = [ + "matplotlib.tests*", + "mpl_toolkits.axes_grid1.tests*", + "mpl_toolkits.axisartist.tests*", + "mpl_toolkits.mplot3d.tests*", +] +namespaces = true + +[tool.setuptools.exclude-package-data] +"*" = ["*.png", "*.svg"] + [tool.setuptools_scm] version_scheme = "release-branch-semver" local_scheme = "node-and-date" diff --git a/setup.py b/setup.py index 899021a83254..66698a3aed6c 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ import shutil import subprocess -from setuptools import setup, find_namespace_packages, Distribution, Extension +from setuptools import setup, Distribution, Extension import setuptools.command.build_ext import setuptools.command.build_py import setuptools.command.sdist @@ -277,89 +277,11 @@ def make_release_tree(self, base_dir, files): package_data[key] = list(set(val + package_data[key])) setup( # Finally, pass this all along to setuptools to do the heavy lifting. - name="matplotlib", - description="Python plotting package", - author="John D. Hunter, Michael Droettboom", - author_email="matplotlib-users@python.org", - url="https://matplotlib.org", - download_url="https://matplotlib.org/stable/users/installing/index.html", - project_urls={ - 'Documentation': 'https://matplotlib.org', - 'Source Code': 'https://github.com/matplotlib/matplotlib', - 'Bug Tracker': 'https://github.com/matplotlib/matplotlib/issues', - 'Forum': 'https://discourse.matplotlib.org/', - 'Donate': 'https://numfocus.org/donate-to-matplotlib' - }, - long_description=Path("README.md").read_text(encoding="utf-8"), - long_description_content_type="text/markdown", - license="PSF", - platforms="any", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Matplotlib', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Education', - 'License :: OSI Approved :: Python Software Foundation License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Topic :: Scientific/Engineering :: Visualization', - ], - - package_dir={"": "lib"}, - packages=find_namespace_packages( - where="lib", - exclude=["*baseline_images*", "*tinypages*", "*mpl-data*", "*web_backend*"], - ), - py_modules=["pylab"], # Dummy extension to trigger build_ext, which will swap it out with # real extensions that can depend on numpy for the build. ext_modules=[Extension("", [])], package_data=package_data, - python_requires='>={}'.format('.'.join(str(n) for n in py_min_version)), - # When updating the list of dependencies, add an api_changes/development - # entry and also update the following places: - # - lib/matplotlib/__init__.py (matplotlib._check_versions()) - # - requirements/testing/minver.txt - # - doc/devel/dependencies.rst - # - .github/workflows/tests.yml - # - environment.yml - install_requires=[ - "contourpy>=1.0.1", - "cycler>=0.10", - "fonttools>=4.22.0", - "kiwisolver>=1.3.1", - "numpy>=1.21", - "packaging>=20.0", - "pillow>=8", - "pyparsing>=2.3.1", - "python-dateutil>=2.7", - ] + ( - # Installing from a git checkout that is not producing a wheel. - # setuptools_scm warns with older setuptools, which turns into errors for our - # test suite. However setuptools_scm does not themselves pin the version of - # setuptools. - ["setuptools_scm>=7", "setuptools>=64"] if ( - Path(__file__).with_name(".git").exists() and - os.environ.get("CIBUILDWHEEL", "0") != "1" - ) else [] - ), - extras_require={ - ':python_version<"3.10"': [ - "importlib-resources>=3.2.0", - ], - }, - use_scm_version={ - "version_scheme": "release-branch-semver", - "local_scheme": "node-and-date", - "write_to": "lib/matplotlib/_version.py", - "parentdir_prefix_version": "matplotlib-", - "fallback_version": "0.0+UNKNOWN", - }, cmdclass={ "build_ext": BuildExtraLibraries, "build_py": BuildPy, From 044a5e50093a9572e72ca0a5aa2424f55d902aed Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 24 Aug 2023 03:28:10 -0400 Subject: [PATCH 0227/1120] BLD: Use Meson to build extensions --- .circleci/config.yml | 7 +- .gitignore | 3 + extern/agg24-svn/meson.build | 22 + extern/meson.build | 41 + extern/ttconv/meson.build | 14 + meson.build | 28 + meson_options.txt | 17 + src/_c_internal_utils.c | 6 + src/_tkagg.cpp | 7 + src/_ttconv.cpp | 2 +- src/meson.build | 191 ++++ src/tri/_tri.cpp | 10 +- subprojects/freetype-2.11.1.wrap | 7 + subprojects/freetype-2.6.1.wrap | 9 + .../freetype-2.6.1-meson/LICENSE.build | 19 + .../builds/unix/ftconfig.h.in | 498 ++++++++++ .../include/freetype/config/ftoption.h.in | 886 ++++++++++++++++++ .../freetype-2.6.1-meson/meson.build | 187 ++++ .../packagefiles/qhull-2020.2/meson.build | 31 + subprojects/qhull.wrap | 9 + 20 files changed, 1982 insertions(+), 12 deletions(-) create mode 100644 extern/agg24-svn/meson.build create mode 100644 extern/meson.build create mode 100644 extern/ttconv/meson.build create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 src/meson.build create mode 100644 subprojects/freetype-2.11.1.wrap create mode 100644 subprojects/freetype-2.6.1.wrap create mode 100644 subprojects/packagefiles/freetype-2.6.1-meson/LICENSE.build create mode 100644 subprojects/packagefiles/freetype-2.6.1-meson/builds/unix/ftconfig.h.in create mode 100644 subprojects/packagefiles/freetype-2.6.1-meson/include/freetype/config/ftoption.h.in create mode 100644 subprojects/packagefiles/freetype-2.6.1-meson/meson.build create mode 100644 subprojects/packagefiles/qhull-2020.2/meson.build create mode 100644 subprojects/qhull.wrap diff --git a/.circleci/config.yml b/.circleci/config.yml index 60a1e8211e60..680798abe5fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -117,12 +117,9 @@ commands: python -m pip install --user -ve . fi - save_cache: - key: build-deps-1 + key: build-deps-2 paths: - # FreeType 2.6.1 tarball. - - ~/.cache/matplotlib/0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014 - # Qhull 2020.2 tarball. - - ~/.cache/matplotlib/b5c2d7eb833278881b952c8a52d20179eab87766b00b865000469a45c1838b7e + - subprojects/packagecache doc-build: steps: diff --git a/.gitignore b/.gitignore index 9c6bb2263c99..a76efc1cd47a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ pip-wheel-metadata/* mplsetup.cfg # generated by setuptools_scm lib/matplotlib/_version.py +# build subproject files +subprojects/*/ +!subprojects/packagefiles/ # OS generated files # ###################### diff --git a/extern/agg24-svn/meson.build b/extern/agg24-svn/meson.build new file mode 100644 index 000000000000..a1c088423cb8 --- /dev/null +++ b/extern/agg24-svn/meson.build @@ -0,0 +1,22 @@ +# We need a patched Agg not available elsewhere, so always use the vendored +# version. + +agg_incdir = include_directories('include') + +agg_lib = static_library('agg', + 'src/agg_bezier_arc.cpp', + 'src/agg_curves.cpp', + 'src/agg_image_filters.cpp', + 'src/agg_trans_affine.cpp', + 'src/agg_vcgen_contour.cpp', + 'src/agg_vcgen_dash.cpp', + 'src/agg_vcgen_stroke.cpp', + 'src/agg_vpgen_segmentator.cpp', + include_directories : agg_incdir, + gnu_symbol_visibility: 'inlineshidden', +) + +agg_dep = declare_dependency( + include_directories: agg_incdir, + link_with: agg_lib, +) diff --git a/extern/meson.build b/extern/meson.build new file mode 100644 index 000000000000..af809ce2531d --- /dev/null +++ b/extern/meson.build @@ -0,0 +1,41 @@ +# Bundled code. +subdir('agg24-svn') +subdir('ttconv') + +# External code. + +# FreeType 2.3 has libtool version 9.11.3 as can be checked from the tarball. +# For FreeType>=2.4, there is a conversion table in docs/VERSIONS.txt in the +# FreeType source tree. +if get_option('system-freetype') + freetype_dep = dependency('freetype2', version: '>=9.11.3') +else + # This is the version of FreeType to use when building a local version. It + # must match the value in `lib/matplotlib.__init__.py`. Also update the docs + # in `docs/devel/dependencies.rst`. Bump the cache key in + # `.circleci/config.yml` when changing requirements. + TESTING_VERSION_OF_FREETYPE = '2.6.1' + if host_machine.system() == 'windows' and host_machine.cpu_family() == 'aarch64' + # Older versions of freetype are not supported for win/arm64 + # Matplotlib tests will not pass + LOCAL_FREETYPE_VERSION = '2.11.1' + else + LOCAL_FREETYPE_VERSION = TESTING_VERSION_OF_FREETYPE + endif + + freetype_proj = subproject( + f'freetype-@LOCAL_FREETYPE_VERSION@', + default_options: ['default_library=static']) + freetype_dep = freetype_proj.get_variable('freetype_dep') +endif + +if get_option('system-qhull') + qhull_dep = dependency('qhull_r', version: '>=8.0.2', required: false) + if not qhull_dep.found() + cc.check_header('libqhull_r/qhull_ra.h', required: true) + qhull_dep = cc.find_library('qhull_r') + endif +else + qhull_proj = subproject('qhull') + qhull_dep = qhull_proj.get_variable('qhull_dep') +endif diff --git a/extern/ttconv/meson.build b/extern/ttconv/meson.build new file mode 100644 index 000000000000..939eb3069c43 --- /dev/null +++ b/extern/ttconv/meson.build @@ -0,0 +1,14 @@ +ttconv_lib = static_library('ttconv', + 'pprdrv_tt2.cpp', + 'pprdrv_tt.cpp', + 'ttutil.cpp', + 'pprdrv.h', + 'truetype.h', + dependencies: [py3_dep], + gnu_symbol_visibility: 'inlineshidden', +) + +ttconv_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: ttconv_lib, +) diff --git a/meson.build b/meson.build new file mode 100644 index 000000000000..c294eea2a678 --- /dev/null +++ b/meson.build @@ -0,0 +1,28 @@ +project( + 'matplotlib', + 'c', 'cpp', + version: '3.9.0.dev0', + # qt_editor backend is MIT + # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 + # Carlogo, STIX and Computer Modern is OFL + # DejaVu is Bitstream Vera and Public Domain + license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', + default_options: [ + 'b_lto=true', + 'cpp_std=c++11', + 'auto_features=disabled', # Force FreeType to avoid extra dependencies. + ], +) + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +# https://mesonbuild.com/Python-module.html +py_mod = import('python') +py3 = py_mod.find_installation() +py3_dep = py3.dependency() + +pybind11_dep = dependency('pybind11', version: '>=2.6') + +subdir('extern') +subdir('src') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000000..0b10c331ae48 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,17 @@ +# By default, Matplotlib downloads and builds its own copies of FreeType and of +# Qhull. You may set the following to True to instead link against a system +# FreeType/Qhull. As an exception, Matplotlib defaults to the system version +# of FreeType on AIX. +option('system-freetype', type: 'boolean', value: false, + description: 'Build against system version of FreeType') +option('system-qhull', type: 'boolean', value: false, + description: 'Build against system version of Qhull') + +# Some of Matplotlib's components are optional: the MacOSX backend (installed +# by default on MacOSX; requires the Cocoa headers included with XCode), and +# the test data (i.e., the baseline image files; not installed by default). You +# can control whether they are installed by uncommenting the following lines. +# Note that the MacOSX backend is never built on Linux or Windows, regardless +# of the config value. +option('macosx', type: 'boolean', value: true, + description: 'Enable MacOSX backend (requires Cocoa)') diff --git a/src/_c_internal_utils.c b/src/_c_internal_utils.c index f1bd22a42c54..49e5057f1349 100644 --- a/src/_c_internal_utils.c +++ b/src/_c_internal_utils.c @@ -1,3 +1,9 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +// Windows 10, for latest HiDPI API support. +#define WINVER 0x0A00 +#define _WIN32_WINNT 0x0A00 +#endif #define PY_SSIZE_T_CLEAN #include #ifdef __linux__ diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 5c36b3f07f50..a967845707d4 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -9,6 +9,13 @@ // rewritten, we have removed the PIL licensing information. If you want PIL, // you can get it at https://python-pillow.org/ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +// Windows 8.1 +#define WINVER 0x0603 +#define _WIN32_WINNT 0x0603 +#endif + #define PY_SSIZE_T_CLEAN #include diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp index 72fdfba6961d..51c2531bed99 100644 --- a/src/_ttconv.cpp +++ b/src/_ttconv.cpp @@ -8,7 +8,7 @@ #include "mplutils.h" #include -#include "ttconv/pprdrv.h" +#include "pprdrv.h" #include namespace py = pybind11; diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 000000000000..6a33d2dfb12a --- /dev/null +++ b/src/meson.build @@ -0,0 +1,191 @@ +# NumPy include directory - needed in all submodules +# The try-except is needed because when things are split across drives on Windows, there +# is no relative path and an exception gets raised. There may be other such cases, so add +# a catch-all and switch to an absolute path. Relative paths are needed when for example +# a virtualenv is placed inside the source tree; Meson rejects absolute paths to places +# inside the source tree. +# For cross-compilation it is often not possible to run the Python interpreter in order +# to retrieve numpy's include directory. It can be specified in the cross file instead: +# +# [properties] +# numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include +# +# This uses the path as is, and avoids running the interpreter. +incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given') +if incdir_numpy == 'not-given' + incdir_numpy = run_command(py3, + [ + '-c', + '''import os +import numpy as np +try: + incdir = os.path.relpath(np.get_include()) +except Exception: + incdir = np.get_include() +print(incdir)''' + ], + check: true + ).stdout().strip() +endif +numpy_dep = declare_dependency( + compile_args: [ + '-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION', + # Allow NumPy's printf format specifiers in C++. + '-D__STDC_FORMAT_MACROS=1', + ], + include_directories: include_directories(incdir_numpy), + dependencies: py3_dep, +) + +# For cross-compilation it is often not possible to run the Python interpreter in order +# to retrieve the platform-specific /dev/null. It can be specified in the cross file +# instead: +# +# [properties] +# devnull = /dev/null +# +# This uses the value as is, and avoids running the interpreter. +devnull = meson.get_external_property('devnull', 'not-given') +if devnull == 'not-given' + devnull = run_command(py3, '-c', 'import os; print(os.devnull)', + capture: true, check: true).stdout().strip() +endif + +# Will only exist on Linux with older glibc. +dl = cc.find_library('dl', required: false) + +# With Meson >= 1.2.0, use cpp_winlibs instead of manually searching. +if ['cygwin', 'windows'].contains(host_machine.system()) + comctl32 = cc.find_library('comctl32') + ole32 = cc.find_library('ole32') + psapi = cc.find_library('psapi') + shell32 = cc.find_library('shell32') + user32 = cc.find_library('user32') +else + comctl32 = [] + ole32 = [] + psapi = [] + shell32 = [] + user32 = [] +endif + +extension_data = { + '_backend_agg': { + 'subdir': 'matplotlib/backends', + 'sources': files( + 'py_converters.cpp', + '_backend_agg.cpp', + '_backend_agg_wrapper.cpp', + ), + 'dependencies': [agg_dep, numpy_dep, freetype_dep], + }, + '_c_internal_utils': { + 'subdir': 'matplotlib', + 'sources': files( + '_c_internal_utils.c', + ), + 'dependencies': [py3_dep, dl, ole32, shell32, user32], + }, + 'ft2font': { + 'subdir': 'matplotlib', + 'sources': files( + 'ft2font.cpp', + 'ft2font_wrapper.cpp', + 'py_converters.cpp', + ), + 'dependencies': [ + freetype_dep, numpy_dep, agg_dep.partial_dependency(includes: true), + ], + }, + '_image': { + 'subdir': 'matplotlib', + 'sources': files( + '_image_wrapper.cpp', + 'py_converters_11.cpp', + ), + 'dependencies': [ + pybind11_dep, + # Only need source code files agg_image_filters.cpp and agg_trans_affine.cpp + agg_dep, + ], + }, + '_path': { + 'subdir': 'matplotlib', + 'sources': files( + 'py_converters.cpp', + '_path_wrapper.cpp', + ), + 'dependencies': [numpy_dep, agg_dep], + }, + '_qhull': { + 'subdir': 'matplotlib', + 'sources': files( + '_qhull_wrapper.cpp', + ), + 'dependencies': [numpy_dep, qhull_dep], + 'c_args': [f'-DMPL_DEVNULL=@devnull@'], + 'cpp_args': [f'-DMPL_DEVNULL=@devnull@'], + }, + '_tkagg': { + 'subdir': 'matplotlib/backends', + 'sources': files( + '_tkagg.cpp', + ), + 'include_directories': include_directories('.'), + # The dl/psapi libraries are needed for finding Tcl/Tk at run time. + 'dependencies': [ + numpy_dep, agg_dep.partial_dependency(includes: true), dl, comctl32, psapi, + ], + }, + '_tri': { + 'subdir': 'matplotlib', + 'sources': files( + 'tri/_tri.cpp', + 'tri/_tri_wrapper.cpp', + ), + 'dependencies': [pybind11_dep], + }, + '_ttconv': { + 'subdir': 'matplotlib', + 'sources': files( + '_ttconv.cpp', + ), + 'dependencies': [ttconv_dep, pybind11_dep], + }, +} + +cpp_special_arguments = [] +if cpp.get_id() == 'msvc' and get_option('buildtype') != 'plain' + # Disable FH4 Exception Handling implementation so that we don't require + # VCRUNTIME140_1.dll. For more details, see: + # https://devblogs.microsoft.com/cppblog/making-cpp-exception-handling-smaller-x64/ + # https://github.com/joerick/cibuildwheel/issues/423#issuecomment-677763904 + cpp_special_arguments += ['/d2FH4-'] +endif + +foreach ext, kwargs : extension_data + # Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for each extension. + unique_array_api = '-DPY_ARRAY_UNIQUE_SYMBOL=MPL_@0@_ARRAY_API'.format(ext.replace('.', '_')) + additions = { + 'c_args': [unique_array_api] + kwargs.get('c_args', []), + 'cpp_args': cpp_special_arguments + [unique_array_api] + kwargs.get('cpp_args', []), + } + py3.extension_module( + ext, + install: true, + kwargs: kwargs + additions) +endforeach + +if get_option('macosx') and host_machine.system() == 'darwin' + add_languages('objc', native: false) + py3.extension_module( + '_macosx', + subdir: 'matplotlib/backends', + sources: files( + '_macosx.m', + ), + dependencies: dependency('appleframeworks', modules: 'Cocoa'), + override_options: ['werror=true'], + install: true, + ) +endif diff --git a/src/tri/_tri.cpp b/src/tri/_tri.cpp index 2674a3140b35..5c01dbfa681c 100644 --- a/src/tri/_tri.cpp +++ b/src/tri/_tri.cpp @@ -1,9 +1,7 @@ -/* This file contains liberal use of asserts to assist code development and - * debugging. Standard matplotlib builds disable asserts so they cause no - * performance reduction. To enable the asserts, you need to undefine the - * NDEBUG macro, which is achieved by adding the following - * undef_macros=['NDEBUG'] - * to the appropriate make_extension call in setupext.py, and then rebuilding. +/* This file contains liberal use of asserts to assist code development and debugging. + * Standard Matplotlib builds disable asserts so they cause no performance reduction. To + * enable the asserts, you need to undefine the NDEBUG macro, which is achieved by + * passing ``b_ndebug=false`` to the Meson configuration. */ #include "../mplutils.h" #include "_tri.h" diff --git a/subprojects/freetype-2.11.1.wrap b/subprojects/freetype-2.11.1.wrap new file mode 100644 index 000000000000..af4e91e2d0a5 --- /dev/null +++ b/subprojects/freetype-2.11.1.wrap @@ -0,0 +1,7 @@ +[wrap-file] +source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.xz +source_filename = freetype-2.11.1.tar.xz +source_hash = 3333ae7cfda88429c97a7ae63b7d01ab398076c3b67182e960e5684050f2c5c8 + +[provide] +freetype-2.11.1 = freetype_dep diff --git a/subprojects/freetype-2.6.1.wrap b/subprojects/freetype-2.6.1.wrap new file mode 100644 index 000000000000..9da2890cdac8 --- /dev/null +++ b/subprojects/freetype-2.6.1.wrap @@ -0,0 +1,9 @@ +[wrap-file] +source_url = https://download.savannah.gnu.org/releases/freetype/freetype-old/freetype-2.6.1.tar.gz +source_filename = freetype-2.6.1.tar.gz +source_hash = 0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014 + +patch_directory = freetype-2.6.1-meson + +[provide] +freetype-2.6.1 = freetype_dep diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/LICENSE.build b/subprojects/packagefiles/freetype-2.6.1-meson/LICENSE.build new file mode 100644 index 000000000000..ec288041f388 --- /dev/null +++ b/subprojects/packagefiles/freetype-2.6.1-meson/LICENSE.build @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Meson development team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/builds/unix/ftconfig.h.in b/subprojects/packagefiles/freetype-2.6.1-meson/builds/unix/ftconfig.h.in new file mode 100644 index 000000000000..400f3a2a5bf2 --- /dev/null +++ b/subprojects/packagefiles/freetype-2.6.1-meson/builds/unix/ftconfig.h.in @@ -0,0 +1,498 @@ +/***************************************************************************/ +/* */ +/* ftconfig.in */ +/* */ +/* UNIX-specific configuration file (specification only). */ +/* */ +/* Copyright 1996-2015 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + + /*************************************************************************/ + /* */ + /* This header file contains a number of macro definitions that are used */ + /* by the rest of the engine. Most of the macros here are automatically */ + /* determined at compile time, and you should not need to change it to */ + /* port FreeType, except to compile the library with a non-ANSI */ + /* compiler. */ + /* */ + /* Note however that if some specific modifications are needed, we */ + /* advise you to place a modified copy in your build directory. */ + /* */ + /* The build directory is usually `builds/', and contains */ + /* system-specific files that are always included first when building */ + /* the library. */ + /* */ + /*************************************************************************/ + +/* MESON: based on unix/ftconfig.in with but meson-friendly configuration defines */ + +#ifndef FTCONFIG_H_ +#define FTCONFIG_H_ + +#include +#include FT_CONFIG_OPTIONS_H +#include FT_CONFIG_STANDARD_LIBRARY_H + + +FT_BEGIN_HEADER + + + /*************************************************************************/ + /* */ + /* PLATFORM-SPECIFIC CONFIGURATION MACROS */ + /* */ + /* These macros can be toggled to suit a specific system. The current */ + /* ones are defaults used to compile FreeType in an ANSI C environment */ + /* (16bit compilers are also supported). Copy this file to your own */ + /* `builds/' directory, and edit it to port the engine. */ + /* */ + /*************************************************************************/ + + +#define HAVE_UNISTD_H @HAVE_UNISTD_H@ +#define HAVE_FCNTL_H @HAVE_FCNTL_H@ +#define HAVE_STDINT_H @HAVE_STDINT_H@ + + + /* There are systems (like the Texas Instruments 'C54x) where a `char' */ + /* has 16 bits. ANSI C says that sizeof(char) is always 1. Since an */ + /* `int' has 16 bits also for this system, sizeof(int) gives 1 which */ + /* is probably unexpected. */ + /* */ + /* `CHAR_BIT' (defined in limits.h) gives the number of bits in a */ + /* `char' type. */ + +#ifndef FT_CHAR_BIT +#define FT_CHAR_BIT CHAR_BIT +#endif + + +#undef FT_USE_AUTOCONF_SIZEOF_TYPES +#ifdef FT_USE_AUTOCONF_SIZEOF_TYPES + +#undef SIZEOF_INT +#undef SIZEOF_LONG +#define FT_SIZEOF_INT SIZEOF_INT +#define FT_SIZEOF_LONG SIZEOF_LONG + +#else /* !FT_USE_AUTOCONF_SIZEOF_TYPES */ + + /* Following cpp computation of the bit length of int and long */ + /* is copied from default include/freetype/config/ftconfig.h. */ + /* If any improvement is required for this file, it should be */ + /* applied to the original header file for the builders that */ + /* do not use configure script. */ + + /* The size of an `int' type. */ +#if FT_UINT_MAX == 0xFFFFUL +#define FT_SIZEOF_INT (16 / FT_CHAR_BIT) +#elif FT_UINT_MAX == 0xFFFFFFFFUL +#define FT_SIZEOF_INT (32 / FT_CHAR_BIT) +#elif FT_UINT_MAX > 0xFFFFFFFFUL && FT_UINT_MAX == 0xFFFFFFFFFFFFFFFFUL +#define FT_SIZEOF_INT (64 / FT_CHAR_BIT) +#else +#error "Unsupported size of `int' type!" +#endif + + /* The size of a `long' type. A five-byte `long' (as used e.g. on the */ + /* DM642) is recognized but avoided. */ +#if FT_ULONG_MAX == 0xFFFFFFFFUL +#define FT_SIZEOF_LONG (32 / FT_CHAR_BIT) +#elif FT_ULONG_MAX > 0xFFFFFFFFUL && FT_ULONG_MAX == 0xFFFFFFFFFFUL +#define FT_SIZEOF_LONG (32 / FT_CHAR_BIT) +#elif FT_ULONG_MAX > 0xFFFFFFFFUL && FT_ULONG_MAX == 0xFFFFFFFFFFFFFFFFUL +#define FT_SIZEOF_LONG (64 / FT_CHAR_BIT) +#else +#error "Unsupported size of `long' type!" +#endif + +#endif /* !FT_USE_AUTOCONF_SIZEOF_TYPES */ + + + /* FT_UNUSED is a macro used to indicate that a given parameter is not */ + /* used -- this is only used to get rid of unpleasant compiler warnings */ +#ifndef FT_UNUSED +#define FT_UNUSED( arg ) ( (arg) = (arg) ) +#endif + + + /*************************************************************************/ + /* */ + /* AUTOMATIC CONFIGURATION MACROS */ + /* */ + /* These macros are computed from the ones defined above. Don't touch */ + /* their definition, unless you know precisely what you are doing. No */ + /* porter should need to mess with them. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* Mac support */ + /* */ + /* This is the only necessary change, so it is defined here instead */ + /* providing a new configuration file. */ + /* */ +#if defined( __APPLE__ ) || ( defined( __MWERKS__ ) && defined( macintosh ) ) + /* no Carbon frameworks for 64bit 10.4.x */ + /* AvailabilityMacros.h is available since Mac OS X 10.2, */ + /* so guess the system version by maximum errno before inclusion */ +#include +#ifdef ECANCELED /* defined since 10.2 */ +#include "AvailabilityMacros.h" +#endif +#if defined( __LP64__ ) && \ + ( MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 ) +/undef FT_MACINTOSH +#endif + +#elif defined( __SC__ ) || defined( __MRC__ ) + /* Classic MacOS compilers */ +#include "ConditionalMacros.h" +#if TARGET_OS_MAC +#define FT_MACINTOSH 1 +#endif + +#endif + + + /* Fix compiler warning with sgi compiler */ +#if defined( __sgi ) && !defined( __GNUC__ ) +#if defined( _COMPILER_VERSION ) && ( _COMPILER_VERSION >= 730 ) +#pragma set woff 3505 +#endif +#endif + + + /*************************************************************************/ + /* */ + /*

*/ + /* basic_types */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* */ + /* FT_Int16 */ + /* */ + /* */ + /* A typedef for a 16bit signed integer type. */ + /* */ + typedef signed short FT_Int16; + + + /*************************************************************************/ + /* */ + /* */ + /* FT_UInt16 */ + /* */ + /* */ + /* A typedef for a 16bit unsigned integer type. */ + /* */ + typedef unsigned short FT_UInt16; + + /* */ + + + /* this #if 0 ... #endif clause is for documentation purposes */ +#if 0 + + /*************************************************************************/ + /* */ + /* */ + /* FT_Int32 */ + /* */ + /* */ + /* A typedef for a 32bit signed integer type. The size depends on */ + /* the configuration. */ + /* */ + typedef signed XXX FT_Int32; + + + /*************************************************************************/ + /* */ + /* */ + /* FT_UInt32 */ + /* */ + /* A typedef for a 32bit unsigned integer type. The size depends on */ + /* the configuration. */ + /* */ + typedef unsigned XXX FT_UInt32; + + + /*************************************************************************/ + /* */ + /* */ + /* FT_Int64 */ + /* */ + /* A typedef for a 64bit signed integer type. The size depends on */ + /* the configuration. Only defined if there is real 64bit support; */ + /* otherwise, it gets emulated with a structure (if necessary). */ + /* */ + typedef signed XXX FT_Int64; + + + /*************************************************************************/ + /* */ + /* */ + /* FT_UInt64 */ + /* */ + /* A typedef for a 64bit unsigned integer type. The size depends on */ + /* the configuration. Only defined if there is real 64bit support; */ + /* otherwise, it gets emulated with a structure (if necessary). */ + /* */ + typedef unsigned XXX FT_UInt64; + + /* */ + +#endif + +#if FT_SIZEOF_INT == 4 + + typedef signed int FT_Int32; + typedef unsigned int FT_UInt32; + +#elif FT_SIZEOF_LONG == 4 + + typedef signed long FT_Int32; + typedef unsigned long FT_UInt32; + +#else +#error "no 32bit type found -- please check your configuration files" +#endif + + + /* look up an integer type that is at least 32 bits */ +#if FT_SIZEOF_INT >= 4 + + typedef int FT_Fast; + typedef unsigned int FT_UFast; + +#elif FT_SIZEOF_LONG >= 4 + + typedef long FT_Fast; + typedef unsigned long FT_UFast; + +#endif + + + /* determine whether we have a 64-bit int type for platforms without */ + /* Autoconf */ +#if FT_SIZEOF_LONG == 8 + + /* FT_LONG64 must be defined if a 64-bit type is available */ +#define FT_LONG64 +#define FT_INT64 long +#define FT_UINT64 unsigned long + + /*************************************************************************/ + /* */ + /* A 64-bit data type may create compilation problems if you compile */ + /* in strict ANSI mode. To avoid them, we disable other 64-bit data */ + /* types if __STDC__ is defined. You can however ignore this rule */ + /* by defining the FT_CONFIG_OPTION_FORCE_INT64 configuration macro. */ + /* */ +#elif !defined( __STDC__ ) || defined( FT_CONFIG_OPTION_FORCE_INT64 ) + +#if defined( _MSC_VER ) && _MSC_VER >= 900 /* Visual C++ (and Intel C++) */ + + /* this compiler provides the __int64 type */ +#define FT_LONG64 +#define FT_INT64 __int64 +#define FT_UINT64 unsigned __int64 + +#elif defined( __BORLANDC__ ) /* Borland C++ */ + + /* XXXX: We should probably check the value of __BORLANDC__ in order */ + /* to test the compiler version. */ + + /* this compiler provides the __int64 type */ +#define FT_LONG64 +#define FT_INT64 __int64 +#define FT_UINT64 unsigned __int64 + +#elif defined( __WATCOMC__ ) /* Watcom C++ */ + + /* Watcom doesn't provide 64-bit data types */ + +#elif defined( __MWERKS__ ) /* Metrowerks CodeWarrior */ + +#define FT_LONG64 +#define FT_INT64 long long int +#define FT_UINT64 unsigned long long int + +#elif defined( __GNUC__ ) + + /* GCC provides the `long long' type */ +#define FT_LONG64 +#define FT_INT64 long long int +#define FT_UINT64 unsigned long long int + +#endif /* _MSC_VER */ + +#endif /* FT_SIZEOF_LONG == 8 */ + +#ifdef FT_LONG64 + typedef FT_INT64 FT_Int64; + typedef FT_UINT64 FT_UInt64; +#endif + + + /*************************************************************************/ + /* */ + /* miscellaneous */ + /* */ + /*************************************************************************/ + + +#define FT_BEGIN_STMNT do { +#define FT_END_STMNT } while ( 0 ) +#define FT_DUMMY_STMNT FT_BEGIN_STMNT FT_END_STMNT + + + /* typeof condition taken from gnulib's `intprops.h' header file */ +#if ( __GNUC__ >= 2 || \ + defined( __IBM__TYPEOF__ ) || \ + ( __SUNPRO_C >= 0x5110 && !__STDC__ ) ) +#define FT_TYPEOF( type ) (__typeof__ (type)) +#else +#define FT_TYPEOF( type ) /* empty */ +#endif + + +#ifdef FT_MAKE_OPTION_SINGLE_OBJECT + +#define FT_LOCAL( x ) static x +#define FT_LOCAL_DEF( x ) static x + +#else + +#ifdef __cplusplus +#define FT_LOCAL( x ) extern "C" x +#define FT_LOCAL_DEF( x ) extern "C" x +#else +#define FT_LOCAL( x ) extern x +#define FT_LOCAL_DEF( x ) x +#endif + +#endif /* FT_MAKE_OPTION_SINGLE_OBJECT */ + +#define FT_LOCAL_ARRAY( x ) extern const x +#define FT_LOCAL_ARRAY_DEF( x ) const x + + +#ifndef FT_BASE + +#ifdef __cplusplus +#define FT_BASE( x ) extern "C" x +#else +#define FT_BASE( x ) extern x +#endif + +#endif /* !FT_BASE */ + + +#ifndef FT_BASE_DEF + +#ifdef __cplusplus +#define FT_BASE_DEF( x ) x +#else +#define FT_BASE_DEF( x ) x +#endif + +#endif /* !FT_BASE_DEF */ + + +#ifndef FT_EXPORT + +#ifdef __cplusplus +#define FT_EXPORT( x ) extern "C" x +#else +#define FT_EXPORT( x ) extern x +#endif + +#endif /* !FT_EXPORT */ + + +#ifndef FT_EXPORT_DEF + +#ifdef __cplusplus +#define FT_EXPORT_DEF( x ) extern "C" x +#else +#define FT_EXPORT_DEF( x ) extern x +#endif + +#endif /* !FT_EXPORT_DEF */ + + +#ifndef FT_EXPORT_VAR + +#ifdef __cplusplus +#define FT_EXPORT_VAR( x ) extern "C" x +#else +#define FT_EXPORT_VAR( x ) extern x +#endif + +#endif /* !FT_EXPORT_VAR */ + + /* The following macros are needed to compile the library with a */ + /* C++ compiler and with 16bit compilers. */ + /* */ + + /* This is special. Within C++, you must specify `extern "C"' for */ + /* functions which are used via function pointers, and you also */ + /* must do that for structures which contain function pointers to */ + /* assure C linkage -- it's not possible to have (local) anonymous */ + /* functions which are accessed by (global) function pointers. */ + /* */ + /* */ + /* FT_CALLBACK_DEF is used to _define_ a callback function. */ + /* */ + /* FT_CALLBACK_TABLE is used to _declare_ a constant variable that */ + /* contains pointers to callback functions. */ + /* */ + /* FT_CALLBACK_TABLE_DEF is used to _define_ a constant variable */ + /* that contains pointers to callback functions. */ + /* */ + /* */ + /* Some 16bit compilers have to redefine these macros to insert */ + /* the infamous `_cdecl' or `__fastcall' declarations. */ + /* */ +#ifndef FT_CALLBACK_DEF +#ifdef __cplusplus +#define FT_CALLBACK_DEF( x ) extern "C" x +#else +#define FT_CALLBACK_DEF( x ) static x +#endif +#endif /* FT_CALLBACK_DEF */ + +#ifndef FT_CALLBACK_TABLE +#ifdef __cplusplus +#define FT_CALLBACK_TABLE extern "C" +#define FT_CALLBACK_TABLE_DEF extern "C" +#else +#define FT_CALLBACK_TABLE extern +#define FT_CALLBACK_TABLE_DEF /* nothing */ +#endif +#endif /* FT_CALLBACK_TABLE */ + + +FT_END_HEADER + + +#endif /* FTCONFIG_H_ */ + + +/* END */ diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/include/freetype/config/ftoption.h.in b/subprojects/packagefiles/freetype-2.6.1-meson/include/freetype/config/ftoption.h.in new file mode 100644 index 000000000000..5df84c706800 --- /dev/null +++ b/subprojects/packagefiles/freetype-2.6.1-meson/include/freetype/config/ftoption.h.in @@ -0,0 +1,886 @@ +/***************************************************************************/ +/* */ +/* ftoption.h */ +/* */ +/* User-selectable configuration macros (specification only). */ +/* */ +/* Copyright 1996-2015 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + +#ifndef FTOPTION_H_ +#define FTOPTION_H_ + + +#include + + +FT_BEGIN_HEADER + + /*************************************************************************/ + /* */ + /* USER-SELECTABLE CONFIGURATION MACROS */ + /* */ + /* This file contains the default configuration macro definitions for */ + /* a standard build of the FreeType library. There are three ways to */ + /* use this file to build project-specific versions of the library: */ + /* */ + /* - You can modify this file by hand, but this is not recommended in */ + /* cases where you would like to build several versions of the */ + /* library from a single source directory. */ + /* */ + /* - You can put a copy of this file in your build directory, more */ + /* precisely in `$BUILD/freetype/config/ftoption.h', where `$BUILD' */ + /* is the name of a directory that is included _before_ the FreeType */ + /* include path during compilation. */ + /* */ + /* The default FreeType Makefiles and Jamfiles use the build */ + /* directory `builds/' by default, but you can easily change */ + /* that for your own projects. */ + /* */ + /* - Copy the file to `$BUILD/ft2build.h' and modify it */ + /* slightly to pre-define the macro FT_CONFIG_OPTIONS_H used to */ + /* locate this file during the build. For example, */ + /* */ + /* #define FT_CONFIG_OPTIONS_H */ + /* #include */ + /* */ + /* will use `$BUILD/myftoptions.h' instead of this file for macro */ + /* definitions. */ + /* */ + /* Note also that you can similarly pre-define the macro */ + /* FT_CONFIG_MODULES_H used to locate the file listing of the modules */ + /* that are statically linked to the library at compile time. By */ + /* default, this file is . */ + /* */ + /* We highly recommend using the third method whenever possible. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** G E N E R A L F R E E T Y P E 2 C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* Uncomment the line below if you want to activate sub-pixel rendering */ + /* (a.k.a. LCD rendering, or ClearType) in this build of the library. */ + /* */ + /* Note that this feature is covered by several Microsoft patents */ + /* and should not be activated in any default build of the library. */ + /* */ + /* This macro has no impact on the FreeType API, only on its */ + /* _implementation_. For example, using FT_RENDER_MODE_LCD when calling */ + /* FT_Render_Glyph still generates a bitmap that is 3 times wider than */ + /* the original size in case this macro isn't defined; however, each */ + /* triplet of subpixels has R=G=B. */ + /* */ + /* This is done to allow FreeType clients to run unmodified, forcing */ + /* them to display normal gray-level anti-aliased glyphs. */ + /* */ +/* #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING */ + + + /*************************************************************************/ + /* */ + /* Many compilers provide a non-ANSI 64-bit data type that can be used */ + /* by FreeType to speed up some computations. However, this will create */ + /* some problems when compiling the library in strict ANSI mode. */ + /* */ + /* For this reason, the use of 64-bit integers is normally disabled when */ + /* the __STDC__ macro is defined. You can however disable this by */ + /* defining the macro FT_CONFIG_OPTION_FORCE_INT64 here. */ + /* */ + /* For most compilers, this will only create compilation warnings when */ + /* building the library. */ + /* */ + /* ObNote: The compiler-specific 64-bit integers are detected in the */ + /* file `ftconfig.h' either statically or through the */ + /* `configure' script on supported platforms. */ + /* */ +#undef FT_CONFIG_OPTION_FORCE_INT64 + + + /*************************************************************************/ + /* */ + /* If this macro is defined, do not try to use an assembler version of */ + /* performance-critical functions (e.g. FT_MulFix). You should only do */ + /* that to verify that the assembler function works properly, or to */ + /* execute benchmark tests of the various implementations. */ +/* #define FT_CONFIG_OPTION_NO_ASSEMBLER */ + + + /*************************************************************************/ + /* */ + /* If this macro is defined, try to use an inlined assembler version of */ + /* the `FT_MulFix' function, which is a `hotspot' when loading and */ + /* hinting glyphs, and which should be executed as fast as possible. */ + /* */ + /* Note that if your compiler or CPU is not supported, this will default */ + /* to the standard and portable implementation found in `ftcalc.c'. */ + /* */ +#define FT_CONFIG_OPTION_INLINE_MULFIX + + + /*************************************************************************/ + /* */ + /* LZW-compressed file support. */ + /* */ + /* FreeType now handles font files that have been compressed with the */ + /* `compress' program. This is mostly used to parse many of the PCF */ + /* files that come with various X11 distributions. The implementation */ + /* uses NetBSD's `zopen' to partially uncompress the file on the fly */ + /* (see src/lzw/ftgzip.c). */ + /* */ + /* Define this macro if you want to enable this `feature'. */ + /* */ +#define FT_CONFIG_OPTION_USE_LZW + + + /*************************************************************************/ + /* */ + /* Gzip-compressed file support. */ + /* */ + /* FreeType now handles font files that have been compressed with the */ + /* `gzip' program. This is mostly used to parse many of the PCF files */ + /* that come with XFree86. The implementation uses `zlib' to */ + /* partially uncompress the file on the fly (see src/gzip/ftgzip.c). */ + /* */ + /* Define this macro if you want to enable this `feature'. See also */ + /* the macro FT_CONFIG_OPTION_SYSTEM_ZLIB below. */ + /* */ +#define FT_CONFIG_OPTION_USE_ZLIB + + + /*************************************************************************/ + /* */ + /* ZLib library selection */ + /* */ + /* This macro is only used when FT_CONFIG_OPTION_USE_ZLIB is defined. */ + /* It allows FreeType's `ftgzip' component to link to the system's */ + /* installation of the ZLib library. This is useful on systems like */ + /* Unix or VMS where it generally is already available. */ + /* */ + /* If you let it undefined, the component will use its own copy */ + /* of the zlib sources instead. These have been modified to be */ + /* included directly within the component and *not* export external */ + /* function names. This allows you to link any program with FreeType */ + /* _and_ ZLib without linking conflicts. */ + /* */ + /* Do not #undef this macro here since the build system might define */ + /* it for certain configurations only. */ + /* */ +#mesondefine FT_CONFIG_OPTION_SYSTEM_ZLIB + + + /*************************************************************************/ + /* */ + /* Bzip2-compressed file support. */ + /* */ + /* FreeType now handles font files that have been compressed with the */ + /* `bzip2' program. This is mostly used to parse many of the PCF */ + /* files that come with XFree86. The implementation uses `libbz2' to */ + /* partially uncompress the file on the fly (see src/bzip2/ftbzip2.c). */ + /* Contrary to gzip, bzip2 currently is not included and need to use */ + /* the system available bzip2 implementation. */ + /* */ + /* Define this macro if you want to enable this `feature'. */ + /* */ +#mesondefine FT_CONFIG_OPTION_USE_BZIP2 + + + /*************************************************************************/ + /* */ + /* Define to disable the use of file stream functions and types, FILE, */ + /* fopen() etc. Enables the use of smaller system libraries on embedded */ + /* systems that have multiple system libraries, some with or without */ + /* file stream support, in the cases where file stream support is not */ + /* necessary such as memory loading of font files. */ + /* */ +/* #define FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT */ + + + /*************************************************************************/ + /* */ + /* PNG bitmap support. */ + /* */ + /* FreeType now handles loading color bitmap glyphs in the PNG format. */ + /* This requires help from the external libpng library. Uncompressed */ + /* color bitmaps do not need any external libraries and will be */ + /* supported regardless of this configuration. */ + /* */ + /* Define this macro if you want to enable this `feature'. */ + /* */ +#mesondefine FT_CONFIG_OPTION_USE_PNG + + + /*************************************************************************/ + /* */ + /* HarfBuzz support. */ + /* */ + /* FreeType uses the HarfBuzz library to improve auto-hinting of */ + /* OpenType fonts. If available, many glyphs not directly addressable */ + /* by a font's character map will be hinted also. */ + /* */ + /* Define this macro if you want to enable this `feature'. */ + /* */ +#mesondefine FT_CONFIG_OPTION_USE_HARFBUZZ + + + /*************************************************************************/ + /* */ + /* DLL export compilation */ + /* */ + /* When compiling FreeType as a DLL, some systems/compilers need a */ + /* special keyword in front OR after the return type of function */ + /* declarations. */ + /* */ + /* Two macros are used within the FreeType source code to define */ + /* exported library functions: FT_EXPORT and FT_EXPORT_DEF. */ + /* */ + /* FT_EXPORT( return_type ) */ + /* */ + /* is used in a function declaration, as in */ + /* */ + /* FT_EXPORT( FT_Error ) */ + /* FT_Init_FreeType( FT_Library* alibrary ); */ + /* */ + /* */ + /* FT_EXPORT_DEF( return_type ) */ + /* */ + /* is used in a function definition, as in */ + /* */ + /* FT_EXPORT_DEF( FT_Error ) */ + /* FT_Init_FreeType( FT_Library* alibrary ) */ + /* { */ + /* ... some code ... */ + /* return FT_Err_Ok; */ + /* } */ + /* */ + /* You can provide your own implementation of FT_EXPORT and */ + /* FT_EXPORT_DEF here if you want. If you leave them undefined, they */ + /* will be later automatically defined as `extern return_type' to */ + /* allow normal compilation. */ + /* */ + /* Do not #undef these macros here since the build system might define */ + /* them for certain configurations only. */ + /* */ +/* #define FT_EXPORT(x) extern x */ +/* #define FT_EXPORT_DEF(x) x */ + + + /*************************************************************************/ + /* */ + /* Glyph Postscript Names handling */ + /* */ + /* By default, FreeType 2 is compiled with the `psnames' module. This */ + /* module is in charge of converting a glyph name string into a */ + /* Unicode value, or return a Macintosh standard glyph name for the */ + /* use with the TrueType `post' table. */ + /* */ + /* Undefine this macro if you do not want `psnames' compiled in your */ + /* build of FreeType. This has the following effects: */ + /* */ + /* - The TrueType driver will provide its own set of glyph names, */ + /* if you build it to support postscript names in the TrueType */ + /* `post' table. */ + /* */ + /* - The Type 1 driver will not be able to synthesize a Unicode */ + /* charmap out of the glyphs found in the fonts. */ + /* */ + /* You would normally undefine this configuration macro when building */ + /* a version of FreeType that doesn't contain a Type 1 or CFF driver. */ + /* */ +#define FT_CONFIG_OPTION_POSTSCRIPT_NAMES + + + /*************************************************************************/ + /* */ + /* Postscript Names to Unicode Values support */ + /* */ + /* By default, FreeType 2 is built with the `PSNames' module compiled */ + /* in. Among other things, the module is used to convert a glyph name */ + /* into a Unicode value. This is especially useful in order to */ + /* synthesize on the fly a Unicode charmap from the CFF/Type 1 driver */ + /* through a big table named the `Adobe Glyph List' (AGL). */ + /* */ + /* Undefine this macro if you do not want the Adobe Glyph List */ + /* compiled in your `PSNames' module. The Type 1 driver will not be */ + /* able to synthesize a Unicode charmap out of the glyphs found in the */ + /* fonts. */ + /* */ +#define FT_CONFIG_OPTION_ADOBE_GLYPH_LIST + + + /*************************************************************************/ + /* */ + /* Support for Mac fonts */ + /* */ + /* Define this macro if you want support for outline fonts in Mac */ + /* format (mac dfont, mac resource, macbinary containing a mac */ + /* resource) on non-Mac platforms. */ + /* */ + /* Note that the `FOND' resource isn't checked. */ + /* */ +#define FT_CONFIG_OPTION_MAC_FONTS + + + /*************************************************************************/ + /* */ + /* Guessing methods to access embedded resource forks */ + /* */ + /* Enable extra Mac fonts support on non-Mac platforms (e.g. */ + /* GNU/Linux). */ + /* */ + /* Resource forks which include fonts data are stored sometimes in */ + /* locations which users or developers don't expected. In some cases, */ + /* resource forks start with some offset from the head of a file. In */ + /* other cases, the actual resource fork is stored in file different */ + /* from what the user specifies. If this option is activated, */ + /* FreeType tries to guess whether such offsets or different file */ + /* names must be used. */ + /* */ + /* Note that normal, direct access of resource forks is controlled via */ + /* the FT_CONFIG_OPTION_MAC_FONTS option. */ + /* */ +#ifdef FT_CONFIG_OPTION_MAC_FONTS +#define FT_CONFIG_OPTION_GUESSING_EMBEDDED_RFORK +#endif + + + /*************************************************************************/ + /* */ + /* Allow the use of FT_Incremental_Interface to load typefaces that */ + /* contain no glyph data, but supply it via a callback function. */ + /* This is required by clients supporting document formats which */ + /* supply font data incrementally as the document is parsed, such */ + /* as the Ghostscript interpreter for the PostScript language. */ + /* */ +#define FT_CONFIG_OPTION_INCREMENTAL + + + /*************************************************************************/ + /* */ + /* The size in bytes of the render pool used by the scan-line converter */ + /* to do all of its work. */ + /* */ +#define FT_RENDER_POOL_SIZE 16384L + + + /*************************************************************************/ + /* */ + /* FT_MAX_MODULES */ + /* */ + /* The maximum number of modules that can be registered in a single */ + /* FreeType library object. 32 is the default. */ + /* */ +#define FT_MAX_MODULES 32 + + + /*************************************************************************/ + /* */ + /* Debug level */ + /* */ + /* FreeType can be compiled in debug or trace mode. In debug mode, */ + /* errors are reported through the `ftdebug' component. In trace */ + /* mode, additional messages are sent to the standard output during */ + /* execution. */ + /* */ + /* Define FT_DEBUG_LEVEL_ERROR to build the library in debug mode. */ + /* Define FT_DEBUG_LEVEL_TRACE to build it in trace mode. */ + /* */ + /* Don't define any of these macros to compile in `release' mode! */ + /* */ + /* Do not #undef these macros here since the build system might define */ + /* them for certain configurations only. */ + /* */ +/* #define FT_DEBUG_LEVEL_ERROR */ +/* #define FT_DEBUG_LEVEL_TRACE */ + + + /*************************************************************************/ + /* */ + /* Autofitter debugging */ + /* */ + /* If FT_DEBUG_AUTOFIT is defined, FreeType provides some means to */ + /* control the autofitter behaviour for debugging purposes with global */ + /* boolean variables (consequently, you should *never* enable this */ + /* while compiling in `release' mode): */ + /* */ + /* _af_debug_disable_horz_hints */ + /* _af_debug_disable_vert_hints */ + /* _af_debug_disable_blue_hints */ + /* */ + /* Additionally, the following functions provide dumps of various */ + /* internal autofit structures to stdout (using `printf'): */ + /* */ + /* af_glyph_hints_dump_points */ + /* af_glyph_hints_dump_segments */ + /* af_glyph_hints_dump_edges */ + /* af_glyph_hints_get_num_segments */ + /* af_glyph_hints_get_segment_offset */ + /* */ + /* As an argument, they use another global variable: */ + /* */ + /* _af_debug_hints */ + /* */ + /* Please have a look at the `ftgrid' demo program to see how those */ + /* variables and macros should be used. */ + /* */ + /* Do not #undef these macros here since the build system might define */ + /* them for certain configurations only. */ + /* */ +/* #define FT_DEBUG_AUTOFIT */ + + + /*************************************************************************/ + /* */ + /* Memory Debugging */ + /* */ + /* FreeType now comes with an integrated memory debugger that is */ + /* capable of detecting simple errors like memory leaks or double */ + /* deletes. To compile it within your build of the library, you */ + /* should define FT_DEBUG_MEMORY here. */ + /* */ + /* Note that the memory debugger is only activated at runtime when */ + /* when the _environment_ variable `FT2_DEBUG_MEMORY' is defined also! */ + /* */ + /* Do not #undef this macro here since the build system might define */ + /* it for certain configurations only. */ + /* */ +/* #define FT_DEBUG_MEMORY */ + + + /*************************************************************************/ + /* */ + /* Module errors */ + /* */ + /* If this macro is set (which is _not_ the default), the higher byte */ + /* of an error code gives the module in which the error has occurred, */ + /* while the lower byte is the real error code. */ + /* */ + /* Setting this macro makes sense for debugging purposes only, since */ + /* it would break source compatibility of certain programs that use */ + /* FreeType 2. */ + /* */ + /* More details can be found in the files ftmoderr.h and fterrors.h. */ + /* */ +#undef FT_CONFIG_OPTION_USE_MODULE_ERRORS + + + /*************************************************************************/ + /* */ + /* Position Independent Code */ + /* */ + /* If this macro is set (which is _not_ the default), FreeType2 will */ + /* avoid creating constants that require address fixups. Instead the */ + /* constants will be moved into a struct and additional intialization */ + /* code will be used. */ + /* */ + /* Setting this macro is needed for systems that prohibit address */ + /* fixups, such as BREW. */ + /* */ +#mesondefine FT_CONFIG_OPTION_PIC + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** S F N T D R I V E R C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_EMBEDDED_BITMAPS if you want to support */ + /* embedded bitmaps in all formats using the SFNT module (namely */ + /* TrueType & OpenType). */ + /* */ +#define TT_CONFIG_OPTION_EMBEDDED_BITMAPS + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_POSTSCRIPT_NAMES if you want to be able to */ + /* load and enumerate the glyph Postscript names in a TrueType or */ + /* OpenType file. */ + /* */ + /* Note that when you do not compile the `PSNames' module by undefining */ + /* the above FT_CONFIG_OPTION_POSTSCRIPT_NAMES, the `sfnt' module will */ + /* contain additional code used to read the PS Names table from a font. */ + /* */ + /* (By default, the module uses `PSNames' to extract glyph names.) */ + /* */ +#define TT_CONFIG_OPTION_POSTSCRIPT_NAMES + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_SFNT_NAMES if your applications need to */ + /* access the internal name table in a SFNT-based format like TrueType */ + /* or OpenType. The name table contains various strings used to */ + /* describe the font, like family name, copyright, version, etc. It */ + /* does not contain any glyph name though. */ + /* */ + /* Accessing SFNT names is done through the functions declared in */ + /* `ftsnames.h'. */ + /* */ +#define TT_CONFIG_OPTION_SFNT_NAMES + + + /*************************************************************************/ + /* */ + /* TrueType CMap support */ + /* */ + /* Here you can fine-tune which TrueType CMap table format shall be */ + /* supported. */ +#define TT_CONFIG_CMAP_FORMAT_0 +#define TT_CONFIG_CMAP_FORMAT_2 +#define TT_CONFIG_CMAP_FORMAT_4 +#define TT_CONFIG_CMAP_FORMAT_6 +#define TT_CONFIG_CMAP_FORMAT_8 +#define TT_CONFIG_CMAP_FORMAT_10 +#define TT_CONFIG_CMAP_FORMAT_12 +#define TT_CONFIG_CMAP_FORMAT_13 +#define TT_CONFIG_CMAP_FORMAT_14 + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** T R U E T Y P E D R I V E R C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_BYTECODE_INTERPRETER if you want to compile */ + /* a bytecode interpreter in the TrueType driver. */ + /* */ + /* By undefining this, you will only compile the code necessary to load */ + /* TrueType glyphs without hinting. */ + /* */ + /* Do not #undef this macro here, since the build system might */ + /* define it for certain configurations only. */ + /* */ +#define TT_CONFIG_OPTION_BYTECODE_INTERPRETER + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_SUBPIXEL_HINTING if you want to compile */ + /* EXPERIMENTAL subpixel hinting support into the TrueType driver. This */ + /* replaces the native TrueType hinting mechanism when anything but */ + /* FT_RENDER_MODE_MONO is requested. */ + /* */ + /* Enabling this causes the TrueType driver to ignore instructions under */ + /* certain conditions. This is done in accordance with the guide here, */ + /* with some minor differences: */ + /* */ + /* http://www.microsoft.com/typography/cleartype/truetypecleartype.aspx */ + /* */ + /* By undefining this, you only compile the code necessary to hint */ + /* TrueType glyphs with native TT hinting. */ + /* */ + /* This option requires TT_CONFIG_OPTION_BYTECODE_INTERPRETER to be */ + /* defined. */ + /* */ +/* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + + + /*************************************************************************/ + /* */ + /* If you define TT_CONFIG_OPTION_UNPATENTED_HINTING, a special version */ + /* of the TrueType bytecode interpreter is used that doesn't implement */ + /* any of the patented opcodes and algorithms. The patents related to */ + /* TrueType hinting have expired worldwide since May 2010; this option */ + /* is now deprecated. */ + /* */ + /* Note that the TT_CONFIG_OPTION_UNPATENTED_HINTING macro is *ignored* */ + /* if you define TT_CONFIG_OPTION_BYTECODE_INTERPRETER; in other words, */ + /* either define TT_CONFIG_OPTION_BYTECODE_INTERPRETER or */ + /* TT_CONFIG_OPTION_UNPATENTED_HINTING but not both at the same time. */ + /* */ + /* This macro is only useful for a small number of font files (mostly */ + /* for Asian scripts) that require bytecode interpretation to properly */ + /* load glyphs. For all other fonts, this produces unpleasant results, */ + /* thus the unpatented interpreter is never used to load glyphs from */ + /* TrueType fonts unless one of the following two options is used. */ + /* */ + /* - The unpatented interpreter is explicitly activated by the user */ + /* through the FT_PARAM_TAG_UNPATENTED_HINTING parameter tag */ + /* when opening the FT_Face. */ + /* */ + /* - FreeType detects that the FT_Face corresponds to one of the */ + /* `trick' fonts (e.g., `Mingliu') it knows about. The font engine */ + /* contains a hard-coded list of font names and other matching */ + /* parameters (see function `tt_face_init' in file */ + /* `src/truetype/ttobjs.c'). */ + /* */ + /* Here a sample code snippet for using FT_PARAM_TAG_UNPATENTED_HINTING. */ + /* */ + /* { */ + /* FT_Parameter parameter; */ + /* FT_Open_Args open_args; */ + /* */ + /* */ + /* parameter.tag = FT_PARAM_TAG_UNPATENTED_HINTING; */ + /* */ + /* open_args.flags = FT_OPEN_PATHNAME | FT_OPEN_PARAMS; */ + /* open_args.pathname = my_font_pathname; */ + /* open_args.num_params = 1; */ + /* open_args.params = ¶meter; */ + /* */ + /* error = FT_Open_Face( library, &open_args, index, &face ); */ + /* ... */ + /* } */ + /* */ +/* #define TT_CONFIG_OPTION_UNPATENTED_HINTING */ + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_COMPONENT_OFFSET_SCALED to compile the */ + /* TrueType glyph loader to use Apple's definition of how to handle */ + /* component offsets in composite glyphs. */ + /* */ + /* Apple and MS disagree on the default behavior of component offsets */ + /* in composites. Apple says that they should be scaled by the scaling */ + /* factors in the transformation matrix (roughly, it's more complex) */ + /* while MS says they should not. OpenType defines two bits in the */ + /* composite flags array which can be used to disambiguate, but old */ + /* fonts will not have them. */ + /* */ + /* http://www.microsoft.com/typography/otspec/glyf.htm */ + /* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html */ + /* */ +#undef TT_CONFIG_OPTION_COMPONENT_OFFSET_SCALED + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_GX_VAR_SUPPORT if you want to include */ + /* support for Apple's distortable font technology (fvar, gvar, cvar, */ + /* and avar tables). This has many similarities to Type 1 Multiple */ + /* Masters support. */ + /* */ +#define TT_CONFIG_OPTION_GX_VAR_SUPPORT + + + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_BDF if you want to include support for */ + /* an embedded `BDF ' table within SFNT-based bitmap formats. */ + /* */ +#define TT_CONFIG_OPTION_BDF + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** T Y P E 1 D R I V E R C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* T1_MAX_DICT_DEPTH is the maximum depth of nest dictionaries and */ + /* arrays in the Type 1 stream (see t1load.c). A minimum of 4 is */ + /* required. */ + /* */ +#define T1_MAX_DICT_DEPTH 5 + + + /*************************************************************************/ + /* */ + /* T1_MAX_SUBRS_CALLS details the maximum number of nested sub-routine */ + /* calls during glyph loading. */ + /* */ +#define T1_MAX_SUBRS_CALLS 16 + + + /*************************************************************************/ + /* */ + /* T1_MAX_CHARSTRING_OPERANDS is the charstring stack's capacity. A */ + /* minimum of 16 is required. */ + /* */ + /* The Chinese font MingTiEG-Medium (CNS 11643 character set) needs 256. */ + /* */ +#define T1_MAX_CHARSTRINGS_OPERANDS 256 + + + /*************************************************************************/ + /* */ + /* Define this configuration macro if you want to prevent the */ + /* compilation of `t1afm', which is in charge of reading Type 1 AFM */ + /* files into an existing face. Note that if set, the T1 driver will be */ + /* unable to produce kerning distances. */ + /* */ +#undef T1_CONFIG_OPTION_NO_AFM + + + /*************************************************************************/ + /* */ + /* Define this configuration macro if you want to prevent the */ + /* compilation of the Multiple Masters font support in the Type 1 */ + /* driver. */ + /* */ +#undef T1_CONFIG_OPTION_NO_MM_SUPPORT + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** C F F D R I V E R C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* Using CFF_CONFIG_OPTION_DARKENING_PARAMETER_{X,Y}{1,2,3,4} it is */ + /* possible to set up the default values of the four control points that */ + /* define the stem darkening behaviour of the (new) CFF engine. For */ + /* more details please read the documentation of the */ + /* `darkening-parameters' property of the cff driver module (file */ + /* `ftcffdrv.h'), which allows the control at run-time. */ + /* */ + /* Do *not* undefine these macros! */ + /* */ +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 500 +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 400 + +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 1000 +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 275 + +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 1667 +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 275 + +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 2333 +#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 0 + + + /*************************************************************************/ + /* */ + /* CFF_CONFIG_OPTION_OLD_ENGINE controls whether the pre-Adobe CFF */ + /* engine gets compiled into FreeType. If defined, it is possible to */ + /* switch between the two engines using the `hinting-engine' property of */ + /* the cff driver module. */ + /* */ +/* #define CFF_CONFIG_OPTION_OLD_ENGINE */ + + + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** A U T O F I T M O D U L E C O N F I G U R A T I O N ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* Compile autofit module with CJK (Chinese, Japanese, Korean) script */ + /* support. */ + /* */ +#define AF_CONFIG_OPTION_CJK + + /*************************************************************************/ + /* */ + /* Compile autofit module with Indic script support. */ + /* */ +#define AF_CONFIG_OPTION_INDIC + + /*************************************************************************/ + /* */ + /* Compile autofit module with warp hinting. The idea of the warping */ + /* code is to slightly scale and shift a glyph within a single dimension */ + /* so that as much of its segments are aligned (more or less) on the */ + /* grid. To find out the optimal scaling and shifting value, various */ + /* parameter combinations are tried and scored. */ + /* */ + /* This experimental option is active only if the rendering mode is */ + /* FT_RENDER_MODE_LIGHT; you can switch warping on and off with the */ + /* `warping' property of the auto-hinter (see file `ftautoh.h' for more */ + /* information; by default it is switched off). */ + /* */ +#define AF_CONFIG_OPTION_USE_WARPER + + /* */ + + + /* + * This macro is obsolete. Support has been removed in FreeType + * version 2.5. + */ +/* #define FT_CONFIG_OPTION_OLD_INTERNALS */ + + + /* + * This macro is defined if either unpatented or native TrueType + * hinting is requested by the definitions above. + */ +#ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER +#define TT_USE_BYTECODE_INTERPRETER +#undef TT_CONFIG_OPTION_UNPATENTED_HINTING +#elif defined TT_CONFIG_OPTION_UNPATENTED_HINTING +#define TT_USE_BYTECODE_INTERPRETER +#endif + + + /* + * Check CFF darkening parameters. The checks are the same as in function + * `cff_property_set' in file `cffdrivr.c'. + */ +#if CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 < 0 || \ + \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 < 0 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 < 0 || \ + \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 > \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 > \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 > \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 || \ + \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 > 500 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 > 500 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 > 500 || \ + CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 > 500 +#error "Invalid CFF darkening parameters!" +#endif + +FT_END_HEADER + + +#endif /* FTOPTION_H_ */ + + +/* END */ diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/meson.build b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build new file mode 100644 index 000000000000..dddfd67f6d09 --- /dev/null +++ b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build @@ -0,0 +1,187 @@ +project('freetype2', 'c', + license : ['FTL', 'GPL2'], + version: '2.6.1') + +# NOTE about FreeType versions +# There are 3 versions numbers associated with each releases: +# - official release number (eg. 2.6.1) - accessible via +# FREETYPE_{MAJOR,MINOR,PATCH} macros from FT_FREETYPE_H +# - libtool-specific version number, this is what is returned by +# freetype-config --version / pkg-config --modversion (eg. 22.1.16) +# - the platform-specific shared object version number (eg. 6.16.1) +# See https://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/VERSIONS.TXT +# for more information +release_version = meson.project_version() +libtool_version = '18.1.12' +so_version = '6.12.1' +so_soversion = '6' + +pkgmod = import('pkgconfig') + +cc = meson.get_compiler('c') + +base_sources = [ + 'src/autofit/autofit.c', + 'src/base/ftbase.c', + 'src/base/ftbbox.c', + 'src/base/ftbdf.c', + 'src/base/ftbitmap.c', + 'src/base/ftcid.c', + 'src/base/ftfntfmt.c', + 'src/base/ftfstype.c', + 'src/base/ftgasp.c', + 'src/base/ftglyph.c', + 'src/base/ftgxval.c', + 'src/base/ftinit.c', + 'src/base/ftlcdfil.c', + 'src/base/ftmm.c', + 'src/base/ftotval.c', + 'src/base/ftpatent.c', + 'src/base/ftpfr.c', + 'src/base/ftstroke.c', + 'src/base/ftsynth.c', + 'src/base/ftsystem.c', + 'src/base/fttype1.c', + 'src/base/ftwinfnt.c', + 'src/bdf/bdf.c', + 'src/bzip2/ftbzip2.c', + 'src/cache/ftcache.c', + 'src/cff/cff.c', + 'src/cid/type1cid.c', + 'src/gzip/ftgzip.c', + 'src/lzw/ftlzw.c', + 'src/pcf/pcf.c', + 'src/pfr/pfr.c', + 'src/psaux/psaux.c', + 'src/pshinter/pshinter.c', + 'src/psnames/psnames.c', + 'src/raster/raster.c', + 'src/sfnt/sfnt.c', + 'src/smooth/smooth.c', + 'src/truetype/truetype.c', + 'src/type1/type1.c', + 'src/type42/type42.c', + 'src/winfonts/winfnt.c', +] + +ft2build_h = [ + 'include/ft2build.h', +] + +ft_headers = [ + 'include/freetype/freetype.h', + 'include/freetype/ftadvanc.h', + 'include/freetype/ftautoh.h', + 'include/freetype/ftbbox.h', + 'include/freetype/ftbdf.h', + 'include/freetype/ftbitmap.h', + 'include/freetype/ftbzip2.h', + 'include/freetype/ftcache.h', + 'include/freetype/ftcffdrv.h', + 'include/freetype/ftchapters.h', + 'include/freetype/ftcid.h', + 'include/freetype/fterrdef.h', + 'include/freetype/fterrors.h', + 'include/freetype/ftfntfmt.h', + 'include/freetype/ftgasp.h', + 'include/freetype/ftglyph.h', + 'include/freetype/ftgxval.h', + 'include/freetype/ftgzip.h', + 'include/freetype/ftimage.h', + 'include/freetype/ftincrem.h', + 'include/freetype/ftlcdfil.h', + 'include/freetype/ftlist.h', + 'include/freetype/ftlzw.h', + 'include/freetype/ftmac.h', + 'include/freetype/ftmm.h', + 'include/freetype/ftmodapi.h', + 'include/freetype/ftmoderr.h', + 'include/freetype/ftotval.h', + 'include/freetype/ftoutln.h', + 'include/freetype/ftpfr.h', + 'include/freetype/ftrender.h', + 'include/freetype/ftsizes.h', + 'include/freetype/ftsnames.h', + 'include/freetype/ftstroke.h', + 'include/freetype/ftsynth.h', + 'include/freetype/ftsystem.h', + 'include/freetype/fttrigon.h', + 'include/freetype/ftttdrv.h', + 'include/freetype/fttypes.h', + 'include/freetype/ftwinfnt.h', + 'include/freetype/t1tables.h', + 'include/freetype/ttnameid.h', + 'include/freetype/tttables.h', + 'include/freetype/tttags.h', + 'include/freetype/ttunpat.h', +] + +ft_config_headers = [ + 'include/freetype/config/ftconfig.h', + 'include/freetype/config/ftheader.h', + 'include/freetype/config/ftmodule.h', + 'include/freetype/config/ftoption.h', + 'include/freetype/config/ftstdlib.h', +] + +if host_machine.system() == 'windows' + base_sources += [ + 'builds/windows/ftdebug.c', + ] +else + base_sources += [ + 'src/base/ftdebug.c', + ] +endif + +c_args = [ + '-DFT2_BUILD_LIBRARY', + '-DFT_CONFIG_CONFIG_H=', + '-DFT_CONFIG_OPTIONS_H=' +] + +check_headers = [] + +if ['linux', 'darwin', 'cygwin'].contains(host_machine.system()) + check_headers += [ + ['unistd.h'], + ['fcntl.h'], + ['stdint.h'], + ] + ftconfig_h_in = files('builds/unix/ftconfig.h.in') +else + ftconfig_h_in = files('include/freetype/config/ftconfig.h') +endif + +conf = configuration_data() +deps = [] +incbase = include_directories(['include']) + +foreach check : check_headers + name = check[0] + + if cc.has_header(name) + conf.set('HAVE_@0@'.format(name.to_upper().underscorify()), 1) + endif +endforeach + +configure_file(input: ftconfig_h_in, + output: 'ftconfig.h', + configuration: conf) + +ft_config_headers += [configure_file(input: 'include/freetype/config/ftoption.h.in', + output: 'ftoption.h', + configuration: conf)] + +libfreetype = static_library('freetype', base_sources, + include_directories: incbase, + dependencies: deps, + c_args: c_args, + gnu_symbol_visibility: 'inlineshidden', +) + +freetype_dep = declare_dependency( + link_with: libfreetype, + include_directories : incbase, + dependencies: deps, +) diff --git a/subprojects/packagefiles/qhull-2020.2/meson.build b/subprojects/packagefiles/qhull-2020.2/meson.build new file mode 100644 index 000000000000..3d20c5d2ae55 --- /dev/null +++ b/subprojects/packagefiles/qhull-2020.2/meson.build @@ -0,0 +1,31 @@ +project('qhull', 'c', + version: '8.0.2', + license: 'Qhull') + +qhull_inc = include_directories('src') +qhull_lib = static_library('qhull_r', + 'src/libqhull_r/geom2_r.c', + 'src/libqhull_r/geom_r.c', + 'src/libqhull_r/global_r.c', + 'src/libqhull_r/io_r.c', + 'src/libqhull_r/libqhull_r.c', + 'src/libqhull_r/mem_r.c', + 'src/libqhull_r/merge_r.c', + 'src/libqhull_r/poly2_r.c', + 'src/libqhull_r/poly_r.c', + 'src/libqhull_r/qset_r.c', + 'src/libqhull_r/random_r.c', + 'src/libqhull_r/rboxlib_r.c', + 'src/libqhull_r/stat_r.c', + 'src/libqhull_r/usermem_r.c', + 'src/libqhull_r/userprintf_rbox_r.c', + 'src/libqhull_r/userprintf_r.c', + 'src/libqhull_r/user_r.c', + gnu_symbol_visibility: 'inlineshidden', +) + +qhull_dep = declare_dependency( + include_directories: qhull_inc, + link_with: qhull_lib) + +meson.override_dependency('qhull_r', qhull_dep) diff --git a/subprojects/qhull.wrap b/subprojects/qhull.wrap new file mode 100644 index 000000000000..f43ead300834 --- /dev/null +++ b/subprojects/qhull.wrap @@ -0,0 +1,9 @@ +[wrap-file] +# Also bump the cache key in `.circleci/config.yml`. +# Also update the docs in `docs/devel/dependencies.rst`. +directory = qhull-2020.2 +source_url = http://www.qhull.org/download/qhull-2020-src-8.0.2.tgz +source_filename = qhull-2020-src-8.0.2.tgz +source_hash = b5c2d7eb833278881b952c8a52d20179eab87766b00b865000469a45c1838b7e + +patch_directory = qhull-2020.2 From 7d85c0e1e942be5c65118a678c78605ef9533089 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 24 Aug 2023 03:28:22 -0400 Subject: [PATCH 0228/1120] BLD: Install Python files via Meson --- .flake8 | 2 - lib/matplotlib/_api/meson.build | 12 + lib/matplotlib/axes/meson.build | 16 + lib/matplotlib/backends/meson.build | 49 ++ lib/matplotlib/backends/qt_editor/meson.build | 11 + .../backends/web_backend/meson.build | 14 + lib/matplotlib/meson.build | 154 ++++ lib/matplotlib/projections/meson.build | 14 + lib/matplotlib/sphinxext/meson.build | 12 + lib/matplotlib/style/meson.build | 11 + lib/matplotlib/testing/jpl_units/meson.build | 16 + lib/matplotlib/testing/meson.build | 22 + lib/matplotlib/tests/meson.build | 113 +++ lib/matplotlib/tri/meson.build | 25 + lib/meson.build | 8 + lib/mpl_toolkits/axes_grid1/meson.build | 15 + lib/mpl_toolkits/axes_grid1/tests/meson.build | 12 + lib/mpl_toolkits/axisartist/meson.build | 18 + lib/mpl_toolkits/axisartist/tests/meson.build | 17 + lib/mpl_toolkits/meson.build | 3 + lib/mpl_toolkits/mplot3d/meson.build | 11 + lib/mpl_toolkits/mplot3d/tests/meson.build | 14 + meson.build | 4 +- meson_options.txt | 11 + mplsetup.cfg.template | 38 - pyproject.toml | 39 +- setup.cfg | 5 - setup.py | 290 ------- setupext.py | 799 ------------------ 29 files changed, 592 insertions(+), 1163 deletions(-) create mode 100644 lib/matplotlib/_api/meson.build create mode 100644 lib/matplotlib/axes/meson.build create mode 100644 lib/matplotlib/backends/meson.build create mode 100644 lib/matplotlib/backends/qt_editor/meson.build create mode 100644 lib/matplotlib/backends/web_backend/meson.build create mode 100644 lib/matplotlib/meson.build create mode 100644 lib/matplotlib/projections/meson.build create mode 100644 lib/matplotlib/sphinxext/meson.build create mode 100644 lib/matplotlib/style/meson.build create mode 100644 lib/matplotlib/testing/jpl_units/meson.build create mode 100644 lib/matplotlib/testing/meson.build create mode 100644 lib/matplotlib/tests/meson.build create mode 100644 lib/matplotlib/tri/meson.build create mode 100644 lib/meson.build create mode 100644 lib/mpl_toolkits/axes_grid1/meson.build create mode 100644 lib/mpl_toolkits/axes_grid1/tests/meson.build create mode 100644 lib/mpl_toolkits/axisartist/meson.build create mode 100644 lib/mpl_toolkits/axisartist/tests/meson.build create mode 100644 lib/mpl_toolkits/meson.build create mode 100644 lib/mpl_toolkits/mplot3d/meson.build create mode 100644 lib/mpl_toolkits/mplot3d/tests/meson.build delete mode 100644 mplsetup.cfg.template delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 setupext.py diff --git a/.flake8 b/.flake8 index ee739cdf4231..00cd213e0f79 100644 --- a/.flake8 +++ b/.flake8 @@ -31,8 +31,6 @@ exclude = .eggs per-file-ignores = - setup.py: E402 - lib/matplotlib/__init__.py: E402, F401 lib/matplotlib/_animation_data.py: E501 lib/matplotlib/_api/__init__.py: F401 diff --git a/lib/matplotlib/_api/meson.build b/lib/matplotlib/_api/meson.build new file mode 100644 index 000000000000..57aa234ab878 --- /dev/null +++ b/lib/matplotlib/_api/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'deprecation.py', +] + +typing_sources = [ + '__init__.pyi', + 'deprecation.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/_api') diff --git a/lib/matplotlib/axes/meson.build b/lib/matplotlib/axes/meson.build new file mode 100644 index 000000000000..8de3c9c382d7 --- /dev/null +++ b/lib/matplotlib/axes/meson.build @@ -0,0 +1,16 @@ +python_sources = [ + '__init__.py', + '_axes.py', + '_base.py', + '_secondary_axes.py', +] + +typing_sources = [ + '__init__.pyi', + '_axes.pyi', + '_base.pyi', + '_secondary_axes.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/axes') diff --git a/lib/matplotlib/backends/meson.build b/lib/matplotlib/backends/meson.build new file mode 100644 index 000000000000..050cc616b42c --- /dev/null +++ b/lib/matplotlib/backends/meson.build @@ -0,0 +1,49 @@ +python_sources = [ + '__init__.py', + 'backend_agg.py', + 'backend_cairo.py', + '_backend_gtk.py', + 'backend_gtk3.py', + 'backend_gtk3agg.py', + 'backend_gtk3cairo.py', + 'backend_gtk4.py', + 'backend_gtk4agg.py', + 'backend_gtk4cairo.py', + 'backend_macosx.py', + 'backend_mixed.py', + 'backend_nbagg.py', + '_backend_pdf_ps.py', + 'backend_pdf.py', + 'backend_pgf.py', + 'backend_ps.py', + 'backend_qt.py', + 'backend_qtagg.py', + 'backend_qtcairo.py', + 'backend_qt5.py', + 'backend_qt5agg.py', + 'backend_qt5cairo.py', + 'backend_svg.py', + 'backend_template.py', + '_backend_tk.py', + 'backend_tkagg.py', + 'backend_tkcairo.py', + 'backend_webagg.py', + 'backend_webagg_core.py', + 'backend_wx.py', + 'backend_wxagg.py', + 'backend_wxcairo.py', + 'qt_compat.py', +] + +typing_sources = [ + # Compiled extension types. + '_backend_agg.pyi', + '_macosx.pyi', + '_tkagg.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/backends') + +subdir('qt_editor') +subdir('web_backend') diff --git a/lib/matplotlib/backends/qt_editor/meson.build b/lib/matplotlib/backends/qt_editor/meson.build new file mode 100644 index 000000000000..1f4ea9cd4691 --- /dev/null +++ b/lib/matplotlib/backends/qt_editor/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + '_formlayout.py', + 'figureoptions.py', +] + +typing_sources = [ +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/backends/qt_editor') diff --git a/lib/matplotlib/backends/web_backend/meson.build b/lib/matplotlib/backends/web_backend/meson.build new file mode 100644 index 000000000000..f8025d1742d3 --- /dev/null +++ b/lib/matplotlib/backends/web_backend/meson.build @@ -0,0 +1,14 @@ +install_dir = py3.get_install_dir(subdir: 'matplotlib/backends/web_backend') + +install_data( + 'all_figures.html', + 'ipython_inline_figure.html', + 'single_figure.html', + install_tag: 'data', + install_dir: install_dir) +install_subdir('css', install_tag: 'data', install_dir: install_dir) +install_subdir('js', install_tag: 'data', install_dir: install_dir) +install_data( + 'nbagg_uat.ipynb', + install_tag: 'tests', + install_dir: install_dir) diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build new file mode 100644 index 000000000000..4f8d93d1f01d --- /dev/null +++ b/lib/matplotlib/meson.build @@ -0,0 +1,154 @@ +python_sources = [ + '__init__.py', + '_afm.py', + '_animation_data.py', + '_blocking_input.py', + '_cm.py', + '_cm_listed.py', + '_color_data.py', + '_constrained_layout.py', + '_docstring.py', + '_enums.py', + '_fontconfig_pattern.py', + '_internal_utils.py', + '_layoutgrid.py', + '_mathtext.py', + '_mathtext_data.py', + '_pylab_helpers.py', + '_text_helpers.py', + '_tight_bbox.py', + '_tight_layout.py', + '_type1font.py', + 'animation.py', + 'artist.py', + 'axis.py', + 'backend_bases.py', + 'backend_managers.py', + 'backend_tools.py', + 'bezier.py', + 'category.py', + 'cbook.py', + 'cm.py', + 'collections.py', + 'colorbar.py', + 'colors.py', + 'container.py', + 'contour.py', + 'dates.py', + 'dviread.py', + 'figure.py', + 'font_manager.py', + 'gridspec.py', + 'hatch.py', + 'image.py', + 'layout_engine.py', + 'legend_handler.py', + 'legend.py', + 'lines.py', + 'markers.py', + 'mathtext.py', + 'mlab.py', + 'offsetbox.py', + 'patches.py', + 'patheffects.py', + 'path.py', + 'pylab.py', + 'pyplot.py', + 'quiver.py', + 'rcsetup.py', + 'sankey.py', + 'scale.py', + 'spines.py', + 'stackplot.py', + 'streamplot.py', + 'table.py', + 'texmanager.py', + 'textpath.py', + 'text.py', + 'ticker.py', + 'transforms.py', + 'typing.py', + 'units.py', + 'widgets.py', +] + +typing_sources = [ + 'py.typed', + # Compiled extension types. + '_c_internal_utils.pyi', + 'ft2font.pyi', + '_image.pyi', + '_qhull.pyi', + '_tri.pyi', + # Pure Python types. + '__init__.pyi', + '_color_data.pyi', + '_docstring.pyi', + '_enums.pyi', + '_path.pyi', + '_pylab_helpers.pyi', + '_ttconv.pyi', + 'animation.pyi', + 'artist.pyi', + 'axis.pyi', + 'backend_bases.pyi', + 'backend_managers.pyi', + 'backend_tools.pyi', + 'bezier.pyi', + 'cbook.pyi', + 'cm.pyi', + 'collections.pyi', + 'colorbar.pyi', + 'colors.pyi', + 'container.pyi', + 'contour.pyi', + 'dviread.pyi', + 'figure.pyi', + 'font_manager.pyi', + 'gridspec.pyi', + 'hatch.pyi', + 'image.pyi', + 'layout_engine.pyi', + 'legend_handler.pyi', + 'legend.pyi', + 'lines.pyi', + 'markers.pyi', + 'mathtext.pyi', + 'mlab.pyi', + 'offsetbox.pyi', + 'patches.pyi', + 'patheffects.pyi', + 'path.pyi', + 'quiver.pyi', + 'rcsetup.pyi', + 'sankey.pyi', + 'scale.pyi', + 'spines.pyi', + 'stackplot.pyi', + 'streamplot.pyi', + 'table.pyi', + 'texmanager.pyi', + 'textpath.pyi', + 'text.pyi', + 'ticker.pyi', + 'transforms.pyi', + 'widgets.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib') + +subdir('_api') +subdir('axes') +subdir('backends') +subdir('projections') +subdir('sphinxext') +subdir('style') +subdir('testing') +subdir('tests') +subdir('tri') + +install_subdir( + 'mpl-data', + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib')) diff --git a/lib/matplotlib/projections/meson.build b/lib/matplotlib/projections/meson.build new file mode 100644 index 000000000000..221b93efadee --- /dev/null +++ b/lib/matplotlib/projections/meson.build @@ -0,0 +1,14 @@ +python_sources = [ + '__init__.py', + 'geo.py', + 'polar.py', +] + +typing_sources = [ + '__init__.pyi', + 'geo.pyi', + 'polar.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/projections') diff --git a/lib/matplotlib/sphinxext/meson.build b/lib/matplotlib/sphinxext/meson.build new file mode 100644 index 000000000000..5dc7388384eb --- /dev/null +++ b/lib/matplotlib/sphinxext/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'figmpl_directive.py', + 'mathmpl.py', + 'plot_directive.py', +] + +typing_sources = [ +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/sphinxext') diff --git a/lib/matplotlib/style/meson.build b/lib/matplotlib/style/meson.build new file mode 100644 index 000000000000..03e7972132bb --- /dev/null +++ b/lib/matplotlib/style/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + 'core.py', +] + +typing_sources = [ + 'core.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/style') diff --git a/lib/matplotlib/testing/jpl_units/meson.build b/lib/matplotlib/testing/jpl_units/meson.build new file mode 100644 index 000000000000..c950f0bfa4dd --- /dev/null +++ b/lib/matplotlib/testing/jpl_units/meson.build @@ -0,0 +1,16 @@ +python_sources = [ + '__init__.py', + 'Duration.py', + 'EpochConverter.py', + 'Epoch.py', + 'StrConverter.py', + 'UnitDblConverter.py', + 'UnitDblFormatter.py', + 'UnitDbl.py', +] + +typing_sources = [ +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/testing/jpl_units') diff --git a/lib/matplotlib/testing/meson.build b/lib/matplotlib/testing/meson.build new file mode 100644 index 000000000000..1016f81941ca --- /dev/null +++ b/lib/matplotlib/testing/meson.build @@ -0,0 +1,22 @@ +python_sources = [ + '__init__.py', + '_markers.py', + 'compare.py', + 'conftest.py', + 'decorators.py', + 'exceptions.py', + 'widgets.py', +] + +typing_sources = [ + '__init__.pyi', + 'compare.pyi', + 'conftest.pyi', + 'decorators.pyi', + 'widgets.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/testing') + +subdir('jpl_units') diff --git a/lib/matplotlib/tests/meson.build b/lib/matplotlib/tests/meson.build new file mode 100644 index 000000000000..148a40f11a2f --- /dev/null +++ b/lib/matplotlib/tests/meson.build @@ -0,0 +1,113 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_afm.py', + 'test_agg_filter.py', + 'test_agg.py', + 'test_animation.py', + 'test_api.py', + 'test_arrow_patches.py', + 'test_artist.py', + 'test_axes.py', + 'test_axis.py', + 'test_backend_bases.py', + 'test_backend_cairo.py', + 'test_backend_gtk3.py', + 'test_backend_macosx.py', + 'test_backend_nbagg.py', + 'test_backend_pdf.py', + 'test_backend_pgf.py', + 'test_backend_ps.py', + 'test_backend_qt.py', + 'test_backends_interactive.py', + 'test_backend_svg.py', + 'test_backend_template.py', + 'test_backend_tk.py', + 'test_backend_tools.py', + 'test_backend_webagg.py', + 'test_basic.py', + 'test_bbox_tight.py', + 'test_category.py', + 'test_cbook.py', + 'test_collections.py', + 'test_colorbar.py', + 'test_colors.py', + 'test_compare_images.py', + 'test_constrainedlayout.py', + 'test_container.py', + 'test_contour.py', + 'test_cycles.py', + 'test_dates.py', + 'test_datetime.py', + 'test_determinism.py', + 'test_doc.py', + 'test_dviread.py', + 'test_figure.py', + 'test_fontconfig_pattern.py', + 'test_font_manager.py', + 'test_ft2font.py', + 'test_getattr.py', + 'test_gridspec.py', + 'test_image.py', + 'test_legend.py', + 'test_lines.py', + 'test_marker.py', + 'test_mathtext.py', + 'test_matplotlib.py', + 'test_mlab.py', + 'test_offsetbox.py', + 'test_patches.py', + 'test_patheffects.py', + 'test_path.py', + 'test_pickle.py', + 'test_png.py', + 'test_polar.py', + 'test_preprocess_data.py', + 'test_pyplot.py', + 'test_quiver.py', + 'test_rcparams.py', + 'test_sankey.py', + 'test_scale.py', + 'test_simplification.py', + 'test_skew.py', + 'test_sphinxext.py', + 'test_spines.py', + 'test_streamplot.py', + 'test_style.py', + 'test_subplots.py', + 'test_table.py', + 'test_testing.py', + 'test_texmanager.py', + 'test_textpath.py', + 'test_text.py', + 'test_ticker.py', + 'test_tightlayout.py', + 'test_transforms.py', + 'test_triangulation.py', + 'test_ttconv.py', + 'test_type1font.py', + 'test_units.py', + 'test_usetex.py', + 'test_widgets.py', +] + +py3.install_sources(python_sources, + subdir: 'matplotlib/tests') + +install_data( + 'README', + 'Courier10PitchBT-Bold.pfb', + 'cmr10.pfb', + 'mpltest.ttf', + 'test_nbagg_01.ipynb', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests/')) + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests')) +install_subdir( + 'tinypages', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests')) diff --git a/lib/matplotlib/tri/meson.build b/lib/matplotlib/tri/meson.build new file mode 100644 index 000000000000..9e71f3348c69 --- /dev/null +++ b/lib/matplotlib/tri/meson.build @@ -0,0 +1,25 @@ +python_sources = [ + '__init__.py', + '_triangulation.py', + '_tricontour.py', + '_trifinder.py', + '_triinterpolate.py', + '_tripcolor.py', + '_triplot.py', + '_trirefine.py', + '_tritools.py', +] + +typing_sources = [ + '_triangulation.pyi', + '_tricontour.pyi', + '_trifinder.pyi', + '_triinterpolate.pyi', + '_tripcolor.pyi', + '_triplot.pyi', + '_trirefine.pyi', + '_tritools.pyi', +] + +py3.install_sources(python_sources, typing_sources, + subdir: 'matplotlib/tri') diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 000000000000..9701a7da98dd --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,8 @@ +python_sources = [ + 'pylab.py', +] + +py3.install_sources(python_sources) + +subdir('matplotlib') +subdir('mpl_toolkits') diff --git a/lib/mpl_toolkits/axes_grid1/meson.build b/lib/mpl_toolkits/axes_grid1/meson.build new file mode 100644 index 000000000000..7dd5c3861163 --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/meson.build @@ -0,0 +1,15 @@ +python_sources = [ + '__init__.py', + 'anchored_artists.py', + 'axes_divider.py', + 'axes_grid.py', + 'axes_rgb.py', + 'axes_size.py', + 'inset_locator.py', + 'mpl_axes.py', + 'parasite_axes.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axes_grid1') + +subdir('tests') diff --git a/lib/mpl_toolkits/axes_grid1/tests/meson.build b/lib/mpl_toolkits/axes_grid1/tests/meson.build new file mode 100644 index 000000000000..980bf173cc10 --- /dev/null +++ b/lib/mpl_toolkits/axes_grid1/tests/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_axes_grid1.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axes_grid1/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/axes_grid1/tests')) diff --git a/lib/mpl_toolkits/axisartist/meson.build b/lib/mpl_toolkits/axisartist/meson.build new file mode 100644 index 000000000000..8d9314e42576 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/meson.build @@ -0,0 +1,18 @@ +python_sources = [ + '__init__.py', + 'angle_helper.py', + 'axes_divider.py', + 'axes_grid.py', + 'axes_rgb.py', + 'axis_artist.py', + 'axislines.py', + 'axisline_style.py', + 'floating_axes.py', + 'grid_finder.py', + 'grid_helper_curvelinear.py', + 'parasite_axes.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axisartist') + +subdir('tests') diff --git a/lib/mpl_toolkits/axisartist/tests/meson.build b/lib/mpl_toolkits/axisartist/tests/meson.build new file mode 100644 index 000000000000..634c8190a037 --- /dev/null +++ b/lib/mpl_toolkits/axisartist/tests/meson.build @@ -0,0 +1,17 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_angle_helper.py', + 'test_axis_artist.py', + 'test_axislines.py', + 'test_floating_axes.py', + 'test_grid_finder.py', + 'test_grid_helper_curvelinear.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/axisartist/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/axisartist/tests')) diff --git a/lib/mpl_toolkits/meson.build b/lib/mpl_toolkits/meson.build new file mode 100644 index 000000000000..290cd6139f94 --- /dev/null +++ b/lib/mpl_toolkits/meson.build @@ -0,0 +1,3 @@ +subdir('axes_grid1') +subdir('axisartist') +subdir('mplot3d') diff --git a/lib/mpl_toolkits/mplot3d/meson.build b/lib/mpl_toolkits/mplot3d/meson.build new file mode 100644 index 000000000000..2d9cade6c93c --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + 'art3d.py', + 'axes3d.py', + 'axis3d.py', + 'proj3d.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/mplot3d') + +subdir('tests') diff --git a/lib/mpl_toolkits/mplot3d/tests/meson.build b/lib/mpl_toolkits/mplot3d/tests/meson.build new file mode 100644 index 000000000000..7a638aedf72b --- /dev/null +++ b/lib/mpl_toolkits/mplot3d/tests/meson.build @@ -0,0 +1,14 @@ +python_sources = [ + '__init__.py', + 'conftest.py', + 'test_art3d.py', + 'test_axes3d.py', + 'test_legend3d.py', +] + +py3.install_sources(python_sources, subdir: 'mpl_toolkits/mplot3d/tests') + +install_subdir( + 'baseline_images', + install_tag: 'tests', + install_dir: py3.get_install_dir(subdir: 'mpl_toolkits/mplot3d/tests')) diff --git a/meson.build b/meson.build index c294eea2a678..1ba673948a1e 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ project( # Carlogo, STIX and Computer Modern is OFL # DejaVu is Bitstream Vera and Public Domain license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', + meson_version: '>=1.1.0', default_options: [ 'b_lto=true', 'cpp_std=c++11', @@ -19,10 +20,11 @@ cpp = meson.get_compiler('cpp') # https://mesonbuild.com/Python-module.html py_mod = import('python') -py3 = py_mod.find_installation() +py3 = py_mod.find_installation(pure: false) py3_dep = py3.dependency() pybind11_dep = dependency('pybind11', version: '>=2.6') subdir('extern') subdir('src') +subdir('lib') diff --git a/meson_options.txt b/meson_options.txt index 0b10c331ae48..ed753bb26854 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -15,3 +15,14 @@ option('system-qhull', type: 'boolean', value: false, # of the config value. option('macosx', type: 'boolean', value: true, description: 'Enable MacOSX backend (requires Cocoa)') + +# User-configurable options +# +# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, +# MacOSX, Pdf, Ps, QtAgg, QtCairo, SVG, TkAgg, WX, WXAgg. +# +# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do +# not choose MacOSX if you have disabled the relevant extension modules. The +# default is determined by fallback. +option('rcParams-backend', type: 'string', value: 'auto', + description: 'Set default backend at runtime') diff --git a/mplsetup.cfg.template b/mplsetup.cfg.template deleted file mode 100644 index 30985b2e313d..000000000000 --- a/mplsetup.cfg.template +++ /dev/null @@ -1,38 +0,0 @@ -# Rename this file to mplsetup.cfg to modify Matplotlib's build options. - -[libs] -# By default, Matplotlib builds with LTO, which may be slow if you re-compile -# often, and don't need the space saving/speedup. -# -#enable_lto = True -# -# By default, Matplotlib downloads and builds its own copies of FreeType and of -# Qhull. You may set the following to True to instead link against a system -# FreeType/Qhull. As an exception, Matplotlib defaults to the system version -# of FreeType on AIX. -# -#system_freetype = False -#system_qhull = False - -[packages] -# Some of Matplotlib's components are optional: the MacOSX backend (installed -# by default on MacOSX; requires the Cocoa headers included with XCode), and -# the test data (i.e., the baseline image files; not installed by default). -# You can control whether they are installed by uncommenting the following -# lines. Note that the MacOSX backend is never built on Linux or Windows, -# regardless of the config value. -# -#tests = False -#macosx = True - -[rc_options] -# User-configurable options -# -# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, -# MacOSX, Pdf, Ps, QtAgg, QtCairo, SVG, TkAgg, WX, WXAgg. -# -# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do -# not choose MacOSX if you have disabled the relevant extension modules. The -# default is determined by fallback. -# -#backend = Agg diff --git a/pyproject.toml b/pyproject.toml index 423d6352ae8f..37d97e02b956 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,8 +40,6 @@ dependencies = [ "pillow >= 8", "pyparsing >= 2.3.1", "python-dateutil >= 2.7", - "setuptools >= 64", - "setuptools_scm >= 7", "importlib-resources >= 3.2.0; python_version < '3.10'", ] requires-python = ">=3.9" @@ -49,11 +47,16 @@ requires-python = ">=3.9" [project.optional-dependencies] # Should be a copy of the build dependencies below. dev = [ - "certifi>=2020.06.20", + "meson-python>=0.13.1", "numpy>=1.25", "pybind11>=2.6", - "setuptools>=64", "setuptools_scm>=7", + # Not required by us but setuptools_scm without a version, cso _if_ + # installed, then setuptools_scm 8 requires at least this version. + # Unfortunately, we can't do a sort of minimum-if-instaled dependency, so + # we need to keep this for now until setuptools_scm _fully_ drops + # setuptools. + "setuptools>=64", ] [project.urls] @@ -66,35 +69,17 @@ dev = [ "Donate" = "https://numfocus.org/donate-to-matplotlib" [build-system] -build-backend = "setuptools.build_meta" +build-backend = "mesonpy" # Also keep in sync with optional dependencies above. requires = [ - "certifi>=2020.06.20", + "meson-python>=0.13.1", "numpy>=1.25", "pybind11>=2.6", - "setuptools>=64", "setuptools_scm>=7", ] -[tool.setuptools] -platforms = ["any"] -py-modules = ["pylab"] -license-files = ["LICENSE/*"] -namespace-packages = ["mpl_toolkits"] - -[tool.setuptools.packages.find] -where = ["lib"] -include = ["matplotlib*", "mpl_toolkits*"] -exclude = [ - "matplotlib.tests*", - "mpl_toolkits.axes_grid1.tests*", - "mpl_toolkits.axisartist.tests*", - "mpl_toolkits.mplot3d.tests*", -] -namespaces = true - -[tool.setuptools.exclude-package-data] -"*" = ["*.png", "*.svg"] +[tool.meson-python.args] +install = ['--tags=data,python-runtime,runtime'] [tool.setuptools_scm] version_scheme = "release-branch-semver" @@ -171,8 +156,6 @@ target-version = "py39" convention = "numpy" [tool.ruff.per-file-ignores] -"setup.py" = ["E402"] - "doc/conf.py" = ["E402"] "galleries/examples/animation/frame_grabbing_sgskip.py" = ["E402"] "galleries/examples/lines_bars_and_markers/marker_reference.py" = ["E402"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9d4cf0e7b72c..000000000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -# NOTE: Matplotlib-specific configuration options have been moved to -# mplsetup.cfg.template. - -[metadata] -license_files = LICENSE/* diff --git a/setup.py b/setup.py deleted file mode 100644 index 66698a3aed6c..000000000000 --- a/setup.py +++ /dev/null @@ -1,290 +0,0 @@ -""" -The Matplotlib build options can be modified with a mplsetup.cfg file. See -mplsetup.cfg.template for more information. -""" - -# NOTE: This file must remain Python 2 compatible for the foreseeable future, -# to ensure that we error out properly for people with outdated setuptools -# and/or pip. -import sys - -py_min_version = (3, 9) # minimal supported python version -since_mpl_version = (3, 8) # py_min_version is required since this mpl version - -if sys.version_info < py_min_version: - error = """ -Beginning with Matplotlib {0}, Python {1} or above is required. -You are using Python {2}. - -This may be due to an out of date pip. - -Make sure you have pip >= 9.0.1. -""".format('.'.join(str(n) for n in since_mpl_version), - '.'.join(str(n) for n in py_min_version), - '.'.join(str(n) for n in sys.version_info[:3])) - sys.exit(error) - -import os -from pathlib import Path -import shutil -import subprocess - -from setuptools import setup, Distribution, Extension -import setuptools.command.build_ext -import setuptools.command.build_py -import setuptools.command.sdist - -# sys.path modified to find setupext.py during pyproject.toml builds. -sys.path.append(str(Path(__file__).resolve().parent)) - -import setupext -from setupext import print_raw, print_status - - -# These are the packages in the order we want to display them. -mpl_packages = [ - setupext.Matplotlib(), - setupext.Python(), - setupext.Platform(), - setupext.FreeType(), - setupext.Qhull(), - setupext.Tests(), - setupext.BackendMacOSX(), - ] - - -# From https://bugs.python.org/issue26689 -def has_flag(self, flagname): - """Return whether a flag name is supported on the specified compiler.""" - import tempfile - with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f: - f.write('int main (int argc, char **argv) { return 0; }') - try: - self.compile([f.name], extra_postargs=[flagname]) - except Exception as exc: - # https://github.com/pypa/setuptools/issues/2698 - if type(exc).__name__ != "CompileError": - raise - return False - return True - - -class BuildExtraLibraries(setuptools.command.build_ext.build_ext): - def finalize_options(self): - # If coverage is enabled then need to keep the .o and .gcno files in a - # non-temporary directory otherwise coverage info not collected. - cppflags = os.getenv('CPPFLAGS') - if cppflags and '--coverage' in cppflags: - self.build_temp = 'build' - - self.distribution.ext_modules[:] = [ - ext - for package in good_packages - for ext in package.get_extensions() - ] - super().finalize_options() - - def add_optimization_flags(self): - """ - Add optional optimization flags to extension. - - This adds flags for LTO and hidden visibility to both compiled - extensions, and to the environment variables so that vendored libraries - will also use them. If the compiler does not support these flags, then - none are added. - """ - - env = os.environ.copy() - if sys.platform == 'win32': - return env - enable_lto = setupext.config.getboolean('libs', 'enable_lto', - fallback=None) - - def prepare_flags(name, enable_lto): - """ - Prepare *FLAGS from the environment. - - If set, return them, and also check whether LTO is disabled in each - one, raising an error if Matplotlib config explicitly enabled LTO. - """ - if name in os.environ: - if '-fno-lto' in os.environ[name]: - if enable_lto is True: - raise ValueError('Configuration enable_lto=True, but ' - '{0} contains -fno-lto'.format(name)) - enable_lto = False - return [os.environ[name]], enable_lto - return [], enable_lto - - _, enable_lto = prepare_flags('CFLAGS', enable_lto) # Only check lto. - cppflags, enable_lto = prepare_flags('CPPFLAGS', enable_lto) - cxxflags, enable_lto = prepare_flags('CXXFLAGS', enable_lto) - ldflags, enable_lto = prepare_flags('LDFLAGS', enable_lto) - - if enable_lto is False: - return env - - if has_flag(self.compiler, '-fvisibility=hidden'): - for ext in self.extensions: - ext.extra_compile_args.append('-fvisibility=hidden') - cppflags.append('-fvisibility=hidden') - if has_flag(self.compiler, '-fvisibility-inlines-hidden'): - for ext in self.extensions: - if self.compiler.detect_language(ext.sources) != 'cpp': - continue - ext.extra_compile_args.append('-fvisibility-inlines-hidden') - cxxflags.append('-fvisibility-inlines-hidden') - ranlib = 'RANLIB' in env - if not ranlib and self.compiler.compiler_type == 'unix': - try: - result = subprocess.run(self.compiler.compiler + - ['--version'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True) - except Exception: - pass - else: - version = result.stdout.lower() - if 'gcc' in version: - ranlib = shutil.which('gcc-ranlib') - elif 'clang' in version: - if sys.platform == 'darwin': - ranlib = True - else: - ranlib = shutil.which('llvm-ranlib') - if ranlib and has_flag(self.compiler, '-flto'): - for ext in self.extensions: - ext.extra_compile_args.append('-flto') - cppflags.append('-flto') - ldflags.append('-flto') - # Needed so FreeType static library doesn't lose its LTO objects. - if isinstance(ranlib, str): - env['RANLIB'] = ranlib - - env['CPPFLAGS'] = ' '.join(cppflags) - env['CXXFLAGS'] = ' '.join(cxxflags) - env['LDFLAGS'] = ' '.join(ldflags) - - return env - - def build_extensions(self): - if (self.compiler.compiler_type == 'msvc' and - os.environ.get('MPL_DISABLE_FH4')): - # Disable FH4 Exception Handling implementation so that we don't - # require VCRUNTIME140_1.dll. For more details, see: - # https://devblogs.microsoft.com/cppblog/making-cpp-exception-handling-smaller-x64/ - # https://github.com/joerick/cibuildwheel/issues/423#issuecomment-677763904 - for ext in self.extensions: - ext.extra_compile_args.append('/d2FH4-') - - env = self.add_optimization_flags() - for package in good_packages: - package.do_custom_build(env) - # Make sure we don't accidentally use too modern C++ constructs, even - # though modern compilers default to enabling them. Enabling this for - # a single platform is enough; also only do this for C++-only - # extensions as clang refuses to compile C/ObjC with -std=c++11. - if sys.platform != "win32": - for ext in self.distribution.ext_modules[:]: - if not any(src.endswith((".c", ".m")) for src in ext.sources): - ext.extra_compile_args.append("-std=c++11") - return super().build_extensions() - - def build_extension(self, ext): - # When C coverage is enabled, the path to the object file is saved. - # Since we re-use source files in multiple extensions, libgcov will - # complain at runtime that it is trying to save coverage for the same - # object file at different timestamps (since each source is compiled - # again for each extension). Thus, we need to use unique temporary - # build directories to store object files for each extension. - orig_build_temp = self.build_temp - self.build_temp = os.path.join(self.build_temp, ext.name) - try: - super().build_extension(ext) - finally: - self.build_temp = orig_build_temp - - -def update_matplotlibrc(path): - # If packagers want to change the default backend, insert a `#backend: ...` - # line. Otherwise, use the default `##backend: Agg` which has no effect - # even after decommenting, which allows _auto_backend_sentinel to be filled - # in at import time. - template_lines = path.read_text(encoding="utf-8").splitlines(True) - backend_line_idx, = [ # Also asserts that there is a single such line. - idx for idx, line in enumerate(template_lines) - if "#backend:" in line] - template_lines[backend_line_idx] = ( - "#backend: {}\n".format(setupext.options["backend"]) - if setupext.options["backend"] - else "##backend: Agg\n") - path.write_text("".join(template_lines), encoding="utf-8") - - -class BuildPy(setuptools.command.build_py.build_py): - def run(self): - super().run() - if not getattr(self, 'editable_mode', False): - update_matplotlibrc( - Path(self.build_lib, "matplotlib/mpl-data/matplotlibrc")) - - -class Sdist(setuptools.command.sdist.sdist): - def make_release_tree(self, base_dir, files): - super().make_release_tree(base_dir, files) - update_matplotlibrc( - Path(base_dir, "lib/matplotlib/mpl-data/matplotlibrc")) - -# Start with type hint data -# Will be further filled below by the various components. -package_data = {"matplotlib": ["py.typed", "**/*.pyi"]} - -# If the user just queries for information, don't bother figuring out which -# packages to build or install. -if not (any('--' + opt in sys.argv - for opt in Distribution.display_option_names + ['help']) - or 'clean' in sys.argv): - # Go through all of the packages and figure out which ones we are - # going to build/install. - print_raw() - print_raw("Edit mplsetup.cfg to change the build options; " - "suppress output with --quiet.") - print_raw() - print_raw("BUILDING MATPLOTLIB") - - good_packages = [] - for package in mpl_packages: - try: - message = package.check() - except setupext.Skipped as e: - print_status(package.name, "no [{e}]".format(e=e)) - continue - if message is not None: - print_status(package.name, - "yes [{message}]".format(message=message)) - good_packages.append(package) - - print_raw() - - # Now collect all of the information we need to build all of the packages. - for package in good_packages: - # Extension modules only get added in build_ext, as numpy will have - # been installed (as setup_requires) at that point. - data = package.get_package_data() - for key, val in data.items(): - package_data.setdefault(key, []) - package_data[key] = list(set(val + package_data[key])) - -setup( # Finally, pass this all along to setuptools to do the heavy lifting. - # Dummy extension to trigger build_ext, which will swap it out with - # real extensions that can depend on numpy for the build. - ext_modules=[Extension("", [])], - package_data=package_data, - - cmdclass={ - "build_ext": BuildExtraLibraries, - "build_py": BuildPy, - "sdist": Sdist, - }, -) diff --git a/setupext.py b/setupext.py deleted file mode 100644 index 049f46c65c7d..000000000000 --- a/setupext.py +++ /dev/null @@ -1,799 +0,0 @@ -import configparser -import functools -import hashlib -from io import BytesIO -import logging -import os -from pathlib import Path -import platform -import shlex -import shutil -import subprocess -import sys -import sysconfig -import tarfile -from tempfile import TemporaryDirectory -import textwrap -import urllib.request - -from pybind11.setup_helpers import Pybind11Extension -from setuptools import Distribution, Extension - -_log = logging.getLogger(__name__) - - -def _get_xdg_cache_dir(): - """ - Return the `XDG cache directory`__. - - __ https://specifications.freedesktop.org/basedir-spec/latest/ - """ - cache_dir = os.environ.get('XDG_CACHE_HOME') - if not cache_dir: - cache_dir = os.path.expanduser('~/.cache') - if cache_dir.startswith('~/'): # Expansion failed. - return None - return Path(cache_dir, 'matplotlib') - - -def _get_hash(data): - """Compute the sha256 hash of *data*.""" - hasher = hashlib.sha256() - hasher.update(data) - return hasher.hexdigest() - - -@functools.cache -def _get_ssl_context(): - import certifi - import ssl - return ssl.create_default_context(cafile=certifi.where()) - - -def get_from_cache_or_download(url, sha): - """ - Get bytes from the given url or local cache. - - Parameters - ---------- - url : str - The url to download. - sha : str - The sha256 of the file. - - Returns - ------- - BytesIO - The file loaded into memory. - """ - cache_dir = _get_xdg_cache_dir() - - if cache_dir is not None: # Try to read from cache. - try: - data = (cache_dir / sha).read_bytes() - except OSError: - pass - else: - if _get_hash(data) == sha: - return BytesIO(data) - - # jQueryUI's website blocks direct downloads from urllib.request's - # default User-Agent, but not (for example) wget; so I don't feel too - # bad passing in an empty User-Agent. - with urllib.request.urlopen( - urllib.request.Request(url, headers={"User-Agent": ""}), - context=_get_ssl_context()) as req: - data = req.read() - - file_sha = _get_hash(data) - if file_sha != sha: - raise Exception( - f"The downloaded file does not match the expected sha. {url} was " - f"expected to have {sha} but it had {file_sha}") - - if cache_dir is not None: # Try to cache the downloaded file. - try: - cache_dir.mkdir(parents=True, exist_ok=True) - with open(cache_dir / sha, "xb") as fout: - fout.write(data) - except OSError: - pass - - return BytesIO(data) - - -def get_and_extract_tarball(urls, sha, dirname): - """ - Obtain a tarball (from cache or download) and extract it. - - Parameters - ---------- - urls : list[str] - URLs from which download is attempted (in order of attempt), if the - tarball is not in the cache yet. - sha : str - SHA256 hash of the tarball; used both as a cache key (by - `get_from_cache_or_download`) and to validate a downloaded tarball. - dirname : path-like - Directory where the tarball is extracted. - """ - toplevel = Path("build", dirname) - if not toplevel.exists(): # Download it or load it from cache. - try: - import certifi # noqa - except ImportError as e: - raise ImportError( - f"`certifi` is unavailable ({e}) so unable to download any of " - f"the following: {urls}.") from None - - Path("build").mkdir(exist_ok=True) - for url in urls: - try: - tar_contents = get_from_cache_or_download(url, sha) - break - except Exception: - pass - else: - raise OSError( - f"Failed to download any of the following: {urls}. " - f"Please download one of these urls and extract it into " - f"'build/' at the top-level of the source repository.") - print(f"Extracting {urllib.parse.urlparse(url).path}") - with tarfile.open(fileobj=tar_contents, mode="r:gz") as tgz: - if os.path.commonpath(tgz.getnames()) != dirname: - raise OSError( - f"The downloaded tgz file was expected to have {dirname} " - f"as sole top-level directory, but that is not the case") - tgz.extractall("build") - return toplevel - - -# SHA256 hashes of the FreeType tarballs -_freetype_hashes = { - '2.6.1': - '0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014', - '2.6.2': - '8da42fc4904e600be4b692555ae1dcbf532897da9c5b9fb5ebd3758c77e5c2d4', - '2.6.3': - '7942096c40ee6fea882bd4207667ad3f24bff568b96b10fd3885e11a7baad9a3', - '2.6.4': - '27f0e38347a1850ad57f84fc4dfed68ba0bc30c96a6fa6138ef84d485dd9a8d7', - '2.6.5': - '3bb24add9b9ec53636a63ea8e867ed978c4f8fdd8f1fa5ccfd41171163d4249a', - '2.7': - '7b657d5f872b0ab56461f3bd310bd1c5ec64619bd15f0d8e08282d494d9cfea4', - '2.7.1': - '162ef25aa64480b1189cdb261228e6c5c44f212aac4b4621e28cf2157efb59f5', - '2.8': - '33a28fabac471891d0523033e99c0005b95e5618dc8ffa7fa47f9dadcacb1c9b', - '2.8.1': - '876711d064a6a1bd74beb18dd37f219af26100f72daaebd2d86cb493d7cd7ec6', - '2.9': - 'bf380e4d7c4f3b5b1c1a7b2bf3abb967bda5e9ab480d0df656e0e08c5019c5e6', - '2.9.1': - 'ec391504e55498adceb30baceebd147a6e963f636eb617424bcfc47a169898ce', - '2.10.0': - '955e17244e9b38adb0c98df66abb50467312e6bb70eac07e49ce6bd1a20e809a', - '2.10.1': - '3a60d391fd579440561bf0e7f31af2222bc610ad6ce4d9d7bd2165bca8669110', - '2.11.1': - 'f8db94d307e9c54961b39a1cc799a67d46681480696ed72ecf78d4473770f09b' -} -# This is the version of FreeType to use when building a local version. It -# must match the value in lib/matplotlib.__init__.py, and the cache path in -# `.circleci/config.yml`. Also update the docs in -# `docs/devel/dependencies.rst`. -TESTING_VERSION_OF_FREETYPE = '2.6.1' -if sys.platform.startswith('win') and platform.machine() == 'ARM64': - # older versions of freetype are not supported for win/arm64 - # Matplotlib tests will not pass - LOCAL_FREETYPE_VERSION = '2.11.1' -else: - LOCAL_FREETYPE_VERSION = TESTING_VERSION_OF_FREETYPE - -LOCAL_FREETYPE_HASH = _freetype_hashes.get(LOCAL_FREETYPE_VERSION, 'unknown') - -# Also update the cache path in `.circleci/config.yml`. -# Also update the docs in `docs/devel/dependencies.rst`. -LOCAL_QHULL_VERSION = '2020.2' -LOCAL_QHULL_HASH = ( - 'b5c2d7eb833278881b952c8a52d20179eab87766b00b865000469a45c1838b7e') - - -# Matplotlib build options, which can be altered using mplsetup.cfg -mplsetup_cfg = os.environ.get('MPLSETUPCFG') or 'mplsetup.cfg' -config = configparser.ConfigParser() -if os.path.exists(mplsetup_cfg): - config.read(mplsetup_cfg) -options = { - 'backend': config.get('rc_options', 'backend', fallback=None), - 'system_freetype': config.getboolean( - 'libs', 'system_freetype', - fallback=sys.platform.startswith(('aix', 'os400')) - ), - 'system_qhull': config.getboolean( - 'libs', 'system_qhull', fallback=sys.platform.startswith('os400') - ), -} - - -if '-q' in sys.argv or '--quiet' in sys.argv: - def print_raw(*args, **kwargs): pass # Suppress our own output. -else: - print_raw = print - - -def print_status(package, status): - initial_indent = "%12s: " % package - indent = ' ' * 18 - print_raw(textwrap.fill(status, width=80, - initial_indent=initial_indent, - subsequent_indent=indent)) - - -@functools.cache # We only need to compute this once. -def get_pkg_config(): - """ - Get path to pkg-config and set up the PKG_CONFIG environment variable. - """ - if sys.platform == 'win32': - return None - pkg_config = os.environ.get('PKG_CONFIG') or 'pkg-config' - if shutil.which(pkg_config) is None: - print( - "IMPORTANT WARNING:\n" - " pkg-config is not installed.\n" - " Matplotlib may not be able to find some of its dependencies.") - return None - pkg_config_path = sysconfig.get_config_var('LIBDIR') - if pkg_config_path is not None: - pkg_config_path = os.path.join(pkg_config_path, 'pkgconfig') - try: - os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path - except KeyError: - os.environ['PKG_CONFIG_PATH'] = pkg_config_path - return pkg_config - - -def pkg_config_setup_extension( - ext, package, - atleast_version=None, alt_exec=None, default_libraries=()): - """Add parameters to the given *ext* for the given *package*.""" - - # First, try to get the flags from pkg-config. - - pkg_config = get_pkg_config() - cmd = [pkg_config, package] if pkg_config else alt_exec - if cmd is not None: - try: - if pkg_config and atleast_version: - subprocess.check_call( - [*cmd, f"--atleast-version={atleast_version}"]) - # Use sys.getfilesystemencoding() to allow round-tripping - # when passed back to later subprocess calls; do not use - # locale.getpreferredencoding() which universal_newlines=True - # would do. - cflags = shlex.split( - os.fsdecode(subprocess.check_output([*cmd, "--cflags"]))) - libs = shlex.split( - os.fsdecode(subprocess.check_output([*cmd, "--libs"]))) - except (OSError, subprocess.CalledProcessError): - pass - else: - ext.extra_compile_args.extend(cflags) - ext.extra_link_args.extend(libs) - return - - # If that fails, fall back on the defaults. - - # conda Windows header and library paths. - # https://github.com/conda/conda/issues/2312 re: getting the env dir. - if sys.platform == 'win32': - conda_env_path = (os.getenv('CONDA_PREFIX') # conda >= 4.1 - or os.getenv('CONDA_DEFAULT_ENV')) # conda < 4.1 - if conda_env_path and os.path.isdir(conda_env_path): - conda_env_path = Path(conda_env_path) - ext.include_dirs.append(str(conda_env_path / "Library/include")) - ext.library_dirs.append(str(conda_env_path / "Library/lib")) - - # Default linked libs. - ext.libraries.extend(default_libraries) - - -class Skipped(Exception): - """ - Exception thrown by `SetupPackage.check` to indicate that a package should - be skipped. - """ - - -class SetupPackage: - - def check(self): - """ - If the package should be installed, return an informative string, or - None if no information should be displayed at all. - - If the package should be skipped, raise a `Skipped` exception. - - If a missing build dependency is fatal, call `sys.exit`. - """ - - def get_package_data(self): - """ - Get a package data dictionary to add to the configuration. - These are merged into to the *package_data* list passed to - `setuptools.setup`. - """ - return {} - - def get_extensions(self): - """ - Return or yield a list of C extensions (`distutils.core.Extension` - objects) to add to the configuration. These are added to the - *extensions* list passed to `setuptools.setup`. - """ - return [] - - def do_custom_build(self, env): - """ - If a package needs to do extra custom things, such as building a - third-party library, before building an extension, it should - override this method. - """ - - -class OptionalPackage(SetupPackage): - default_config = True - - def check(self): - """ - Check whether ``mplsetup.cfg`` requests this package to be installed. - - May be overridden by subclasses for additional checks. - """ - if config.getboolean("packages", self.name, - fallback=self.default_config): - return "installing" - else: # Configuration opt-out by user - raise Skipped("skipping due to configuration") - - -class Platform(SetupPackage): - name = "platform" - - def check(self): - return sys.platform - - -class Python(SetupPackage): - name = "python" - - def check(self): - return sys.version - - -def _pkg_data_helper(pkg, subdir): - """Glob "lib/$pkg/$subdir/**/*", returning paths relative to "lib/$pkg".""" - base = Path("lib", pkg) - return [str(path.relative_to(base)) for path in (base / subdir).rglob("*")] - - -class Matplotlib(SetupPackage): - name = "matplotlib" - - def get_package_data(self): - return { - 'matplotlib': [ - 'mpl-data/matplotlibrc', - *_pkg_data_helper('matplotlib', 'mpl-data'), - *_pkg_data_helper('matplotlib', 'backends/web_backend'), - '*.dll', # Only actually matters on Windows. - ], - } - - def get_extensions(self): - # agg - ext = Extension( - "matplotlib.backends._backend_agg", [ - "src/py_converters.cpp", - "src/_backend_agg.cpp", - "src/_backend_agg_wrapper.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags_and_sources(ext) - FreeType.add_flags(ext) - yield ext - # c_internal_utils - ext = Extension( - "matplotlib._c_internal_utils", ["src/_c_internal_utils.c"], - libraries=({ - "linux": ["dl"], - "win32": ["ole32", "shell32", "user32"], - }.get(sys.platform, []))) - yield ext - # ft2font - ext = Extension( - "matplotlib.ft2font", [ - "src/ft2font.cpp", - "src/ft2font_wrapper.cpp", - "src/py_converters.cpp", - ]) - FreeType.add_flags(ext) - add_numpy_flags(ext) - add_libagg_flags(ext) - yield ext - # image - ext = Pybind11Extension( - "matplotlib._image", [ - "src/_image_wrapper.cpp", - "src/py_converters_11.cpp", - ], - cxx_std=11) - # Only need source code files agg_image_filters.cpp and agg_trans_affine.cpp - add_libagg_flags_and_sources(ext) - yield ext - # path - ext = Extension( - "matplotlib._path", [ - "src/py_converters.cpp", - "src/_path_wrapper.cpp", - ]) - add_numpy_flags(ext) - add_libagg_flags_and_sources(ext) - yield ext - # qhull - ext = Extension( - "matplotlib._qhull", ["src/_qhull_wrapper.cpp"], - define_macros=[("MPL_DEVNULL", os.devnull)]) - add_numpy_flags(ext) - Qhull.add_flags(ext) - yield ext - # tkagg - ext = Extension( - "matplotlib.backends._tkagg", [ - "src/_tkagg.cpp", - ], - include_dirs=["src"], - # psapi library needed for finding Tcl/Tk at run time. - libraries={"linux": ["dl"], "win32": ["comctl32", "psapi"], - "cygwin": ["comctl32", "psapi"]}.get(sys.platform, []), - extra_link_args={"win32": ["-mwindows"]}.get(sys.platform, [])) - add_numpy_flags(ext) - add_libagg_flags(ext) - yield ext - # tri - ext = Pybind11Extension( - "matplotlib._tri", [ - "src/tri/_tri.cpp", - "src/tri/_tri_wrapper.cpp", - ], - cxx_std=11) - yield ext - # ttconv - ext = Pybind11Extension( - "matplotlib._ttconv", [ - "src/_ttconv.cpp", - "extern/ttconv/pprdrv_tt.cpp", - "extern/ttconv/pprdrv_tt2.cpp", - "extern/ttconv/ttutil.cpp", - ], - include_dirs=["extern"], - cxx_std=11) - yield ext - - -class Tests(OptionalPackage): - name = "tests" - default_config = False - - def get_package_data(self): - return { - 'matplotlib': [ - *_pkg_data_helper('matplotlib', 'tests/baseline_images'), - *_pkg_data_helper('matplotlib', 'tests/tinypages'), - 'tests/cmr10.pfb', - 'tests/Courier10PitchBT-Bold.pfb', - 'tests/mpltest.ttf', - 'tests/test_*.ipynb', - ], - 'mpl_toolkits': [ - *_pkg_data_helper('mpl_toolkits', - 'axes_grid1/tests/baseline_images'), - *_pkg_data_helper('mpl_toolkits', - 'axisartist/tests/baseline_images'), - *_pkg_data_helper('mpl_toolkits', - 'mplot3d/tests/baseline_images'), - ] - } - - -def add_numpy_flags(ext): - import numpy as np - ext.include_dirs.append(np.get_include()) - ext.define_macros.extend([ - # Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for each - # extension. - ('PY_ARRAY_UNIQUE_SYMBOL', - 'MPL_' + ext.name.replace('.', '_') + '_ARRAY_API'), - ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'), - # Allow NumPy's printf format specifiers in C++. - ('__STDC_FORMAT_MACROS', 1), - ]) - - -def add_libagg_flags(ext): - # We need a patched Agg not available elsewhere, so always use the vendored - # version. - ext.include_dirs.insert(0, "extern/agg24-svn/include") - - -def add_libagg_flags_and_sources(ext): - # We need a patched Agg not available elsewhere, so always use the vendored - # version. - ext.include_dirs.insert(0, "extern/agg24-svn/include") - agg_sources = [ - "agg_bezier_arc.cpp", - "agg_curves.cpp", - "agg_image_filters.cpp", - "agg_trans_affine.cpp", - "agg_vcgen_contour.cpp", - "agg_vcgen_dash.cpp", - "agg_vcgen_stroke.cpp", - "agg_vpgen_segmentator.cpp", - ] - ext.sources.extend( - os.path.join("extern", "agg24-svn", "src", x) for x in agg_sources) - - -def get_ccompiler(): - """ - Return a new CCompiler instance. - - CCompiler used to be constructible via `distutils.ccompiler.new_compiler`, - but this API was removed as part of the distutils deprecation. Instead, - we trick setuptools into instantiating it by creating a dummy Distribution - with a list of extension modules that claims to be truthy, but is actually - empty, and then running the Distribution's build_ext command. (If using - a plain empty ext_modules, build_ext would early-return without doing - anything.) - """ - - class L(list): - def __bool__(self): - return True - - build_ext = Distribution({"ext_modules": L()}).get_command_obj("build_ext") - build_ext.finalize_options() - build_ext.run() - return build_ext.compiler - - -class FreeType(SetupPackage): - name = "freetype" - - @classmethod - def add_flags(cls, ext): - # checkdep_freetype2.c immediately aborts the compilation either with - # "foo.h: No such file or directory" if the header is not found, or an - # appropriate error message if the header indicates a too-old version. - ext.sources.insert(0, 'src/checkdep_freetype2.c') - if options.get('system_freetype'): - pkg_config_setup_extension( - # FreeType 2.3 has libtool version 9.11.3 as can be checked - # from the tarball. For FreeType>=2.4, there is a conversion - # table in docs/VERSIONS.txt in the FreeType source tree. - ext, 'freetype2', - atleast_version='9.11.3', - alt_exec=['freetype-config'], - default_libraries=['freetype']) - ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'system')) - else: - src_path = Path('build', f'freetype-{LOCAL_FREETYPE_VERSION}') - # Statically link to the locally-built freetype. - ext.include_dirs.insert(0, str(src_path / 'include')) - ext.extra_objects.insert( - 0, str((src_path / 'objs/.libs/libfreetype').with_suffix( - '.lib' if sys.platform == 'win32' else '.a'))) - ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local')) - if sys.platform == 'darwin': - name = ext.name.split('.')[-1] - ext.extra_link_args.append( - f'-Wl,-exported_symbol,_PyInit_{name}') - - def do_custom_build(self, env): - # We're using a system freetype - if options.get('system_freetype'): - return - - tarball = f'freetype-{LOCAL_FREETYPE_VERSION}.tar.gz' - src_path = get_and_extract_tarball( - urls=[ - (f'https://downloads.sourceforge.net/project/freetype' - f'/freetype2/{LOCAL_FREETYPE_VERSION}/{tarball}'), - (f'https://download.savannah.gnu.org/releases/freetype' - f'/{tarball}'), - (f'https://download.savannah.gnu.org/releases/freetype' - f'/freetype-old/{tarball}') - ], - sha=LOCAL_FREETYPE_HASH, - dirname=f'freetype-{LOCAL_FREETYPE_VERSION}', - ) - - libfreetype = (src_path / "objs/.libs/libfreetype").with_suffix( - ".lib" if sys.platform == "win32" else ".a") - if libfreetype.is_file(): - return # Bail out because we have already built FreeType. - - print(f"Building freetype in {src_path}") - if sys.platform != 'win32': # compilation on non-windows - env = { - **{ - var: value - for var, value in sysconfig.get_config_vars().items() - if var in {"CC", "CFLAGS", "CXX", "CXXFLAGS", "LD", - "LDFLAGS"} - }, - **env, - } - configure_ac = Path(src_path, "builds/unix/configure.ac") - if ((src_path / "autogen.sh").exists() - and not configure_ac.exists()): - print(f"{configure_ac} does not exist. " - f"Using sh autogen.sh to generate.") - subprocess.check_call( - ["sh", "./autogen.sh"], env=env, cwd=src_path) - env["CFLAGS"] = env.get("CFLAGS", "") + " -fPIC" - configure = [ - "./configure", "--with-zlib=no", "--with-bzip2=no", - "--with-png=no", "--with-harfbuzz=no", "--enable-static", - "--disable-shared" - ] - host = sysconfig.get_config_var('HOST_GNU_TYPE') - if host is not None: # May be unset on PyPy. - configure.append(f"--host={host}") - subprocess.check_call(configure, env=env, cwd=src_path) - if 'GNUMAKE' in env: - make = env['GNUMAKE'] - elif 'MAKE' in env: - make = env['MAKE'] - else: - try: - output = subprocess.check_output(['make', '-v'], - stderr=subprocess.DEVNULL) - except subprocess.CalledProcessError: - output = b'' - if b'GNU' not in output and b'makepp' not in output: - make = 'gmake' - else: - make = 'make' - subprocess.check_call([make], env=env, cwd=src_path) - else: # compilation on windows - shutil.rmtree(src_path / "objs", ignore_errors=True) - base_path = Path( - f"build/freetype-{LOCAL_FREETYPE_VERSION}/builds/windows" - ) - vc = 'vc2010' - sln_path = base_path / vc / "freetype.sln" - # https://developercommunity.visualstudio.com/comments/190992/view.html - (sln_path.parent / "Directory.Build.props").write_text( - "" - "" - "" - # WindowsTargetPlatformVersion must be given on a single line. - "$(" - "[Microsoft.Build.Utilities.ToolLocationHelper]" - "::GetLatestSDKTargetPlatformVersion('Windows', '10.0')" - ")" - "" - "", - encoding="utf-8") - # It is not a trivial task to determine PlatformToolset to plug it - # into msbuild command, and Directory.Build.props will not override - # the value in the project file. - # The DefaultPlatformToolset is from Microsoft.Cpp.Default.props - with open(base_path / vc / "freetype.vcxproj", 'r+b') as f: - toolset_repl = b'PlatformToolset>$(DefaultPlatformToolset)<' - vcxproj = f.read().replace(b'PlatformToolset>v100<', - toolset_repl) - assert toolset_repl in vcxproj, ( - 'Upgrading Freetype might break this') - f.seek(0) - f.truncate() - f.write(vcxproj) - - cc = get_ccompiler() - cc.initialize() - # On setuptools versions that use "local" distutils, - # ``cc.spawn(["msbuild", ...])`` no longer manages to locate the - # right executable, even though they are correctly on the PATH, - # because only the env kwarg to Popen() is updated, and not - # os.environ["PATH"]. Instead, use shutil.which to walk the PATH - # and get absolute executable paths. - with TemporaryDirectory() as tmpdir: - dest = Path(tmpdir, "path") - cc.spawn([ - sys.executable, "-c", - "import pathlib, shutil, sys\n" - "dest = pathlib.Path(sys.argv[1])\n" - "dest.write_text(shutil.which('msbuild'))\n", - str(dest), - ]) - msbuild_path = dest.read_text() - msbuild_platform = ( - "ARM64" if platform.machine() == "ARM64" else - "x64" if platform.architecture()[0] == "64bit" else - "Win32") - # Freetype 2.10.0+ support static builds. - msbuild_config = ( - "Release Static" - if [*map(int, LOCAL_FREETYPE_VERSION.split("."))] >= [2, 10] - else "Release" - ) - - cc.spawn([msbuild_path, str(sln_path), - "/t:Clean;Build", - f"/p:Configuration={msbuild_config};" - f"Platform={msbuild_platform}"]) - # Move to the corresponding Unix build path. - libfreetype.parent.mkdir() - # Be robust against change of FreeType version. - lib_paths = Path(src_path / "objs").rglob('freetype*.lib') - # Select FreeType library for required platform - lib_path, = [ - p for p in lib_paths - if msbuild_platform in p.resolve().as_uri() - ] - print(f"Copying {lib_path} to {libfreetype}") - shutil.copy2(lib_path, libfreetype) - - -class Qhull(SetupPackage): - name = "qhull" - _extensions_to_update = [] - - @classmethod - def add_flags(cls, ext): - if options.get("system_qhull"): - ext.libraries.append("qhull_r") - else: - cls._extensions_to_update.append(ext) - - def do_custom_build(self, env): - if options.get('system_qhull'): - return - - toplevel = get_and_extract_tarball( - urls=["http://www.qhull.org/download/qhull-2020-src-8.0.2.tgz"], - sha=LOCAL_QHULL_HASH, - dirname=f"qhull-{LOCAL_QHULL_VERSION}", - ) - shutil.copyfile(toplevel / "COPYING.txt", "LICENSE/LICENSE_QHULL") - - for ext in self._extensions_to_update: - qhull_path = Path(f'build/qhull-{LOCAL_QHULL_VERSION}/src') - ext.include_dirs.insert(0, str(qhull_path)) - ext.sources.extend( - map(str, sorted(qhull_path.glob('libqhull_r/*.c')))) - if sysconfig.get_config_var("LIBM") == "-lm": - ext.libraries.extend("m") - - -class BackendMacOSX(OptionalPackage): - name = 'macosx' - - def check(self): - if sys.platform != 'darwin': - raise Skipped("Mac OS-X only") - return super().check() - - def get_extensions(self): - ext = Extension( - 'matplotlib.backends._macosx', [ - 'src/_macosx.m' - ]) - ext.extra_compile_args.extend(['-Werror']) - ext.extra_link_args.extend(['-framework', 'Cocoa']) - if platform.python_implementation().lower() == 'pypy': - ext.extra_compile_args.append('-DPYPY=1') - yield ext From 05b664f09fed1fea8e13985f3e075e85f431990a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 25 Aug 2023 23:30:16 -0400 Subject: [PATCH 0229/1120] Try to generate a version from Meson --- lib/matplotlib/__init__.py | 23 ++++++++++++++--------- lib/matplotlib/_version.py.in | 1 + lib/matplotlib/meson.build | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 lib/matplotlib/_version.py.in diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 3ffe046e6ba9..821d1bdeb5ea 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -219,15 +219,20 @@ def _get_version(): if ((root / ".matplotlib-repo").exists() and (root / ".git").exists() and not (root / ".git/shallow").exists()): - import setuptools_scm - return setuptools_scm.get_version( - root=root, - version_scheme="release-branch-semver", - local_scheme="node-and-date", - fallback_version=_version.version, - ) - else: # Get the version from the _version.py setuptools_scm file. - return _version.version + try: + import setuptools_scm + except ImportError: + pass + else: + return setuptools_scm.get_version( + root=root, + version_scheme="release-branch-semver", + local_scheme="node-and-date", + fallback_version=_version.version, + ) + # Get the version from the _version.py file if not in repo or setuptools_scm is + # unavailable. + return _version.version @_api.caching_module_getattr diff --git a/lib/matplotlib/_version.py.in b/lib/matplotlib/_version.py.in new file mode 100644 index 000000000000..a809c61c2099 --- /dev/null +++ b/lib/matplotlib/_version.py.in @@ -0,0 +1 @@ +version = @VCS_TAG@ diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build index 4f8d93d1f01d..7847e7c8b438 100644 --- a/lib/matplotlib/meson.build +++ b/lib/matplotlib/meson.build @@ -138,6 +138,20 @@ typing_sources = [ py3.install_sources(python_sources, typing_sources, subdir: 'matplotlib') +fs = import('fs') +if fs.exists('_version.py') + py3.install_sources('_version.py', subdir: 'matplotlib') +else + cfg = configuration_data() + cfg.set_quoted('VCS_TAG', meson.project_version()) + configure_file( + input: '_version.py.in', output: '_version.py', + configuration: cfg, + install: true, + install_tag: 'python-runtime', + install_dir: py3.get_install_dir() / 'matplotlib') +endif + subdir('_api') subdir('axes') subdir('backends') From 0c0a9d3d15e7969663c99440e1dfcf33e1b5cff6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2023 00:46:59 -0400 Subject: [PATCH 0230/1120] Implement backend setup in matplotlibrc --- lib/matplotlib/meson.build | 6 +----- lib/matplotlib/mpl-data/meson.build | 24 ++++++++++++++++++++++++ tools/generate_matplotlibrc.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 lib/matplotlib/mpl-data/meson.build create mode 100755 tools/generate_matplotlibrc.py diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build index 7847e7c8b438..c4b66fc1b336 100644 --- a/lib/matplotlib/meson.build +++ b/lib/matplotlib/meson.build @@ -155,14 +155,10 @@ endif subdir('_api') subdir('axes') subdir('backends') +subdir('mpl-data') subdir('projections') subdir('sphinxext') subdir('style') subdir('testing') subdir('tests') subdir('tri') - -install_subdir( - 'mpl-data', - install_tag: 'data', - install_dir: py3.get_install_dir(subdir: 'matplotlib')) diff --git a/lib/matplotlib/mpl-data/meson.build b/lib/matplotlib/mpl-data/meson.build new file mode 100644 index 000000000000..00a825fbbbea --- /dev/null +++ b/lib/matplotlib/mpl-data/meson.build @@ -0,0 +1,24 @@ +custom_target('matplotlibrc', + command: [ + find_program(meson.project_source_root() / 'tools/generate_matplotlibrc.py'), + '@INPUT@', + '@OUTPUT@', + get_option('rcParams-backend') + ], + input: 'matplotlibrc', + output: 'matplotlibrc', + install: true, + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) + +install_data( + 'kpsewhich.lua', + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) + +foreach dir : ['fonts', 'images', 'plot_directive', 'sample_data', 'stylelib'] + install_subdir( + dir, + install_tag: 'data', + install_dir: py3.get_install_dir(subdir: 'matplotlib/mpl-data')) +endforeach diff --git a/tools/generate_matplotlibrc.py b/tools/generate_matplotlibrc.py new file mode 100755 index 000000000000..33d6277870da --- /dev/null +++ b/tools/generate_matplotlibrc.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +""" +Generate matplotlirc for installs. + +If packagers want to change the default backend, insert a `#backend: ...` line. +Otherwise, use the default `##backend: Agg` which has no effect even after +decommenting, which allows _auto_backend_sentinel to be filled in at import +time. +""" + +import sys +from pathlib import Path + + +if len(sys.argv) != 4: + raise SystemExit('usage: {sys.argv[0]} ') + +input = Path(sys.argv[1]) +output = Path(sys.argv[2]) +backend = sys.argv[3] + +template_lines = input.read_text(encoding="utf-8").splitlines(True) +backend_line_idx, = [ # Also asserts that there is a single such line. + idx for idx, line in enumerate(template_lines) + if "#backend:" in line] +template_lines[backend_line_idx] = ( + f"#backend: {backend}\n" if backend not in ['', 'auto'] else "##backend: Agg\n") +output.write_text("".join(template_lines), encoding="utf-8") From 8830387a497b3809ff522e0d7eb30f04d91cd266 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 2 Sep 2023 02:47:17 -0400 Subject: [PATCH 0231/1120] Update CI for Meson build --- .appveyor.yml | 2 +- .circleci/config.yml | 7 +++++-- .github/workflows/cibuildwheel.yml | 9 +++++---- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/cygwin.yml | 14 ++++--------- .github/workflows/mypy-stubtest.yml | 3 ++- .github/workflows/tests.yml | 29 +++++++-------------------- azure-pipelines.yml | 13 ++++++++---- environment.yml | 5 +++-- requirements/testing/minver.txt | 8 +++++--- 10 files changed, 45 insertions(+), 53 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b48726bb3e51..6e71e94e58f5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -70,7 +70,7 @@ install: test_script: # Now build the thing.. - set LINK=/LIBPATH:%cd%\lib - - pip install -ve . + - pip install -v --no-build-isolation --config-settings=setup-args="--vsenv" --editable .[dev] # this should show no freetype dll... - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" - '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' diff --git a/.circleci/config.yml b/.circleci/config.yml index 680798abe5fb..e8c8c2fca394 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,6 +55,7 @@ commands: graphviz \ inkscape \ lmodern \ + ninja-build \ optipng \ texlive-fonts-recommended \ texlive-latex-base \ @@ -97,6 +98,7 @@ commands: - run: name: Install Python dependencies command: | + python -m pip install --user meson-python pybind11 python -m pip install --user \ numpy<< parameters.numpy_version >> \ -r requirements/doc/doc-requirements.txt @@ -114,7 +116,8 @@ commands: version=${version#v} python -m pip install matplotlib==${version} else - python -m pip install --user -ve . + python -m pip install --user --verbose \ + --no-build-isolation --editable .[dev] fi - save_cache: key: build-deps-2 @@ -215,8 +218,8 @@ jobs: - fonts-install - pip-install - - mpl-install - doc-deps-install + - mpl-install - doc-build - doc-show-errors-warnings diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 8c76303cbb85..d4230e8f17ab 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -91,16 +91,17 @@ jobs: runs-on: ${{ matrix.os }} env: CIBW_BEFORE_BUILD: >- - pip install certifi numpy>=1.25 && + pip install numpy>=1.25 && rm -rf {package}/build CIBW_BEFORE_BUILD_WINDOWS: >- - pip install certifi delvewheel numpy>=1.25 && + pip install delvewheel numpy>=1.25 && rm -rf {package}/build CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- delvewheel repair -w {dest_dir} {wheel} CIBW_AFTER_BUILD: >- twine check {wheel} && python {package}/ci/check_wheel_licenses.py {wheel} + CIBW_CONFIG_SETTINGS: setup-args="--vsenv" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- @@ -144,11 +145,11 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} # Remove this once NumPy with Python 3.12 wheels is not pre-release. CIBW_BEFORE_BUILD: >- - pip install certifi "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && + pip install "meson-python>=0.13.1" ninja "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && pip install --pre "numpy>=1.25" && rm -rf {package}/build CIBW_BEFORE_BUILD_WINDOWS: >- - pip install certifi delvewheel "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && + pip install delvewheel "meson-python>=0.13.1" ninja "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && pip install --pre "numpy>=1.25" && rm -rf {package}/build CIBW_ENVIRONMENT: PIP_NO_BUILD_ISOLATION=0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d31973e954d5..5753b17922d6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,9 +41,9 @@ jobs: # dependencies so we don't need another copy here. # https://github.com/jazzband/pip-tools/pull/1681 python -m pip install --upgrade \ - certifi contourpy cycler fonttools kiwisolver importlib_resources \ - numpy packaging pillow pyparsing python-dateutil setuptools-scm \ - pybind11 + build contourpy cycler fonttools kiwisolver \ + importlib_resources meson-python numpy packaging pillow pybind11 \ + pyparsing python-dateutil setuptools-scm echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV - name: Initialize CodeQL @@ -56,7 +56,7 @@ jobs: if: matrix.language == 'cpp' run: | mkdir ~/.cache/matplotlib - $CODEQL_PYTHON setup.py build + $CODEQL_PYTHON -m build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 11ea1377b36d..feded9f0c1ad 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -203,20 +203,14 @@ jobs: AUTOCONF: /usr/bin/autoconf-2.69 MAKEFLAGS: dw run: | + export PATH="/usr/local/bin:$PATH" ccache -s git describe - cat <> mplsetup.cfg - [rc_options] - backend=Agg - - [libs] - system_freetype = False - system_qhull = True - EOT - cat mplsetup.cfg # All dependencies must have been pre-installed, so that the minver # constraints are held. - python -m pip install --no-deps -ve . + python -m pip install --no-deps --no-build-isolation --verbose \ + --config-settings=setup-args="-DrcParams-backend=Agg" \ + --editable .[dev] - name: Find DLLs to rebase shell: bash.exe -eo pipefail -o igncr "{0}" diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml index 4cb225258f4f..8d2943e38ecf 100644 --- a/.github/workflows/mypy-stubtest.yml +++ b/.github/workflows/mypy-stubtest.yml @@ -22,7 +22,7 @@ jobs: run: | pip3 install -r requirements/testing/mypy.txt \ -r requirements/testing/all.txt - pip3 install -e . + pip3 install . - name: Set up reviewdog run: | @@ -38,6 +38,7 @@ jobs: run: | set -o pipefail MPLBACKEND=agg python tools/stubtest.py | \ + sed -e "s!$pythonLocation/lib/python3.9/site-packages!lib!g" | \ reviewdog \ -efm '%Eerror: %m' \ -efm '%CStub: in file %f:%l' \ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index adff7f1ce292..a3d3d63c64fa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,7 +56,6 @@ jobs: pyside2-ver: '==5.15.1' # oldest version with working Py3.9 wheel. pyside6-ver: '==6.0.0' delete-font-cache: true - no-build-isolation: true - os: ubuntu-20.04 python-version: 3.9 extra-requirements: '-r requirements/testing/extra.txt' @@ -81,7 +80,6 @@ jobs: python-version: '3.12-dev' pyside6-ver: '!=6.5.1' pre: true - no-build-isolation: true - os: macos-latest python-version: 3.9 # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 @@ -131,6 +129,7 @@ jobs: libxcb-render-util0 \ libxcb-xinerama0 \ lmodern \ + ninja-build \ pkg-config \ qtbase5-dev \ texlive-fonts-recommended \ @@ -150,7 +149,7 @@ jobs: macOS) brew install ccache brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk gobject-introspection gtk4 + brew install font-noto-sans-cjk gobject-introspection gtk4 ninja ;; esac @@ -205,17 +204,14 @@ jobs: fi # Install dependencies from PyPI. + # Preinstall build requirements to enable no-build-isolation builds. python -m pip install --upgrade $PRE \ 'contourpy>=1.0.1' cycler fonttools kiwisolver importlib_resources \ numpy packaging pillow 'pyparsing!=3.1.0' python-dateutil setuptools-scm \ + 'meson-python>=0.13.1' 'pybind11>=2.6' \ -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} - # Preinstall pybind11 on no-build-isolation builds. - if [[ "${{ matrix.no-build-isolation }}" == 'true' ]]; then - python -m pip install 'pybind11>=2.6' - fi - # Install optional dependencies from PyPI. # Sphinx is needed to run sphinxext tests python -m pip install --upgrade sphinx!=6.1.2 @@ -300,20 +296,9 @@ jobs: fi fi - cat <> mplsetup.cfg - [rc_options] - backend=Agg - EOT - - cat mplsetup.cfg - - if [[ "${{ matrix.no-build-isolation }}" == 'true' ]]; then - # Minimum versions run does not use build isolation so that it - # builds against the pre-installed minver dependencies. - python -m pip install --no-deps --no-build-isolation -ve . - else - python -m pip install --no-deps -ve . - fi + python -m pip install --no-deps --no-build-isolation --verbose \ + --config-settings=setup-args="-DrcParams-backend=Agg" \ + --editable .[dev] if [[ "${{ runner.os }}" != 'macOS' ]]; then unset CPPFLAGS diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7d00e499fd18..8eb5cacfb55a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -96,6 +96,7 @@ stages: cm-super \ dvipng \ ffmpeg \ + fonts-freefont-otf \ fonts-noto-cjk \ fonts-wqy-zenhei \ gdb \ @@ -105,23 +106,24 @@ stages: libcairo2 \ libgirepository-1.0-1 \ lmodern \ - fonts-freefont-otf \ + ninja-build \ poppler-utils \ - texlive-pictures \ texlive-fonts-recommended \ texlive-latex-base \ texlive-latex-extra \ texlive-latex-recommended \ texlive-luatex \ + texlive-pictures \ texlive-xetex ;; darwin) brew install --cask xquartz - brew install pkg-config ffmpeg imagemagick mplayer ccache + brew install ccache ffmpeg imagemagick mplayer ninja pkg-config brew tap homebrew/cask-fonts brew install font-noto-sans-cjk-sc ;; win32) + choco install ninja ;; *) exit 1 @@ -131,12 +133,15 @@ stages: - bash: | python -m pip install --upgrade pip + python -m pip install --upgrade meson-python pybind11 python -m pip install -r requirements/testing/all.txt -r requirements/testing/extra.txt || [[ "$PYTHON_VERSION" = 'Pre' ]] displayName: 'Install dependencies with pip' - bash: | - python -m pip install -ve . || + python -m pip install \ + --no-build-isolation --config-settings=setup-args="--vsenv" \ + --verbose --editable .[dev] || [[ "$PYTHON_VERSION" = 'Pre' ]] displayName: "Install self" diff --git a/environment.yml b/environment.yml index 7b13735bb172..095e3d240bd2 100644 --- a/environment.yml +++ b/environment.yml @@ -2,7 +2,7 @@ # # conda env create -f environment.yml # conda activate mpl-dev -# pip install -e . +# pip install -e .[dev] # name: mpl-dev channels: @@ -15,14 +15,15 @@ dependencies: - fonttools>=4.22.0 - importlib-resources>=3.2.0 - kiwisolver>=1.3.1 + - meson-python>=0.13.1 - numpy>=1.21 - pillow>=8 + - pkg-config - pybind11>=2.6.0 - pygobject - pyparsing>=2.3.1 - pyqt - python-dateutil>=2.1 - - setuptools - setuptools_scm - wxpython # building documentation diff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt index b989922c5527..b399bd996e08 100644 --- a/requirements/testing/minver.txt +++ b/requirements/testing/minver.txt @@ -2,12 +2,14 @@ contourpy==1.0.1 cycler==0.10 -kiwisolver==1.3.1 +fonttools==4.22.0 importlib-resources==3.2.0 +kiwisolver==1.3.1 +meson-python==0.13.1 +meson==1.1.0 numpy==1.21.0 packaging==20.0 pillow==8.0.0 pyparsing==2.3.1 -python-dateutil==2.7 -fonttools==4.22.0 pytest==7.0.0 +python-dateutil==2.7 From 65545ddd98ab0964f3b96f8aecf0514e6d31a118 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 7 Sep 2023 00:42:34 -0400 Subject: [PATCH 0232/1120] DOC: Update documentation for Meson change --- .gitignore | 7 +- .../next_api_changes/development/26621-ES.rst | 46 +++++++++++++ doc/devel/contribute.rst | 7 +- doc/devel/dependencies.rst | 67 +++++++++---------- doc/devel/development_setup.rst | 34 +++++----- doc/devel/min_dep_policy.rst | 2 +- doc/users/faq.rst | 14 ++-- lib/matplotlib/__init__.py | 8 +-- meson_options.txt | 18 ++--- requirements/doc/doc-requirements.txt | 2 +- src/_qhull_wrapper.cpp | 2 +- 11 files changed, 126 insertions(+), 81 deletions(-) create mode 100644 doc/api/next_api_changes/development/26621-ES.rst diff --git a/.gitignore b/.gitignore index a76efc1cd47a..1514710bd9be 100644 --- a/.gitignore +++ b/.gitignore @@ -29,10 +29,10 @@ # Python files # ################ -# setup.py working directory +# meson-python working directory build -# setup.py dist directory +# meson-python/build frontend dist directory dist # Egg metadata *.egg-info @@ -41,9 +41,6 @@ dist pip-wheel-metadata/* # tox testing tool .tox -mplsetup.cfg -# generated by setuptools_scm -lib/matplotlib/_version.py # build subproject files subprojects/*/ !subprojects/packagefiles/ diff --git a/doc/api/next_api_changes/development/26621-ES.rst b/doc/api/next_api_changes/development/26621-ES.rst new file mode 100644 index 000000000000..ff87f53b3573 --- /dev/null +++ b/doc/api/next_api_changes/development/26621-ES.rst @@ -0,0 +1,46 @@ +Build system ported to Meson +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The build system of Matplotlib has been ported from setuptools to `meson-python +`_ and `Meson `_. +Consequently, there have been a few changes for development and packaging purposes. + +1. Installation by ``pip`` of packages with ``pyproject.toml`` use `build isolation + `_ + by default, which interferes with editable installation. Thus for developers using + editable installs, it is now necessary to pass the ``--no-build-isolation`` flag to + ``pip install``. This means that all build-time requirements must be available in the + environment for an editable install. +2. Build configuration has moved from a custom :file:`mplsetup.cfg` (also configurable + via ``MPLSETUP`` environment variable) to Meson options. These may be specified using + `meson-python's build config settings + `_ + for ``setup-args``. See :file:`meson_options.txt` for all options. For example, a + :file:`mplsetup.cfg` containing the following:: + + [rc_options] + backend=Agg + + [libs] + system_qhull = True + + may be replaced by passing the following arguments to ``pip``:: + + --config-settings=setup-args="-DrcParams-backend=Agg" \ + --config-settings=setup-args="-Dsystem-qhull=true" + + Note that you must use ``pip`` >= 23.1 in order to pass more than one setting. +3. Relatedly, Meson's `builtin options `_ + are now used instead of custom options, e.g., the LTO option is now ``b_lto``. +4. On Windows, Meson activates a Visual Studio environment automatically. However, it + will not do so if another compiler is available. See `Meson's documentation + `_ if you wish to + change the priority of chosen compilers. +5. Installation of test data was previously controlled by :file:`mplsetup.cfg`, but has + now been moved to Meson's install tags. To install test data, add the ``tests`` + tag to the requested install (be sure to include the existing tags as below):: + + --config-settings=install-args="--tags=data,python-runtime,runtime,tests" +6. Checking typing stubs with ``stubtest`` does not work easily with editable install. + For the time being, we suggest using a normal (non-editable) install if you wish to + run ``stubtest``. diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 85c061b7ce8f..976e03c97776 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -266,7 +266,7 @@ A brief overview of the workflow is as follows. #. Install the local version of Matplotlib with:: - python -m pip install -e . + python -m pip install --no-build-isolation --editable .[dev] See :ref:`installing_for_devs` for detailed instructions. @@ -476,9 +476,8 @@ take particular care when adding new API: New modules and files: installation ----------------------------------- -* If you have added new files or directories, or reorganized existing - ones, make sure the new files are included in the match patterns in - in *package_data* in :file:`setupext.py`. +* If you have added new files or directories, or reorganized existing ones, make sure the + new files are included in the :file:`meson.build` in the corresponding directories. * New modules *may* be typed inline or using parallel stub file like existing modules. C/C++ extensions diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 400bc63d42ac..77df6e494d50 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -100,7 +100,8 @@ Matplotlib brings its own copies of the following libraries: Additionally, Matplotlib depends on: - FreeType_ (>= 2.3): a font rendering library -- QHull_ (>= 2020.2): a library for computing triangulations +- QHull_ (>= 8.0.2): a library for computing triangulations (note that this version is + also known as 2020.2) .. _FreeType: https://www.freetype.org/ .. _Qhull: http://www.qhull.org/ @@ -113,24 +114,16 @@ defaults to the system version of FreeType on AIX. Use system libraries ^^^^^^^^^^^^^^^^^^^^ -To force Matplotlib to use a copy of FreeType or Qhull already installed in -your system, create a :file:`mplsetup.cfg` file with the following contents: - -.. code-block:: cfg - - [libs] - system_freetype = true - system_qhull = true - -before running +To force Matplotlib to use a copy of FreeType or Qhull already installed in your system, +you must `pass configuration settings to Meson via meson-python +`_: .. code-block:: sh - python -m pip install . - - -You can also use the :envvar:`MPLSETUPCFG` to specify the path to a cfg file when -installing from pypi. + python -m pip install \ + --config-settings=setup-args="-Dsystem-freetype=true" \ + --config-settings=setup-args="-Dsystem-qhull=true" \ + . In this case, you need to install the FreeType and Qhull library and headers. @@ -187,18 +180,12 @@ remember to clear your artifacts before re-building:: Manual Download ^^^^^^^^^^^^^^^ - -If the automatic download does not work (for example on air-gapped systems) it -is preferable to instead use system libraries. However you can manually -download and unpack the tarballs into:: - - build/freetype-2.6.1 # on all platforms but windows ARM64 - build/freetype-2.11.1 # on windows ARM64 - build/qhull-2020.2 - -at the top level of the checkout repository. The expected sha256 hashes of -the downloaded tarballs is in :file:`setupext.py` if you wish to verify -before unpacking. +If the automatic download does not work (for example, on air-gapped systems) it is +preferable to instead use system libraries. However you can manually download the +tarballs into :file:`subprojects/packagecache` at the top level of the checkout +repository. The expected SHA256 hashes of the downloaded tarballs are in +:file:`subprojects/*.wrap` if you wish to verify them, but they will also be checked by +the build system before unpacking. Minimum pip / manylinux support (linux) @@ -207,7 +194,7 @@ Minimum pip / manylinux support (linux) Matplotlib publishes `manylinux wheels `_ which have a minimum version of pip which will recognize the wheels -- Python 3.9+: ``manylinx2014`` / pip >= 19.3 +- Python 3.9+: ``manylinux2014`` / pip >= 19.3 In all cases the required version of pip is embedded in the CPython source. @@ -223,12 +210,18 @@ Dependencies for building Matplotlib Setup dependencies ------------------ -- `certifi `_ (>= 2020.06.20). Used while - downloading the freetype and QHull source during build. This is not a - runtime dependency. +By default, ``pip`` will build packages using build isolation, and the following +dependencies will be automatically installed in the isolated environment to build +Matplotlib. However, for development, you may wish to make an editable install, which +will require disabling build isolation, so these build dependencies should be installed +in your target environment manually: + +- `meson-python `_ (>= 0.13.1). +- `ninja `_ (>= 1.8.2). This may be available in your package + manager or bundled with Meson, but may be installed via ``pip`` if otherwise not + available. - `PyBind11 `_ (>= 2.6). Used to connect C/C++ code with Python. -- `setuptools `_ (>= 64). - `setuptools_scm `_ (>= 7). Used to update the reported ``mpl.__version__`` based on the current git commit. Also a runtime dependency for editable installs. @@ -288,8 +281,6 @@ Xcode, VS Code or Linux package manager. Choose **one** compiler from this list: - Linux, macOS, Windows - `gcc 4.8.1 `_, `GCC: Binaries `_, - - For gcc <6.5 you will need to set ``$CFLAGS=-std=c++11`` to enable C++11 support. * - Clang (LLVM) - **3.3** - Linux, macOS @@ -330,8 +321,8 @@ testing the following will be used if they are installed. - pytest-xvfb_ to run tests without windows popping up (Linux) - pytz_ used to test pytz int - sphinx_ used to test our sphinx extensions -- WenQuanYi Zen Hei and `Noto Sans CJK `_ - fonts for testing font fallback and non-western fonts +- `WenQuanYi Zen Hei`_ and `Noto Sans CJK`_ fonts for testing font fallback and + non-Western fonts - xarray_ used to test compatibility with xarray If any of these dependencies are not discovered, then the tests that rely on @@ -359,6 +350,8 @@ them will be skipped by pytest. .. _pytest-xvfb: https://pypi.org/project/pytest-xvfb/ .. _pytest: http://doc.pytest.org/en/latest/ .. _sphinx: https://pypi.org/project/Sphinx/ +.. _WenQuanYi Zen Hei: http://wenq.org/en/ +.. _Noto Sans CJK: https://fonts.google.com/noto/use .. _xarray: https://pypi.org/project/xarray/ diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 8537e1229df4..0e7db431163f 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -157,34 +157,36 @@ must be installed separately. For a full list, see :ref:`dependencies`. Install Matplotlib in editable mode =================================== -Install Matplotlib in editable mode from the :file:`matplotlib` directory -using the command :: +Install Matplotlib in editable mode from the :file:`matplotlib` directory using the +command :: - python -m pip install -ve . + python -m pip install --verbose --no-build-isolation --editable .[dev] -The 'editable/develop mode', builds everything and places links in your Python -environment so that Python will be able to import Matplotlib from your -development source directory. This allows you to import your modified version -of Matplotlib without re-installing after every change. Note that this is only -true for ``*.py`` files. If you change the C-extension source (which might -also happen if you change branches) you will have to re-run -``python -m pip install -ve .`` +The 'editable/develop mode' builds everything and places links in your Python environment +so that Python will be able to import Matplotlib from your development source directory. +This allows you to import your modified version of Matplotlib without re-installing after +every change. Note that before the merging of the `Meson port +`_, this is only true for ``*.py`` +files. If you change the C-extension source based on a commit before the change to the +Meson build system (which might also happen if you change branches), you will have to +re-run the above command. Verify the Installation ======================= -Run the following command to make sure you have correctly installed Matplotlib in editable mode. -The command should be run when the virtual environment is activated :: +Run the following command to make sure you have correctly installed Matplotlib in +editable mode. The command should be run when the virtual environment is activated:: python -c "import matplotlib; print(matplotlib.__file__)" This command should return : ``\lib\matplotlib\__init__.py`` -We encourage you to run tests and build docs to verify that the code installed correctly and that the docs build cleanly, -so that when you make code or document related changes you are aware of the existing issues beforehand. +We encourage you to run tests and build docs to verify that the code installed correctly +and that the docs build cleanly, so that when you make code or document related changes +you are aware of the existing issues beforehand. - * Run test cases to verify installation :ref:`testing` - * Verify documentation build :ref:`documenting-matplotlib` +* Run test cases to verify installation :ref:`testing` +* Verify documentation build :ref:`documenting-matplotlib` Install pre-commit hooks ======================== diff --git a/doc/devel/min_dep_policy.rst b/doc/devel/min_dep_policy.rst index 475985992bbc..dd8e069f0b94 100644 --- a/doc/devel/min_dep_policy.rst +++ b/doc/devel/min_dep_policy.rst @@ -22,7 +22,7 @@ Matplotlib supports: - All minor versions of ``numpy`` released in the 24 months prior to the project, and at minimum the last three minor versions. -In ``setup.py``, the ``python_requires`` variable should be set to +In :file:`pyproject.toml`, the ``requires-python`` variable should be set to the minimum supported version of Python. All supported minor versions of Python should be in the test matrix and have binary artifacts built for the release. diff --git a/doc/users/faq.rst b/doc/users/faq.rst index c4e133d56d73..42747c814f94 100644 --- a/doc/users/faq.rst +++ b/doc/users/faq.rst @@ -355,17 +355,23 @@ provide the following information in your e-mail to the `mailing list If you compiled Matplotlib yourself, please also provide: -* any changes you have made to ``setup.py`` or ``setupext.py``. +* your compiler version -- e.g., ``gcc --version``. * the output of:: - rm -rf build - python setup.py build + pip install --verbose The beginning of the build output contains lots of details about your platform that are useful for the Matplotlib developers to diagnose your problem. -* your compiler version -- e.g., ``gcc --version``. +If you compiled an older version of Matplotlib using the pre-Meson build system, instead +provide: + +* any changes you have made to ``setup.py``/``setupext.py``, +* the output of:: + + rm -rf build + python setup.py build Including this information in your first e-mail to the mailing list will save a lot of time. diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 821d1bdeb5ea..0cdfa43b40d1 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1307,8 +1307,8 @@ def _val_or_rc(val, rc_name): def _init_tests(): - # The version of FreeType to install locally for running the - # tests. This must match the value in `setupext.py` + # The version of FreeType to install locally for running the tests. This must match + # the value in `meson.build`. LOCAL_FREETYPE_VERSION = '2.6.1' from matplotlib import ft2font @@ -1316,8 +1316,8 @@ def _init_tests(): ft2font.__freetype_build_type__ != 'local'): _log.warning( f"Matplotlib is not built with the correct FreeType version to " - f"run tests. Rebuild without setting system_freetype=1 in " - f"mplsetup.cfg. Expect many image comparison failures below. " + f"run tests. Rebuild without setting system-freetype=true in " + f"Meson setup options. Expect many image comparison failures below. " f"Expected freetype version {LOCAL_FREETYPE_VERSION}. " f"Found freetype version {ft2font.__freetype_version__}. " "Freetype build type is {}local".format( diff --git a/meson_options.txt b/meson_options.txt index ed753bb26854..77f2b5ec22de 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,18 +1,20 @@ +# Options may be set by passing through `pip` or `build` via meson-python: +# https://meson-python.readthedocs.io/en/stable/how-to-guides/config-settings.html + # By default, Matplotlib downloads and builds its own copies of FreeType and of -# Qhull. You may set the following to True to instead link against a system -# FreeType/Qhull. As an exception, Matplotlib defaults to the system version -# of FreeType on AIX. +# Qhull. You may use the following options to instead link against a system +# FreeType/Qhull. As an exception, Matplotlib defaults to the system version of +# FreeType on AIX. option('system-freetype', type: 'boolean', value: false, description: 'Build against system version of FreeType') option('system-qhull', type: 'boolean', value: false, description: 'Build against system version of Qhull') # Some of Matplotlib's components are optional: the MacOSX backend (installed -# by default on MacOSX; requires the Cocoa headers included with XCode), and -# the test data (i.e., the baseline image files; not installed by default). You -# can control whether they are installed by uncommenting the following lines. -# Note that the MacOSX backend is never built on Linux or Windows, regardless -# of the config value. +# by default on MacOSX; requires the Cocoa headers included with XCode). You +# can control whether they are installed using the following options. Note that +# the MacOSX backend is never built on Linux or Windows, regardless of the +# config value. option('macosx', type: 'boolean', value: true, description: 'Enable MacOSX backend (requires Cocoa)') diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index c0b4a19e3dd3..f60e9addf47b 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -2,7 +2,7 @@ # # You will first need a matching Matplotlib installation # e.g (from the Matplotlib root directory) -# pip install -e . +# pip install --no-build-isolation --editable .[dev] # # Install the documentation requirements with: # pip install -r requirements/doc/doc-requirements.txt diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index 7e4f306305b8..ef374d8e42c0 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -162,7 +162,7 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y, if (hide_qhull_errors) { /* qhull errors are ignored by writing to OS-equivalent of /dev/null. * Rather than have OS-specific code here, instead it is determined by - * setupext.py and passed in via the macro MPL_DEVNULL. */ + * meson.build and passed in via the macro MPL_DEVNULL. */ error_file = fopen(STRINGIFY(MPL_DEVNULL), "w"); if (error_file == NULL) { throw std::runtime_error("Could not open devnull"); From 01c1ff610fe89c1425666465b9dc044ffb9dd563 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 27 Sep 2023 15:59:16 -0400 Subject: [PATCH 0233/1120] ci: Pin NumPy on Cygwin build Cherry-picked out of #26800; also unpin setuptools. --- .github/workflows/cygwin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index feded9f0c1ad..6968d7afb4f5 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -174,8 +174,8 @@ jobs: - name: Install Python dependencies shell: bash.exe -eo pipefail -o igncr "{0}" run: | - python -m pip install --upgrade pip 'setuptools<60' wheel - python -m pip install kiwisolver 'numpy!=1.21.*' pillow importlib_resources + python -m pip install --upgrade pip setuptools wheel + python -m pip install kiwisolver 'numpy>=1.22,<1.26' pillow importlib_resources grep -v -F -e psutil requirements/testing/all.txt >requirements_test.txt python -m pip install meson-python pybind11 export PATH="/usr/local/bin:$PATH" From 15362458ce3a1a7a7106fdbbde140e037e67ac0d Mon Sep 17 00:00:00 2001 From: Anvi Verma <114463178+AnviVerma27@users.noreply.github.com> Date: Wed, 4 Oct 2023 22:01:04 +0530 Subject: [PATCH 0234/1120] user/project/citing updated (#26995) update citing.rst formatting --- doc/users/project/citing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/project/citing.rst b/doc/users/project/citing.rst index 2230ecf1463c..77a7449b356e 100644 --- a/doc/users/project/citing.rst +++ b/doc/users/project/citing.rst @@ -25,7 +25,7 @@ specific DOI from the list below, referring to the version used for your publica :target: https://doi.org/10.5281/zenodo.592536 By version -~~~~~~~~~~ +^^^^^^^^^^ .. START OF AUTOGENERATED From 500c5f28798c9dcd6cc9a23d53a4f435d870c736 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 2 Oct 2023 08:36:50 -0700 Subject: [PATCH 0235/1120] DOC: add units to user/explain [ci doc] --- .../examples/ticks/date_concise_formatter.py | 2 + .../ticks/date_formatters_locators.py | 2 + galleries/examples/units/basic_units.py | 2 + galleries/users_explain/axes/axes_units.py | 278 ++++++++++++++++++ galleries/users_explain/axes/index.rst | 1 + 5 files changed, 285 insertions(+) create mode 100644 galleries/users_explain/axes/axes_units.py diff --git a/galleries/examples/ticks/date_concise_formatter.py b/galleries/examples/ticks/date_concise_formatter.py index f757fc4fbe09..540ebf0e56c1 100644 --- a/galleries/examples/ticks/date_concise_formatter.py +++ b/galleries/examples/ticks/date_concise_formatter.py @@ -1,4 +1,6 @@ """ +.. _date_concise_formatter: + ================================================ Formatting date ticks using ConciseDateFormatter ================================================ diff --git a/galleries/examples/ticks/date_formatters_locators.py b/galleries/examples/ticks/date_formatters_locators.py index e500f2e90abc..39492168242f 100644 --- a/galleries/examples/ticks/date_formatters_locators.py +++ b/galleries/examples/ticks/date_formatters_locators.py @@ -1,4 +1,6 @@ """ +.. _date_formatters_locators: + ================================= Date tick locators and formatters ================================= diff --git a/galleries/examples/units/basic_units.py b/galleries/examples/units/basic_units.py index e9b5d18ef340..0b493ab7216c 100644 --- a/galleries/examples/units/basic_units.py +++ b/galleries/examples/units/basic_units.py @@ -1,4 +1,6 @@ """ +.. _basic_units: + =========== Basic Units =========== diff --git a/galleries/users_explain/axes/axes_units.py b/galleries/users_explain/axes/axes_units.py new file mode 100644 index 000000000000..cfc6d787ca74 --- /dev/null +++ b/galleries/users_explain/axes/axes_units.py @@ -0,0 +1,278 @@ +""" +.. _user_axes_units: + +========================================= +Plotting beyond floats: dates and strings +========================================= + +The most basic way to use Matplotlib plotting methods is to pass coordinates in +as numerical numpy arrays. For example, ``plot(x, y)`` will work if ``x`` and +``y`` are numpy arrays of floats (or integers). Plotting methods will also +work if `numpy.asarray` will convert ``x`` and ``y`` to an array of floating +point numbers; e.g. ``x`` could be a python list. + +Matplotlib also has the ability to convert other data types if a "unit +converter" exists for the data type. Matplotlib has two built-in converters, +one for dates and the other for lists of strings. Other downstream libraries +have their own converters to handle their data types. + +The method to add converters to Matplotlib is described in `matplotlib.units`. +Here we briefly overview the built-in date and string converters. + +Date conversion +=============== + +If ``x`` and/or ``y`` are a list of `datetime` or an array of +`numpy.datetime64`, Matplotlib has a built in converter that will convert the +datetime to a float, and add locators and formatters to the axis that are +appropriate for dates. + +In the following example, the x-axis gains a converter that converts from +`numpy.datetime64` to float, and a locator that put ticks at the beginning of +the month, and a formatter that label the ticks appropriately: +""" + +import numpy as np + +import matplotlib.dates as mdates +import matplotlib.units as munits + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5.4, 2), layout='constrained') +time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') +x = np.arange(len(time)) +ax.plot(time, x) + +# %% +# +# Note that if we try to plot a float on the x-axis, it will be plotted in +# units of days since the "epoch" for the converter, in this case 1970-01-01 +# (see :ref:`date-format`). So when we plot the value 0, the ticks start at +# 1970-01-01, and note that the locator now chooses every two years for a tick +# instead of every month: + +fig, ax = plt.subplots(figsize=(5.4, 2), layout='constrained') +time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') +x = np.arange(len(time)) +ax.plot(time, x) +# 0 gets labeled as 1970-01-01 +ax.plot(0, 0, 'd') +ax.text(0, 0, ' Float x=0', rotation=45) + + +# %% +# +# We can customize the locator and the formatter; see :ref:`date-locators` and +# :ref:`date-formatters` for a complete list, and +# :ref:`date_formatters_locators` for examples of them in use. Here we locate +# by every second month, and format just with the month's 3-letter name using +# ``"%b"`` (see `~datetime.datetime.strftime` for format codes): + +fig, ax = plt.subplots(figsize=(5.4, 2), layout='constrained') +time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') +x = np.arange(len(time)) +ax.plot(time, x) +ax.xaxis.set_major_locator(mdates.MonthLocator(bymonth=np.arange(1, 13, 2))) +ax.xaxis.set_major_formatter(mdates.DateFormatter('%b')) +ax.set_xlabel('1980') + +# %% +# +# The default locator is the `~.dates.AutoDateLocator`, and the default +# Formatter `~.dates.AutoDateFormatter`. There is also a "concise" +# formatter/locator that gives a more compact labelling, and can be set via +# rcParams. Note how instead of the redundant "Jan" label at the start of the +# year, "1980" is used instead. See :ref:`date_concise_formatter` for more +# examples. + +plt.rcParams['date.converter'] = 'concise' + +fig, ax = plt.subplots(figsize=(5.4, 2), layout='constrained') +time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') +x = np.arange(len(time)) +ax.plot(time, x) + +# %% +# +# We can set the limits on the axis either by passing the appropriate dates in +# or by passing a floating point value in the proper units of floating days +# since the epoch. We can get this value from `~.dates.date2num`. + +fig, axs = plt.subplots(2, 1, figsize=(5.4, 3), layout='constrained') +for ax in axs.flat: + time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') + x = np.arange(len(time)) + ax.plot(time, x) + +# set xlim using datetime64: +axs[0].set_xlim(np.datetime64('1980-02-01'), np.datetime64('1980-04-01')) + +# set xlim using floats: +# Note can get from mdates.date2num(np.datetime64('1980-02-01')) +axs[1].set_xlim(3683, 3683+60) + +# %% +# +# String conversion: categorical plots +# ==================================== +# +# Sometimes we want to label categories on an axis rather than numbers. +# Matplotlib allows this using a "categorical" converter (see +# `~.matplotlib.category`). + +data = {'apple': 10, 'orange': 15, 'lemon': 5, 'lime': 20} +names = list(data.keys()) +values = list(data.values()) + +fig, axs = plt.subplots(1, 3, figsize=(7, 3), sharey=True, layout='constrained') +axs[0].bar(names, values) +axs[1].scatter(names, values) +axs[2].plot(names, values) +fig.suptitle('Categorical Plotting') + +# %% +# +# Note that the "categories" are plotted in the order that they are first +# specified and that subsequent plotting in a different order will not affect +# the original order. Further, new additions will be added on the end: + +fig, ax = plt.subplots(figsize=(5, 3), layout='constrained') +ax.bar(names, values) + +# plot in a different order: +ax.scatter(['lemon', 'apple'], [7, 12]) + +# add a new category, and out of order: +ax.plot(['pear', 'orange', 'apple', 'lemon'], [13, 10, 7, 12], color='C1') + + +# %% +# +# Note that when using ``plot`` like in the above, the order of the plotting is +# mapped onto the original order of the data, so the new line goes in the order +# specified. +# +# The category converter maps from categories to integers, starting at zero. So +# data can also be manually added to the axis using a float. However, note +# that a float that is not a category will not get a label. + +fig, ax = plt.subplots(figsize=(5, 3), layout='constrained') +ax.bar(names, values) + +# 0 gets labeled as "apple" +ax.plot(0, 0, 'd', color='C1') +ax.text(0, 0, ' Float x=0', rotation=45, color='C1') + +# 2 gets labeled as "lemon" +ax.plot(2, 0, 'd', color='C1') +ax.text(2, 0, ' Float x=2', rotation=45, color='C1') + +# 4 doesn't get a label +ax.plot(4, 0, 'd', color='C1') +ax.text(4, 0, ' Float x=4', rotation=45, color='C1') + +# 2.5 doesn't get a label +ax.plot(2.5, 0, 'd', color='C1') +ax.text(2.5, 0, ' Float x=2.5', rotation=45, color='C1') + + +# %% +# +# Setting the limits for a category axis can be done by specifying the +# categories, or by specifying floating point numbers: + +fig, axs = plt.subplots(2, 1, figsize=(5, 5), layout='constrained') +ax = axs[0] +ax.bar(names, values) +ax.set_xlim('orange', 'lemon') +ax.set_xlabel('limits set with categories') +ax = axs[1] +ax.bar(names, values) +ax.set_xlim(0.5, 2.5) +ax.set_xlabel('limits set with floats') + +# %% +# +# The category axes are helpful for some plot types, but can lead to confusion +# if data is read in as a list of strings, even if it is meant to be a list of +# floats or dates. This sometimes happens when reading comma-separated value +# (CSV) files. The categorical locator and formatter will put a tick at every +# string value and label each one as well: + +fig, ax = plt.subplots(figsize=(5.4, 2.5), layout='constrained') +x = [str(xx) for xx in np.arange(100)] # list of strings +ax.plot(x, np.arange(100)) +ax.set_xlabel('x is list of strings') + +# %% +# +# If this is not desired, then simply convert the data to floats before plotting: + +fig, ax = plt.subplots(figsize=(5.4, 2.5), layout='constrained') +x = np.asarray(x, dtype='float') # array of float. +ax.plot(x, np.arange(100)) +ax.set_xlabel('x is array of floats') + +# %% +# +# Determine converter, formatter, and locator on an axis +# ====================================================== +# +# Sometimes it is helpful to be able to debug what Matplotlib is using to +# convert the incoming data: we can do that by querying the ``converter`` +# property on the axis. We can also query the formatters and locators using +# `~.axis.Axis.get_major_locator` and `~.axis.Axis.get_major_formatter`. +# +# Note that by default the converter is *None*. + +fig, axs = plt.subplots(3, 1, figsize=(6.4, 7), layout='constrained') +x = np.arange(100) +ax = axs[0] +ax.plot(x, x) +label = f'Converter: {ax.xaxis.converter}\n ' +label += f'Locator: {ax.xaxis.get_major_locator()}\n' +label += f'Formatter: {ax.xaxis.get_major_formatter()}\n' +ax.set_xlabel(label) + +ax = axs[1] +time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') +x = np.arange(len(time)) +ax.plot(time, x) +label = f'Converter: {ax.xaxis.converter}\n ' +label += f'Locator: {ax.xaxis.get_major_locator()}\n' +label += f'Formatter: {ax.xaxis.get_major_formatter()}\n' +ax.set_xlabel(label) + +ax = axs[2] +data = {'apple': 10, 'orange': 15, 'lemon': 5, 'lime': 20} +names = list(data.keys()) +values = list(data.values()) +ax.plot(names, values) +label = f'Converter: {ax.xaxis.converter}\n ' +label += f'Locator: {ax.xaxis.get_major_locator()}\n' +label += f'Formatter: {ax.xaxis.get_major_formatter()}\n' +ax.set_xlabel(label) + +# %% +# +# General unit support +# ==================== +# +# The support for dates and categories is part of "units" support that is +# built into Matplotlib. This is described at `.matplotlib.units` and in the # +# :ref:`basic_units` example. +# +# Unit support works by querying the type of data passed to the plotting +# function and dispatching to the first converter in a list that accepts that +# type of data. So below, if ``x`` has ``datetime`` objects in it, the +# converter will be ``_SwitchableDateConverter``; if it has has strings in it, +# it will be sent to the ``StrCategoryConverter``. + +for k in munits.registry: + print(f"type: {k};\n converter: {munits.registry[k]}") + +# %% +# +# Downstream libraries like pandas, astropy, and pint all can add their own +# converters to Matplotlib. diff --git a/galleries/users_explain/axes/index.rst b/galleries/users_explain/axes/index.rst index 3d0a67ca14e7..f0e14f8e3923 100644 --- a/galleries/users_explain/axes/index.rst +++ b/galleries/users_explain/axes/index.rst @@ -43,6 +43,7 @@ annotations like x- and y-labels, titles, and legends. axes_scales axes_ticks + axes_units Legends Subplot mosaic From feb602a4edd7bc83cfee073426dfc485207d1c35 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 3 Oct 2023 19:20:48 -0700 Subject: [PATCH 0236/1120] DOC: add units to user/explain [ci doc] --- galleries/users_explain/axes/axes_units.py | 75 ++++++++++++---------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/galleries/users_explain/axes/axes_units.py b/galleries/users_explain/axes/axes_units.py index cfc6d787ca74..37b3b5a09ef6 100644 --- a/galleries/users_explain/axes/axes_units.py +++ b/galleries/users_explain/axes/axes_units.py @@ -23,9 +23,9 @@ =============== If ``x`` and/or ``y`` are a list of `datetime` or an array of -`numpy.datetime64`, Matplotlib has a built in converter that will convert the -datetime to a float, and add locators and formatters to the axis that are -appropriate for dates. +`numpy.datetime64`, Matplotlib has a built-in converter that will convert the +datetime to a float, and add tick locators and formatters to the axis that are +appropriate for dates. See `matplotlib.dates`. In the following example, the x-axis gains a converter that converts from `numpy.datetime64` to float, and a locator that put ticks at the beginning of @@ -49,8 +49,8 @@ # Note that if we try to plot a float on the x-axis, it will be plotted in # units of days since the "epoch" for the converter, in this case 1970-01-01 # (see :ref:`date-format`). So when we plot the value 0, the ticks start at -# 1970-01-01, and note that the locator now chooses every two years for a tick -# instead of every month: +# 1970-01-01. (The locator also now chooses every two years for a tick instead +# of every month): fig, ax = plt.subplots(figsize=(5.4, 2), layout='constrained') time = np.arange('1980-01-01', '1980-06-25', dtype='datetime64[D]') @@ -60,7 +60,6 @@ ax.plot(0, 0, 'd') ax.text(0, 0, ' Float x=0', rotation=45) - # %% # # We can customize the locator and the formatter; see :ref:`date-locators` and @@ -80,11 +79,10 @@ # %% # # The default locator is the `~.dates.AutoDateLocator`, and the default -# Formatter `~.dates.AutoDateFormatter`. There is also a "concise" -# formatter/locator that gives a more compact labelling, and can be set via -# rcParams. Note how instead of the redundant "Jan" label at the start of the -# year, "1980" is used instead. See :ref:`date_concise_formatter` for more -# examples. +# Formatter `~.dates.AutoDateFormatter`. There are also "concise" formatter +# and locators that give a more compact labelling, and can be set via rcParams. +# Note how instead of the redundant "Jan" label at the start of the year, +# "1980" is used instead. See :ref:`date_concise_formatter` for more examples. plt.rcParams['date.converter'] = 'concise' @@ -95,9 +93,10 @@ # %% # -# We can set the limits on the axis either by passing the appropriate dates in -# or by passing a floating point value in the proper units of floating days -# since the epoch. We can get this value from `~.dates.date2num`. +# We can set the limits on the axis either by passing the appropriate dates as +# limits, or by passing a floating-point value in the proper units of days +# since the epoch. If we need it, we can get this value from +# `~.dates.date2num`. fig, axs = plt.subplots(2, 1, figsize=(5.4, 3), layout='constrained') for ax in axs.flat: @@ -135,7 +134,8 @@ # # Note that the "categories" are plotted in the order that they are first # specified and that subsequent plotting in a different order will not affect -# the original order. Further, new additions will be added on the end: +# the original order. Further, new additions will be added on the end (see +# "pear" below): fig, ax = plt.subplots(figsize=(5, 3), layout='constrained') ax.bar(names, values) @@ -143,7 +143,7 @@ # plot in a different order: ax.scatter(['lemon', 'apple'], [7, 12]) -# add a new category, and out of order: +# add a new category, "pear", and put the other categories in a different order: ax.plot(['pear', 'orange', 'apple', 'lemon'], [13, 10, 7, 12], color='C1') @@ -154,28 +154,34 @@ # specified. # # The category converter maps from categories to integers, starting at zero. So -# data can also be manually added to the axis using a float. However, note -# that a float that is not a category will not get a label. +# data can also be manually added to the axis using a float. Note that if a +# float is passed in that does not have a "category" associated with it, the +# data point can still be plotted, but a tick will not be created. In the +# following, we plot data at 4.0 and 2.5, but no tick is added there because +# those are not categories. fig, ax = plt.subplots(figsize=(5, 3), layout='constrained') ax.bar(names, values) +# arguments for styling the labels below: +args = {'rotation': 70, 'color': 'C1', + 'bbox': {'color': 'white', 'alpha': .7, 'boxstyle': 'round'}} + # 0 gets labeled as "apple" -ax.plot(0, 0, 'd', color='C1') -ax.text(0, 0, ' Float x=0', rotation=45, color='C1') +ax.plot(0, 2, 'd', color='C1') +ax.text(0, 3, 'Float x=0', **args) # 2 gets labeled as "lemon" -ax.plot(2, 0, 'd', color='C1') -ax.text(2, 0, ' Float x=2', rotation=45, color='C1') +ax.plot(2, 2, 'd', color='C1') +ax.text(2, 3, 'Float x=2', **args) # 4 doesn't get a label -ax.plot(4, 0, 'd', color='C1') -ax.text(4, 0, ' Float x=4', rotation=45, color='C1') +ax.plot(4, 2, 'd', color='C1') +ax.text(4, 3, 'Float x=4', **args) # 2.5 doesn't get a label -ax.plot(2.5, 0, 'd', color='C1') -ax.text(2.5, 0, ' Float x=2.5', rotation=45, color='C1') - +ax.plot(2.5, 2, 'd', color='C1') +ax.text(2.5, 3, 'Float x=2.5', **args) # %% # @@ -220,7 +226,7 @@ # ====================================================== # # Sometimes it is helpful to be able to debug what Matplotlib is using to -# convert the incoming data: we can do that by querying the ``converter`` +# convert the incoming data. We can do that by querying the ``converter`` # property on the axis. We can also query the formatters and locators using # `~.axis.Axis.get_major_locator` and `~.axis.Axis.get_major_formatter`. # @@ -256,11 +262,11 @@ # %% # -# General unit support -# ==================== +# More about "unit" support +# ========================= # -# The support for dates and categories is part of "units" support that is -# built into Matplotlib. This is described at `.matplotlib.units` and in the # +# The support for dates and categories is part of "units" support that is built +# into Matplotlib. This is described at `.matplotlib.units` and in the # :ref:`basic_units` example. # # Unit support works by querying the type of data passed to the plotting @@ -274,5 +280,6 @@ # %% # -# Downstream libraries like pandas, astropy, and pint all can add their own -# converters to Matplotlib. +# Downstream libraries like `pandas `_, +# `astropy `_, `pint `_ +# and others supply their own converters that can be used with Matplotlib. From b420c9d2fc087c59bd83c0be8c9036d52878c7c9 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 4 Oct 2023 15:38:19 -0700 Subject: [PATCH 0237/1120] DOC: add units to user/explain [ci doc] --- galleries/users_explain/axes/axes_units.py | 24 ++++++++++++++-------- galleries/users_explain/axes/index.rst | 1 - 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/galleries/users_explain/axes/axes_units.py b/galleries/users_explain/axes/axes_units.py index 37b3b5a09ef6..6b486f007a12 100644 --- a/galleries/users_explain/axes/axes_units.py +++ b/galleries/users_explain/axes/axes_units.py @@ -1,9 +1,9 @@ """ .. _user_axes_units: -========================================= -Plotting beyond floats: dates and strings -========================================= +========================== +Plotting dates and strings +========================== The most basic way to use Matplotlib plotting methods is to pass coordinates in as numerical numpy arrays. For example, ``plot(x, y)`` will work if ``x`` and @@ -275,11 +275,19 @@ # converter will be ``_SwitchableDateConverter``; if it has has strings in it, # it will be sent to the ``StrCategoryConverter``. -for k in munits.registry: - print(f"type: {k};\n converter: {munits.registry[k]}") +for k, v in munits.registry.items(): + print(f"type: {k};\n converter: {type(v)}") # %% # -# Downstream libraries like `pandas `_, -# `astropy `_, `pint `_ -# and others supply their own converters that can be used with Matplotlib. +# There are a number of downstream libraries that provide their own converters +# with locators and formatters. Physical unit support is provided by +# `astropy `_, `pint `_, and +# `unyt `_, among others. +# +# High level libraries like `pandas `_ and +# `nc-time-axis `_ (and thus +# `xarray `_) provide their own datetime support. +# This support can sometimes be incompatible with Matplotlib native datetime +# support, so care should be taken when using Matplotlib locators and +# formatters if these libraries are being used. diff --git a/galleries/users_explain/axes/index.rst b/galleries/users_explain/axes/index.rst index f0e14f8e3923..308a4d4ea208 100644 --- a/galleries/users_explain/axes/index.rst +++ b/galleries/users_explain/axes/index.rst @@ -24,7 +24,6 @@ annotations like x- and y-labels, titles, and legends. color='darkgrey') fig.suptitle('plt.subplots()') - .. toctree:: :maxdepth: 2 From 67878c2039512695073581b0156009f48b2c7afd Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 28 Sep 2023 20:06:40 +0200 Subject: [PATCH 0238/1120] Clarify that explicit ticklabels are used without further formatting. This was not made explicit in the docs (it is in essence due to the use of FixedFormatter, but that's a rather involved detail for end users). Also minor rewordings. --- lib/matplotlib/axis.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index fa28597a9d08..e7f6724c4372 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1961,8 +1961,9 @@ def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs): ---------- labels : sequence of str or of `.Text`\s Texts for labeling each tick location in the sequence set by - `.Axis.set_ticks`; the number of labels must match the number of - locations. + `.Axis.set_ticks`; the number of labels must match the number of locations. + The labels are used as is, via a `.FixedFormatter` (without further + formatting). minor : bool If True, set minor ticks instead of major ticks. @@ -2093,26 +2094,25 @@ def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs): Parameters ---------- ticks : 1D array-like - Array of tick locations. The axis `.Locator` is replaced by a - `~.ticker.FixedLocator`. + Array of tick locations (either floats or in axis units). The axis + `.Locator` is replaced by a `~.ticker.FixedLocator`. - The values may be either floats or in axis units. - - Pass an empty list to remove all ticks:: - - set_ticks([]) + Pass an empty list (``set_ticks([])``) to remove all ticks. Some tick formatters will not label arbitrary tick positions; e.g. log formatters only label decade ticks by default. In such a case you can set a formatter explicitly on the axis using `.Axis.set_major_formatter` or provide formatted *labels* yourself. + labels : list of str, optional - Tick labels for each location in *ticks*. *labels* must be of the same - length as *ticks*. If not set, the labels are generate using the axis - tick `.Formatter`. + Tick labels for each location in *ticks*; must have the same length as + *ticks*. If set, the labels are used as is, via a `.FixedFormatter`. + If not set, the labels are generated using the axis tick `.Formatter`. + minor : bool, default: False If ``False``, set the major ticks; if ``True``, the minor ticks. + **kwargs `.Text` properties for the labels. Using these is only allowed if you pass *labels*. In other cases, please use `~.Axes.tick_params`. From 78904b65db2ac5baee6c891872447f0505ee912c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 23 Sep 2023 11:28:51 +0200 Subject: [PATCH 0239/1120] Deprecate setting the timer interval while starting it. This feature is unused internally and has simple replacements; removing it will make it easier for subclasses to directly override Timer.start rather than having Timer.start handle the interval and then deferring to a private _timer_start. --- doc/api/next_api_changes/deprecations/26894-AL.rst | 6 ++++++ lib/matplotlib/backend_bases.py | 1 + 2 files changed, 7 insertions(+) create mode 100644 doc/api/next_api_changes/deprecations/26894-AL.rst diff --git a/doc/api/next_api_changes/deprecations/26894-AL.rst b/doc/api/next_api_changes/deprecations/26894-AL.rst new file mode 100644 index 000000000000..b156fa843917 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/26894-AL.rst @@ -0,0 +1,6 @@ +*interval* parameter of ``TimerBase.start`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Setting the timer *interval* while starting it is deprecated. The interval can +be specified instead in the timer constructor, or by setting the +``timer.interval`` attribute. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index cdca80df0c82..a411cfbe3a4a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1117,6 +1117,7 @@ def __del__(self): """Need to stop timer and possibly disconnect timer.""" self._timer_stop() + @_api.delete_parameter("3.9", "interval", alternative="timer.interval") def start(self, interval=None): """ Start the timer object. From eb6e233a85dc2227efef354a04b687ee87199673 Mon Sep 17 00:00:00 2001 From: Pradeep Reddy Raamana Date: Fri, 30 Mar 2018 14:37:00 -0400 Subject: [PATCH 0240/1120] Add clear methods for check and radio buttons Co-authored-by: Elliott Sales de Andrade Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- .../next_whats_new/widget_button_clear.rst | 6 ++ lib/matplotlib/tests/test_widgets.py | 25 ++++- lib/matplotlib/widgets.py | 92 +++++++++++++++++-- lib/matplotlib/widgets.pyi | 9 +- 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 doc/users/next_whats_new/widget_button_clear.rst diff --git a/doc/users/next_whats_new/widget_button_clear.rst b/doc/users/next_whats_new/widget_button_clear.rst new file mode 100644 index 000000000000..2d16cf281e7c --- /dev/null +++ b/doc/users/next_whats_new/widget_button_clear.rst @@ -0,0 +1,6 @@ +Check and Radio Button widgets support clearing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The `.CheckButtons` and `.RadioButtons` widgets now support clearing their +state by calling their ``.clear`` method. Note that it is not possible to have +no selected radio buttons, so the selected option at construction time is selected. diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 3e41712ba530..de17bb79bd4b 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1004,10 +1004,23 @@ def test_lasso_selector_set_props(ax): def test_CheckButtons(ax): - check = widgets.CheckButtons(ax, ('a', 'b', 'c'), (True, False, True)) + labels = ('a', 'b', 'c') + check = widgets.CheckButtons(ax, labels, (True, False, True)) assert check.get_status() == [True, False, True] check.set_active(0) assert check.get_status() == [False, False, True] + assert check.get_checked_labels() == ['c'] + check.clear() + assert check.get_status() == [False, False, False] + assert check.get_checked_labels() == [] + + for invalid_index in [-1, len(labels), len(labels)+5]: + with pytest.raises(ValueError): + check.set_active(index=invalid_index) + + for invalid_value in ['invalid', -1]: + with pytest.raises(TypeError): + check.set_active(1, state=invalid_value) cid = check.on_clicked(lambda: None) check.disconnect(cid) @@ -1045,6 +1058,16 @@ def test_TextBox(ax, toolbar): assert text_change_event.call_count == 3 +def test_RadioButtons(ax): + radio = widgets.RadioButtons(ax, ('Radio 1', 'Radio 2', 'Radio 3')) + radio.set_active(1) + assert radio.value_selected == 'Radio 2' + assert radio.index_selected == 1 + radio.clear() + assert radio.value_selected == 'Radio 1' + assert radio.index_selected == 0 + + @image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) def test_check_radio_buttons_image(): ax = get_ax() diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index a1974a43fe12..cd9716408303 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1176,9 +1176,9 @@ def set_check_props(self, props): # If new colours are supplied, then we must re-apply the status. self._init_status(actives) - def set_active(self, index): + def set_active(self, index, state=None): """ - Toggle (activate or deactivate) a check button by index. + Modify the state of a check button by index. Callbacks will be triggered if :attr:`eventson` is True. @@ -1187,22 +1187,27 @@ def set_active(self, index): index : int Index of the check button to toggle. + state : bool, optional + If a boolean value, set the state explicitly. If no value is + provided, the state is toggled. + Raises ------ ValueError If *index* is invalid. + TypeError + If *state* is not boolean. """ if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') + _api.check_isinstance((bool, None), state=state) invisible = colors.to_rgba('none') facecolors = self._checks.get_facecolor() - facecolors[index] = ( - self._active_check_colors[index] - if colors.same_color(facecolors[index], invisible) - else invisible - ) + if state is None: + state = colors.same_color(facecolors[index], invisible) + facecolors[index] = self._active_check_colors[index] if state else invisible self._checks.set_facecolor(facecolors) if self.drawon: @@ -1233,6 +1238,23 @@ def _init_status(self, actives): [ec if active else "none" for ec, active in zip(self._active_check_colors, actives)]) + def clear(self): + """Uncheck all checkboxes.""" + + self._checks.set_facecolor(['none'] * len(self._active_check_colors)) + + if hasattr(self, '_lines'): + for l1, l2 in self._lines: + l1.set_visible(False) + l2.set_visible(False) + + if self.drawon: + self.canvas.draw() + + if self.eventson: + # Call with no label, as all checkboxes are being cleared. + self._observers.process('clicked', None) + def get_status(self): """ Return a list of the status (True/False) of all of the check buttons. @@ -1240,11 +1262,31 @@ def get_status(self): return [not colors.same_color(color, colors.to_rgba("none")) for color in self._checks.get_facecolors()] + def get_checked_labels(self): + """Return a list of labels currently checked by user.""" + + return [l.get_text() for l, box_checked in + zip(self.labels, self.get_status()) + if box_checked] + def on_clicked(self, func): """ Connect the callback function *func* to button click events. - Returns a connection id, which can be used to disconnect the callback. + Parameters + ---------- + func : callable + When the button is clicked, call *func* with button label. + When all buttons are cleared, call *func* with None. + The callback func must have the signature:: + + def func(label: str | None) -> Any + + Return values may exist, but are ignored. + + Returns + ------- + A connection id, which can be used to disconnect the callback. """ return self._observers.connect('clicked', lambda text: func(text)) @@ -1533,6 +1575,8 @@ class RadioButtons(AxesWidget): The buttons. value_selected : str The label text of the currently selected button. + index_selected : int + The index of the selected button. """ def __init__(self, ax, labels, active=0, activecolor=None, *, @@ -1590,7 +1634,9 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, activecolor = 'blue' # Default. self._activecolor = activecolor + self._initial_active = active self.value_selected = labels[active] + self.index_selected = active ax.set_xticks([]) ax.set_yticks([]) @@ -1716,10 +1762,21 @@ def set_active(self, index): Select button with number *index*. Callbacks will be triggered if :attr:`eventson` is True. + + Parameters + ---------- + index : int + The index of the button to activate. + + Raises + ------ + ValueError + If the index is invalid. """ if index not in range(len(self.labels)): raise ValueError(f'Invalid RadioButton index: {index}') self.value_selected = self.labels[index].get_text() + self.index_selected = index button_facecolors = self._buttons.get_facecolor() button_facecolors[:] = colors.to_rgba("none") button_facecolors[index] = colors.to_rgba(self._active_colors[index]) @@ -1737,11 +1794,28 @@ def set_active(self, index): if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) + def clear(self): + """Reset the active button to the initially active one.""" + self.set_active(self._initial_active) + def on_clicked(self, func): """ Connect the callback function *func* to button click events. - Returns a connection id, which can be used to disconnect the callback. + Parameters + ---------- + func : callable + When the button is clicked, call *func* with button label. + When all buttons are cleared, call *func* with None. + The callback func must have the signature:: + + def func(label: str | None) -> Any + + Return values may exist, but are ignored. + + Returns + ------- + A connection id, which can be used to disconnect the callback. """ return self._observers.connect('clicked', func) diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index ca1e5a71a288..e27da7575b74 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -160,9 +160,11 @@ class CheckButtons(AxesWidget): def set_label_props(self, props: dict[str, Any]) -> None: ... def set_frame_props(self, props: dict[str, Any]) -> None: ... def set_check_props(self, props: dict[str, Any]) -> None: ... - def set_active(self, index: int) -> None: ... + def set_active(self, index: int, state: bool | None = ...) -> None: ... # type: ignore[override] + def clear(self) -> None: ... def get_status(self) -> list[bool]: ... - def on_clicked(self, func: Callable[[str], Any]) -> int: ... + def get_checked_labels(self) -> list[str]: ... + def on_clicked(self, func: Callable[[str | None], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... class TextBox(AxesWidget): @@ -211,7 +213,8 @@ class RadioButtons(AxesWidget): def set_label_props(self, props: dict[str, Any]) -> None: ... def set_radio_props(self, props: dict[str, Any]) -> None: ... def set_active(self, index: int) -> None: ... - def on_clicked(self, func: Callable[[str], Any]) -> int: ... + def clear(self) -> None: ... + def on_clicked(self, func: Callable[[str | None], Any]) -> int: ... def disconnect(self, cid: int) -> None: ... class SubplotTool(Widget): From cd7ba8268b4f2006c4c3777f079163ed706d0e5c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 5 Oct 2023 17:23:04 -0400 Subject: [PATCH 0241/1120] DOC: Fix resizing of animation examples On resize, either `init_func` will be called, or if not defined, the main `func` with `i=0` is called. On animations that save a state, the latter can cause weird glitching as the history will skip around. * For `bayes_update`, I fixed it by providing an `init_func`. * For `double_pendulum`, I fixed it by simplifying the trace line (since we have the entire history pre-computed.) --- galleries/examples/animation/bayes_update.py | 7 ++++++- galleries/examples/animation/double_pendulum.py | 11 ++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/galleries/examples/animation/bayes_update.py b/galleries/examples/animation/bayes_update.py index 86b6e5c342d1..1081b4704623 100644 --- a/galleries/examples/animation/bayes_update.py +++ b/galleries/examples/animation/bayes_update.py @@ -41,6 +41,11 @@ def __init__(self, ax, prob=0.5): # which the plotted distribution should converge. self.ax.axvline(prob, linestyle='--', color='black') + def start(self): + # Used for the *init_func* parameter of FuncAnimation; this is called when + # initializing the animation, and also after resizing the figure. + return self.line, + def __call__(self, i): # This way the plot can continuously run and we just keep # watching new realizations of the process @@ -62,5 +67,5 @@ def __call__(self, i): fig, ax = plt.subplots() ud = UpdateDist(ax, prob=0.7) -anim = FuncAnimation(fig, ud, frames=100, interval=100, blit=True) +anim = FuncAnimation(fig, ud, init_func=ud.start, frames=100, interval=100, blit=True) plt.show() diff --git a/galleries/examples/animation/double_pendulum.py b/galleries/examples/animation/double_pendulum.py index 5f8282daea0f..7a42a6d989ba 100644 --- a/galleries/examples/animation/double_pendulum.py +++ b/galleries/examples/animation/double_pendulum.py @@ -11,8 +11,6 @@ Output generated via `matplotlib.animation.Animation.to_jshtml`. """ -from collections import deque - import matplotlib.pyplot as plt import numpy as np from numpy import cos, sin @@ -92,19 +90,14 @@ def derivs(t, state): trace, = ax.plot([], [], '.-', lw=1, ms=2) time_template = 'time = %.1fs' time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes) -history_x, history_y = deque(maxlen=history_len), deque(maxlen=history_len) def animate(i): thisx = [0, x1[i], x2[i]] thisy = [0, y1[i], y2[i]] - if i == 0: - history_x.clear() - history_y.clear() - - history_x.appendleft(thisx[2]) - history_y.appendleft(thisy[2]) + history_x = x2[:i] + history_y = y2[:i] line.set_data(thisx, thisy) trace.set_data(history_x, history_y) From 4a8397433a7b6383e2341a87901b4c64b4f54f0a Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 1 Oct 2023 17:27:43 -0600 Subject: [PATCH 0242/1120] FIX: Add PyOS_InputHook back to macos backend The PyOS_InputHook is used to run our event loop while waiting on standard input from the user. This means we should flush our events while waiting for any input from the user so the figures are responsive. --- src/_macosx.m | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/_macosx.m b/src/_macosx.m index 8d92b75f6bfe..e9677a40e6b5 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -41,6 +41,66 @@ static bool keyChangeCapsLock = false; /* Keep track of the current mouse up/down state for open/closed cursor hand */ static bool leftMouseGrabbing = false; +/* Keep track of whether stdin has been received */ +static bool stdin_received = false; +static bool stdin_sigint = false; +// Global variable to store the original SIGINT handler +static struct sigaction originalSigintAction = {0}; + +// Signal handler for SIGINT, only sets a flag to exit the run loop +static void handleSigint(int signal) { + stdin_sigint = true; +} + +static int wait_for_stdin() { + @autoreleasepool { + stdin_received = false; + stdin_sigint = false; + + // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too + struct sigaction customAction = {0}; + customAction.sa_handler = handleSigint; + // Set the new handler and store the old one + sigaction(SIGINT, &customAction, &originalSigintAction); + + // Create an NSFileHandle for standard input + NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; + + // Register for data available notifications on standard input + [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification + object: stdinHandle + queue: [NSOperationQueue mainQueue] // Use the main queue + usingBlock: ^(NSNotification *notification) { + // Mark that input has been received + stdin_received = true; + } + ]; + + // Wait in the background for anything that happens to stdin + [stdinHandle waitForDataInBackgroundAndNotify]; + + // continuously run an event loop until the stdin_received flag is set to exit + while (!stdin_received && !stdin_sigint) { + while (true) { + NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) { break; } + [NSApp sendEvent: event]; + } + // We need to run the run loop for a short time to allow the + // events to be processed and keep flushing them while we wait for stdin + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + // Remove the input handler as an observer + [[NSNotificationCenter defaultCenter] removeObserver: stdinHandle]; + + // Restore the original SIGINT handler upon exiting the function + sigaction(SIGINT, &originalSigintAction, NULL); + return 1; + } +} /* ---------------------------- Cocoa classes ---------------------------- */ @@ -139,6 +199,9 @@ static void lazy_init(void) { NSApp = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + // Run our own event loop while waiting for stdin on the Python side + // this is needed to keep the application responsive while waiting for input + PyOS_InputHook = wait_for_stdin; } static PyObject* From 1a3b32fa96806692404788df6baf914426ee1409 Mon Sep 17 00:00:00 2001 From: esibinga Date: Fri, 4 Aug 2023 09:45:15 -0400 Subject: [PATCH 0243/1120] tag guideline draft --- doc/devel/index.rst | 1 + galleries/examples/tag_glossary.rst | 155 ++++++++++++++++++++++++++ galleries/examples/tag_guidelines.rst | 70 ++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 galleries/examples/tag_glossary.rst create mode 100644 galleries/examples/tag_guidelines.rst diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 2b358595255b..75088bce774a 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -146,6 +146,7 @@ Policies and guidelines document style_guide + ../gallery/tag_guidelines .. grid-item-card:: :shadow: none diff --git a/galleries/examples/tag_glossary.rst b/galleries/examples/tag_glossary.rst new file mode 100644 index 000000000000..439524f0b99c --- /dev/null +++ b/galleries/examples/tag_glossary.rst @@ -0,0 +1,155 @@ +:orphan: + +Tag Glossary +============ + + I. API tags: what content from the API reference is in the example? + II. Structural tags: what format is the example? What context can we provide? + III. Domain tags: what discipline(s) might seek this example consistently? + IV. Internal tags: what information is helpful for maintainers or contributors? + + +*1. API tags: what content from the API reference is in the example?* + ++-----------------------------------+---------------------------------------------+ +|``tag`` | use case - if not obvious | ++===================================+=============================================+ +|**Styling** | ++-----------------------------------+---------------------------------------------+ +|``styling: color`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: shape`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: size`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: position`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: texture`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: colormap`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: colorbar`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: linestyle`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: small-multiples`` | | ++-----------------------------------+---------------------------------------------+ +| | | ++-----------------------------------+---------------------------------------------+ +|**Primary or relevant plot component** | ++-----------------------------------+---------------------------------------------+ +|Use these tags when plot contains a teachable example | ++-----------------------------------+---------------------------------------------+ +|``component: axes`` | | ++-----------------------------------+---------------------------------------------+ +|``component: axis`` | | ++-----------------------------------+---------------------------------------------+ +|``component: marker`` | | ++-----------------------------------+---------------------------------------------+ +|``component: label`` | | ++-----------------------------------+---------------------------------------------+ +|``component: title`` | | ++-----------------------------------+---------------------------------------------+ +|``component: legend`` | | ++-----------------------------------+---------------------------------------------+ +|``component: subplot`` | | ++-----------------------------------+---------------------------------------------+ +|``component: figure`` | | ++-----------------------------------+---------------------------------------------+ +|``component: annotation`` | | ++-----------------------------------+---------------------------------------------+ +|``component: label`` | | ++-----------------------------------+---------------------------------------------+ +|``component: ticks`` | | ++-----------------------------------+---------------------------------------------+ +|``component: spines`` |frequently paired with ``component: axis`` | ++-----------------------------------+---------------------------------------------+ +|``component: error`` | | ++-----------------------------------+---------------------------------------------+ +| | | ++-----------------------------------+---------------------------------------------+ +|**Interactivity** | ++-----------------------------------+---------------------------------------------+ +|``interactivity: event handling`` | | ++-----------------------------------+---------------------------------------------+ +|``interactivity: zoom`` | | ++-----------------------------------+---------------------------------------------+ +|``interactivity: pan`` | | ++-----------------------------------+---------------------------------------------+ +|``interactivity: brush`` | | ++-----------------------------------+---------------------------------------------+ +| | | ++-----------------------------------+---------------------------------------------+ +|**Plot Type** | ++-----------------------------------+---------------------------------------------+ +|``plot type: bar`` |example contains a bar plot | ++-----------------------------------+---------------------------------------------+ +|``plot type: line`` |example contains a line plot | ++-----------------------------------+---------------------------------------------+ +|``plot type: pie`` |example contains a pie plot | ++-----------------------------------+---------------------------------------------+ +|``plot type: polar`` |example contains a polar plot | ++-----------------------------------+---------------------------------------------+ +|``plot type: 3D`` |example contains a 3D plot | ++-----------------------------------+---------------------------------------------+ +|``plot type: histogram`` |example contains a histogram | ++-----------------------------------+---------------------------------------------+ +|``plot type: specialty`` | | ++-----------------------------------+---------------------------------------------+ +|``plot type: `` | | ++-----------------------------------+---------------------------------------------+ + + +*2. Structural tags: what format is the example? What context can we provide?* + ++-----------------------+-------------------------------------------------------------------+ +|``tag`` | use case | ++=======================+===================================================================+ +|``level: reference`` |reference docs like "marker reference" or "list of named colors" | +| | - dense collection of short-format information | +| | - well-defined scope, accessible information | ++-----------------------+-------------------------------------------------------------------+ +|``level`` |level refers to how much context/background the user will need | ++-----------------------+-------------------------------------------------------------------+ +|``level: beginner`` |concepts are standalone, self-contained, require only one module | ++-----------------------+-------------------------------------------------------------------+ +|``level: intermediate``|concepts may require knowledge of other modules, have dependencies | ++-----------------------+-------------------------------------------------------------------+ +|``level: advanced`` |concepts require multiple modules and have dependencies | ++-----------------------+-------------------------------------------------------------------+ + +*3. Domain tags: what discipline(s) might seek this example consistently?* + +Note: it's futile to draw fences around "who owns what", and that's not the point of domain tags. Domain tags help groups of people to privately organize relevant information, and so are not displayed publicly. See below for a list of existing domain tags. If you don't see the one you're looking for and you think it should exist, consider proposing it. + ++-------------------------------+----------------------------------------+ +|``tag`` | use case | ++=======================+================================================+ +|``domain`` |for whom is the example relevant? | ++-------------------------------+----------------------------------------+ +|``domain: cartography`` | | ++-------------------------------+----------------------------------------+ +|``domain: geometry`` | | ++-------------------------------+----------------------------------------+ +|``domain: statistics`` | | ++-------------------------------+----------------------------------------+ +|``domain: oceanography`` | | ++-------------------------------+----------------------------------------+ +|``domain: signal-processing`` | | ++-------------------------------+----------------------------------------+ +| | | ++-------------------------------+----------------------------------------+ + +*4. Internal tags: what information is helpful for maintainers or contributors?* + ++---------------------------+-----------------------------------------------------------------------+ +|``tag`` | use case | ++===========================+=======================================================================+ +|``internal: low bandwidth``|allows users to filter out bandwidth-intensive examples like animations| ++---------------------------+-----------------------------------------------------------------------+ +|``internal: untagged`` |allows docs contributors to easily find untagged examples | ++---------------------------+-----------------------------------------------------------------------+ +|``internal: deprecated`` |examples containing deprecated functionality or concepts | ++---------------------------+-----------------------------------------------------------------------+ +|``internal: needs-review`` |example needs to be reviewed for accuracy or pedagogical value | ++---------------------------+-----------------------------------------------------------------------+ diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst new file mode 100644 index 000000000000..2fac2f8712c9 --- /dev/null +++ b/galleries/examples/tag_guidelines.rst @@ -0,0 +1,70 @@ +Guidelines for assigning tags to gallery examples +================================================= + +*Why do we need tags?* + + Tags serve multiple purposes. + + Tags have a one-to-many organization (i.e. one example can have several tags), while the gallery structure requires that examples are placed in only one location. This means tags provide a secondary layer of organization and make the gallery of examples more flexible and more user-friendly. + + They allow for better discoverability, search, and browse functions. They are helpful for users struggling to write a search query for what they're looking for. + + Hidden tags provide additional functionality for maintainers and contributors. + +*What gets a tag?* + +Every gallery example should be tagged with: + +* 1+ content tags +* structural, domain, or internal tag(s) if helpful + +Examples with many tags may indicate that the entry is a showcase example. + +Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). + +Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. + +*Tag Categories* - See :doc:`Tag Glossary ` for a complete list of tags. + +I. API tags: what content from the API reference is in the example? +II. Structural tags: what format is the example? What context can we provide? +III. Domain tags: what discipline(s) might seek this example consistently? +IV. Internal tags: what information is helpful for maintainers or contributors? + +*Proposing new tags* + + 1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``). + 2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two parts: ``subcategory: tag``. Tags should be one or two words. + 3. New tags should be be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. + 4. Tags can recreate other forms of organization. + +Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. + +*How to tag?* + where you put the directive is where the tag will show + +**Related content** + +*What is a gallery example?* + +The gallery of examples contains visual demonstrations of matplolib features. Gallery examples exist so that users can scan through visual examples. + +Unlike tutorials or user guides, gallery examples teach by demonstration, rather than by explanation or instruction. + +Gallery examples should avoid instruction or excessive explanation except for brief clarifying code comments. Instead, they can tag related concepts and/or link to relevant tutorials or user guides. + +Format: + +All gallery examples should aim to follow the following format: + +* Title: 1-6 words, descriptive of content +* Subtitle: 10-50 words, action-oriented description of the example subject +* Image: a clear demonstration of the subject, showing edge cases and different applications if possible +* Code + Text (optional): code, commented as appropriate + written text to add context if necessary + +Example: + +``bbox_intersect`` gallery example showcases the point of visual examples + +* messy example/ hard to categorize, but the gallery is definitely the right spot for it because it's the kind of thing you'd find by visual search +* https://matplotlib.org/devdocs/gallery/misc/bbox_intersect.html#sphx-glr-gallery-misc-bbox-intersect-py From efe71e4d9a92aed867f3301e7c2226880112a261 Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Fri, 6 Oct 2023 15:20:00 -0500 Subject: [PATCH 0244/1120] CI: Update scientific-python/upload-nightly-action to 0.2.0 * The scientific-python/upload-nightly-action action has received stability updates in 0.2.0 that provides a reproducible runtime environment for the action, which should mitigate issues with stability. - c.f. https://github.com/scientific-python/upload-nightly-action/releases/tag/0.2.0 --- .github/workflows/nightlies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 31d043f55819..76e7fee0b205 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -59,7 +59,7 @@ jobs: ls -l dist/ - name: Upload wheels to Anaconda Cloud as nightlies - uses: scientific-python/upload-nightly-action@8f0394fd2aa0c85d7364a9958652e8994e06b23c # 0.1.0 + uses: scientific-python/upload-nightly-action@5fb764c5bce1ac2297084c0f7161b1919f17c74f # 0.2.0 with: artifacts_path: dist anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} From 52d87ca317a2f6ed7a272422ace8a2e5d3f2138a Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 12 Sep 2023 13:50:42 -0400 Subject: [PATCH 0245/1120] separate and clarify axisartist default tables --- .../users_explain/toolkits/axisartist.rst | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst index 313a0fb5a9e9..4927f9a3c940 100644 --- a/galleries/users_explain/toolkits/axisartist.rst +++ b/galleries/users_explain/toolkits/axisartist.rst @@ -341,18 +341,37 @@ On the other hand, there is a concept of "axis_direction". This is a default setting of above properties for each, "bottom", "left", "top", and "right" axis. -========== ========== ========= ========== ========= ========== -label type parameter left bottom right top -========== ========== ========= ========== ========= ========== -axislabel direction '-' '+' '+' '-' -axislabel rotation 180 0 0 180 -axislabel va center top center bottom -axislabel ha right center right center -ticklabel direction '-' '+' '+' '-' -ticklabel rotation 90 0 -90 180 -ticklabel ha right center right center -ticklabel va center baseline center baseline -========== ========== ========= ========== ========= ========== +.. table:: ``axislabel`` property defaults + + +---------------------+-----------------+----------------+----------------------+--------------------+ + | reference direction | label direction | label rotation | horizontal alignment | vertical alignment | + +=====================+=================+================+======================+====================+ + | left | '-' | 180 | right | center | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | bottom | '+' | 0 | center | top | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | right | '+' | 0 | right | center | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | top | '-' | 180 | center | bottom | + +---------------------+-----------------+----------------+----------------------+--------------------+ + + + +.. table:: ``ticklabel`` property defaults + + +---------------------+-----------------+----------------+----------------------+--------------------+ + | reference direction | label direction | label rotation | horizontal alignment | vertical alignment | + +=====================+=================+================+======================+====================+ + | left | '-' | 90 | right | center | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | bottom | '+' | 0 | center | baseline | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | right | '+' | -90 | right | center | + +---------------------+-----------------+----------------+----------------------+--------------------+ + | top | '-' | 180 | center | baseline | + +---------------------+-----------------+----------------+----------------------+--------------------+ + + And, 'set_axis_direction("top")' means to adjust the text rotation etc, for settings suitable for "top" axis. The concept of axis From be1c941a72592326a7775c7fe93ca8ea9c7f6297 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 6 Oct 2023 01:28:26 -0400 Subject: [PATCH 0246/1120] document table usage and clean up headings --- doc/devel/document.rst | 48 ++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/doc/devel/document.rst b/doc/devel/document.rst index ac92888b4052..095b42d689ce 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -142,7 +142,7 @@ It is useful to strive for consistency in the Matplotlib documentation. Here are some formatting and style conventions that are used. Section formatting -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ Use `sentence case `__ ``Upper lower`` for section titles, e.g., ``Possible hangups`` rather than @@ -163,8 +163,25 @@ for section markup characters, i.e.: This may not yet be applied consistently in existing docs. +Table formatting +^^^^^^^^^^^^^^^^ +Given the size of the table and length of each entry, use: + ++-------------+-------------------------------+--------------------+ +| | small table | large table | ++-------------+-------------------------------+--------------------+ +| short entry | `simple or grid table `_ | `grid table `_ | ++-------------+-------------------------------+--------------------+ +| long entry | `list table `_ | `csv table `_ | ++-------------+-------------------------------+--------------------+ + +For more information, see `rst tables `_. +.. _sg: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables +.. _lt: https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table +.. _csv: https://docutils.sourceforge.io/docs/ref/rst/directives.html#toc-entry-22 + Function arguments -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ Function arguments and keywords within docstrings should be referred to using the ``*emphasis*`` role. This will keep Matplotlib's documentation consistent @@ -445,7 +462,8 @@ and the Sphinx_ documentation. Some Matplotlib-specific formatting conventions to keep in mind: Quote positions -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ + The quotes for single line docstrings are on the same line (pydocstyle D200):: def get_linewidth(self): @@ -461,7 +479,8 @@ The quotes for multi-line docstrings are on separate lines (pydocstyle D213):: """ Function arguments -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ + Function arguments and keywords within docstrings should be referred to using the ``*emphasis*`` role. This will keep Matplotlib's documentation consistent with Python's documentation: @@ -478,7 +497,8 @@ Do not use the ```default role``` or the ````literal```` role: Quotes for strings -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ + Matplotlib does not have a convention whether to use single-quotes or double-quotes. There is a mixture of both in the current code. @@ -495,7 +515,8 @@ slightly improve the rendered docs, they are cumbersome to type and difficult to read in plain-text docs. Parameter type descriptions -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + The main goal for parameter type descriptions is to be readable and understandable by humans. If the possible types are too complex use a simplification for the type description and explain the type more @@ -534,7 +555,8 @@ Non-numeric homogeneous sequences are described as lists, e.g.:: list of `.Artist` Reference types -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ + Generally, the rules from referring-to-other-code_ apply. More specifically: Use full references ```~matplotlib.colors.Normalize``` with an @@ -550,7 +572,8 @@ Use abbreviated links ```.Normalize``` in the text. A `.Normalize` instance is used to scale luminance data to 0, 1. Default values -~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^ + As opposed to the numpydoc guide, parameters need not be marked as *optional* if they have a simple default: @@ -592,7 +615,8 @@ effect. ``See also`` sections -~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^ + Sphinx automatically links code elements in the definition blocks of ``See also`` sections. No need to use backticks there:: @@ -602,7 +626,8 @@ also`` sections. No need to use backticks there:: axhline : horizontal line across the Axes Wrap parameter lists -~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^ + Long parameter lists should be wrapped using a ``\`` for continuation and starting on the new line without any indent (no indent because pydoc will parse the docstring and strip the line continuation so that indent would @@ -627,7 +652,8 @@ Alternatively, you can describe the valid parameter values in a dedicated section of the docstring. rcParams -~~~~~~~~ +^^^^^^^^ + rcParams can be referenced with the custom ``:rc:`` role: :literal:`:rc:\`foo\`` yields ``rcParams["foo"] = 'default'``, which is a link to the :file:`matplotlibrc` file description. From 6e46393b6103af3eb41acb201bf68625a1757251 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 6 Oct 2023 04:14:18 -0400 Subject: [PATCH 0247/1120] Bump required C++ standard to c++17 According to SciPy's Toolchain Roadmap [1], C++17 core features became available in 2022. This was concluded based on minimum compiler versions in `manylinux2014` images, AIX systems, FreeBSD systems, and Windows. Note, some standard library features are not universally supported. This commit does not move much code to C++17, just a couple of minor improvements that I could easily change. [1] https://docs.scipy.org/doc/scipy/dev/toolchain.html#c-language-standards --- .../next_api_changes/development/27012-ES.rst | 7 +++ doc/devel/dependencies.rst | 14 ++--- meson.build | 2 +- src/_image_wrapper.cpp | 51 +++++++++---------- 4 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 doc/api/next_api_changes/development/27012-ES.rst diff --git a/doc/api/next_api_changes/development/27012-ES.rst b/doc/api/next_api_changes/development/27012-ES.rst new file mode 100644 index 000000000000..1bec3e7d91df --- /dev/null +++ b/doc/api/next_api_changes/development/27012-ES.rst @@ -0,0 +1,7 @@ +Extensions require C++17 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Matplotlib now requires a compiler that supports C++17 in order to build its extensions. +According to `SciPy's analysis +`_, this +should be available on all supported platforms. diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 77df6e494d50..0ffdaf65696a 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -233,7 +233,7 @@ in your target environment manually: C++ compiler ------------ -Matplotlib requires a C++ compiler that supports C++11, and each platform has a +Matplotlib requires a C++ compiler that supports C++17, and each platform has a development environment that must be installed before a compiler can be installed. .. tab-set:: @@ -277,18 +277,18 @@ Xcode, VS Code or Linux package manager. Choose **one** compiler from this list: - platforms - notes * - GCC - - **4.8.1** + - **7.2** - Linux, macOS, Windows - - `gcc 4.8.1 `_, + - `gcc 7.2 `_, `GCC: Binaries `_, * - Clang (LLVM) - - **3.3** + - **5** - Linux, macOS - - `clang 3.3 `_, `LLVM `_ + - `clang 5 `_, `LLVM `_ * - MSVC++ - - **14.0** + - **16.0** - Windows - - `Visual Studio 2015 C++ `_ + - `Visual Studio 2019 C++ `_ diff --git a/meson.build b/meson.build index 1ba673948a1e..41f080b3700e 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project( meson_version: '>=1.1.0', default_options: [ 'b_lto=true', - 'cpp_std=c++11', + 'cpp_std=c++17', 'auto_features=disabled', # Force FreeType to avoid extra dependencies. ], ) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 179f8d1301f7..b18e93a50fb3 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -10,41 +10,40 @@ * */ const char* image_resample__doc__ = -"Resample input_array, blending it in-place into output_array, using an\n" -"affine transformation.\n\n" +R"""(Resample input_array, blending it in-place into output_array, using an affine transform. -"Parameters\n" -"----------\n" -"input_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n" -" If 2-d, the image is grayscale. If 3-d, the image must be of size\n" -" 4 in the last dimension and represents RGBA data.\n\n" +Parameters +---------- +input_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8` + If 2-d, the image is grayscale. If 3-d, the image must be of size 4 in the last + dimension and represents RGBA data. -"output_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n" -" The dtype and number of dimensions must match `input_array`.\n\n" +output_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8` + The dtype and number of dimensions must match `input_array`. -"transform : matplotlib.transforms.Transform instance\n" -" The transformation from the input array to the output array.\n\n" +transform : matplotlib.transforms.Transform instance + The transformation from the input array to the output array. -"interpolation : int, default: NEAREST\n" -" The interpolation method. Must be one of the following constants\n" -" defined in this module:\n\n" +interpolation : int, default: NEAREST + The interpolation method. Must be one of the following constants defined in this + module: -" NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36,\n" -" HANNING, HAMMING, HERMITE, KAISER, QUADRIC, CATROM, GAUSSIAN,\n" -" BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN\n\n" + NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36, HANNING, HAMMING, HERMITE, KAISER, + QUADRIC, CATROM, GAUSSIAN, BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN -"resample : bool, optional\n" -" When `True`, use a full resampling method. When `False`, only\n" -" resample when the output image is larger than the input image.\n\n" +resample : bool, optional + When `True`, use a full resampling method. When `False`, only resample when the + output image is larger than the input image. -"alpha : float, default: 1\n" -" The transparency level, from 0 (transparent) to 1 (opaque).\n\n" +alpha : float, default: 1 + The transparency level, from 0 (transparent) to 1 (opaque). -"norm : bool, default: False\n" -" Whether to norm the interpolation function.\n\n" +norm : bool, default: False + Whether to norm the interpolation function. -"radius: float, default: 1\n" -" The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; +radius: float, default: 1 + The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN. +)"""; static pybind11::array_t From 1a53e5ba11089c747c9ffbd38dcd03372b1f98ef Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 Sep 2023 03:27:21 -0400 Subject: [PATCH 0248/1120] ci: Run mypy against typed cycler --- .github/workflows/reviewdog.yml | 2 -- pyproject.toml | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index dafc331a11eb..f9c1121581c0 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -65,9 +65,7 @@ jobs: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -o pipefail - # The --ignore-missing-imports can be removed when typed cycler is released and used mypy --config pyproject.toml lib/matplotlib \ - --ignore-missing-imports \ --follow-imports silent | \ reviewdog -f=mypy -name=mypy \ -tee -reporter=github-check -filter-mode nofilter diff --git a/pyproject.toml b/pyproject.toml index 37d97e02b956..e0c7e4e9dfee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,7 +214,6 @@ exclude = [ # stubtest will import and run, opening a figure if not excluded ".*/tinypages", ] -ignore_missing_imports = true enable_incomplete_feature = [ "Unpack", ] From 677a4064f87edef0ef27c68b520df5094eb4ee87 Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Sat, 7 Oct 2023 11:23:19 +0530 Subject: [PATCH 0249/1120] Add test_contourf #26864 --- lib/matplotlib/tests/test_datetime.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 53958229f174..39bf6f6356e8 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -122,11 +122,29 @@ def test_contour(self): fig, ax = plt.subplots() ax.contour(...) - @pytest.mark.xfail(reason="Test for contourf not written yet") @mpl.style.context("default") def test_contourf(self): - fig, ax = plt.subplots() - ax.contourf(...) + mpl.rcParams["date.converter"] = "concise" + range_threshold = 10 + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + + x_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + y_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + x_ranges = np.array(range(1, range_threshold)) + y_ranges = np.array(range(1, range_threshold)) + + X_dates, Y_dates = np.meshgrid(x_dates, y_dates) + X_ranges, Y_ranges = np.meshgrid(x_ranges, y_ranges) + + Z_ranges = np.cos(X_ranges / 4) + np.sin(Y_ranges / 4) + + ax1.contourf(X_dates, Y_dates, Z_ranges) + ax2.contourf(X_dates, Y_ranges, Z_ranges) + ax3.contourf(X_ranges, Y_dates, Z_ranges) @pytest.mark.xfail(reason="Test for csd not written yet") @mpl.style.context("default") From 10b4157c1d99e46a88428ab893400cd6e3be8309 Mon Sep 17 00:00:00 2001 From: Augusto Borges Date: Fri, 6 Oct 2023 12:00:23 +0200 Subject: [PATCH 0250/1120] ValueError exception added to handle mix of {} and % string --- lib/matplotlib/colorbar.py | 2 +- lib/matplotlib/tests/test_colorbar.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 5c37eef5190b..6c92f3795384 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -404,7 +404,7 @@ def __init__(self, ax, mappable=None, *, cmap=None, try: self._formatter = ticker.FormatStrFormatter(format) _ = self._formatter(0) - except TypeError: + except (TypeError, ValueError): self._formatter = ticker.StrMethodFormatter(format) else: self._formatter = format # Assume it is a Formatter or None diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 73c4dab9a87f..0cf098e787ee 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -15,7 +15,7 @@ BoundaryNorm, LogNorm, PowerNorm, Normalize, NoNorm ) from matplotlib.colorbar import Colorbar -from matplotlib.ticker import FixedLocator, LogFormatter +from matplotlib.ticker import FixedLocator, LogFormatter, StrMethodFormatter from matplotlib.testing.decorators import check_figures_equal @@ -1230,3 +1230,9 @@ def test_colorbar_wrong_figure(): fig_tl.colorbar(im) fig_tl.draw_without_rendering() fig_cl.draw_without_rendering() + + +def test_colorbar_format_string_and_old(): + plt.imshow([[0, 1]]) + cb = plt.colorbar(format="{x}%") + assert isinstance(cb._formatter, StrMethodFormatter) From 38a17d681a16a8499694a26854971ca24df13f49 Mon Sep 17 00:00:00 2001 From: Augusto Borges Date: Thu, 5 Oct 2023 20:34:31 +0200 Subject: [PATCH 0251/1120] Warning issued if handles and labels have a len mismatch when __len__ exists for both New check for the no len case. Now artists are stored --- lib/matplotlib/legend.py | 6 ++++++ lib/matplotlib/tests/test_legend.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 14249cb73442..58e5dfcfe3b8 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1337,6 +1337,12 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): _api.warn_external("You have mixed positional and keyword arguments, " "some input may be discarded.") + if (hasattr(handles, "__len__") and + hasattr(labels, "__len__") and + len(handles) != len(labels)): + _api.warn_external(f"Mismatched number of handles and labels: " + f"len(handles) = {len(handles)} " + f"len(labels) = {len(labels)}") # if got both handles and labels as kwargs, make same length if handles and labels: handles, labels = zip(*zip(handles, labels)) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 1549354ba56b..3a35b4051c71 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1357,3 +1357,21 @@ def test_loc_validation_string_value(): ax.legend(loc='upper center') with pytest.raises(ValueError, match="'wrong' is not a valid value for"): ax.legend(loc='wrong') + + +def test_legend_handle_label_mismatch(): + pl1, = plt.plot(range(10)) + pl2, = plt.plot(range(10)) + with pytest.warns(UserWarning, match="number of handles and labels"): + legend = plt.legend(handles=[pl1, pl2], labels=["pl1", "pl2", "pl3"]) + assert len(legend.legend_handles) == 2 + assert len(legend.get_texts()) == 2 + + +def test_legend_handle_label_mismatch_no_len(): + pl1, = plt.plot(range(10)) + pl2, = plt.plot(range(10)) + legend = plt.legend(handles=iter([pl1, pl2]), + labels=iter(["pl1", "pl2", "pl3"])) + assert len(legend.legend_handles) == 2 + assert len(legend.get_texts()) == 2 From 04347743c7a2850ca85da0259ec92e78fa3bb9e4 Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Sun, 8 Oct 2023 19:29:54 +0530 Subject: [PATCH 0252/1120] Add test_bar in test_datetime --- lib/matplotlib/tests/test_datetime.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 53958229f174..2566baad27d1 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -62,11 +62,24 @@ def test_axvspan(self): fig, ax = plt.subplots() ax.axvspan(...) - @pytest.mark.xfail(reason="Test for bar not written yet") @mpl.style.context("default") def test_bar(self): - fig, ax = plt.subplots() - ax.bar(...) + mpl.rcParams["date.converter"] = "concise" + range_threshold = 10 + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + + x_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + y_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + x_ranges = np.array(range(1, range_threshold)) + y_ranges = np.array(range(1, range_threshold)) + + ax1.bar(x_dates, y_ranges) + ax2.bar(x_dates, y_dates) + ax3.bar(x_ranges, y_dates) @pytest.mark.xfail(reason="Test for bar_label not written yet") @mpl.style.context("default") From 7d89a388a188a6181d2df498ff8c2fb001f3389d Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 6 Oct 2023 08:03:55 -0700 Subject: [PATCH 0253/1120] DOC: clarify usetex versus mathtext [ci doc] --- doc/users/prev_whats_new/whats_new_3.8.0.rst | 4 ++++ galleries/users_explain/text/mathtext.py | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/users/prev_whats_new/whats_new_3.8.0.rst b/doc/users/prev_whats_new/whats_new_3.8.0.rst index ab5a0dce85da..8c34252098db 100644 --- a/doc/users/prev_whats_new/whats_new_3.8.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.8.0.rst @@ -327,6 +327,10 @@ This allows users to set the location of the legend in a more flexible and consi Mathtext improvements ===================== +Improvements are to Mathtext, Matplotlib's native TeX-like mathematics parser +(see :ref:`mathtext`, not to be confused with Matplotlib using LaTeX directly: +:ref:`usetex`). + Boldsymbol mathtext command ``\boldsymbol`` ------------------------------------------- diff --git a/galleries/users_explain/text/mathtext.py b/galleries/users_explain/text/mathtext.py index 067a332aa54a..27aad440dcb5 100644 --- a/galleries/users_explain/text/mathtext.py +++ b/galleries/users_explain/text/mathtext.py @@ -8,11 +8,15 @@ ================================ Matplotlib implements a lightweight TeX expression parser and layout engine and -*Mathtext* is the subset of Tex markup that this engine supports. Any string can -be processed as Mathtext by placing the string inside a pair of dollar signs -``'$'``. Mathtext often contains many backslashes ``'\'``; so that the backslashes -do not need to be escaped, Mathtext is often written using raw strings. For -example: +*Mathtext* is the subset of Tex markup that this engine supports. Note that +Matplotlib can also render all text directly using TeX if :rc:`text.usetex` is +*True*; see :ref:`usetex` for more details. Mathtext support is available +if :rc:`text.usetex` is *False*. + +Any string can be processed as Mathtext by placing the string inside a pair of +dollar signs ``'$'``. Mathtext often contains many backslashes ``'\'``; so that +the backslashes do not need to be escaped, Mathtext is often written using raw +strings. For example: """ import matplotlib.pyplot as plt From 0d17fb025b1d653e45b894bad3fbb7a165fdccb6 Mon Sep 17 00:00:00 2001 From: Abdul Razak Taha Date: Sun, 8 Oct 2023 23:43:23 +0000 Subject: [PATCH 0254/1120] Add a section to Highlight past winners for JDH plotting contest in docs (#27021) * Update index.rst Added a link to past winners for the JDH plotting contest in fixing issue #11129 * Update index.rst changed formatting on JDH plotting link to RST syntax. * changed description of PR and alignment contest items realigned contest link, and author items under galleries section to render on same line --- doc/users/resources/index.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/users/resources/index.rst b/doc/users/resources/index.rst index a2c4ccd4a7fc..77010f176048 100644 --- a/doc/users/resources/index.rst +++ b/doc/users/resources/index.rst @@ -71,10 +71,6 @@ Videos Tutorials ========= - -* `The Python Graph Gallery `_ - by Yan Holtz - * `Matplotlib tutorial `_ by Nicolas P. Rougier @@ -85,3 +81,13 @@ Tutorials * `Beyond the Basics: Data Visualization in Python `_ by Stefanie Molin + +========= +Galleries +========= + +* `Past winners for JDH plotting contest `_ + by Nelle Varoquaux + +* `The Python Graph Gallery `_ + by Yan Holtz From 05035bae9f9513ec49c330bba72872f5627beed7 Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Thu, 14 Sep 2023 22:39:23 +0530 Subject: [PATCH 0255/1120] Add mypy overloading to stix table Co-authored-by: Kyle Sunden --- lib/matplotlib/_mathtext_data.py | 749 ++++++++++++++++++++++++------- 1 file changed, 598 insertions(+), 151 deletions(-) diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index baaee1b876da..764555def495 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -3,7 +3,7 @@ """ from __future__ import annotations - +from typing import overload latex_to_bakoma = { '\\__sqrt__' : ('cmex10', 0x70), @@ -1112,197 +1112,644 @@ # Each element is a 4-tuple of the form: # src_start, src_end, dst_font, dst_start -# -stix_virtual_fonts: dict[str, dict[str, list[tuple[int, int, str, int]]] | - list[tuple[int, int, str, int]]] = { + +_EntryTypeIn = tuple[str, str, str, str | int] +_EntryTypeOut = tuple[int, int, str, int] + +_stix_virtual_fonts: dict[str, dict[str, list[_EntryTypeIn]] | + list[_EntryTypeIn]] = { 'bb': { - 'rm': + "rm": [ - (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9 - (0x0041, 0x0042, 'rm', 0x1d538), # A-B - (0x0043, 0x0043, 'rm', 0x2102), # C - (0x0044, 0x0047, 'rm', 0x1d53b), # D-G - (0x0048, 0x0048, 'rm', 0x210d), # H - (0x0049, 0x004d, 'rm', 0x1d540), # I-M - (0x004e, 0x004e, 'rm', 0x2115), # N - (0x004f, 0x004f, 'rm', 0x1d546), # O - (0x0050, 0x0051, 'rm', 0x2119), # P-Q - (0x0052, 0x0052, 'rm', 0x211d), # R - (0x0053, 0x0059, 'rm', 0x1d54a), # S-Y - (0x005a, 0x005a, 'rm', 0x2124), # Z - (0x0061, 0x007a, 'rm', 0x1d552), # a-z - (0x0393, 0x0393, 'rm', 0x213e), # \Gamma - (0x03a0, 0x03a0, 'rm', 0x213f), # \Pi - (0x03a3, 0x03a3, 'rm', 0x2140), # \Sigma - (0x03b3, 0x03b3, 'rm', 0x213d), # \gamma - (0x03c0, 0x03c0, 'rm', 0x213c), # \pi + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER B}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK CAPITAL A}"), + ("\N{LATIN CAPITAL LETTER C}", + "\N{LATIN CAPITAL LETTER C}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL C}"), + ("\N{LATIN CAPITAL LETTER D}", + "\N{LATIN CAPITAL LETTER G}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK CAPITAL D}"), + ("\N{LATIN CAPITAL LETTER H}", + "\N{LATIN CAPITAL LETTER H}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL H}"), + ("\N{LATIN CAPITAL LETTER I}", + "\N{LATIN CAPITAL LETTER M}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK CAPITAL I}"), + ("\N{LATIN CAPITAL LETTER N}", + "\N{LATIN CAPITAL LETTER N}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL N}"), + ("\N{LATIN CAPITAL LETTER O}", + "\N{LATIN CAPITAL LETTER O}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK CAPITAL O}"), + ("\N{LATIN CAPITAL LETTER P}", + "\N{LATIN CAPITAL LETTER Q}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL P}"), + ("\N{LATIN CAPITAL LETTER R}", + "\N{LATIN CAPITAL LETTER R}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL R}"), + ("\N{LATIN CAPITAL LETTER S}", + "\N{LATIN CAPITAL LETTER Y}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK CAPITAL S}"), + ("\N{LATIN CAPITAL LETTER Z}", + "\N{LATIN CAPITAL LETTER Z}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL Z}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK SMALL A}"), + ("\N{GREEK CAPITAL LETTER GAMMA}", + "\N{GREEK CAPITAL LETTER GAMMA}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL GAMMA}"), + ("\N{GREEK CAPITAL LETTER PI}", + "\N{GREEK CAPITAL LETTER PI}", + "rm", + "\N{DOUBLE-STRUCK CAPITAL PI}"), + ("\N{GREEK CAPITAL LETTER SIGMA}", + "\N{GREEK CAPITAL LETTER SIGMA}", + "rm", + "\N{DOUBLE-STRUCK N-ARY SUMMATION}"), + ("\N{GREEK SMALL LETTER GAMMA}", + "\N{GREEK SMALL LETTER GAMMA}", + "rm", + "\N{DOUBLE-STRUCK SMALL GAMMA}"), + ("\N{GREEK SMALL LETTER PI}", + "\N{GREEK SMALL LETTER PI}", + "rm", + "\N{DOUBLE-STRUCK SMALL PI}"), ], - 'it': + "it": [ - (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9 - (0x0041, 0x0042, 'it', 0xe154), # A-B - (0x0043, 0x0043, 'it', 0x2102), # C - (0x0044, 0x0044, 'it', 0x2145), # D - (0x0045, 0x0047, 'it', 0xe156), # E-G - (0x0048, 0x0048, 'it', 0x210d), # H - (0x0049, 0x004d, 'it', 0xe159), # I-M - (0x004e, 0x004e, 'it', 0x2115), # N - (0x004f, 0x004f, 'it', 0xe15e), # O - (0x0050, 0x0051, 'it', 0x2119), # P-Q - (0x0052, 0x0052, 'it', 0x211d), # R - (0x0053, 0x0059, 'it', 0xe15f), # S-Y - (0x005a, 0x005a, 'it', 0x2124), # Z - (0x0061, 0x0063, 'it', 0xe166), # a-c - (0x0064, 0x0065, 'it', 0x2146), # d-e - (0x0066, 0x0068, 'it', 0xe169), # f-h - (0x0069, 0x006a, 'it', 0x2148), # i-j - (0x006b, 0x007a, 'it', 0xe16c), # k-z - (0x0393, 0x0393, 'it', 0x213e), # \Gamma (not in beta STIX fonts) - (0x03a0, 0x03a0, 'it', 0x213f), # \Pi - (0x03a3, 0x03a3, 'it', 0x2140), # \Sigma (not in beta STIX fonts) - (0x03b3, 0x03b3, 'it', 0x213d), # \gamma (not in beta STIX fonts) - (0x03c0, 0x03c0, 'it', 0x213c), # \pi + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER B}", + "it", + 0xe154), + ("\N{LATIN CAPITAL LETTER C}", + "\N{LATIN CAPITAL LETTER C}", + "it", + "\N{DOUBLE-STRUCK CAPITAL C}"), + ("\N{LATIN CAPITAL LETTER D}", + "\N{LATIN CAPITAL LETTER D}", + "it", + "\N{DOUBLE-STRUCK ITALIC CAPITAL D}"), + ("\N{LATIN CAPITAL LETTER E}", + "\N{LATIN CAPITAL LETTER G}", + "it", + 0xe156), + ("\N{LATIN CAPITAL LETTER H}", + "\N{LATIN CAPITAL LETTER H}", + "it", + "\N{DOUBLE-STRUCK CAPITAL H}"), + ("\N{LATIN CAPITAL LETTER I}", + "\N{LATIN CAPITAL LETTER M}", + "it", + 0xe159), + ("\N{LATIN CAPITAL LETTER N}", + "\N{LATIN CAPITAL LETTER N}", + "it", + "\N{DOUBLE-STRUCK CAPITAL N}"), + ("\N{LATIN CAPITAL LETTER O}", + "\N{LATIN CAPITAL LETTER O}", + "it", + 0xe15e), + ("\N{LATIN CAPITAL LETTER P}", + "\N{LATIN CAPITAL LETTER Q}", + "it", + "\N{DOUBLE-STRUCK CAPITAL P}"), + ("\N{LATIN CAPITAL LETTER R}", + "\N{LATIN CAPITAL LETTER R}", + "it", + "\N{DOUBLE-STRUCK CAPITAL R}"), + ("\N{LATIN CAPITAL LETTER S}", + "\N{LATIN CAPITAL LETTER Y}", + "it", + 0xe15f), + ("\N{LATIN CAPITAL LETTER Z}", + "\N{LATIN CAPITAL LETTER Z}", + "it", + "\N{DOUBLE-STRUCK CAPITAL Z}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER C}", + "it", + 0xe166), + ("\N{LATIN SMALL LETTER D}", + "\N{LATIN SMALL LETTER E}", + "it", + "\N{DOUBLE-STRUCK ITALIC SMALL D}"), + ("\N{LATIN SMALL LETTER F}", + "\N{LATIN SMALL LETTER H}", + "it", + 0xe169), + ("\N{LATIN SMALL LETTER I}", + "\N{LATIN SMALL LETTER J}", + "it", + "\N{DOUBLE-STRUCK ITALIC SMALL I}"), + ("\N{LATIN SMALL LETTER K}", + "\N{LATIN SMALL LETTER Z}", + "it", + 0xe16c), + ("\N{GREEK CAPITAL LETTER GAMMA}", + "\N{GREEK CAPITAL LETTER GAMMA}", + "it", + "\N{DOUBLE-STRUCK CAPITAL GAMMA}"), # \Gamma (not in beta STIX fonts) + ("\N{GREEK CAPITAL LETTER PI}", + "\N{GREEK CAPITAL LETTER PI}", + "it", + "\N{DOUBLE-STRUCK CAPITAL PI}"), + ("\N{GREEK CAPITAL LETTER SIGMA}", + "\N{GREEK CAPITAL LETTER SIGMA}", + "it", + "\N{DOUBLE-STRUCK N-ARY SUMMATION}"), # \Sigma (not in beta STIX fonts) + ("\N{GREEK SMALL LETTER GAMMA}", + "\N{GREEK SMALL LETTER GAMMA}", + "it", + "\N{DOUBLE-STRUCK SMALL GAMMA}"), # \gamma (not in beta STIX fonts) + ("\N{GREEK SMALL LETTER PI}", + "\N{GREEK SMALL LETTER PI}", + "it", + "\N{DOUBLE-STRUCK SMALL PI}"), ], - 'bf': + "bf": [ - (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9 - (0x0041, 0x0042, 'bf', 0xe38a), # A-B - (0x0043, 0x0043, 'bf', 0x2102), # C - (0x0044, 0x0044, 'bf', 0x2145), # D - (0x0045, 0x0047, 'bf', 0xe38d), # E-G - (0x0048, 0x0048, 'bf', 0x210d), # H - (0x0049, 0x004d, 'bf', 0xe390), # I-M - (0x004e, 0x004e, 'bf', 0x2115), # N - (0x004f, 0x004f, 'bf', 0xe395), # O - (0x0050, 0x0051, 'bf', 0x2119), # P-Q - (0x0052, 0x0052, 'bf', 0x211d), # R - (0x0053, 0x0059, 'bf', 0xe396), # S-Y - (0x005a, 0x005a, 'bf', 0x2124), # Z - (0x0061, 0x0063, 'bf', 0xe39d), # a-c - (0x0064, 0x0065, 'bf', 0x2146), # d-e - (0x0066, 0x0068, 'bf', 0xe3a2), # f-h - (0x0069, 0x006a, 'bf', 0x2148), # i-j - (0x006b, 0x007a, 'bf', 0xe3a7), # k-z - (0x0393, 0x0393, 'bf', 0x213e), # \Gamma - (0x03a0, 0x03a0, 'bf', 0x213f), # \Pi - (0x03a3, 0x03a3, 'bf', 0x2140), # \Sigma - (0x03b3, 0x03b3, 'bf', 0x213d), # \gamma - (0x03c0, 0x03c0, 'bf', 0x213c), # \pi + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER B}", + "bf", + 0xe38a), + ("\N{LATIN CAPITAL LETTER C}", + "\N{LATIN CAPITAL LETTER C}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL C}"), + ("\N{LATIN CAPITAL LETTER D}", + "\N{LATIN CAPITAL LETTER D}", + "bf", + "\N{DOUBLE-STRUCK ITALIC CAPITAL D}"), + ("\N{LATIN CAPITAL LETTER E}", + "\N{LATIN CAPITAL LETTER G}", + "bf", + 0xe38d), + ("\N{LATIN CAPITAL LETTER H}", + "\N{LATIN CAPITAL LETTER H}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL H}"), + ("\N{LATIN CAPITAL LETTER I}", + "\N{LATIN CAPITAL LETTER M}", + "bf", + 0xe390), + ("\N{LATIN CAPITAL LETTER N}", + "\N{LATIN CAPITAL LETTER N}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL N}"), + ("\N{LATIN CAPITAL LETTER O}", + "\N{LATIN CAPITAL LETTER O}", + "bf", + 0xe395), + ("\N{LATIN CAPITAL LETTER P}", + "\N{LATIN CAPITAL LETTER Q}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL P}"), + ("\N{LATIN CAPITAL LETTER R}", + "\N{LATIN CAPITAL LETTER R}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL R}"), + ("\N{LATIN CAPITAL LETTER S}", + "\N{LATIN CAPITAL LETTER Y}", + "bf", + 0xe396), + ("\N{LATIN CAPITAL LETTER Z}", + "\N{LATIN CAPITAL LETTER Z}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL Z}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER C}", + "bf", + 0xe39d), + ("\N{LATIN SMALL LETTER D}", + "\N{LATIN SMALL LETTER E}", + "bf", + "\N{DOUBLE-STRUCK ITALIC SMALL D}"), + ("\N{LATIN SMALL LETTER F}", + "\N{LATIN SMALL LETTER H}", + "bf", + 0xe3a2), + ("\N{LATIN SMALL LETTER I}", + "\N{LATIN SMALL LETTER J}", + "bf", + "\N{DOUBLE-STRUCK ITALIC SMALL I}"), + ("\N{LATIN SMALL LETTER K}", + "\N{LATIN SMALL LETTER Z}", + "bf", + 0xe3a7), + ("\N{GREEK CAPITAL LETTER GAMMA}", + "\N{GREEK CAPITAL LETTER GAMMA}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL GAMMA}"), + ("\N{GREEK CAPITAL LETTER PI}", + "\N{GREEK CAPITAL LETTER PI}", + "bf", + "\N{DOUBLE-STRUCK CAPITAL PI}"), + ("\N{GREEK CAPITAL LETTER SIGMA}", + "\N{GREEK CAPITAL LETTER SIGMA}", + "bf", + "\N{DOUBLE-STRUCK N-ARY SUMMATION}"), + ("\N{GREEK SMALL LETTER GAMMA}", + "\N{GREEK SMALL LETTER GAMMA}", + "bf", + "\N{DOUBLE-STRUCK SMALL GAMMA}"), + ("\N{GREEK SMALL LETTER PI}", + "\N{GREEK SMALL LETTER PI}", + "bf", + "\N{DOUBLE-STRUCK SMALL PI}"), ], }, 'cal': [ - (0x0041, 0x005a, 'it', 0xe22d), # A-Z + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "it", + 0xe22d), ], 'frak': { - 'rm': + "rm": [ - (0x0041, 0x0042, 'rm', 0x1d504), # A-B - (0x0043, 0x0043, 'rm', 0x212d), # C - (0x0044, 0x0047, 'rm', 0x1d507), # D-G - (0x0048, 0x0048, 'rm', 0x210c), # H - (0x0049, 0x0049, 'rm', 0x2111), # I - (0x004a, 0x0051, 'rm', 0x1d50d), # J-Q - (0x0052, 0x0052, 'rm', 0x211c), # R - (0x0053, 0x0059, 'rm', 0x1d516), # S-Y - (0x005a, 0x005a, 'rm', 0x2128), # Z - (0x0061, 0x007a, 'rm', 0x1d51e), # a-z + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER B}", + "rm", + "\N{MATHEMATICAL FRAKTUR CAPITAL A}"), + ("\N{LATIN CAPITAL LETTER C}", + "\N{LATIN CAPITAL LETTER C}", + "rm", + "\N{BLACK-LETTER CAPITAL C}"), + ("\N{LATIN CAPITAL LETTER D}", + "\N{LATIN CAPITAL LETTER G}", + "rm", + "\N{MATHEMATICAL FRAKTUR CAPITAL D}"), + ("\N{LATIN CAPITAL LETTER H}", + "\N{LATIN CAPITAL LETTER H}", + "rm", + "\N{BLACK-LETTER CAPITAL H}"), + ("\N{LATIN CAPITAL LETTER I}", + "\N{LATIN CAPITAL LETTER I}", + "rm", + "\N{BLACK-LETTER CAPITAL I}"), + ("\N{LATIN CAPITAL LETTER J}", + "\N{LATIN CAPITAL LETTER Q}", + "rm", + "\N{MATHEMATICAL FRAKTUR CAPITAL J}"), + ("\N{LATIN CAPITAL LETTER R}", + "\N{LATIN CAPITAL LETTER R}", + "rm", + "\N{BLACK-LETTER CAPITAL R}"), + ("\N{LATIN CAPITAL LETTER S}", + "\N{LATIN CAPITAL LETTER Y}", + "rm", + "\N{MATHEMATICAL FRAKTUR CAPITAL S}"), + ("\N{LATIN CAPITAL LETTER Z}", + "\N{LATIN CAPITAL LETTER Z}", + "rm", + "\N{BLACK-LETTER CAPITAL Z}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "rm", + "\N{MATHEMATICAL FRAKTUR SMALL A}"), ], - 'bf': + "bf": [ - (0x0041, 0x005a, 'bf', 0x1d56c), # A-Z - (0x0061, 0x007a, 'bf', 0x1d586), # a-z + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "bf", + "\N{MATHEMATICAL BOLD FRAKTUR CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "bf", + "\N{MATHEMATICAL BOLD FRAKTUR SMALL A}"), ], }, 'scr': [ - (0x0041, 0x0041, 'it', 0x1d49c), # A - (0x0042, 0x0042, 'it', 0x212c), # B - (0x0043, 0x0044, 'it', 0x1d49e), # C-D - (0x0045, 0x0046, 'it', 0x2130), # E-F - (0x0047, 0x0047, 'it', 0x1d4a2), # G - (0x0048, 0x0048, 'it', 0x210b), # H - (0x0049, 0x0049, 'it', 0x2110), # I - (0x004a, 0x004b, 'it', 0x1d4a5), # J-K - (0x004c, 0x004c, 'it', 0x2112), # L - (0x004d, 0x004d, 'it', 0x2133), # M - (0x004e, 0x0051, 'it', 0x1d4a9), # N-Q - (0x0052, 0x0052, 'it', 0x211b), # R - (0x0053, 0x005a, 'it', 0x1d4ae), # S-Z - (0x0061, 0x0064, 'it', 0x1d4b6), # a-d - (0x0065, 0x0065, 'it', 0x212f), # e - (0x0066, 0x0066, 'it', 0x1d4bb), # f - (0x0067, 0x0067, 'it', 0x210a), # g - (0x0068, 0x006e, 'it', 0x1d4bd), # h-n - (0x006f, 0x006f, 'it', 0x2134), # o - (0x0070, 0x007a, 'it', 0x1d4c5), # p-z + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER A}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL A}"), + ("\N{LATIN CAPITAL LETTER B}", + "\N{LATIN CAPITAL LETTER B}", + "it", + "\N{SCRIPT CAPITAL B}"), + ("\N{LATIN CAPITAL LETTER C}", + "\N{LATIN CAPITAL LETTER D}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL C}"), + ("\N{LATIN CAPITAL LETTER E}", + "\N{LATIN CAPITAL LETTER F}", + "it", + "\N{SCRIPT CAPITAL E}"), + ("\N{LATIN CAPITAL LETTER G}", + "\N{LATIN CAPITAL LETTER G}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL G}"), + ("\N{LATIN CAPITAL LETTER H}", + "\N{LATIN CAPITAL LETTER H}", + "it", + "\N{SCRIPT CAPITAL H}"), + ("\N{LATIN CAPITAL LETTER I}", + "\N{LATIN CAPITAL LETTER I}", + "it", + "\N{SCRIPT CAPITAL I}"), + ("\N{LATIN CAPITAL LETTER J}", + "\N{LATIN CAPITAL LETTER K}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL J}"), + ("\N{LATIN CAPITAL LETTER L}", + "\N{LATIN CAPITAL LETTER L}", + "it", + "\N{SCRIPT CAPITAL L}"), + ("\N{LATIN CAPITAL LETTER M}", + "\N{LATIN CAPITAL LETTER M}", + "it", + "\N{SCRIPT CAPITAL M}"), + ("\N{LATIN CAPITAL LETTER N}", + "\N{LATIN CAPITAL LETTER Q}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL N}"), + ("\N{LATIN CAPITAL LETTER R}", + "\N{LATIN CAPITAL LETTER R}", + "it", + "\N{SCRIPT CAPITAL R}"), + ("\N{LATIN CAPITAL LETTER S}", + "\N{LATIN CAPITAL LETTER Z}", + "it", + "\N{MATHEMATICAL SCRIPT CAPITAL S}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER D}", + "it", + "\N{MATHEMATICAL SCRIPT SMALL A}"), + ("\N{LATIN SMALL LETTER E}", + "\N{LATIN SMALL LETTER E}", + "it", + "\N{SCRIPT SMALL E}"), + ("\N{LATIN SMALL LETTER F}", + "\N{LATIN SMALL LETTER F}", + "it", + "\N{MATHEMATICAL SCRIPT SMALL F}"), + ("\N{LATIN SMALL LETTER G}", + "\N{LATIN SMALL LETTER G}", + "it", + "\N{SCRIPT SMALL G}"), + ("\N{LATIN SMALL LETTER H}", + "\N{LATIN SMALL LETTER N}", + "it", + "\N{MATHEMATICAL SCRIPT SMALL H}"), + ("\N{LATIN SMALL LETTER O}", + "\N{LATIN SMALL LETTER O}", + "it", + "\N{SCRIPT SMALL O}"), + ("\N{LATIN SMALL LETTER P}", + "\N{LATIN SMALL LETTER Z}", + "it", + "\N{MATHEMATICAL SCRIPT SMALL P}"), ], 'sf': { - 'rm': + "rm": [ - (0x0030, 0x0039, 'rm', 0x1d7e2), # 0-9 - (0x0041, 0x005a, 'rm', 0x1d5a0), # A-Z - (0x0061, 0x007a, 'rm', 0x1d5ba), # a-z - (0x0391, 0x03a9, 'rm', 0xe17d), # \Alpha-\Omega - (0x03b1, 0x03c9, 'rm', 0xe196), # \alpha-\omega - (0x03d1, 0x03d1, 'rm', 0xe1b0), # theta variant - (0x03d5, 0x03d5, 'rm', 0xe1b1), # phi variant - (0x03d6, 0x03d6, 'rm', 0xe1b3), # pi variant - (0x03f1, 0x03f1, 'rm', 0xe1b2), # rho variant - (0x03f5, 0x03f5, 'rm', 0xe1af), # lunate epsilon - (0x2202, 0x2202, 'rm', 0xe17c), # partial differential + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL SANS-SERIF DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "rm", + "\N{MATHEMATICAL SANS-SERIF CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "rm", + "\N{MATHEMATICAL SANS-SERIF SMALL A}"), + ("\N{GREEK CAPITAL LETTER ALPHA}", + "\N{GREEK CAPITAL LETTER OMEGA}", + "rm", + 0xe17d), + ("\N{GREEK SMALL LETTER ALPHA}", + "\N{GREEK SMALL LETTER OMEGA}", + "rm", + 0xe196), + ("\N{GREEK THETA SYMBOL}", + "\N{GREEK THETA SYMBOL}", + "rm", + 0xe1b0), + ("\N{GREEK PHI SYMBOL}", + "\N{GREEK PHI SYMBOL}", + "rm", + 0xe1b1), + ("\N{GREEK PI SYMBOL}", + "\N{GREEK PI SYMBOL}", + "rm", + 0xe1b3), + ("\N{GREEK RHO SYMBOL}", + "\N{GREEK RHO SYMBOL}", + "rm", + 0xe1b2), + ("\N{GREEK LUNATE EPSILON SYMBOL}", + "\N{GREEK LUNATE EPSILON SYMBOL}", + "rm", + 0xe1af), + ("\N{PARTIAL DIFFERENTIAL}", + "\N{PARTIAL DIFFERENTIAL}", + "rm", + 0xe17c), ], - 'it': + "it": [ # These numerals are actually upright. We don't actually # want italic numerals ever. - (0x0030, 0x0039, 'rm', 0x1d7e2), # 0-9 - (0x0041, 0x005a, 'it', 0x1d608), # A-Z - (0x0061, 0x007a, 'it', 0x1d622), # a-z - (0x0391, 0x03a9, 'rm', 0xe17d), # \Alpha-\Omega - (0x03b1, 0x03c9, 'it', 0xe1d8), # \alpha-\omega - (0x03d1, 0x03d1, 'it', 0xe1f2), # theta variant - (0x03d5, 0x03d5, 'it', 0xe1f3), # phi variant - (0x03d6, 0x03d6, 'it', 0xe1f5), # pi variant - (0x03f1, 0x03f1, 'it', 0xe1f4), # rho variant - (0x03f5, 0x03f5, 'it', 0xe1f1), # lunate epsilon + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL SANS-SERIF DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "it", + "\N{MATHEMATICAL SANS-SERIF ITALIC CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "it", + "\N{MATHEMATICAL SANS-SERIF ITALIC SMALL A}"), + ("\N{GREEK CAPITAL LETTER ALPHA}", + "\N{GREEK CAPITAL LETTER OMEGA}", + "rm", + 0xe17d), + ("\N{GREEK SMALL LETTER ALPHA}", + "\N{GREEK SMALL LETTER OMEGA}", + "it", + 0xe1d8), + ("\N{GREEK THETA SYMBOL}", + "\N{GREEK THETA SYMBOL}", + "it", + 0xe1f2), + ("\N{GREEK PHI SYMBOL}", + "\N{GREEK PHI SYMBOL}", + "it", + 0xe1f3), + ("\N{GREEK PI SYMBOL}", + "\N{GREEK PI SYMBOL}", + "it", + 0xe1f5), + ("\N{GREEK RHO SYMBOL}", + "\N{GREEK RHO SYMBOL}", + "it", + 0xe1f4), + ("\N{GREEK LUNATE EPSILON SYMBOL}", + "\N{GREEK LUNATE EPSILON SYMBOL}", + "it", + 0xe1f1), ], - 'bf': + "bf": [ - (0x0030, 0x0039, 'bf', 0x1d7ec), # 0-9 - (0x0041, 0x005a, 'bf', 0x1d5d4), # A-Z - (0x0061, 0x007a, 'bf', 0x1d5ee), # a-z - (0x0391, 0x03a9, 'bf', 0x1d756), # \Alpha-\Omega - (0x03b1, 0x03c9, 'bf', 0x1d770), # \alpha-\omega - (0x03d1, 0x03d1, 'bf', 0x1d78b), # theta variant - (0x03d5, 0x03d5, 'bf', 0x1d78d), # phi variant - (0x03d6, 0x03d6, 'bf', 0x1d78f), # pi variant - (0x03f0, 0x03f0, 'bf', 0x1d78c), # kappa variant - (0x03f1, 0x03f1, 'bf', 0x1d78e), # rho variant - (0x03f5, 0x03f5, 'bf', 0x1d78a), # lunate epsilon - (0x2202, 0x2202, 'bf', 0x1d789), # partial differential - (0x2207, 0x2207, 'bf', 0x1d76f), # \Nabla + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD SMALL A}"), + ("\N{GREEK CAPITAL LETTER ALPHA}", + "\N{GREEK CAPITAL LETTER OMEGA}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA}"), + ("\N{GREEK SMALL LETTER ALPHA}", + "\N{GREEK SMALL LETTER OMEGA}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA}"), + ("\N{GREEK THETA SYMBOL}", + "\N{GREEK THETA SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD THETA SYMBOL}"), + ("\N{GREEK PHI SYMBOL}", + "\N{GREEK PHI SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD PHI SYMBOL}"), + ("\N{GREEK PI SYMBOL}", + "\N{GREEK PI SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD PI SYMBOL}"), + ("\N{GREEK KAPPA SYMBOL}", + "\N{GREEK KAPPA SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD KAPPA SYMBOL}"), + ("\N{GREEK RHO SYMBOL}", + "\N{GREEK RHO SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL}"), + ("\N{GREEK LUNATE EPSILON SYMBOL}", + "\N{GREEK LUNATE EPSILON SYMBOL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL}"), + ("\N{PARTIAL DIFFERENTIAL}", + "\N{PARTIAL DIFFERENTIAL}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL}"), + ("\N{NABLA}", + "\N{NABLA}", + "bf", + "\N{MATHEMATICAL SANS-SERIF BOLD NABLA}"), ], - 'bfit': + "bfit": [ - (0x0041, 0x005a, 'bfit', 0x1d468), # A-Z - (0x0061, 0x007a, 'bfit', 0x1d482), # a-z - (0x0393, 0x03a9, 'bfit', 0x1d71e), # \Gamma-\Omega - (0x03b1, 0x03c9, 'bfit', 0x1d736), # \alpha-\omega + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "bfit", + "\N{MATHEMATICAL BOLD ITALIC CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "bfit", + "\N{MATHEMATICAL BOLD ITALIC SMALL A}"), + ("\N{GREEK CAPITAL LETTER GAMMA}", + "\N{GREEK CAPITAL LETTER OMEGA}", + "bfit", + "\N{MATHEMATICAL BOLD ITALIC CAPITAL GAMMA}"), + ("\N{GREEK SMALL LETTER ALPHA}", + "\N{GREEK SMALL LETTER OMEGA}", + "bfit", + "\N{MATHEMATICAL BOLD ITALIC SMALL ALPHA}"), ], }, 'tt': [ - (0x0030, 0x0039, 'rm', 0x1d7f6), # 0-9 - (0x0041, 0x005a, 'rm', 0x1d670), # A-Z - (0x0061, 0x007a, 'rm', 0x1d68a) # a-z + ("\N{DIGIT ZERO}", + "\N{DIGIT NINE}", + "rm", + "\N{MATHEMATICAL MONOSPACE DIGIT ZERO}"), + ("\N{LATIN CAPITAL LETTER A}", + "\N{LATIN CAPITAL LETTER Z}", + "rm", + "\N{MATHEMATICAL MONOSPACE CAPITAL A}"), + ("\N{LATIN SMALL LETTER A}", + "\N{LATIN SMALL LETTER Z}", + "rm", + "\N{MATHEMATICAL MONOSPACE SMALL A}") ], } +@overload +def _normalize_stix_fontcodes(d: _EntryTypeIn) -> _EntryTypeOut: ... + + +@overload +def _normalize_stix_fontcodes(d: list[_EntryTypeIn]) -> list[_EntryTypeOut]: ... + + +@overload +def _normalize_stix_fontcodes(d: dict[str, list[_EntryTypeIn] | + dict[str, list[_EntryTypeIn]]] + ) -> dict[str, list[_EntryTypeOut] | + dict[str, list[_EntryTypeOut]]]: ... + + +def _normalize_stix_fontcodes(d): + if isinstance(d, tuple): + return tuple(ord(x) if isinstance(x, str) and len(x) == 1 else x for x in d) + elif isinstance(d, list): + return [_normalize_stix_fontcodes(x) for x in d] + elif isinstance(d, dict): + return {k: _normalize_stix_fontcodes(v) for k, v in d.items()} + + +stix_virtual_fonts: dict[str, dict[str, list[_EntryTypeOut]] | list[_EntryTypeOut]] +stix_virtual_fonts = _normalize_stix_fontcodes(_stix_virtual_fonts) + +# Free redundant list now that it has been normalized +del _stix_virtual_fonts + # Fix some incorrect glyphs. stix_glyph_fixes = { # Cap and Cup glyphs are swapped. From a7f5df32f2445378fbc48e61a77292d183a98d1d Mon Sep 17 00:00:00 2001 From: Binaya Sharma Date: Mon, 9 Oct 2023 13:27:14 +0545 Subject: [PATCH 0256/1120] updated artist_intro.rst --- galleries/users_explain/artists/artist_intro.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/galleries/users_explain/artists/artist_intro.rst b/galleries/users_explain/artists/artist_intro.rst index 213a945f44b8..d23c59da631d 100644 --- a/galleries/users_explain/artists/artist_intro.rst +++ b/galleries/users_explain/artists/artist_intro.rst @@ -10,7 +10,7 @@ and :doc:`Axes <../axes/index>` are Artists, and generally contain Creating Artists -~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ Usually we do not instantiate Artists directly, but rather use a plotting method on `~.axes.Axes`. Some examples of plotting methods and the Artist @@ -58,7 +58,7 @@ Artist via `~.Axes.get_lines()`: Line2D(example) Changing Artist properties -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ Getting the ``lines`` object gives us access to all the properties of the Line2D object. So if we want to change the *linewidth* after the fact, we can do so using `.Artist.set`. @@ -132,7 +132,7 @@ Note most Artists also have a distinct list of setters; e.g. `.Line2D.set_color` or `.Line2D.set_linewidth`. Changing Artist data -~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^ In addition to styling properties like *color* and *linewidth*, the Line2D object has a *data* property. You can set the data after the line has been @@ -149,7 +149,7 @@ same line is shown evolving over time (see :doc:`../animations/index`) lines[0].set_data([x, np.cos(x)]) Manually adding Artists -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ Not all Artists have helper methods, or you may want to use a low-level method for some reason. For example the `.patches.Circle` Artist does not have a @@ -178,7 +178,7 @@ be clipped, as is the case above for the ``clipped_circle`` patch. See :ref:`artist_reference` for other patches. Removing Artists -~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ Sometimes we want to remove an Artist from a figure without re-specifying the whole figure from scratch. Most Artists have a usable *remove* method that From 128541a973d10d43a3380141a9785296fb5ebbd4 Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Mon, 9 Oct 2023 16:38:09 +0530 Subject: [PATCH 0257/1120] add test_plot_date in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 53958229f174..9c2be8ba541d 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -252,11 +252,24 @@ def test_plot(self): ax2.plot(range(1, N), x) ax3.plot(x, x) - @pytest.mark.xfail(reason="Test for plot_date not written yet") @mpl.style.context("default") def test_plot_date(self): - fig, ax = plt.subplots() - ax.plot_date(...) + mpl.rcParams["date.converter"] = "concise" + range_threshold = 10 + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + + x_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + y_dates = np.array( + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + ) + x_ranges = np.array(range(1, range_threshold)) + y_ranges = np.array(range(1, range_threshold)) + + ax1.plot_date(x_dates, y_dates) + ax2.plot_date(x_dates, y_ranges) + ax3.plot_date(x_ranges, y_dates) @pytest.mark.xfail(reason="Test for psd not written yet") @mpl.style.context("default") From e0acb05992bfd445ba534473129ae446b54bb2db Mon Sep 17 00:00:00 2001 From: Gaurav-Kumar-Soni Date: Mon, 9 Oct 2023 18:19:16 +0530 Subject: [PATCH 0258/1120] Formatted docs --- doc/users/project/index.rst | 2 +- doc/users/release_notes.rst | 39 +++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/doc/users/project/index.rst b/doc/users/project/index.rst index 80dd6e27bc3d..ad55576190ab 100644 --- a/doc/users/project/index.rst +++ b/doc/users/project/index.rst @@ -1,7 +1,7 @@ .. redirect-from:: /users/backmatter Project information -------------------- +=================== .. toctree:: :maxdepth: 2 diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 1d281543b76c..7862305af65e 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -13,7 +13,7 @@ Release notes Version 3.8 -=========== +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -22,7 +22,7 @@ Version 3.8 github_stats.rst Version 3.7 -=========== +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -34,7 +34,7 @@ Version 3.7 prev_whats_new/github_stats_3.7.0.rst Version 3.6 -=========== +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -47,7 +47,7 @@ Version 3.6 prev_whats_new/github_stats_3.6.0.rst Version 3.5 -=========== +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -62,7 +62,7 @@ Version 3.5 prev_whats_new/github_stats_3.5.0.rst Version 3.4 -=========== +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -72,11 +72,12 @@ Version 3.4 prev_whats_new/github_stats_3.4.1.rst prev_whats_new/github_stats_3.4.0.rst +============= Past versions ============= Version 3.3 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -90,7 +91,7 @@ Version 3.3 prev_whats_new/github_stats_3.3.0.rst Version 3.2 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -101,7 +102,7 @@ Version 3.2 prev_whats_new/github_stats_3.2.0.rst Version 3.1 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -114,7 +115,7 @@ Version 3.1 prev_whats_new/github_stats_3.1.0.rst Version 3.0 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -127,7 +128,7 @@ Version 3.0 prev_whats_new/github_stats_3.0.0.rst Version 2.2 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -135,7 +136,7 @@ Version 2.2 ../api/prev_api_changes/api_changes_2.2.0.rst Version 2.1 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -145,7 +146,7 @@ Version 2.1 ../api/prev_api_changes/api_changes_2.1.0.rst Version 2.0 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -154,7 +155,7 @@ Version 2.0 ../api/prev_api_changes/api_changes_2.0.0.rst Version 1.5 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -164,7 +165,7 @@ Version 1.5 ../api/prev_api_changes/api_changes_1.5.0.rst Version 1.4 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -172,7 +173,7 @@ Version 1.4 ../api/prev_api_changes/api_changes_1.4.x.rst Version 1.3 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -180,7 +181,7 @@ Version 1.3 ../api/prev_api_changes/api_changes_1.3.x.rst Version 1.2 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -189,7 +190,7 @@ Version 1.2 ../api/prev_api_changes/api_changes_1.2.x.rst Version 1.1 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 @@ -197,14 +198,14 @@ Version 1.1 ../api/prev_api_changes/api_changes_1.1.x.rst Version 1.0 -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 prev_whats_new/whats_new_1.0.rst Version 0.x -~~~~~~~~~~~ +^^^^^^^^^^^ .. toctree:: :maxdepth: 1 From 4f32e81f3a52dda57ee41024d7f841d70664a951 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:12:20 +0000 Subject: [PATCH 0259/1120] Bump pypa/cibuildwheel from 2.16.1 to 2.16.2 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.16.1 to 2.16.2. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/7da7df1efc530f07d1945c00934b8cfd34be0d50...fff9ec32ed25a9c576750c91e06b410ed0c15db7) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index d4230e8f17ab..3b141f934e29 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -137,7 +137,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 + uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: pip install --pre "numpy>=1.25" - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 + uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 + uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -175,7 +175,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 + uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -183,7 +183,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@7da7df1efc530f07d1945c00934b8cfd34be0d50 # v2.16.1 + uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From f4abe20b6b06c9b24e7e6fb71d1be7707d3af27c Mon Sep 17 00:00:00 2001 From: Ratnabali Dutta Date: Tue, 10 Oct 2023 02:05:21 +0530 Subject: [PATCH 0260/1120] Add Union for multiple type info Co-authored-by: Kyle Sunden --- lib/matplotlib/_mathtext_data.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index 764555def495..8f0aece5de92 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from typing import overload +from typing import overload, Union latex_to_bakoma = { '\\__sqrt__' : ('cmex10', 0x70), @@ -1113,11 +1113,11 @@ # Each element is a 4-tuple of the form: # src_start, src_end, dst_font, dst_start -_EntryTypeIn = tuple[str, str, str, str | int] +_EntryTypeIn = tuple[str, str, str, Union[str, int]] _EntryTypeOut = tuple[int, int, str, int] -_stix_virtual_fonts: dict[str, dict[str, list[_EntryTypeIn]] | - list[_EntryTypeIn]] = { +_stix_virtual_fonts: dict[str, Union[dict[ + str, list[_EntryTypeIn]], list[_EntryTypeIn]]] = { 'bb': { "rm": @@ -1744,7 +1744,8 @@ def _normalize_stix_fontcodes(d): return {k: _normalize_stix_fontcodes(v) for k, v in d.items()} -stix_virtual_fonts: dict[str, dict[str, list[_EntryTypeOut]] | list[_EntryTypeOut]] +stix_virtual_fonts: dict[str, Union[dict[str, list[_EntryTypeOut]], + list[_EntryTypeOut]]] stix_virtual_fonts = _normalize_stix_fontcodes(_stix_virtual_fonts) # Free redundant list now that it has been normalized From 4976bc98d5021d4dc561ed8eaebd816a6bf549db Mon Sep 17 00:00:00 2001 From: Binaya Sharma Date: Tue, 10 Oct 2023 10:54:00 +0545 Subject: [PATCH 0261/1120] updated api_interfaces.rst --- galleries/users_explain/figure/api_interfaces.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/users_explain/figure/api_interfaces.rst b/galleries/users_explain/figure/api_interfaces.rst index 473c808794ca..6947817e6781 100644 --- a/galleries/users_explain/figure/api_interfaces.rst +++ b/galleries/users_explain/figure/api_interfaces.rst @@ -31,7 +31,7 @@ Native Matplotlib interfaces ---------------------------- The explicit "Axes" interface -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The "Axes" interface is how Matplotlib is implemented, and many customizations and fine-tuning end up being done at this level. @@ -59,7 +59,7 @@ but before they are displayed. The implicit "pyplot" interface -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The `~.matplotlib.pyplot` module shadows most of the `~.matplotlib.axes.Axes` plotting methods to give the equivalent of From bfde4897d9110d429b24479489ba112408d3dfc0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 10 Oct 2023 00:30:28 -0500 Subject: [PATCH 0262/1120] Ensure valid path mangling for ContourLabeler --- lib/matplotlib/contour.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..6a4b27035dc1 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -418,8 +418,13 @@ def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T] new_code_blocks = [] if is_closed_path: if i0 != -1 and i1 != -1: - new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:i0+1], [(x0, y0)]]) - new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 2 - i1)]) + # This is probably wrong in the case that the entire contour would + # be discarded, but ensures that a valid path is returned and is + # consistent with behavior of mpl <3.8 + points = cc_xys[i1:i0+1] + new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]]) + nlines = len(points) + 1 + new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines]) else: if i0 != -1: new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]]) From 5874f3256ebf38da425379341a5b78c9ebc9cf72 Mon Sep 17 00:00:00 2001 From: Binaya Sharma Date: Tue, 10 Oct 2023 21:30:18 +0545 Subject: [PATCH 0263/1120] updated interactive.rst --- galleries/users_explain/figure/interactive.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index 0d94a1cf8493..a32d1a1eb847 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -366,7 +366,7 @@ which uses the `.backend_nbagg` backend provided by Matplotlib; however, nbagg does not work in Jupyter Lab. GUIs + Jupyter -~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^ You can also use one of the non-``ipympl`` GUI backends in a Jupyter Notebook. If you are running your Jupyter kernel locally, the GUI window will spawn on From b2027c3fcab34933b40bd2f9227ddd1f2b50fd20 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 10 Oct 2023 13:52:29 -0500 Subject: [PATCH 0264/1120] Add test of clabel with large padding --- lib/matplotlib/tests/test_contour.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index c911d499ea96..b3b7f575b07a 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -338,6 +338,22 @@ def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder): assert clabel.get_zorder() == expected_clabel_zorder +def test_clabel_with_large_spacing(): + # When the inline spacing is large relative to the contour, it may cause the + # entire contour to be removed. In current implementation, one line segment is + # retained between the identified points. + # This behavior may be worth reconsidering, but check to be sure we do not produce + # an invalid path, which results in an error at clabel call time. + # see gh-27045 for more information + x = y = np.arange(-3.0, 3.01, 0.05) + X, Y = np.meshgrid(x, y) + Z = np.exp(-X**2 - Y**2) + + fig, ax = plt.subplots() + contourset = ax.contour(X, Y, Z, levels=[0.01, 0.2, .5, .8]) + ax.clabel(contourset, inline_spacing=100) + + # tol because ticks happen to fall on pixel boundaries so small # floating point changes in tick location flip which pixel gets # the tick. From 24518d61d1cb5f539a459bda7c640c4ed4c1214b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 16 Sep 2023 00:14:03 -0400 Subject: [PATCH 0265/1120] Change internal utility extension to C++ This requires a minor bit of typecasting as in the `_tkagg.cpp` file. This is a separate commit from the pybind11 change to improve rename detection. --- ..._internal_utils.c => _c_internal_utils.cpp} | 18 ++++++++++-------- src/meson.build | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) rename src/{_c_internal_utils.c => _c_internal_utils.cpp} (90%) diff --git a/src/_c_internal_utils.c b/src/_c_internal_utils.cpp similarity index 90% rename from src/_c_internal_utils.c rename to src/_c_internal_utils.cpp index 49e5057f1349..a8fc31fa8a7b 100644 --- a/src/_c_internal_utils.c +++ b/src/_c_internal_utils.cpp @@ -24,11 +24,11 @@ mpl_display_is_valid(PyObject* module) // than dlopen(). if (getenv("DISPLAY") && (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) { + typedef struct Display* (*XOpenDisplay_t)(char const*); + typedef int (*XCloseDisplay_t)(struct Display*); struct Display* display = NULL; - struct Display* (* XOpenDisplay)(char const*) = - dlsym(libX11, "XOpenDisplay"); - int (* XCloseDisplay)(struct Display*) = - dlsym(libX11, "XCloseDisplay"); + XOpenDisplay_t XOpenDisplay = (XOpenDisplay_t)dlsym(libX11, "XOpenDisplay"); + XCloseDisplay_t XCloseDisplay = (XCloseDisplay_t)dlsym(libX11, "XCloseDisplay"); if (XOpenDisplay && XCloseDisplay && (display = XOpenDisplay(NULL))) { XCloseDisplay(display); @@ -44,11 +44,13 @@ mpl_display_is_valid(PyObject* module) void* libwayland_client; if (getenv("WAYLAND_DISPLAY") && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { + typedef struct wl_display* (*wl_display_connect_t)(char const*); + typedef void (*wl_display_disconnect_t)(struct wl_display*); struct wl_display* display = NULL; - struct wl_display* (* wl_display_connect)(char const*) = - dlsym(libwayland_client, "wl_display_connect"); - void (* wl_display_disconnect)(struct wl_display*) = - dlsym(libwayland_client, "wl_display_disconnect"); + wl_display_connect_t wl_display_connect = + (wl_display_connect_t)dlsym(libwayland_client, "wl_display_connect"); + wl_display_disconnect_t wl_display_disconnect = + (wl_display_disconnect_t)dlsym(libwayland_client, "wl_display_disconnect"); if (wl_display_connect && wl_display_disconnect && (display = wl_display_connect(NULL))) { wl_display_disconnect(display); diff --git a/src/meson.build b/src/meson.build index 6a33d2dfb12a..ce7a8cb034aa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -82,7 +82,7 @@ extension_data = { '_c_internal_utils': { 'subdir': 'matplotlib', 'sources': files( - '_c_internal_utils.c', + '_c_internal_utils.cpp', ), 'dependencies': [py3_dep, dl, ole32, shell32, user32], }, From 6355271f37423de1062ec99bf0886036b6cc62d9 Mon Sep 17 00:00:00 2001 From: Daniel Hitchcock Date: Sun, 24 Sep 2023 19:21:37 -0400 Subject: [PATCH 0266/1120] Removal of deprecations for Contour fix pyi fix pyi --- doc/api/artist_api.rst | 2 +- .../next_api_changes/removals/26907-DCH.rst | 14 +++++++ lib/matplotlib/contour.py | 38 ------------------- lib/matplotlib/contour.pyi | 9 +---- 4 files changed, 16 insertions(+), 47 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26907-DCH.rst diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index df7a6e8b1c04..0ca3fb364c41 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -11,7 +11,7 @@ Inheritance Diagrams ==================== -.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet +.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet :parts: 1 :private-bases: diff --git a/doc/api/next_api_changes/removals/26907-DCH.rst b/doc/api/next_api_changes/removals/26907-DCH.rst new file mode 100644 index 000000000000..889743ba9cb0 --- /dev/null +++ b/doc/api/next_api_changes/removals/26907-DCH.rst @@ -0,0 +1,14 @@ +``contour.ClabelText`` and ``ContourLabeler.set_label_props`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... are removed. + +Use ``Text(..., transform_rotates_text=True)`` as a replacement for +``contour.ClabelText(...)`` and ``text.set(text=text, color=color, +fontproperties=labeler.labelFontProps, clip_box=labeler.axes.bbox)`` as a +replacement for the ``ContourLabeler.set_label_props(label, text, color)``. + +``ContourLabeler`` attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``labelFontProps``, ``labelFontSizeList``, and ``labelTextsList`` +attributes of `.ContourLabeler` have been removed. Use the ``labelTexts`` +attribute and the font properties of the corresponding text objects instead. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..4104bebfceda 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -25,20 +25,6 @@ import matplotlib.transforms as mtransforms -@_api.deprecated("3.7", alternative="Text.set_transform_rotates_text") -class ClabelText(Text): - """ - Unlike the ordinary text, the get_rotation returns an updated - angle in the pixel coordinate assuming that the input rotation is - an angle in data coordinate (or whatever transform set). - """ - - def get_rotation(self): - new_angle, = self.get_transform().transform_angles( - [super().get_rotation()], [self.get_position()]) - return new_angle - - def _contour_labeler_event_handler(cs, inline, inline_spacing, event): canvas = cs.axes.figure.canvas is_button = event.name == "button_press_event" @@ -223,22 +209,6 @@ def clabel(self, levels=None, *, return cbook.silent_list('text.Text', self.labelTexts) - @_api.deprecated("3.7", alternative="cs.labelTexts[0].get_font()") - @property - def labelFontProps(self): - return self._label_font_props - - @_api.deprecated("3.7", alternative=( - "[cs.labelTexts[0].get_font().get_size()] * len(cs.labelLevelList)")) - @property - def labelFontSizeList(self): - return [self._label_font_props.get_size()] * len(self.labelLevelList) - - @_api.deprecated("3.7", alternative="cs.labelTexts") - @property - def labelTextsList(self): - return cbook.silent_list('text.Text', self.labelTexts) - def print_label(self, linecontour, labelwidth): """Return whether a contour is long enough to hold a label.""" return (len(linecontour) > 10 * labelwidth @@ -260,14 +230,6 @@ def _get_nth_label_width(self, nth): figure=fig, fontproperties=self._label_font_props) .get_window_extent(renderer).width) - @_api.deprecated("3.7", alternative="Artist.set") - def set_label_props(self, label, text, color): - """Set the label properties - color, fontsize, text.""" - label.set_text(text) - label.set_color(color) - label.set_fontproperties(self._label_font_props) - label.set_clip_box(self.axes.bbox) - def get_text(self, lev, fmt): """Get the text of the label.""" if isinstance(lev, str): diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi index 4a1e1e1d9d2e..ca034c4af265 100644 --- a/lib/matplotlib/contour.pyi +++ b/lib/matplotlib/contour.pyi @@ -16,7 +16,7 @@ from collections.abc import Callable, Iterable, Sequence from typing import Literal from .typing import ColorType -class ClabelText(Text): ... + class ContourLabeler: labelFmt: str | Formatter | Callable[[float], str] | dict[float, str] @@ -41,15 +41,8 @@ class ContourLabeler: rightside_up: bool = ..., zorder: float | None = ... ) -> list[Text]: ... - @property - def labelFontProps(self) -> FontProperties: ... - @property - def labelFontSizeList(self) -> list[float]: ... - @property - def labelTextsList(self) -> list[Text]: ... def print_label(self, linecontour: ArrayLike, labelwidth: float) -> bool: ... def too_close(self, x: float, y: float, lw: float) -> bool: ... - def set_label_props(self, label: Text, text: str, color: ColorType) -> None: ... def get_text( self, lev: float, From 49dd3aac0e3fce2b132a30d1d6c5a0a244b93c62 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 10 Oct 2023 23:35:23 -0400 Subject: [PATCH 0267/1120] ci: Clean up Python 3.12 builds The final release is out, as are non-prerelease wheels, so we can drop those workarounds. --- .github/workflows/cibuildwheel.yml | 14 -------------- .github/workflows/tests.yml | 15 ++++++--------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 3b141f934e29..cdb62658e6c9 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -143,20 +143,6 @@ jobs: env: CIBW_BUILD: "cp312-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - # Remove this once NumPy with Python 3.12 wheels is not pre-release. - CIBW_BEFORE_BUILD: >- - pip install "meson-python>=0.13.1" ninja "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && - pip install --pre "numpy>=1.25" && - rm -rf {package}/build - CIBW_BEFORE_BUILD_WINDOWS: >- - pip install delvewheel "meson-python>=0.13.1" ninja "pybind11>=2.6" "setuptools>=42" "setuptools_scm>=7" && - pip install --pre "numpy>=1.25" && - rm -rf {package}/build - CIBW_ENVIRONMENT: PIP_NO_BUILD_ISOLATION=0 - # Remove this once contourpy has Python 3.12 wheels. - CIBW_BEFORE_TEST: >- - pip install "meson>=1.2.0" "meson-python>=0.13.1" "ninja" "pybind11>=2.10.4" && - pip install --pre "numpy>=1.25" - name: Build wheels for CPython 3.11 uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3d3d63c64fa..614f9235a5a4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,9 +77,8 @@ jobs: pyside6-ver: '!=6.5.1' extra-requirements: '-r requirements/testing/extra.txt' - os: ubuntu-22.04 - python-version: '3.12-dev' + python-version: '3.12' pyside6-ver: '!=6.5.1' - pre: true - os: macos-latest python-version: 3.9 # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 @@ -196,10 +195,8 @@ jobs: python -m pip install --upgrade pip setuptools wheel # Install pre-release versions during our weekly upcoming dependency tests. - # Also install for 3.12 to get working NumPy (remove when 1.26 is released) - if [[ "${{ github.event_name == 'schedule' && - matrix.name-suffix != '(Minimum Versions)' }}" = "true" - || "${{ matrix.pre }}" = "true" ]]; then + if [[ "${{ github.event_name }}" == 'schedule' + && "${{ matrix.name-suffix }}" != '(Minimum Versions)' ]]; then PRE="--pre" fi @@ -241,7 +238,7 @@ jobs: echo 'PyQt5 is available' || echo 'PyQt5 is not available' if [[ "${{ runner.os }}" != 'macOS' - && "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then + && "${{ matrix.python-version }}" != '3.12' ]]; then python -mpip install --upgrade pyside2${{ matrix.pyside2-ver }} && python -c 'import PySide2.QtCore' && echo 'PySide2 is available' || @@ -254,14 +251,14 @@ jobs: echo 'PyQt6 is not available' fi if [[ "${{ runner.os }}" != 'macOS' - && "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then + && "${{ matrix.python-version }}" != '3.12' ]]; then python -mpip install --upgrade pyside6${{ matrix.pyside6-ver }} && python -c 'import PySide6.QtCore' && echo 'PySide6 is available' || echo 'PySide6 is not available' fi - if [[ "${{ matrix.python-version != '3.12-dev'}}" = "true" ]]; then + if [[ "${{ matrix.python-version }}" != '3.12' ]]; then python -mpip install --upgrade \ -f "https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }}" \ wxPython && From f33f6cfcef3670bcee0ba1f3643ff3d72851b3f1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 14 Sep 2023 07:25:26 -0400 Subject: [PATCH 0268/1120] Convert internal utilities to pybind11 --- src/_c_internal_utils.cpp | 175 +++++++++++++++++++------------------- src/meson.build | 2 +- 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index a8fc31fa8a7b..813aeb6f7d5a 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -1,11 +1,12 @@ +#include + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN // Windows 10, for latest HiDPI API support. #define WINVER 0x0A00 #define _WIN32_WINNT 0x0A00 #endif -#define PY_SSIZE_T_CLEAN -#include +#include #ifdef __linux__ #include #endif @@ -13,10 +14,16 @@ #include #include #include +#define UNUSED_ON_NON_WINDOWS(x) x +#else +#define UNUSED_ON_NON_WINDOWS Py_UNUSED #endif -static PyObject* -mpl_display_is_valid(PyObject* module) +namespace py = pybind11; +using namespace pybind11::literals; + +static bool +mpl_display_is_valid(void) { #ifdef __linux__ void* libX11; @@ -34,11 +41,10 @@ mpl_display_is_valid(PyObject* module) XCloseDisplay(display); } if (dlclose(libX11)) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - return NULL; + throw std::runtime_error(dlerror()); } if (display) { - Py_RETURN_TRUE; + return true; } } void* libwayland_client; @@ -56,84 +62,74 @@ mpl_display_is_valid(PyObject* module) wl_display_disconnect(display); } if (dlclose(libwayland_client)) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - return NULL; + throw std::runtime_error(dlerror()); } if (display) { - Py_RETURN_TRUE; + return true; } } - Py_RETURN_FALSE; + return false; #else - Py_RETURN_TRUE; + return true; #endif } -static PyObject* -mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module) +static py::object +mpl_GetCurrentProcessExplicitAppUserModelID(void) { #ifdef _WIN32 wchar_t* appid = NULL; HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); + PyErr_SetFromWindowsErr(hr); + throw py::error_already_set(); } - PyObject* py_appid = PyUnicode_FromWideChar(appid, -1); + auto py_appid = py::cast(appid); CoTaskMemFree(appid); return py_appid; #else - Py_RETURN_NONE; + return py::none(); #endif } -static PyObject* -mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg) +static void +mpl_SetCurrentProcessExplicitAppUserModelID(const wchar_t* UNUSED_ON_NON_WINDOWS(appid)) { #ifdef _WIN32 - wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL); - if (!appid) { - return NULL; - } HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); - PyMem_Free(appid); if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); + PyErr_SetFromWindowsErr(hr); + throw py::error_already_set(); } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; #endif } -static PyObject* -mpl_GetForegroundWindow(PyObject* module) +static py::object +mpl_GetForegroundWindow(void) { #ifdef _WIN32 - return PyLong_FromVoidPtr(GetForegroundWindow()); + return py::capsule(GetForegroundWindow(), "HWND"); #else - Py_RETURN_NONE; + return py::none(); #endif } -static PyObject* -mpl_SetForegroundWindow(PyObject* module, PyObject *arg) +static void +mpl_SetForegroundWindow(py::capsule UNUSED_ON_NON_WINDOWS(handle_p)) { #ifdef _WIN32 - HWND handle = PyLong_AsVoidPtr(arg); - if (PyErr_Occurred()) { - return NULL; - } - if (!SetForegroundWindow(handle)) { - return PyErr_Format(PyExc_RuntimeError, "Error setting window"); - } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; + if (handle_p.name() != "HWND") { + throw std::runtime_error("Handle must be a value returned from Win32_GetForegroundWindow"); + } + HWND handle = static_cast(handle_p.get_pointer()); + if (!SetForegroundWindow(handle)) { + throw std::runtime_error("Error setting window"); + } #endif } -static PyObject* -mpl_SetProcessDpiAwareness_max(PyObject* module) +static void +mpl_SetProcessDpiAwareness_max(void) { #ifdef _WIN32 #ifdef _DPI_AWARENESS_CONTEXTS_ @@ -171,49 +167,52 @@ mpl_SetProcessDpiAwareness_max(PyObject* module) SetProcessDPIAware(); #endif #endif - Py_RETURN_NONE; } -static PyMethodDef functions[] = { - {"display_is_valid", (PyCFunction)mpl_display_is_valid, METH_NOARGS, - "display_is_valid()\n--\n\n" - "Check whether the current X11 or Wayland display is valid.\n\n" - "On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL)\n" - "succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n" - "succeeds.\n\n" - "On other platforms, always returns True."}, - {"Win32_GetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS, - "Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n" - "Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID.\n\n" - "On non-Windows platforms, always returns None."}, - {"Win32_SetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_SetCurrentProcessExplicitAppUserModelID, METH_O, - "Win32_SetCurrentProcessExplicitAppUserModelID(appid, /)\n--\n\n" - "Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID.\n\n" - "On non-Windows platforms, does nothing."}, - {"Win32_GetForegroundWindow", - (PyCFunction)mpl_GetForegroundWindow, METH_NOARGS, - "Win32_GetForegroundWindow()\n--\n\n" - "Wrapper for Windows' GetForegroundWindow.\n\n" - "On non-Windows platforms, always returns None."}, - {"Win32_SetForegroundWindow", - (PyCFunction)mpl_SetForegroundWindow, METH_O, - "Win32_SetForegroundWindow(hwnd, /)\n--\n\n" - "Wrapper for Windows' SetForegroundWindow.\n\n" - "On non-Windows platforms, does nothing."}, - {"Win32_SetProcessDpiAwareness_max", - (PyCFunction)mpl_SetProcessDpiAwareness_max, METH_NOARGS, - "Win32_SetProcessDpiAwareness_max()\n--\n\n" - "Set Windows' process DPI awareness to best option available.\n\n" - "On non-Windows platforms, does nothing."}, - {NULL, NULL}}; // sentinel. -static PyModuleDef util_module = { - PyModuleDef_HEAD_INIT, "_c_internal_utils", NULL, 0, functions -}; - -#pragma GCC visibility push(default) -PyMODINIT_FUNC PyInit__c_internal_utils(void) +PYBIND11_MODULE(_c_internal_utils, m) { - return PyModule_Create(&util_module); + m.def( + "display_is_valid", &mpl_display_is_valid, + R"""( -- + Check whether the current X11 or Wayland display is valid. + + On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) + succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) + succeeds. + + On other platforms, always returns True.)"""); + m.def( + "Win32_GetCurrentProcessExplicitAppUserModelID", + &mpl_GetCurrentProcessExplicitAppUserModelID, + R"""( -- + Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID. + + On non-Windows platforms, always returns None.)"""); + m.def( + "Win32_SetCurrentProcessExplicitAppUserModelID", + &mpl_SetCurrentProcessExplicitAppUserModelID, + "appid"_a, py::pos_only(), + R"""( -- + Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID. + + On non-Windows platforms, does nothing.)"""); + m.def( + "Win32_GetForegroundWindow", &mpl_GetForegroundWindow, + R"""( -- + Wrapper for Windows' GetForegroundWindow. + + On non-Windows platforms, always returns None.)"""); + m.def( + "Win32_SetForegroundWindow", &mpl_SetForegroundWindow, + "hwnd"_a, + R"""( -- + Wrapper for Windows' SetForegroundWindow. + + On non-Windows platforms, does nothing.)"""); + m.def( + "Win32_SetProcessDpiAwareness_max", &mpl_SetProcessDpiAwareness_max, + R"""( -- + Set Windows' process DPI awareness to best option available. + + On non-Windows platforms, does nothing.)"""); } diff --git a/src/meson.build b/src/meson.build index ce7a8cb034aa..54457d74aceb 100644 --- a/src/meson.build +++ b/src/meson.build @@ -84,7 +84,7 @@ extension_data = { 'sources': files( '_c_internal_utils.cpp', ), - 'dependencies': [py3_dep, dl, ole32, shell32, user32], + 'dependencies': [pybind11_dep, dl, ole32, shell32, user32], }, 'ft2font': { 'subdir': 'matplotlib', From f0a3a09ed935bf937251372e201d91b4b670118c Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 11 Oct 2023 12:25:35 -0500 Subject: [PATCH 0269/1120] Only rotate the subsection of the Path Closes #27062 The code for rotation had referred to the original path, when it should only be rotating the connected segment, which had already been extracted. --- lib/matplotlib/contour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 6a4b27035dc1..adb72ef46017 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -383,7 +383,7 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing= # If the path is closed, rotate it s.t. it starts at the label. is_closed_path = codes[stop - 1] == Path.CLOSEPOLY if is_closed_path: - cc_xys = np.concatenate([xys[idx:-1], xys[:idx+1]]) + cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]]) idx = 0 # Like np.interp, but additionally vectorized over fp. From c06a97e84b8424a87d0e294c5eaf406a1f8d0a62 Mon Sep 17 00:00:00 2001 From: Gauri Chaudhari Date: Wed, 11 Oct 2023 17:43:10 -0400 Subject: [PATCH 0270/1120] Removing deprecated api from patches (#26890) * Removed deprecated class SimpleEvents * Removed deprecated SimpleEvents class * Create 26886-GC.rst * Removing whitespaces * Removing whitespaces * Rename 26886-GC.rst to 26888-GC.rst * Update 26888-GC.rst * Update 26888-GC.rst * Rename 26888-GC.rst to 26889-GC.rst * Update 26890-GC.rst --- doc/api/next_api_changes/removals/26889-GC.rst | 3 +++ lib/matplotlib/patches.py | 6 ------ lib/matplotlib/patches.pyi | 3 --- 3 files changed, 3 insertions(+), 9 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26889-GC.rst diff --git a/doc/api/next_api_changes/removals/26889-GC.rst b/doc/api/next_api_changes/removals/26889-GC.rst new file mode 100644 index 000000000000..2cccc9fee113 --- /dev/null +++ b/doc/api/next_api_changes/removals/26889-GC.rst @@ -0,0 +1,3 @@ +Removing Deprecated API SimpleEvent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``matplotlib.patches.ConnectionStyle._Base.SimpleEvent`` diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index f80df92c62fc..7b9e6e7aa060 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -2707,12 +2707,6 @@ class _Base: points. This base class defines a __call__ method, and a few helper methods. """ - - @_api.deprecated("3.7") - class SimpleEvent: - def __init__(self, xy): - self.x, self.y = xy - def _in_patch(self, patch): """ Return a predicate function testing whether a point *xy* is diff --git a/lib/matplotlib/patches.pyi b/lib/matplotlib/patches.pyi index 29fe36aa6c6b..287ea0f738ab 100644 --- a/lib/matplotlib/patches.pyi +++ b/lib/matplotlib/patches.pyi @@ -455,9 +455,6 @@ class BoxStyle(_Style): class ConnectionStyle(_Style): class _Base(ConnectionStyle): - class SimpleEvent: - def __init__(self, xy: tuple[float, float]) -> None: ... - def __call__( self, posA: tuple[float, float], From 9923ef9eb98375c10811e3e3f86bd9a0e4614847 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 28 Sep 2023 16:28:16 -0400 Subject: [PATCH 0271/1120] add a mypy pre-commit hook and add more explicit excludes to mypy toml config --- .pre-commit-config.yaml | 17 +++++++++++++++++ pyproject.toml | 20 +++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05a14a1a08cc..a60ec6ce0169 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,6 +26,23 @@ repos: - id: no-commit-to-branch #default is master and main - id: trailing-whitespace exclude_types: [svg] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.1.1 + hooks: + - id: mypy + additional_dependencies: [ + pandas-stubs, + types-pillow, + types-python-dateutil, + types-psutil, + types-docutils, + types-PyYAML] + args: [ + "--config-file=pyproject.toml", + "lib/matplotlib" + ] + files: lib/matplotlib #only run when files in lib/matplotlib are changed + pass_filenames: false - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index e0c7e4e9dfee..50e533b0c15b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -207,15 +207,25 @@ convention = "numpy" "galleries/users_explain/text/text_props.py" = ["E501"] [tool.mypy] +ignore_missing_imports = true +enable_incomplete_feature = [ + "Unpack", +] exclude = [ + #stubtest ".*/matplotlib/(sphinxext|backends|testing/jpl_units)", - ".*/mpl_toolkits", + #mypy precommit + "galleries/", + "doc/", + "lib/matplotlib/backends/", + "lib/matplotlib/sphinxext", + "lib/matplotlib/testing/jpl_units", + "lib/mpl_toolkits/", + #removing tests causes errors in backends + "lib/matplotlib/tests/", # tinypages is used for testing the sphinx ext, # stubtest will import and run, opening a figure if not excluded - ".*/tinypages", -] -enable_incomplete_feature = [ - "Unpack", + ".*/tinypages" ] [tool.rstcheck] From 7c02e8528f1208e1206c62d26f629e4119de9ce3 Mon Sep 17 00:00:00 2001 From: yangyangdotcom <70668131+yangyangdotcom@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:23:17 -0500 Subject: [PATCH 0272/1120] Added test_hist in test_datetime.py (#27028) * Added test_hist * reset * remove comment * Added test_hist * added test_hist.py to test_datetime.py * comply with PEP8 * datetime as bins * datetime as bins --- lib/matplotlib/tests/test_datetime.py | 47 +++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 9c2be8ba541d..392ed7ead0d0 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -170,11 +170,52 @@ def test_hexbin(self): fig, ax = plt.subplots() ax.hexbin(...) - @pytest.mark.xfail(reason="Test for hist not written yet") @mpl.style.context("default") def test_hist(self): - fig, ax = plt.subplots() - ax.hist(...) + mpl.rcParams["date.converter"] = 'concise' + + start_date = datetime.datetime(2023, 10, 1) + time_delta = datetime.timedelta(days=1) + + values1 = np.random.randint(1, 10, 30) + values2 = np.random.randint(1, 10, 30) + values3 = np.random.randint(1, 10, 30) + + bin_edges = [start_date + i * time_delta for i in range(31)] + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, constrained_layout=True) + ax1.hist( + [start_date + i * time_delta for i in range(30)], + bins=10, + weights=values1 + ) + ax2.hist( + [start_date + i * time_delta for i in range(30)], + bins=10, + weights=values2 + ) + ax3.hist( + [start_date + i * time_delta for i in range(30)], + bins=10, + weights=values3 + ) + + fig, (ax4, ax5, ax6) = plt.subplots(3, 1, constrained_layout=True) + ax4.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values1 + ) + ax5.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values2 + ) + ax6.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values3 + ) @pytest.mark.xfail(reason="Test for hist2d not written yet") @mpl.style.context("default") From a68878188df30fac1031bb953c55d1591131a05a Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 12 Oct 2023 13:29:53 -0500 Subject: [PATCH 0273/1120] Remove datetime test stubs for spectral methods/table --- lib/matplotlib/tests/test_datetime.py | 48 --------------------------- 1 file changed, 48 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 392ed7ead0d0..ef83b837348d 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -14,12 +14,6 @@ def test_acorr(self): fig, ax = plt.subplots() ax.acorr(...) - @pytest.mark.xfail(reason="Test for angle_spectrum not written yet") - @mpl.style.context("default") - def test_angle_spectrum(self): - fig, ax = plt.subplots() - ax.angle_spectrum(...) - @pytest.mark.xfail(reason="Test for annotate not written yet") @mpl.style.context("default") def test_annotate(self): @@ -110,12 +104,6 @@ def test_clabel(self): fig, ax = plt.subplots() ax.clabel(...) - @pytest.mark.xfail(reason="Test for cohere not written yet") - @mpl.style.context("default") - def test_cohere(self): - fig, ax = plt.subplots() - ax.cohere(...) - @pytest.mark.xfail(reason="Test for contour not written yet") @mpl.style.context("default") def test_contour(self): @@ -128,12 +116,6 @@ def test_contourf(self): fig, ax = plt.subplots() ax.contourf(...) - @pytest.mark.xfail(reason="Test for csd not written yet") - @mpl.style.context("default") - def test_csd(self): - fig, ax = plt.subplots() - ax.csd(...) - @pytest.mark.xfail(reason="Test for errorbar not written yet") @mpl.style.context("default") def test_errorbar(self): @@ -241,12 +223,6 @@ def test_loglog(self): fig, ax = plt.subplots() ax.loglog(...) - @pytest.mark.xfail(reason="Test for magnitude_spectrum not written yet") - @mpl.style.context("default") - def test_magnitude_spectrum(self): - fig, ax = plt.subplots() - ax.magnitude_spectrum(...) - @pytest.mark.xfail(reason="Test for matshow not written yet") @mpl.style.context("default") def test_matshow(self): @@ -277,12 +253,6 @@ def test_pcolormesh(self): fig, ax = plt.subplots() ax.pcolormesh(...) - @pytest.mark.xfail(reason="Test for phase_spectrum not written yet") - @mpl.style.context("default") - def test_phase_spectrum(self): - fig, ax = plt.subplots() - ax.phase_spectrum(...) - @mpl.style.context("default") def test_plot(self): mpl.rcParams["date.converter"] = 'concise' @@ -312,12 +282,6 @@ def test_plot_date(self): ax2.plot_date(x_dates, y_ranges) ax3.plot_date(x_ranges, y_dates) - @pytest.mark.xfail(reason="Test for psd not written yet") - @mpl.style.context("default") - def test_psd(self): - fig, ax = plt.subplots() - ax.psd(...) - @pytest.mark.xfail(reason="Test for quiver not written yet") @mpl.style.context("default") def test_quiver(self): @@ -348,12 +312,6 @@ def test_semilogy(self): fig, ax = plt.subplots() ax.semilogy(...) - @pytest.mark.xfail(reason="Test for specgram not written yet") - @mpl.style.context("default") - def test_specgram(self): - fig, ax = plt.subplots() - ax.specgram(...) - @pytest.mark.xfail(reason="Test for spy not written yet") @mpl.style.context("default") def test_spy(self): @@ -390,12 +348,6 @@ def test_streamplot(self): fig, ax = plt.subplots() ax.streamplot(...) - @pytest.mark.xfail(reason="Test for table not written yet") - @mpl.style.context("default") - def test_table(self): - fig, ax = plt.subplots() - ax.table(...) - @pytest.mark.xfail(reason="Test for text not written yet") @mpl.style.context("default") def test_text(self): From bda0b30f012c25fa108f949a073d6492b941bde4 Mon Sep 17 00:00:00 2001 From: Nivedita Chaudhari <76914526+niviPy@users.noreply.github.com> Date: Thu, 12 Oct 2023 20:56:26 +0000 Subject: [PATCH 0274/1120] Issue 26990 Fix added for image not getting rendered properly. Solution: Split image into two rows for two blocks of code resp. --- galleries/examples/misc/histogram_path.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/galleries/examples/misc/histogram_path.py b/galleries/examples/misc/histogram_path.py index 55260f9699bb..3e30dcfa424d 100644 --- a/galleries/examples/misc/histogram_path.py +++ b/galleries/examples/misc/histogram_path.py @@ -20,7 +20,7 @@ import matplotlib.patches as patches import matplotlib.path as path -fig, axs = plt.subplots(2) +fig, axs = plt.subplots(1) np.random.seed(19680801) # Fixing random state for reproducibility @@ -44,14 +44,16 @@ # make a patch out of it, don't add a margin at y=0 patch = patches.PathPatch(barpath) patch.sticky_edges.y[:] = [0] -axs[0].add_patch(patch) -axs[0].autoscale_view() +axs.add_patch(patch) +axs.autoscale_view() +plt.show() # %% # Instead of creating a three-dimensional array and using # `~.path.Path.make_compound_path_from_polys`, we could as well create the # compound path directly using vertices and codes as shown below +fig, axs = plt.subplots(1) nrects = len(left) nverts = nrects*(1+3+1) verts = np.zeros((nverts, 2)) @@ -72,9 +74,8 @@ # make a patch out of it, don't add a margin at y=0 patch = patches.PathPatch(barpath) patch.sticky_edges.y[:] = [0] -axs[1].add_patch(patch) -axs[1].autoscale_view() - +axs.add_patch(patch) +axs.autoscale_view() plt.show() # %% From 6b9615157c5447d10924f22cfa75c91f20961bf5 Mon Sep 17 00:00:00 2001 From: Nivedita Chaudhari <76914526+niviPy@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:32:40 +0000 Subject: [PATCH 0275/1120] Updated with comments suggestions. --- galleries/examples/misc/histogram_path.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/galleries/examples/misc/histogram_path.py b/galleries/examples/misc/histogram_path.py index 3e30dcfa424d..d35e291aa8ce 100644 --- a/galleries/examples/misc/histogram_path.py +++ b/galleries/examples/misc/histogram_path.py @@ -20,8 +20,6 @@ import matplotlib.patches as patches import matplotlib.path as path -fig, axs = plt.subplots(1) - np.random.seed(19680801) # Fixing random state for reproducibility # histogram our data with numpy @@ -44,8 +42,10 @@ # make a patch out of it, don't add a margin at y=0 patch = patches.PathPatch(barpath) patch.sticky_edges.y[:] = [0] -axs.add_patch(patch) -axs.autoscale_view() + +fig, ax = plt.subplots() +ax.add_patch(patch) +ax.autoscale_view() plt.show() # %% @@ -53,7 +53,6 @@ # `~.path.Path.make_compound_path_from_polys`, we could as well create the # compound path directly using vertices and codes as shown below -fig, axs = plt.subplots(1) nrects = len(left) nverts = nrects*(1+3+1) verts = np.zeros((nverts, 2)) @@ -74,8 +73,10 @@ # make a patch out of it, don't add a margin at y=0 patch = patches.PathPatch(barpath) patch.sticky_edges.y[:] = [0] -axs.add_patch(patch) -axs.autoscale_view() + +fig, ax = plt.subplots() +ax.add_patch(patch) +ax.autoscale_view() plt.show() # %% From 11ea9f5921200f3926a734a36c03f3a57104e20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 13 Oct 2023 14:18:01 -0300 Subject: [PATCH 0276/1120] MAINT: Update environment.yml to match requirements files --- environment.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/environment.yml b/environment.yml index 095e3d240bd2..b655d972a1ea 100644 --- a/environment.yml +++ b/environment.yml @@ -31,18 +31,18 @@ dependencies: - graphviz - ipython - ipywidgets - - numpydoc>=0.8 - - packaging - - pydata-sphinx-theme + - numpydoc>=1.0 + - packaging>=20 + - pydata-sphinx-theme~=0.13.1 - pyyaml - - sphinx>=1.8.1,!=2.0.0 + - sphinx>=3.0.0,!=6.1.2 - sphinx-copybutton - - sphinx-gallery>=0.12 + - sphinx-gallery>=0.12.0 - sphinx-design - pip - pip: - - mpl-sphinx-theme - - sphinxcontrib-svg2pdfconverter + - mpl-sphinx-theme~=3.8.0 + - sphinxcontrib-svg2pdfconverter>=1.1.0 - pikepdf # testing - coverage From 48b53ce21bb61c89aed7a0f68a4a7c5a786ff00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Fri, 13 Oct 2023 16:03:34 -0300 Subject: [PATCH 0277/1120] DOC: Add tags infrastructure for gallery examples Adds sphinx-tags as a dependency and some basic configuration to display tags with color, using sphinx-design. Co-authored-by: hannah Co-authored-by: esibinga --- .gitignore | 1 + doc/_static/mpl.css | 5 +++++ doc/conf.py | 13 +++++++++++++ environment.yml | 1 + galleries/examples/README.txt | 4 ++++ galleries/examples/animation/animated_histogram.py | 3 +++ galleries/examples/animation/multiple_axes.py | 2 ++ galleries/examples/event_handling/resample.py | 3 +++ requirements/doc/doc-requirements.txt | 1 + 9 files changed, 33 insertions(+) diff --git a/.gitignore b/.gitignore index 1514710bd9be..471431dd3433 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ galleries/examples/*/*.svgz result_images doc/_static/constrained_layout*.png doc/.mpl_skip_subdirs.yaml +doc/_tags # Nose/Pytest generated files # ############################### diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 45ecb21d5511..370116d9af7d 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -3,6 +3,11 @@ --pst-color-link-hover: var(--pst-color-secondary); --sd-color-primary: var(--pst-color-primary); --sd-color-primary-text: var(--pst-color-text-base); + --sd-color-secondary: #ee9040; + --sd-color-success: #28a745; + --sd-color-dark: #323232; + --sd-color-danger: #dc3545; + --sd-color-light: #c9c9c9; } .simple li>p { diff --git a/doc/conf.py b/doc/conf.py index c2cdadf0ec24..ca6aa25e8ac6 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -118,6 +118,7 @@ def _parse_skip_subdirs_file(): 'sphinxext.redirect_from', 'sphinx_copybutton', 'sphinx_design', + 'sphinx_tags', ] exclude_patterns = [ @@ -282,6 +283,18 @@ def gallery_image_warning_filter(record): logger = logging.getLogger('sphinx') logger.addFilter(gallery_image_warning_filter) +# Sphinx tags configuration +tags_create_tags = True +tags_page_title = "All tags" +tags_create_badges = True +tags_badge_colors = { + "animation": "primary", + "component:*": "secondary", + "event-handling": "success", + "interactivity:*": "dark", + "plot-type:*": "danger", + "*": "light" # default value +} mathmpl_fontsize = 11.0 mathmpl_srcset = ['2x'] diff --git a/environment.yml b/environment.yml index 095e3d240bd2..9b3c2231f4a1 100644 --- a/environment.yml +++ b/environment.yml @@ -39,6 +39,7 @@ dependencies: - sphinx-copybutton - sphinx-gallery>=0.12 - sphinx-design + - sphinx-tags - pip - pip: - mpl-sphinx-theme diff --git a/galleries/examples/README.txt b/galleries/examples/README.txt index bab703b4168e..8ffe42358352 100644 --- a/galleries/examples/README.txt +++ b/galleries/examples/README.txt @@ -14,3 +14,7 @@ and source code. For longer tutorials, see our :ref:`tutorials page `. You can also find :ref:`external resources ` and a :ref:`FAQ ` in our :ref:`user guide `. + + +.. note:: + You can also browse the example gallery by :ref:`tags `. diff --git a/galleries/examples/animation/animated_histogram.py b/galleries/examples/animation/animated_histogram.py index d23d174ad2ca..24aa1c11474d 100644 --- a/galleries/examples/animation/animated_histogram.py +++ b/galleries/examples/animation/animated_histogram.py @@ -55,3 +55,6 @@ def animate(frame_number): ani = animation.FuncAnimation(fig, prepare_animation(bar_container), 50, repeat=False, blit=True) plt.show() + +# %% +# .. tags:: plot-type:histogram, animation diff --git a/galleries/examples/animation/multiple_axes.py b/galleries/examples/animation/multiple_axes.py index 2c3442c36d63..189bf2fc6af2 100644 --- a/galleries/examples/animation/multiple_axes.py +++ b/galleries/examples/animation/multiple_axes.py @@ -80,3 +80,5 @@ def animate(i): # # - `matplotlib.patches.ConnectionPatch` # - `matplotlib.animation.FuncAnimation` +# +# .. tags:: component:axes, animation diff --git a/galleries/examples/event_handling/resample.py b/galleries/examples/event_handling/resample.py index c034254d060a..66b2a09e85d7 100644 --- a/galleries/examples/event_handling/resample.py +++ b/galleries/examples/event_handling/resample.py @@ -75,3 +75,6 @@ def update(self, ax): ax.callbacks.connect('xlim_changed', d.update) ax.set_xlim(16, 365) plt.show() + +# %% +# .. tags:: interactivity:zoom, event-handling diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index f60e9addf47b..89ee5b74ef5b 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -21,3 +21,4 @@ sphinxcontrib-svg2pdfconverter>=1.1.0 sphinx-gallery>=0.12.0 sphinx-copybutton sphinx-design +sphinx-tags From c563085b554080ae57f61972271b91f9c71fa457 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Fri, 13 Oct 2023 15:27:19 -0400 Subject: [PATCH 0278/1120] Add test_barh in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 53958229f174..46bcf5071295 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -80,11 +80,18 @@ def test_barbs(self): fig, ax = plt.subplots() ax.barbs(...) - @pytest.mark.xfail(reason="Test for barh not written yet") @mpl.style.context("default") def test_barh(self): - fig, ax = plt.subplots() - ax.barh(...) + mpl.rcParams["date.converter"] = 'concise' + N = 14 + base = datetime.datetime(1970, 1, 1) + indices = np.arange(N) + dates = [base + datetime.timedelta(days=(7 * i)) for i in range(N)] + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + error = np.random.rand(N) + ax1.barh(indices, dates, xerr=error) + ax2.barh(dates, indices) + ax3.barh(dates, dates) @pytest.mark.xfail(reason="Test for boxplot not written yet") @mpl.style.context("default") From 18358eb2a99c8844c07414e8536277242647e86d Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Fri, 13 Oct 2023 16:41:52 -0400 Subject: [PATCH 0279/1120] Add test for parameters width and left --- lib/matplotlib/tests/test_datetime.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index c5e55b3c3958..b035a7873083 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -81,11 +81,13 @@ def test_barh(self): base = datetime.datetime(1970, 1, 1) indices = np.arange(N) dates = [base + datetime.timedelta(days=(7 * i)) for i in range(N)] - fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + widths = [datetime.timedelta(days=(7*i)) for i in range(N)] + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout='constrained') error = np.random.rand(N) ax1.barh(indices, dates, xerr=error) ax2.barh(dates, indices) ax3.barh(dates, dates) + ax4.barh(indices, width=widths, left=base) @pytest.mark.xfail(reason="Test for boxplot not written yet") @mpl.style.context("default") From 1446d1c9df681c24c71a7d69a1523a6ec261029d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 13 Oct 2023 22:40:11 -0400 Subject: [PATCH 0280/1120] Rename py namespace to mpl in extension code This will allow using the standard namespace alias for pybind11. --- src/_backend_agg.cpp | 4 ++-- src/_backend_agg.h | 10 +++++----- src/_backend_agg_basic_types.h | 4 ++-- src/_backend_agg_wrapper.cpp | 8 ++++---- src/_path.h | 10 +++++----- src/_path_wrapper.cpp | 28 ++++++++++++++-------------- src/ft2font.cpp | 2 +- src/numpy_cpp.h | 6 +++--- src/py_adaptors.h | 15 +++++++-------- src/py_converters.cpp | 6 +++--- src/py_exceptions.h | 6 +++--- 11 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 335e40971948..aec7676b7951 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -128,11 +128,11 @@ RendererAgg::restore_region(BufferRegion ®ion, int xx1, int yy1, int xx2, int rendererBase.copy_from(rbuf, &rect, x, y); } -bool RendererAgg::render_clippath(py::PathIterator &clippath, +bool RendererAgg::render_clippath(mpl::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; /* Unlike normal Paths, the clip path cannot be clipped to the Figure bbox, * because it needs to remain a complete closed path, so there is no diff --git a/src/_backend_agg.h b/src/_backend_agg.h index dd73aecde0f2..0319b449dce2 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -244,7 +244,7 @@ class RendererAgg template void set_clipbox(const agg::rect_d &cliprect, R &rasterizer); - bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); + bool render_clippath(mpl::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); template void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc); @@ -340,11 +340,11 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, rendererBase.reset_clipping(true); // Create and transform the path - typedef agg::conv_transform hatch_path_trans_t; + typedef agg::conv_transform hatch_path_trans_t; typedef agg::conv_curve hatch_path_curve_t; typedef agg::conv_stroke hatch_path_stroke_t; - py::PathIterator hatch_path(gc.hatchpath); + mpl::PathIterator hatch_path(gc.hatchpath); agg::trans_affine hatch_trans; hatch_trans *= agg::trans_affine_scaling(1.0, -1.0); hatch_trans *= agg::trans_affine_translation(0.0, 1.0); @@ -447,7 +447,7 @@ template inline void RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; @@ -490,7 +490,7 @@ inline void RendererAgg::draw_markers(GCAgg &gc, agg::trans_affine &trans, agg::rgba color) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; typedef PathSnapper snap_t; typedef agg::conv_curve curve_t; diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h index 21a84bb6a5ae..95c7a3ca5a3c 100644 --- a/src/_backend_agg_basic_types.h +++ b/src/_backend_agg_basic_types.h @@ -14,7 +14,7 @@ struct ClipPath { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; }; @@ -103,7 +103,7 @@ class GCAgg e_snap_mode snap_mode; - py::PathIterator hatchpath; + mpl::PathIterator hatchpath; agg::rgba hatch_color; double hatch_linewidth; diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 02832f1c3e69..7ff344f6c2e8 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -209,7 +209,7 @@ static void PyRendererAgg_dealloc(PyRendererAgg *self) static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args) { GCAgg gc; - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; PyObject *faceobj = NULL; agg::rgba face; @@ -263,9 +263,9 @@ static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *ar PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args) { GCAgg gc; - py::PathIterator marker_path; + mpl::PathIterator marker_path; agg::trans_affine marker_path_trans; - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; PyObject *faceobj = NULL; agg::rgba face; @@ -328,7 +328,7 @@ PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args) { GCAgg gc; agg::trans_affine master_transform; - py::PathGenerator paths; + mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; agg::trans_affine offset_trans; diff --git a/src/_path.h b/src/_path.h index 387a0a6737f4..10a1101612ae 100644 --- a/src/_path.h +++ b/src/_path.h @@ -856,7 +856,7 @@ inline bool segments_intersect(const double &x1, template bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) { - typedef PathNanRemover no_nans_t; + typedef PathNanRemover no_nans_t; typedef agg::conv_curve curve_t; if (p1.total_vertices() < 2 || p2.total_vertices() < 2) { @@ -920,7 +920,7 @@ bool path_intersects_rectangle(PathIterator &path, double rect_x2, double rect_y2, bool filled) { - typedef PathNanRemover no_nans_t; + typedef PathNanRemover no_nans_t; typedef agg::conv_curve curve_t; if (path.total_vertices() == 0) { @@ -966,7 +966,7 @@ void convert_path_to_polygons(PathIterator &path, int closed_only, std::vector &result) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSimplifier simplify_t; @@ -1032,7 +1032,7 @@ void cleanup_path(PathIterator &path, std::vector &vertices, std::vector &codes) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSnapper snapped_t; @@ -1189,7 +1189,7 @@ bool convert_to_string(PathIterator &path, std::string& buffer) { size_t buffersize; - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removal_t; typedef PathClipper clipped_t; typedef PathSimplifier simplify_t; diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 369d9e030880..fc4d35da6309 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -35,7 +35,7 @@ const char *Py_point_in_path__doc__ = static PyObject *Py_point_in_path(PyObject *self, PyObject *args) { double x, y, r; - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; bool result; @@ -68,7 +68,7 @@ static PyObject *Py_points_in_path(PyObject *self, PyObject *args) { numpy::array_view points; double r; - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; if (!PyArg_ParseTuple(args, @@ -97,7 +97,7 @@ const char *Py_update_path_extents__doc__ = static PyObject *Py_update_path_extents(PyObject *self, PyObject *args) { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; agg::rect_d rect; numpy::array_view minpos; @@ -177,7 +177,7 @@ const char *Py_get_path_collection_extents__doc__ = static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args) { agg::trans_affine master_transform; - py::PathGenerator paths; + mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; agg::trans_affine offset_trans; @@ -227,7 +227,7 @@ static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args) { double x, y, radius; agg::trans_affine master_transform; - py::PathGenerator paths; + mpl::PathGenerator paths; numpy::array_view transforms; numpy::array_view offsets; agg::trans_affine offset_trans; @@ -280,9 +280,9 @@ const char *Py_path_in_path__doc__ = static PyObject *Py_path_in_path(PyObject *self, PyObject *args) { - py::PathIterator a; + mpl::PathIterator a; agg::trans_affine atrans; - py::PathIterator b; + mpl::PathIterator b; agg::trans_affine btrans; bool result; @@ -314,7 +314,7 @@ const char *Py_clip_path_to_rect__doc__ = static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args) { - py::PathIterator path; + mpl::PathIterator path; agg::rect_d rect; bool inside; std::vector result; @@ -407,8 +407,8 @@ const char *Py_path_intersects_path__doc__ = static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds) { - py::PathIterator p1; - py::PathIterator p2; + mpl::PathIterator p1; + mpl::PathIterator p2; agg::trans_affine t1; agg::trans_affine t2; int filled = 0; @@ -453,7 +453,7 @@ const char *Py_path_intersects_rectangle__doc__ = static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds) { - py::PathIterator path; + mpl::PathIterator path; double rect_x1, rect_y1, rect_x2, rect_y2; bool filled = false; const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL }; @@ -489,7 +489,7 @@ const char *Py_convert_path_to_polygons__doc__ = static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds) { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; double width = 0.0, height = 0.0; int closed_only = 1; @@ -524,7 +524,7 @@ const char *Py_cleanup_path__doc__ = static PyObject *Py_cleanup_path(PyObject *self, PyObject *args) { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; bool remove_nans; agg::rect_d clip_rect; @@ -631,7 +631,7 @@ const char *Py_convert_to_string__doc__ = static PyObject *Py_convert_to_string(PyObject *self, PyObject *args) { - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; agg::rect_d cliprect; PyObject *simplifyobj; diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 975041374133..fbc00dc69696 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -195,7 +195,7 @@ static void ft_glyph_warn(FT_ULong charcode) Py_XDECREF(text_helpers); Py_XDECREF(tmp); if (PyErr_Occurred()) { - throw py::exception(); + throw mpl::exception(); } } diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 36c763d1584e..e634b9a7271e 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -378,7 +378,7 @@ class array_view : public detail::array_view_accessors array_view(PyObject *arr, bool contiguous = false) : m_arr(NULL), m_data(NULL) { if (!set(arr, contiguous)) { - throw py::exception(); + throw mpl::exception(); } } @@ -413,11 +413,11 @@ class array_view : public detail::array_view_accessors { PyObject *arr = PyArray_SimpleNew(ND, shape, type_num_of::value); if (arr == NULL) { - throw py::exception(); + throw mpl::exception(); } if (!set(arr, true)) { Py_DECREF(arr); - throw py::exception(); + throw mpl::exception(); } Py_DECREF(arr); } diff --git a/src/py_adaptors.h b/src/py_adaptors.h index 7722137dc6e1..1863cd6efbb0 100644 --- a/src/py_adaptors.h +++ b/src/py_adaptors.h @@ -18,12 +18,11 @@ extern "C" { int convert_path(PyObject *obj, void *pathp); } -namespace py -{ +namespace mpl { /************************************************************ - * py::PathIterator acts as a bridge between Numpy and Agg. Given a - * pair of Numpy arrays, vertices and codes, it iterates over + * mpl::PathIterator acts as a bridge between NumPy and Agg. Given a + * pair of NumPy arrays, vertices and codes, it iterates over * those vertices and codes, using the standard Agg vertex source * interface: * @@ -66,14 +65,14 @@ class PathIterator : m_vertices(NULL), m_codes(NULL), m_iterator(0) { if (!set(vertices, codes, should_simplify, simplify_threshold)) - throw py::exception(); + throw mpl::exception(); } inline PathIterator(PyObject *vertices, PyObject *codes) : m_vertices(NULL), m_codes(NULL), m_iterator(0) { if (!set(vertices, codes)) - throw py::exception(); + throw mpl::exception(); } inline PathIterator(const PathIterator &other) @@ -233,11 +232,11 @@ class PathGenerator item = PySequence_GetItem(m_paths, i % m_npaths); if (item == NULL) { - throw py::exception(); + throw mpl::exception(); } if (!convert_path(item, &path)) { Py_DECREF(item); - throw py::exception(); + throw mpl::exception(); } Py_DECREF(item); return path; diff --git a/src/py_converters.cpp b/src/py_converters.cpp index 04382c5f94d0..e700342d5c78 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -343,7 +343,7 @@ int convert_trans_affine(PyObject *obj, void *transp) int convert_path(PyObject *obj, void *pathp) { - py::PathIterator *path = (py::PathIterator *)pathp; + mpl::PathIterator *path = (mpl::PathIterator *)pathp; PyObject *vertices_obj = NULL; PyObject *codes_obj = NULL; @@ -404,7 +404,7 @@ int convert_path(PyObject *obj, void *pathp) int convert_pathgen(PyObject *obj, void *pathgenp) { - py::PathGenerator *paths = (py::PathGenerator *)pathgenp; + mpl::PathGenerator *paths = (mpl::PathGenerator *)pathgenp; if (!paths->set(obj)) { PyErr_SetString(PyExc_TypeError, "Not an iterable of paths"); return 0; @@ -415,7 +415,7 @@ int convert_pathgen(PyObject *obj, void *pathgenp) int convert_clippath(PyObject *clippath_tuple, void *clippathp) { ClipPath *clippath = (ClipPath *)clippathp; - py::PathIterator path; + mpl::PathIterator path; agg::trans_affine trans; if (clippath_tuple != NULL && clippath_tuple != Py_None) { diff --git a/src/py_exceptions.h b/src/py_exceptions.h index c4accf263408..7a7e004a4a7a 100644 --- a/src/py_exceptions.h +++ b/src/py_exceptions.h @@ -6,8 +6,8 @@ #include #include -namespace py -{ +namespace mpl { + class exception : public std::exception { public: @@ -23,7 +23,7 @@ class exception : public std::exception { \ a; \ } \ - catch (const py::exception &) \ + catch (const mpl::exception &) \ { \ { \ cleanup; \ From 9fa1186da9f98994932118dc2914658610b89e8f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 13 Oct 2023 23:12:11 -0400 Subject: [PATCH 0281/1120] Use pybind11's usual namespace alias in extensions --- src/_image_wrapper.cpp | 72 +++++++++++++++++++++------------------- src/_ttconv.cpp | 9 ++--- src/py_converters_11.cpp | 4 +-- src/py_converters_11.h | 4 ++- src/tri/_tri_wrapper.cpp | 22 ++++++------ 5 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index b18e93a50fb3..716116c0ba56 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -4,6 +4,8 @@ #include "_image_resample.h" #include "py_converters_11.h" +namespace py = pybind11; +using namespace pybind11::literals; /********************************************************************** * Free functions @@ -46,8 +48,8 @@ radius: float, default: 1 )"""; -static pybind11::array_t -_get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t *dims) +static py::array_t +_get_transform_mesh(const py::object& transform, const py::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -58,8 +60,8 @@ _get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t * // If attribute doesn't exist, raises Python AttributeError auto inverse = transform.attr("inverted")(); - pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[1], 2}; - pybind11::array_t input_mesh(mesh_dims); + py::ssize_t mesh_dims[2] = {dims[0]*dims[1], 2}; + py::array_t input_mesh(mesh_dims); auto p = input_mesh.mutable_data(); for (auto y = 0; y < dims[0]; ++y) { @@ -72,7 +74,7 @@ _get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t * auto output_mesh = inverse.attr("transform")(input_mesh); auto output_mesh_array = - pybind11::array_t(output_mesh); + py::array_t(output_mesh); if (output_mesh_array.ndim() != 2) { throw std::runtime_error( @@ -84,12 +86,12 @@ _get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t * } -// Using generic pybind::array for input and output arrays rather than the more usual -// pybind::array_t as function supports multiple array dtypes. +// Using generic py::array for input and output arrays rather than the more usual +// py::array_t as this function supports multiple array dtypes. static void -image_resample(pybind11::array input_array, - pybind11::array& output_array, - const pybind11::object& transform, +image_resample(py::array input_array, + py::array& output_array, + const py::object& transform, interpolation_e interpolation, bool resample_, // Avoid name clash with resample() function float alpha, @@ -111,7 +113,7 @@ image_resample(pybind11::array input_array, } // Ensure input array is contiguous, regardless of dtype - input_array = pybind11::array::ensure(input_array, pybind11::array::c_style); + input_array = py::array::ensure(input_array, py::array::c_style); // Validate output array auto out_ndim = output_array.ndim(); @@ -132,7 +134,7 @@ image_resample(pybind11::array input_array, throw std::invalid_argument("Input and output arrays have mismatched types"); } - if ((output_array.flags() & pybind11::array::c_style) == 0) { + if ((output_array.flags() & py::array::c_style) == 0) { throw std::invalid_argument("Output array must be C-contiguous"); } @@ -150,14 +152,14 @@ image_resample(pybind11::array input_array, // Only used if transform is not affine. // Need to keep it in scope for the duration of this function. - pybind11::array_t transform_mesh; + py::array_t transform_mesh; // Validate transform if (transform.is_none()) { params.is_affine = true; } else { // Raises Python AttributeError if no such attribute or TypeError if cast fails - bool is_affine = pybind11::cast(transform.attr("is_affine")); + bool is_affine = py::cast(transform.attr("is_affine")); if (is_affine) { convert_trans_affine(transform, params.affine); @@ -171,20 +173,20 @@ image_resample(pybind11::array input_array, if (auto resampler = (ndim == 2) ? ( - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : nullptr) : ( // ndim == 3 - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : - (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : + (dtype.is(py::dtype::of())) ? resample : nullptr)) { Py_BEGIN_ALLOW_THREADS resampler( @@ -199,7 +201,7 @@ image_resample(pybind11::array input_array, PYBIND11_MODULE(_image, m) { - pybind11::enum_(m, "_InterpolationType") + py::enum_(m, "_InterpolationType") .value("NEAREST", NEAREST) .value("BILINEAR", BILINEAR) .value("BICUBIC", BICUBIC) @@ -220,13 +222,13 @@ PYBIND11_MODULE(_image, m) { .export_values(); m.def("resample", &image_resample, - pybind11::arg("input_array"), - pybind11::arg("output_array"), - pybind11::arg("transform"), - pybind11::arg("interpolation") = interpolation_e::NEAREST, - pybind11::arg("resample") = false, - pybind11::arg("alpha") = 1, - pybind11::arg("norm") = false, - pybind11::arg("radius") = 1, + "input_array"_a, + "output_array"_a, + "transform"_a, + "interpolation"_a = interpolation_e::NEAREST, + "resample"_a = false, + "alpha"_a = 1, + "norm"_a = false, + "radius"_a = 1, image_resample__doc__); } diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp index 51c2531bed99..a682c26b17b0 100644 --- a/src/_ttconv.cpp +++ b/src/_ttconv.cpp @@ -12,6 +12,7 @@ #include namespace py = pybind11; +using namespace pybind11::literals; /** * An implementation of TTStreamWriter that writes to a Python @@ -75,10 +76,10 @@ PYBIND11_MODULE(_ttconv, m) { "fonts to Postscript Type 3, Postscript Type 42 and " "Pdf Type 3 fonts."; m.def("convert_ttf_to_ps", &convert_ttf_to_ps, - py::arg("filename"), - py::arg("output"), - py::arg("fonttype"), - py::arg("glyph_ids") = py::none(), + "filename"_a, + "output"_a, + "fonttype"_a, + "glyph_ids"_a = py::none(), "Converts the Truetype font into a Type 3 or Type 42 Postscript font, " "optionally subsetting the font to only the desired set of characters.\n" "\n" diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp index 982cc9ac6c46..830ee6336fb4 100644 --- a/src/py_converters_11.cpp +++ b/src/py_converters_11.cpp @@ -1,13 +1,13 @@ #include "py_converters_11.h" -void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) +void convert_trans_affine(const py::object& transform, agg::trans_affine& affine) { // If None assume identity transform so leave affine unchanged if (transform.is_none()) { return; } - auto array = pybind11::array_t::ensure(transform); + auto array = py::array_t::ensure(transform); if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) { throw std::invalid_argument("Invalid affine transformation matrix"); } diff --git a/src/py_converters_11.h b/src/py_converters_11.h index 1017b2a3e5c1..9af617d3ee8b 100644 --- a/src/py_converters_11.h +++ b/src/py_converters_11.h @@ -6,8 +6,10 @@ #include #include +namespace py = pybind11; + #include "agg_trans_affine.h" -void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine); +void convert_trans_affine(const py::object& transform, agg::trans_affine& affine); #endif diff --git a/src/tri/_tri_wrapper.cpp b/src/tri/_tri_wrapper.cpp index 1b0c3d75555e..d6c4fb10c5d0 100644 --- a/src/tri/_tri_wrapper.cpp +++ b/src/tri/_tri_wrapper.cpp @@ -1,5 +1,7 @@ #include "_tri.h" +using namespace pybind11::literals; + PYBIND11_MODULE(_tri, m) { py::class_(m, "Triangulation") .def(py::init(), - py::arg("x"), - py::arg("y"), - py::arg("triangles"), - py::arg("mask"), - py::arg("edges"), - py::arg("neighbors"), - py::arg("correct_triangle_orientations"), + "x"_a, + "y"_a, + "triangles"_a, + "mask"_a, + "edges"_a, + "neighbors"_a, + "correct_triangle_orientations"_a, "Create a new C++ Triangulation object.\n" "This should not be called directly, use the python class\n" "matplotlib.tri.Triangulation instead.\n") @@ -31,8 +33,8 @@ PYBIND11_MODULE(_tri, m) { py::class_(m, "TriContourGenerator") .def(py::init(), - py::arg("triangulation"), - py::arg("z"), + "triangulation"_a, + "z"_a, "Create a new C++ TriContourGenerator object.\n" "This should not be called directly, use the functions\n" "matplotlib.axes.tricontour and tricontourf instead.\n") @@ -43,7 +45,7 @@ PYBIND11_MODULE(_tri, m) { py::class_(m, "TrapezoidMapTriFinder") .def(py::init(), - py::arg("triangulation"), + "triangulation"_a, "Create a new C++ TrapezoidMapTriFinder object.\n" "This should not be called directly, use the python class\n" "matplotlib.tri.TrapezoidMapTriFinder instead.\n") From 26f12fad6c95b6abaedaec20bd55587317080324 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Oct 2023 04:13:08 -0400 Subject: [PATCH 0282/1120] Remove unused array_view.empty method --- src/numpy_cpp.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 36c763d1584e..c1482005c7ab 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -519,11 +519,6 @@ class array_view : public detail::array_view_accessors } } - bool empty() const - { - return size() == 0; - } - // Do not use this for array_view. See comment near top of file. const T *data() const { From c1e6873b8dd31f45524b9a0f9950863e20bf7152 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Oct 2023 02:51:03 -0400 Subject: [PATCH 0283/1120] Rename array_view.dim to shape This matches the name from pybind11, and will make it easier to reuse templated code with either. --- src/_backend_agg.h | 30 +++++++++++++++--------------- src/_backend_agg_wrapper.cpp | 2 +- src/_path.h | 6 +++--- src/_path_wrapper.cpp | 4 ++-- src/_qhull_wrapper.cpp | 4 ++-- src/array.h | 4 ++-- src/mplutils.h | 8 ++++---- src/numpy_cpp.h | 4 ++-- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/_backend_agg.h b/src/_backend_agg.h index dd73aecde0f2..82846ebb89bb 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -731,22 +731,22 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in rendererBase.reset_clipping(true); if (angle != 0.0) { agg::rendering_buffer srcbuf( - image.data(), (unsigned)image.dim(1), - (unsigned)image.dim(0), (unsigned)image.dim(1)); + image.data(), (unsigned)image.shape(1), + (unsigned)image.shape(0), (unsigned)image.shape(1)); agg::pixfmt_gray8 pixf_img(srcbuf); set_clipbox(gc.cliprect, theRasterizer); agg::trans_affine mtx; - mtx *= agg::trans_affine_translation(0, -image.dim(0)); + mtx *= agg::trans_affine_translation(0, -image.shape(0)); mtx *= agg::trans_affine_rotation(-angle * (agg::pi / 180.0)); mtx *= agg::trans_affine_translation(x, y); agg::path_storage rect; rect.move_to(0, 0); - rect.line_to(image.dim(1), 0); - rect.line_to(image.dim(1), image.dim(0)); - rect.line_to(0, image.dim(0)); + rect.line_to(image.shape(1), 0); + rect.line_to(image.shape(1), image.shape(0)); + rect.line_to(0, image.shape(0)); rect.line_to(0, 0); agg::conv_transform rect2(rect, mtx); @@ -767,10 +767,10 @@ inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, in } else { agg::rect_i fig, text; - int deltay = y - image.dim(0); + int deltay = y - image.shape(0); fig.init(0, 0, width, height); - text.init(x, deltay, x + image.dim(1), y); + text.init(x, deltay, x + image.shape(1), y); text.clip(fig); if (gc.cliprect.x1 != 0.0 || gc.cliprect.y1 != 0.0 || gc.cliprect.x2 != 0.0 || gc.cliprect.y2 != 0.0) { @@ -832,19 +832,19 @@ inline void RendererAgg::draw_image(GCAgg &gc, agg::rendering_buffer buffer; buffer.attach( - image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), -(int)image.dim(1) * 4); + image.data(), (unsigned)image.shape(1), (unsigned)image.shape(0), -(int)image.shape(1) * 4); pixfmt pixf(buffer); if (has_clippath) { agg::trans_affine mtx; agg::path_storage rect; - mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.dim(0)))); + mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.shape(0)))); rect.move_to(0, 0); - rect.line_to(image.dim(1), 0); - rect.line_to(image.dim(1), image.dim(0)); - rect.line_to(0, image.dim(0)); + rect.line_to(image.shape(1), 0); + rect.line_to(image.shape(1), image.shape(0)); + rect.line_to(0, image.shape(0)); rect.line_to(0, 0); agg::conv_transform rect2(rect, mtx); @@ -880,7 +880,7 @@ inline void RendererAgg::draw_image(GCAgg &gc, } else { set_clipbox(gc.cliprect, rendererBase); rendererBase.blend_from( - pixf, 0, (int)x, (int)(height - (y + image.dim(0))), (agg::int8u)(alpha * 255)); + pixf, 0, (int)x, (int)(height - (y + image.shape(0))), (agg::int8u)(alpha * 255)); } rendererBase.reset_clipping(true); @@ -1234,7 +1234,7 @@ inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode); - for (int i = 0; i < points.dim(0); ++i) { + for (int i = 0; i < points.shape(0); ++i) { typename PointArray::sub_t point = points.subarray(i); typename ColorArray::sub_t color = colors.subarray(i); diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 02832f1c3e69..86543b2567b4 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -466,7 +466,7 @@ PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args) PyErr_Format(PyExc_ValueError, "points and colors arrays must be the same length, got " "%" NPY_INTP_FMT " points and %" NPY_INTP_FMT "colors", - points.dim(0), colors.dim(0)); + points.shape(0), colors.shape(0)); return NULL; } diff --git a/src/_path.h b/src/_path.h index 387a0a6737f4..cc11aee913df 100644 --- a/src/_path.h +++ b/src/_path.h @@ -379,7 +379,7 @@ void get_path_collection_extents(agg::trans_affine &master_transform, agg::trans_affine &offset_trans, extent_limits &extent) { - if (offsets.size() != 0 && offsets.dim(1) != 2) { + if (offsets.size() != 0 && offsets.shape(1) != 2) { throw std::runtime_error("Offsets array must have shape (N, 2)"); } @@ -709,7 +709,7 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto template void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) { - if (vertices.size() != 0 && vertices.dim(1) != 2) { + if (vertices.size() != 0 && vertices.shape(1) != 2) { throw std::runtime_error("Invalid vertices array."); } @@ -739,7 +739,7 @@ void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, Resu template void affine_transform_1d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) { - if (vertices.dim(0) != 2) { + if (vertices.shape(0) != 2) { throw std::runtime_error("Invalid vertices array."); } diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 369d9e030880..60704e92d1dd 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -118,10 +118,10 @@ static PyObject *Py_update_path_extents(PyObject *self, PyObject *args) return NULL; } - if (minpos.dim(0) != 2) { + if (minpos.shape(0) != 2) { PyErr_Format(PyExc_ValueError, "minpos must be of length 2, got %" NPY_INTP_FMT, - minpos.dim(0)); + minpos.shape(0)); return NULL; } diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index ef374d8e42c0..3abce833fe6f 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -267,8 +267,8 @@ delaunay(PyObject *self, PyObject *args) return NULL; } - npoints = xarray.dim(0); - if (npoints != yarray.dim(0)) { + npoints = xarray.shape(0); + if (npoints != yarray.shape(0)) { PyErr_SetString(PyExc_ValueError, "x and y must be 1D arrays of the same length"); return NULL; diff --git a/src/array.h b/src/array.h index 47d82995541b..e5d8fb64b2fb 100644 --- a/src/array.h +++ b/src/array.h @@ -29,7 +29,7 @@ class scalar return m_value; } - int dim(size_t i) + int shape(size_t i) { return 1; } @@ -65,7 +65,7 @@ class empty return empty(); } - int dim(size_t i) const + int shape(size_t i) const { return 0; } diff --git a/src/mplutils.h b/src/mplutils.h index 186449e8f63c..8c2921a1d2ff 100644 --- a/src/mplutils.h +++ b/src/mplutils.h @@ -72,10 +72,10 @@ inline int prepare_and_add_type(PyTypeObject *type, PyObject *module) template inline bool check_trailing_shape(T array, char const* name, long d1) { - if (array.dim(1) != d1) { + if (array.shape(1) != d1) { PyErr_Format(PyExc_ValueError, "%s must have shape (N, %ld), got (%ld, %ld)", - name, d1, array.dim(0), array.dim(1)); + name, d1, array.shape(0), array.shape(1)); return false; } return true; @@ -84,10 +84,10 @@ inline bool check_trailing_shape(T array, char const* name, long d1) template inline bool check_trailing_shape(T array, char const* name, long d1, long d2) { - if (array.dim(1) != d1 || array.dim(2) != d2) { + if (array.shape(1) != d1 || array.shape(2) != d2) { PyErr_Format(PyExc_ValueError, "%s must have shape (N, %ld, %ld), got (%ld, %ld, %ld)", - name, d1, d2, array.dim(0), array.dim(1), array.dim(2)); + name, d1, d2, array.shape(0), array.shape(1), array.shape(2)); return false; } return true; diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index c1482005c7ab..0d3fd025890e 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -492,7 +492,7 @@ class array_view : public detail::array_view_accessors return true; } - npy_intp dim(size_t i) const + npy_intp shape(size_t i) const { if (i >= ND) { return 0; @@ -515,7 +515,7 @@ class array_view : public detail::array_view_accessors if (empty) { return 0; } else { - return (size_t)dim(0); + return (size_t)shape(0); } } From 02fa8fced30c7392e3a729c80cef6f0aab58eb4c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Oct 2023 03:39:36 -0400 Subject: [PATCH 0284/1120] Replace some calls to array_view.size with .shape(0) The other dimensions have already been confirmed to be non-zero, so size() == shape(0) in these cases. --- lib/matplotlib/tests/test_transforms.py | 4 ++-- src/_backend_agg_wrapper.cpp | 6 +++--- src/_path.h | 2 +- src/_path_wrapper.cpp | 14 +++++++++++--- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index a9a92d33cff3..4145ff37f6f0 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -627,9 +627,9 @@ def test_invalid_arguments(): t.transform([]) with pytest.raises(RuntimeError): t.transform([1]) - with pytest.raises(RuntimeError): + with pytest.raises(ValueError): t.transform([[1]]) - with pytest.raises(RuntimeError): + with pytest.raises(ValueError): t.transform([[1, 2, 3]]) diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 86543b2567b4..a8c40969e427 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -456,13 +456,13 @@ PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args) &trans)) { return NULL; } - if (points.size() && !check_trailing_shape(points, "points", 3, 2)) { + if (points.shape(0) && !check_trailing_shape(points, "points", 3, 2)) { return NULL; } - if (colors.size() && !check_trailing_shape(colors, "colors", 3, 4)) { + if (colors.shape(0) && !check_trailing_shape(colors, "colors", 3, 4)) { return NULL; } - if (points.size() != colors.size()) { + if (points.shape(0) != colors.shape(0)) { PyErr_Format(PyExc_ValueError, "points and colors arrays must be the same length, got " "%" NPY_INTP_FMT " points and %" NPY_INTP_FMT "colors", diff --git a/src/_path.h b/src/_path.h index cc11aee913df..568618613bbc 100644 --- a/src/_path.h +++ b/src/_path.h @@ -713,7 +713,7 @@ void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, Resu throw std::runtime_error("Invalid vertices array."); } - size_t n = vertices.size(); + size_t n = vertices.shape(0); double x; double y; double t0; diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 60704e92d1dd..cc6d1c05ebca 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -83,7 +83,11 @@ static PyObject *Py_points_in_path(PyObject *self, PyObject *args) return NULL; } - npy_intp dims[] = { (npy_intp)points.size() }; + if (!check_trailing_shape(points, "points", 2)) { + return NULL; + } + + npy_intp dims[] = { (npy_intp)points.shape(0) }; numpy::array_view results(dims); CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results))); @@ -361,7 +365,11 @@ static PyObject *Py_affine_transform(PyObject *self, PyObject *args) numpy::array_view vertices(vertices_arr); Py_DECREF(vertices_arr); - npy_intp dims[] = { (npy_intp)vertices.size(), 2 }; + if(!check_trailing_shape(vertices, "vertices", 2)) { + return NULL; + } + + npy_intp dims[] = { (npy_intp)vertices.shape(0), 2 }; numpy::array_view result(dims); CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result))); return result.pyobj(); @@ -369,7 +377,7 @@ static PyObject *Py_affine_transform(PyObject *self, PyObject *args) numpy::array_view vertices(vertices_arr); Py_DECREF(vertices_arr); - npy_intp dims[] = { (npy_intp)vertices.size() }; + npy_intp dims[] = { (npy_intp)vertices.shape(0) }; numpy::array_view result(dims); CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result))); return result.pyobj(); From 343fa1312f3ea485e0db2dfb5d4f5e0cf178df48 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Oct 2023 03:57:57 -0400 Subject: [PATCH 0285/1120] Replace array_view.size with an external helper This currently checks if any dimension is 0, and returns the first dimension or 0. However, `pybind11::array_t.size()` returns the product of the shapes, so we want to be explicit when we need the former. I also checked the remaining `size` calls, and those will be fine with either interpretation, so I did not change them. --- src/_backend_agg.h | 12 ++++++------ src/_path.h | 14 +++++++------- src/array.h | 13 +++++++++++++ src/numpy_cpp.h | 45 +++++++++++++++++++++++++++------------------ 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 82846ebb89bb..2aafd8526171 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -918,15 +918,15 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, typedef agg::conv_curve curve_t; size_t Npaths = path_generator.num_paths(); - size_t Noffsets = offsets.size(); + size_t Noffsets = safe_first_shape(offsets); size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = transforms.size(); - size_t Nfacecolors = facecolors.size(); - size_t Nedgecolors = edgecolors.size(); - size_t Nlinewidths = linewidths.size(); + size_t Ntransforms = safe_first_shape(transforms); + size_t Nfacecolors = safe_first_shape(facecolors); + size_t Nedgecolors = safe_first_shape(edgecolors); + size_t Nlinewidths = safe_first_shape(linewidths); size_t Nlinestyles = std::min(linestyles.size(), N); - size_t Naa = antialiaseds.size(); + size_t Naa = safe_first_shape(antialiaseds); if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { return; diff --git a/src/_path.h b/src/_path.h index 568618613bbc..75ac1779b091 100644 --- a/src/_path.h +++ b/src/_path.h @@ -113,7 +113,7 @@ void point_in_path_impl(PointArray &points, PathIterator &path, ResultArray &ins size_t i; bool all_done; - size_t n = points.size(); + size_t n = safe_first_shape(points); std::vector yflag0(n); std::vector subpath_flag(n); @@ -247,7 +247,7 @@ inline void points_in_path(PointArray &points, typedef agg::conv_contour contour_t; size_t i; - for (i = 0; i < points.size(); ++i) { + for (i = 0; i < safe_first_shape(points); ++i) { result[i] = false; } @@ -384,9 +384,9 @@ void get_path_collection_extents(agg::trans_affine &master_transform, } size_t Npaths = paths.size(); - size_t Noffsets = offsets.size(); + size_t Noffsets = safe_first_shape(offsets); size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms.size(), N); + size_t Ntransforms = std::min(safe_first_shape(transforms), N); size_t i; agg::trans_affine trans; @@ -436,9 +436,9 @@ void point_in_path_collection(double x, return; } - size_t Noffsets = offsets.size(); + size_t Noffsets = safe_first_shape(offsets); size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms.size(), N); + size_t Ntransforms = std::min(safe_first_shape(transforms), N); size_t i; agg::trans_affine trans; @@ -776,7 +776,7 @@ int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes) std::swap(a.y1, a.y2); } - size_t num_bboxes = bboxes.size(); + size_t num_bboxes = safe_first_shape(bboxes); for (size_t i = 0; i < num_bboxes; ++i) { b = agg::rect_d(bboxes(i, 0, 0), bboxes(i, 0, 1), bboxes(i, 1, 0), bboxes(i, 1, 1)); diff --git a/src/array.h b/src/array.h index e5d8fb64b2fb..6694412b8df5 100644 --- a/src/array.h +++ b/src/array.h @@ -40,6 +40,13 @@ class scalar } }; +template +size_t +safe_first_shape(scalar) +{ + return 1; +} + template class empty { @@ -75,6 +82,12 @@ class empty return 0; } }; + +template +size_t safe_first_shape(empty) +{ + return 0; +} } #endif diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 0d3fd025890e..b26f73e2c4fb 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -500,24 +500,7 @@ class array_view : public detail::array_view_accessors return m_shape[i]; } - /* - In most cases, code should use size() instead of dim(0), since - size() == 0 when any dimension is 0. - */ - size_t size() const - { - bool empty = (ND == 0); - for (size_t i = 0; i < ND; i++) { - if (m_shape[i] == 0) { - empty = true; - } - } - if (empty) { - return 0; - } else { - return (size_t)shape(0); - } - } + size_t size() const; // Do not use this for array_view. See comment near top of file. const T *data() const @@ -567,6 +550,32 @@ class array_view : public detail::array_view_accessors } }; +/* In most cases, code should use safe_first_shape(obj) instead of obj.shape(0), since + safe_first_shape(obj) == 0 when any dimension is 0. */ +template +size_t +safe_first_shape(const array_view &a) +{ + bool empty = (ND == 0); + for (size_t i = 0; i < ND; i++) { + if (a.shape(i) == 0) { + empty = true; + } + } + if (empty) { + return 0; + } else { + return (size_t)a.shape(0); + } +} + +template +size_t +array_view::size() const +{ + return safe_first_shape(*this); +} + } // namespace numpy From 82503fbf7b9394f4d3a03fddfba9f67b33b9aa06 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:55:24 +0200 Subject: [PATCH 0286/1120] Restore figaspect() API documentation This was accidentally lost through #26629. --- doc/api/figure_api.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 84d068f5c3af..316bd8322d32 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -306,3 +306,8 @@ FigureBase parent class ======================= .. autoclass:: FigureBase + +Helper functions +================ + +.. autofunction:: figaspect From ab1e1cbc6c3ff5b348d4522d22c1c5c4bc4d151d Mon Sep 17 00:00:00 2001 From: Ant Lockyer Date: Sat, 14 Oct 2023 15:40:10 +0100 Subject: [PATCH 0287/1120] Add test_step to test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index ef83b837348d..f441502334e2 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -336,11 +336,15 @@ def test_stem(self): fig, ax = plt.subplots() ax.stem(...) - @pytest.mark.xfail(reason="Test for step not written yet") @mpl.style.context("default") def test_step(self): - fig, ax = plt.subplots() - ax.step(...) + mpl.rcParams["date.converter"] = "concise" + N = 6 + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + x = np.array([datetime.datetime(2023, 9, n) for n in range(1, N)]) + ax1.step(x, range(1, N)) + ax2.step(range(1, N), x) + ax3.step(x, x) @pytest.mark.xfail(reason="Test for streamplot not written yet") @mpl.style.context("default") From 34026319c0501d53bfc00f216e05a496058a269e Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Sat, 14 Oct 2023 23:52:24 +0530 Subject: [PATCH 0288/1120] raising revision for test_bar in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index bea744cb435d..deaef36fef51 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -60,20 +60,23 @@ def test_axvspan(self): def test_bar(self): mpl.rcParams["date.converter"] = "concise" range_threshold = 10 - fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") x_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)], + dtype=np.datetime64, ) y_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] + [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)], + dtype=np.datetime64, ) x_ranges = np.array(range(1, range_threshold)) y_ranges = np.array(range(1, range_threshold)) - ax1.bar(x_dates, y_ranges) - ax2.bar(x_dates, y_dates) - ax3.bar(x_ranges, y_dates) + ax1.bar(x_dates, y_ranges, width=np.timedelta64(range_threshold, "D")) + ax2.bar(x_dates, y_dates, width=np.timedelta64(range_threshold, "D")) + ax3.bar(x_ranges, y_dates, width=np.timedelta64(range_threshold, "D")) + ax4.bar(x_ranges, y_ranges, bottom=datetime.datetime(2023, 10, 1)) @pytest.mark.xfail(reason="Test for bar_label not written yet") @mpl.style.context("default") From cc814f99de463debf522f42cb6c96e97457d8e59 Mon Sep 17 00:00:00 2001 From: Alice Descoeudres Date: Sun, 15 Oct 2023 15:22:56 +0200 Subject: [PATCH 0289/1120] make fonts.py, mathtext.py, text_intro.py confirm to docs guidelines --- galleries/users_explain/text/fonts.py | 4 ++-- galleries/users_explain/text/mathtext.py | 2 +- galleries/users_explain/text/text_intro.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/galleries/users_explain/text/fonts.py b/galleries/users_explain/text/fonts.py index fdb2de82ff5c..7efb9a00aa09 100644 --- a/galleries/users_explain/text/fonts.py +++ b/galleries/users_explain/text/fonts.py @@ -73,7 +73,7 @@ - Limited support with Matplotlib Font subsetting -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ The PDF and PostScript formats support embedding fonts in files, allowing the display program to correctly render the text, independent of what fonts are @@ -93,7 +93,7 @@ Currently Type 3, Type 42, and TrueType fonts are subsetted. Type 1 fonts are not. Core Fonts -~~~~~~~~~~ +^^^^^^^^^^ In addition to the ability to embed fonts, as part of the `PostScript `_ and `PDF diff --git a/galleries/users_explain/text/mathtext.py b/galleries/users_explain/text/mathtext.py index 27aad440dcb5..7ff317804f98 100644 --- a/galleries/users_explain/text/mathtext.py +++ b/galleries/users_explain/text/mathtext.py @@ -288,7 +288,7 @@ # (not minus). # # Custom fonts -# ~~~~~~~~~~~~ +# ^^^^^^^^^^^^ # Mathtext also provides a way to use custom fonts for math. This method is # fairly tricky to use, and should be considered an experimental feature for # patient users only. By setting :rc:`mathtext.fontset` to ``custom``, diff --git a/galleries/users_explain/text/text_intro.py b/galleries/users_explain/text/text_intro.py index eccd584ce36f..54fcb00c7a86 100644 --- a/galleries/users_explain/text/text_intro.py +++ b/galleries/users_explain/text/text_intro.py @@ -238,7 +238,7 @@ # locations, and how they are labelled. # # Terminology -# ~~~~~~~~~~~ +# ^^^^^^^^^^^ # # *Axes* have an `matplotlib.axis.Axis` object for the ``ax.xaxis`` and # ``ax.yaxis`` that contain the information about how the labels in the axis @@ -254,7 +254,7 @@ # that format the tick labels. # # Simple ticks -# ~~~~~~~~~~~~ +# ^^^^^^^^^^^^ # # It is often convenient to simply define the # tick values, and sometimes the tick labels, overriding the default @@ -287,7 +287,7 @@ # %% # Tick Locators and Formatters -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # Instead of making a list of all the ticklabels, we could have # used `matplotlib.ticker.StrMethodFormatter` (new-style ``str.format()`` @@ -380,7 +380,7 @@ def formatoddticks(x, pos): # %% # Dateticks -# ~~~~~~~~~ +# ^^^^^^^^^ # # Matplotlib can accept `datetime.datetime` and `numpy.datetime64` # objects as plotting arguments. Dates and times require special From d58860f58c76d0278ce8d933afa781605bcb837c Mon Sep 17 00:00:00 2001 From: Gurudatta Shanbhag Date: Sun, 15 Oct 2023 19:39:30 +0530 Subject: [PATCH 0290/1120] [Doc]: Move Automated Tests section to workflow docs #26998 (#27093) * fixes #26998 --- doc/devel/coding_guide.rst | 50 +-------------------------- doc/devel/development_workflow.rst | 54 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst index 8e2a0aa7df4b..49f35e3ad13e 100644 --- a/doc/devel/coding_guide.rst +++ b/doc/devel/coding_guide.rst @@ -339,55 +339,7 @@ Merging Automated tests --------------- - -Whenever a pull request is created or updated, various automated test tools -will run on all supported platforms and versions of Python. - -* Make sure the Linting, GitHub Actions, AppVeyor, CircleCI, and Azure - pipelines are passing before merging (All checks are listed at the bottom of - the GitHub page of your pull request). Here are some tips for finding the - cause of the test failure: - - - If *Linting* fails, you have a code style issue, which will be listed - as annotations on the pull request's diff. - - If *Mypy* or *Stubtest* fails, you have inconsistency in type hints, which - will be listed as annotations in the diff. - - If a GitHub Actions or AppVeyor run fails, search the log for ``FAILURES``. - The subsequent section will contain information on the failed tests. - - If CircleCI fails, likely you have some reStructuredText style issue in - the docs. Search the CircleCI log for ``WARNING``. - - If Azure pipelines fail with an image comparison error, you can find the - images as *artifacts* of the Azure job: - - - Click *Details* on the check on the GitHub PR page. - - Click *View more details on Azure Pipelines* to go to Azure. - - On the overview page *artifacts* are listed in the section *Related*. - - -* Codecov and CodeQL are currently for information only. Their failure is not - necessarily a blocker. - -* tox_ is not used in the automated testing. It is supported for testing - locally. - - .. _tox: https://tox.readthedocs.io/ - -* If you know only a subset of CIs need to be run, this can be controlled on - individual commits by including the following substrings in commit messages: - - - ``[ci doc]``: restrict the CI to documentation checks. For when you only - changed documentation (this skip is automatic if the changes are only under - ``doc/`` or ``galleries/``). - - ``[skip circle]``: skip the documentation build check. For when you didn't - change documentation. - - Unit tests can be turned off for individual platforms with - - - ``[skip actions]``: GitHub Actions - - ``[skip appveyor]`` (must be in the first line of the commit): AppVeyor - - ``[skip azp]``: Azure Pipelines - - - ``[skip ci]``: skip all CIs. Use this only if you know your changes do not - need to be tested at all, which is very rare. +Before being merged, a PR should pass the :ref:`automated-tests`. If you are unsure why a test is failing, ask on the PR or in our `chat space `_ .. _pr-squashing: diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index c03dab47c759..addc4315b94a 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -426,3 +426,57 @@ thought it was. Be judicious with force-pushing. It is effectively re-writing published history, and if anyone has fetched the old commits, it will have a different view of history which can cause confusion. + +.. _automated-tests: + +Automated tests +--------------- + +Whenever a pull request is created or updated, various automated test tools +will run on all supported platforms and versions of Python. + +* Make sure the Linting, GitHub Actions, AppVeyor, CircleCI, and Azure + pipelines are passing before merging (All checks are listed at the bottom of + the GitHub page of your pull request). Here are some tips for finding the + cause of the test failure: + + - If *Linting* fails, you have a code style issue, which will be listed + as annotations on the pull request's diff. + - If *Mypy* or *Stubtest* fails, you have inconsistency in type hints, which + will be listed as annotations in the diff. + - If a GitHub Actions or AppVeyor run fails, search the log for ``FAILURES``. + The subsequent section will contain information on the failed tests. + - If CircleCI fails, likely you have some reStructuredText style issue in + the docs. Search the CircleCI log for ``WARNING``. + - If Azure pipelines fail with an image comparison error, you can find the + images as *artifacts* of the Azure job: + + - Click *Details* on the check on the GitHub PR page. + - Click *View more details on Azure Pipelines* to go to Azure. + - On the overview page *artifacts* are listed in the section *Related*. + + +* Codecov and CodeQL are currently for information only. Their failure is not + necessarily a blocker. + +* tox_ is not used in the automated testing. It is supported for testing + locally. + + .. _tox: https://tox.readthedocs.io/ + +* If you know only a subset of CIs need to be run, this can be controlled on + individual commits by including the following substrings in commit messages: + + - ``[ci doc]``: restrict the CI to documentation checks. For when you only + changed documentation (this skip is automatic if the changes are only under + ``doc/`` or ``galleries/``). + - ``[skip circle]``: skip the documentation build check. For when you didn't + change documentation. + - Unit tests can be turned off for individual platforms with + + - ``[skip actions]``: GitHub Actions + - ``[skip appveyor]`` (must be in the first line of the commit): AppVeyor + - ``[skip azp]``: Azure Pipelines + + - ``[skip ci]``: skip all CIs. Use this only if you know your changes do not + need to be tested at all, which is very rare. From 8d69190a840b37e467c199fca69c845ebc83db15 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 13 Oct 2023 11:53:40 +0200 Subject: [PATCH 0291/1120] Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. FixedAxisArtistHelpers place an axis on either the bottom, top, left, or right of an axis per (`loc`); we already infer whether it's an x-axis or a y-axis (`nth_coord`) from that and error out if there's a mismatch, so we may just as well delete the nth_coord parameter (which was already optional to start with). While at it, also expire deprecation for passing inconsistent nth_coord and loc. --- .../deprecations/27095-AL.rst | 10 +++++ .../next_api_changes/removals/27095-AL.rst | 5 +++ lib/mpl_toolkits/axisartist/axislines.py | 37 ++++++------------- .../axisartist/grid_helper_curvelinear.py | 2 + 4 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/27095-AL.rst create mode 100644 doc/api/next_api_changes/removals/27095-AL.rst diff --git a/doc/api/next_api_changes/deprecations/27095-AL.rst b/doc/api/next_api_changes/deprecations/27095-AL.rst new file mode 100644 index 000000000000..2e5b2e1ea5e5 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27095-AL.rst @@ -0,0 +1,10 @@ +*nth_coord* parameter to axisartist helpers for fixed axis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Helper APIs in `.axisartist` for generating a "fixed" axis on rectilinear axes +(`.FixedAxisArtistHelperRectilinear`) no longer take a *nth_coord* parameter, +as that parameter is entirely inferred from the (required) *loc* parameter and +having inconsistent *nth_coord* and *loc* is an error. + +For curvilinear axes, the *nth_coord* parameter remains supported (it affects +the *ticks*, not the axis position itself), but that parameter will become +keyword-only, for consistency with the rectilinear case. diff --git a/doc/api/next_api_changes/removals/27095-AL.rst b/doc/api/next_api_changes/removals/27095-AL.rst new file mode 100644 index 000000000000..7b8e5981ca79 --- /dev/null +++ b/doc/api/next_api_changes/removals/27095-AL.rst @@ -0,0 +1,5 @@ +Inconsistent *nth_coord* and *loc* passed to ``_FixedAxisArtistHelperBase`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The value of the *nth_coord* parameter of ``_FixedAxisArtistHelperBase`` and +its subclasses is now inferred from the value of *loc*; passing inconsistent +values (e.g., requesting a "top y axis" or a "left x axis") has no more effect. diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index 35717da8eaa9..e61c9a9fa8c3 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -116,17 +116,11 @@ class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): lambda self: {"left": (0, 0), "right": (1, 0), "bottom": (0, 0), "top": (0, 1)}[self._loc])) + @_api.delete_parameter("3.9", "nth_coord") def __init__(self, loc, nth_coord=None): """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" - self.nth_coord = ( - nth_coord if nth_coord is not None else - _api.check_getitem( - {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) - if (nth_coord == 0 and loc not in ["left", "right"] - or nth_coord == 1 and loc not in ["bottom", "top"]): - _api.warn_deprecated( - "3.7", message=f"{loc=!r} is incompatible with " - "{nth_coord=}; support is deprecated since %(since)s") + self.nth_coord = _api.check_getitem( + {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc) self._loc = loc self._pos = {"bottom": 0, "top": 1, "left": 0, "right": 1}[loc] super().__init__() @@ -184,12 +178,13 @@ def get_line(self, axes): class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): + @_api.delete_parameter("3.9", "nth_coord") def __init__(self, axes, loc, nth_coord=None): """ nth_coord = along which coordinate value varies in 2D, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis """ - super().__init__(loc, nth_coord) + super().__init__(loc) self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] # TICK @@ -333,6 +328,8 @@ def __init__(self, axes): super().__init__() self.axes = axes + @_api.delete_parameter( + "3.9", "nth_coord", addendum="'nth_coord' is now inferred from 'loc'.") def new_fixed_axis(self, loc, nth_coord=None, axis_direction=None, @@ -345,8 +342,7 @@ def new_fixed_axis(self, loc, axes = self.axes if axis_direction is None: axis_direction = loc - - helper = FixedAxisArtistHelperRectilinear(axes, loc, nth_coord) + helper = FixedAxisArtistHelperRectilinear(axes, loc) axisline = AxisArtist(axes, helper, offset=offset, axis_direction=axis_direction) return axisline @@ -359,7 +355,6 @@ def new_floating_axis(self, nth_coord, value, _api.warn_external( "'new_floating_axis' explicitly requires the axes keyword.") axes = self.axes - helper = FloatingAxisArtistHelperRectilinear( axes, nth_coord, value, axis_direction) axisline = AxisArtist(axes, helper, axis_direction=axis_direction) @@ -494,21 +489,11 @@ def get_children(self): return children def new_fixed_axis(self, loc, offset=None): - gh = self.get_grid_helper() - axis = gh.new_fixed_axis(loc, - nth_coord=None, - axis_direction=None, - offset=offset, - axes=self, - ) - return axis + return self.get_grid_helper().new_fixed_axis(loc, offset=offset, axes=self) def new_floating_axis(self, nth_coord, value, axis_direction="bottom"): - gh = self.get_grid_helper() - axis = gh.new_floating_axis(nth_coord, value, - axis_direction=axis_direction, - axes=self) - return axis + return self.get_grid_helper().new_floating_axis( + nth_coord, value, axis_direction=axis_direction, axes=self) class AxesZero(Axes): diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index f76fd84b55fe..d34a848780e3 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -8,6 +8,7 @@ import numpy as np import matplotlib as mpl +from matplotlib import _api from matplotlib.path import Path from matplotlib.transforms import Affine2D, IdentityTransform from .axislines import ( @@ -270,6 +271,7 @@ def update_grid_finder(self, aux_trans=None, **kwargs): self.grid_finder.update(**kwargs) self._old_limits = None # Force revalidation. + @_api.make_keyword_only("3.9", "nth_coord") def new_fixed_axis(self, loc, nth_coord=None, axis_direction=None, From 9c6db6670f490462f5e1a6d677db843408868928 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 16 Oct 2023 19:39:02 -0400 Subject: [PATCH 0292/1120] TST: Use importlib for subprocess tests This allows running pytest with any import-mode option. --- lib/matplotlib/testing/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index da21eb42c5b2..d740b625b7c1 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -122,11 +122,16 @@ def subprocess_run_helper(func, *args, timeout, extra_env=None): """ target = func.__name__ module = func.__module__ + file = func.__code__.co_filename proc = subprocess_run_for_testing( [ sys.executable, "-c", - f"from {module} import {target}; {target}()", + f"import importlib.util;" + f"_spec = importlib.util.spec_from_file_location({module!r}, {file!r});" + f"_module = importlib.util.module_from_spec(_spec);" + f"_spec.loader.exec_module(_module);" + f"_module.{target}()", *args ], env={**os.environ, "SOURCE_DATE_EPOCH": "0", **(extra_env or {})}, From 239ed51d7ac781241a9aa0d555f050012d3d362a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 16 Oct 2023 20:04:47 -0400 Subject: [PATCH 0293/1120] BLD: Fix setting FreeType build type in extension Previously, this used a macro defined on the command line to a literal, which was "stringified" with macro magic. But if the command-line definition isn't there, then you just get a stringified version of the macro name. Instead, don't do any stringification, so that it fails on error. Then pass a string as the defnition. --- lib/matplotlib/__init__.py | 16 +++++++++------- src/ft2font_wrapper.cpp | 5 +---- src/meson.build | 5 +++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 0cdfa43b40d1..53f27c46314a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1315,13 +1315,15 @@ def _init_tests(): if (ft2font.__freetype_version__ != LOCAL_FREETYPE_VERSION or ft2font.__freetype_build_type__ != 'local'): _log.warning( - f"Matplotlib is not built with the correct FreeType version to " - f"run tests. Rebuild without setting system-freetype=true in " - f"Meson setup options. Expect many image comparison failures below. " - f"Expected freetype version {LOCAL_FREETYPE_VERSION}. " - f"Found freetype version {ft2font.__freetype_version__}. " - "Freetype build type is {}local".format( - "" if ft2font.__freetype_build_type__ == 'local' else "not ")) + "Matplotlib is not built with the correct FreeType version to run tests. " + "Rebuild without setting system-freetype=true in Meson setup options. " + "Expect many image comparison failures below. " + "Expected freetype version %s. " + "Found freetype version %s. " + "Freetype build type is %slocal.", + LOCAL_FREETYPE_VERSION, + ft2font.__freetype_version__, + "" if ft2font.__freetype_build_type__ == 'local' else "not ") def _replacer(data, value): diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 7888a9c212a5..ef47a6574985 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -10,9 +10,6 @@ #include #include -#define STRINGIFY(s) XSTRINGIFY(s) -#define XSTRINGIFY(s) #s - static PyObject *convert_xys_to_array(std::vector &xys) { npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 }; @@ -1532,7 +1529,7 @@ PyMODINIT_FUNC PyInit_ft2font(void) // Glyph is not constructible from Python, thus not added to the module. PyType_Ready(PyGlyph_init_type()) || PyModule_AddStringConstant(m, "__freetype_version__", version_string) || - PyModule_AddStringConstant(m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE)) || + PyModule_AddStringConstant(m, "__freetype_build_type__", FREETYPE_BUILD_TYPE) || PyModule_AddIntConstant(m, "SCALABLE", FT_FACE_FLAG_SCALABLE) || PyModule_AddIntConstant(m, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) || PyModule_AddIntConstant(m, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) || diff --git a/src/meson.build b/src/meson.build index 6a33d2dfb12a..9cee34bfce8b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -96,6 +96,11 @@ extension_data = { 'dependencies': [ freetype_dep, numpy_dep, agg_dep.partial_dependency(includes: true), ], + 'cpp_args': [ + '-DFREETYPE_BUILD_TYPE="@0@"'.format( + freetype_dep.type_name() == 'internal' ? 'local' : 'system', + ), + ], }, '_image': { 'subdir': 'matplotlib', From 44a201076f4e96d736a2891ffa26f11791404080 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 17 Oct 2023 02:14:58 +0200 Subject: [PATCH 0294/1120] DOC: Move figure member sections one level down so that we have the sections: - Figure - SubFigure - FigureBase - Helper functions --- doc/api/figure_api.rst | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 316bd8322d32..937020afd8fc 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -8,8 +8,11 @@ :no-members: :no-undoc-members: -The Figure class -================ +Figure +====== + +Figure class +------------ .. autosummary:: :toctree: _as_gen :template: autosummary_class_only.rst @@ -19,7 +22,7 @@ The Figure class Adding Axes and SubFigures -========================== +-------------------------- .. autosummary:: :toctree: _as_gen @@ -38,7 +41,7 @@ Adding Axes and SubFigures Figure.add_subfigure Saving -====== +------ .. autosummary:: :toctree: _as_gen @@ -49,7 +52,7 @@ Saving Annotating -========== +---------- .. autosummary:: :toctree: _as_gen @@ -72,7 +75,7 @@ Annotating Figure geometry -=============== +--------------- .. autosummary:: :toctree: _as_gen @@ -90,7 +93,7 @@ Figure geometry Figure.set_dpi Subplot layout -============== +-------------- .. autosummary:: :toctree: _as_gen @@ -102,7 +105,7 @@ Subplot layout Figure.get_layout_engine Discouraged or deprecated -------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^ .. autosummary:: :toctree: _as_gen @@ -118,7 +121,7 @@ Discouraged or deprecated Figure.get_constrained_layout_pads Interactive -=========== +----------- .. seealso:: @@ -135,7 +138,7 @@ Interactive Figure.pick Modifying appearance -==================== +-------------------- .. autosummary:: :toctree: _as_gen @@ -152,7 +155,7 @@ Modifying appearance Figure.get_edgecolor Adding and getting Artists -========================== +-------------------------- .. autosummary:: :toctree: _as_gen @@ -164,7 +167,7 @@ Adding and getting Artists Figure.figimage Getting and modifying state -=========================== +--------------------------- .. seealso:: From a5cb9ef1d1550db0540334deba7f32265f1173f3 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Sun, 24 Sep 2023 02:57:04 -0600 Subject: [PATCH 0295/1120] Consolidate 3D masking and fix for Axes3D.plot() Move function to cbook Whitespace Privatize cbook._broadcast_with_masks --- lib/matplotlib/cbook.py | 39 +++++++++++++++++++ lib/matplotlib/cbook.pyi | 1 + lib/mpl_toolkits/mplot3d/axes3d.py | 28 +++---------- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 18 +++++++++ 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index b56de559532d..5e9d8b82eb5a 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1088,6 +1088,45 @@ def _combine_masks(*args): return margs +def _broadcast_with_masks(*args, compress=False): + """ + Broadcast inputs, combining all masked arrays. + + Parameters + ---------- + *args : array-like + The inputs to broadcast. + compress : bool, default: False + Whether to compress the masked arrays. If False, the masked values + are replaced by NaNs. + + Returns + ------- + list of array-like + The broadcasted and masked inputs. + """ + # extract the masks, if any + masks = [k.mask for k in args if isinstance(k, np.ma.MaskedArray)] + # broadcast to match the shape + bcast = np.broadcast_arrays(*args, *masks) + inputs = bcast[:len(args)] + masks = bcast[len(args):] + if masks: + # combine the masks into one + mask = functools.reduce(np.logical_or, masks) + # put mask on and compress + if compress: + inputs = [np.ma.array(k, mask=mask).compressed() + for k in inputs] + else: + inputs = [np.ravel(np.ma.array(k, mask=mask, + dtype=float).filled(np.nan)) + for k in inputs] + else: + inputs = [np.ravel(k) for k in inputs] + return inputs + + def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, autorange=False): r""" diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index a6ad1e54a51c..36516e368e8b 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -130,6 +130,7 @@ class GrouperView(Generic[_T]): def simple_linear_interpolation(a: ArrayLike, steps: int) -> np.ndarray: ... def delete_masked_points(*args): ... +def _broadcast_with_masks(*args, compress: bool=False) -> list[ArrayLike]: ... def boxplot_stats( X: ArrayLike, whis: float | tuple[float, float] = ..., diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 0c18bce8ebd3..84bbd763f882 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -11,7 +11,6 @@ """ from collections import defaultdict -import functools import itertools import math import textwrap @@ -1909,8 +1908,7 @@ def plot(self, xs, ys, *args, zdir='z', **kwargs): else: zs = kwargs.pop('zs', 0) - # Match length - zs = np.broadcast_to(zs, np.shape(xs)) + xs, ys, zs = cbook._broadcast_with_masks(xs, ys, zs) lines = super().plot(xs, ys, *args, **kwargs) for line in lines: @@ -2665,8 +2663,7 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, had_data = self.has_data() zs_orig = zs - xs, ys, zs = np.broadcast_arrays( - *[np.ravel(np.ma.filled(t, np.nan)) for t in [xs, ys, zs]]) + xs, ys, zs = cbook._broadcast_with_masks(xs, ys, zs) s = np.ma.ravel(s) # This doesn't have to match x, y in size. xs, ys, zs, s, c, color = cbook.delete_masked_points( @@ -2722,7 +2719,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): patches = super().bar(left, height, *args, **kwargs) - zs = np.broadcast_to(zs, len(left)) + zs = np.broadcast_to(zs, len(left), subok=True) verts = [] verts_zs = [] @@ -2988,23 +2985,8 @@ def calc_arrows(UVW): had_data = self.has_data() - input_args = [X, Y, Z, U, V, W] - - # extract the masks, if any - masks = [k.mask for k in input_args - if isinstance(k, np.ma.MaskedArray)] - # broadcast to match the shape - bcast = np.broadcast_arrays(*input_args, *masks) - input_args = bcast[:6] - masks = bcast[6:] - if masks: - # combine the masks into one - mask = functools.reduce(np.logical_or, masks) - # put mask on and compress - input_args = [np.ma.array(k, mask=mask).compressed() - for k in input_args] - else: - input_args = [np.ravel(k) for k in input_args] + input_args = cbook._broadcast_with_masks(X, Y, Z, U, V, W, + compress=True) if any(len(v) == 0 for v in input_args): # No quivers, so just make an empty collection and return early diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index f3666fffc418..4024eeddcf17 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -668,6 +668,24 @@ def test_surface3d_masked(): ax.view_init(30, -80, 0) +@check_figures_equal(extensions=["png"]) +def test_plot_scatter_masks(fig_test, fig_ref): + x = np.linspace(0, 10, 100) + y = np.linspace(0, 10, 100) + z = np.sin(x) * np.cos(y) + mask = z > 0 + + z_masked = np.ma.array(z, mask=mask) + ax_test = fig_test.add_subplot(projection='3d') + ax_test.scatter(x, y, z_masked) + ax_test.plot(x, y, z_masked) + + x[mask] = y[mask] = z[mask] = np.nan + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.scatter(x, y, z) + ax_ref.plot(x, y, z) + + @check_figures_equal(extensions=["png"]) def test_plot_surface_None_arg(fig_test, fig_ref): x, y = np.meshgrid(np.arange(5), np.arange(5)) From eca4a346ea6a4944ad0707eeba9c70119d2fa1ba Mon Sep 17 00:00:00 2001 From: Sheepfan0828 <100391900+Sheepfan0828@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:07:19 +1100 Subject: [PATCH 0296/1120] Remove datetime test for axes.pie (#27117) * Added the smoke testing for pie function * Fixed some typos and mistakes in test code. * added further comments * Remove the test for pie function --------- Co-authored-by: YiLun Fan --- lib/matplotlib/tests/test_datetime.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index ef83b837348d..72701cd2d8cd 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -229,12 +229,6 @@ def test_matshow(self): fig, ax = plt.subplots() ax.matshow(...) - @pytest.mark.xfail(reason="Test for pie not written yet") - @mpl.style.context("default") - def test_pie(self): - fig, ax = plt.subplots() - ax.pcolor(...) - @pytest.mark.xfail(reason="Test for pcolor not written yet") @mpl.style.context("default") def test_pcolor(self): From fb2240f865cf500281918d68a310689cdf419cb9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 17 Oct 2023 11:50:54 +0200 Subject: [PATCH 0297/1120] Update clabel comment. Mostly because BlockingContourLabeler stopped existing a while ago, so update that part. Also reword the rest, while at it. --- lib/matplotlib/contour.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..d8e317be7c0a 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -157,21 +157,19 @@ def clabel(self, levels=None, *, A list of `.Text` instances for the labels. """ - # clabel basically takes the input arguments and uses them to - # add a list of "label specific" attributes to the ContourSet - # object. These attributes are all of the form label* and names - # should be fairly self explanatory. + # Based on the input arguments, clabel() adds a list of "label + # specific" attributes to the ContourSet object. These attributes are + # all of the form label* and names should be fairly self explanatory. # - # Once these attributes are set, clabel passes control to the - # labels method (case of automatic label placement) or - # `BlockingContourLabeler` (case of manual label placement). + # Once these attributes are set, clabel passes control to the labels() + # method (for automatic label placement) or blocking_input_loop and + # _contour_labeler_event_handler (for manual label placement). if fmt is None: fmt = ticker.ScalarFormatter(useOffset=False) fmt.create_dummy_axis() self.labelFmt = fmt self._use_clabeltext = use_clabeltext - # Detect if manual selection is desired and remove from argument list. self.labelManual = manual self.rightside_up = rightside_up self._clabel_zorder = 2 + self.get_zorder() if zorder is None else zorder From 85008799417ff51d2623d734c982bab443f5c459 Mon Sep 17 00:00:00 2001 From: wemi3 <62965919+wemi3@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:27:28 -0700 Subject: [PATCH 0298/1120] Added documentation on getting full list of registered colormaps re: issue #26244 (#26930) * closes #26244, added documentation on getting full list of registered colormaps --- galleries/users_explain/colors/colormaps.py | 5 +++++ lib/matplotlib/cm.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/galleries/users_explain/colors/colormaps.py b/galleries/users_explain/colors/colormaps.py index a38dd2af0fc9..92b56d298976 100644 --- a/galleries/users_explain/colors/colormaps.py +++ b/galleries/users_explain/colors/colormaps.py @@ -15,6 +15,11 @@ help on creating your own colormaps, see :ref:`colormap-manipulation`. +To get a list of all registered colormaps, you can do:: + + from matplotlib import colormaps + list(colormaps) + Overview ======== diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index a2cd3788114d..3911986f3673 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -74,6 +74,11 @@ class ColormapRegistry(Mapping): Additional colormaps can be added via `.ColormapRegistry.register`:: mpl.colormaps.register(my_colormap) + + To get a list of all registered colormaps, you can do:: + + from matplotlib import colormaps + list(colormaps) """ def __init__(self, cmaps): self._cmaps = cmaps From fb0802d333af9d20571c4368d775afc4484a191b Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Tue, 17 Oct 2023 15:45:13 -0400 Subject: [PATCH 0299/1120] Make suggested modifications to test_barh in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index b035a7873083..900eb3643a21 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -77,17 +77,21 @@ def test_barbs(self): @mpl.style.context("default") def test_barh(self): mpl.rcParams["date.converter"] = 'concise' - N = 14 - base = datetime.datetime(1970, 1, 1) - indices = np.arange(N) - dates = [base + datetime.timedelta(days=(7 * i)) for i in range(N)] - widths = [datetime.timedelta(days=(7*i)) for i in range(N)] - fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout='constrained') - error = np.random.rand(N) - ax1.barh(indices, dates, xerr=error) - ax2.barh(dates, indices) - ax3.barh(dates, dates) - ax4.barh(indices, width=widths, left=base) + fig, (ax1, ax2) = plt.subplots(2, 1, layout='constrained') + birth_date = np.array([datetime.datetime(2020, 4, 10), + datetime.datetime(2020, 5, 30), + datetime.datetime(2020, 10, 12), + datetime.datetime(2020, 11, 15)]) + year_start = datetime.datetime(2020, 1, 1) + year_end = datetime.datetime(2020, 12, 31) + age = [21, 53, 20, 24] + ax1.set_xlabel('Age') + ax1.set_ylabel('Birth Date') + ax1.barh(birth_date, width=age, height=datetime.timedelta(days=10)) + ax2.set_xlim(left=year_start, right=year_end) + ax2.set_xlabel('Birth Date') + ax2.set_ylabel('Order of Birth Dates') + ax2.barh(np.arange(4), birth_date-year_start, left=year_start) @pytest.mark.xfail(reason="Test for boxplot not written yet") @mpl.style.context("default") From 027f38e4fd759ad6f0b765a153bbc181f2a9e3f5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 17 Oct 2023 16:49:01 -0400 Subject: [PATCH 0300/1120] BLD: use python3 for shebang consistent with pep-394 https://peps.python.org/pep-0394/ --- tools/generate_matplotlibrc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/generate_matplotlibrc.py b/tools/generate_matplotlibrc.py index 33d6277870da..b779187c1e0d 100755 --- a/tools/generate_matplotlibrc.py +++ b/tools/generate_matplotlibrc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Generate matplotlirc for installs. From 2d70e18573048d993293385b953920bcea2199e2 Mon Sep 17 00:00:00 2001 From: danielcobej Date: Wed, 18 Oct 2023 02:07:29 +0200 Subject: [PATCH 0301/1120] added test_axvspan --- lib/matplotlib/tests/test_datetime.py | 29 ++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 72701cd2d8cd..87e9e51afef1 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -50,11 +50,34 @@ def test_axvline(self): fig, ax = plt.subplots() ax.axvline(...) - @pytest.mark.xfail(reason="Test for axvspan not written yet") @mpl.style.context("default") def test_axvspan(self): - fig, ax = plt.subplots() - ax.axvspan(...) + mpl.rcParams["date.converter"] = 'concise' + + start_date = datetime.datetime(2023, 1, 1) + time_delta = datetime.timedelta(days=1) + + values1 = np.random.randint(1, 10, 30) + values2 = np.random.randint(1, 10, 30) + values3 = np.random.randint(1, 10, 30) + + bin_edges = [start_date + i * time_delta for i in range(31)] + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, constrained_layout=True) + + axes = [ax1, ax2, ax3] + values_list = [values1, values2, values3] + + for ax, values in zip(axes, values_list): + ax.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values + ) + for i in range(np.random.randint(1, 5)): + xmin = start_date + np.random.randint(0, 30) * time_delta + xmax = xmin + np.random.randint(1, 3) * time_delta + ax.axvspan(xmin=xmin, xmax=xmax, facecolor='green', alpha=0.5) @pytest.mark.xfail(reason="Test for bar not written yet") @mpl.style.context("default") From 74ff93b06db9b32a0379f3faefb5fc51678dc55d Mon Sep 17 00:00:00 2001 From: Jeremy Farrell Date: Tue, 17 Oct 2023 18:17:48 -0600 Subject: [PATCH 0302/1120] 26865 removing deprecations to axislines.py --- doc/api/next_api_changes/removals/26900-jf.rst | 4 ++++ lib/mpl_toolkits/axisartist/axislines.py | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26900-jf.rst diff --git a/doc/api/next_api_changes/removals/26900-jf.rst b/doc/api/next_api_changes/removals/26900-jf.rst new file mode 100644 index 000000000000..5f14c1543ad8 --- /dev/null +++ b/doc/api/next_api_changes/removals/26900-jf.rst @@ -0,0 +1,4 @@ +``passthru_pt`` +~~~~~~~~~~~~~~~ + +This attribute of ``AxisArtistHelper``\s has been removed. diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index e61c9a9fa8c3..cbcdefe38f7c 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -112,10 +112,6 @@ def _to_xy(self, values, const): class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): """Helper class for a fixed (in the axes coordinate) axis.""" - passthru_pt = _api.deprecated("3.7")(property( - lambda self: {"left": (0, 0), "right": (1, 0), - "bottom": (0, 0), "top": (0, 1)}[self._loc])) - @_api.delete_parameter("3.9", "nth_coord") def __init__(self, loc, nth_coord=None): """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" From 103ca388cb00abeb2f2dc868d4abed6d4abecb95 Mon Sep 17 00:00:00 2001 From: 0taj <89607583+0taj@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:23:54 -0400 Subject: [PATCH 0303/1120] Update developer release guide to follow conventions (#27118) * Update doc/devel/release_guide.rst * Update doc/devel/release_guide.rst Co-authored-by: hannah --- doc/devel/release_guide.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst index fe7c1022e038..2bbe589282a3 100644 --- a/doc/devel/release_guide.rst +++ b/doc/devel/release_guide.rst @@ -157,7 +157,7 @@ Update release notes -------------------- What's new -~~~~~~~~~~ +^^^^^^^^^^ *Only needed for major and minor releases. Bugfix releases should not have new features.* @@ -167,7 +167,7 @@ file :file:`doc/users/prev_whats_new/whats_new_{X}.{Y}.0.rst` and delete the ind files. API changes -~~~~~~~~~~~ +^^^^^^^^^^^ *Primarily needed for major and minor releases. We may sometimes have API changes in bugfix releases.* @@ -177,7 +177,7 @@ file :file:`doc/api/prev_api_changes/api_changes_{X}.{Y}.{Z}.rst` and delete the individual files. Release notes TOC -~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^ Update :file:`doc/users/release_notes.rst`: @@ -202,7 +202,7 @@ Update :file:`doc/users/release_notes.rst`: prev_whats_new/github_stats_X.Y.Z.rst Update version switcher -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ Update ``doc/_static/switcher.json``: From eb0ff88cde0b1b497d17ffb836d19f13f923aeae Mon Sep 17 00:00:00 2001 From: danielcobej Date: Wed, 18 Oct 2023 03:37:36 +0200 Subject: [PATCH 0304/1120] added random seed number --- lib/matplotlib/tests/test_datetime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 87e9e51afef1..fb6eedc3b5e0 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -53,6 +53,7 @@ def test_axvline(self): @mpl.style.context("default") def test_axvspan(self): mpl.rcParams["date.converter"] = 'concise' + np.random.seed(19680801) start_date = datetime.datetime(2023, 1, 1) time_delta = datetime.timedelta(days=1) From f6b55a448e5970b03deab1c943c5ab975f74a166 Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 18 Oct 2023 00:46:47 -0400 Subject: [PATCH 0305/1120] changed automated tests from subsection to section in workflow Accidentally missed this in my review on #26998 but automated tests makes more sense as a standalone then as a subsection of commit history tasks. --- doc/devel/development_workflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index addc4315b94a..50170ee4ade3 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -430,7 +430,7 @@ of history which can cause confusion. .. _automated-tests: Automated tests ---------------- +=============== Whenever a pull request is created or updated, various automated test tools will run on all supported platforms and versions of Python. From 1d1171fa04a7d1fc3c2826b593610a2f832374f0 Mon Sep 17 00:00:00 2001 From: Cameron <19280099+QuadroTec@users.noreply.github.com> Date: Wed, 18 Oct 2023 20:09:16 +1100 Subject: [PATCH 0306/1120] add test_stackplot in test_datetime.py (#27114) * add test_stackplot in test_datetime.py * Remove stackplot test with datetime y-axis * Refactor test_stackplot --- lib/matplotlib/tests/test_datetime.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index f42e0a0d04d8..276b7c179afe 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -325,11 +325,15 @@ def test_spy(self): fig, ax = plt.subplots() ax.spy(...) - @pytest.mark.xfail(reason="Test for stackplot not written yet") @mpl.style.context("default") def test_stackplot(self): - fig, ax = plt.subplots() - ax.stackplot(...) + mpl.rcParams["date.converter"] = 'concise' + N = 10 + stacked_nums = np.tile(np.arange(1, N), (4, 1)) + dates = np.array([datetime.datetime(2020 + i, 1, 1) for i in range(N - 1)]) + + fig, ax = plt.subplots(layout='constrained') + ax.stackplot(dates, stacked_nums) @pytest.mark.xfail(reason="Test for stairs not written yet") @mpl.style.context("default") From 73c0bd69cb242c35fa9b8e1f87c018ec0081e4f7 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 11 Oct 2023 21:29:13 +0200 Subject: [PATCH 0307/1120] Close all plot windows of a blocking show() on Ctrl+C Addresses the Qt part of #23385. It appears that `qapp.quit()` does not automatically close the windows of the app. We therefore do it explicitly. A unit test for this would be quite complex. Test this by hand by Ctrl+C in an interactive shell. --- lib/matplotlib/backends/qt_compat.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index d587223ab9cf..98f9f9572d69 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -163,7 +163,7 @@ def _exec(obj): @contextlib.contextmanager -def _maybe_allow_interrupt(qapp): +def _maybe_allow_interrupt(qapp_or_eventloop): """ This manager allows to terminate a plot by sending a SIGINT. It is necessary because the running Qt backend prevents Python interpreter to @@ -215,7 +215,9 @@ def _may_clear_sock(*args): def handle(*args): nonlocal handler_args handler_args = args - qapp.quit() + if hasattr(qapp_or_eventloop, 'closeAllWindows'): + qapp_or_eventloop.closeAllWindows() + qapp_or_eventloop.quit() signal.signal(signal.SIGINT, handle) try: From 3432b7ab46e2866e7d2a360e07d1c4c4f1835a8a Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Tue, 5 Sep 2023 10:16:58 +0200 Subject: [PATCH 0308/1120] DOC: Fix colLoc default - fix colLoc default - make docs in table.py conform to numpydoc - move setting default value of loc of Cell from code to signature --- .../next_api_changes/behavior/26696-SR.rst | 6 ++++ lib/matplotlib/table.py | 31 +++++++++---------- lib/matplotlib/table.pyi | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26696-SR.rst diff --git a/doc/api/next_api_changes/behavior/26696-SR.rst b/doc/api/next_api_changes/behavior/26696-SR.rst new file mode 100644 index 000000000000..231f412e426d --- /dev/null +++ b/doc/api/next_api_changes/behavior/26696-SR.rst @@ -0,0 +1,6 @@ +*loc* parameter of ``Cell`` doesn't accept ``None`` anymore +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default value of the *loc* parameter has been changed from ``None`` to ``right``, +which already was the default location. The behavior of `.Cell` didn't change when +called without an explicit *loc* parameter. diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index d42cdf878d61..345ce98ccf60 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -57,7 +57,7 @@ def __init__(self, xy, width, height, *, edgecolor='k', facecolor='w', fill=True, text='', - loc=None, + loc='right', fontproperties=None, visible_edges='closed', ): @@ -70,20 +70,21 @@ def __init__(self, xy, width, height, *, The cell width. height : float The cell height. - edgecolor : color + edgecolor : color, default: 'k' The color of the cell border. - facecolor : color + facecolor : color, default: 'w' The cell facecolor. - fill : bool + fill : bool, default: True Whether the cell background is filled. - text : str + text : str, optional The cell text. - loc : {'left', 'center', 'right'}, default: 'right' + loc : {'right', 'center', 'left'} The alignment of the text within the cell. - fontproperties : dict + fontproperties : dict, optional A dict defining the font properties of the text. Supported keys and values are the keyword arguments accepted by `.FontProperties`. - visible_edges : str, default: 'closed' + visible_edges : {'closed', 'open', 'horizontal', 'vertical'} or \ +substring of 'BRTL' The cell edges to be drawn with a line: a substring of 'BRTL' (bottom, right, top, left), or one of 'open' (no edges drawn), 'closed' (all edges drawn), 'horizontal' (bottom and top), @@ -97,8 +98,6 @@ def __init__(self, xy, width, height, *, self.visible_edges = visible_edges # Create text object - if loc is None: - loc = 'right' self._loc = loc self._text = Text(x=xy[0], y=xy[1], clip_on=False, text=text, fontproperties=fontproperties, @@ -283,7 +282,7 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs): ---------- ax : `~matplotlib.axes.Axes` The `~.axes.Axes` to plot the table into. - loc : str + loc : str, optional The position of the cell with respect to *ax*. This must be one of the `~.Table.codes`. bbox : `.Bbox` or [xmin, ymin, width, height], optional @@ -685,7 +684,7 @@ def table(ax, cellColours : 2D list of colors, optional The background colors of the cells. - cellLoc : {'left', 'center', 'right'}, default: 'right' + cellLoc : {'right', 'center', 'left'} The alignment of the text within the cells. colWidths : list of float, optional @@ -698,7 +697,7 @@ def table(ax, rowColours : list of colors, optional The colors of the row header cells. - rowLoc : {'left', 'center', 'right'}, default: 'left' + rowLoc : {'left', 'center', 'right'} The text alignment of the row header cells. colLabels : list of str, optional @@ -707,10 +706,10 @@ def table(ax, colColours : list of colors, optional The colors of the column header cells. - colLoc : {'left', 'center', 'right'}, default: 'left' + colLoc : {'center', 'left', 'right'} The text alignment of the column header cells. - loc : str, optional + loc : str, default: 'bottom' The position of the cell with respect to *ax*. This must be one of the `~.Table.codes`. @@ -718,7 +717,7 @@ def table(ax, A bounding box to draw the table into. If this is not *None*, this overrides *loc*. - edges : substring of 'BRTL' or {'open', 'closed', 'horizontal', 'vertical'} + edges : {'closed', 'open', 'horizontal', 'vertical'} or substring of 'BRTL' The cell edges to be drawn with a line. See also `~.Cell.visible_edges`. diff --git a/lib/matplotlib/table.pyi b/lib/matplotlib/table.pyi index 842c55edb5a3..0108ecd99f89 100644 --- a/lib/matplotlib/table.pyi +++ b/lib/matplotlib/table.pyi @@ -22,7 +22,7 @@ class Cell(Rectangle): facecolor: ColorType = ..., fill: bool = ..., text: str = ..., - loc: Literal["left", "center", "right"] | None = ..., + loc: Literal["left", "center", "right"] = ..., fontproperties: dict[str, Any] | None = ..., visible_edges: str | None = ... ) -> None: ... From 994abce7bff31372b4e25f5c0e6f40836d9c1727 Mon Sep 17 00:00:00 2001 From: danielcobej Date: Wed, 18 Oct 2023 15:14:12 +0200 Subject: [PATCH 0309/1120] added test_axhspan --- lib/matplotlib/tests/test_datetime.py | 32 ++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 276b7c179afe..3c5f664d7fdb 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -32,11 +32,37 @@ def test_axhline(self): fig, ax = plt.subplots() ax.axhline(...) - @pytest.mark.xfail(reason="Test for axhspan not written yet") @mpl.style.context("default") def test_axhspan(self): - fig, ax = plt.subplots() - ax.axhspan(...) + mpl.rcParams["date.converter"] = 'concise' + np.random.seed(19680801) + + start_date = datetime.datetime(2023, 1, 1) + time_delta = datetime.timedelta(days=1) + + values = np.random.randint(1, 10, 30) + bin_edges = [start_date + i * time_delta for i in range(31)] + + fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True) + + ax1.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values) + + for i in range(np.random.randint(1, 5)): + ymin = np.random.randint(1, 8) + ymax = ymin + np.random.randint(1, 3) + ax1.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) + + ax2.hist( + [start_date + i * time_delta for i in range(30)], + bins=bin_edges, + weights=values) + + y_values = np.unique(values) + for i, y in enumerate(y_values[::2]): + ax2.axhspan(ymin=y, ymax=y+1, facecolor='green', alpha=0.5) @pytest.mark.xfail(reason="Test for axline not written yet") @mpl.style.context("default") From a589856d2c1f8a5f8236808a4098a4efb341a17c Mon Sep 17 00:00:00 2001 From: Seohyeon Lee <96499556+seohyeon-lee-2025@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:13:26 -0700 Subject: [PATCH 0310/1120] Add scatter test for datetime units (#26882) * test_axes added 3 subplots with datetime array on the x axis, y axis, and both axes. * incorporated issues in previous PR * deleted whitespaces after end of line * got rid of space in 311 * Simplified datetime array * fixed mistake in previous commit * Update test_datetime.py I needed to rotate axis labels because they were still clogged after I reduced array length * Update test_datetime.py eliminated unused variable according to feedback ('lims') --- lib/matplotlib/tests/test_datetime.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 276b7c179afe..c9455d450b70 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -301,11 +301,27 @@ def test_quiverkey(self): fig, ax = plt.subplots() ax.quiverkey(...) - @pytest.mark.xfail(reason="Test for scatter not written yet") @mpl.style.context("default") def test_scatter(self): - fig, ax = plt.subplots() - ax.scatter(...) + mpl.rcParams["date.converter"] = 'concise' + base = datetime.datetime(2005, 2, 1) + dates = [base + datetime.timedelta(hours=(2 * i)) for i in range(10)] + N = len(dates) + np.random.seed(19680801) + y = np.cumsum(np.random.randn(N)) + fig, axs = plt.subplots(3, 1, layout='constrained', figsize=(6, 6)) + # datetime array on x axis + axs[0].scatter(dates, y) + for label in axs[0].get_xticklabels(): + label.set_rotation(40) + label.set_horizontalalignment('right') + # datetime on y axis + axs[1].scatter(y, dates) + # datetime on both x, y axes + axs[2].scatter(dates, dates) + for label in axs[2].get_xticklabels(): + label.set_rotation(40) + label.set_horizontalalignment('right') @pytest.mark.xfail(reason="Test for semilogx not written yet") @mpl.style.context("default") From 42c4ef04cdf64fc0618bcb9d1cd30d11fe0e5dbd Mon Sep 17 00:00:00 2001 From: danielcobej Date: Wed, 18 Oct 2023 23:09:25 +0200 Subject: [PATCH 0311/1120] switched x and y axis --- lib/matplotlib/tests/test_datetime.py | 28 +++++++++------------------ 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 3c5f664d7fdb..9a359d6c33b7 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -40,29 +40,19 @@ def test_axhspan(self): start_date = datetime.datetime(2023, 1, 1) time_delta = datetime.timedelta(days=1) - values = np.random.randint(1, 10, 30) - bin_edges = [start_date + i * time_delta for i in range(31)] - - fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True) - - ax1.hist( - [start_date + i * time_delta for i in range(30)], - bins=bin_edges, - weights=values) + fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True, figsize=(10, 8)) + ax1.set_ylim(start_date, start_date + 29*time_delta) for i in range(np.random.randint(1, 5)): - ymin = np.random.randint(1, 8) - ymax = ymin + np.random.randint(1, 3) + ymin = start_date + np.random.randint(0, 30) * time_delta + ymax = ymin + np.random.randint(1, 3) * time_delta ax1.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) - ax2.hist( - [start_date + i * time_delta for i in range(30)], - bins=bin_edges, - weights=values) - - y_values = np.unique(values) - for i, y in enumerate(y_values[::2]): - ax2.axhspan(ymin=y, ymax=y+1, facecolor='green', alpha=0.5) + ax2.set_ylim(start_date, start_date + 29*time_delta) + for i in range(0, 30, 2): + ymin = start_date + i * time_delta + ymax = ymin + time_delta + ax2.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) @pytest.mark.xfail(reason="Test for axline not written yet") @mpl.style.context("default") From 3783494358d15a35fe63e9607a84cf1d57cbd18f Mon Sep 17 00:00:00 2001 From: Junpei Ota Date: Thu, 19 Oct 2023 02:19:32 -0400 Subject: [PATCH 0312/1120] Added smoke test for Axes.text --- lib/matplotlib/tests/test_datetime.py | 28 ++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index f42e0a0d04d8..5d9ca3bb8f18 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -355,11 +355,33 @@ def test_streamplot(self): fig, ax = plt.subplots() ax.streamplot(...) - @pytest.mark.xfail(reason="Test for text not written yet") @mpl.style.context("default") def test_text(self): - fig, ax = plt.subplots() - ax.text(...) + mpl.rcParams["date.converter"] = 'concise' + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + + limit_value = 10 + font_properties = {'family': 'serif', 'size': 12, 'weight': 'bold'} + test_date = datetime.datetime(2023, 10, 1) + + x_data = np.array(range(1, limit_value)) + y_data = np.array(range(1, limit_value)) + + x_dates = np.array( + [datetime.datetime(2023, 10, n) for n in range(1, limit_value)] + ) + y_dates = np.array( + [datetime.datetime(2023, 10, n) for n in range(1, limit_value)] + ) + + ax1.plot(x_dates, y_data) + ax1.text(test_date, 5, "Inserted Text", **font_properties) + + ax2.plot(x_data, y_dates) + ax2.text(7, test_date, "Inserted Text", **font_properties) + + ax3.plot(x_dates, y_dates) + ax3.text(test_date, test_date, "Inserted Text", **font_properties) @pytest.mark.xfail(reason="Test for tricontour not written yet") @mpl.style.context("default") From 0762613cf07dcace06cffdae731b681e0a728dea Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:36:56 -0600 Subject: [PATCH 0313/1120] Apply suggestions from code review Co-authored-by: Elliott Sales de Andrade Co-authored-by: Kyle Sunden --- lib/matplotlib/cbook.py | 5 ++--- lib/matplotlib/cbook.pyi | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 5e9d8b82eb5a..b0cde743482b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1113,14 +1113,13 @@ def _broadcast_with_masks(*args, compress=False): masks = bcast[len(args):] if masks: # combine the masks into one - mask = functools.reduce(np.logical_or, masks) + mask = np.logical_or.reduce(masks) # put mask on and compress if compress: inputs = [np.ma.array(k, mask=mask).compressed() for k in inputs] else: - inputs = [np.ravel(np.ma.array(k, mask=mask, - dtype=float).filled(np.nan)) + inputs = [np.ma.array(k, mask=mask, dtype=float).filled(np.nan).ravel() for k in inputs] else: inputs = [np.ravel(k) for k in inputs] diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index 36516e368e8b..c60ae0781af1 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -130,7 +130,7 @@ class GrouperView(Generic[_T]): def simple_linear_interpolation(a: ArrayLike, steps: int) -> np.ndarray: ... def delete_masked_points(*args): ... -def _broadcast_with_masks(*args, compress: bool=False) -> list[ArrayLike]: ... +def _broadcast_with_masks(*args: ArrayLike, compress: bool = ...) -> list[ArrayLike]: ... def boxplot_stats( X: ArrayLike, whis: float | tuple[float, float] = ..., From 585ba9f4f0e6a298bc521762cd054298b34696e1 Mon Sep 17 00:00:00 2001 From: Matthew Morrison Date: Thu, 19 Oct 2023 16:19:06 -0400 Subject: [PATCH 0314/1120] Fixes to message about classic notebooks. Simplifying other text in the section --- .../users_explain/figure/interactive.rst | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index a32d1a1eb847..1e82d37f10f4 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -7,7 +7,7 @@ .. _interactive_figures: =================== -Interactive figures +Interactive Figures =================== When working with data, interactivity can be invaluable. The pan/zoom and @@ -36,11 +36,16 @@ that include interactive tools, a toolbar, a tool-tip, and `.pyplot.subplots` Creates a new `.Figure` and fills it with a grid of `~.axes.Axes` -`.pyplot` has a notion of "The Current Figure" which can be accessed -through `.pyplot.gcf` and a notion of "The Current Axes" accessed -through `.pyplot.gca`. Almost all of the functions in `.pyplot` pass -through the current `.Figure` / `~.axes.Axes` (or create one) as -appropriate. +`.pyplot.gcf` + Get the current `.Figure`. If there is current no figure on the pyplot figure + stack, a new figure is created + +`.pyplot.gca` + Get the current `~.axes.Axes`. If there is current no Axes on the Figure, + a new one is created + +Almost all of the functions in `.pyplot` pass through the current `.Figure` / `~.axes.Axes` +(or create one) as appropriate. Matplotlib keeps a reference to all of the open figures created via `pyplot.figure` or `pyplot.subplots` so that the figures will not be garbage @@ -51,14 +56,13 @@ collected. `.Figure`\s can be closed and deregistered from `.pyplot` individuall .. seealso:: For more discussion of Matplotlib's event system and integrated event loops: - - :ref:`interactive_figures_and_eventloops` - :ref:`event-handling` .. _ipython-pylab: -IPython integration +IPython Integration =================== We recommend using IPython for an interactive shell. In addition to @@ -189,7 +193,7 @@ the GUI main loop in some other way. .. warning:: - Using `.Figure.show` it is possible to display a figure on + Using `.Figure.show`, it is possible to display a figure on the screen without starting the event loop and without being in interactive mode. This may work (depending on the GUI toolkit) but will likely result in a non-responsive figure. @@ -211,8 +215,7 @@ Interactive navigation .. image:: ../../../_static/toolbar.png All figure windows come with a navigation toolbar, which can be used -to navigate through the data set. Here is a description of each of -the buttons at the bottom of the toolbar +to navigate through the data set. .. image:: ../../../../lib/matplotlib/mpl-data/images/home_large.png @@ -221,20 +224,19 @@ the buttons at the bottom of the toolbar .. image:: ../../../../lib/matplotlib/mpl-data/images/forward_large.png The ``Home``, ``Forward`` and ``Back`` buttons - These are akin to a web browser's home, forward and back controls. + These are similar to a web browser's home, forward and back controls. ``Forward`` and ``Back`` are used to navigate back and forth between previously defined views. They have no meaning unless you have already navigated somewhere else using the pan and zoom buttons. This is analogous to trying to click ``Back`` on your web browser before visiting a new page or ``Forward`` before you have gone back to a page -- - nothing happens. ``Home`` always takes you to the - first, default view of your data. Again, all of these buttons should - feel very familiar to any user of a web browser. + nothing happens. ``Home`` takes you to the + first, default view of your data. .. image:: ../../../../lib/matplotlib/mpl-data/images/move_large.png The ``Pan/Zoom`` button - This button has two modes: pan and zoom. Click the toolbar button + This button has two modes: pan and zoom. Click the ``Pan/Zoom`` button to activate panning and zooming, then put your mouse somewhere over an axes. Press the left mouse button and hold it to pan the figure, dragging it to a new position. When you release it, the @@ -244,8 +246,8 @@ The ``Pan/Zoom`` button the right mouse button to zoom, dragging it to a new position. The x axis will be zoomed in proportionately to the rightward movement and zoomed out proportionately to the leftward movement. - The same is true for the y axis and up/down motions. The point under your - mouse when you begin the zoom remains stationary, allowing you to + The same is true for the y axis and up/down motions (up zooms in, down zooms out). + The point under your mouse when you begin the zoom remains stationary, allowing you to zoom in or out around that point as much as you wish. You can use the modifier keys 'x', 'y' or 'CONTROL' to constrain the zoom to the x axis, the y axis, or aspect ratio preserve, respectively. @@ -257,9 +259,8 @@ The ``Pan/Zoom`` button .. image:: ../../../../lib/matplotlib/mpl-data/images/zoom_to_rect_large.png -The ``Zoom-to-rectangle`` button - Click this toolbar button to activate this mode. Put your mouse somewhere - over an axes and press a mouse button. Define a rectangular region by +The ``Zoom-to-Rectangle`` button + Put your mouse somewhere over an axes and press a mouse button. Define a rectangular region by dragging the mouse while holding the button to a new location. When using the left mouse button, the axes view limits will be zoomed to the defined region. When using the right mouse button, the axes view limits will be @@ -268,8 +269,8 @@ The ``Zoom-to-rectangle`` button .. image:: ../../../../lib/matplotlib/mpl-data/images/subplots_large.png The ``Subplot-configuration`` button - Use this tool to configure the appearance of the subplot: - you can stretch or compress the left, right, top, or bottom + Use this button to configure the appearance of the subplot. + You can stretch or compress the left, right, top, or bottom side of the subplot, or the space between the rows or space between the columns. @@ -313,7 +314,7 @@ Preserve aspect ratio hold **CONTROL** when panning/zooming with mo .. _other-shells: -Other Python prompts +Other Python Prompts ==================== Interactive mode works in the default Python prompt: @@ -325,7 +326,7 @@ Interactive mode works in the default Python prompt: >>> plt.ion() >>> -however this does not ensure that the event hook is properly installed +However, this does not ensure that the event hook is properly installed and your figures may not be responsive. Please consult the documentation of your GUI toolkit for details. @@ -333,17 +334,7 @@ documentation of your GUI toolkit for details. .. _jupyter_notebooks_jupyterlab: Jupyter Notebooks / JupyterLab ------------------------------- - -.. note:: - - To get the interactive functionality described here, you must be - using an interactive backend. The default backend in notebooks, - the inline backend, is not. `~ipykernel.pylab.backend_inline` - renders the figure once and inserts a static image into the - notebook when the cell is executed. Because the images are static, they - cannot be panned / zoomed, take user input, or be updated from other - cells. +============================== To get interactive figures in the 'classic' notebook or Jupyter lab, use the `ipympl `__ backend @@ -356,7 +347,7 @@ If ``ipympl`` is installed use the magic: to select and enable it. -If you only need to use the classic notebook, you can use +If you only need to use the classic notebook (i.e. `notebook<7`), you can use .. sourcecode:: ipython @@ -365,8 +356,18 @@ If you only need to use the classic notebook, you can use which uses the `.backend_nbagg` backend provided by Matplotlib; however, nbagg does not work in Jupyter Lab. +.. note:: + + To get the interactive functionality described here, you must be + using an interactive backend. The default backend in notebooks, + the inline backend, is not. `~ipykernel.pylab.backend_inline` + renders the figure once and inserts a static image into the + notebook when the cell is executed. Because the images are static, they + cannot be panned / zoomed, take user input, or be updated from other + cells. + GUIs + Jupyter -^^^^^^^^^^^^^^ +-------------- You can also use one of the non-``ipympl`` GUI backends in a Jupyter Notebook. If you are running your Jupyter kernel locally, the GUI window will spawn on From 8325676e0d5b60b3175388404beaf2cfc5f776c5 Mon Sep 17 00:00:00 2001 From: Matthew Morrison Date: Thu, 19 Oct 2023 16:23:00 -0400 Subject: [PATCH 0315/1120] Adding note about classic notebook to figure_intro.rst --- galleries/users_explain/figure/figure_intro.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index 000828bdee0b..c126b1bcf4c0 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -73,6 +73,13 @@ other than the default "inline" backend, you will likely need to use an ipython .. seealso:: :ref:`interactive_figures`. +.. note:: + + If you only need to use the classic notebook (i.e. `notebook<7`), + you can use: + + .. sourcecode:: ipython + Standalone scripts and interactive use -------------------------------------- From 5cf3da9a1f4086ab6d9e24a0a591701326b36850 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 19 Oct 2023 19:00:20 -0500 Subject: [PATCH 0316/1120] Add test for disconnected paths, remove invalidated paths --- lib/matplotlib/contour.py | 5 ++++ .../contour_disconnected_segments.png | Bin 0 -> 9795 bytes lib/matplotlib/tests/test_contour.py | 23 ++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index adb72ef46017..ec343763cce0 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -350,6 +350,11 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing= taken into account when breaking the path, but not when computing the angle. """ if hasattr(self, "_old_style_split_collections"): + vis = False + for coll in self._old_style_split_collections: + vis |= coll.get_visible() + coll.remove() + self.set_visible(vis) del self._old_style_split_collections # Invalidate them. xys = path.vertices diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb700e09de290f6c07c280904551e2b25114382 GIT binary patch literal 9795 zcmeHtcTkhr{&&<>)`IAI(FGLT6-A{9C_SLEiii;RA{~h=O=^VD5+Jdxx|W4C2uO{J zf^?*XP!bfS7ZF3~ks1Od0g@2Xe<%Cfz4QKk-+5==xpQZD9+-#cyy!DUBV z#citFAP|V6-G!h234v_934v_*`t26*l5h@aqx+{5sHWk z4f^F^jDI9D2oY|iXQp>T&(A;R|dkx5RrlUzngo)!3S0*ol`L{hcnq^SOW9-q~fiGe2)*US8gv?G^(OYSCj`DlVTk-n{um ziT}@g&kgR~CEjq={bI-qyRaV|QaGYRyUukE;c8)@*@6lCY*h0UN^AdthbJuec$%@D0$zAbDcjpFRwEq-){kf0-8tvck=l|zMlP?dV z-G+qqb@J#A-R$!CgCV8VXAZVI`+j@UQpsqaR1x<6ZuFdlr5vi<9~Bk#DlsBnsN`mV z&3)uCYB-(TH&b}Oz`yfVP=+B2J=YW78*g20bN&Fq)_5%Hj`Hf_#xB>Zr7p$J(4P>L z&!2Ii)SNUiDR}YX#X`ha#gV-=v$-LxJ|2A>r=VrEn%}oLT8_x99jY+eMo>@NedvDR z09eJEFr>86hmn?)lw>JremaKiNmMcXMY0s%%^H{H>NlOxhru!@l+oaxmI1R&U;D^J z2v3bNiOy@X8E+K(eyp3%?rr1#GF?c+$1Oj>cuP)w^n3V;%&b=06I4}JiYBPA!#=dI zO8ygB_F(!xupb84k4G*h&SQ>TeeD4)kjYcC@Li@H-ER=-mN`N45%OY}pBG$*kL9*u zWwO;SHmrRU;ZkoG2i~zlGqqH}thCrmpnD0rz1`MOD$(ImW?F$(td#$HUuOEc_!L$B znFB#3ceiJ3_#R%dQhU!n$q_zn{f77G>yjF8-!+x0kTnYotoSz9bd9#L!ZY+VSF^ly z&B2tPW*M~zcNGn)RK}b?P!%sX^`M6Fx*{A@DMNY&F~hJBc{i}L*g_VF&rUro;d~;K zu=kpf)6@fJ)xqKiy+mQ!T;9t0WSYoN&q_i^am%zSTdWQtzc3W&@14rqo9~@ky&HkI z106`nbzd!<;E!`}uV<>rx0<{ zlAtQ&blqrVi-0iukq%!#Cp16_otQRuDnIc<(Lv}P2>}UtL5*K4q$4FL!ra^nA#fr) zO%r>SDf+6#?@SUQ(n%5_i9w}ODE>|P(N*39^}~!iqp?uw>TOp;W`9PG_Sy2Xy^cAS zhqlwn*#+vEncx!EvyJM4E>nM)=^!5XPX`$NVs%!Kd zM_+yn^8B)fl}cebmQa&qa^50`LaX&S|9F2I@QAup!yq#59%A?@MWL+r3n%nMLepb2 z&L8_D7H$b7(Mo>rQG1ZG-<9a^9wg6zE2?x`l6iP?X!YR@KHKMa=Ez$HN|t zMD|hm{@`^uVZ`KmOHCEqpds+fTYGQjVi^eL#uH(Vxw&6Z^XoN~_wEC8jgv&R&`R$l zOXl+>>4TgB0y|6LXjj`*?MDWc40jZ*6F&D(JTWShMvjb*(ztwS;r;wMRN_? ztAw9le0O~1J=cslSwJB9INunBn)fMJAHSfyMBaRjFm%&xcp|ihJ)T$FA#IaQQ%K7G zXE?VnWmx8McCEf*5M$qOLPZQVYIan_hG!cDs^j>{n7;3n)ILLNJaoYiaBpF8ao5KJ z*^dNxL~O5YnqQ>Oj2_uXpigX7nyW`5P!i(pJ8G5uXXBN$6_?HKsJ|yx@%Cgku*N)B zm&1}{prn_sqWbC7yQM@Sa1EW-SiiV21v}z1uphbhVmQ37^zPSfQBqg^o*A0d0NyY~ zt7P3o#?+U`Ua+huby&4{fg2`|1~a`~@3DY?*RDM$!6Mf?&@ zT24~086oIM4H0L`#Jx4dg?Q?0>gG~A+>-{9c3XDh^$EcQ!PRoSXtrj%Re0mqwQSMH z?VLx$(`fMvKGkKjR(7mCtKnVILG5G-0kX7LZCBUm%fZ?)Bu~R|vhe}Jjj0yBq&^Jx z8$T9I`y3Y(gVfUy6?=(Nvg*r>#~C)G^m!wtp5IFEJIf~_scCeYw26<>vg0lq4@X|7 zOzYH`cq)$t@O3%wU@U*0P#>%U!=w5zqXFb;es_|H zC#H~I8^pI}dnL4{5>a9%V}9Ne5k|F{NIwL#FjYf@zYYu%3xe1hJZ&9W-96B`iQ@v zg{O4bZi{i*6TIbe&kmKhn2#wuojw@q2mFN8-u+YK3A@z?3H+yb) zVH3Ok2bn?rP>Y8rOPrJtB;IO=8`W#lhCwZP8VTqA+S;>Y0Geh;vZHGH$FN7*x!zmhe&yPI4 zb^-U;LV$ak@ViH;OBW1A(zFAWCcgfMRhsF-<2zM@U+;CtiHE%;chrj170`z_e5*Y? z26|Ba^7Ih!DZ(@4+Q&USo%@NBn&akS=}a3ceU+k@IC>YX~}2%;zz9)Ln~sy3M}K3hfjGn23F~0%Oa^^Pj9}~ zg7_Rp&-At-7yDS^tdwDivKQ`-RC352$-I>zZ3~R-I!7)=XQ#5#9$`{clC85&QBj^d zg?uGfsAKY%0vJAf`A+Ynf+ODIQA;#++Bx}~riV>RC7R4fqnwsKZ6Iqb!jl^l?>lJc zzM-Q>JvJfdJ2b$)qDGY6xt%W`YI&gcHdP+omotcZ*!)5ZtFuWh=f~xM2`Q_`aA9aX zMbp|e!s|)#$7PqTr`_zzEa+*VaX>@LpeJnck~x#r*8}&h8(>=i3P1+*_LO52oG8;J zC#mB3{BBf0xcs?>V9Clkd0_eg2aj*h3{mQSYByjsnf<^yc~jFp$gio_CbH)q%g}TS zFVJj1ohi>F4ZF3{d>d=5fl&Vh^V0RTN3e}J*^#cSR>7&8^XP=BfY_Q+dVzqR~x z_sxpi5(cRtvZZbmr>Q0WI>cp;62Sb)0JFq2v6;@aCOrU2%SUdxJl_}C^c0RkoW;#N zt5R~HVQN!eQwVTc_WB*)6iup(J1AyI3*-@R1p@Ulz+0HOsk$%0HA|=u3YD`W!W;A} z??8GoNlP9ojg=LrtSva5MQ!drn3y?dl)q0%?N9`#5zCD)QZJwmN77^cl3QZ!KxQbx z_NN1YQDl2t-2w7`TNG#AV)cVYZl`KUS?;uSak;3CkqYv;K3r%qIradB>D&%NRSe~G zOT4h&^vyfkeutK<^Y3DclVq*C-6dhgvk&6&-97uH`3n)xZTA2hbYmI%D*Odc)B%8s zR8D^ANuU*l#f%zSEEySsm@|?-?J8CeB&uOMXM;e;iDfr9oM#Ekz{E9M6x}Usv(cAv zeq&g%4mN|Z05|;xkqY@$dFG`PHW!8TFytOk^XERulW)sn&Dz3s1EPUjX31Or^02@i zBd_=WTk#A6u`j2B(B$OYlgDjnZ-1Wa3F^38;Qcet^ph{pMm~*Rf~%HQYo!?HymJcpPQC` zRS6Bp&AfP!;`$&q@j3$0XR}93%T6k2<~v*V6!~V26S~nhR0msIUH4KKQ#V zc@8|b3c8Y6DHEI8Ex8^1!a zg+;C?H>!YGzaZNn;S94ia$a%#xW`g*9!p)uV;EO6*5ffY2ier3QMW+p;?#Xs(Nz&IhC?v!5j;d0^r`o6_T&HUn-N z*}|r zbwdTD#48J~8h_YgL~-F{F$+X(^pR|&eSkw&O7y*woPI5)E6-HBdp_m7e~#i{XGsv! z@Xty%usDVpzKQ~M+=(o_DefX9m!S90uY7Z`L>V}&c!;+8c%VuXm1VZ=r zV$ITX6P@8S{yCdUz&V^jbu$4~ylOH) z%@1%bFW&f~+?I7SCB=qdT;*i`BE4BZ;SI=cS9!_Y;TCqB%u;&Ijw_X{Tyy388?tO zqKjs-qO(dB{Q)+SU-wyOYYIB(Z3Vp9LGXTt9PkEtR-2A4k^G+!I7687(yE?t_C-J^ ziOVNVcEfRU6p58F-)nZ9kx8F~)-kpy!@g;H1e)8@A1T(E?;P%|%IVpZ8ubcTs1dJU zB_nPHk)jF8tquVBa=1XDP@+pWIyTM|GKBMw!L+Ed^;U{}$t5OHwT6U*wnoW#7O;qaZCu~nu)x{$4>HY?MJG(sb zpd9D-2OjX?nWkC@OKb)TU{^8xMGKH9FtsH+11uXJpFB&9Ll`` zq;abZc}cjom=#|})W_CMz9_OS@&bLTE=AuCw`3GRIN6k#_fV^7;*+j%=Y+308*9p% zvG+2|otaqF=w@RQvGT!v7}@-fu-!Lj&p^(eyVq28EDyFnRa3A+*K2<{SV@!dl&IFv zY}x1^H@Ph04>>|B79!A(0Vg^tSrpNqSZL@=74DuZ4U-SJfJ@-Mk{B-KILJMe4*7MP zypOrZOIj^OGaSKSzjdef_O6EGasAql{uFlzNz$Kje%!5jhH=wplwlO)j)}O-=kXN? z+>EC$<{2D=J^RF3N8Y1ps+<-}{+E)Hs&^h{;-{O6Kvg*f%%D0{ao$n5vcec3z*u!+ zRzf(vd^ST1Uyd$GeF;=GIk9bRl)MtpaHa>ubb$)VKSkR-{i{53CfUIkIe0o<3$KAU zkVX9>C&1PE;pOP+)G6>0ee@Wq2T*8oxr|XQEy?;iAQ^cJZ=bss7n>E%=!O8P-`CeS zGb>B3{_{+^zocL|ZoTI7WFv&5J-`8#Eu>LDKK(7wmu5)VAdD!IRXa{AhG9eQqUoX37Zj#M)Z)3(jvEtBNxS+%M1N zpwtHUH6UF8S>aB`jfoFq*y)x+O0av#4I9!L2iw(-#kf+Y`@4p*+ndARZg^|ilWU+Q zlXM{Exm>UA8Q4Ce65Y*Gi5^pT#}0knREKKc<9@Eo-^z&3lyy82eKe^RIA zcm;3GhgSlTN!hWj=>1;=Sb0)zS4HclQ$Qbra^|WvI~H9w$Btneya^YL1q5ujJkgTJ z$5GlTrv|;C3JH@of+6}^>`}I^$EV5@WbwqWUV@yEsvFWSJDhIp#E)&+k_U1=c04zH zII46lC*Gb#4*DmR(i@@}-ag?Q)AeS0t-Uo%sW9%M%i({C8K9G`HiWRce&rmQd*P|U zK#Oaf-z6rGL)CV`%inL6y_NBs2Z&!o(r2Yv=B}p{N(Qa-VBe?SAzPX&-We>3t9Q2P z8WRx3WK3!ZCdzkj(`Qsfdjo&`h;Of@rmR|$uMFd3vrdJ@gs(G7i7U59fmB6cs(bp& zU@Rcm`Q7Jgd?LT!>hAovpDHayUpPc*!IaNp`Xiu~EBCt)Se(9jh{9K@s?ZY0bn_@_VBSxph(t3qR z3DC;OBZU7|K>w7HN4kSGwA22Q0kl0q-v&%gl5vBji*QpOA{9iH zRpNqSLh*~?sF~%*Lufak1Lgt++LCD2%(U`q`f>dNnwy@2KxB$eT z`N*tWuT6MQ7%JFKWg zb;MH%c^IKz1*DmL$6RT>HNP;qJ8qzvc5VIZ<2c@~)%?*NefhvJlF2*pSoS-h7jPOv zG}?LeridDLjmA&eTTa7#{+9VEwQqFEqG&qtb~zB)u9YrD4C$f#Ctm{Ms_UfHH(!6Z zou<~f^4wKvH>v)+laX%5W1#}|A79bscpVArS@1fxB=ot4p0hNHeb5K@?V(h44B8m| zvN#E}n+-%B&O#ol)WKodI<$1H|Il9<(ywL-cvM*m8}6wr_LL}ayDDN&HGwSy8Ib6( z-Oe0g#PQf+1toHx-ee^ATmtX`5myGhy8UmjrU0*EW0?~7KuL|c665#%Y54@iP+s^C zHhqM;u!sODgV=jCAud=>cjU({F~U!4E~5FrWnRd0{#%)&OKGf<0_3a4cQ+xBySx9r z8^e_cX|f9m2?3|l4OWPtpu!ufkeh_oZC?$)w}=Tgs|sRUNd9xog?*O?f#CP!WnR1QPYgrupAr-oLr4 z|Hs4rYqWnO-2Q8{|9hi_hdbh`tE;b^`VLa@*n1Q~4X$~pRhR_v+Tz=m)WayiOYgO1 zWo90KwgobuL`2f%62^kT@t<9_&9LeHv~oyqmWh7LJ;koaDn<`SZhClleE#y~DUc%a z|2(dBQ7s=`)VoDxMyEgj>-!}3`b|%320e-wB98lPzM=AkN-ZFfq>9NV#>RVj`{<_> z6&1Z>&or_s Date: Tue, 10 Oct 2023 11:32:30 -0400 Subject: [PATCH 0317/1120] added info for getting compilation output from meson on autorebuild added force MSVC to troubleshooting added meson-python to intersphinx Co-authored-by: Kyle Sunden Co-authored-by: Elliott Sales de Andrade --- doc/conf.py | 1 + doc/devel/development_setup.rst | 25 +++++++++++++++++++------ doc/devel/troubleshooting.rst | 20 ++++++++++++++++++++ pyproject.toml | 3 +++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c2cdadf0ec24..b817d52ff62e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -207,6 +207,7 @@ def _check_dependencies(): 'scipy': ('https://docs.scipy.org/doc/scipy/', None), 'tornado': ('https://www.tornadoweb.org/en/stable/', None), 'xarray': ('https://docs.xarray.dev/en/stable/', None), + 'meson-python': ('https://meson-python.readthedocs.io/en/stable/', None) } diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 0e7db431163f..cad0d3084f8a 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -164,12 +164,25 @@ command :: The 'editable/develop mode' builds everything and places links in your Python environment so that Python will be able to import Matplotlib from your development source directory. -This allows you to import your modified version of Matplotlib without re-installing after -every change. Note that before the merging of the `Meson port -`_, this is only true for ``*.py`` -files. If you change the C-extension source based on a commit before the change to the -Meson build system (which might also happen if you change branches), you will have to -re-run the above command. +This allows you to import your modified version of Matplotlib without having to +re-install after changing a ``.py`` or compiled extension file. + +When working on a branch that does not have Meson enabled, meaning it does not +have :ghpull:`26621` in its history (log), you will have to reinstall from source +each time you change any compiled extension code. + +Build options +------------- +If you are working heavily with files that need to be compiled, you may want to +inspect the compilation log. This can be enabled by setting the environment +variable :envvar:`MESONPY_EDITABLE_VERBOSE` or by setting the ``editable-verbose`` +config during installation :: + + python -m pip install --no-build-isolation --config-settings=editable-verbose=true --editable . + +For more information on installation and other configuration options, see the +Meson Python :external+meson-python:ref:`editable installs guide `. + Verify the Installation ======================= diff --git a/doc/devel/troubleshooting.rst b/doc/devel/troubleshooting.rst index 77c1b242399b..74ce81b2da00 100644 --- a/doc/devel/troubleshooting.rst +++ b/doc/devel/troubleshooting.rst @@ -43,3 +43,23 @@ unlink this file. Multiple versions of Matplotlib can be linked to the same DLL, for example a development version installed in a development conda environment and a stable version running in a Jupyter notebook. To resolve this error, fully close all running instances of Matplotlib. + +Windows compilation errors +========================== +If the compiled extensions are not building on Windows due to errors in linking to +Windows' header files, for example ``../../src/_tkagg.cpp:133:10: error: 'WM_DPICHANGED' was not declared in this scope``, +you should check which compiler Meson is using: + +.. code-block:: bat + + Build type: native build + Project name: matplotlib + Project version: 3.9.0.dev0 + C compiler for the host machine: cc (gcc 7.2.0 "cc (Rev1, Built by MSYS2 project) 7.2.0") + C linker for the host machine: cc ld.bfd 2.29.1 + C++ compiler for the host machine: c++ (gcc 7.2.0 "c++ (Rev1, Built by MSYS2 project) 7.2.0") + C++ linker for the host machine: c++ ld.bfd 2.29.1 + +Our :ref:`dependencies ` documentation lists the minimum header +version if you intended to use ``MSYS2``. If you intended to use ``MSVC`` then +you may need to force Meson to :external+meson-python:ref:`use MSVC `. diff --git a/pyproject.toml b/pyproject.toml index 50e533b0c15b..4bdab4eb93c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -287,4 +287,7 @@ ignore_messages = [ "Hyperlink target \".*\" is not referenced.", "Duplicate implicit target name: \".*\".", "Duplicate explicit target name: \".*\".", + # sphinx.ext.intersphinx directives + "No role entry for \"external+.*\".", + "Unknown interpreted text role \"external+.*\"." ] From 0a139a0a60f9c2ccac679d7e1ba8c0259a288849 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 10 Oct 2023 23:00:25 -0400 Subject: [PATCH 0318/1120] reduced redundancy in headings and note required MinGW windows header version Co-authored-by: Elliott Sales de Andrade --- doc/devel/dependencies.rst | 109 +++++++++++++++++++------------- doc/devel/development_setup.rst | 2 + doc/users/installing/index.rst | 2 +- 3 files changed, 67 insertions(+), 46 deletions(-) diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 0ffdaf65696a..458fff51b82f 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -1,15 +1,17 @@ .. _dependencies: -============ +************ Dependencies -============ +************ + +.. _runtime_dependencies: Runtime dependencies ==================== -Mandatory dependencies ----------------------- +Required +-------- When installing through a package manager like ``pip`` or ``conda``, the mandatory dependencies are automatically installed. This list is mainly for @@ -31,8 +33,8 @@ reference. .. _optional_dependencies: -Optional dependencies ---------------------- +Optional +-------- The following packages and tools are not required but extend the capabilities of Matplotlib. @@ -106,10 +108,14 @@ Additionally, Matplotlib depends on: .. _FreeType: https://www.freetype.org/ .. _Qhull: http://www.qhull.org/ -By default, Matplotlib downloads and builds its own copies of FreeType (this is -necessary to run the test suite, because different versions of FreeType -rasterize characters differently) and of Qhull. As an exception, Matplotlib -defaults to the system version of FreeType on AIX. + +Download during install +^^^^^^^^^^^^^^^^^^^^^^^ + +By default, Matplotlib downloads and builds its own copies of Qhull and FreeType. +The vendored version of FreeType is necessary to run the test suite, because +different versions of FreeType rasterize characters differently. + Use system libraries ^^^^^^^^^^^^^^^^^^^^ @@ -176,9 +182,8 @@ remember to clear your artifacts before re-building:: git clean -xfd - -Manual Download -^^^^^^^^^^^^^^^ +From source files +^^^^^^^^^^^^^^^^^ If the automatic download does not work (for example, on air-gapped systems) it is preferable to instead use system libraries. However you can manually download the @@ -202,19 +207,23 @@ In all cases the required version of pip is embedded in the CPython source. .. _development-dependencies: -Dependencies for building Matplotlib -==================================== +Build dependencies +================== + .. _setup-dependencies: -Setup dependencies ------------------- +Python +------ + +By default, ``pip`` will build packages using build isolation, meaning that these +build dependencies are temporally installed by pip for the duration of the +Matplotlib build process. However, build isolation is disabled when :ref:`installing Matplotlib for development `; +therefore we recommend using one of our :ref:`virtual environment configurations ` to +create a development environment in which these packages are automatically installed. -By default, ``pip`` will build packages using build isolation, and the following -dependencies will be automatically installed in the isolated environment to build -Matplotlib. However, for development, you may wish to make an editable install, which -will require disabling build isolation, so these build dependencies should be installed -in your target environment manually: +If you are developing Matplotlib and unable to use our environment configurations, +then you must manually install the following packages into your development environment: - `meson-python `_ (>= 0.13.1). - `ninja `_ (>= 1.8.2). This may be available in your package @@ -230,11 +239,13 @@ in your target environment manually: .. _compile-dependencies: -C++ compiler ------------- +Compiled extensions +------------------- Matplotlib requires a C++ compiler that supports C++17, and each platform has a development environment that must be installed before a compiler can be installed. +You may also need to install headers for various libraries used in the compiled extension +source files. .. tab-set:: @@ -261,6 +272,8 @@ development environment that must be installed before a compiler can be installe Alternatively, you can install a Linux-like environment such as `CygWin `_ or `Windows Subsystem for Linux `_. + If using `MinGW-64 `_, we require **v6** of the + ```Mingw-w64-x86_64-headers``. We highly recommend that you install a compiler using your platform tool, i.e., @@ -294,16 +307,19 @@ Xcode, VS Code or Linux package manager. Choose **one** compiler from this list: .. _test-dependencies: -Dependencies for testing Matplotlib -=================================== +Test dependencies +================= + This section lists the additional software required for :ref:`running the tests `. -Required: +Required +-------- - pytest_ (>= 7.0.0) -Optional: +Optional +-------- In addition to all of the optional dependencies on the main library, for testing the following will be used if they are installed. @@ -357,11 +373,12 @@ them will be skipped by pytest. .. _doc-dependencies: -Dependencies for building Matplotlib's documentation -==================================================== +Documentation dependencies +========================== + +Python +------ -Python packages ---------------- The additional Python packages required to build the :ref:`documentation ` are listed in :file:`doc-requirements.txt` and can be installed using :: @@ -373,13 +390,22 @@ The content of :file:`doc-requirements.txt` is also shown below: .. include:: ../../requirements/doc/doc-requirements.txt :literal: -Additional external dependencies --------------------------------- -Required: +External tools +-------------- + +The documentation requires LaTeX and Graphviz. These are not +Python packages and must be installed separately. The documentation can be +built without Inkscape and optipng, but the build process will raise various +warnings. If the build process warns that you are missing fonts, make sure +your LaTeX distribution bundles cm-super or install it separately. + +Required +^^^^^^^^ + +* `Graphviz `_ * a minimal working LaTeX distribution, e.g., `TeX Live `_ or `MikTeX `_ -* `Graphviz `_ * the following LaTeX packages (if your OS bundles TeX Live, the "complete" version of the installer, e.g. "texlive-full" or "texlive-all", will often automatically include these packages): @@ -388,18 +414,11 @@ Required: * `dvipng `_ * `underscore `_ -Optional, but recommended: +Optional +^^^^^^^^ * `Inkscape `_ * `optipng `_ * the font "Humor Sans" (aka the "XKCD" font), or the free alternative `Comic Neue `_ * the font "Times New Roman" - -.. note:: - - The documentation will not build without LaTeX and Graphviz. These are not - Python packages and must be installed separately. The documentation can be - built without Inkscape and optipng, but the build process will raise various - warnings. If the build process warns that you are missing fonts, make sure - your LaTeX distribution bundles cm-super or install it separately. diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 0e7db431163f..a41a74351660 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -154,6 +154,8 @@ Most Python dependencies will be installed when :ref:`setting up the environment but non-Python dependencies like C++ compilers, LaTeX, and other system applications must be installed separately. For a full list, see :ref:`dependencies`. +.. _development-install: + Install Matplotlib in editable mode =================================== diff --git a/doc/users/installing/index.rst b/doc/users/installing/index.rst index c8c9ba549775..0d25ffbf76fa 100644 --- a/doc/users/installing/index.rst +++ b/doc/users/installing/index.rst @@ -31,7 +31,7 @@ precompiled wheel for your OS and Python. For support of other GUI frameworks, LaTeX rendering, saving animations and a larger selection of file formats, you can - install :ref:`optional_dependencies`. + install :ref:`optional dependencies `. Third-party distributions From a1868d4ec4e7d4487ef24ee2034339c399c40097 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:47:49 +0200 Subject: [PATCH 0319/1120] Link xkcd color survey in named colors example and slightly reword their description --- galleries/examples/color/named_colors.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/galleries/examples/color/named_colors.py b/galleries/examples/color/named_colors.py index 0181e0f06742..9ae4ec4957f3 100644 --- a/galleries/examples/color/named_colors.py +++ b/galleries/examples/color/named_colors.py @@ -101,8 +101,10 @@ def plot_colortable(colors, *, ncols=4, sort_colors=True): # ----------- # XKCD Colors # ----------- -# XKCD colors are supported, but they produce a large figure, so we skip them -# for now. You can use the following code if desired:: +# Matplotlib supports colors from the +# `xkcd color survey `_, e.g. ``"xkcd:sky blue"``. Since +# this contains almost 1000 colors, a figure of this would be very large and is thus +# omitted here. You can use the following code to generate the overview yourself :: # # xkcd_fig = plot_colortable(mcolors.XKCD_COLORS) # xkcd_fig.savefig("XKCD_Colors.png") From 4af86972fafe0a9ebf18ddded200e910d25c57d2 Mon Sep 17 00:00:00 2001 From: danielcobej Date: Fri, 20 Oct 2023 15:48:24 +0200 Subject: [PATCH 0320/1120] datetime vs number, number vs datetime, datetime vs datetime --- lib/matplotlib/tests/test_datetime.py | 45 ++++++++++++++++++--------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 9a359d6c33b7..ca8b746bad0d 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -35,24 +35,39 @@ def test_axhline(self): @mpl.style.context("default") def test_axhspan(self): mpl.rcParams["date.converter"] = 'concise' - np.random.seed(19680801) start_date = datetime.datetime(2023, 1, 1) - time_delta = datetime.timedelta(days=1) - - fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True, figsize=(10, 8)) - - ax1.set_ylim(start_date, start_date + 29*time_delta) - for i in range(np.random.randint(1, 5)): - ymin = start_date + np.random.randint(0, 30) * time_delta - ymax = ymin + np.random.randint(1, 3) * time_delta - ax1.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) - - ax2.set_ylim(start_date, start_date + 29*time_delta) - for i in range(0, 30, 2): - ymin = start_date + i * time_delta - ymax = ymin + time_delta + dates = [start_date + datetime.timedelta(days=i) for i in range(31)] + numbers = list(range(1, 32)) + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, + constrained_layout=True, + figsize=(10, 12)) + + ax1.plot(dates, numbers, marker='o', color='blue') + for i in range(0, 31, 2): + ax1.axhspan(ymin=i+1, ymax=i+2, facecolor='green', alpha=0.5) + ax1.set_title('Datetime vs. Number') + ax1.set_xlabel('Date') + ax1.set_ylabel('Number') + + ax2.plot(numbers, dates, marker='o', color='blue') + for i in range(0, 31, 2): + ymin = start_date + datetime.timedelta(days=i) + ymax = ymin + datetime.timedelta(days=1) ax2.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) + ax2.set_title('Number vs. Datetime') + ax2.set_xlabel('Number') + ax2.set_ylabel('Date') + + ax3.plot(dates, dates, marker='o', color='blue') + for i in range(0, 31, 2): + ymin = start_date + datetime.timedelta(days=i) + ymax = ymin + datetime.timedelta(days=1) + ax3.axhspan(ymin=ymin, ymax=ymax, facecolor='green', alpha=0.5) + ax3.set_title('Datetime vs. Datetime') + ax3.set_xlabel('Date') + ax3.set_ylabel('Date') @pytest.mark.xfail(reason="Test for axline not written yet") @mpl.style.context("default") From fd16b352b1e16673bd7b2aae3a1e9ae8d6bcc3cf Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Sat, 21 Oct 2023 18:57:39 +0530 Subject: [PATCH 0321/1120] raising revision for test_bar in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 69ff5d37c0d0..2ec3d03ae377 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -90,24 +90,23 @@ def test_axvspan(self): @mpl.style.context("default") def test_bar(self): mpl.rcParams["date.converter"] = "concise" - range_threshold = 10 - fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") + + fig, (ax1, ax2) = plt.subplots(2, 1, layout="constrained") x_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)], + [ + datetime(2020, 6, 30), + datetime(2020, 7, 22), + datetime(2020, 8, 3), + datetime(2020, 9, 14), + ], dtype=np.datetime64, ) - y_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)], - dtype=np.datetime64, - ) - x_ranges = np.array(range(1, range_threshold)) - y_ranges = np.array(range(1, range_threshold)) + x_ranges = [8800, 2600, 8500, 7400] - ax1.bar(x_dates, y_ranges, width=np.timedelta64(range_threshold, "D")) - ax2.bar(x_dates, y_dates, width=np.timedelta64(range_threshold, "D")) - ax3.bar(x_ranges, y_dates, width=np.timedelta64(range_threshold, "D")) - ax4.bar(x_ranges, y_ranges, bottom=datetime.datetime(2023, 10, 1)) + x = np.datetime64(datetime(2020, 6, 1)) + ax1.bar(x_dates, x_ranges, width=np.timedelta64(4, "D")) + ax2.bar(np.arange(4), x_dates, bottom=x) @pytest.mark.xfail(reason="Test for bar_label not written yet") @mpl.style.context("default") From fbbbc53dcf0812fa40d28bac6f4eef8fb0e9bac2 Mon Sep 17 00:00:00 2001 From: Jirka Date: Sat, 21 Oct 2023 16:23:23 +0200 Subject: [PATCH 0322/1120] docs: adding explanation for color in `set_facecolor` --- lib/matplotlib/axes/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 8d29df3b72e8..ee74ab188d98 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1499,7 +1499,7 @@ def set_facecolor(self, color): Parameters ---------- - color : color + color : color defined by string name, char abbreviation or hex, see :doc:`gallery/color/named_colors` """ self._facecolor = color self.stale = True From 86b12d3c8ccb840384cd53d49b1cf4d5ba6fcb84 Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Sat, 21 Oct 2023 21:37:40 +0530 Subject: [PATCH 0323/1120] Update test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 2ec3d03ae377..30645f7ec1aa 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -95,16 +95,16 @@ def test_bar(self): x_dates = np.array( [ - datetime(2020, 6, 30), - datetime(2020, 7, 22), - datetime(2020, 8, 3), - datetime(2020, 9, 14), + datetime.datetime(2020, 6, 30), + datetime.datetime(2020, 7, 22), + datetime.datetime(2020, 8, 3), + datetime.datetime(2020, 9, 14), ], dtype=np.datetime64, ) x_ranges = [8800, 2600, 8500, 7400] - x = np.datetime64(datetime(2020, 6, 1)) + x = np.datetime64(datetime.datetime(2020, 6, 1)) ax1.bar(x_dates, x_ranges, width=np.timedelta64(4, "D")) ax2.bar(np.arange(4), x_dates, bottom=x) From 63a11fac4490a0a4eb91ae1d48b7748ac01572e9 Mon Sep 17 00:00:00 2001 From: hannah Date: Sat, 14 Oct 2023 22:27:15 -0400 Subject: [PATCH 0324/1120] move pytest configs from .ini to.toml remove configs that are now defaults Co-authored-by: Elliott Sales de Andrade --- pyproject.toml | 7 +++++++ pytest.ini | 12 ------------ 2 files changed, 7 insertions(+), 12 deletions(-) delete mode 100644 pytest.ini diff --git a/pyproject.toml b/pyproject.toml index 4bdab4eb93c7..ba090844331e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -291,3 +291,10 @@ ignore_messages = [ "No role entry for \"external+.*\".", "Unknown interpreted text role \"external+.*\"." ] + +[tool.pytest.ini_options] +# Because tests can be run from an installed copy, most of our Pytest +# configuration is in the `pytest_configure` function in +# `lib/matplotlib/testing/conftest.py`. +minversion = "7.0" +testpaths = ["lib"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 68920f59be01..000000000000 --- a/pytest.ini +++ /dev/null @@ -1,12 +0,0 @@ -# Because tests can be run from an installed copy, most of our Pytest -# configuration is in the `pytest_configure` function in -# `lib/matplotlib/testing/conftest.py`. This configuration file exists only to -# set a minimum pytest version and to prevent pytest from wasting time trying -# to check examples and documentation files that are not really tests. - -[pytest] -minversion = 7.0.0 - -testpaths = lib -python_files = test_*.py -junit_family = xunit2 From 46e0dc258b019fc90cd5c0eb9e9584cbe3fd7837 Mon Sep 17 00:00:00 2001 From: danielcobej Date: Sun, 22 Oct 2023 17:53:05 +0200 Subject: [PATCH 0325/1120] datetime vs datetime, number vs datetime, datetime vs number --- lib/matplotlib/tests/test_datetime.py | 55 +++++++++++++++------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index fb6eedc3b5e0..993948d8f106 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -53,32 +53,39 @@ def test_axvline(self): @mpl.style.context("default") def test_axvspan(self): mpl.rcParams["date.converter"] = 'concise' - np.random.seed(19680801) start_date = datetime.datetime(2023, 1, 1) - time_delta = datetime.timedelta(days=1) - - values1 = np.random.randint(1, 10, 30) - values2 = np.random.randint(1, 10, 30) - values3 = np.random.randint(1, 10, 30) - - bin_edges = [start_date + i * time_delta for i in range(31)] - - fig, (ax1, ax2, ax3) = plt.subplots(3, 1, constrained_layout=True) - - axes = [ax1, ax2, ax3] - values_list = [values1, values2, values3] - - for ax, values in zip(axes, values_list): - ax.hist( - [start_date + i * time_delta for i in range(30)], - bins=bin_edges, - weights=values - ) - for i in range(np.random.randint(1, 5)): - xmin = start_date + np.random.randint(0, 30) * time_delta - xmax = xmin + np.random.randint(1, 3) * time_delta - ax.axvspan(xmin=xmin, xmax=xmax, facecolor='green', alpha=0.5) + dates = [start_date + datetime.timedelta(days=i) for i in range(31)] + numbers = list(range(1, 32)) + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, + constrained_layout=True, + figsize=(10, 12)) + + ax1.plot(dates, numbers, marker='o', color='blue') + for i in range(0, 31, 2): + xmin = start_date + datetime.timedelta(days=i) + xmax = xmin + datetime.timedelta(days=1) + ax1.axvspan(xmin=xmin, xmax=xmax, facecolor='red', alpha=0.5) + ax1.set_title('Datetime vs. Number') + ax1.set_xlabel('Date') + ax1.set_ylabel('Number') + + ax2.plot(numbers, dates, marker='o', color='blue') + for i in range(0, 31, 2): + ax2.axvspan(xmin=i+1, xmax=i+2, facecolor='red', alpha=0.5) + ax2.set_title('Number vs. Datetime') + ax2.set_xlabel('Number') + ax2.set_ylabel('Date') + + ax3.plot(dates, dates, marker='o', color='blue') + for i in range(0, 31, 2): + xmin = start_date + datetime.timedelta(days=i) + xmax = xmin + datetime.timedelta(days=1) + ax3.axvspan(xmin=xmin, xmax=xmax, facecolor='red', alpha=0.5) + ax3.set_title('Datetime vs. Datetime') + ax3.set_xlabel('Date') + ax3.set_ylabel('Date') @pytest.mark.xfail(reason="Test for bar not written yet") @mpl.style.context("default") From 8c55381236ca74e67874ed31fe7ae93dd63da047 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Mon, 23 Oct 2023 12:08:20 -0400 Subject: [PATCH 0326/1120] Add test_axhline in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index c9455d450b70..7c188420d91f 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -29,8 +29,20 @@ def test_arrow(self): @pytest.mark.xfail(reason="Test for axhline not written yet") @mpl.style.context("default") def test_axhline(self): - fig, ax = plt.subplots() - ax.axhline(...) + mpl.rcParams["date.converter"] = 'concise' + fig, ax = plt.subplots(layout='constrained') + birth_date = np.array([datetime.datetime(2020, 4, 10), + datetime.datetime(2020, 5, 30), + datetime.datetime(2020, 10, 12), + datetime.datetime(2020, 11, 15)]) + year_start = datetime.datetime(2020, 1, 1) + year_end = datetime.datetime(2020, 12, 31) + age = [21, 53, 20, 24] + ax.set_xlabel('Birth Date') + ax.set_ylabel('Age') + ax.set_ylim(bottom=year_start, top=year_end) + ax.bar(x=age, height=birth_date) + ax.axhline(y=datetime.datetime(2020, 6, 1)) @pytest.mark.xfail(reason="Test for axhspan not written yet") @mpl.style.context("default") From 90dfb0b5d557977e9cf65e960bccd975a0376f8a Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 23 Oct 2023 15:06:58 -0500 Subject: [PATCH 0327/1120] Try/except import of Axes3D On some installs, the `mpl_toolkits` namespace package gets an old version which uses deprecated (And removed) code from the main library. On such the import of Axes3D will error with an `ImportError`. This prevents users from using any of `matplotlib`, since it is imported unconditionally by default. This just try/excepts the imports (and adjusts the registration code accordingly) to allow users to continue using matplotlib (though not 3D and possibly not other mpl_toolkits) even with older installs occluding the mpl_toolkits. --- lib/matplotlib/projections/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 16a5651da1d1..17a5fadd2691 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -55,7 +55,11 @@ from .. import axes, _docstring from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes from .polar import PolarAxes -from mpl_toolkits.mplot3d import Axes3D + +try: + from mpl_toolkits.mplot3d import Axes3D +except ImportError: + Axes3D = None class ProjectionRegistry: @@ -87,8 +91,12 @@ def get_projection_names(self): HammerAxes, LambertAxes, MollweideAxes, - Axes3D, ) +if Axes3D is not None: + projection_registry.register(Axes3D) +else: + # remove from namespace if not importable + del Axes3D def register_projection(cls): From 1344a9018f243281d09c29fdb788c9a966dc4714 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 23 Oct 2023 17:49:16 -0500 Subject: [PATCH 0328/1120] Restore default behavior of hexbin mincnt with C provided --- doc/api/next_api_changes/behavior/27179-KS.rst | 7 +++++++ .../prev_api_changes/api_changes_3.8.0/behaviour.rst | 6 ++++++ lib/matplotlib/axes/_axes.py | 11 ++++++++--- lib/matplotlib/tests/test_axes.py | 2 ++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/27179-KS.rst diff --git a/doc/api/next_api_changes/behavior/27179-KS.rst b/doc/api/next_api_changes/behavior/27179-KS.rst new file mode 100644 index 000000000000..fed4675a5f38 --- /dev/null +++ b/doc/api/next_api_changes/behavior/27179-KS.rst @@ -0,0 +1,7 @@ +Default behavior of ``hexbin`` with *C* provided requires at least 1 point +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The behavior changed in 3.8.0 to be inclusive of *mincnt*, however that resulted in +errors or warnings with some reduction functions, so now the default is to require at +least 1 point to call the reduction function. This effectively restores the default +behavior to match that of Matplotlib 3.7 and before. diff --git a/doc/api/prev_api_changes/api_changes_3.8.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.8.0/behaviour.rst index 9a528bdbc251..3476a05394df 100644 --- a/doc/api/prev_api_changes/api_changes_3.8.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.8.0/behaviour.rst @@ -165,3 +165,9 @@ PostScript paper type adds option to use figure size The :rc:`ps.papertype` rcParam can now be set to ``'figure'``, which will use a paper size that corresponds exactly with the size of the figure that is being saved. + +``hexbin`` *mincnt* parameter made consistently inclusive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, *mincnt* was inclusive with no *C* provided but exclusive when *C* is provided. +It is now inclusive of *mincnt* in both cases. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0fcabac8c7c0..9199f1cafae3 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4873,8 +4873,8 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, yscale : {'linear', 'log'}, default: 'linear' Use a linear or log10 scale on the vertical axis. - mincnt : int > 0, default: *None* - If not *None*, only display cells with more than *mincnt* + mincnt : int >= 0, default: *None* + If not *None*, only display cells with at least *mincnt* number of points in the cell. marginals : bool, default: *False* @@ -4941,6 +4941,11 @@ def reduce_C_function(C: array) -> float - `numpy.sum`: integral of the point values - `numpy.amax`: value taken from the largest point + By default will only reduce cells with at least 1 point because some + reduction functions (such as `numpy.amax`) will error/warn with empty + input. Changing *mincnt* will adjust the cutoff, and if set to 0 will + pass empty input to the reduction function. + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -5038,7 +5043,7 @@ def reduce_C_function(C: array) -> float else: Cs_at_i2[i2[i]].append(C[i]) if mincnt is None: - mincnt = 0 + mincnt = 1 accum = np.array( [reduce_C_function(acc) if len(acc) >= mincnt else np.nan for Cs_at_i in [Cs_at_i1, Cs_at_i2] diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 564bf6a86b52..c82c79272d87 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -971,6 +971,8 @@ def test_hexbin_empty(): # From #23922: creating hexbin with log scaling from empty # dataset raises ValueError ax.hexbin([], [], bins='log') + # From #27103: np.max errors when handed empty data + ax.hexbin([], [], C=[], reduce_C_function=np.max) def test_hexbin_pickable(): From 865e87c82144b11ce43d06912be196b70b7c6730 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 24 Oct 2023 00:52:05 -0500 Subject: [PATCH 0329/1120] Add a warning --- lib/matplotlib/projections/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 17a5fadd2691..7941cf7e8e5d 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -59,6 +59,10 @@ try: from mpl_toolkits.mplot3d import Axes3D except ImportError: + import warnings + warnings.warn("Unable to import Axes3D. This may be due to multiple versions of" + "Matplotlib being installed (e.g. as a system package and as a pip" + "package). As a result, the 3D projection is not available.") Axes3D = None From 92a8f88c2d2a6aaf317923e13008d738e319de29 Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 10:10:47 -0700 Subject: [PATCH 0330/1120] Example for plotting a bihistogram --- .../statistics/histogram_bihistogram.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 galleries/examples/statistics/histogram_bihistogram.py diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py new file mode 100644 index 000000000000..de574176cee0 --- /dev/null +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -0,0 +1,40 @@ +""" +========== +Bihistogram +========== + +How to plot a bihistogram with Matplotlib. +""" + +import matplotlib.pyplot as plt +import numpy as np + +# Create a random number generator with a fixed seed for reproducibility +rng = np.random.default_rng(19680801) + +# %% +# Generate data and plot a bihistogram +# ----------------------------------------- +# +# To generate a bihistogram we need two datasets (each being a vector of numbers). +# We will plot both histograms using plt.hist() and set the weights of the second one to be negative. +# We'll generate data below and plot the bihistogram. + +N_points = 10_000 +n_bins = 30 + +# Generate two normal distributions +dataset1 = np.random.normal(0, 1, size=N_points) +dataset2 = np.random.normal(1, 2, size=N_points) + +fig, ax = plt.subplots() + +# Plot the first histogram +ax.hist(dataset1, bins=n_bins, label="Dataset 1") + +# Plot the second histogram (notice the negative weights, which flip the histogram upside down) +ax.hist(dataset2, weights=-np.ones_like(dataset2), bins=n_bins, label="Dataset 2") +ax.axhline(0, color="k") +ax.legend() + +plt.show() From 0ca30c29580a9d4175fbdb0f5f605558e792ef98 Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 10:17:09 -0700 Subject: [PATCH 0331/1120] linting, line length --- galleries/examples/statistics/histogram_bihistogram.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index de574176cee0..9d0e6589c681 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -17,8 +17,8 @@ # ----------------------------------------- # # To generate a bihistogram we need two datasets (each being a vector of numbers). -# We will plot both histograms using plt.hist() and set the weights of the second one to be negative. -# We'll generate data below and plot the bihistogram. +# We will plot both histograms using plt.hist() and set the weights of the second +# one to be negative. We'll generate data below and plot the bihistogram. N_points = 10_000 n_bins = 30 @@ -32,7 +32,8 @@ # Plot the first histogram ax.hist(dataset1, bins=n_bins, label="Dataset 1") -# Plot the second histogram (notice the negative weights, which flip the histogram upside down) +# Plot the second histogram +# (notice the negative weights, which flip the histogram upside down) ax.hist(dataset2, weights=-np.ones_like(dataset2), bins=n_bins, label="Dataset 2") ax.axhline(0, color="k") ax.legend() From 6ac62c8a054b3a45ec7fbbde267864521a198cd2 Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 10:19:24 -0700 Subject: [PATCH 0332/1120] remove trailing whitespace --- galleries/examples/statistics/histogram_bihistogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index 9d0e6589c681..d311660123b9 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -32,7 +32,7 @@ # Plot the first histogram ax.hist(dataset1, bins=n_bins, label="Dataset 1") -# Plot the second histogram +# Plot the second histogram # (notice the negative weights, which flip the histogram upside down) ax.hist(dataset2, weights=-np.ones_like(dataset2), bins=n_bins, label="Dataset 2") ax.axhline(0, color="k") From f767a760610d77d79cc7517199fa23390614139b Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 10:49:33 -0700 Subject: [PATCH 0333/1120] Use constant bin widths to make histograms easier to compare visually --- galleries/examples/statistics/histogram_bihistogram.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index d311660123b9..46d3bbab1f75 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -21,20 +21,23 @@ # one to be negative. We'll generate data below and plot the bihistogram. N_points = 10_000 -n_bins = 30 # Generate two normal distributions dataset1 = np.random.normal(0, 1, size=N_points) dataset2 = np.random.normal(1, 2, size=N_points) +# Use a constant bin width to make the two histograms easier to compare visually +bin_width = 0.25 +bins=np.arange(np.min([dataset1, dataset2]), np.max([dataset1, dataset2]) + bin_width, bin_width) + fig, ax = plt.subplots() # Plot the first histogram -ax.hist(dataset1, bins=n_bins, label="Dataset 1") +ax.hist(dataset1, bins=bins, label="Dataset 1") # Plot the second histogram # (notice the negative weights, which flip the histogram upside down) -ax.hist(dataset2, weights=-np.ones_like(dataset2), bins=n_bins, label="Dataset 2") +ax.hist(dataset2, weights=-np.ones_like(dataset2), bins=bins, label="Dataset 2") ax.axhline(0, color="k") ax.legend() From 760812a61f28e0018a0355f62a771fac47c26536 Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 10:57:30 -0700 Subject: [PATCH 0334/1120] satiate flake8 --- galleries/examples/statistics/histogram_bihistogram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index 46d3bbab1f75..83247542b7f1 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -28,7 +28,8 @@ # Use a constant bin width to make the two histograms easier to compare visually bin_width = 0.25 -bins=np.arange(np.min([dataset1, dataset2]), np.max([dataset1, dataset2]) + bin_width, bin_width) +bins = np.arange(np.min([dataset1, dataset2]), + np.max([dataset1, dataset2]) + bin_width, bin_width) fig, ax = plt.subplots() From 0c2f104457fd10c0dc69bc760bba654c257d4e16 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Tue, 24 Oct 2023 14:15:26 -0400 Subject: [PATCH 0335/1120] Simplified example code and added arguments for xmin and xmax --- lib/matplotlib/tests/test_datetime.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 7c188420d91f..1aafa437e98e 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -31,18 +31,7 @@ def test_arrow(self): def test_axhline(self): mpl.rcParams["date.converter"] = 'concise' fig, ax = plt.subplots(layout='constrained') - birth_date = np.array([datetime.datetime(2020, 4, 10), - datetime.datetime(2020, 5, 30), - datetime.datetime(2020, 10, 12), - datetime.datetime(2020, 11, 15)]) - year_start = datetime.datetime(2020, 1, 1) - year_end = datetime.datetime(2020, 12, 31) - age = [21, 53, 20, 24] - ax.set_xlabel('Birth Date') - ax.set_ylabel('Age') - ax.set_ylim(bottom=year_start, top=year_end) - ax.bar(x=age, height=birth_date) - ax.axhline(y=datetime.datetime(2020, 6, 1)) + ax.axhline(y=datetime.datetime(2020, 6, 1), xmin=0.2, xmax=0.8) @pytest.mark.xfail(reason="Test for axhspan not written yet") @mpl.style.context("default") From 72d604261ad49141dfedda203090d89f0aeb6beb Mon Sep 17 00:00:00 2001 From: i-jey Date: Tue, 24 Oct 2023 11:28:56 -0700 Subject: [PATCH 0336/1120] fix delimiters --- galleries/examples/statistics/histogram_bihistogram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index 83247542b7f1..7bfae14e9285 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -1,7 +1,7 @@ """ -========== +=========== Bihistogram -========== +=========== How to plot a bihistogram with Matplotlib. """ @@ -14,7 +14,7 @@ # %% # Generate data and plot a bihistogram -# ----------------------------------------- +# ------------------------------------ # # To generate a bihistogram we need two datasets (each being a vector of numbers). # We will plot both histograms using plt.hist() and set the weights of the second From e27da804a4779752f2aee9d3e9b8ff4f1519420f Mon Sep 17 00:00:00 2001 From: Koustav Ghosh Date: Wed, 25 Oct 2023 00:09:29 +0530 Subject: [PATCH 0337/1120] Revising test_bar in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 30645f7ec1aa..6aca1bc6eb4c 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -106,7 +106,7 @@ def test_bar(self): x = np.datetime64(datetime.datetime(2020, 6, 1)) ax1.bar(x_dates, x_ranges, width=np.timedelta64(4, "D")) - ax2.bar(np.arange(4), x_dates, bottom=x) + ax2.bar(np.arange(4), x_dates - x, bottom=x) @pytest.mark.xfail(reason="Test for bar_label not written yet") @mpl.style.context("default") From 875b441f54efe1b773acfd319fdbeec0ee771ecf Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Tue, 24 Oct 2023 16:58:56 -0400 Subject: [PATCH 0338/1120] Add test_axhline in test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 1aafa437e98e..79389e818e71 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -26,12 +26,18 @@ def test_arrow(self): fig, ax = plt.subplots() ax.arrow(...) - @pytest.mark.xfail(reason="Test for axhline not written yet") @mpl.style.context("default") def test_axhline(self): mpl.rcParams["date.converter"] = 'concise' - fig, ax = plt.subplots(layout='constrained') - ax.axhline(y=datetime.datetime(2020, 6, 1), xmin=0.2, xmax=0.8) + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + ax1.set_ylim(bottom=datetime.datetime(2020, 4, 1), + top=datetime.datetime(2020, 8, 1)) + ax2.set_ylim(bottom=np.datetime64('2005-01-01'), + top=np.datetime64('2005-04-01')) + ax3.set_ylim(bottom=datetime.datetime(2023, 9, 1), + top=datetime.datetime(2023, 11, 1)) + ax1.axhline(y=datetime.datetime(2020, 6, 3), xmin=0.5, xmax=0.7) + ax2.axhline(np.datetime64('2005-02-25T03:30'), xmin=0.1, xmax=0.9) @pytest.mark.xfail(reason="Test for axhspan not written yet") @mpl.style.context("default") From f8000bc3aa5493c36e0041721dd0e9e6b12ffa59 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 28 Aug 2022 21:06:07 +0100 Subject: [PATCH 0339/1120] Use pybind11 for qhull wrapper --- src/_qhull_wrapper.cpp | 172 ++++++++++++++++------------------------- src/meson.build | 2 +- 2 files changed, 68 insertions(+), 106 deletions(-) diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index 3abce833fe6f..9784a1698ba1 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -5,9 +5,9 @@ * triangulation, construct an instance of the matplotlib.tri.Triangulation * class without specifying a triangles array. */ -#define PY_SSIZE_T_CLEAN -#include "Python.h" -#include "numpy_cpp.h" +#include +#include + #ifdef _MSC_VER /* The Qhull header does not declare this as extern "C", but only MSVC seems to * do name mangling on global variables. We thus need to declare this before @@ -16,11 +16,11 @@ extern "C" { extern const char qh_version[]; } #endif + #include "libqhull_r/qhull_ra.h" #include #include - #ifndef MPL_DEVNULL #error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null" #endif @@ -28,6 +28,16 @@ extern const char qh_version[]; #define STRINGIFY(x) STR(x) #define STR(x) #x +namespace py = pybind11; +using namespace pybind11::literals; + +// Input numpy array class. +typedef py::array_t CoordArray; + +// Output numpy array class. +typedef py::array_t IndexArray; + + static const char* qhull_error_msg[6] = { "", /* 0 = qh_ERRnone */ @@ -64,17 +74,16 @@ get_facet_neighbours(const facetT* facet, std::vector& tri_indices, /* Return true if the specified points arrays contain at least 3 unique points, * or false otherwise. */ static bool -at_least_3_unique_points(npy_intp npoints, const double* x, const double* y) +at_least_3_unique_points(py::ssize_t npoints, const double* x, const double* y) { - int i; - const int unique1 = 0; /* First unique point has index 0. */ - int unique2 = 0; /* Second unique point index is 0 until set. */ + const py::ssize_t unique1 = 0; /* First unique point has index 0. */ + py::ssize_t unique2 = 0; /* Second unique point index is 0 until set. */ if (npoints < 3) { return false; } - for (i = 1; i < npoints; ++i) { + for (py::ssize_t i = 1; i < npoints; ++i) { if (unique2 == 0) { /* Looking for second unique point. */ if (x[i] != x[unique1] || y[i] != y[unique1]) { @@ -125,8 +134,8 @@ class QhullInfo { /* Delaunay implementation method. * If hide_qhull_errors is true then qhull error messages are discarded; * if it is false then they are written to stderr. */ -static PyObject* -delaunay_impl(npy_intp npoints, const double* x, const double* y, +static py::tuple +delaunay_impl(py::ssize_t npoints, const double* x, const double* y, bool hide_qhull_errors) { qhT qh_qh; /* qh variable type and name must be like */ @@ -179,11 +188,13 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y, exitcode = qh_new_qhull(qh, ndim, (int)npoints, points.data(), False, (char*)"qhull d Qt Qbb Qc Qz", NULL, error_file); if (exitcode != qh_ERRnone) { - PyErr_Format(PyExc_RuntimeError, - "Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s", - qhull_error_msg[exitcode], exitcode, - hide_qhull_errors ? "; use python verbose option (-v) to see original qhull error." : ""); - return NULL; + std::string msg = + py::str("Error in qhull Delaunay triangulation calculation: {} (exitcode={})") + .format(qhull_error_msg[exitcode], exitcode).cast(); + if (hide_qhull_errors) { + msg += "; use python verbose option (-v) to see original qhull error."; + } + throw std::runtime_error(msg); } /* Split facets so that they only have 3 points each. */ @@ -204,12 +215,12 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y, std::vector tri_indices(max_facet_id+1); /* Allocate Python arrays to return. */ - npy_intp dims[2] = {ntri, 3}; - numpy::array_view triangles(dims); - int* triangles_ptr = triangles.data(); + int dims[2] = {ntri, 3}; + IndexArray triangles(dims); + int* triangles_ptr = triangles.mutable_data(); - numpy::array_view neighbors(dims); - int* neighbors_ptr = neighbors.data(); + IndexArray neighbors(dims); + int* neighbors_ptr = neighbors.mutable_data(); /* Determine triangles array and set tri_indices array. */ i = 0; @@ -238,103 +249,54 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y, } } - PyObject* tuple = PyTuple_New(2); - if (tuple == 0) { - throw std::runtime_error("Failed to create Python tuple"); - } - - PyTuple_SET_ITEM(tuple, 0, triangles.pyobj()); - PyTuple_SET_ITEM(tuple, 1, neighbors.pyobj()); - return tuple; + return py::make_tuple(triangles, neighbors); } /* Process Python arguments and call Delaunay implementation method. */ -static PyObject* -delaunay(PyObject *self, PyObject *args) +static py::tuple +delaunay(const CoordArray& x, const CoordArray& y, int verbose) { - numpy::array_view xarray; - numpy::array_view yarray; - PyObject* ret; - npy_intp npoints; - const double* x; - const double* y; - int verbose = 0; - - if (!PyArg_ParseTuple(args, "O&O&i:delaunay", - &xarray.converter_contiguous, &xarray, - &yarray.converter_contiguous, &yarray, - &verbose)) { - return NULL; + if (x.ndim() != 1 || y.ndim() != 1) { + throw std::invalid_argument("x and y must be 1D arrays"); } - npoints = xarray.shape(0); - if (npoints != yarray.shape(0)) { - PyErr_SetString(PyExc_ValueError, - "x and y must be 1D arrays of the same length"); - return NULL; + auto npoints = x.shape(0); + if (npoints != y.shape(0)) { + throw std::invalid_argument("x and y must be 1D arrays of the same length"); } if (npoints < 3) { - PyErr_SetString(PyExc_ValueError, - "x and y arrays must have a length of at least 3"); - return NULL; + throw std::invalid_argument("x and y arrays must have a length of at least 3"); } - x = xarray.data(); - y = yarray.data(); - - if (!at_least_3_unique_points(npoints, x, y)) { - PyErr_SetString(PyExc_ValueError, - "x and y arrays must consist of at least 3 unique points"); - return NULL; + if (!at_least_3_unique_points(npoints, x.data(), y.data())) { + throw std::invalid_argument("x and y arrays must consist of at least 3 unique points"); } - CALL_CPP("qhull.delaunay", - (ret = delaunay_impl(npoints, x, y, verbose == 0))); - - return ret; -} - -/* Return qhull version string for assistance in debugging. */ -static PyObject* -version(PyObject *self, PyObject *arg) -{ - return PyBytes_FromString(qh_version); + return delaunay_impl(npoints, x.data(), y.data(), verbose == 0); } -static PyMethodDef qhull_methods[] = { - {"delaunay", delaunay, METH_VARARGS, - "delaunay(x, y, verbose, /)\n" - "--\n\n" - "Compute a Delaunay triangulation.\n" - "\n" - "Parameters\n" - "----------\n" - "x, y : 1d arrays\n" - " The coordinates of the point set, which must consist of at least\n" - " three unique points.\n" - "verbose : int\n" - " Python's verbosity level.\n" - "\n" - "Returns\n" - "-------\n" - "triangles, neighbors : int arrays, shape (ntri, 3)\n" - " Indices of triangle vertices and indices of triangle neighbors.\n" - }, - {"version", version, METH_NOARGS, - "version()\n--\n\n" - "Return the qhull version string."}, - {NULL, NULL, 0, NULL} -}; - -static struct PyModuleDef qhull_module = { - PyModuleDef_HEAD_INIT, - "qhull", "Computing Delaunay triangulations.\n", -1, qhull_methods -}; - -PyMODINIT_FUNC -PyInit__qhull(void) -{ - import_array(); - return PyModule_Create(&qhull_module); +PYBIND11_MODULE(_qhull, m) { + m.doc() = "Computing Delaunay triangulations.\n"; + + m.def("delaunay", &delaunay, "x"_a, "y"_a, "verbose"_a, + "--\n\n" + "Compute a Delaunay triangulation.\n" + "\n" + "Parameters\n" + "----------\n" + "x, y : 1d arrays\n" + " The coordinates of the point set, which must consist of at least\n" + " three unique points.\n" + "verbose : int\n" + " Python's verbosity level.\n" + "\n" + "Returns\n" + "-------\n" + "triangles, neighbors : int arrays, shape (ntri, 3)\n" + " Indices of triangle vertices and indices of triangle neighbors.\n"); + + m.def("version", []() { return qh_version; }, + "version()\n--\n\n" + "Return the qhull version string."); } diff --git a/src/meson.build b/src/meson.build index 9cee34bfce8b..7836f276f4bc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -127,7 +127,7 @@ extension_data = { 'sources': files( '_qhull_wrapper.cpp', ), - 'dependencies': [numpy_dep, qhull_dep], + 'dependencies': [pybind11_dep, qhull_dep], 'c_args': [f'-DMPL_DEVNULL=@devnull@'], 'cpp_args': [f'-DMPL_DEVNULL=@devnull@'], }, From 0d0f3c233d4f95ade5dd4e9d25f169c9f14f157f Mon Sep 17 00:00:00 2001 From: Junpei Ota Date: Tue, 24 Oct 2023 22:09:44 -0400 Subject: [PATCH 0340/1120] Added smoke tests for errorbar --- lib/matplotlib/tests/test_datetime.py | 38 ++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index f42e0a0d04d8..0116ba42524b 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -129,11 +129,43 @@ def test_contourf(self): fig, ax = plt.subplots() ax.contourf(...) - @pytest.mark.xfail(reason="Test for errorbar not written yet") @mpl.style.context("default") def test_errorbar(self): - fig, ax = plt.subplots() - ax.errorbar(...) + mpl.rcParams["date.converter"] = "concise" + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") + limit = 7 + start_date = datetime.datetime(2023, 1, 1) + + x_dates = np.array([datetime.datetime(2023, 10, delta) for delta in range(1, limit)]) + y_dates = np.array([datetime.datetime(2023, 10, delta) for delta in range(1, limit)]) + x_date_error = datetime.timedelta(days=1) + y_date_error = datetime.timedelta(days=1) + + x_values = list(range(1, limit)) + y_values = list(range(1, limit)) + x_value_error = 0.5 + y_value_error = 0.5 + + ax1.errorbar(x_dates, y_values, + yerr=y_value_error, + capsize=10, + barsabove=True, + label='Data') + + ax2.errorbar(x_values, y_dates, + xerr=x_value_error, yerr=y_date_error, + errorevery=(1, 2), + fmt='-o', label='Data') + + ax3.errorbar(x_dates, y_dates, + xerr=x_date_error, yerr=y_date_error, + lolims=True, xlolims=True, + label='Data') + + ax4.errorbar(x_dates, y_values, + xerr=x_date_error, yerr=y_value_error, + uplims=True, xuplims=True, + label='Data') @pytest.mark.xfail(reason="Test for eventplot not written yet") @mpl.style.context("default") From 1727e8d6c5581a5d2373a1e8768c6d21554f1e49 Mon Sep 17 00:00:00 2001 From: Junpei Ota Date: Tue, 24 Oct 2023 22:29:05 -0400 Subject: [PATCH 0341/1120] Fixed code formatting --- lib/matplotlib/tests/test_datetime.py | 33 ++++++++++++--------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index b544ae49e047..1dfb1131364f 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -216,8 +216,8 @@ def test_errorbar(self): limit = 7 start_date = datetime.datetime(2023, 1, 1) - x_dates = np.array([datetime.datetime(2023, 10, delta) for delta in range(1, limit)]) - y_dates = np.array([datetime.datetime(2023, 10, delta) for delta in range(1, limit)]) + x_dates = np.array([datetime.datetime(2023, 10, d) for d in range(1, limit)]) + y_dates = np.array([datetime.datetime(2023, 10, d) for d in range(1, limit)]) x_date_error = datetime.timedelta(days=1) y_date_error = datetime.timedelta(days=1) @@ -227,25 +227,22 @@ def test_errorbar(self): y_value_error = 0.5 ax1.errorbar(x_dates, y_values, - yerr=y_value_error, - capsize=10, - barsabove=True, - label='Data') - + yerr=y_value_error, + capsize=10, + barsabove=True, + label='Data') ax2.errorbar(x_values, y_dates, - xerr=x_value_error, yerr=y_date_error, - errorevery=(1, 2), - fmt='-o', label='Data') - + xerr=x_value_error, yerr=y_date_error, + errorevery=(1, 2), + fmt='-o', label='Data') ax3.errorbar(x_dates, y_dates, - xerr=x_date_error, yerr=y_date_error, - lolims=True, xlolims=True, - label='Data') - + xerr=x_date_error, yerr=y_date_error, + lolims=True, xlolims=True, + label='Data') ax4.errorbar(x_dates, y_values, - xerr=x_date_error, yerr=y_value_error, - uplims=True, xuplims=True, - label='Data') + xerr=x_date_error, yerr=y_value_error, + uplims=True, xuplims=True, + label='Data') @pytest.mark.xfail(reason="Test for eventplot not written yet") @mpl.style.context("default") From 931bf748c26d3c9ad205c4affc2785631c656fd1 Mon Sep 17 00:00:00 2001 From: Junpei Ota Date: Tue, 24 Oct 2023 22:31:14 -0400 Subject: [PATCH 0342/1120] Removed Trailing Whitespace --- lib/matplotlib/tests/test_datetime.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 1dfb1131364f..47c54c1389be 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -211,7 +211,7 @@ def test_contourf(self): @mpl.style.context("default") def test_errorbar(self): - mpl.rcParams["date.converter"] = "concise" + mpl.rcParams["date.converter"] = "concise" fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") limit = 7 start_date = datetime.datetime(2023, 1, 1) @@ -226,20 +226,20 @@ def test_errorbar(self): x_value_error = 0.5 y_value_error = 0.5 - ax1.errorbar(x_dates, y_values, + ax1.errorbar(x_dates, y_values, yerr=y_value_error, capsize=10, - barsabove=True, + barsabove=True, label='Data') - ax2.errorbar(x_values, y_dates, + ax2.errorbar(x_values, y_dates, xerr=x_value_error, yerr=y_date_error, errorevery=(1, 2), fmt='-o', label='Data') - ax3.errorbar(x_dates, y_dates, + ax3.errorbar(x_dates, y_dates, xerr=x_date_error, yerr=y_date_error, lolims=True, xlolims=True, label='Data') - ax4.errorbar(x_dates, y_values, + ax4.errorbar(x_dates, y_values, xerr=x_date_error, yerr=y_value_error, uplims=True, xuplims=True, label='Data') From 3bd567202c82038a5d8e5f424ac6ae5b3a5437f7 Mon Sep 17 00:00:00 2001 From: Jirka Date: Wed, 25 Oct 2023 08:17:56 +0200 Subject: [PATCH 0343/1120] suggestion --- lib/matplotlib/axes/_base.py | 2 +- lib/matplotlib/axes/_secondary_axes.py | 2 +- lib/matplotlib/figure.py | 4 ++-- lib/matplotlib/lines.py | 2 +- lib/matplotlib/patches.py | 4 ++-- lib/matplotlib/text.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index ee74ab188d98..b113b0f21156 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1499,7 +1499,7 @@ def set_facecolor(self, color): Parameters ---------- - color : color defined by string name, char abbreviation or hex, see :doc:`gallery/color/named_colors` + color : color; see :ref:`colors_def` """ self._facecolor = color self.stale = True diff --git a/lib/matplotlib/axes/_secondary_axes.py b/lib/matplotlib/axes/_secondary_axes.py index f0ab4f3e3be8..4c757f61c7b6 100644 --- a/lib/matplotlib/axes/_secondary_axes.py +++ b/lib/matplotlib/axes/_secondary_axes.py @@ -233,7 +233,7 @@ def set_color(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` """ axis = self._axis_map[self._orientation] axis.set_tick_params(colors=color) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 2b83a7ae4a73..baea9232800f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -410,7 +410,7 @@ def set_edgecolor(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` """ self.patch.set_edgecolor(color) @@ -420,7 +420,7 @@ def set_facecolor(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` """ self.patch.set_facecolor(color) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 31b931a52c82..0b4b8d48adc2 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1056,7 +1056,7 @@ def set_color(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` """ mcolors._check_color_like(color=color) self._color = color diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7b9e6e7aa060..d1537322ab76 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -332,7 +332,7 @@ def set_edgecolor(self, color): Parameters ---------- - color : color or None + color : color or None; see :ref:`colors_def` """ self._original_edgecolor = color self._set_edgecolor(color) @@ -350,7 +350,7 @@ def set_facecolor(self, color): Parameters ---------- - color : color or None + color : color or None; see :ref:`colors_def` """ self._original_facecolor = color self._set_facecolor(color) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 7a58ce717200..6371e03603d6 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -965,7 +965,7 @@ def set_backgroundcolor(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` See Also -------- @@ -985,7 +985,7 @@ def set_color(self, color): Parameters ---------- - color : color + color : color; see :ref:`colors_def` """ # "auto" is only supported by axisartist, but we can just let it error # out at draw time for simplicity. From e4dcb7f4291b1ac58e830203975ac14498f62dac Mon Sep 17 00:00:00 2001 From: Artyom Romanov Date: Wed, 25 Oct 2023 21:11:31 +0500 Subject: [PATCH 0344/1120] Fix typo in docstring of `matplotlib.colors.from_levels_and_colors` --- lib/matplotlib/colors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index b9866974d8a3..d81f761faa78 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -2757,8 +2757,8 @@ def from_levels_and_colors(levels, colors, extend='neither'): Returns ------- - cmap : `~matplotlib.colors.Normalize` - norm : `~matplotlib.colors.Colormap` + cmap : `~matplotlib.colors.Colormap` + norm : `~matplotlib.colors.Normalize` """ slice_map = { 'both': slice(1, -1), From d2b2a069066eb6fe137685c33ba4139100e45e94 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 25 Oct 2023 15:08:11 -0500 Subject: [PATCH 0345/1120] Grammar in release note for hexbin mincnt Co-authored-by: Elliott Sales de Andrade --- doc/api/next_api_changes/behavior/27179-KS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/next_api_changes/behavior/27179-KS.rst b/doc/api/next_api_changes/behavior/27179-KS.rst index fed4675a5f38..873cd622bbd4 100644 --- a/doc/api/next_api_changes/behavior/27179-KS.rst +++ b/doc/api/next_api_changes/behavior/27179-KS.rst @@ -1,7 +1,7 @@ Default behavior of ``hexbin`` with *C* provided requires at least 1 point ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The behavior changed in 3.8.0 to be inclusive of *mincnt*, however that resulted in +The behavior changed in 3.8.0 to be inclusive of *mincnt*. However that resulted in errors or warnings with some reduction functions, so now the default is to require at least 1 point to call the reduction function. This effectively restores the default behavior to match that of Matplotlib 3.7 and before. From 0b8b37e7deab69923fc85338e3da114b19f28ce3 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 25 Oct 2023 15:21:51 -0500 Subject: [PATCH 0346/1120] Fix spacing on line breaks Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/projections/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 7941cf7e8e5d..8ce118986065 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -60,8 +60,8 @@ from mpl_toolkits.mplot3d import Axes3D except ImportError: import warnings - warnings.warn("Unable to import Axes3D. This may be due to multiple versions of" - "Matplotlib being installed (e.g. as a system package and as a pip" + warnings.warn("Unable to import Axes3D. This may be due to multiple versions of " + "Matplotlib being installed (e.g. as a system package and as a pip " "package). As a result, the 3D projection is not available.") Axes3D = None From b9010535ce4f4158d7f22b43a4215f72768fff00 Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 25 Oct 2023 17:37:52 -0400 Subject: [PATCH 0347/1120] DOC: clean up links under table formatting docs I guess I forgot a space and nobody checked the final build - currently all the urls in the table are batched underneath and they should be hidden --- doc/devel/document.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/devel/document.rst b/doc/devel/document.rst index 095b42d689ce..261929d0298d 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -176,6 +176,7 @@ Given the size of the table and length of each entry, use: +-------------+-------------------------------+--------------------+ For more information, see `rst tables `_. + .. _sg: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables .. _lt: https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table .. _csv: https://docutils.sourceforge.io/docs/ref/rst/directives.html#toc-entry-22 From 689ab844d300e5209d38778dac748b9337919112 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 25 Oct 2023 23:58:01 +0200 Subject: [PATCH 0348/1120] DOC: Add role for custom informal types like color The role is intended to be used for type references in docstrings like ``` Parameters ---------- color : :mpltype:`color` ``` It is easily extendable to other types. The naming `mpltype` was a quick choice. I'm open to better names. This PR contains one example usage in `widgets.Button` so that one can see the effect in the built docs. Systematic application throughout the codebase should be deferred to a separate PR. Closes #24859. Formalizes #27164. --- doc/sphinxext/custom_roles.py | 15 +++++++++++++++ lib/matplotlib/widgets.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/sphinxext/custom_roles.py b/doc/sphinxext/custom_roles.py index 3dcecc3df733..8961037ed0f1 100644 --- a/doc/sphinxext/custom_roles.py +++ b/doc/sphinxext/custom_roles.py @@ -64,8 +64,23 @@ def rcparam_role(name, rawtext, text, lineno, inliner, options={}, content=[]): return node_list, messages +def mpltype_role(name, rawtext, text, lineno, inliner, options={}, content=[]): + mpltype = text + type_to_link_target = { + 'color': 'colors_def', + } + if mpltype not in type_to_link_target: + raise ValueError(f"Unknown mpltype: {mpltype!r}") + + ref_nodes, messages = inliner.interpreted( + mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) + node_list = [ref_nodes] + return node_list, messages + + def setup(app): app.add_role("rc", rcparam_role) + app.add_role("mpltype", mpltype_role) app.add_node( QueryReference, html=(visit_query_reference_node, depart_query_reference_node), diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index cd9716408303..74cac5eec5a1 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -191,7 +191,7 @@ def __init__(self, ax, label, image=None, image : array-like or PIL Image The image to place in the button, if not *None*. The parameter is directly forwarded to `~.axes.Axes.imshow`. - color : color + color : :mpltype:`color` The color of the button when not activated. hovercolor : color The color of the button when the mouse is over it. From 3987f401b9f1f060110b0fa23417c1f2aa595d07 Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 25 Oct 2023 18:32:43 -0400 Subject: [PATCH 0349/1120] fixed links to table docs --- doc/devel/document.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/devel/document.rst b/doc/devel/document.rst index 261929d0298d..476a40c320b8 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -170,16 +170,17 @@ Given the size of the table and length of each entry, use: +-------------+-------------------------------+--------------------+ | | small table | large table | +-------------+-------------------------------+--------------------+ -| short entry | `simple or grid table `_ | `grid table `_ | +| short entry | `simple or grid table`_ | `grid table`_ | +-------------+-------------------------------+--------------------+ -| long entry | `list table `_ | `csv table `_ | +| long entry | `list table`_ | `csv table`_ | +-------------+-------------------------------+--------------------+ For more information, see `rst tables `_. -.. _sg: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables -.. _lt: https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table -.. _csv: https://docutils.sourceforge.io/docs/ref/rst/directives.html#toc-entry-22 +.. _`simple or grid table`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables +.. _`grid table`: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#grid-tables +.. _`list table`: https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table +.. _`csv table`: https://docutils.sourceforge.io/docs/ref/rst/directives.html#csv-table-1 Function arguments ^^^^^^^^^^^^^^^^^^ From d8e2c5f6f629dfc77b7a59307a55938bcf52d780 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 23 Oct 2023 17:50:07 +0200 Subject: [PATCH 0350/1120] Deprecate mixing positional and keyword args for legend(handles, labels) This was previously already a warning and also broken. --- doc/api/next_api_changes/deprecations/27175-AL.rst | 5 +++++ lib/matplotlib/legend.py | 8 +++++--- lib/matplotlib/tests/test_legend.py | 8 ++++---- 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/27175-AL.rst diff --git a/doc/api/next_api_changes/deprecations/27175-AL.rst b/doc/api/next_api_changes/deprecations/27175-AL.rst new file mode 100644 index 000000000000..3fce05765a59 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27175-AL.rst @@ -0,0 +1,5 @@ +Mixing positional and keyword arguments for ``legend`` handles and labels +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This previously only raised a warning, but is now formally deprecated. If +passing *handles* and *labels*, they must be passed either both positionally or +both as keyword. diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 58e5dfcfe3b8..c46770974567 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1301,7 +1301,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): legend(handles=handles, labels=labels) The behavior for a mixture of positional and keyword handles and labels - is undefined and issues a warning. + is undefined and issues a warning; it will be an error in the future. Parameters ---------- @@ -1334,8 +1334,10 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): handlers = kwargs.get('handler_map') if (handles is not None or labels is not None) and args: - _api.warn_external("You have mixed positional and keyword arguments, " - "some input may be discarded.") + _api.warn_deprecated("3.9", message=( + "You have mixed positional and keyword arguments, some input may " + "be discarded. This is deprecated since %(since)s and will " + "become an error %(removal)s.")) if (hasattr(handles, "__len__") and hasattr(labels, "__len__") and diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 3a35b4051c71..90b0a3f38999 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -405,10 +405,10 @@ def test_warn_mixed_args_and_kwargs(self): th = np.linspace(0, 2*np.pi, 1024) lns, = ax.plot(th, np.sin(th), label='sin') lnc, = ax.plot(th, np.cos(th), label='cos') - with pytest.warns(UserWarning) as record: + with pytest.warns(DeprecationWarning) as record: ax.legend((lnc, lns), labels=('a', 'b')) assert len(record) == 1 - assert str(record[0].message) == ( + assert str(record[0].message).startswith( "You have mixed positional and keyword arguments, some input may " "be discarded.") @@ -474,10 +474,10 @@ def test_warn_args_kwargs(self): fig, axs = plt.subplots(1, 2) lines = axs[0].plot(range(10)) lines2 = axs[1].plot(np.arange(10) * 2.) - with pytest.warns(UserWarning) as record: + with pytest.warns(DeprecationWarning) as record: fig.legend((lines, lines2), labels=('a', 'b')) assert len(record) == 1 - assert str(record[0].message) == ( + assert str(record[0].message).startswith( "You have mixed positional and keyword arguments, some input may " "be discarded.") From 89979cb5262b5891ab77dd39aef0f5eead3be21e Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 26 Oct 2023 13:13:03 +0200 Subject: [PATCH 0351/1120] BLD: Use NumPy nightly wheels for non-release builds This uses the NumPy nightlies for non-release builds to ensure the matplotlib nightlies uploaded are build against the new NumPy version. These nightlies are not tested, but if they were they should actually still be tested against the release NumPy. This is necessary to allow NumPy to enforce API changes, it does mean that potential C-API breaks in NumPy need quicker follow-up unfortunately (either in NumPy or in matplotlib). --- .github/workflows/cibuildwheel.yml | 35 +++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index cdb62658e6c9..9b311cc5c863 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -90,12 +90,35 @@ jobs: name: Build wheels on ${{ matrix.os }} for ${{ matrix.cibw_archs }} runs-on: ${{ matrix.os }} env: - CIBW_BEFORE_BUILD: >- - pip install numpy>=1.25 && - rm -rf {package}/build - CIBW_BEFORE_BUILD_WINDOWS: >- - pip install delvewheel numpy>=1.25 && - rm -rf {package}/build + # The following commands branch based on whether we are on the main + # branch or have a PR into main. For these, we use the NumPy 2.0 + # nightlies to ensure future C-API/ABI compatibility. + # This branching becomes unnecessary when NumPy 2.0 is released. + # When using no-build-isolation we need to install all requirements. + CIBW_BEFORE_BUILD: + ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && + 'pip install meson-python pybind11 setuptools_scm "setuptools>=64" ninja && + pip install "numpy>=2.0.0.dev0" --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" && + rm -rf {package}/build' + ) || + 'pip install "numpy>=1.25" && + rm -rf {package}/build' + }} + CIBW_BEFORE_BUILD_WINDOWS: + ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && + 'pip install delvewheel meson-python pybind11 setuptools_scm "setuptools>=64" ninja && + pip install "numpy>=2.0.0.dev0" --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" && + rm -rf {package}/build' + ) || + 'pip install delvewheel "numpy>=1.25" && + rm -rf {package}/build' + }} + CIBW_BUILD_FRONTEND: >- + ${{ (((github.event_name == '' && github.ref == 'refs/heads/main') || + (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && + 'pip; args: --no-build-isolation') || 'build' }} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- delvewheel repair -w {dest_dir} {wheel} CIBW_AFTER_BUILD: >- From 641a5b699812e1781435f05651a0fc5ee88a9e5b Mon Sep 17 00:00:00 2001 From: Joshua Stevenson Date: Thu, 26 Oct 2023 09:43:46 -0400 Subject: [PATCH 0352/1120] Improve legend picking example * Pass an actual pickradius instead of `True`. `True` (`1`) is a bad user experience. * Fix `on_pick` to work when the legend is draggable. (Early exit) * For good measure, show that it works with draggable legends. * Use higher visability syntax for single element destrturing for higher readability. * Refactor varible names. (`lined` -> `map_legend_to_ax`) --- .../examples/event_handling/legend_picking.py | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/galleries/examples/event_handling/legend_picking.py b/galleries/examples/event_handling/legend_picking.py index 79c27358a8ff..2fed3c9c1649 100644 --- a/galleries/examples/event_handling/legend_picking.py +++ b/galleries/examples/event_handling/legend_picking.py @@ -18,33 +18,46 @@ import numpy as np t = np.linspace(0, 1) -y1 = 2 * np.sin(2*np.pi*t) -y2 = 4 * np.sin(2*np.pi*2*t) +y1 = 2 * np.sin(2 * np.pi * t) +y2 = 4 * np.sin(2 * np.pi * 2 * t) fig, ax = plt.subplots() ax.set_title('Click on legend line to toggle line on/off') -line1, = ax.plot(t, y1, lw=2, label='1 Hz') -line2, = ax.plot(t, y2, lw=2, label='2 Hz') +(line1, ) = ax.plot(t, y1, lw=2, label='1 Hz') +(line2, ) = ax.plot(t, y2, lw=2, label='2 Hz') leg = ax.legend(fancybox=True, shadow=True) lines = [line1, line2] -lined = {} # Will map legend lines to original lines. -for legline, origline in zip(leg.get_lines(), lines): - legline.set_picker(True) # Enable picking on the legend line. - lined[legline] = origline +map_legend_to_ax = {} # Will map legend lines to original lines. + +pickradius = 5 # Points (Pt). How close the click needs to be to trigger an event. + +for legend_line, ax_line in zip(leg.get_lines(), lines): + legend_line.set_picker(pickradius) # Enable picking on the legend line. + map_legend_to_ax[legend_line] = ax_line def on_pick(event): # On the pick event, find the original line corresponding to the legend # proxy line, and toggle its visibility. - legline = event.artist - origline = lined[legline] - visible = not origline.get_visible() - origline.set_visible(visible) + legend_line = event.artist + + # Do nothing if the source of the event is not a legend line. + if legend_line not in map_legend_to_ax: + return + + ax_line = map_legend_to_ax[legend_line] + visible = not ax_line.get_visible() + ax_line.set_visible(visible) # Change the alpha on the line in the legend, so we can see what lines # have been toggled. - legline.set_alpha(1.0 if visible else 0.2) + legend_line.set_alpha(1.0 if visible else 0.2) fig.canvas.draw() + fig.canvas.mpl_connect('pick_event', on_pick) + +# Works even if the legend is draggable. This is independent from picking legend lines. +leg.set_draggable(True) + plt.show() From b2f32ac429fa9c66574565c65f2d42e4418cf34c Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 26 Oct 2023 18:18:27 +0200 Subject: [PATCH 0353/1120] Update .github/workflows/cibuildwheel.yml Co-authored-by: Thomas A Caswell --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9b311cc5c863..0ea480fc9a06 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -116,7 +116,7 @@ jobs: rm -rf {package}/build' }} CIBW_BUILD_FRONTEND: >- - ${{ (((github.event_name == '' && github.ref == 'refs/heads/main') || + ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && 'pip; args: --no-build-isolation') || 'build' }} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- From f319e356af49381b29b7c7da63f1ddd5ce717398 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Thu, 26 Oct 2023 13:20:55 -0400 Subject: [PATCH 0354/1120] Add axhline to ax3 --- lib/matplotlib/tests/test_datetime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 79389e818e71..a8b6bcee3bd4 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -38,6 +38,7 @@ def test_axhline(self): top=datetime.datetime(2023, 11, 1)) ax1.axhline(y=datetime.datetime(2020, 6, 3), xmin=0.5, xmax=0.7) ax2.axhline(np.datetime64('2005-02-25T03:30'), xmin=0.1, xmax=0.9) + ax3.axhline(y=datetime.datetime(2023, 10, 24), xmin=0.4, xmax=0.7) @pytest.mark.xfail(reason="Test for axhspan not written yet") @mpl.style.context("default") From d97c8a90e0aa848ea32a0c4f4b1dfe5bcec4d775 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Thu, 26 Oct 2023 13:24:58 -0400 Subject: [PATCH 0355/1120] Add test_axvline to test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index c9455d450b70..954151c18edb 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -44,11 +44,19 @@ def test_axline(self): fig, ax = plt.subplots() ax.axline(...) - @pytest.mark.xfail(reason="Test for axvline not written yet") @mpl.style.context("default") def test_axvline(self): - fig, ax = plt.subplots() - ax.axvline(...) + mpl.rcParams["date.converter"] = 'concise' + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + ax1.set_xlim(left=datetime.datetime(2020, 4, 1), + right=datetime.datetime(2020, 8, 1)) + ax2.set_xlim(left=np.datetime64('2005-01-01'), + right=np.datetime64('2005-04-01')) + ax3.set_xlim(left=datetime.datetime(2023, 9, 1), + right=datetime.datetime(2023, 11, 1)) + ax1.axvline(x=datetime.datetime(2020, 6, 3), ymin=0.5, ymax=0.7) + ax2.axvline(np.datetime64('2005-02-25T03:30'), ymin=0.1, ymax=0.9) + ax3.axvline(x=datetime.datetime(2023, 10, 24), ymin=0.4, ymax=0.7) @pytest.mark.xfail(reason="Test for axvspan not written yet") @mpl.style.context("default") From 8081658910f69709ec93d01058b526937b1a592c Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 26 Oct 2023 19:36:16 +0200 Subject: [PATCH 0356/1120] Simplify everything and just use --pre, also remove pre install of NumPy? --- .github/workflows/cibuildwheel.yml | 40 ++++++++++-------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 0ea480fc9a06..16a5c4fdde8f 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -90,35 +90,21 @@ jobs: name: Build wheels on ${{ matrix.os }} for ${{ matrix.cibw_archs }} runs-on: ${{ matrix.os }} env: - # The following commands branch based on whether we are on the main - # branch or have a PR into main. For these, we use the NumPy 2.0 - # nightlies to ensure future C-API/ABI compatibility. - # This branching becomes unnecessary when NumPy 2.0 is released. - # When using no-build-isolation we need to install all requirements. - CIBW_BEFORE_BUILD: - ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || - (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && - 'pip install meson-python pybind11 setuptools_scm "setuptools>=64" ninja && - pip install "numpy>=2.0.0.dev0" --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" && - rm -rf {package}/build' - ) || - 'pip install "numpy>=1.25" && - rm -rf {package}/build' - }} - CIBW_BEFORE_BUILD_WINDOWS: - ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || - (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && - 'pip install delvewheel meson-python pybind11 setuptools_scm "setuptools>=64" ninja && - pip install "numpy>=2.0.0.dev0" --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" && - rm -rf {package}/build' - ) || - 'pip install delvewheel "numpy>=1.25" && - rm -rf {package}/build' - }} + CIBW_BEFORE_BUILD: >- + rm -rf {package}/build + CIBW_BEFORE_BUILD_WINDOWS: >- + pip install delvewheel && + rm -rf {package}/build + # Live on the edge when building on main or a PR against main since + # these wheels are also used for nightlies and NumPy 2.0 transition + # requires using the NumPy 2.0 nightlies for compatibility. + # If using all `--pre` releases creates issues, the NumPy wheel can be + # istalled more targeted. CIBW_BUILD_FRONTEND: >- - ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || + ${{ (((github.event_name == 'main' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && - 'pip; args: --no-build-isolation') || 'build' }} + 'pip; args: --pre --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple"') || + 'build' }} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- delvewheel repair -w {dest_dir} {wheel} CIBW_AFTER_BUILD: >- From 490f82804fe927eac4fbf3bbe127dda10595e823 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 26 Oct 2023 19:51:56 +0200 Subject: [PATCH 0357/1120] Update .github/workflows/cibuildwheel.yml Co-authored-by: Thomas A Caswell --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 16a5c4fdde8f..981411671436 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -101,7 +101,7 @@ jobs: # If using all `--pre` releases creates issues, the NumPy wheel can be # istalled more targeted. CIBW_BUILD_FRONTEND: >- - ${{ (((github.event_name == 'main' && github.ref == 'refs/heads/main') || + ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && 'pip; args: --pre --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple"') || 'build' }} From 25ec2c0e43066c1bad3b0ca6111644cb8383181d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:57:55 +0100 Subject: [PATCH 0358/1120] Update find_nearest_contour to not use collections attribute Co-authored-by: Antony Lee --- lib/matplotlib/contour.py | 70 +++++++++------------------- lib/matplotlib/contour.pyi | 2 +- lib/matplotlib/tests/test_contour.py | 6 +-- 3 files changed, 27 insertions(+), 51 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..f65538f865a3 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -2,6 +2,7 @@ Classes to support contour plotting and labelling for the Axes class. """ +from contextlib import ExitStack import functools import math from numbers import Integral @@ -1409,64 +1410,39 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): Returns ------- - contour : `.Collection` - The contour that is closest to ``(x, y)``. - segment : int - The index of the `.Path` in *contour* that is closest to - ``(x, y)``. + path : int + The index of the path that is closest to ``(x, y)``. Each path corresponds + to one contour level. + subpath : int + The index within that closest path of the subpath that is closest to + ``(x, y)``. Each subpath corresponds to one unbroken contour line. index : int - The index of the path segment in *segment* that is closest to + The index of the vertices within that subpath that are closest to ``(x, y)``. xmin, ymin : float The point in the contour plot that is closest to ``(x, y)``. d2 : float The squared distance from ``(xmin, ymin)`` to ``(x, y)``. """ + segment = index = d2 = None - # This function uses a method that is probably quite - # inefficient based on converting each contour segment to - # pixel coordinates and then comparing the given point to - # those coordinates for each contour. This will probably be - # quite slow for complex contours, but for normal use it works - # sufficiently well that the time is not noticeable. - # Nonetheless, improvements could probably be made. + with ExitStack() as stack: + if not pixel: + # _find_nearest_contour works in pixel space. We want axes space, so + # effectively disable the transformation here by setting to identity. + stack.enter_context(self._cm_set( + transform=mtransforms.IdentityTransform())) - if self.filled: - raise ValueError("Method does not support filled contours.") + i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices) - if indices is None: - indices = range(len(self.collections)) + if i_level is not None: + cc_cumlens = np.cumsum( + [*map(len, self._paths[i_level]._iter_connected_components())]) + segment = cc_cumlens.searchsorted(i_vtx, "right") + index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1] + d2 = (xmin-x)**2 + (ymin-y)**2 - d2min = np.inf - conmin = None - segmin = None - imin = None - xmin = None - ymin = None - - point = np.array([x, y]) - - for icon in indices: - con = self.collections[icon] - trans = con.get_transform() - paths = con.get_paths() - - for segNum, linepath in enumerate(paths): - lc = linepath.vertices - # transfer all data points to screen coordinates if desired - if pixel: - lc = trans.transform(lc) - - d2, xc, leg = _find_closest_point_on_path(lc, point) - if d2 < d2min: - d2min = d2 - conmin = icon - segmin = segNum - imin = leg[1] - xmin = xc[0] - ymin = xc[1] - - return (conmin, segmin, imin, xmin, ymin, d2min) + return (i_level, segment, index, xmin, ymin, d2) def draw(self, renderer): paths = self._paths diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi index 4a1e1e1d9d2e..8a987b762445 100644 --- a/lib/matplotlib/contour.pyi +++ b/lib/matplotlib/contour.pyi @@ -159,6 +159,6 @@ class ContourSet(ContourLabeler, Collection): ) -> tuple[list[Artist], list[str]]: ... def find_nearest_contour( self, x: float, y: float, indices: Iterable[int] | None = ..., pixel: bool = ... - ) -> tuple[Collection, int, int, float, float, float]: ... + ) -> tuple[int, int, int, float, float, float]: ... class QuadContourSet(ContourSet): ... diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index c911d499ea96..81328b0b3218 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -557,15 +557,15 @@ def test_find_nearest_contour_no_filled(): cs = plt.contourf(img, 10) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 1, pixel=False) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True) From e9a102f090d91dae6745b7f6313ecc6040def959 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 12 Oct 2023 15:05:15 -0700 Subject: [PATCH 0359/1120] MNT: revert contour deprecations Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../deprecations/27088-JK.rst | 5 +++ lib/matplotlib/contour.py | 9 +++--- lib/matplotlib/tests/test_contour.py | 32 +++++++------------ 3 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/27088-JK.rst diff --git a/doc/api/next_api_changes/deprecations/27088-JK.rst b/doc/api/next_api_changes/deprecations/27088-JK.rst new file mode 100644 index 000000000000..ea7fef5abf64 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27088-JK.rst @@ -0,0 +1,5 @@ +Deprecations removed in ``contour`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``contour.allsegs``, ``contour.allkinds``, and ``contour.find_nearest_contour`` are no +longer marked for deprecation. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index f65538f865a3..58a6d7937731 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -930,12 +930,12 @@ def __init__(self, ax, *args, ", ".join(map(repr, kwargs)) ) - allsegs = _api.deprecated("3.8", pending=True)(property(lambda self: [ + allsegs = property(lambda self: [ [subp.vertices for subp in p._iter_connected_components()] - for p in self.get_paths()])) - allkinds = _api.deprecated("3.8", pending=True)(property(lambda self: [ + for p in self.get_paths()]) + allkinds = property(lambda self: [ [subp.codes for subp in p._iter_connected_components()] - for p in self.get_paths()])) + for p in self.get_paths()]) tcolors = _api.deprecated("3.8")(property(lambda self: [ (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) tlinewidths = _api.deprecated("3.8")(property(lambda self: [ @@ -1389,7 +1389,6 @@ def _find_nearest_contour(self, xy, indices=None): return idx_level_min, idx_vtx_min, proj_min - @_api.deprecated("3.8") def find_nearest_contour(self, x, y, indices=None, pixel=True): """ Find the point in the contour plot that is closest to ``(x, y)``. diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 81328b0b3218..b655649898bc 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -530,23 +530,19 @@ def test_find_nearest_contour(): img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2)) cs = plt.contour(img, 10) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(1, 1, pixel=False) + nearest_contour = cs.find_nearest_contour(1, 1, pixel=False) expected_nearest = (1, 0, 33, 1.965966, 1.965966, 1.866183) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(8, 1, pixel=False) + nearest_contour = cs.find_nearest_contour(8, 1, pixel=False) expected_nearest = (1, 0, 5, 7.550173, 1.587542, 0.547550) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(2, 5, pixel=False) + nearest_contour = cs.find_nearest_contour(2, 5, pixel=False) expected_nearest = (3, 0, 21, 1.884384, 5.023335, 0.013911) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(2, 5, indices=(5, 7), pixel=False) + nearest_contour = cs.find_nearest_contour(2, 5, indices=(5, 7), pixel=False) expected_nearest = (5, 0, 16, 2.628202, 5.0, 0.394638) assert_array_almost_equal(nearest_contour, expected_nearest) @@ -556,16 +552,13 @@ def test_find_nearest_contour_no_filled(): img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2)) cs = plt.contourf(img, 10) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 1, pixel=False) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True) @@ -825,12 +818,11 @@ def test_allsegs_allkinds(): cs = plt.contour(x, y, z, levels=[0, 0.5]) - # Expect two levels, first with 5 segments and the second with 4. - with pytest.warns(PendingDeprecationWarning, match="all"): - for result in [cs.allsegs, cs.allkinds]: - assert len(result) == 2 - assert len(result[0]) == 5 - assert len(result[1]) == 4 + # Expect two levels, the first with 5 segments and the second with 4. + for result in [cs.allsegs, cs.allkinds]: + assert len(result) == 2 + assert len(result[0]) == 5 + assert len(result[1]) == 4 def test_deprecated_apis(): From 6b08ac2857aa07a5037dc915057fcc810ac4b7b3 Mon Sep 17 00:00:00 2001 From: Matthew Morrison Date: Thu, 26 Oct 2023 15:58:20 -0400 Subject: [PATCH 0360/1120] Fixing sentence case for sections, adding info to empty note --- galleries/users_explain/figure/figure_intro.rst | 2 ++ galleries/users_explain/figure/interactive.rst | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index c126b1bcf4c0..610885fc21f2 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -80,6 +80,8 @@ other than the default "inline" backend, you will likely need to use an ipython .. sourcecode:: ipython + %matplotlib notebook + Standalone scripts and interactive use -------------------------------------- diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index 1e82d37f10f4..adcabaaca1d0 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -7,7 +7,7 @@ .. _interactive_figures: =================== -Interactive Figures +Interactive figures =================== When working with data, interactivity can be invaluable. The pan/zoom and @@ -62,7 +62,7 @@ collected. `.Figure`\s can be closed and deregistered from `.pyplot` individuall .. _ipython-pylab: -IPython Integration +IPython integration =================== We recommend using IPython for an interactive shell. In addition to @@ -314,7 +314,7 @@ Preserve aspect ratio hold **CONTROL** when panning/zooming with mo .. _other-shells: -Other Python Prompts +Other Python prompts ==================== Interactive mode works in the default Python prompt: From b0509d9c5c076b9341688094ab7d8556bde0d8b8 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 26 Oct 2023 22:00:59 +0200 Subject: [PATCH 0361/1120] Update .github/workflows/cibuildwheel.yml --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 981411671436..af733ff04502 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -102,7 +102,7 @@ jobs: # istalled more targeted. CIBW_BUILD_FRONTEND: >- ${{ (((github.event_name == 'push' && github.ref == 'refs/heads/main') || - (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/main')) && + (github.event_name == 'pull_request' && github.base_ref == 'main')) && 'pip; args: --pre --extra-index-url "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple"') || 'build' }} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >- From 71e1c63a6028aa24ccef1eb5138ba8a433c4eb8a Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Thu, 26 Oct 2023 16:20:26 -0400 Subject: [PATCH 0362/1120] Add test_hlines to test_datetimes.py --- lib/matplotlib/tests/test_datetime.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index c9455d450b70..3cac147c002f 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -218,11 +218,27 @@ def test_hist2d(self): fig, ax = plt.subplots() ax.hist2d(...) - @pytest.mark.xfail(reason="Test for hlines not written yet") @mpl.style.context("default") def test_hlines(self): - fig, ax = plt.subplots() - ax.hlines(...) + mpl.rcParams["date.converter"] = 'concise' + fig, axs = plt.subplots(2, 3, layout='constrained') + dateStrs = ['2023-03-08', + '2023-04-09', + '2023-05-13', + '2023-07-28', + '2023-12-24'] + dates = [datetime.datetime(2023, m*2, 10) for m in range(1, 6)] + npDates = [np.datetime64(s) for s in dateStrs] + axs[0, 0].hlines(y=dates, + xmin=[0.1, 0.2, 0.3, 0.4, 0.5], + xmax=[0.5, 0.6, 0.7, 0.8, 0.9]) + axs[0, 1].hlines(y=dates, xmin=0.2, xmax=0.8) + axs[0, 2].hlines(dates, xmin=0, xmax=1) + axs[1, 0].hlines(y=npDates, + xmin=[0.5, 0.6, 0.7, 0.8, 0.9], + xmax=[0.1, 0.2, 0.3, 0.4, 0.5]) + axs[1, 1].hlines(y=npDates, xmin=0.45, xmax=0.65) + axs[1, 2].hlines(npDates, xmin=0, xmax=1) @pytest.mark.xfail(reason="Test for imshow not written yet") @mpl.style.context("default") From 8b81c13cf394909b51ef66082a62ed00d749f8dc Mon Sep 17 00:00:00 2001 From: bersbersbers <12128514+bersbersbers@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:26:18 +0200 Subject: [PATCH 0363/1120] Add `@QtCore.Slot()` decorations --- lib/matplotlib/backends/backend_qt.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 2aa7874fbdb7..059eff768601 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -642,8 +642,13 @@ def __init__(self, canvas, parent=None, coordinates=True): if text is None: self.addSeparator() else: + slot = getattr(self, callback) + # https://bugreports.qt.io/browse/PYSIDE-2512 + slot = lambda slot=slot, *args: slot(*args) + slot = QtCore.Slot()(slot) + a = self.addAction(self._icon(image_file + '.png'), - text, getattr(self, callback)) + text, slot) self._actions[callback] = a if callback in ['zoom', 'pan']: a.setCheckable(True) From 498b5b126191d9f927891b7623fd85df034c7524 Mon Sep 17 00:00:00 2001 From: bersbersbers <12128514+bersbersbers@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:38:06 +0200 Subject: [PATCH 0364/1120] Avoid `lambda` expression for `flake8` --- lib/matplotlib/backends/backend_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 059eff768601..3d8882bf59fa 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -644,7 +644,7 @@ def __init__(self, canvas, parent=None, coordinates=True): else: slot = getattr(self, callback) # https://bugreports.qt.io/browse/PYSIDE-2512 - slot = lambda slot=slot, *args: slot(*args) + slot = functools.partial(slot) slot = QtCore.Slot()(slot) a = self.addAction(self._icon(image_file + '.png'), From 4ece8ab71d3073f263bb022c0dc0a4130589cf46 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 3 Oct 2023 17:56:47 -0400 Subject: [PATCH 0365/1120] prints font name in missing glyph warning lists all fallback fonts if glyph missing in all fonts removed ft_get_char_index_or_warn added a test for otf fonts on pdf + some windows fonts to tests Co-authored-by: Elliott Sales de Andrade Co-authored-by: Thomas A Caswell --- lib/matplotlib/_text_helpers.py | 9 ++-- lib/matplotlib/tests/test_backend_pdf.py | 16 ++++++ lib/matplotlib/tests/test_ft2font.py | 46 ++++++++++------- lib/matplotlib/tests/test_text.py | 5 +- src/ft2font.cpp | 63 +++++++++++++----------- src/ft2font.h | 2 + 6 files changed, 89 insertions(+), 52 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 18bfb550c90b..8625186ba8ec 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -12,11 +12,12 @@ "LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"]) -def warn_on_missing_glyph(codepoint): +def warn_on_missing_glyph(codepoint, fontnames): _api.warn_external( - "Glyph {} ({}) missing from current font.".format( - codepoint, - chr(codepoint).encode("ascii", "namereplace").decode("ascii"))) + f"Glyph {codepoint} " + f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) " + f"missing from font(s) {fontnames}.") + block = ("Hebrew" if 0x0590 <= codepoint <= 0x05ff else "Arabic" if 0x0600 <= codepoint <= 0x06ff else "Devanagari" if 0x0900 <= codepoint <= 0x097f else diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 68ce02cc366c..ae17c7a4a4e3 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -443,3 +443,19 @@ def test_multi_font_type42(): fig = plt.figure() fig.text(0.15, 0.475, "There are 几个汉字 in between!") + + +@pytest.mark.parametrize('family_name, file_name', + [("Noto Sans", "NotoSans-Regular.otf"), + ("FreeMono", "FreeMono.otf")]) +def test_otf_font_smoke(family_name, file_name): + # checks that there's no segfault + fp = fm.FontProperties(family=[family_name]) + if Path(fm.findfont(fp)).name != file_name: + pytest.skip(f"Font {family_name} may be missing") + + plt.rc('font', family=[family_name], size=27) + + fig = plt.figure() + fig.text(0.15, 0.475, "Привет мир!") + fig.savefig(io.BytesIO(), format="pdf") diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index 0139bdf41526..2e2ce673f4b8 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -30,25 +30,21 @@ def test_ft2font_positive_hinting_factor(): ft2font.FT2Font(file_name, 0) -def test_fallback_smoke(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font wqy-zenhei.ttc may be missing") - - fp = fm.FontProperties(family=["Noto Sans CJK JP"]) - if Path(fm.findfont(fp)).name != "NotoSansCJK-Regular.ttc": - pytest.skip("Noto Sans CJK JP font may be missing.") - +@pytest.mark.parametrize('family_name, file_name', + [("WenQuanYi Zen Hei", "wqy-zenhei.ttc"), + ("Noto Sans CJK JP", "NotoSansCJK.ttc"), + ("Noto Sans TC", "NotoSansTC-Regular.otf")] + ) +def test_fallback_smoke(family_name, file_name): + fp = fm.FontProperties(family=[family_name]) + if Path(fm.findfont(fp)).name != file_name: + pytest.skip(f"Font {family_name} ({file_name}) is missing") plt.rcParams['font.size'] = 20 fig = plt.figure(figsize=(4.75, 1.85)) fig.text(0.05, 0.45, "There are 几个汉字 in between!", - family=['DejaVu Sans', "Noto Sans CJK JP"]) - fig.text(0.05, 0.25, "There are 几个汉字 in between!", - family=['DejaVu Sans', "WenQuanYi Zen Hei"]) - fig.text(0.05, 0.65, "There are 几个汉字 in between!", - family=["Noto Sans CJK JP"]) + family=['DejaVu Sans', family_name]) fig.text(0.05, 0.85, "There are 几个汉字 in between!", - family=["WenQuanYi Zen Hei"]) + family=[family_name]) # TODO enable fallback for other backends! for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]: @@ -57,7 +53,8 @@ def test_fallback_smoke(): @pytest.mark.parametrize('family_name, file_name', [("WenQuanYi Zen Hei", "wqy-zenhei"), - ("Noto Sans CJK JP", "NotoSansCJK")] + ("Noto Sans CJK JP", "NotoSansCJK"), + ("Noto Sans TC", "NotoSansTC-Regular.otf")] ) @check_figures_equal(extensions=["png", "pdf", "eps", "svg"]) def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name): @@ -78,11 +75,27 @@ def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name): fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font) +@pytest.mark.parametrize("font_list", + [['DejaVu Serif', 'DejaVu Sans'], + ['DejaVu Sans Mono']], + ids=["two fonts", "one font"]) +def test_fallback_missing(recwarn, font_list): + fig = plt.figure() + fig.text(.5, .5, "Hello 🙃 World!", family=font_list) + fig.canvas.draw() + assert all(isinstance(warn.message, UserWarning) for warn in recwarn) + # not sure order is guaranteed on the font listing so + assert recwarn[0].message.args[0].startswith( + "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)") + assert all([font in recwarn[0].message.args[0] for font in font_list]) + + @pytest.mark.parametrize( "family_name, file_name", [ ("WenQuanYi Zen Hei", "wqy-zenhei"), ("Noto Sans CJK JP", "NotoSansCJK"), + ("Noto Sans TC", "NotoSansTC-Regular.otf") ], ) def test__get_fontmap(family_name, file_name): @@ -97,7 +110,6 @@ def test__get_fontmap(family_name, file_name): fm.FontProperties(family=["DejaVu Sans", family_name]) ) ) - fontmap = ft._get_fontmap(text) for char, font in fontmap.items(): if ord(char) > 127: diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 7222d5b00cf7..3c14357e45f3 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -817,12 +817,13 @@ def test_pdf_kerning(): def test_unsupported_script(recwarn): fig = plt.figure() - fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") + t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") fig.canvas.draw() assert all(isinstance(warn.message, UserWarning) for warn in recwarn) assert ( [warn.message.args for warn in recwarn] == - [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from current font.",), + [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) " + + f"{t.get_fontname()}.",), (r"Matplotlib currently does not support Bengali natively.",)]) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index fbc00dc69696..ced138f9a2af 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -184,11 +185,20 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, m_dirty = true; } -static void ft_glyph_warn(FT_ULong charcode) +static void ft_glyph_warn(FT_ULong charcode, std::set family_names) { PyObject *text_helpers = NULL, *tmp = NULL; + std::set::iterator it = family_names.begin(); + std::stringstream ss; + ss<<*it; + while(++it != family_names.end()){ + ss<<", "<<*it; + } + if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) || - !(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) { + !(tmp = PyObject_CallMethod(text_helpers, + "warn_on_missing_glyph", "(k, s)", + charcode, ss.str().c_str()))) { goto exit; } exit: @@ -199,19 +209,6 @@ static void ft_glyph_warn(FT_ULong charcode) } } -static FT_UInt -ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true) -{ - FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - if (glyph_index) { - return glyph_index; - } - if (warn) { - ft_glyph_warn(charcode); - } - return 0; -} - // ft_outline_decomposer should be passed to FT_Outline_Decompose. On the // first pass, vertices and codes are set to NULL, and index is simply // incremented for each vertex that should be inserted, so that it is set, at @@ -510,13 +507,13 @@ void FT2Font::set_text( FT_Pos last_advance; FT_Error charcode_error, glyph_error; + std::set glyph_seen_fonts; FT2Font *ft_object_with_glyph = this; bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs, char_to_font, glyph_to_font, codepoints[n], flags, - charcode_error, glyph_error, false); + charcode_error, glyph_error, glyph_seen_fonts, false); if (!was_found) { - ft_glyph_warn((FT_ULong)codepoints[n]); - + ft_glyph_warn((FT_ULong)codepoints[n], glyph_seen_fonts); // render missing glyph tofu // come back to top-most font ft_object_with_glyph = this; @@ -570,6 +567,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool // if this is parent FT2Font, cache will be filled in 2 ways: // 1. set_text was previously called // 2. set_text was not called and fallback was enabled + std::set glyph_seen_fonts; if (fallback && char_to_font.find(charcode) != char_to_font.end()) { ft_object = char_to_font[charcode]; // since it will be assigned to ft_object anyway @@ -579,10 +577,12 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool FT_UInt final_glyph_index; FT_Error charcode_error, glyph_error; FT2Font *ft_object_with_glyph = this; - bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font, - glyph_to_font, charcode, flags, charcode_error, glyph_error, true); + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, + glyphs, char_to_font, glyph_to_font, + charcode, flags, charcode_error, glyph_error, + glyph_seen_fonts, true); if (!was_found) { - ft_glyph_warn(charcode); + ft_glyph_warn(charcode, glyph_seen_fonts); if (charcode_error) { throw_ft_error("Could not load charcode", charcode_error); } @@ -592,9 +592,13 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool } ft_object = ft_object_with_glyph; } else { + //no fallback case ft_object = this; - FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); - + FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode); + if (!glyph_index){ + glyph_seen_fonts.insert((face != NULL)?face->family_name: NULL); + ft_glyph_warn((FT_ULong)charcode, glyph_seen_fonts); + } if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { throw_ft_error("Could not load charcode", error); } @@ -640,19 +644,21 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, FT_Int32 flags, FT_Error &charcode_error, FT_Error &glyph_error, + std::set &glyph_seen_fonts, bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + glyph_seen_fonts.insert(face->family_name); if (glyph_index || override) { + charcode_error = FT_Load_Glyph(face, glyph_index, flags); if (charcode_error) { return false; } - FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (glyph_error) { + if (glyph_error){ return false; } @@ -667,12 +673,12 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, parent_glyphs.push_back(thisGlyph); return true; } - else { for (size_t i = 0; i < fallbacks.size(); ++i) { bool was_found = fallbacks[i]->load_char_with_fallback( ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, - parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override); + parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, + glyph_seen_fonts, override); if (was_found) { return true; } @@ -721,8 +727,7 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) ft_object = this; } - // historically, get_char_index never raises a warning - return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false); + return FT_Get_Char_Index(ft_object->get_face(), charcode); } void FT2Font::get_width_height(long *width, long *height) diff --git a/src/ft2font.h b/src/ft2font.h index d566c3f9bd9d..69dafb66bdca 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -5,6 +5,7 @@ #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H #include +#include #include #include @@ -91,6 +92,7 @@ class FT2Font FT_Int32 flags, FT_Error &charcode_error, FT_Error &glyph_error, + std::set &glyph_seen_fonts, bool override); void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback); void load_glyph(FT_UInt glyph_index, FT_Int32 flags); From 15c44a5a474f1213179882973c4096886c0fe1e7 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 27 Oct 2023 12:56:58 -0400 Subject: [PATCH 0366/1120] mend --- src/ft2font.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index ced138f9a2af..b20f224715bf 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -651,14 +651,13 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, glyph_seen_fonts.insert(face->family_name); if (glyph_index || override) { - charcode_error = FT_Load_Glyph(face, glyph_index, flags); if (charcode_error) { return false; } FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (glyph_error){ + if (glyph_error) { return false; } @@ -676,9 +675,9 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, else { for (size_t i = 0; i < fallbacks.size(); ++i) { bool was_found = fallbacks[i]->load_char_with_fallback( - ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, - parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, - glyph_seen_fonts, override); + ft_object_with_glyph, final_glyph_index, parent_glyphs, + parent_char_to_font, parent_glyph_to_font, charcode, flags, + charcode_error, glyph_error, glyph_seen_fonts, override); if (was_found) { return true; } From 86672afb63d0484e90ca6f6fb216662e8fd26d25 Mon Sep 17 00:00:00 2001 From: esibinga Date: Thu, 21 Sep 2023 00:14:07 -0400 Subject: [PATCH 0367/1120] carriage breaks --- galleries/examples/tag_guidelines.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 2fac2f8712c9..470b1b93e4c6 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -18,9 +18,9 @@ Every gallery example should be tagged with: * 1+ content tags * structural, domain, or internal tag(s) if helpful -Examples with many tags may indicate that the entry is a showcase example. + Examples with many tags may indicate that the entry is a showcase example. -Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). + Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. From af6366e1891d0ec427f3273e5cda3a92c0aec6fb Mon Sep 17 00:00:00 2001 From: esibinga Date: Thu, 21 Sep 2023 00:19:36 -0400 Subject: [PATCH 0368/1120] tabs --- galleries/examples/tag_guidelines.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 470b1b93e4c6..5d360240bbe2 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -38,7 +38,7 @@ IV. Internal tags: what information is helpful for maintainers or contributors? 3. New tags should be be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. 4. Tags can recreate other forms of organization. -Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. + Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. *How to tag?* where you put the directive is where the tag will show From c87995e661c2d80e245806c2fd6ac4525c93c2c4 Mon Sep 17 00:00:00 2001 From: esibinga Date: Thu, 21 Sep 2023 00:23:27 -0400 Subject: [PATCH 0369/1120] indentation --- galleries/examples/tag_guidelines.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 5d360240bbe2..3354abfd963c 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -13,14 +13,14 @@ Guidelines for assigning tags to gallery examples *What gets a tag?* -Every gallery example should be tagged with: + Every gallery example should be tagged with: -* 1+ content tags -* structural, domain, or internal tag(s) if helpful + * 1+ content tags + * structural, domain, or internal tag(s) if helpful - Examples with many tags may indicate that the entry is a showcase example. + Examples with many tags may indicate that the entry is a showcase example. - Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). + Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. From c289586d91e92888d8686dae15dc6c77acd7a9cf Mon Sep 17 00:00:00 2001 From: esibinga Date: Thu, 5 Oct 2023 23:39:46 -0400 Subject: [PATCH 0370/1120] styling changes and Melissa's suggested changes --- galleries/examples/tag_guidelines.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 3354abfd963c..2fac2f8712c9 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -13,14 +13,14 @@ Guidelines for assigning tags to gallery examples *What gets a tag?* - Every gallery example should be tagged with: +Every gallery example should be tagged with: - * 1+ content tags - * structural, domain, or internal tag(s) if helpful +* 1+ content tags +* structural, domain, or internal tag(s) if helpful - Examples with many tags may indicate that the entry is a showcase example. +Examples with many tags may indicate that the entry is a showcase example. - Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). +Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. @@ -38,7 +38,7 @@ IV. Internal tags: what information is helpful for maintainers or contributors? 3. New tags should be be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. 4. Tags can recreate other forms of organization. - Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. +Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. *How to tag?* where you put the directive is where the tag will show From ad4dee28baa63a34be5f97cce6015098f898c714 Mon Sep 17 00:00:00 2001 From: esibinga Date: Fri, 27 Oct 2023 14:24:49 -0400 Subject: [PATCH 0371/1120] changes to level and purpose --- galleries/examples/tag_glossary.rst | 80 +++++++++++++++++++---------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/galleries/examples/tag_glossary.rst b/galleries/examples/tag_glossary.rst index 439524f0b99c..5b4b79d07b5e 100644 --- a/galleries/examples/tag_glossary.rst +++ b/galleries/examples/tag_glossary.rst @@ -34,6 +34,9 @@ Tag Glossary +-----------------------------------+---------------------------------------------+ |``styling: small-multiples`` | | +-----------------------------------+---------------------------------------------+ +|``styling: conditional`` |styling is determined programmatically by a | +| |condition being met | ++-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ |**Primary or relevant plot component** | @@ -66,18 +69,28 @@ Tag Glossary +-----------------------------------+---------------------------------------------+ |``component: error`` | | +-----------------------------------+---------------------------------------------+ +|``component: animation`` | | ++-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ |**Interactivity** | +-----------------------------------+---------------------------------------------+ |``interactivity: event handling`` | | +-----------------------------------+---------------------------------------------+ +|``interactivity: click`` | | ++-----------------------------------+---------------------------------------------+ +|``interactivity: mouseover`` | | ++-----------------------------------+---------------------------------------------+ |``interactivity: zoom`` | | +-----------------------------------+---------------------------------------------+ |``interactivity: pan`` | | +-----------------------------------+---------------------------------------------+ |``interactivity: brush`` | | +-----------------------------------+---------------------------------------------+ +|``interactivity: drag`` | | ++-----------------------------------+---------------------------------------------+ +|``interactivity: scroll`` | | ++-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ |**Plot Type** | @@ -96,27 +109,33 @@ Tag Glossary +-----------------------------------+---------------------------------------------+ |``plot type: specialty`` | | +-----------------------------------+---------------------------------------------+ -|``plot type: `` | | +|``plot type: scatter`` | | +-----------------------------------+---------------------------------------------+ *2. Structural tags: what format is the example? What context can we provide?* -+-----------------------+-------------------------------------------------------------------+ -|``tag`` | use case | -+=======================+===================================================================+ -|``level: reference`` |reference docs like "marker reference" or "list of named colors" | -| | - dense collection of short-format information | -| | - well-defined scope, accessible information | -+-----------------------+-------------------------------------------------------------------+ -|``level`` |level refers to how much context/background the user will need | -+-----------------------+-------------------------------------------------------------------+ -|``level: beginner`` |concepts are standalone, self-contained, require only one module | -+-----------------------+-------------------------------------------------------------------+ -|``level: intermediate``|concepts may require knowledge of other modules, have dependencies | -+-----------------------+-------------------------------------------------------------------+ -|``level: advanced`` |concepts require multiple modules and have dependencies | -+-----------------------+-------------------------------------------------------------------+ ++----------------------------+-------------------------------------------------------------------+ +|``tag`` | use case | ++============================+===================================================================+ +|``level`` |level refers to how much context/background the user will need | ++----------------------------+-------------------------------------------------------------------+ +|``level: beginner`` |concepts are standalone, self-contained, require only one module | ++----------------------------+-------------------------------------------------------------------+ +|``level: intermediate`` |concepts may require knowledge of other modules, have dependencies | ++----------------------------+-------------------------------------------------------------------+ +|``level: advanced`` |concepts require multiple modules and have dependencies | ++----------------------------+-------------------------------------------------------------------+ +|``purpose`` |what's it here for? | ++----------------------------+-------------------------------------------------------------------+ +|``purpose: storytelling`` |storytelling exemplar -- build a visual argument | ++----------------------------+-------------------------------------------------------------------+ +|``purpose: reference`` |reference docs like "marker reference" or "list of named colors" | +| | - dense collection of short-format information | +| | - well-defined scope, accessible information | ++----------------------------+-------------------------------------------------------------------+ +|``purpose: fun`` |just for fun! | ++----------------------------+-------------------------------------------------------------------+ *3. Domain tags: what discipline(s) might seek this example consistently?* @@ -124,7 +143,7 @@ Note: it's futile to draw fences around "who owns what", and that's not the poin +-------------------------------+----------------------------------------+ |``tag`` | use case | -+=======================+================================================+ ++===============================+========================================+ |``domain`` |for whom is the example relevant? | +-------------------------------+----------------------------------------+ |``domain: cartography`` | | @@ -142,14 +161,19 @@ Note: it's futile to draw fences around "who owns what", and that's not the poin *4. Internal tags: what information is helpful for maintainers or contributors?* -+---------------------------+-----------------------------------------------------------------------+ -|``tag`` | use case | -+===========================+=======================================================================+ -|``internal: low bandwidth``|allows users to filter out bandwidth-intensive examples like animations| -+---------------------------+-----------------------------------------------------------------------+ -|``internal: untagged`` |allows docs contributors to easily find untagged examples | -+---------------------------+-----------------------------------------------------------------------+ -|``internal: deprecated`` |examples containing deprecated functionality or concepts | -+---------------------------+-----------------------------------------------------------------------+ -|``internal: needs-review`` |example needs to be reviewed for accuracy or pedagogical value | -+---------------------------+-----------------------------------------------------------------------+ ++-------------------------------+-----------------------------------------------------------------------+ +|``tag`` | use case | ++===============================+=======================================================================+ +|``internal: low bandwidth`` |allows users to filter out bandwidth-intensive examples like animations| ++-------------------------------+-----------------------------------------------------------------------+ +|``internal: untagged`` |allows docs contributors to easily find untagged examples | ++-------------------------------+-----------------------------------------------------------------------+ +|``internal: deprecated`` |examples containing deprecated functionality or concepts | ++-------------------------------+-----------------------------------------------------------------------+ +|``internal: needs-review`` |example needs to be reviewed for accuracy or pedagogical value | ++-------------------------------+-----------------------------------------------------------------------+ +|``internal: outstanding-todo`` |example has an unfinished to-do | ++-------------------------------+-----------------------------------------------------------------------+ +|``internal: too-much`` |the example should be refined, split into multiple examples, or | +| |reformatted into a tutorial or reference | ++-------------------------------+-----------------------------------------------------------------------+ From 2ae0421894a9b4efada67120c275c2aa06954691 Mon Sep 17 00:00:00 2001 From: Eva Sibinga <46283995+esibinga@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:03:27 -0400 Subject: [PATCH 0372/1120] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Melissa Weber Mendonça --- galleries/examples/tag_glossary.rst | 10 ++++------ galleries/examples/tag_guidelines.rst | 16 ++++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/galleries/examples/tag_glossary.rst b/galleries/examples/tag_glossary.rst index 5b4b79d07b5e..8aa7ebb18c60 100644 --- a/galleries/examples/tag_glossary.rst +++ b/galleries/examples/tag_glossary.rst @@ -3,10 +3,10 @@ Tag Glossary ============ - I. API tags: what content from the API reference is in the example? - II. Structural tags: what format is the example? What context can we provide? - III. Domain tags: what discipline(s) might seek this example consistently? - IV. Internal tags: what information is helpful for maintainers or contributors? +I. API tags: what content from the API reference is in the example? +II. Structural tags: what format is the example? What context can we provide? +III. Domain tags: what discipline(s) might seek this example consistently? +IV. Internal tags: what information is helpful for maintainers or contributors? *1. API tags: what content from the API reference is in the example?* @@ -156,8 +156,6 @@ Note: it's futile to draw fences around "who owns what", and that's not the poin +-------------------------------+----------------------------------------+ |``domain: signal-processing`` | | +-------------------------------+----------------------------------------+ -| | | -+-------------------------------+----------------------------------------+ *4. Internal tags: what information is helpful for maintainers or contributors?* diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 2fac2f8712c9..1128699513eb 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -3,13 +3,13 @@ Guidelines for assigning tags to gallery examples *Why do we need tags?* - Tags serve multiple purposes. +Tags serve multiple purposes. - Tags have a one-to-many organization (i.e. one example can have several tags), while the gallery structure requires that examples are placed in only one location. This means tags provide a secondary layer of organization and make the gallery of examples more flexible and more user-friendly. +Tags have a one-to-many organization (i.e. one example can have several tags), while the gallery structure requires that examples are placed in only one location. This means tags provide a secondary layer of organization and make the gallery of examples more flexible and more user-friendly. - They allow for better discoverability, search, and browse functions. They are helpful for users struggling to write a search query for what they're looking for. +They allow for better discoverability, search, and browse functions. They are helpful for users struggling to write a search query for what they're looking for. - Hidden tags provide additional functionality for maintainers and contributors. +Hidden tags provide additional functionality for maintainers and contributors. *What gets a tag?* @@ -33,10 +33,10 @@ IV. Internal tags: what information is helpful for maintainers or contributors? *Proposing new tags* - 1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``). - 2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two parts: ``subcategory: tag``. Tags should be one or two words. - 3. New tags should be be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. - 4. Tags can recreate other forms of organization. +1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``). +2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two parts: ``subcategory: tag``. Tags should be one or two words. +3. New tags should be be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. +4. Tags can recreate other forms of organization. Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. From 44c57b4a3e33fbd5e7c3696e937c43691a484e21 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 27 Oct 2023 15:17:01 -0500 Subject: [PATCH 0373/1120] Fix type hints for undeprecated contour APIs They whad been in the limbo land of deprecated APIs that I didn't bother to ensure were the _best_ type hints because they were deprecated. So I just added them to the allowlist for stubtest, but now that they are undeprecated, make sure that they are correct. While I'm here, may as well get rid of the allow list entries for all of them, even the ones that are still deprecated, as that is documented standard practice going forward that pyi match implementation for deprecations as well --- ci/mypy-stubtest-allowlist.txt | 6 ------ lib/matplotlib/contour.pyi | 11 ++++++++--- lib/matplotlib/path.pyi | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e9256de10b36..9f3efd283906 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -41,12 +41,6 @@ matplotlib.figure.Figure.set_tight_layout matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap -# 3.8 deprecations -matplotlib.contour.ContourSet.allkinds -matplotlib.contour.ContourSet.allsegs -matplotlib.contour.ContourSet.tcolors -matplotlib.contour.ContourSet.tlinewidths - # positional-only argument name lacking leading underscores matplotlib.axes._base._AxesBase.axis diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi index 8a987b762445..d7bddfe8f1f5 100644 --- a/lib/matplotlib/contour.pyi +++ b/lib/matplotlib/contour.pyi @@ -103,12 +103,17 @@ class ContourSet(ContourLabeler, Collection): clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None labelTexts: list[Text] labelCValues: list[ColorType] - allkinds: list[np.ndarray] - tcolors: list[tuple[float, float, float, float]] + @property + def tcolors(self) -> list[tuple[tuple[float, float, float, float]]]: ... # only for not filled - tlinewidths: list[tuple[float]] + @property + def tlinewidths(self) -> list[tuple[float]]: ... + @property + def allkinds(self) -> list[list[np.ndarray | None]]: ... + @property + def allsegs(self) -> list[list[np.ndarray]]: ... @property def alpha(self) -> float | None: ... @property diff --git a/lib/matplotlib/path.pyi b/lib/matplotlib/path.pyi index c96c5a0ba9e4..464fc6d9a912 100644 --- a/lib/matplotlib/path.pyi +++ b/lib/matplotlib/path.pyi @@ -30,7 +30,7 @@ class Path: @vertices.setter def vertices(self, vertices: ArrayLike) -> None: ... @property - def codes(self) -> ArrayLike: ... + def codes(self) -> ArrayLike | None: ... @codes.setter def codes(self, codes: ArrayLike) -> None: ... @property From e5a8ef7550d559420611d49ba726dea209c61576 Mon Sep 17 00:00:00 2001 From: dohyun Date: Fri, 27 Oct 2023 19:33:00 -0400 Subject: [PATCH 0374/1120] regularized formatting --- .../examples/text_labels_and_annotations/angle_annotation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/text_labels_and_annotations/angle_annotation.py b/galleries/examples/text_labels_and_annotations/angle_annotation.py index 419d094bb6a9..fa46d8674443 100644 --- a/galleries/examples/text_labels_and_annotations/angle_annotation.py +++ b/galleries/examples/text_labels_and_annotations/angle_annotation.py @@ -30,7 +30,7 @@ # %% # AngleAnnotation class -# ~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^ # The essential idea here is to subclass `~.patches.Arc` and set its transform # to the `~.transforms.IdentityTransform`, making the parameters of the arc # defined in pixel space. @@ -215,7 +215,7 @@ def R(a, r, w, h): # .. _angle-annotation-usage: # # Usage -# ~~~~~ +# ^^^^^ # # Required arguments to ``AngleAnnotation`` are the center of the arc, *xy*, # and two points, such that the arc spans between the two vectors connecting From 10a23c89a0e64c2009d9b2ed663708e097dbcf3b Mon Sep 17 00:00:00 2001 From: dohyun Date: Fri, 27 Oct 2023 19:35:46 -0400 Subject: [PATCH 0375/1120] regularized formatting --- .../subplots_axes_and_figures/axes_box_aspect.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py index 9ad5a14b7649..00fdeff17ceb 100644 --- a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py +++ b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py @@ -15,7 +15,7 @@ # %% # A square axes, independent of data -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # Produce a square axes, no matter what the data limits are. @@ -31,7 +31,7 @@ # %% # Shared square axes -# ~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^ # # Produce shared subplots that are squared in size. # @@ -47,7 +47,7 @@ # %% # Square twin axes -# ~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^ # # Produce a square axes, with a twin axes. The twinned axes takes over the # box aspect of the parent. @@ -67,7 +67,7 @@ # %% # Normal plot next to image -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^ # # When creating an image plot with fixed data aspect and the default # ``adjustable="box"`` next to a normal plot, the axes would be unequal in @@ -90,7 +90,7 @@ # %% # Square joint/marginal plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^ # # It may be desirable to show marginal distributions next to a plot of joint # data. The following creates a square plot with the box aspect of the @@ -116,7 +116,7 @@ # %% # Square joint/marginal plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^ # # When setting the box aspect, one may still set the data aspect as well. # Here we create an Axes with a box twice as long as tall and use an "equal" @@ -133,7 +133,7 @@ # %% # Box aspect for many subplots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # It is possible to pass the box aspect to an Axes at initialization. The # following creates a 2 by 3 subplot grid with all square Axes. From 40c67564b35c4b8114b9bf159b5552b080868f9b Mon Sep 17 00:00:00 2001 From: bersbersbers <12128514+bersbersbers@users.noreply.github.com> Date: Sat, 28 Oct 2023 08:18:46 +0200 Subject: [PATCH 0376/1120] Maintain `__name__` for `PyQt` --- lib/matplotlib/backends/backend_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 3d8882bf59fa..07849728b4c8 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -644,7 +644,7 @@ def __init__(self, canvas, parent=None, coordinates=True): else: slot = getattr(self, callback) # https://bugreports.qt.io/browse/PYSIDE-2512 - slot = functools.partial(slot) + slot = functools.wraps(slot)(functools.partial(slot)) slot = QtCore.Slot()(slot) a = self.addAction(self._icon(image_file + '.png'), From faddff9a21095a9f2777016dcd244904812f8441 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sat, 28 Oct 2023 09:28:14 -0600 Subject: [PATCH 0377/1120] FIX: Enable interrupts on macosx event loops Follow the same logic with a context manager for the handling of the event interrupts so that we can re-use the logic from the main loop implementation in the start_event_loop function as well. --- lib/matplotlib/backends/backend_macosx.py | 75 +++++++++++++++-------- src/_macosx.m | 6 +- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index ecf21b07aef4..a39f5b5b1497 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -1,3 +1,4 @@ +import contextlib import os import signal import socket @@ -106,6 +107,13 @@ def resize(self, width, height): ResizeEvent("resize_event", self)._process() self.draw_idle() + def start_event_loop(self, timeout=0): + # docstring inherited + with _maybe_allow_interrupt(): + # Call the objc implementation of the event loop after + # setting up the interrupt handling + self._start_event_loop(timeout=timeout) + class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): @@ -171,34 +179,8 @@ def start_main_loop(cls): # Set up a SIGINT handler to allow terminating a plot via CTRL-C. # The logic is largely copied from qt_compat._maybe_allow_interrupt; see its # docstring for details. Parts are implemented by wake_on_fd_write in ObjC. - - old_sigint_handler = signal.getsignal(signal.SIGINT) - if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): - _macosx.show() - return - - handler_args = None - wsock, rsock = socket.socketpair() - wsock.setblocking(False) - rsock.setblocking(False) - old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) - _macosx.wake_on_fd_write(rsock.fileno()) - - def handle(*args): - nonlocal handler_args - handler_args = args - _macosx.stop() - - signal.signal(signal.SIGINT, handle) - try: + with _maybe_allow_interrupt(): _macosx.show() - finally: - wsock.close() - rsock.close() - signal.set_wakeup_fd(old_wakeup_fd) - signal.signal(signal.SIGINT, old_sigint_handler) - if handler_args is not None: - old_sigint_handler(*handler_args) def show(self): if not self._shown: @@ -208,6 +190,45 @@ def show(self): self._raise() +@contextlib.contextmanager +def _maybe_allow_interrupt(): + """ + This manager allows to terminate a plot by sending a SIGINT. It is + necessary because the running backend prevents Python interpreter to + run and process signals (i.e., to raise KeyboardInterrupt exception). To + solve this one needs to somehow wake up the interpreter and make it close + the plot window. The implementation is taken from qt_compat, see that + docstring for a more detailed description. + """ + old_sigint_handler = signal.getsignal(signal.SIGINT) + if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): + yield + return + + handler_args = None + wsock, rsock = socket.socketpair() + wsock.setblocking(False) + rsock.setblocking(False) + old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) + _macosx.wake_on_fd_write(rsock.fileno()) + + def handle(*args): + nonlocal handler_args + handler_args = args + _macosx.stop() + + signal.signal(signal.SIGINT, handle) + try: + yield + finally: + wsock.close() + rsock.close() + signal.set_wakeup_fd(old_wakeup_fd) + signal.signal(signal.SIGINT, old_sigint_handler) + if handler_args is not None: + old_sigint_handler(*handler_args) + + @_Backend.export class _BackendMac(_Backend): FigureCanvas = FigureCanvasMac diff --git a/src/_macosx.m b/src/_macosx.m index e9677a40e6b5..6df00d0eca8e 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -451,7 +451,7 @@ int mpl_check_modifier( } static PyObject* -FigureCanvas_start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) +FigureCanvas__start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { float timeout = 0.0; @@ -522,8 +522,8 @@ int mpl_check_modifier( (PyCFunction)FigureCanvas_remove_rubberband, METH_NOARGS, "Remove the current rubberband rectangle."}, - {"start_event_loop", - (PyCFunction)FigureCanvas_start_event_loop, + {"_start_event_loop", + (PyCFunction)FigureCanvas__start_event_loop, METH_KEYWORDS | METH_VARARGS, NULL}, // docstring inherited {"stop_event_loop", From b6d26414a75ba6ed912625c489cfd94f13cb22e3 Mon Sep 17 00:00:00 2001 From: Eva Sibinga <46283995+esibinga@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:00:44 -0400 Subject: [PATCH 0378/1120] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Melissa Weber Mendonça --- galleries/examples/tag_glossary.rst | 12 ++++++++---- galleries/examples/tag_guidelines.rst | 23 +++++++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/galleries/examples/tag_glossary.rst b/galleries/examples/tag_glossary.rst index 8aa7ebb18c60..2bb2057d003f 100644 --- a/galleries/examples/tag_glossary.rst +++ b/galleries/examples/tag_glossary.rst @@ -9,7 +9,8 @@ III. Domain tags: what discipline(s) might seek this example consistently? IV. Internal tags: what information is helpful for maintainers or contributors? -*1. API tags: what content from the API reference is in the example?* +1. API tags: what content from the API reference is in the example? +------------------------------------------------------------------- +-----------------------------------+---------------------------------------------+ |``tag`` | use case - if not obvious | @@ -113,7 +114,8 @@ IV. Internal tags: what information is helpful for maintainers or contributors? +-----------------------------------+---------------------------------------------+ -*2. Structural tags: what format is the example? What context can we provide?* +2. Structural tags: what format is the example? What context can we provide? +---------------------------------------------------------------------------- +----------------------------+-------------------------------------------------------------------+ |``tag`` | use case | @@ -137,7 +139,8 @@ IV. Internal tags: what information is helpful for maintainers or contributors? |``purpose: fun`` |just for fun! | +----------------------------+-------------------------------------------------------------------+ -*3. Domain tags: what discipline(s) might seek this example consistently?* +3. Domain tags: what discipline(s) might seek this example consistently? +------------------------------------------------------------------------ Note: it's futile to draw fences around "who owns what", and that's not the point of domain tags. Domain tags help groups of people to privately organize relevant information, and so are not displayed publicly. See below for a list of existing domain tags. If you don't see the one you're looking for and you think it should exist, consider proposing it. @@ -157,7 +160,8 @@ Note: it's futile to draw fences around "who owns what", and that's not the poin |``domain: signal-processing`` | | +-------------------------------+----------------------------------------+ -*4. Internal tags: what information is helpful for maintainers or contributors?* +4. Internal tags: what information is helpful for maintainers or contributors? +------------------------------------------------------------------------------ +-------------------------------+-----------------------------------------------------------------------+ |``tag`` | use case | diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index 1128699513eb..d6061fc16647 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -1,7 +1,8 @@ Guidelines for assigning tags to gallery examples ================================================= -*Why do we need tags?* +Why do we need tags? +-------------------- Tags serve multiple purposes. @@ -11,7 +12,8 @@ They allow for better discoverability, search, and browse functions. They are he Hidden tags provide additional functionality for maintainers and contributors. -*What gets a tag?* +What gets a tag? +---------------- Every gallery example should be tagged with: @@ -24,14 +26,15 @@ Tags can repeat existing forms of organization (e.g. an example is in the Animat Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. -*Tag Categories* - See :doc:`Tag Glossary ` for a complete list of tags. +**Tag Categories** - See :doc:`Tag Glossary ` for a complete list of tags. I. API tags: what content from the API reference is in the example? II. Structural tags: what format is the example? What context can we provide? III. Domain tags: what discipline(s) might seek this example consistently? IV. Internal tags: what information is helpful for maintainers or contributors? -*Proposing new tags* +Proposing new tags +------------------ 1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``). 2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two parts: ``subcategory: tag``. Tags should be one or two words. @@ -40,12 +43,15 @@ IV. Internal tags: what information is helpful for maintainers or contributors? Note: Tagging organization aims to work for 80-90% of cases. Some examples fall outside of the tagging structure. Niche or specific examples shouldn't be given standalone tags that won't apply to other examples. -*How to tag?* +How to tag? +----------- where you put the directive is where the tag will show -**Related content** +Related content +--------------- -*What is a gallery example?* +What is a gallery example? +^^^^^^^^^^^^^^^^^^^^^^^^^^ The gallery of examples contains visual demonstrations of matplolib features. Gallery examples exist so that users can scan through visual examples. @@ -53,7 +59,8 @@ Unlike tutorials or user guides, gallery examples teach by demonstration, rather Gallery examples should avoid instruction or excessive explanation except for brief clarifying code comments. Instead, they can tag related concepts and/or link to relevant tutorials or user guides. -Format: +Format +^^^^^^ All gallery examples should aim to follow the following format: From ed45c85bba7dd3d7e92ea366d67e0eb05667baa0 Mon Sep 17 00:00:00 2001 From: Eva Sibinga <46283995+esibinga@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:04:07 -0400 Subject: [PATCH 0379/1120] Apply suggestions from code review Co-authored-by: hannah --- galleries/examples/tag_guidelines.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/tag_guidelines.rst b/galleries/examples/tag_guidelines.rst index d6061fc16647..b0514e65e30d 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/galleries/examples/tag_guidelines.rst @@ -8,7 +8,7 @@ Tags serve multiple purposes. Tags have a one-to-many organization (i.e. one example can have several tags), while the gallery structure requires that examples are placed in only one location. This means tags provide a secondary layer of organization and make the gallery of examples more flexible and more user-friendly. -They allow for better discoverability, search, and browse functions. They are helpful for users struggling to write a search query for what they're looking for. +They allow for better discoverability, search, and browse functionality. They are helpful for users struggling to write a search query for what they're looking for. Hidden tags provide additional functionality for maintainers and contributors. From 71f25f49856b86700c423fae500f274998357837 Mon Sep 17 00:00:00 2001 From: esibinga Date: Sun, 29 Oct 2023 21:20:45 -0400 Subject: [PATCH 0380/1120] move docs back to devel, updates from review --- {galleries/examples => doc/devel}/tag_glossary.rst | 2 ++ {galleries/examples => doc/devel}/tag_guidelines.rst | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) rename {galleries/examples => doc/devel}/tag_glossary.rst (98%) rename {galleries/examples => doc/devel}/tag_guidelines.rst (86%) diff --git a/galleries/examples/tag_glossary.rst b/doc/devel/tag_glossary.rst similarity index 98% rename from galleries/examples/tag_glossary.rst rename to doc/devel/tag_glossary.rst index 2bb2057d003f..e73a557a850a 100644 --- a/galleries/examples/tag_glossary.rst +++ b/doc/devel/tag_glossary.rst @@ -38,6 +38,8 @@ IV. Internal tags: what information is helpful for maintainers or contributors? |``styling: conditional`` |styling is determined programmatically by a | | |condition being met | +-----------------------------------+---------------------------------------------+ +|``styling: showcase`` |good showcase example | ++-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ |**Primary or relevant plot component** | diff --git a/galleries/examples/tag_guidelines.rst b/doc/devel/tag_guidelines.rst similarity index 86% rename from galleries/examples/tag_guidelines.rst rename to doc/devel/tag_guidelines.rst index b0514e65e30d..ca6b8cfde01d 100644 --- a/galleries/examples/tag_guidelines.rst +++ b/doc/devel/tag_guidelines.rst @@ -20,8 +20,6 @@ Every gallery example should be tagged with: * 1+ content tags * structural, domain, or internal tag(s) if helpful -Examples with many tags may indicate that the entry is a showcase example. - Tags can repeat existing forms of organization (e.g. an example is in the Animation folder and also gets an ``animation`` tag). Tags are helpful to denote particularly good "byproduct" examples. E.g. the explicit purpose of a gallery example might be to demonstrate a colormap, but it's also a good demonstration of a legend. Tag ``legend`` to indicate that, rather than changing the title or the scope of the example. @@ -45,7 +43,7 @@ Note: Tagging organization aims to work for 80-90% of cases. Some examples fall How to tag? ----------- - where you put the directive is where the tag will show +Put each tag as a directive at the bottom of the page. Related content --------------- @@ -53,7 +51,7 @@ Related content What is a gallery example? ^^^^^^^^^^^^^^^^^^^^^^^^^^ -The gallery of examples contains visual demonstrations of matplolib features. Gallery examples exist so that users can scan through visual examples. +The gallery of examples contains visual demonstrations of matplotlib features. Gallery examples exist so that users can scan through visual examples. Unlike tutorials or user guides, gallery examples teach by demonstration, rather than by explanation or instruction. @@ -62,7 +60,7 @@ Gallery examples should avoid instruction or excessive explanation except for br Format ^^^^^^ -All gallery examples should aim to follow the following format: +All :ref:`examples-index` should aim to follow the following format: * Title: 1-6 words, descriptive of content * Subtitle: 10-50 words, action-oriented description of the example subject @@ -71,7 +69,7 @@ All gallery examples should aim to follow the following format: Example: -``bbox_intersect`` gallery example showcases the point of visual examples +The ``bbox_intersect`` gallery example demonstrates the point of visual examples: -* messy example/ hard to categorize, but the gallery is definitely the right spot for it because it's the kind of thing you'd find by visual search +* this example is "messy" in that it's hard to categorize, but the gallery is the right spot for it because it makes sense to find it by visual search * https://matplotlib.org/devdocs/gallery/misc/bbox_intersect.html#sphx-glr-gallery-misc-bbox-intersect-py From 00ba10d8f157e7ee7908e530dfa2500c8c30b58e Mon Sep 17 00:00:00 2001 From: Eva Sibinga <46283995+esibinga@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:10:43 -0400 Subject: [PATCH 0381/1120] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Melissa Weber Mendonça --- doc/devel/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 75088bce774a..1d91aa2790b5 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -146,7 +146,7 @@ Policies and guidelines document style_guide - ../gallery/tag_guidelines + tag_guidelines .. grid-item-card:: :shadow: none From 87261c1934974e5e4527a8dbd0971aaa1fd48b97 Mon Sep 17 00:00:00 2001 From: esibinga Date: Mon, 30 Oct 2023 14:19:33 -0400 Subject: [PATCH 0382/1120] clarify component and styling --- doc/devel/tag_glossary.rst | 58 ++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/doc/devel/tag_glossary.rst b/doc/devel/tag_glossary.rst index e73a557a850a..8f775879aa75 100644 --- a/doc/devel/tag_glossary.rst +++ b/doc/devel/tag_glossary.rst @@ -15,38 +15,9 @@ IV. Internal tags: what information is helpful for maintainers or contributors? +-----------------------------------+---------------------------------------------+ |``tag`` | use case - if not obvious | +===================================+=============================================+ -|**Styling** | -+-----------------------------------+---------------------------------------------+ -|``styling: color`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: shape`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: size`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: position`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: texture`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: colormap`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: colorbar`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: linestyle`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: small-multiples`` | | -+-----------------------------------+---------------------------------------------+ -|``styling: conditional`` |styling is determined programmatically by a | -| |condition being met | -+-----------------------------------+---------------------------------------------+ -|``styling: showcase`` |good showcase example | -+-----------------------------------+---------------------------------------------+ -| | | -+-----------------------------------+---------------------------------------------+ |**Primary or relevant plot component** | +-----------------------------------+---------------------------------------------+ -|Use these tags when plot contains a teachable example | -+-----------------------------------+---------------------------------------------+ -|``component: axes`` | | +|``component: axes`` |remarkable or very clear use of component | +-----------------------------------+---------------------------------------------+ |``component: axis`` | | +-----------------------------------+---------------------------------------------+ @@ -76,6 +47,33 @@ IV. Internal tags: what information is helpful for maintainers or contributors? +-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ +|**Styling** | ++-----------------------------------+---------------------------------------------+ +|Use these tags when plot contains a teachable example | ++-----------------------------------+---------------------------------------------+ +|``styling: color`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: shape`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: size`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: position`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: texture`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: colormap`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: linestyle`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: small-multiples`` | | ++-----------------------------------+---------------------------------------------+ +|``styling: conditional`` |styling is determined programmatically by a | +| |condition being met | ++-----------------------------------+---------------------------------------------+ +|``styling: showcase`` |good showcase example | ++-----------------------------------+---------------------------------------------+ +| | | ++-----------------------------------+---------------------------------------------+ |**Interactivity** | +-----------------------------------+---------------------------------------------+ |``interactivity: event handling`` | | From 20a51b670e6e13c2f944f698632709d950f9a52a Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 30 Oct 2023 12:53:14 -0400 Subject: [PATCH 0383/1120] link out to troubleshooting guide in install --- doc/devel/development_setup.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 944a91b456f7..5827fe929d61 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -173,6 +173,11 @@ When working on a branch that does not have Meson enabled, meaning it does not have :ghpull:`26621` in its history (log), you will have to reinstall from source each time you change any compiled extension code. +If the installation is not working, please consult the :ref:`troubleshooting guide `. +If the guide does not offer a solution, please reach out via `chat `_ +or :ref:`open an issue `. + + Build options ------------- If you are working heavily with files that need to be compiled, you may want to From c45922bad4ecac90a302659c2fd15aa511ac5b47 Mon Sep 17 00:00:00 2001 From: esibinga Date: Mon, 30 Oct 2023 16:44:53 -0400 Subject: [PATCH 0384/1120] updates from review --- doc/devel/tag_glossary.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/devel/tag_glossary.rst b/doc/devel/tag_glossary.rst index 8f775879aa75..61765d5b7826 100644 --- a/doc/devel/tag_glossary.rst +++ b/doc/devel/tag_glossary.rst @@ -9,8 +9,8 @@ III. Domain tags: what discipline(s) might seek this example consistently? IV. Internal tags: what information is helpful for maintainers or contributors? -1. API tags: what content from the API reference is in the example? -------------------------------------------------------------------- +API tags: what content from the API reference is in the example? +---------------------------------------------------------------- +-----------------------------------+---------------------------------------------+ |``tag`` | use case - if not obvious | @@ -70,8 +70,6 @@ IV. Internal tags: what information is helpful for maintainers or contributors? |``styling: conditional`` |styling is determined programmatically by a | | |condition being met | +-----------------------------------+---------------------------------------------+ -|``styling: showcase`` |good showcase example | -+-----------------------------------+---------------------------------------------+ | | | +-----------------------------------+---------------------------------------------+ |**Interactivity** | @@ -114,8 +112,8 @@ IV. Internal tags: what information is helpful for maintainers or contributors? +-----------------------------------+---------------------------------------------+ -2. Structural tags: what format is the example? What context can we provide? ----------------------------------------------------------------------------- +Structural tags: what format is the example? What context can we provide? +------------------------------------------------------------------------- +----------------------------+-------------------------------------------------------------------+ |``tag`` | use case | @@ -138,11 +136,13 @@ IV. Internal tags: what information is helpful for maintainers or contributors? +----------------------------+-------------------------------------------------------------------+ |``purpose: fun`` |just for fun! | +----------------------------+-------------------------------------------------------------------+ +|``styling: showcase`` |good showcase example | ++----------------------------+-------------------------------------------------------------------+ -3. Domain tags: what discipline(s) might seek this example consistently? ------------------------------------------------------------------------- +Domain tags: what discipline(s) might seek this example consistently? +--------------------------------------------------------------------- -Note: it's futile to draw fences around "who owns what", and that's not the point of domain tags. Domain tags help groups of people to privately organize relevant information, and so are not displayed publicly. See below for a list of existing domain tags. If you don't see the one you're looking for and you think it should exist, consider proposing it. +It's futile to draw fences around "who owns what", and that's not the point of domain tags. Domain tags help groups of people to privately organize relevant information, and so are not displayed publicly. See below for a list of existing domain tags. If you don't see the one you're looking for and you think it should exist, consider proposing it. +-------------------------------+----------------------------------------+ |``tag`` | use case | @@ -160,8 +160,8 @@ Note: it's futile to draw fences around "who owns what", and that's not the poin |``domain: signal-processing`` | | +-------------------------------+----------------------------------------+ -4. Internal tags: what information is helpful for maintainers or contributors? ------------------------------------------------------------------------------- +Internal tags: what information is helpful for maintainers or contributors? +--------------------------------------------------------------------------- +-------------------------------+-----------------------------------------------------------------------+ |``tag`` | use case | From 6e3ed38a8a975144789c56562a643ab20b7846e9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 30 Oct 2023 21:20:51 +0100 Subject: [PATCH 0385/1120] Query macOS for available system fonts. ... instead of only relying on a fixed list of directories to check. Note that both previously and with this PR, we continue also querying fontconfig. --- lib/matplotlib/font_manager.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 68b22ccf7df1..addf808afe15 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -36,6 +36,7 @@ from numbers import Number import os from pathlib import Path +import plistlib import re import subprocess import sys @@ -261,6 +262,14 @@ def _get_fontconfig_fonts(): return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')] +@lru_cache +def _get_macos_fonts(): + """Cache and list the font paths known to ``system_profiler SPFontsDataType``.""" + d, = plistlib.loads( + subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + return [Path(entry["path"]) for entry in d["_items"]] + + def findSystemFonts(fontpaths=None, fontext='ttf'): """ Search for fonts in the specified font paths. If no paths are @@ -279,6 +288,7 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): else: installed_fonts = _get_fontconfig_fonts() if sys.platform == 'darwin': + installed_fonts += _get_macos_fonts() fontpaths = [*X11FontDirectories, *OSXFontDirectories] else: fontpaths = X11FontDirectories @@ -1011,7 +1021,7 @@ class FontManager: # Increment this version number whenever the font cache data # format or behavior has changed and requires an existing font # cache files to be rebuilt. - __version__ = 330 + __version__ = 390 def __init__(self, size=None, weight='normal'): self._version = self.__version__ From afa15c4afe1b63736afe263fc750c73cdc042f76 Mon Sep 17 00:00:00 2001 From: Eva Sibinga <46283995+esibinga@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:23:58 -0400 Subject: [PATCH 0386/1120] Apply suggestions from code review Co-authored-by: hannah --- doc/devel/tag_glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/tag_glossary.rst b/doc/devel/tag_glossary.rst index 61765d5b7826..9b70a2a96159 100644 --- a/doc/devel/tag_glossary.rst +++ b/doc/devel/tag_glossary.rst @@ -136,7 +136,7 @@ Structural tags: what format is the example? What context can we provide? +----------------------------+-------------------------------------------------------------------+ |``purpose: fun`` |just for fun! | +----------------------------+-------------------------------------------------------------------+ -|``styling: showcase`` |good showcase example | +|``purpose: showcase`` |good showcase example | +----------------------------+-------------------------------------------------------------------+ Domain tags: what discipline(s) might seek this example consistently? From cd34b80582f00a6e5a8278cd30b4aac5756d8160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Tue, 31 Oct 2023 09:43:45 +0000 Subject: [PATCH 0387/1120] Addressing review comments * Add minimum sphinx-tags version * Fix trailing whitespace * Change admonition title --- environment.yml | 2 +- galleries/examples/README.txt | 3 ++- galleries/examples/animation/animated_histogram.py | 2 +- galleries/examples/animation/multiple_axes.py | 2 +- galleries/examples/event_handling/resample.py | 2 +- requirements/doc/doc-requirements.txt | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/environment.yml b/environment.yml index 9b3c2231f4a1..e613743b4439 100644 --- a/environment.yml +++ b/environment.yml @@ -39,7 +39,7 @@ dependencies: - sphinx-copybutton - sphinx-gallery>=0.12 - sphinx-design - - sphinx-tags + - sphinx-tags>=0.3.0 - pip - pip: - mpl-sphinx-theme diff --git a/galleries/examples/README.txt b/galleries/examples/README.txt index 8ffe42358352..31d4beae578d 100644 --- a/galleries/examples/README.txt +++ b/galleries/examples/README.txt @@ -16,5 +16,6 @@ You can also find :ref:`external resources ` and a :ref:`FAQ ` in our :ref:`user guide `. -.. note:: +.. admonition:: Tagging! + You can also browse the example gallery by :ref:`tags `. diff --git a/galleries/examples/animation/animated_histogram.py b/galleries/examples/animation/animated_histogram.py index 24aa1c11474d..a5055f39c4e5 100644 --- a/galleries/examples/animation/animated_histogram.py +++ b/galleries/examples/animation/animated_histogram.py @@ -57,4 +57,4 @@ def animate(frame_number): plt.show() # %% -# .. tags:: plot-type:histogram, animation +# .. tags:: plot-type: histogram, animation diff --git a/galleries/examples/animation/multiple_axes.py b/galleries/examples/animation/multiple_axes.py index 189bf2fc6af2..8772b5b53d4f 100644 --- a/galleries/examples/animation/multiple_axes.py +++ b/galleries/examples/animation/multiple_axes.py @@ -81,4 +81,4 @@ def animate(i): # - `matplotlib.patches.ConnectionPatch` # - `matplotlib.animation.FuncAnimation` # -# .. tags:: component:axes, animation +# .. tags:: component: axes, animation diff --git a/galleries/examples/event_handling/resample.py b/galleries/examples/event_handling/resample.py index 66b2a09e85d7..913cac9cdf0c 100644 --- a/galleries/examples/event_handling/resample.py +++ b/galleries/examples/event_handling/resample.py @@ -77,4 +77,4 @@ def update(self, ax): plt.show() # %% -# .. tags:: interactivity:zoom, event-handling +# .. tags:: interactivity: zoom, event-handling diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 89ee5b74ef5b..6b7f7c333787 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -21,4 +21,4 @@ sphinxcontrib-svg2pdfconverter>=1.1.0 sphinx-gallery>=0.12.0 sphinx-copybutton sphinx-design -sphinx-tags +sphinx-tags>=0.3.0 From 8326b514cd953c7b1c5e048303f921c32dea0584 Mon Sep 17 00:00:00 2001 From: esibinga Date: Tue, 31 Oct 2023 09:02:54 -0400 Subject: [PATCH 0388/1120] add template for new tag proposal issues --- .github/ISSUE_TEMPLATE/tag_proposal.yml | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/tag_proposal.yml diff --git a/.github/ISSUE_TEMPLATE/tag_proposal.yml b/.github/ISSUE_TEMPLATE/tag_proposal.yml new file mode 100644 index 000000000000..1e3bceedf70c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tag_proposal.yml @@ -0,0 +1,26 @@ +name: Tag Proposal +description: Suggest a new tag or subcategory for the gallery of examples +title: "[Tag]: " +labels: [Tag proposal] +body: + - type: markdown + attributes: + value: | + Please search the [tag glossary]() for relevant tags before creating a new tag proposal. + - type: textarea + id: need + attributes: + label: Need + description: Briefly describe the need this tag will fill. (1-4 sentences) + placeholder: | + * A tag is needed for examples that share [...] + * Existing tags do not work because [...] + * Current gallery examples that would use this tag include [...] + * Indicate which subcategory this tag falls under, or whether a new subcategory is proposed. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: What should the tag be? All tags are in the format `subcategory: tag` From 914b213b5ce753f02c7e8f5cbf1098547190c67c Mon Sep 17 00:00:00 2001 From: nbarlowATI Date: Fri, 20 Oct 2023 16:45:17 +0100 Subject: [PATCH 0389/1120] Individual hatching styles for stackplot, as per Issue 27146 --- doc/users/next_whats_new/stackplot_hatch.rst | 27 ++++++++++++++++++++ lib/matplotlib/pyplot.py | 5 +++- lib/matplotlib/stackplot.py | 21 +++++++++++++-- lib/matplotlib/stackplot.pyi | 1 + lib/matplotlib/tests/test_axes.py | 21 +++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 doc/users/next_whats_new/stackplot_hatch.rst diff --git a/doc/users/next_whats_new/stackplot_hatch.rst b/doc/users/next_whats_new/stackplot_hatch.rst new file mode 100644 index 000000000000..8fd4ca0f81b0 --- /dev/null +++ b/doc/users/next_whats_new/stackplot_hatch.rst @@ -0,0 +1,27 @@ +``hatch`` parameter for stackplot +------------------------------------------- + +The `~.Axes.stackplot` *hatch* parameter now accepts a list of strings describing hatching styles that will be applied sequentially to the layers in the stack: + +.. plot:: + :include-source: true + :alt: Two charts, identified as ax1 and ax2, showing "stackplots", i.e. one-dimensional distributions of data stacked on top of one another. The first plot, ax1 has cross-hatching on all slices, having been given a single string as the "hatch" argument. The second plot, ax2 has different styles of hatching on each slice - diagonal hatching in opposite directions on the first two slices, cross-hatching on the third slice, and open circles on the fourth. + + import matplotlib.pyplot as plt + fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10,5)) + + cols = 10 + rows = 4 + data = ( + np.reshape(np.arange(0, cols, 1), (1, -1)) ** 2 + + np.reshape(np.arange(0, rows), (-1, 1)) + + np.random.random((rows, cols))*5 + ) + x = range(data.shape[1]) + ax1.stackplot(x, data, hatch="x") + ax2.stackplot(x, data, hatch=["//","\\","x","o"]) + + ax1.set_title("hatch='x'") + ax2.set_title("hatch=['//','\\\\','x','o']") + + plt.show() diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f4aa12c9ed6..05b0d1b8917b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3792,12 +3792,15 @@ def spy( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.stackplot) -def stackplot(x, *args, labels=(), colors=None, baseline="zero", data=None, **kwargs): +def stackplot( + x, *args, labels=(), colors=None, hatch=None, baseline="zero", data=None, **kwargs +): return gca().stackplot( x, *args, labels=labels, colors=colors, + hatch=hatch, baseline=baseline, **({"data": data} if data is not None else {}), **kwargs, diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 2629593683e5..4e933f52887d 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -16,7 +16,7 @@ def stackplot(axes, x, *args, - labels=(), colors=None, baseline='zero', + labels=(), colors=None, hatch=None, baseline='zero', **kwargs): """ Draw a stacked area plot. @@ -55,6 +55,15 @@ def stackplot(axes, x, *args, If not specified, the colors from the Axes property cycle will be used. + hatch : list of str, default: None + A sequence of hatching styles. See + :doc:`/gallery/shapes_and_collections/hatch_style_reference`. + The sequence will be cycled through for filling the + stacked areas from bottom to top. + It need not be exactly the same length as the number + of provided *y*, in which case the styles will repeat from the + beginning. + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -76,6 +85,11 @@ def stackplot(axes, x, *args, else: colors = (axes._get_lines.get_next_color() for _ in y) + if hatch is None or isinstance(hatch, str): + hatch = itertools.cycle([hatch]) + else: + hatch = itertools.cycle(hatch) + # Assume data passed has not been 'stacked', so stack it here. # We'll need a float buffer for the upcoming calculations. stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32)) @@ -113,7 +127,9 @@ def stackplot(axes, x, *args, # Color between x = 0 and the first array. coll = axes.fill_between(x, first_line, stack[0, :], - facecolor=next(colors), label=next(labels, None), + facecolor=next(colors), + hatch=next(hatch), + label=next(labels, None), **kwargs) coll.sticky_edges.y[:] = [0] r = [coll] @@ -122,6 +138,7 @@ def stackplot(axes, x, *args, for i in range(len(y) - 1): r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], facecolor=next(colors), + hatch=next(hatch), label=next(labels, None), **kwargs)) return r diff --git a/lib/matplotlib/stackplot.pyi b/lib/matplotlib/stackplot.pyi index 503e282665d6..2981d449b566 100644 --- a/lib/matplotlib/stackplot.pyi +++ b/lib/matplotlib/stackplot.pyi @@ -12,6 +12,7 @@ def stackplot( *args: ArrayLike, labels: Iterable[str] = ..., colors: Iterable[ColorType] | None = ..., + hatch: Iterable[str] | str | None = ..., baseline: Literal["zero", "sym", "wiggle", "weighted_wiggle"] = ..., **kwargs ) -> list[PolyCollection]: ... diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c82c79272d87..799bf5ddd774 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3084,6 +3084,27 @@ def layers(n, m): axs[1, 1].stackplot(range(100), d.T, baseline='weighted_wiggle') +@check_figures_equal() +def test_stackplot_hatching(fig_ref, fig_test): + x = np.linspace(0, 10, 10) + y1 = 1.0 * x + y2 = 2.0 * x + 1 + y3 = 3.0 * x + 2 + # stackplot with different hatching styles (issue #27146) + ax_test = fig_test.subplots() + ax_test.stackplot(x, y1, y2, y3, hatch=["x", "//", "\\\\"], colors=["white"]) + ax_test.set_xlim((0, 10)) + ax_test.set_ylim((0, 70)) + # compare with result from hatching each layer individually + stack_baseline = np.zeros(len(x)) + ax_ref = fig_ref.subplots() + ax_ref.fill_between(x, stack_baseline, y1, hatch="x", facecolor="white") + ax_ref.fill_between(x, y1, y1+y2, hatch="//", facecolor="white") + ax_ref.fill_between(x, y1+y2, y1+y2+y3, hatch="\\\\", facecolor="white") + ax_ref.set_xlim((0, 10)) + ax_ref.set_ylim((0, 70)) + + def _bxp_test_helper( stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}): np.random.seed(937) From f4af444aa5721cd2aa7c5f14e1bebd208383efa3 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Tue, 31 Oct 2023 13:26:03 -0400 Subject: [PATCH 0390/1120] Add datetime arguments for xmin and xmax --- lib/matplotlib/tests/test_datetime.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 3cac147c002f..184962d12564 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -228,17 +228,27 @@ def test_hlines(self): '2023-07-28', '2023-12-24'] dates = [datetime.datetime(2023, m*2, 10) for m in range(1, 6)] + date_start = [datetime.datetime(2023, 6, d) for d in range(5, 30, 5)] + date_end = [datetime.datetime(2023, 7, d) for d in range(5, 30, 5)] npDates = [np.datetime64(s) for s in dateStrs] axs[0, 0].hlines(y=dates, xmin=[0.1, 0.2, 0.3, 0.4, 0.5], xmax=[0.5, 0.6, 0.7, 0.8, 0.9]) - axs[0, 1].hlines(y=dates, xmin=0.2, xmax=0.8) - axs[0, 2].hlines(dates, xmin=0, xmax=1) + axs[0, 1].hlines(dates, + xmin=datetime.datetime(2020, 5, 10), + xmax=datetime.datetime(2020, 5, 31)) + axs[0, 2].hlines(dates, + xmin=date_start, + xmax=date_end) axs[1, 0].hlines(y=npDates, xmin=[0.5, 0.6, 0.7, 0.8, 0.9], xmax=[0.1, 0.2, 0.3, 0.4, 0.5]) - axs[1, 1].hlines(y=npDates, xmin=0.45, xmax=0.65) - axs[1, 2].hlines(npDates, xmin=0, xmax=1) + axs[1, 2].hlines(y=npDates, + xmin=date_start, + xmax=date_end) + axs[1, 1].hlines(npDates, + xmin=datetime.datetime(2020, 5, 10), + xmax=datetime.datetime(2020, 5, 31)) @pytest.mark.xfail(reason="Test for imshow not written yet") @mpl.style.context("default") From 97213e54ef4365f6703f88cad64d085dbf750547 Mon Sep 17 00:00:00 2001 From: Matthew Morrison Date: Tue, 31 Oct 2023 15:31:16 -0400 Subject: [PATCH 0391/1120] Suggested changes to commit --- galleries/users_explain/figure/figure_intro.rst | 2 +- galleries/users_explain/figure/interactive.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index 610885fc21f2..c2a823d815ce 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -75,7 +75,7 @@ other than the default "inline" backend, you will likely need to use an ipython .. note:: - If you only need to use the classic notebook (i.e. `notebook<7`), + If you only need to use the classic notebook (i.e. ``notebook<7``), you can use: .. sourcecode:: ipython diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index adcabaaca1d0..9a4c4956ed6d 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -334,7 +334,7 @@ documentation of your GUI toolkit for details. .. _jupyter_notebooks_jupyterlab: Jupyter Notebooks / JupyterLab -============================== +------------------------------ To get interactive figures in the 'classic' notebook or Jupyter lab, use the `ipympl `__ backend @@ -347,7 +347,7 @@ If ``ipympl`` is installed use the magic: to select and enable it. -If you only need to use the classic notebook (i.e. `notebook<7`), you can use +If you only need to use the classic notebook (i.e. ``notebook<7``), you can use .. sourcecode:: ipython @@ -367,7 +367,7 @@ however, nbagg does not work in Jupyter Lab. cells. GUIs + Jupyter --------------- +^^^^^^^^^^^^^^ You can also use one of the non-``ipympl`` GUI backends in a Jupyter Notebook. If you are running your Jupyter kernel locally, the GUI window will spawn on From f05167c70a60e64fb324ace2c5536eb824252a56 Mon Sep 17 00:00:00 2001 From: Lucia Korpas <22487941+notchia@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:33:00 -0400 Subject: [PATCH 0392/1120] DOC: Add command to install appropriate `requirements.txt` during dev venv setup (#27237) * clarify that requirements must be installed during venv setup step using same grammar as original doc --- doc/devel/development_setup.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 5827fe929d61..57c063605c47 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -131,9 +131,13 @@ The simplest way to do this is to use either Python's virtual environment On some systems, you may need to type ``python3`` instead of ``python``. For a discussion of the technical reasons, see `PEP-394 `_. + Install the Python dependencies with :: + + pip install -r requirements/dev/dev-requirements.txt + .. tab-item:: conda environment - Create a new `conda`_ environment with :: + Create a new `conda`_ environment and install the Python dependencies with :: conda env create -f environment.yml From 823d60d0d75dac4cc2f3866a8177d5849f04f0b8 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 31 Aug 2023 14:57:35 -0700 Subject: [PATCH 0393/1120] DOC: some debug steps for backend issues [ci doc] --- doc/users/faq.rst | 8 ++ galleries/users_explain/figure/backends.rst | 106 ++++++++++++++++-- .../users_explain/figure/figure_intro.rst | 6 +- .../users_explain/figure/interactive.rst | 2 +- galleries/users_explain/quick_start.py | 4 +- lib/matplotlib/figure.py | 2 +- 6 files changed, 112 insertions(+), 16 deletions(-) diff --git a/doc/users/faq.rst b/doc/users/faq.rst index 42747c814f94..1ff21d739108 100644 --- a/doc/users/faq.rst +++ b/doc/users/faq.rst @@ -8,6 +8,13 @@ Frequently Asked Questions ========================== +.. _how-do-no-figure: + +I don't see a figure window +--------------------------- + +Please see :ref:`figures-not-showing`. + .. _how-to-too-many-ticks: Why do I have so many ticks, and/or why are they out of order? @@ -301,6 +308,7 @@ you must in that case use a *non-interactive backend* (typically Agg), because most GUI backends *require* being run from the main thread as well. .. _reporting-problems: +.. _get-help: Get help -------- diff --git a/galleries/users_explain/figure/backends.rst b/galleries/users_explain/figure/backends.rst index b769fcc12ba2..41345d46d6ff 100644 --- a/galleries/users_explain/figure/backends.rst +++ b/galleries/users_explain/figure/backends.rst @@ -11,17 +11,17 @@ Backends What is a backend? ------------------ -A lot of documentation on the website and in the mailing lists refers -to the "backend" and many new users are confused by this term. -Matplotlib targets many different use cases and output formats. Some -people use Matplotlib interactively from the Python shell and have -plotting windows pop up when they type commands. Some people run -`Jupyter `_ notebooks and draw inline plots for -quick data analysis. Others embed Matplotlib into graphical user -interfaces like PyQt or PyGObject to build rich applications. Some -people use Matplotlib in batch scripts to generate postscript images -from numerical simulations, and still others run web application -servers to dynamically serve up graphs. +Backends are used for displaying Matplotlib figures (see :ref:`figure-intro`), +on the screen, or for writing to files. A lot of documentation on the website +and in the mailing lists refers to the "backend" and many new users are +confused by this term. Matplotlib targets many different use cases and output +formats. Some people use Matplotlib interactively from the Python shell and +have plotting windows pop up when they type commands. Some people run `Jupyter +`_ notebooks and draw inline plots for quick data +analysis. Others embed Matplotlib into graphical user interfaces like PyQt or +PyGObject to build rich applications. Some people use Matplotlib in batch +scripts to generate postscript images from numerical simulations, and still +others run web application servers to dynamically serve up graphs. To support all of these use cases, Matplotlib can target different outputs, and each of these capabilities is called a backend; the @@ -248,3 +248,87 @@ backend, use ``module://name.of.the.backend`` as the backend name, e.g. ``matplotlib.use('module://name.of.the.backend')``. Information for backend implementers is available at :ref:`writing_backend_interface`. + +.. _figures-not-showing: + +Debugging the figure windows not showing +---------------------------------------- + +Sometimes things do not work as expected, usually during an install. + +If you are using a Notebook or integrated development environment (see :ref:`notebooks-and-ides`), +please consult their documentation for debugging figures not working in their +environments. + +If you are using one of Matplotlib's graphics backends (see :ref:`standalone-scripts-and-interactive-use`), make sure you know which +one is being used: + +.. code-block:: python3 + + import matplotlib + + print(matplotlib.get_backend()) + +Try a simple plot to see if the GUI opens: + +.. code-block:: python3 + + import matplotlib + import matplotlib.pyplot as plt + + print(matplotlib.get_backend()) + plt.plot((1, 4, 6)) + plt.show() + +If it does not, you perhaps have an installation problem. A good step at this +point is to ensure that your GUI toolkit is installed properly, taking +Matplotlib out of the testing. Almost all GUI toolkits have a small test +program that can be run to test basic functionality. If this test fails, try re-installing. + +QtAgg, QtCairo, Qt5Agg, and Qt5Cairo +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Test ``PyQt5``. + +If you have ``PySide`` or ``PyQt6`` installed rather than ``PyQt5``, just change the import +accordingly: + +.. code-block:: bash + + python -c "from PyQt5.QtWidgets import *; app = QApplication([]); win = QMainWindow(); win.show(); app.exec()" + + +TkAgg and TkCairo +^^^^^^^^^^^^^^^^^ + +Test ``tkinter``: + +.. code-block:: bash + + python3 -c "from tkinter import Tk; Tk().mainloop()" + +GTK3Agg, GTK4Agg, GTK3Cairo, GTK4Cairo +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Test ``Gtk``: + +.. code-block:: bash + + python3 -c "from gi.repository import Gtk; win = Gtk.Window(); win.connect('destroy', Gtk.main_quit); win.show(); Gtk.main()" + +wxAgg and wxCairo +^^^^^^^^^^^^^^^^^ + +Test ``wx``: + +.. code-block:: python3 + + import wx + + app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window. + frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window. + frame.Show(True) # Show the frame. + app.MainLoop() + +If the test works for your desired backend but you still cannot get Matplotlib to display a figure, then contact us (see +:ref:`get-help`). diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index c2a823d815ce..462a3fc848dc 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -1,7 +1,7 @@ .. redirect-from:: /users/explain/figure -.. _figure_explanation: +.. _figure-intro: +++++++++++++++++++++++ Introduction to Figures @@ -36,6 +36,8 @@ We will discuss how to create Figures in more detail below, but first it is helpful to understand how to view a Figure. This varies based on how you are using Matplotlib, and what :ref:`Backend ` you are using. +.. _notebooks-and-ides: + Notebooks and IDEs ------------------ @@ -82,6 +84,8 @@ other than the default "inline" backend, you will likely need to use an ipython %matplotlib notebook +.. _standalone-scripts-and-interactive-use: + Standalone scripts and interactive use -------------------------------------- diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index 9a4c4956ed6d..6fd908fcac7a 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -15,7 +15,7 @@ mouse-location tools built into the Matplotlib GUI windows are often sufficient, you can also use the event system to build customized data exploration tools. .. seealso:: - :ref:`figure_explanation`. + :ref:`figure-intro`. Matplotlib ships with :ref:`backends ` binding to diff --git a/galleries/users_explain/quick_start.py b/galleries/users_explain/quick_start.py index 16d0165ec7a6..eef9d13a016e 100644 --- a/galleries/users_explain/quick_start.py +++ b/galleries/users_explain/quick_start.py @@ -38,7 +38,7 @@ # # Note that to get this Figure to display, you may have to call ``plt.show()``, # depending on your backend. For more details of Figures and backends, see -# :ref:`figure_explanation`. +# :ref:`figure-intro`. # # .. _figure_parts: # @@ -71,7 +71,7 @@ # :ref:`Matplotlib backends ` support zooming and # panning on figure windows. # -# For more on Figures, see :ref:`figure_explanation`. +# For more on Figures, see :ref:`figure-intro`. # # :class:`~matplotlib.axes.Axes` # ------------------------------ diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index baea9232800f..86a9fdd1387e 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -24,7 +24,7 @@ Some situations call for directly instantiating a `~.figure.Figure` class, usually inside an application of some sort (see :ref:`user_interfaces` for a list of examples) . More information about Figures can be found at -:ref:`figure_explanation`. +:ref:`figure-intro`. """ from contextlib import ExitStack From 17557489e6cf3c8a0b9fa42c2d898c38f5761a1b Mon Sep 17 00:00:00 2001 From: dohyun Date: Tue, 31 Oct 2023 22:06:48 -0400 Subject: [PATCH 0394/1120] formatting, and fixing duplicate section name --- .../axes_box_aspect.py | 21 ++++++++++--------- .../angle_annotation.py | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py index 00fdeff17ceb..8640af0e8c0a 100644 --- a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py +++ b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py @@ -15,7 +15,7 @@ # %% # A square axes, independent of data -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# ---------------------------------- # # Produce a square axes, no matter what the data limits are. @@ -31,7 +31,7 @@ # %% # Shared square axes -# ^^^^^^^^^^^^^^^^^^ +# ------------------ # # Produce shared subplots that are squared in size. # @@ -47,7 +47,7 @@ # %% # Square twin axes -# ^^^^^^^^^^^^^^^^ +# ---------------- # # Produce a square axes, with a twin axes. The twinned axes takes over the # box aspect of the parent. @@ -67,7 +67,7 @@ # %% # Normal plot next to image -# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# ------------------------- # # When creating an image plot with fixed data aspect and the default # ``adjustable="box"`` next to a normal plot, the axes would be unequal in @@ -90,7 +90,7 @@ # %% # Square joint/marginal plot -# ^^^^^^^^^^^^^^^^^^^^^^^^^^ +# -------------------------- # # It may be desirable to show marginal distributions next to a plot of joint # data. The following creates a square plot with the box aspect of the @@ -115,12 +115,13 @@ plt.show() # %% -# Square joint/marginal plot -# ^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Set data aspect with box aspect +# ------------------------------- # # When setting the box aspect, one may still set the data aspect as well. -# Here we create an Axes with a box twice as long as tall and use an "equal" -# data aspect for its contents, i.e. the circle actually stays circular. +# Here we create an Axes with a box twice as long as it is tall and use +# an "equal" data aspect for its contents, i.e. the circle actually +# stays circular. fig6, ax = plt.subplots() @@ -133,7 +134,7 @@ # %% # Box aspect for many subplots -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# ---------------------------- # # It is possible to pass the box aspect to an Axes at initialization. The # following creates a 2 by 3 subplot grid with all square Axes. diff --git a/galleries/examples/text_labels_and_annotations/angle_annotation.py b/galleries/examples/text_labels_and_annotations/angle_annotation.py index fa46d8674443..178f54863477 100644 --- a/galleries/examples/text_labels_and_annotations/angle_annotation.py +++ b/galleries/examples/text_labels_and_annotations/angle_annotation.py @@ -30,7 +30,7 @@ # %% # AngleAnnotation class -# ^^^^^^^^^^^^^^^^^^^^^ +# --------------------- # The essential idea here is to subclass `~.patches.Arc` and set its transform # to the `~.transforms.IdentityTransform`, making the parameters of the arc # defined in pixel space. @@ -215,7 +215,7 @@ def R(a, r, w, h): # .. _angle-annotation-usage: # # Usage -# ^^^^^ +# ----- # # Required arguments to ``AngleAnnotation`` are the center of the arc, *xy*, # and two points, such that the arc spans between the two vectors connecting @@ -254,7 +254,7 @@ def R(a, r, w, h): # %% # ``AngleLabel`` options -# ~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------- # # The *textposition* and *unit* keyword arguments may be used to modify the # location of the text label, as shown below: From b964da6e39595d1571d0638804ed109a74d3b4a0 Mon Sep 17 00:00:00 2001 From: dohyun Date: Tue, 31 Oct 2023 22:09:35 -0400 Subject: [PATCH 0395/1120] removed whitespace --- .../examples/subplots_axes_and_figures/axes_box_aspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py index 8640af0e8c0a..26974dd49121 100644 --- a/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py +++ b/galleries/examples/subplots_axes_and_figures/axes_box_aspect.py @@ -119,8 +119,8 @@ # ------------------------------- # # When setting the box aspect, one may still set the data aspect as well. -# Here we create an Axes with a box twice as long as it is tall and use -# an "equal" data aspect for its contents, i.e. the circle actually +# Here we create an Axes with a box twice as long as it is tall and use +# an "equal" data aspect for its contents, i.e. the circle actually # stays circular. fig6, ax = plt.subplots() From 2526e51a41ffb13818ade03cbabb9742bb977113 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 5 Sep 2023 22:39:43 -0400 Subject: [PATCH 0396/1120] ported communications guide to development guide --- doc/devel/communication_guide.rst | 121 ++++++++++++++++++++++++++++++ doc/devel/index.rst | 1 + 2 files changed, 122 insertions(+) create mode 100644 doc/devel/communication_guide.rst diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst new file mode 100644 index 000000000000..0fc8559fbe62 --- /dev/null +++ b/doc/devel/communication_guide.rst @@ -0,0 +1,121 @@ +.. _communications-guidelines: + +======================== +Communication guidelines +======================== + +These guidelines are applicable when acting as a representative of Matplotlib, +for example at sprints or when giving official talks or tutorials, and in any +community venue managed by Matplotlib. + +.. _communication-channels: + +Official communication channels +=============================== +The following venues are managed by Matplotlib maintainers and contributors: + +* https://github.com/matplotlib/matplotlib +* https://discourse.matplotlib.org/ +* https://gitter.im/matplotlib/ +* https://github.com/matplotlib/matplotblog + + +Social Media +------------ + +* https://twitter.com/matplotlib +* https://instagram.com/matplotart/ +* https://www.tiktok.com/@matplotart + + +Mailing lists +---------------- + +* https://mail.python.org/mailman/listinfo/matplotlib-announce +* https://mail.python.org/mailman/listinfo/matplotlib-users +* https://mail.python.org/mailman/listinfo/matplotlib-devel + +.. _social-media-coordination: + +Social media coordination +------------------------- +team mailing list: matplotlib-social@numfocus.org +public chat room: https://gitter.im/matplotlib/community + +Content guidelines +==================== +Communication on official channels, such as the Matplotlib homepage or on +Matplotlib social accounts, should conform to the following standards: + +- be primarily about Matplotlib, 3rd party packages, and visualizations made with Matplotlib +- also acceptable topics: Python, Visualization, NumFOCUS libraries +- no gratuitous disparaging of other visualization libraries and tools; criticism is acceptable so long as it serves a constructive purpose +- follow visualization communication best practices + - don't share non-expert visualizations when it could be harmful + - put on meeting agenda when answer isn't clearly to hold off on sharing. + - clearly state when the visualization data/conclusions cannot be verified + - do not rely on machine translations for sensitive visualizations + - example: https://twitter.com/matplotlib/status/1244178154618605568 +- verify sourcing of content (especially on instagram & blog) + - Instagram/blog: ensure mpl has right to repost/share content + - make sure content is clearly cited + - example: a tutorial using someone else’s example clearly cites the original source +- Limited self/corporate promotion is acceptable, but should be no more than about a quarter of the content of the blog/discourse post. +- if you think content is borderline, ask on the :ref:`_social-media-coordination` channels before publishing it +- acceptable image guide: + - keep it geared towards science/data visualization, and non-controversial images + - must be conform to the guidelines of all sites it may be posed on: + - https://help.twitter.com/en/rules-and-policies/twitter-rules + - https://help.instagram.com/477434105621119 + +Accessibility +------------- +Images in communications should be as accessible as possible: + +- add alt text to images and videos when the platform allows + - https://webaim.org/techniques/alttext/ + - https://medium.com/nightingale/writing-alt-text-for-data-visualization-2a218ef43f81 +- warn on bright, strobing, images & turn off autoplay if possible +- for images made by the social media team: + - make graphic perceivable to people who cannot perceive color well, due to color-blindness or low vision + - do not make bright, strobing images + - more guidelines at https://webaim.org/techniques/images/ + + + +Social media account information +================================ + +Behavior +-------- +When acting as a representative of the library, keep responses polite, and assume +user statements are in good faith unless they violate the [Code of Conduct](https://www.python.org/psf/conduct/) + +Persona +------- +On social media, Matplotlib: + +* Is a sentient visualization library, so talks about itself as a we, us, our, and it. Avoids talking about itself in the 3rd person. +* Is very earnest, eager to please, and aims to be patient & painfully oblivious to snark and sarcasm. +* Gets over-excited over shiny visualizations - lots of emojis and the like - and encourages folks to share their work. +* Highlights various parts of the library, especially the more obscure bits and bobbles. +* Acknowledges that it is a sometimes frustrating tangle of bits & bobbles that can confuse even the folks who work on it & signal boosts their confuzzlment. + +Social graph +------------ +- only follow organizations/projects - mostly NumFocus projects + - especially 3rd party packages + - should at least be visualization related + - sponsors are also acceptable +- do not follow individual accounts for any reason (even maintainers/project leads/Guido!) + +Recurrent social campaigns +-------------------------- + +- Release Announcements + - Highlight new features & major deprecations + - Link to download/install instructions + - Ask folks to try it out. +- signal boost third party packages +- GSOC work during GSOC recruiting and work times +- John Hunter Excellence in Plotting, submission and winners diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 2b358595255b..9537859c107a 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -167,6 +167,7 @@ Policies and guidelines :maxdepth: 1 release_guide + communication_guide min_dep_policy MEP/index From 22e2fe51eb3f1bc73743357c4c28d6d07335ed4a Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 6 Sep 2023 00:43:43 -0400 Subject: [PATCH 0397/1120] updated communications guidelines with current information Fix section title formatting Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/devel/communication_guide.rst | 226 ++++++++++++++++++-------- doc/users/project/code_of_conduct.rst | 2 +- 2 files changed, 158 insertions(+), 70 deletions(-) diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst index 0fc8559fbe62..2b8e9f384037 100644 --- a/doc/devel/communication_guide.rst +++ b/doc/devel/communication_guide.rst @@ -8,28 +8,38 @@ These guidelines are applicable when acting as a representative of Matplotlib, for example at sprints or when giving official talks or tutorials, and in any community venue managed by Matplotlib. + + .. _communication-channels: Official communication channels =============================== The following venues are managed by Matplotlib maintainers and contributors: -* https://github.com/matplotlib/matplotlib -* https://discourse.matplotlib.org/ -* https://gitter.im/matplotlib/ -* https://github.com/matplotlib/matplotblog +* library and docs: https://github.com/matplotlib/matplotlib +* forum: https://discourse.matplotlib.org/ +* chat: `https://matrix.to/#/#matplotlib:matrix.org `_ +* blog: https://blog.scientific-python.org/ - -Social Media +Social media ------------ +Active social media +^^^^^^^^^^^^^^^^^^^ + * https://twitter.com/matplotlib * https://instagram.com/matplotart/ + +Official accounts +^^^^^^^^^^^^^^^^^ +* https://bsky.app/profile/matplotlib.bsky.social +* https://fosstodon.org/@matplotlib * https://www.tiktok.com/@matplotart +* https://www.youtube.com/matplotlib Mailing lists ----------------- +------------- * https://mail.python.org/mailman/listinfo/matplotlib-announce * https://mail.python.org/mailman/listinfo/matplotlib-users @@ -39,83 +49,161 @@ Mailing lists Social media coordination ------------------------- -team mailing list: matplotlib-social@numfocus.org -public chat room: https://gitter.im/matplotlib/community +* Team mailing list: matplotlib-social@numfocus.org +* Public chat room: `https://matrix.to/#/#matplotlib_community:gitter.im `_ + + +Maintenance +----------- + +If you are interested in moderating the chat or forum or accessing the social +media accounts: + +* Matplotlib maintainers should reach out to the `community-manager`_. + +* Everyone else should send an email to matplotlib-social-admin@numfocus.org: + + * Introduce yourself - github handle and participation in the community. + * Describe the reason for wanting to moderate or contribute to social. + Content guidelines -==================== +================== + Communication on official channels, such as the Matplotlib homepage or on -Matplotlib social accounts, should conform to the following standards: - -- be primarily about Matplotlib, 3rd party packages, and visualizations made with Matplotlib -- also acceptable topics: Python, Visualization, NumFOCUS libraries -- no gratuitous disparaging of other visualization libraries and tools; criticism is acceptable so long as it serves a constructive purpose -- follow visualization communication best practices - - don't share non-expert visualizations when it could be harmful - - put on meeting agenda when answer isn't clearly to hold off on sharing. - - clearly state when the visualization data/conclusions cannot be verified - - do not rely on machine translations for sensitive visualizations - - example: https://twitter.com/matplotlib/status/1244178154618605568 -- verify sourcing of content (especially on instagram & blog) - - Instagram/blog: ensure mpl has right to repost/share content - - make sure content is clearly cited - - example: a tutorial using someone else’s example clearly cites the original source -- Limited self/corporate promotion is acceptable, but should be no more than about a quarter of the content of the blog/discourse post. -- if you think content is borderline, ask on the :ref:`_social-media-coordination` channels before publishing it -- acceptable image guide: - - keep it geared towards science/data visualization, and non-controversial images - - must be conform to the guidelines of all sites it may be posed on: - - https://help.twitter.com/en/rules-and-policies/twitter-rules - - https://help.instagram.com/477434105621119 +Matplotlib social accounts, should conform to the following standards. If you +are unsure if content that you would like to post or share meets these +guidelines, ask on the :ref:`social-media-coordination` channels before posting. + +General guidelines +------------------ + +* Focus on Matplotlib, 3rd party packages, and visualizations made with Matplotlib. +* These are also acceptable topics: + + * Visualization best practices and libraries. + * Projects and initiatives by NumFOCUS and Scientific Python. + * How to contribute to open source projects. + * Projects, such as scientific papers, that use Matplotlib. + +* No gratuitous disparaging of other visualization libraries and tools, but + criticism is acceptable so long as it serves a constructive purpose. + +* Follow communication best practices: + + * Do not share non-expert visualizations when it could cause harm: + + * e.g. https://twitter.com/matplotlib/status/1244178154618605568 + + * Clearly state when the visualization data/conclusions cannot be verified. + * Do not rely on machine translations for sensitive visualization. + +* Verify sourcing of content (especially on instagram & blog): + + * Instagram/blog: ensure mpl has right to repost/share content + * Make sure content is clearly cited: + + * e.g. a tutorial reworking an example must credit the original example + +* Limited self/corporate promotion is acceptable. + + * Should be no more than about a quarter of the content. + +Visual media guidelines +----------------------- + +Visual media, such as images and videos, must not violate the +:ref:`code of conduct `, nor any platform's rules. +Specifically: + +* Visual media must conform to the guidelines of all sites it may be posted on: + + * https://help.twitter.com/en/rules-and-policies/twitter-rules + * https://help.instagram.com/477434105621119 + +* Emphasize the visualization techniques demonstrated by the visual media. +* Clearly state that sharing is not an endorsement of the content. + + * e.g. bitcoin related visualizations Accessibility -------------- -Images in communications should be as accessible as possible: +^^^^^^^^^^^^^ -- add alt text to images and videos when the platform allows - - https://webaim.org/techniques/alttext/ - - https://medium.com/nightingale/writing-alt-text-for-data-visualization-2a218ef43f81 -- warn on bright, strobing, images & turn off autoplay if possible -- for images made by the social media team: - - make graphic perceivable to people who cannot perceive color well, due to color-blindness or low vision - - do not make bright, strobing images - - more guidelines at https://webaim.org/techniques/images/ +Visual media in communications should be made as accessible as possible: +* Add alt text to images and videos when the platform allows: + * `alt text for data viz `_ + * `general alt text guide `_ -Social media account information -================================ +* Warn on bright, strobing, images & turn off autoplay if possible. +* For images and videos made by the social media team: + + * Make graphic perceivable to people who cannot perceive color well due to + color-blindness, low vision, or any other reason. + + * Do not make bright, strobing images. + * More guidelines at https://webaim.org/techniques/images/. -Behavior --------- -When acting as a representative of the library, keep responses polite, and assume -user statements are in good faith unless they violate the [Code of Conduct](https://www.python.org/psf/conduct/) + +Social media +============ + +Please follow these guidelines to maintain a consistent brand identity across +platforms. Persona ------- On social media, Matplotlib: -* Is a sentient visualization library, so talks about itself as a we, us, our, and it. Avoids talking about itself in the 3rd person. -* Is very earnest, eager to please, and aims to be patient & painfully oblivious to snark and sarcasm. -* Gets over-excited over shiny visualizations - lots of emojis and the like - and encourages folks to share their work. -* Highlights various parts of the library, especially the more obscure bits and bobbles. -* Acknowledges that it is a sometimes frustrating tangle of bits & bobbles that can confuse even the folks who work on it & signal boosts their confuzzlment. +* Acts as a sentient visualization library, so talks about itself as a we, us, + our, and it. Avoids talking about itself in the 3rd person. Never uses 1st person. +* Is very earnest, eager to please, and aims to be patient & painfully oblivious + to snark and sarcasm. +* Gets over-excited over shiny visualizations - lots of emojis and the like - + and encourages folks to share their work. +* Highlights various parts of the library, especially the more obscure bits and + bobbles. +* Acknowledges that it is a sometimes frustrating tangle of bits & bobbles that + can confuse even the folks who work on it & signal boosts their confuzzlment. + + +Behavior +-------- +When acting as a representative of the library, keep responses polite and assume +user statements are in good faith unless they violate the :ref:`code of conduct `. Social graph ------------ -- only follow organizations/projects - mostly NumFocus projects - - especially 3rd party packages - - should at least be visualization related - - sponsors are also acceptable -- do not follow individual accounts for any reason (even maintainers/project leads/Guido!) - -Recurrent social campaigns --------------------------- - -- Release Announcements - - Highlight new features & major deprecations - - Link to download/install instructions - - Ask folks to try it out. -- signal boost third party packages -- GSOC work during GSOC recruiting and work times -- John Hunter Excellence in Plotting, submission and winners + +Only follow **organizations and projects**, do not follow individual accounts for +any reason, even maintainers/project leads/famous Python people! + +Following these types of accounts is encouraged: + +* NumFocus and Scientific Python projects +* 3rd party packages +* Visualization related projects and organizations +* Open Source community projects +* Sponsors + +Recurring campaigns +------------------- + +Typically the social media accounts will promote the following: + +* Matplotlib releases: + + * Highlight new features & major deprecations + * Link to download/install instructions + * Ask folks to try it out. + +* `third party packages `_ +* NumFocus/Scientific Python/open source visualization project releases +* GSOC/GSOD recruiting and progress + +Retired campaigns +^^^^^^^^^^^^^^^^^ +* John Hunter Excellence in Plotting, submission and winners + +.. _community-manager: https://matplotlib.org/governance/people.html#deputy-project-leads diff --git a/doc/users/project/code_of_conduct.rst b/doc/users/project/code_of_conduct.rst index 8deda909bd4c..ac8b268a4e30 100644 --- a/doc/users/project/code_of_conduct.rst +++ b/doc/users/project/code_of_conduct.rst @@ -1,4 +1,4 @@ -.. code_of_conduct +.. _code_of_conduct: ==================================== Contributor Covenant Code of Conduct From d6592361ca9f663178ddb3d2f595c05233626314 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 5 Oct 2023 17:31:11 -0400 Subject: [PATCH 0398/1120] process for changing guidelines --- doc/devel/communication_guide.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst index 2b8e9f384037..e21968a67f68 100644 --- a/doc/devel/communication_guide.rst +++ b/doc/devel/communication_guide.rst @@ -206,4 +206,15 @@ Retired campaigns ^^^^^^^^^^^^^^^^^ * John Hunter Excellence in Plotting, submission and winners + +Changing the guidelines +======================= + +As the person tasked with implementing communications, the `community-manager`_ +should be alerted to proposed changes to the communications guidelines. Similarly, +specific platform guidelines (e.g. twitter, instagram) should be reviewed by the +person responsible for that platform, when different from the community manager. +If there is no consensus, decisions about communications guidelines revert to +the community manager. + .. _community-manager: https://matplotlib.org/governance/people.html#deputy-project-leads From b9ebda8353eb9f82986748169dcbbdde091aed95 Mon Sep 17 00:00:00 2001 From: hannah Date: Sat, 23 Sep 2023 21:27:43 -0400 Subject: [PATCH 0399/1120] update release guide to use contact info --- doc/_static/mpl.css | 19 +++++++++++++++++++ doc/devel/communication_guide.rst | 11 +++++++---- doc/devel/release_guide.rst | 25 ++++++++++++------------- doc/users/release_notes.rst | 1 + 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 45ecb21d5511..a37fb76a0436 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -161,3 +161,22 @@ div.wide-table table th.stub { font-style: italic; font-size: large; } + + +.checklist { + list-style: none; + padding: 0; + margin: 0; +} +.checklist li { + margin-left: 24px; + padding-left: 23px; + margin-right: 6px; +} +.checklist li:before { + content: "\2610\2001"; + margin-left: -24px; +} +.checklist li p { + display: inline; +} diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst index e21968a67f68..b9fa301c0e46 100644 --- a/doc/devel/communication_guide.rst +++ b/doc/devel/communication_guide.rst @@ -9,7 +9,6 @@ for example at sprints or when giving official talks or tutorials, and in any community venue managed by Matplotlib. - .. _communication-channels: Official communication channels @@ -21,6 +20,8 @@ The following venues are managed by Matplotlib maintainers and contributors: * chat: `https://matrix.to/#/#matplotlib:matrix.org `_ * blog: https://blog.scientific-python.org/ +.. _social-media: + Social media ------------ @@ -38,12 +39,14 @@ Official accounts * https://www.youtube.com/matplotlib +.. _mailing-lists: + Mailing lists ------------- -* https://mail.python.org/mailman/listinfo/matplotlib-announce -* https://mail.python.org/mailman/listinfo/matplotlib-users -* https://mail.python.org/mailman/listinfo/matplotlib-devel +* `matplotlib-announce@python.org `_ +* `matplotlib-users@python.org `_ +* `matplotlib-devel@python.org `_ .. _social-media-coordination: diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst index 2bbe589282a3..4adc4546e879 100644 --- a/doc/devel/release_guide.rst +++ b/doc/devel/release_guide.rst @@ -455,22 +455,21 @@ Due to branch protections for the ``main`` branch, this is merged via a standard request, though the PR cleanliness status check is expected to fail. The PR should not be squashed because the intent is to merge the branch histories. -Announcing -========== +Publicize this release +====================== -The final step is to announce the release to the world. A short -version of the release notes along with acknowledgments should be sent to +After the release is published to PyPI and conda, it should be announced +through our communication channels: -- matplotlib-users@python.org -- matplotlib-devel@python.org -- matplotlib-announce@python.org - -In addition, announcements should be made on social networks (e.g., Twitter via the -``@matplotlib`` account, any other via personal accounts). - -Add a release announcement to the ``mpl-brochure-site`` "News" section of -``docs/body.html``, linking to the discourse page for the announcement. +.. rst-class:: checklist +* Send a short version of the release notes and acknowledgments to all the :ref:`mailing-lists` +* Post highlights and link to :ref:`What's new ` on the + active :ref:`social media accounts ` +* Add a release announcement to the "News" section of + `matplotlib.org `_ by editing + ``docs/body.html``. Link to the auto-generated announcement discourse post, + which is in `Announcements > matplotlib-announcements `_. Conda packages ============== diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 7862305af65e..c3cd17ce59ea 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -1,6 +1,7 @@ .. redirect-from:: /api/api_changes_old .. redirect-from:: /users/whats_new_old +.. _release-notes: ============= Release notes From 0887901f3e95115589d653e24430845df2388872 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 5 Oct 2023 14:14:10 -0400 Subject: [PATCH 0400/1120] coding guide --- doc/devel/coding_guide.rst | 9 --------- 1 file changed, 9 deletions(-) diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst index 49f35e3ad13e..0385d6d388f0 100644 --- a/doc/devel/coding_guide.rst +++ b/doc/devel/coding_guide.rst @@ -1,12 +1,3 @@ -.. raw:: html - - - .. _pr-guidelines: *********************** From d2569a35729e1c7fd123f632c6463183abfab647 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 1 Nov 2023 13:29:06 +0100 Subject: [PATCH 0401/1120] Clarify semantics of plt.matshow(..., fignum=...). The semantics of the fignum parameter are rather confusing (in particular wrt. whether the figure gets resized or not -- it only does if fignum=(None or a non-existing figure number) (which creates a new figure), but not if fignum=0 (which also creates a new figure). It's probably not worth fixing that (if anything we should just point users to Axes.matshow, which is just fine), but we can still improve the documentation. --- lib/matplotlib/pyplot.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f4aa12c9ed6..c8822b97f418 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2412,18 +2412,15 @@ def matshow(A: ArrayLike, fignum: None | int = None, **kwargs) -> AxesImage: The matrix to be displayed. fignum : None or int - If *None*, create a new figure window with automatic numbering. + If *None*, create a new, appropriately sized figure window. - If a nonzero integer, draw into the figure with the given number - (create it if it does not exist). + If 0, use the current Axes (creating one if there is none, without ever + adjusting the figure size). - If 0, use the current axes (or create one if it does not exist). - - .. note:: - - Because of how `.Axes.matshow` tries to set the figure aspect - ratio to be the one of the array, strange things may happen if you - reuse an existing figure. + Otherwise, create a new Axes on the figure with the given number + (creating it at the appropriate size if it does not exist, but not + adjusting the figure size otherwise). Note that this will be drawn on + top of any preexisting Axes on the figure. Returns ------- From e3abb2e7d64cdcea8a216727a714ec7d8b359087 Mon Sep 17 00:00:00 2001 From: CozyFrog Date: Wed, 1 Nov 2023 11:26:00 -0400 Subject: [PATCH 0402/1120] Added test for singular float xmin/xmax arguments --- lib/matplotlib/tests/test_datetime.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 184962d12564..a7d4f18080f4 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -221,7 +221,7 @@ def test_hist2d(self): @mpl.style.context("default") def test_hlines(self): mpl.rcParams["date.converter"] = 'concise' - fig, axs = plt.subplots(2, 3, layout='constrained') + fig, axs = plt.subplots(2, 4, layout='constrained') dateStrs = ['2023-03-08', '2023-04-09', '2023-05-13', @@ -240,6 +240,9 @@ def test_hlines(self): axs[0, 2].hlines(dates, xmin=date_start, xmax=date_end) + axs[0, 3].hlines(dates, + xmin=0.45, + xmax=0.65) axs[1, 0].hlines(y=npDates, xmin=[0.5, 0.6, 0.7, 0.8, 0.9], xmax=[0.1, 0.2, 0.3, 0.4, 0.5]) @@ -249,6 +252,9 @@ def test_hlines(self): axs[1, 1].hlines(npDates, xmin=datetime.datetime(2020, 5, 10), xmax=datetime.datetime(2020, 5, 31)) + axs[1, 3].hlines(npDates, + xmin=0.45, + xmax=0.65) @pytest.mark.xfail(reason="Test for imshow not written yet") @mpl.style.context("default") From c9470177c5305509a321c4fd46c6eecdfd68ff51 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Nov 2023 15:43:16 -0400 Subject: [PATCH 0403/1120] MNT: be more careful in Qt backend that there is actually a Figure in mpl_gui we are being more proactive about breaking circular references when closing a GUI window so that it is possible for a FigureCanvasQt to be in a state where the figure attribute is None. In that case short-circuit return in the UI events. --- lib/matplotlib/backends/backend_qt.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 2aa7874fbdb7..cbc490ef6cd1 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -261,6 +261,8 @@ def enterEvent(self, event): # Force querying of the modifiers, as the cached modifier state can # have been invalidated while the window was out of focus. mods = QtWidgets.QApplication.instance().queryKeyboardModifiers() + if self.figure is None: + return LocationEvent("figure_enter_event", self, *self.mouseEventCoords(event), modifiers=self._mpl_modifiers(mods), @@ -268,6 +270,8 @@ def enterEvent(self, event): def leaveEvent(self, event): QtWidgets.QApplication.restoreOverrideCursor() + if self.figure is None: + return LocationEvent("figure_leave_event", self, *self.mouseEventCoords(), modifiers=self._mpl_modifiers(), @@ -275,7 +279,7 @@ def leaveEvent(self, event): def mousePressEvent(self, event): button = self.buttond.get(event.button()) - if button is not None: + if button is not None and self.figure is not None: MouseEvent("button_press_event", self, *self.mouseEventCoords(event), button, modifiers=self._mpl_modifiers(), @@ -283,13 +287,15 @@ def mousePressEvent(self, event): def mouseDoubleClickEvent(self, event): button = self.buttond.get(event.button()) - if button is not None: + if button is not None and self.figure is not None: MouseEvent("button_press_event", self, *self.mouseEventCoords(event), button, dblclick=True, modifiers=self._mpl_modifiers(), guiEvent=event)._process() def mouseMoveEvent(self, event): + if self.figure is None: + return MouseEvent("motion_notify_event", self, *self.mouseEventCoords(event), modifiers=self._mpl_modifiers(), @@ -297,7 +303,7 @@ def mouseMoveEvent(self, event): def mouseReleaseEvent(self, event): button = self.buttond.get(event.button()) - if button is not None: + if button is not None and self.figure is not None: MouseEvent("button_release_event", self, *self.mouseEventCoords(event), button, modifiers=self._mpl_modifiers(), @@ -311,7 +317,7 @@ def wheelEvent(self, event): steps = event.angleDelta().y() / 120 else: steps = event.pixelDelta().y() - if steps: + if steps and self.figure is not None: MouseEvent("scroll_event", self, *self.mouseEventCoords(event), step=steps, modifiers=self._mpl_modifiers(), @@ -319,14 +325,14 @@ def wheelEvent(self, event): def keyPressEvent(self, event): key = self._get_key(event) - if key is not None: + if key is not None and self.figure is not None: KeyEvent("key_press_event", self, key, *self.mouseEventCoords(), guiEvent=event)._process() def keyReleaseEvent(self, event): key = self._get_key(event) - if key is not None: + if key is not None and self.figure is not None: KeyEvent("key_release_event", self, key, *self.mouseEventCoords(), guiEvent=event)._process() @@ -334,6 +340,8 @@ def keyReleaseEvent(self, event): def resizeEvent(self, event): if self._in_resize_event: # Prevent PyQt6 recursion return + if self.figure is None: + return self._in_resize_event = True try: w = event.size().width() * self.device_pixel_ratio From 93fa19b409142b1e8d84e6d00e097cb5a951e7a1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 2 Nov 2023 04:52:17 -0400 Subject: [PATCH 0404/1120] webagg: Don't resize canvas if WebSocket isn't connected - If it is still connecting, then we will get an initial resize from Python once it connects. - If it has disconnected, then resizing will clear the canvas and never get anything back to refill it, so better to not resize and keep something visible. --- lib/matplotlib/backends/web_backend/js/mpl.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index 140f5903852e..6e8ec449d92b 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -192,6 +192,15 @@ mpl.figure.prototype._init_canvas = function () { } this.resizeObserverInstance = new this.ResizeObserver(function (entries) { + // There's no need to resize if the WebSocket is not connected: + // - If it is still connecting, then we will get an initial resize from + // Python once it connects. + // - If it has disconnected, then resizing will clear the canvas and + // never get anything back to refill it, so better to not resize and + // keep something visible. + if (fig.ws.readyState != 1) { + return; + } var nentries = entries.length; for (var i = 0; i < nentries; i++) { var entry = entries[i]; @@ -239,7 +248,7 @@ mpl.figure.prototype._init_canvas = function () { // And update the size in Python. We ignore the initial 0/0 size // that occurs as the element is placed into the DOM, which should // otherwise not happen due to the minimum size styling. - if (fig.ws.readyState == 1 && width != 0 && height != 0) { + if (width != 0 && height != 0) { fig.request_resize(width, height); } } From 1a83a434557ecfb1ff722a9bfebd034c798d9762 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 2 Nov 2023 11:58:26 +0100 Subject: [PATCH 0405/1120] Remove redundant axes_grid colorbar examples. simple_colorbar is basically a shorter version of demo_colorbar_with_axes_divider with no text explanation (and a really confusing title -- we don't actually want most users to even look at that example), so just delete it (with a redirection). demo_colorbar_with_inset_axes is a version of demo_colorbar_with_inset_locator, but specialized for adding a colorbar to an inset axes. That's a rather specialized case and anyways it works "as is" without doing anything special -- it's just saying that you can combine two relatively orthogonal features and they are happy together. So just delete it (with a redirection). --- .../axes_grid1/demo_colorbar_of_inset_axes.py | 33 ------------------- .../demo_colorbar_with_axes_divider.py | 2 ++ .../demo_colorbar_with_inset_locator.py | 1 + .../examples/axes_grid1/simple_colorbar.py | 22 ------------- .../users_explain/toolkits/axes_grid.rst | 4 +-- 5 files changed, 5 insertions(+), 57 deletions(-) delete mode 100644 galleries/examples/axes_grid1/demo_colorbar_of_inset_axes.py delete mode 100644 galleries/examples/axes_grid1/simple_colorbar.py diff --git a/galleries/examples/axes_grid1/demo_colorbar_of_inset_axes.py b/galleries/examples/axes_grid1/demo_colorbar_of_inset_axes.py deleted file mode 100644 index 341e11ff52b5..000000000000 --- a/galleries/examples/axes_grid1/demo_colorbar_of_inset_axes.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -=============================== -Adding a colorbar to inset axes -=============================== -""" - -import matplotlib.pyplot as plt - -from matplotlib import cbook -from mpl_toolkits.axes_grid1.inset_locator import inset_axes, zoomed_inset_axes - -fig, ax = plt.subplots(figsize=[5, 4]) -ax.set(aspect=1, xlim=(-15, 15), ylim=(-20, 5)) - -Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy") -extent = (-3, 4, -4, 3) - -axins = zoomed_inset_axes(ax, zoom=2, loc='upper left') -axins.set(xticks=[], yticks=[]) -im = axins.imshow(Z, extent=extent, origin="lower") - -# colorbar -cax = inset_axes(axins, - width="5%", # width = 10% of parent_bbox width - height="100%", # height : 50% - loc='lower left', - bbox_to_anchor=(1.05, 0., 1, 1), - bbox_transform=axins.transAxes, - borderpad=0, - ) -fig.colorbar(im, cax=cax) - -plt.show() diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py index 9e4611c65bb7..0c0771013c9f 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py @@ -14,6 +14,8 @@ Users should consider simply passing the main axes to the *ax* keyword argument of `~.Figure.colorbar` instead of creating a locatable axes manually like this. See :ref:`colorbar_placement`. + +.. redirect-from:: /gallery/axes_grid1/simple_colorbar """ import matplotlib.pyplot as plt diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py index 8ec7d0e7271b..e450485150d7 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py @@ -16,6 +16,7 @@ Users should consider using `.Axes.inset_axes` instead (see :ref:`colorbar_placement`). +.. redirect-from:: /gallery/axes_grid1/demo_colorbar_of_inset_axes """ import matplotlib.pyplot as plt diff --git a/galleries/examples/axes_grid1/simple_colorbar.py b/galleries/examples/axes_grid1/simple_colorbar.py deleted file mode 100644 index 5617cb0b6b4b..000000000000 --- a/galleries/examples/axes_grid1/simple_colorbar.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -=============== -Simple Colorbar -=============== - -""" -import matplotlib.pyplot as plt -import numpy as np - -from mpl_toolkits.axes_grid1 import make_axes_locatable - -ax = plt.subplot() -im = ax.imshow(np.arange(100).reshape((10, 10))) - -# create an Axes on the right side of ax. The width of cax will be 5% -# of ax and the padding between cax and ax will be fixed at 0.05 inch. -divider = make_axes_locatable(ax) -cax = divider.append_axes("right", size="5%", pad=0.05) - -plt.colorbar(im, cax=cax) - -plt.show() diff --git a/galleries/users_explain/toolkits/axes_grid.rst b/galleries/users_explain/toolkits/axes_grid.rst index ba37c4cf7d78..6f7be49ed4c1 100644 --- a/galleries/users_explain/toolkits/axes_grid.rst +++ b/galleries/users_explain/toolkits/axes_grid.rst @@ -81,8 +81,8 @@ side ("left", "right", "top", "bottom") of the original axes. colorbar whose height (or width) is in sync with the main axes -------------------------------------------------------------- -.. figure:: /gallery/axes_grid1/images/sphx_glr_simple_colorbar_001.png - :target: /gallery/axes_grid1/simple_colorbar.html +.. figure:: /gallery/axes_grid1/images/sphx_glr_demo_colorbar_with_axes_divider_001.png + :target: /gallery/axes_grid1/demo_colorbar_with_axes_divider.html :align: center scatter_hist.py with AxesDivider From 177ae327ecee04e42704d9b333e6968f1d228017 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 2 Nov 2023 11:35:28 +0100 Subject: [PATCH 0406/1120] Copy-edit the standalone colorbar tutorial - The tutorial is mostly about making standalone colorbars, so use that as title. - Most users will still want the colorbar next to an existing axes, so explain how to do that. - "Extended colorbar with continuous colorscale" is first about discrete colorbars (extensions are a second point), so adjust the title; also it already needs to introduce BoundaryNorm. Adjust the next example's text accordingly as well, as it now only needs to introduce ListedColormap. - In that next example, describe the additional kwargs to colorbar() in a bullet list. - More consistently use fully qualified names the first time a new class is introduced, and unqualified names thereafter. - Use constrained layout instead of manually adjusting figure geometries. - Minor additional tweaks. --- .../users_explain/colors/colorbar_only.py | 131 +++++++++--------- 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/galleries/users_explain/colors/colorbar_only.py b/galleries/users_explain/colors/colorbar_only.py index f9f126533a16..a47ced0a4ea6 100644 --- a/galleries/users_explain/colors/colorbar_only.py +++ b/galleries/users_explain/colors/colorbar_only.py @@ -1,39 +1,34 @@ """ .. redirect-from:: /tutorials/colors/colorbar_only -============================= -Customized Colorbars Tutorial -============================= +==================== +Standalone colorbars +==================== This tutorial shows how to build and customize standalone colorbars, i.e. without an attached plot. -Customized Colorbars -==================== - A `~.Figure.colorbar` needs a "mappable" (`matplotlib.cm.ScalarMappable`) object (typically, an image) which indicates the colormap and the norm to be used. In order to create a colorbar without an attached image, one can instead use a `.ScalarMappable` with no associated data. - -Basic continuous colorbar -------------------------- - -Here we create a basic continuous colorbar with ticks and labels. - -The arguments to the `~.Figure.colorbar` call are the `.ScalarMappable` -(constructed using the *norm* and *cmap* arguments), the axes where the -colorbar should be drawn, and the colorbar's orientation. - -For more information see the :mod:`~matplotlib.colorbar` API. """ import matplotlib.pyplot as plt - import matplotlib as mpl -fig, ax = plt.subplots(figsize=(6, 1)) -fig.subplots_adjust(bottom=0.5) +# %% +# Basic continuous colorbar +# ------------------------- +# Here, we create a basic continuous colorbar with ticks and labels. +# +# The arguments to the `~.Figure.colorbar` call are the `.ScalarMappable` +# (constructed using the *norm* and *cmap* arguments), the axes where the +# colorbar should be drawn, and the colorbar's orientation. +# +# For more information see the `~matplotlib.colorbar` API. + +fig, ax = plt.subplots(figsize=(6, 1), layout='constrained') cmap = mpl.cm.cool norm = mpl.colors.Normalize(vmin=5, vmax=10) @@ -41,16 +36,30 @@ fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='horizontal', label='Some Units') +# %% +# Colorbar attached next to a pre-existing axes +# --------------------------------------------- +# All examples in this tutorial (except this one) show a standalone colorbar on +# its own figure, but it is possible to display the colorbar *next* to a +# pre-existing Axes *ax* by passing ``ax=ax`` to the colorbar() call (meaning +# "draw the colorbar next to *ax*") rather than ``cax=ax`` (meaning "draw the +# colorbar on *ax*"). + +fig, ax = plt.subplots(layout='constrained') + +fig.colorbar(mpl.cm.ScalarMappable(norm=mpl.colors.Normalize(0, 1), cmap='magma'), + ax=ax, orientation='vertical', label='a colorbar label') # %% -# Extended colorbar with continuous colorscale -# -------------------------------------------- -# -# The second example shows how to make a discrete colorbar based on a -# continuous cmap. With the "extend" keyword argument the appropriate colors -# are chosen to fill the colorspace, including the extensions: -fig, ax = plt.subplots(figsize=(6, 1)) -fig.subplots_adjust(bottom=0.5) +# Discrete and extended colorbar with continuous colorscale +# --------------------------------------------------------- +# The following example shows how to make a discrete colorbar based on a +# continuous cmap. We use `matplotlib.colors.BoundaryNorm` to describe the +# interval boundaries (which must be in increasing order), and further pass the +# *extend* argument to it to further display "over" and "under" colors (which +# are used for data outside of the norm range). + +fig, ax = plt.subplots(figsize=(6, 1), layout='constrained') cmap = mpl.cm.viridis bounds = [-1, 2, 5, 7, 12, 15] @@ -61,72 +70,58 @@ label="Discrete intervals with extend='both' keyword") # %% -# Discrete intervals colorbar -# --------------------------- -# -# The third example illustrates the use of a -# :class:`~matplotlib.colors.ListedColormap` which generates a colormap from a -# set of listed colors, `.colors.BoundaryNorm` which generates a colormap -# index based on discrete intervals and extended ends to show the "over" and -# "under" value colors. Over and under are used to display data outside of the -# normalized [0, 1] range. Here we pass colors as gray shades as a string -# encoding a float in the 0-1 range. +# Colorbar with arbitrary colors +# ------------------------------ +# The following example still uses a `.BoundaryNorm` to describe discrete +# interval boundaries, but now uses a `matplotlib.colors.ListedColormap` to +# associate each interval with an arbitrary color (there must be as many +# intervals than there are colors). The "over" and "under" colors are set on +# the colormap using `.Colormap.with_extremes`. # -# If a :class:`~matplotlib.colors.ListedColormap` is used, the length of the -# bounds array must be one greater than the length of the color list. The -# bounds must be monotonically increasing. +# We also pass additional arguments to `~.Figure.colorbar`: # -# This time we pass additional arguments to -# `~.Figure.colorbar`. For the out-of-range values to display on the colorbar -# without using the *extend* keyword with -# `.colors.BoundaryNorm`, we have to use the *extend* keyword argument directly -# in the colorbar call. Here we also -# use the spacing argument to make -# the length of each colorbar segment proportional to its corresponding -# interval. - -fig, ax = plt.subplots(figsize=(6, 1)) -fig.subplots_adjust(bottom=0.5) +# - To display the out-of-range values on the colorbar, we use the *extend* +# argument in the colorbar() call. (This is equivalent to passing the +# *extend* argument in the `.BoundaryNorm` constructor as done in the +# previous example.) +# - To make the length of each colorbar segment proportional to its +# corresponding interval, we use the *spacing* argument in the colorbar() +# call. -cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan']) - .with_extremes(over='0.25', under='0.75')) +fig, ax = plt.subplots(figsize=(6, 1), layout='constrained') +cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan']) + .with_extremes(under='yellow', over='magenta')) bounds = [1, 2, 4, 7, 8] norm = mpl.colors.BoundaryNorm(bounds, cmap.N) + fig.colorbar( mpl.cm.ScalarMappable(cmap=cmap, norm=norm), - cax=ax, + cax=ax, orientation='horizontal', extend='both', - ticks=bounds, spacing='proportional', - orientation='horizontal', label='Discrete intervals, some other units', ) # %% # Colorbar with custom extension lengths # -------------------------------------- -# -# Here we illustrate the use of custom length colorbar extensions, on a -# colorbar with discrete intervals. To make the length of each extension the +# We can customize the length colorbar extensions, on a colorbar with discrete +# intervals. To make the length of each extension the # same as the length of the interior colors, use ``extendfrac='auto'``. -fig, ax = plt.subplots(figsize=(6, 1)) -fig.subplots_adjust(bottom=0.5) +fig, ax = plt.subplots(figsize=(6, 1), layout='constrained') cmap = (mpl.colors.ListedColormap(['royalblue', 'cyan', 'yellow', 'orange']) .with_extremes(over='red', under='blue')) - bounds = [-1.0, -0.5, 0.0, 0.5, 1.0] norm = mpl.colors.BoundaryNorm(bounds, cmap.N) + fig.colorbar( mpl.cm.ScalarMappable(cmap=cmap, norm=norm), - cax=ax, - extend='both', - extendfrac='auto', - ticks=bounds, + cax=ax, orientation='horizontal', + extend='both', extendfrac='auto', spacing='uniform', - orientation='horizontal', label='Custom extension lengths, some other units', ) From f73d048ffdf6e6ef9e23088583ca0461105f46c8 Mon Sep 17 00:00:00 2001 From: Sudhanshu Pandey Date: Thu, 2 Nov 2023 08:49:34 -0400 Subject: [PATCH 0407/1120] Tagging Example - Tags for multiple figs demo (#27236) * chore: added new tags for demo * Update multiple_figs_demo.py --- .../examples/subplots_axes_and_figures/multiple_figs_demo.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/galleries/examples/subplots_axes_and_figures/multiple_figs_demo.py b/galleries/examples/subplots_axes_and_figures/multiple_figs_demo.py index 9bb9962c8e28..1026dd110f4c 100644 --- a/galleries/examples/subplots_axes_and_figures/multiple_figs_demo.py +++ b/galleries/examples/subplots_axes_and_figures/multiple_figs_demo.py @@ -49,3 +49,6 @@ ax.set_xticklabels([]) plt.show() + +# %% +# .. tags:: component: figure, plot type: line From da5a48fc1fb94349968dfd343c2bf0d6a81dbd73 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 2 Nov 2023 14:14:14 -0500 Subject: [PATCH 0408/1120] Fix merge of environment.yml --- environment.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/environment.yml b/environment.yml index 32c2e243fd9f..1487578a443b 100644 --- a/environment.yml +++ b/environment.yml @@ -16,6 +16,10 @@ dependencies: - importlib-resources>=3.2.0 - kiwisolver>=1.3.1 - pybind11>=2.6.0 + - meson-python>=0.13.1 + - numpy>=1.21 + - pillow>=8 + - pkg-config - pygobject - pyparsing>=2.3.1 - pyqt From c3a10cb617373a3c861d5b8a4054eea20576b99d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 2 Nov 2023 14:16:01 -0500 Subject: [PATCH 0409/1120] Remove duplicated text from development setup (from manual backport) --- doc/devel/development_setup.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index f619b179ead5..9be22161a89a 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -194,11 +194,6 @@ config during installation :: For more information on installation and other configuration options, see the Meson Python :external+meson-python:ref:`editable installs guide `. - -If the installation is not working, please consult the :ref:`troubleshooting guide `. -If the guide does not offer a solution, please reach out via `chat `_ -or :ref:`open an issue `. - Verify the Installation ======================= From f21380127e0df2d3f2a16a78e72c25e3a700a7ff Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 1 Nov 2023 20:42:18 -0400 Subject: [PATCH 0410/1120] reasoning for the communications guidelines and social media voice + update to changing guidelines section to be name of doc agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Melissa Weber Mendonça Co-authored-by: noatamir <6564007+noatamir@users.noreply.github.com> --- doc/devel/communication_guide.rst | 66 +++++++++++++++++++++++++------ doc/users/project/mission.rst | 2 + 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst index b9fa301c0e46..aed52be84e32 100644 --- a/doc/devel/communication_guide.rst +++ b/doc/devel/communication_guide.rst @@ -1,18 +1,41 @@ .. _communications-guidelines: -======================== -Communication guidelines -======================== +========================== +Community management guide +========================== -These guidelines are applicable when acting as a representative of Matplotlib, +These guidelines are applicable when **acting as a representative** of Matplotlib, for example at sprints or when giving official talks or tutorials, and in any community venue managed by Matplotlib. +Our approach to community engagement is foremost guided by our :ref:`mission-statement`: + +* We demonstrate that we care about visualization as a practice +* We deepen our practice and the community’s capacity to support users, + facilitate exploration, produce high quality visualizations, and be + understandable and extensible +* We showcase advanced use of the library without adding maintenance burden to + the documentation and recognize contributions that happen outside of the github + workflow. +* We use communications platforms to maintain relationships with contributors + who may no longer be active on GitHub, build relationships with potential + contributors, and connect with other projects and communities who use + Matplotlib. +* In prioritizing understandability and extensiblity, we recognize that people + using Matplotlib, in whatever capacity, are part of our community. Doing so + empowers our community members to build community with each other, for example + by creating educational resources, building third party tools, and building + informal mentoring networks. .. _communication-channels: Official communication channels =============================== +The Scientific Python community uses various communications platforms to stay +updated on new features and projects, to contribute by telling us what is on +their mind and suggest issues and bugs, and to showcase their use cases and the +tools they have built. + The following venues are managed by Matplotlib maintainers and contributors: * library and docs: https://github.com/matplotlib/matplotlib @@ -148,12 +171,32 @@ Visual media in communications should be made as accessible as possible: * Do not make bright, strobing images. * More guidelines at https://webaim.org/techniques/images/. +.. _social-media-brand: Social media ============ -Please follow these guidelines to maintain a consistent brand identity across -platforms. +Matplotlib aims for a single voice across all social media platforms to build and +maintain a consistent brand identity for Matplotlib as an organization. This +depersonalization is the norm on social media platforms because it enables +constructive and productive conversations; People generally feel more comfortable +giving negative and constructive feedback to a brand than to specific contributors. + +The current Matplotlib voice and persona aims to be kind, patient, supportive and +educational. This is so that it can de-escalate tensions and facilitate +constructive conversations; being perceived as negative or +argumentative can escalate very fast into long-lasting brand damage, being +perceived as personal leads to aggression and accusations faster than an +impersonal account, and being perceived as friendly and approachable leads to +higher engagement. Instead of speaking with a directive authority, which can be +intimidating and lead to negative engagement, it speaks as a peer or educator to +empower participation. The current voice encourages more input from folks we +engage with, and also makes it possible for folks who are not in the core team +to participate in managing the account. + +While the :ref:`brand identity ` is casual, the showcased +content is high quality, peer-led resource building. Please follow these +guidelines to maintain a consistent brand identity across platforms. Persona ------- @@ -213,11 +256,10 @@ Retired campaigns Changing the guidelines ======================= -As the person tasked with implementing communications, the `community-manager`_ -should be alerted to proposed changes to the communications guidelines. Similarly, -specific platform guidelines (e.g. twitter, instagram) should be reviewed by the -person responsible for that platform, when different from the community manager. -If there is no consensus, decisions about communications guidelines revert to -the community manager. +As the person tasked with implementing these guidelines, the `community-manager`_ +should be alerted to proposed changes. Similarly, specific platform guidelines +(e.g. twitter, instagram) should be reviewed by the person responsible for that +platform, when different from the community manager. If there is no consensus, +decisions about guidelines revert to the community manager. .. _community-manager: https://matplotlib.org/governance/people.html#deputy-project-leads diff --git a/doc/users/project/mission.rst b/doc/users/project/mission.rst index 431da04e4891..438d2fce8b1d 100644 --- a/doc/users/project/mission.rst +++ b/doc/users/project/mission.rst @@ -1,3 +1,5 @@ +.. _mission-statement: + Mission Statement ================= From f4d807de4dda558c93631b4a2b18f5e1264e343c Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 2 Nov 2023 18:53:18 -0500 Subject: [PATCH 0411/1120] Remove unecessary environment variable from cibuildwheel config --- .github/workflows/cibuildwheel.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 749014ac40c1..af733ff04502 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -64,9 +64,6 @@ jobs: run: | python -m build --sdist python ci/export_sdist_name.py - env: - # Prevent including development runtime dependencies in metadata. - CIBUILDWHEEL: 1 - name: Check README rendering for PyPI run: twine check dist/* From 5143bad2be690d666e63ee3e6afcb9e220fe7310 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 26 Oct 2023 21:46:48 -0400 Subject: [PATCH 0412/1120] streamlined coding guide and added naming conventions table Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/api/next_api_changes/README.rst | 12 +- doc/devel/coding_guide.rst | 214 ++++++------------------- doc/devel/contribute.rst | 237 ++++++++++++++++++++++++---- doc/devel/development_setup.rst | 2 + doc/devel/development_workflow.rst | 17 ++ doc/users/next_whats_new/README.rst | 12 +- 6 files changed, 292 insertions(+), 202 deletions(-) diff --git a/doc/api/next_api_changes/README.rst b/doc/api/next_api_changes/README.rst index de494911e6b2..75e70b456eb9 100644 --- a/doc/api/next_api_changes/README.rst +++ b/doc/api/next_api_changes/README.rst @@ -22,11 +22,17 @@ author's initials. Typically, each change will get its own file, but you may also amend existing files when suitable. The overall goal is a comprehensible documentation of the changes. -Please avoid using references in section titles, as it causes links to be -confusing in the table of contents. Instead, ensure that a reference is -included in the descriptive text. A typical entry could look like this:: +A typical entry could look like this:: Locators ~~~~~~~~ The unused `Locator.autoscale()` method is deprecated (pass the axis limits to `Locator.view_limits()` instead). + +Please avoid using references in section titles, as it causes links to be +confusing in the table of contents. Instead, ensure that a reference is +included in the descriptive text. + +.. NOTE + Lines 5-30 of this file are include in :ref:`api_whats_new`; + therefore, please check the doc build after changing this file. diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst index 0385d6d388f0..22873020f103 100644 --- a/doc/devel/coding_guide.rst +++ b/doc/devel/coding_guide.rst @@ -8,144 +8,82 @@ Pull request guidelines `__ are the mechanism for contributing to Matplotlib's code and documentation. -It is recommended to check that your contribution complies with the following -rules before submitting a pull request: +We value contributions from people with all levels of experience. In particular, +if this is your first PR not everything has to be perfect. We'll guide you +through the PR process. Nevertheless, please try to follow our guidelines as well +as you can to help make the PR process quick and smooth. If your pull request is +incomplete or a work-in-progress, please mark it as a `draft pull requests `_ +on GitHub and specify what feedback from the developers would be helpful. -* If your pull request addresses an issue, please use the title to describe the - issue (e.g. "Add ability to plot timedeltas") and mention the issue number - in the pull request description to ensure that a link is created to the - original issue (e.g. "Closes #8869" or "Fixes #8869"). This will ensure the - original issue mentioned is automatically closed when your PR is merged. See - `the GitHub documentation - `__ - for more details. - -* Formatting should follow the recommendations of PEP8_, as enforced by - flake8_. Matplotlib modifies PEP8 to extend the maximum line length to 88 - characters. You can check flake8 compliance from the command line with :: - - python -m pip install flake8 - flake8 /path/to/module.py - - or your editor may provide integration with it. Note that Matplotlib - intentionally does not use the black_ auto-formatter (1__), in particular due - to its inability to understand the semantics of mathematical expressions - (2__, 3__). - - .. _PEP8: https://www.python.org/dev/peps/pep-0008/ - .. _flake8: https://flake8.pycqa.org/ - .. _black: https://black.readthedocs.io/ - .. __: https://github.com/matplotlib/matplotlib/issues/18796 - .. __: https://github.com/psf/black/issues/148 - .. __: https://github.com/psf/black/issues/1984 - -* All public methods should have informative docstrings with sample usage when - appropriate. Use the :ref:`docstring standards `. - -* For high-level plotting functions, consider adding a simple example either in - the ``Example`` section of the docstring or the - :ref:`examples gallery `. - -* Changes (both new features and bugfixes) should have good test coverage. See - :ref:`testing` for more details. - -* Import the following modules using the standard scipy conventions:: +Please be patient with reviewers. We try our best to respond quickly, but we have +limited bandwidth. If there is no feedback within a couple of days, please ping +us by posting a comment to your PR or reaching out on a :ref:`communication channel ` - import numpy as np - import numpy.ma as ma - import matplotlib as mpl - import matplotlib.pyplot as plt - import matplotlib.cbook as cbook - import matplotlib.patches as mpatches - In general, Matplotlib modules should **not** import `.rcParams` using ``from - matplotlib import rcParams``, but rather access it as ``mpl.rcParams``. This - is because some modules are imported very early, before the `.rcParams` - singleton is constructed. - -* If your change is a major new feature, add an entry to the ``What's new`` - section by adding a new file in ``doc/users/next_whats_new`` (see - :file:`doc/users/next_whats_new/README.rst` for more information). +Summary for pull request authors +================================ -* If you change the API in a backward-incompatible way, please document it in - :file:`doc/api/next_api_changes/behavior`, by adding a new file with the - naming convention ``99999-ABC.rst`` where the pull request number is followed - by the contributor's initials. (see :file:`doc/api/api_changes.rst` for more - information) +We recommend that you check that your contribution complies with the following +guidelines before submitting a pull request: -* If you add new public API or change public API, update or add the - corresponding type hints. Most often this is found in the corresponding - ``.pyi`` file for the ``.py`` file which was edited. Changes in ``pyplot.py`` - are type hinted inline. +.. rst-class:: checklist -* See below for additional points about :ref:`keyword-argument-processing`, if - applicable for your pull request. +* Changes, both new features and bugfixes, should have good test coverage. See + :ref:`testing` for more details. -.. note:: +* Update the :ref:`documentation ` if necessary. - The current state of the Matplotlib code base is not compliant with all - of these guidelines, but we expect that enforcing these constraints on all - new contributions will move the overall code base quality in the right - direction. +* All public methods should have informative docstrings with sample usage when + appropriate. Use the :ref:`docstring standards `. +* For high-level plotting functions, consider adding a small example to the + :ref:`examples gallery `. -.. seealso:: +* If you add a major new feature or change the API in a backward-incompatible + way, please document it as described in :ref:`new-changed-api` - * :ref:`coding_guidelines` - * :ref:`testing` - * :ref:`documenting-matplotlib` +* Code should follow our conventions as documented in our :ref:`coding_guidelines` +* When adding or changing public function signatures, add :ref:`type hints ` +* When adding keyword arguments, see our guide to :ref:`keyword-argument-processing`. -Summary for pull request authors -================================ +When opening a pull request on Github, please ensure that: -.. note:: +.. rst-class:: checklist - * We value contributions from people with all levels of experience. In - particular if this is your first PR not everything has to be perfect. - We'll guide you through the PR process. - * Nevertheless, please try to follow the guidelines below as well as you can to - help make the PR process quick and smooth. - * Be patient with reviewers. We try our best to respond quickly, but we - have limited bandwidth. If there is no feedback within a couple of days, - please ping us by posting a comment to your PR. +* Changes were made on a :ref:`feature branch `. -When making a PR, pay attention to: +* :ref:`pre-commit ` checks for spelling, formatting, etc pass -.. rst-class:: checklist +* The pull request targets the :ref:`main branch ` -* :ref:`Target the main branch `. -* Adhere to the :ref:`coding_guidelines`. -* Update the :ref:`documentation ` if necessary. -* Aim at making the PR as "ready-to-go" as you can. This helps to speed up - the review process. -* It is ok to open incomplete or work-in-progress PRs if you need help or - feedback from the developers. You may mark these as - `draft pull requests `_ - on GitHub. -* When updating your PR, instead of adding new commits to fix something, please - consider amending your initial commit(s) to keep the history clean. - You can achieve this by using +* If your pull request addresses an issue, please use the title to describe the + issue (e.g. "Add ability to plot timedeltas") and mention the issue number + in the pull request description to ensure that a link is created to the + original issue (e.g. "Closes #8869" or "Fixes #8869"). This will ensure the + original issue mentioned is automatically closed when your PR is merged. For more + details, see `linking an issue and pull request `__. - .. code-block:: bash +* :ref:`pr-automated-tests` pass - git commit --amend --no-edit - git push [your-remote-repo] [your-branch] --force-with-lease +For guidance on creating and managing a pull request, please see our +:ref:`contributing ` and :ref:`pull request workflow ` +guides. -See also :ref:`contributing` for how to make a PR. Summary for pull request reviewers ================================== .. redirect-from:: /devel/maintainer_workflow -.. note:: +**Please help review and merge PRs!** + +If you have commit rights, then you are trusted to use them. Please be patient +and `kind `__ with contributors. - * If you have commit rights, then you are trusted to use them. - **Please help review and merge PRs!** - * Be patient and `kind `__ with - contributors. +When reviewing, please ensure that the pull request satisfies the following +requirements before merging it: Content topics: @@ -196,61 +134,6 @@ Documentation * See :ref:`documenting-matplotlib` for our documentation style guide. -.. _release_notes: - -New features and API changes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -When adding a major new feature or changing the API in a backward incompatible -way, please document it by including a versioning directive in the docstring -and adding an entry to the folder for either the what's new or API change notes. - -+-------------------+-----------------------------+----------------------------------+ -| for this addition | include this directive | create entry in this folder | -+===================+=============================+==================================+ -| new feature | ``.. versionadded:: 3.N`` | :file:`doc/users/next_whats_new/`| -+-------------------+-----------------------------+----------------------------------+ -| API change | ``.. versionchanged:: 3.N`` | :file:`doc/api/next_api_changes/`| -| | | | -| | | probably in ``behavior/`` | -+-------------------+-----------------------------+----------------------------------+ - -The directives should be placed at the end of a description block. For example:: - - class Foo: - """ - This is the summary. - - Followed by a longer description block. - - Consisting of multiple lines and paragraphs. - - .. versionadded:: 3.5 - - Parameters - ---------- - a : int - The first parameter. - b: bool, default: False - This was added later. - - .. versionadded:: 3.6 - """ - - def set_b(b): - """ - Set b. - - .. versionadded:: 3.6 - - Parameters - ---------- - b: bool - -For classes and functions, the directive should be placed before the -*Parameters* section. For parameters, the directive should be placed at the -end of the parameter description. The patch release version is omitted and -the directive should not be added to entire modules. - .. _pr-labels: Labels @@ -330,7 +213,8 @@ Merging Automated tests --------------- -Before being merged, a PR should pass the :ref:`automated-tests`. If you are unsure why a test is failing, ask on the PR or in our `chat space `_ +Before being merged, a PR should pass the :ref:`automated-tests`. If you are +unsure why a test is failing, ask on the PR or in our :ref:`communication-channels` .. _pr-squashing: diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 37f4e27765c7..a7d81cbc06d9 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -367,27 +367,160 @@ project that leads to a scientific publication, please follow the Coding guidelines ================= -API changes ------------ +While the current state of the Matplotlib code base is not compliant with all +of these guidelines, our goal in enforcing these constraints on new +contributions is that it improves the readability and consistency of the code base +going forward. -API consistency and stability are of great value. Therefore, API changes -(e.g. signature changes, behavior changes, removals) will only be conducted -if the added benefit is worth the user effort for adapting. +PEP8, as enforced by flake8 +--------------------------- + +Formatting should follow the recommendations of PEP8_, as enforced by flake8_. +Matplotlib modifies PEP8 to extend the maximum line length to 88 +characters. You can check flake8 compliance from the command line with :: + + python -m pip install flake8 + flake8 /path/to/module.py + +or your editor may provide integration with it. Note that Matplotlib intentionally +does not use the black_ auto-formatter (1__), in particular due to its inability +to understand the semantics of mathematical expressions (2__, 3__). + +.. _PEP8: https://www.python.org/dev/peps/pep-0008/ +.. _flake8: https://flake8.pycqa.org/ +.. _black: https://black.readthedocs.io/ +.. __: https://github.com/matplotlib/matplotlib/issues/18796 +.. __: https://github.com/psf/black/issues/148 +.. __: https://github.com/psf/black/issues/1984 + + +Package imports +--------------- +Import the following modules using the standard scipy conventions:: + + import numpy as np + import numpy.ma as ma + import matplotlib as mpl + import matplotlib.pyplot as plt + import matplotlib.cbook as cbook + import matplotlib.patches as mpatches + +In general, Matplotlib modules should **not** import `.rcParams` using ``from +matplotlib import rcParams``, but rather access it as ``mpl.rcParams``. This +is because some modules are imported very early, before the `.rcParams` +singleton is constructed. + +Variable names +-------------- + +When feasible, please use our internal variable naming convention for objects +of a given class and objects of any child class: + ++------------------------------------+---------------+------------------------------------------+ +| base class | variable | multiples | ++====================================+===============+==========================================+ +| `~matplotlib.figure.FigureBase` | ``fig`` | | ++------------------------------------+---------------+------------------------------------------+ +| `~matplotlib.axes.Axes` | ``ax`` | | ++------------------------------------+---------------+------------------------------------------+ +| `~matplotlib.transforms.Transform` | ``trans`` | ``trans__`` | ++ + + + +| | | ``trans_`` when target is screen | ++------------------------------------+---------------+------------------------------------------+ + +Generally, denote more than one instance of the same class by adding suffixes to +the variable names. If a format isn't specified in the table, use numbers or +letters as appropriate. + + +.. _type-hints: + +Type hints +---------- + +If you add new public API or change public API, update or add the +corresponding `mypy `_ type hints. +We generally use `stub files +`_ +(``*.pyi``) to store the type information; for example ``colors.pyi`` contains +the type information for ``colors.py``. A notable exception is ``pyplot.py``, +which is type hinted inline. + +Type hints are checked by the mypy :ref:`pre-commit hook ` +and can often be verified using ``tools\stubtest.py`` and occasionally may +require the use of ``tools\check_typehints.py``. + + +.. _new-changed-api: -Because we are a visualization library our primary output is the final -visualization the user sees. Thus it is our :ref:`long standing -` policy that the appearance of the figure is part of the API -and any changes, either semantic or esthetic, will be treated as a -backwards-incompatible API change. +API changes and new features +---------------------------- +API consistency and stability are of great value; Therefore, API changes +(e.g. signature changes, behavior changes, removals) will only be conducted +if the added benefit is worth the effort of adapting existing code. + +Because we are a visualization library, our primary output is the final +visualization the user sees; therefore, the appearance of the figure is part of +the API and any changes, either semantic or :ref:`esthetic `, +are backwards-incompatible API changes. + +.. _api_whats_new: + +Announce changes, deprecations, and new features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When adding or changing the API in a backward in-compatible way, please add the +appropriate :ref:`versioning directive ` and document it +for the release notes and add the entry to the appropriate folder: + ++-------------------+-----------------------------+----------------------------------------------+ +| addition | versioning directive | announcement folder | ++===================+=============================+==============================================+ +| new feature | ``.. versionadded:: 3.N`` | :file:`doc/users/next_whats_new/` | ++-------------------+-----------------------------+----------------------------------------------+ +| API change | ``.. versionchanged:: 3.N`` | :file:`doc/api/next_api_changes/[kind]` | ++-------------------+-----------------------------+----------------------------------------------+ + +API deprecations are first introduced and then expired. During the introduction +period, users are warned that the API *will* change in the future. +During the expiration period, code is changed as described in the notice posted +during the introductory period. + ++-----------+--------------------------------------------------+----------------------------------------------+ +| stage | required changes | announcement folder | ++===========+==================================================+==============================================+ +| introduce | :ref:`introduce deprecation ` | :file:`doc/api/next_api_changes/deprecation` | ++-----------+--------------------------------------------------+----------------------------------------------+ +| expire | :ref:`expire deprecation ` | :file:`doc/api/next_api_changes/[kind]` | ++-----------+--------------------------------------------------+----------------------------------------------+ + +For both change notes and what's new, please avoid using references in section +titles, as it causes links to be confusing in the table of contents. Instead, +ensure that a reference is included in the descriptive text. + +API Change Notes +"""""""""""""""" +.. include:: ../api/next_api_changes/README.rst + :start-line: 5 + :end-line: 31 + +What's new +"""""""""" +.. include:: ../users/next_whats_new/README.rst + :start-line: 5 + :end-line: 24 + + +Deprecation +^^^^^^^^^^^ API changes in Matplotlib have to be performed following the deprecation process -below, except in very rare circumstances as deemed necessary by the development team. -This ensures that users are notified before the change will take effect and thus -prevents unexpected breaking of code. +below, except in very rare circumstances as deemed necessary by the development +team. This ensures that users are notified before the change will take effect +and thus prevents unexpected breaking of code. Rules -^^^^^ - +""""" - Deprecations are targeted at the next point.release (e.g. 3.x) - Deprecated API is generally removed two point-releases after introduction of the deprecation. Longer deprecations can be imposed by core developers on @@ -398,12 +531,12 @@ Rules - If in doubt, decisions about API changes are finally made by the API consistency lead developer -Introducing -^^^^^^^^^^^ +.. _intro-deprecation: + +Introduce deprecation +""""""""""""""""""""" -#. Announce the deprecation in a new file - :file:`doc/api/next_api_changes/deprecations/99999-ABC.rst` where ``99999`` - is the pull request number and ``ABC`` are the contributor's initials. +#. Create :ref:`deprecation notice ` #. If possible, issue a `~matplotlib.MatplotlibDeprecationWarning` when the deprecated API is used. There are a number of helper tools for this: @@ -435,16 +568,13 @@ Introducing for the deleted parameter, even if it did not previously have one (e.g. ``param: = ...``). -Expiring -^^^^^^^^ +.. _expire-deprecation: + +Expire deprecation +"""""""""""""""""" -#. Announce the API changes in a new file - :file:`doc/api/next_api_changes/[kind]/99999-ABC.rst` where ``99999`` - is the pull request number and ``ABC`` are the contributor's initials, and - ``[kind]`` is one of the folders :file:`behavior`, :file:`development`, - :file:`removals`. See :file:`doc/api/next_api_changes/README.rst` for more - information. For the content, you can usually copy the deprecation notice - and adapt it slightly. +#. Create :ref:`deprecation announcement `. For the content, + you can usually copy the deprecation notice and adapt it slightly. #. Change the code functionality and remove any related deprecation warnings. @@ -462,8 +592,8 @@ Expiring instead. For removed items that were not in the stub file, only deleting from the allowlist is required. -Adding new API --------------- +Adding new API and features +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Every new function, parameter and attribute that is not explicitly marked as private (i.e., starts with an underscore) becomes part of Matplotlib's public @@ -481,6 +611,51 @@ take particular care when adding new API: __ https://emptysqua.re/blog/api-evolution-the-right-way/#adding-parameters +.. _versioning-directives: + +Versioning directives +""""""""""""""""""""" + +When making a backward incompatible change, please add a versioning directive in +the docstring. The directives should be placed at the end of a description block. +For example:: + + class Foo: + """ + This is the summary. + + Followed by a longer description block. + + Consisting of multiple lines and paragraphs. + + .. versionadded:: 3.5 + + Parameters + ---------- + a : int + The first parameter. + b: bool, default: False + This was added later. + + .. versionadded:: 3.6 + """ + + def set_b(b): + """ + Set b. + + .. versionadded:: 3.6 + + Parameters + ---------- + b: bool + +For classes and functions, the directive should be placed before the +*Parameters* section. For parameters, the directive should be placed at the +end of the parameter description. The patch release version is omitted and +the directive should not be added to entire modules. + + New modules and files: installation ----------------------------------- diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 57c063605c47..c1c6767e1a80 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -212,6 +212,8 @@ you are aware of the existing issues beforehand. * Run test cases to verify installation :ref:`testing` * Verify documentation build :ref:`documenting-matplotlib` +.. _pre-commit-hooks: + Install pre-commit hooks ======================== `pre-commit `_ hooks save time in the review process by diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index 50170ee4ade3..3f4fe956e37e 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -151,6 +151,23 @@ If you don't think your request is ready to be merged, just say so in your pull request message and use the "Draft PR" feature of GitHub. This is a good way of getting some preliminary code review. +.. _update-pull-request: + +Update a pull request +===================== + +When updating your pull request after making revisions, instead of adding new +commits, please consider amending your initial commit(s) to keep the commit +history clean. + +You can achieve this by using + + .. code-block:: bash + + git commit -a --amend --no-edit + git push [your-remote-repo] [your-branch] --force-with-lease + + Manage commit history ===================== diff --git a/doc/users/next_whats_new/README.rst b/doc/users/next_whats_new/README.rst index e1b27ef97f1e..98b601ee32d8 100644 --- a/doc/users/next_whats_new/README.rst +++ b/doc/users/next_whats_new/README.rst @@ -11,9 +11,7 @@ something like :file:`cool_new_feature.rst` if you have added a brand new feature or something like :file:`updated_feature.rst` for extensions of existing features. -Please avoid using references in section titles, as it causes links to be -confusing in the table of contents. Instead, ensure that a reference is -included in the descriptive text. Include contents of the form: :: +Include contents of the form:: Section title for feature ------------------------- @@ -23,3 +21,11 @@ included in the descriptive text. Include contents of the form: :: A sub-section ~~~~~~~~~~~~~ + +Please avoid using references in section titles, as it causes links to be +confusing in the table of contents. Instead, ensure that a reference is +included in the descriptive text. + +.. NOTE + Lines 5-24 of this file are include in :ref:`api_whats_new`; + therefore, please check the doc build after changing this file. From ed46a19e9b804a3484c5cad1a9ef9e3eb670c9e5 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sat, 4 Nov 2023 11:46:04 -0700 Subject: [PATCH 0413/1120] add secondary axis support for Transform-type functions --- lib/matplotlib/axes/_secondary_axes.py | 5 +++++ .../test_axes/secondary_xy.png | Bin 48775 -> 52572 bytes lib/matplotlib/tests/test_axes.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/lib/matplotlib/axes/_secondary_axes.py b/lib/matplotlib/axes/_secondary_axes.py index 4c757f61c7b6..bb0237ee5332 100644 --- a/lib/matplotlib/axes/_secondary_axes.py +++ b/lib/matplotlib/axes/_secondary_axes.py @@ -6,6 +6,7 @@ import matplotlib.ticker as mticker from matplotlib.axes._base import _AxesBase, _TransformedBoundsLocator from matplotlib.axis import Axis +from matplotlib.transforms import Transform class SecondaryAxis(_AxesBase): @@ -144,11 +145,15 @@ def set_functions(self, functions): If a transform is supplied, then the transform must have an inverse. """ + if (isinstance(functions, tuple) and len(functions) == 2 and callable(functions[0]) and callable(functions[1])): # make an arbitrary convert from a two-tuple of functions # forward and inverse. self._functions = functions + elif isinstance(functions, Transform): + self._functions = ( + functions.transform, functions.inverted().transform) elif functions is None: self._functions = (lambda x: x, lambda x: x) else: diff --git a/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png b/lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png index bbf9f9e13211db95a236de3696bfd31d5aac2ac7..b69241a06bc68f3e03383825a45aca1a606b201a 100644 GIT binary patch literal 52572 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�e8YV$rt@f5{+oe7y+?0y5EL}q&G=~C6MCBahv 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 z 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 b7CtJ&Rm@Kjn_Dht;I($mdaG>9E&KlhWfvw; diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 799bf5ddd774..3ae1a9376ceb 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -7494,6 +7494,20 @@ def test_annotate_across_transforms(): arrowprops=dict(arrowstyle="->")) +class _Translation(mtransforms.Transform): + input_dims = 1 + output_dims = 1 + + def __init__(self, dx): + self.dx = dx + + def transform(self, values): + return values + self.dx + + def inverted(self): + return _Translation(-self.dx) + + @image_comparison(['secondary_xy.png'], style='mpl20') def test_secondary_xy(): fig, axs = plt.subplots(1, 2, figsize=(10, 5), constrained_layout=True) @@ -7513,6 +7527,7 @@ def invert(x): secax(0.4, functions=(lambda x: 2 * x, lambda x: x / 2)) secax(0.6, functions=(lambda x: x**2, lambda x: x**(1/2))) secax(0.8) + secax("top" if nn == 0 else "right", functions=_Translation(2)) def test_secondary_fail(): From 1abf0a40181ec8ad1b493e941df5a06116306c46 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 5 Nov 2023 19:23:41 +0100 Subject: [PATCH 0414/1120] Copy-edit various examples. Mostly removing "in Matplotlib" (where it is clear that it's a Matplotlib example illustrating how to do stuff in Matplotlib), and removing redundant subtitles. --- galleries/examples/color/README.txt | 3 +-- galleries/examples/color/named_colors.py | 2 +- .../images_contours_and_fields/image_demo.py | 8 +++----- .../images_contours_and_fields/pcolor_demo.py | 12 +++++------- .../text_labels_and_annotations/accented_text.py | 6 +++--- .../annotation_demo.py | 2 +- .../text_labels_and_annotations/font_file.py | 6 +++--- .../text_labels_and_annotations/legend_demo.py | 2 -- .../axes/constrainedlayout_guide.py | 8 +++----- galleries/users_explain/axes/legend_guide.py | 11 ++++------- .../users_explain/axes/tight_layout_guide.py | 16 ++++++---------- .../colors/colormap-manipulation.py | 15 ++++++--------- galleries/users_explain/colors/colormaps.py | 6 +++--- galleries/users_explain/text/text_intro.py | 6 +++--- 14 files changed, 42 insertions(+), 61 deletions(-) diff --git a/galleries/examples/color/README.txt b/galleries/examples/color/README.txt index 4bcdb526f4d4..4b8b3bc4b751 100644 --- a/galleries/examples/color/README.txt +++ b/galleries/examples/color/README.txt @@ -3,6 +3,5 @@ Color ===== -For more in-depth information about the colormaps available in matplotlib -as well as a description of their properties, +For a description of the colormaps available in Matplotlib, see the :ref:`colormaps tutorial `. diff --git a/galleries/examples/color/named_colors.py b/galleries/examples/color/named_colors.py index 9ae4ec4957f3..d9a7259da773 100644 --- a/galleries/examples/color/named_colors.py +++ b/galleries/examples/color/named_colors.py @@ -3,7 +3,7 @@ List of named colors ==================== -This plots a list of the named colors supported in matplotlib. +This plots a list of the named colors supported by Matplotlib. For more information on colors in matplotlib see * the :ref:`colors_def` tutorial; diff --git a/galleries/examples/images_contours_and_fields/image_demo.py b/galleries/examples/images_contours_and_fields/image_demo.py index 884e68172698..213b6cdd28c2 100644 --- a/galleries/examples/images_contours_and_fields/image_demo.py +++ b/galleries/examples/images_contours_and_fields/image_demo.py @@ -1,9 +1,7 @@ """ -========== -Image demo -========== - -Many ways to plot images in Matplotlib. +======================== +Many ways to plot images +======================== The most common way to plot images in Matplotlib is with `~.axes.Axes.imshow`. The following examples demonstrate much of the diff --git a/galleries/examples/images_contours_and_fields/pcolor_demo.py b/galleries/examples/images_contours_and_fields/pcolor_demo.py index a783c83409df..7a8ef35caf96 100644 --- a/galleries/examples/images_contours_and_fields/pcolor_demo.py +++ b/galleries/examples/images_contours_and_fields/pcolor_demo.py @@ -1,13 +1,11 @@ """ -=========== -Pcolor demo -=========== +============= +pcolor images +============= -Generating images with `~.axes.Axes.pcolor`. - -Pcolor allows you to generate 2D image-style plots. Below we will show how -to do so in Matplotlib. +`~.Axes.pcolor` generates 2D image-style plots, as illustrated below. """ + import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/text_labels_and_annotations/accented_text.py b/galleries/examples/text_labels_and_annotations/accented_text.py index f57b19532a43..7ff080afefd9 100644 --- a/galleries/examples/text_labels_and_annotations/accented_text.py +++ b/galleries/examples/text_labels_and_annotations/accented_text.py @@ -1,7 +1,7 @@ r""" -================================= -Using accented text in Matplotlib -================================= +============= +Accented text +============= Matplotlib supports accented characters via TeX mathtext or Unicode. diff --git a/galleries/examples/text_labels_and_annotations/annotation_demo.py b/galleries/examples/text_labels_and_annotations/annotation_demo.py index 8b310a7a1865..26f3b80bf203 100644 --- a/galleries/examples/text_labels_and_annotations/annotation_demo.py +++ b/galleries/examples/text_labels_and_annotations/annotation_demo.py @@ -3,7 +3,7 @@ Annotating Plots ================ -The following examples show how it is possible to annotate plots in Matplotlib. +The following examples show ways to annotate plots in Matplotlib. This includes highlighting specific points of interest and using various visual tools to call attention to this point. For a more complete and in-depth description of the annotation and text tools in Matplotlib, see the diff --git a/galleries/examples/text_labels_and_annotations/font_file.py b/galleries/examples/text_labels_and_annotations/font_file.py index 8242b57839aa..b4a58808d716 100644 --- a/galleries/examples/text_labels_and_annotations/font_file.py +++ b/galleries/examples/text_labels_and_annotations/font_file.py @@ -1,7 +1,7 @@ r""" -=================================== -Using a ttf font file in Matplotlib -=================================== +==================== +Using ttf font files +==================== Although it is usually not a good idea to explicitly point to a single ttf file for a font instance, you can do so by passing a `pathlib.Path` instance as the diff --git a/galleries/examples/text_labels_and_annotations/legend_demo.py b/galleries/examples/text_labels_and_annotations/legend_demo.py index a425d39b7d8f..2f550729837e 100644 --- a/galleries/examples/text_labels_and_annotations/legend_demo.py +++ b/galleries/examples/text_labels_and_annotations/legend_demo.py @@ -3,8 +3,6 @@ Legend Demo =========== -Plotting legends in Matplotlib. - There are many ways to create and customize legends in Matplotlib. Below we'll show a few examples for how to do so. diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py index 260a4f76bf71..4581f5f67808 100644 --- a/galleries/users_explain/axes/constrainedlayout_guide.py +++ b/galleries/users_explain/axes/constrainedlayout_guide.py @@ -40,15 +40,13 @@ .. warning:: - Calling ``plt.tight_layout()`` will turn off *constrained layout*! + Calling `~.pyplot.tight_layout` will turn off *constrained layout*! Simple example ============== -In Matplotlib, the location of Axes (including subplots) are specified in -normalized figure coordinates. It can happen that your axis labels or titles -(or sometimes even ticklabels) go outside the figure area, and are thus -clipped. +With the default Axes positioning, the axes title, axis labels, or tick labels +can sometimes go outside the figure area, and thus get clipped. """ # sphinx_gallery_thumbnail_number = 18 diff --git a/galleries/users_explain/axes/legend_guide.py b/galleries/users_explain/axes/legend_guide.py index 9900b0aa4bdd..3b138fe8ada3 100644 --- a/galleries/users_explain/axes/legend_guide.py +++ b/galleries/users_explain/axes/legend_guide.py @@ -7,13 +7,10 @@ Legend guide ============ -Generating legends flexibly in Matplotlib. - .. currentmodule:: matplotlib.pyplot -This legend guide is an extension of the documentation available at -:func:`~matplotlib.pyplot.legend` - please ensure you are familiar with -contents of that documentation before proceeding with this guide. +This legend guide extends the `~.Axes.legend` docstring - +please read it before proceeding with this guide. This guide makes use of some common terms, which are documented here for clarity: @@ -62,8 +59,8 @@ line_down, = ax.plot([3, 2, 1], label='Line 1') ax.legend(handles=[line_up, line_down]) -In some cases, it is not possible to set the label of the handle, so it is -possible to pass through the list of labels to :func:`legend`:: +In the rare case where the labels cannot directly be set on the handles, they +can also be directly passed to :func:`legend`:: fig, ax = plt.subplots() line_up, = ax.plot([1, 2, 3], label='Line 2') diff --git a/galleries/users_explain/axes/tight_layout_guide.py b/galleries/users_explain/axes/tight_layout_guide.py index 42c227b2e360..8525b9773f91 100644 --- a/galleries/users_explain/axes/tight_layout_guide.py +++ b/galleries/users_explain/axes/tight_layout_guide.py @@ -17,15 +17,11 @@ An alternative to *tight_layout* is :ref:`constrained_layout `. - -Simple Example +Simple example ============== -In matplotlib, the location of axes (including subplots) are specified in -normalized figure coordinates. It can happen that your axis labels or -titles (or sometimes even ticklabels) go outside the figure area, and are thus -clipped. - +With the default Axes positioning, the axes title, axis labels, or tick labels +can sometimes go outside the figure area, and thus get clipped. """ # sphinx_gallery_thumbnail_number = 7 @@ -190,8 +186,8 @@ def example_plot(ax, fontsize=12): # %% # You may provide an optional *rect* parameter, which specifies the bounding -# box that the subplots will be fit inside. The coordinates must be in -# normalized figure coordinates and the default is (0, 0, 1, 1). +# box that the subplots will be fit inside. The coordinates are in +# normalized figure coordinates and default to (0, 0, 1, 1) (the whole figure). fig = plt.figure() @@ -245,7 +241,7 @@ def example_plot(ax, fontsize=12): # Use with AxesGrid1 # ================== # -# While limited, :mod:`mpl_toolkits.axes_grid1` is also supported. +# Limited support for :mod:`mpl_toolkits.axes_grid1` is provided. from mpl_toolkits.axes_grid1 import Grid diff --git a/galleries/users_explain/colors/colormap-manipulation.py b/galleries/users_explain/colors/colormap-manipulation.py index 88e4c5befaf0..87269b87befa 100644 --- a/galleries/users_explain/colors/colormap-manipulation.py +++ b/galleries/users_explain/colors/colormap-manipulation.py @@ -3,9 +3,9 @@ .. _colormap-manipulation: -******************************** -Creating Colormaps in Matplotlib -******************************** +****************** +Creating Colormaps +****************** Matplotlib has a number of built-in colormaps accessible via `.matplotlib.colormaps`. There are also external libraries like @@ -13,17 +13,15 @@ .. _palettable: https://jiffyclub.github.io/palettable/ -However, we often want to create or manipulate colormaps in Matplotlib. +However, we may also want to create or manipulate our own colormaps. This can be done using the class `.ListedColormap` or `.LinearSegmentedColormap`. -Seen from the outside, both colormap classes map values between 0 and 1 to -a bunch of colors. There are, however, slight differences, some of which are -shown in the following. +Both colormap classes map values between 0 and 1 to colors. There are however +differences, as explained below. Before manually creating or manipulating colormaps, let us first see how we can obtain colormaps and their colors from existing colormap classes. - Getting colormaps and accessing their values ============================================ @@ -32,7 +30,6 @@ which returns a colormap object. The length of the list of colors used internally to define the colormap can be adjusted via `.Colormap.resampled`. Below we use a modest value of 8 so there are not a lot of values to look at. - """ import matplotlib.pyplot as plt diff --git a/galleries/users_explain/colors/colormaps.py b/galleries/users_explain/colors/colormaps.py index 92b56d298976..b5db551cb5b5 100644 --- a/galleries/users_explain/colors/colormaps.py +++ b/galleries/users_explain/colors/colormaps.py @@ -3,9 +3,9 @@ .. _colormaps: -******************************** -Choosing Colormaps in Matplotlib -******************************** +****************** +Choosing Colormaps +****************** Matplotlib has a number of built-in colormaps accessible via `.matplotlib.colormaps`. There are also external libraries that diff --git a/galleries/users_explain/text/text_intro.py b/galleries/users_explain/text/text_intro.py index 54fcb00c7a86..948545667fa9 100644 --- a/galleries/users_explain/text/text_intro.py +++ b/galleries/users_explain/text/text_intro.py @@ -4,9 +4,9 @@ .. _text_intro: -======================== -Text in Matplotlib Plots -======================== +================== +Text in Matplotlib +================== Introduction to plotting and working with text in Matplotlib. From 7f2b989093c913a53408c14f91e6f55da43646fa Mon Sep 17 00:00:00 2001 From: hannah Date: Sun, 5 Nov 2023 22:43:36 -0500 Subject: [PATCH 0415/1120] minor fixes to dev workflow --- doc/devel/development_setup.rst | 7 ++++--- doc/devel/development_workflow.rst | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 689a0ddc96d6..745a74dcf8bb 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -25,11 +25,12 @@ Fork the Matplotlib repository ============================== Matplotlib is hosted at https://github.com/matplotlib/matplotlib.git. If you -plan on solving issues or submit pull requests to the main Matplotlib +plan on solving issues or submitting pull requests to the main Matplotlib repository, you should first *fork* this repository by visiting https://github.com/matplotlib/matplotlib.git and clicking on the -``Fork`` button on the top right of the page (see -`the GitHub documentation `__ for more details.) +``Fork`` :octicon:`repo-forked` button on the top right of the page. See +`the GitHub documentation `__ +for more details. Retrieve the latest version of the code ======================================= diff --git a/doc/devel/development_workflow.rst b/doc/devel/development_workflow.rst index 3f4fe956e37e..edb21bb862cd 100644 --- a/doc/devel/development_workflow.rst +++ b/doc/devel/development_workflow.rst @@ -162,10 +162,10 @@ history clean. You can achieve this by using - .. code-block:: bash +.. code-block:: bash - git commit -a --amend --no-edit - git push [your-remote-repo] [your-branch] --force-with-lease + git commit -a --amend --no-edit + git push [your-remote-repo] [your-branch] --force-with-lease Manage commit history From dce48368473c4506e34fb1235ce2215a0407206d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 6 Nov 2023 20:32:22 +0100 Subject: [PATCH 0416/1120] Clarify behavior of `prune` parameter to MaxNLocator. --- lib/matplotlib/ticker.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 41114aafbf3e..8f863582b33b 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1996,13 +1996,11 @@ def __init__(self, nbins=None, **kwargs): If True, autoscaling will result in a range symmetric about zero. prune : {'lower', 'upper', 'both', None}, default: None - Remove edge ticks -- useful for stacked or ganged plots where - the upper tick of one axes overlaps with the lower tick of the - axes above it, primarily when :rc:`axes.autolimit_mode` is - ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will - be removed. If ``prune == 'upper'``, the largest tick will be - removed. If ``prune == 'both'``, the largest and smallest ticks - will be removed. If *prune* is *None*, no ticks will be removed. + Remove the 'lower' tick, the 'upper' tick, or ticks on 'both' sides + *if they fall exactly on an axis' edge* (this typically occurs when + :rc:`axes.autolimit_mode` is 'round_numbers'). Removing such ticks + is mostly useful for stacked or ganged plots, where the upper tick + of an axes overlaps with the lower tick of the axes above it. min_n_ticks : int, default: 2 Relax *nbins* and *integer* constraints if necessary to obtain From d2d7e25ba35d6690aa80b4b673bedaed469b0b6c Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 6 Nov 2023 17:55:41 -0500 Subject: [PATCH 0417/1120] added rest of licenses to license page Co-authored-by: Elliott Sales de Andrade --- doc/users/project/license.rst | 74 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/doc/users/project/license.rst b/doc/users/project/license.rst index a55927d9f2c5..102edcb63da8 100644 --- a/doc/users/project/license.rst +++ b/doc/users/project/license.rst @@ -46,5 +46,75 @@ control logs. License agreement ================= -.. literalinclude:: ../../../LICENSE/LICENSE - :language: none +.. dropdown:: License agreement for Matplotlib versions 1.3.0 and later + :open: + + .. literalinclude:: ../../../LICENSE/LICENSE + :language: none + + + +Bundled software +================ + +.. dropdown:: JSX Tools Resize Observer + + .. literalinclude:: ../../../LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER + :language: none + +.. dropdown:: QT4 Editor + + .. literalinclude:: ../../../LICENSE/LICENSE_QT4_EDITOR + :language: none + + +.. _licenses-cmaps-styles: + +Colormaps and themes +-------------------- + +.. dropdown:: ColorBrewer + + .. literalinclude:: ../../../LICENSE/LICENSE_COLORBREWER + :language: none + +.. dropdown:: Solarized + + .. literalinclude:: ../../../LICENSE/LICENSE_SOLARIZED + :language: none + +.. dropdown:: Yorick + + .. literalinclude:: ../../../LICENSE/LICENSE_YORICK + :language: none + + +.. _licenses-fonts: + +Fonts +----- + +.. dropdown:: American Mathematical Society (AMS) fonts + + .. literalinclude:: ../../../LICENSE/LICENSE_AMSFONTS + :language: none + +.. dropdown:: BaKoMa + + .. literalinclude:: ../../../LICENSE/LICENSE_BAKOMA + :language: none + +.. dropdown:: Carlogo + + .. literalinclude:: ../../../LICENSE/LICENSE_CARLOGO + :language: none + +.. dropdown:: Courier 10 + + .. literalinclude:: ../../../LICENSE/LICENSE_COURIERTEN + :language: none + +.. dropdown:: STIX + + .. literalinclude:: ../../../LICENSE/LICENSE_STIX + :language: none From 545d6daaa99bef1a65132aadd73f381b7ffc93db Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 6 Nov 2023 21:42:53 -0500 Subject: [PATCH 0418/1120] styling dropdown --- doc/_static/mpl.css | 19 +++++++++++++++++++ doc/users/project/license.rst | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 5ed418cb06c7..6f17005a810b 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -185,3 +185,22 @@ div.wide-table table th.stub { .checklist li p { display: inline; } + +.sdd.sd-dropdown { + box-shadow: none!important; +} + +.sdd.sd-dropdown.sd-card{ + border-style: solid !important; + border-color: var(--pst-color-border) !important; + border-width: thin !important; + border-radius: .05 +} + +.sdd.sd-dropdown .sd-card-header{ + --pst-sd-dropdown-color: none; +} + +.sdd.sd-dropdown .sd-card-header +.sd-card-body{ + --pst-sd-dropdown-color: none; +} diff --git a/doc/users/project/license.rst b/doc/users/project/license.rst index 102edcb63da8..3f9c1e30531b 100644 --- a/doc/users/project/license.rst +++ b/doc/users/project/license.rst @@ -48,6 +48,7 @@ License agreement .. dropdown:: License agreement for Matplotlib versions 1.3.0 and later :open: + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE :language: none @@ -58,11 +59,13 @@ Bundled software ================ .. dropdown:: JSX Tools Resize Observer + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER :language: none .. dropdown:: QT4 Editor + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_QT4_EDITOR :language: none @@ -74,16 +77,19 @@ Colormaps and themes -------------------- .. dropdown:: ColorBrewer + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_COLORBREWER :language: none .. dropdown:: Solarized + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_SOLARIZED :language: none .. dropdown:: Yorick + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_YORICK :language: none @@ -95,26 +101,31 @@ Fonts ----- .. dropdown:: American Mathematical Society (AMS) fonts + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_AMSFONTS :language: none .. dropdown:: BaKoMa + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_BAKOMA :language: none .. dropdown:: Carlogo + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_CARLOGO :language: none .. dropdown:: Courier 10 + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_COURIERTEN :language: none .. dropdown:: STIX + :class-container: sdd .. literalinclude:: ../../../LICENSE/LICENSE_STIX :language: none From c0ce04a6c32ff719ebad3d28203a188892a1af1f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 6 Nov 2023 21:59:14 +0100 Subject: [PATCH 0419/1120] Tweak a few docstrings. --- lib/matplotlib/axes/_axes.py | 5 +-- lib/matplotlib/colors.py | 64 ++++++++++++++--------------- lib/matplotlib/legend.py | 2 +- lib/matplotlib/projections/polar.py | 2 +- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9199f1cafae3..2136ecb2eb94 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3007,11 +3007,10 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, A format string defining the properties of the baseline. orientation : {'vertical', 'horizontal'}, default: 'vertical' - If 'vertical', will produce a plot with stems oriented vertically, - If 'horizontal', the stems will be oriented horizontally. + The orientation of the stems. bottom : float, default: 0 - The y/x-position of the baseline (depending on orientation). + The y/x-position of the baseline (depending on *orientation*). label : str, default: None The label to use for the stems in legends. diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index d81f761faa78..2ce27a810a43 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1245,21 +1245,19 @@ def __init__(self, vmin=None, vmax=None, clip=False): provided, they default to the minimum and maximum values of the input, respectively. - clip : bool, default: False Determines the behavior for mapping values outside the range ``[vmin, vmax]``. - If *clip* is ``False``, values outside ``[vmin, vmax]`` are also transformed - linearly, leading to results outside ``[0, 1]``. For a standard use with - colormaps, this behavior is desired because colormaps mark these outside - values with specific colors for over or under. - - If *clip* is ``True``, values outside ``[vmin, vmax]`` are set to 0 or 1, - depending on which boundary they're closer to. This makes these values - indistinguishable from regular boundary values and can lead to - misinterpretation of the data. + If clipping is off, values outside the range ``[vmin, vmax]`` are + also transformed, resulting in values outside ``[0, 1]``. This + behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. + If clipping is on, values below *vmin* are mapped to 0 and values + above *vmax* are mapped to 1. Such values become indistinguishable + from regular boundary values, which may cause misinterpretation of + the data. Notes ----- @@ -1567,15 +1565,15 @@ def __init__(self, vcenter=0, halfrange=None, clip=False): Determines the behavior for mapping values outside the range ``[vmin, vmax]``. - If clipping is off, values outside the range ``[vmin, vmax]`` are also - transformed, resulting in values outside ``[0, 1]``. For a - standard use with colormaps, this behavior is desired because colormaps - mark these outside values with specific colors for *over* or *under*. + If clipping is off, values outside the range ``[vmin, vmax]`` are + also transformed, resulting in values outside ``[0, 1]``. This + behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer. This makes these values - indistinguishable from regular boundary values and can lead to - misinterpretation of the data. + If clipping is on, values below *vmin* are mapped to 0 and values + above *vmax* are mapped to 1. Such values become indistinguishable + from regular boundary values, which may cause misinterpretation of + the data. Examples -------- @@ -1852,14 +1850,13 @@ def forward(values: array-like) -> array-like ``[vmin, vmax]``. If clipping is off, values outside the range ``[vmin, vmax]`` are also - transformed by the function, resulting in values outside ``[0, 1]``. For a - standard use with colormaps, this behavior is desired because colormaps - mark these outside values with specific colors for *over* or *under*. - - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer. This makes these values - indistinguishable from regular boundary values and can lead to - misinterpretation of the data. + transformed by the function, resulting in values outside ``[0, 1]``. + This behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. + + If clipping is on, values below *vmin* are mapped to 0 and values above + *vmax* are mapped to 1. Such values become indistinguishable from + regular boundary values, which may cause misinterpretation of the data. """ @@ -1957,14 +1954,13 @@ class PowerNorm(Normalize): ``[vmin, vmax]``. If clipping is off, values outside the range ``[vmin, vmax]`` are also - transformed by the power function, resulting in values outside ``[0, 1]``. For - a standard use with colormaps, this behavior is desired because colormaps - mark these outside values with specific colors for *over* or *under*. - - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer. This makes these values - indistinguishable from regular boundary values and can lead to - misinterpretation of the data. + transformed by the power function, resulting in values outside ``[0, 1]``. + This behavior is usually desirable, as colormaps can mark these *under* + and *over* values with specific colors. + + If clipping is on, values below *vmin* are mapped to 0 and values above + *vmax* are mapped to 1. Such values become indistinguishable from + regular boundary values, which may cause misinterpretation of the data. Notes ----- diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index c46770974567..93ec3d32c0b7 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -194,7 +194,7 @@ def _update_bbox_to_anchor(self, loc_in_canvas): edgecolor : "inherit" or color, default: :rc:`legend.edgecolor` The legend's background patch edge color. - If ``"inherit"``, use take :rc:`axes.edgecolor`. + If ``"inherit"``, use :rc:`axes.edgecolor`. mode : {"expand", None} If *mode* is set to ``"expand"`` the legend will be horizontally diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index f6fa0ea7b982..25338bca021f 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -201,7 +201,7 @@ def __init__(self, axis=None, use_rmin=True, Axis associated with this transform. This is used to get the minimum radial limit. use_rmin : `bool`, optional - If ``True`` add the minimum radial axis limit after + If ``True``, add the minimum radial axis limit after transforming from Cartesian coordinates. *axis* must also be specified for this to take effect. """ From ab70fac168df44cd456389092527af8c2d6e3d68 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 15 Oct 2023 17:40:47 +0200 Subject: [PATCH 0420/1120] axisartist cleanups. Mostly just unwrapping long lists of arguments into a single line where possible. --- lib/mpl_toolkits/axisartist/axislines.py | 47 +++++-------------- lib/mpl_toolkits/axisartist/floating_axes.py | 22 ++------- .../axisartist/grid_helper_curvelinear.py | 24 ++-------- 3 files changed, 22 insertions(+), 71 deletions(-) diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index cbcdefe38f7c..ced2703d1ed6 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -153,12 +153,10 @@ def get_axislabel_pos_angle(self, axes): # TICK def get_tick_transform(self, axes): - return [axes.get_xaxis_transform(), - axes.get_yaxis_transform()][self.nth_coord] + return [axes.get_xaxis_transform(), axes.get_yaxis_transform()][self.nth_coord] class _FloatingAxisArtistHelperBase(_AxisArtistHelperBase): - def __init__(self, nth_coord, value): self.nth_coord = nth_coord self._value = value @@ -168,8 +166,7 @@ def get_nth_coord(self): return self.nth_coord def get_line(self, axes): - raise RuntimeError( - "get_line method should be defined by the derived class") + raise RuntimeError("get_line method should be defined by the derived class") class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): @@ -187,10 +184,7 @@ def __init__(self, axes, loc, nth_coord=None): def get_tick_iterators(self, axes): """tick_loc, tick_angle, tick_label""" - if self._loc in ["bottom", "top"]: - angle_normal, angle_tangent = 90, 0 - else: # "left", "right" - angle_normal, angle_tangent = 0, 90 + angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] major = self.axis.major major_locs = major.locator() @@ -207,8 +201,7 @@ def _f(locs, labels): c = self._to_xy(loc, const=self._pos) # check if the tick point is inside axes c2 = tick_to_axes.transform(c) - if mpl.transforms._interval_contains_close( - (0, 1), c2[self.nth_coord]): + if mpl.transforms._interval_contains_close((0, 1), c2[self.nth_coord]): yield c, angle_normal, angle_tangent, label return _f(major_locs, major_labels), _f(minor_locs, minor_labels) @@ -245,20 +238,14 @@ def get_axislabel_pos_angle(self, axes): data_to_axes = axes.transData - axes.transAxes p = data_to_axes.transform([self._value, self._value]) verts = self._to_xy(0.5, const=p[fixed_coord]) - if 0 <= verts[fixed_coord] <= 1: - return verts, angle - else: - return None, None + return (verts, angle) if 0 <= verts[fixed_coord] <= 1 else (None, None) def get_tick_transform(self, axes): return axes.transData def get_tick_iterators(self, axes): """tick_loc, tick_angle, tick_label""" - if self.nth_coord == 0: - angle_normal, angle_tangent = 90, 0 - else: - angle_normal, angle_tangent = 0, 90 + angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] major = self.axis.major major_locs = major.locator() @@ -326,27 +313,18 @@ def __init__(self, axes): @_api.delete_parameter( "3.9", "nth_coord", addendum="'nth_coord' is now inferred from 'loc'.") - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None, - ): + def new_fixed_axis( + self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): if axes is None: _api.warn_external( "'new_fixed_axis' explicitly requires the axes keyword.") axes = self.axes if axis_direction is None: axis_direction = loc - helper = FixedAxisArtistHelperRectilinear(axes, loc) - axisline = AxisArtist(axes, helper, offset=offset, - axis_direction=axis_direction) - return axisline + return AxisArtist(axes, FixedAxisArtistHelperRectilinear(axes, loc), + offset=offset, axis_direction=axis_direction) - def new_floating_axis(self, nth_coord, value, - axis_direction="bottom", - axes=None, - ): + def new_floating_axis(self, nth_coord, value, axis_direction="bottom", axes=None): if axes is None: _api.warn_external( "'new_floating_axis' explicitly requires the axes keyword.") @@ -404,8 +382,7 @@ def __call__(self, *args, **kwargs): def __init__(self, *args, grid_helper=None, **kwargs): self._axisline_on = True - self._grid_helper = (grid_helper if grid_helper - else GridHelperRectlinear(self)) + self._grid_helper = grid_helper if grid_helper else GridHelperRectlinear(self) super().__init__(*args, **kwargs) self.toggle_axisline(True) diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index 306ae2c5139a..24c9ce61afa7 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -158,11 +158,8 @@ def get_data_boundary(self, side): bottom=(lat1, 1), top=(lat2, 1))[side] - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): + def new_fixed_axis( + self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): if axes is None: axes = self.axes if axis_direction is None: @@ -179,23 +176,16 @@ def new_fixed_axis(self, loc, # new_floating_axis will inherit the grid_helper's extremes. - # def new_floating_axis(self, nth_coord, - # value, - # axes=None, - # axis_direction="bottom" - # ): - + # def new_floating_axis(self, nth_coord, value, axes=None, axis_direction="bottom"): # axis = super(GridHelperCurveLinear, # self).new_floating_axis(nth_coord, # value, axes=axes, # axis_direction=axis_direction) - # # set extreme values of the axis helper # if nth_coord == 1: # axis.get_helper().set_extremes(*self._extremes[:2]) # elif nth_coord == 0: # axis.get_helper().set_extremes(*self._extremes[2:]) - # return axis def _update_grid(self, x1, y1, x2, y2): @@ -291,8 +281,6 @@ def adjust_axes_lim(self): self.set_ylim(bbox.ymin, bbox.ymax) -floatingaxes_class_factory = cbook._make_class_factory( - FloatingAxesBase, "Floating{}") -FloatingAxes = floatingaxes_class_factory( - host_axes_class_factory(axislines.Axes)) +floatingaxes_class_factory = cbook._make_class_factory(FloatingAxesBase, "Floating{}") +FloatingAxes = floatingaxes_class_factory(host_axes_class_factory(axislines.Axes)) FloatingSubplot = FloatingAxes diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index d34a848780e3..08031122c03b 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -272,11 +272,8 @@ def update_grid_finder(self, aux_trans=None, **kwargs): self._old_limits = None # Force revalidation. @_api.make_keyword_only("3.9", "nth_coord") - def new_fixed_axis(self, loc, - nth_coord=None, - axis_direction=None, - offset=None, - axes=None): + def new_fixed_axis( + self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): if axes is None: axes = self.axes if axis_direction is None: @@ -287,11 +284,7 @@ def new_fixed_axis(self, loc, # the floating_axig.GridHelperCurveLinear subclass? return axisline - def new_floating_axis(self, nth_coord, - value, - axes=None, - axis_direction="bottom" - ): + def new_floating_axis(self, nth_coord, value, axes=None, axis_direction="bottom"): if axes is None: axes = self.axes helper = FloatingAxisArtistHelper( @@ -317,22 +310,15 @@ def get_gridlines(self, which="major", axis="both"): return grid_lines def get_tick_iterator(self, nth_coord, axis_side, minor=False): - - # axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side] angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] - # angle = [0, 90, 180, 270][axisnr] lon_or_lat = ["lon", "lat"][nth_coord] if not minor: # major ticks - for (xy, a), l in zip( + for (xy, angle_normal), l in zip( self._grid_info[lon_or_lat]["tick_locs"][axis_side], self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a yield xy, angle_normal, angle_tangent, l else: - for (xy, a), l in zip( + for (xy, angle_normal), l in zip( self._grid_info[lon_or_lat]["tick_locs"][axis_side], self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - angle_normal = a yield xy, angle_normal, angle_tangent, "" - # for xy, a, l in self._grid_info[lon_or_lat]["ticks"][axis_side]: - # yield xy, a, "" From 92de753e9c918b2bd590c2f7cfc0bf04a366e5c9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 28 Oct 2023 13:12:03 +0200 Subject: [PATCH 0421/1120] Shorten contour._make_paths_from_contour_generator. --- lib/matplotlib/contour.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 49f87edc3907..42970a9b26df 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1114,22 +1114,14 @@ def _make_paths_from_contour_generator(self): """Compute ``paths`` using C extension.""" if self._paths is not None: return self._paths - paths = [] + cg = self._contour_generator empty_path = Path(np.empty((0, 2))) - if self.filled: - lowers, uppers = self._get_lowers_and_uppers() - for level, level_upper in zip(lowers, uppers): - vertices, kinds = \ - self._contour_generator.create_filled_contour( - level, level_upper) - paths.append(Path(np.concatenate(vertices), np.concatenate(kinds)) - if len(vertices) else empty_path) - else: - for level in self.levels: - vertices, kinds = self._contour_generator.create_contour(level) - paths.append(Path(np.concatenate(vertices), np.concatenate(kinds)) - if len(vertices) else empty_path) - return paths + vertices_and_codes = ( + map(cg.create_filled_contour, *self._get_lowers_and_uppers()) + if self.filled else + map(cg.create_contour, self.levels)) + return [Path(np.concatenate(vs), np.concatenate(cs)) if len(vs) else empty_path + for vs, cs in vertices_and_codes] def _get_lowers_and_uppers(self): """ From 3bd9abe6b366afb86fe6ae2b5b350d43c15f6d9e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 29 Oct 2023 00:10:03 +0200 Subject: [PATCH 0422/1120] Factor out common parts of qt and macos interrupt handling. Note that we don't actually need to disable the QSocketNotifier at the end, just letting it go out of scope should be sufficient as its destructor also does that (see qsocketnotifier.cpp). --- lib/matplotlib/backend_bases.py | 60 +++++++++++++++++++ lib/matplotlib/backends/backend_macosx.py | 61 ++++--------------- lib/matplotlib/backends/backend_qt.py | 43 +++++++++++-- lib/matplotlib/backends/qt_compat.py | 73 ----------------------- 4 files changed, 108 insertions(+), 129 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index a411cfbe3a4a..fd055fd7015d 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -35,6 +35,8 @@ import itertools import logging import os +import signal +import socket import sys import time import weakref @@ -1651,6 +1653,64 @@ def _is_non_interactive_terminal_ipython(ip): and getattr(ip.parent, 'interact', None) is False) +@contextmanager +def _allow_interrupt(prepare_notifier, handle_sigint): + """ + A context manager that allows terminating a plot by sending a SIGINT. It + is necessary because the running backend prevents the Python interpreter + from running and processing signals (i.e., to raise a KeyboardInterrupt). + To solve this, one needs to somehow wake up the interpreter and make it + close the plot window. We do this by using the signal.set_wakeup_fd() + function which organizes a write of the signal number into a socketpair. + A backend-specific function, *prepare_notifier*, arranges to listen to + the pair's read socket while the event loop is running. (If it returns a + notifier object, that object is kept alive while the context manager runs.) + + If SIGINT was indeed caught, after exiting the on_signal() function the + interpreter reacts to the signal according to the handler function which + had been set up by a signal.signal() call; here, we arrange to call the + backend-specific *handle_sigint* function. Finally, we call the old SIGINT + handler with the same arguments that were given to our custom handler. + + We do this only if the old handler for SIGINT was not None, which means + that a non-python handler was installed, i.e. in Julia, and not SIG_IGN + which means we should ignore the interrupts. + + Parameters + ---------- + prepare_notifier : Callable[[socket.socket], object] + handle_sigint : Callable[[], object] + """ + + old_sigint_handler = signal.getsignal(signal.SIGINT) + if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): + yield + return + + handler_args = None + wsock, rsock = socket.socketpair() + wsock.setblocking(False) + rsock.setblocking(False) + old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) + notifier = prepare_notifier(rsock) + + def save_args_and_handle_sigint(*args): + nonlocal handler_args + handler_args = args + handle_sigint() + + signal.signal(signal.SIGINT, save_args_and_handle_sigint) + try: + yield + finally: + wsock.close() + rsock.close() + signal.set_wakeup_fd(old_wakeup_fd) + signal.signal(signal.SIGINT, old_sigint_handler) + if handler_args is not None: + old_sigint_handler(*handler_args) + + class FigureCanvasBase: """ The canvas the figure renders into. diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index a39f5b5b1497..822cd22ba6c7 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -1,7 +1,4 @@ -import contextlib import os -import signal -import socket import matplotlib as mpl from matplotlib import _api, cbook @@ -10,7 +7,7 @@ from .backend_agg import FigureCanvasAgg from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, - ResizeEvent, TimerBase) + ResizeEvent, TimerBase, _allow_interrupt) class TimerMac(_macosx.Timer, TimerBase): @@ -18,6 +15,12 @@ class TimerMac(_macosx.Timer, TimerBase): # completely implemented at the C-level (in _macosx.Timer) +def _allow_interrupt_macos(): + """A context manager that allows terminating a plot by sending a SIGINT.""" + return _allow_interrupt( + lambda rsock: _macosx.wake_on_fd_write(rsock.fileno()), _macosx.stop) + + class FigureCanvasMac(FigureCanvasAgg, _macosx.FigureCanvas, FigureCanvasBase): # docstring inherited @@ -109,10 +112,9 @@ def resize(self, width, height): def start_event_loop(self, timeout=0): # docstring inherited - with _maybe_allow_interrupt(): - # Call the objc implementation of the event loop after - # setting up the interrupt handling - self._start_event_loop(timeout=timeout) + # Set up a SIGINT handler to allow terminating a plot via CTRL-C. + with _allow_interrupt_macos(): + self._start_event_loop(timeout=timeout) # Forward to ObjC implementation. class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): @@ -177,9 +179,7 @@ def destroy(self): @classmethod def start_main_loop(cls): # Set up a SIGINT handler to allow terminating a plot via CTRL-C. - # The logic is largely copied from qt_compat._maybe_allow_interrupt; see its - # docstring for details. Parts are implemented by wake_on_fd_write in ObjC. - with _maybe_allow_interrupt(): + with _allow_interrupt_macos(): _macosx.show() def show(self): @@ -190,45 +190,6 @@ def show(self): self._raise() -@contextlib.contextmanager -def _maybe_allow_interrupt(): - """ - This manager allows to terminate a plot by sending a SIGINT. It is - necessary because the running backend prevents Python interpreter to - run and process signals (i.e., to raise KeyboardInterrupt exception). To - solve this one needs to somehow wake up the interpreter and make it close - the plot window. The implementation is taken from qt_compat, see that - docstring for a more detailed description. - """ - old_sigint_handler = signal.getsignal(signal.SIGINT) - if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): - yield - return - - handler_args = None - wsock, rsock = socket.socketpair() - wsock.setblocking(False) - rsock.setblocking(False) - old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) - _macosx.wake_on_fd_write(rsock.fileno()) - - def handle(*args): - nonlocal handler_args - handler_args = args - _macosx.stop() - - signal.signal(signal.SIGINT, handle) - try: - yield - finally: - wsock.close() - rsock.close() - signal.set_wakeup_fd(old_wakeup_fd) - signal.signal(signal.SIGINT, old_sigint_handler) - if handler_args is not None: - old_sigint_handler(*handler_args) - - @_Backend.export class _BackendMac(_Backend): FigureCanvas = FigureCanvasMac diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index cbc490ef6cd1..273a746d0b79 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -9,13 +9,12 @@ from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, TimerBase, cursors, ToolContainerBase, MouseButton, - CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent) + CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent, + _allow_interrupt) import matplotlib.backends.qt_editor.figureoptions as figureoptions from . import qt_compat from .qt_compat import ( - QtCore, QtGui, QtWidgets, __version__, QT_API, - _to_int, _isdeleted, _maybe_allow_interrupt -) + QtCore, QtGui, QtWidgets, __version__, QT_API, _to_int, _isdeleted) # SPECIAL_KEYS are Qt::Key that do *not* return their Unicode name @@ -148,6 +147,38 @@ def _create_qApp(): return app +def _allow_interrupt_qt(qapp_or_eventloop): + """A context manager that allows terminating a plot by sending a SIGINT.""" + + # Use QSocketNotifier to read the socketpair while the Qt event loop runs. + + def prepare_notifier(rsock): + sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read) + + @sn.activated.connect + def _may_clear_sock(): + # Running a Python function on socket activation gives the interpreter a + # chance to handle the signal in Python land. We also need to drain the + # socket with recv() to re-arm it, because it will be written to as part of + # the wakeup. (We need this in case set_wakeup_fd catches a signal other + # than SIGINT and we shall continue waiting.) + try: + rsock.recv(1) + except BlockingIOError: + # This may occasionally fire too soon or more than once on Windows, so + # be forgiving about reading an empty socket. + pass + + return sn # Actually keep the notifier alive. + + def handle_sigint(): + if hasattr(qapp_or_eventloop, 'closeAllWindows'): + qapp_or_eventloop.closeAllWindows() + qapp_or_eventloop.quit() + + return _allow_interrupt(prepare_notifier, handle_sigint) + + class TimerQT(TimerBase): """Subclass of `.TimerBase` using QTimer events.""" @@ -417,7 +448,7 @@ def start_event_loop(self, timeout=0): if timeout > 0: _ = QtCore.QTimer.singleShot(int(timeout * 1000), event_loop.quit) - with _maybe_allow_interrupt(event_loop): + with _allow_interrupt_qt(event_loop): qt_compat._exec(event_loop) def stop_event_loop(self, event=None): @@ -598,7 +629,7 @@ def resize(self, width, height): def start_main_loop(cls): qapp = QtWidgets.QApplication.instance() if qapp: - with _maybe_allow_interrupt(qapp): + with _allow_interrupt_qt(qapp): qt_compat._exec(qapp) def show(self): diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 98f9f9572d69..d91f7c14cb22 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -13,9 +13,6 @@ import os import platform import sys -import signal -import socket -import contextlib from packaging.version import parse as parse_version @@ -160,73 +157,3 @@ def _isdeleted(obj): def _exec(obj): # exec on PyQt6, exec_ elsewhere. obj.exec() if hasattr(obj, "exec") else obj.exec_() - - -@contextlib.contextmanager -def _maybe_allow_interrupt(qapp_or_eventloop): - """ - This manager allows to terminate a plot by sending a SIGINT. It is - necessary because the running Qt backend prevents Python interpreter to - run and process signals (i.e., to raise KeyboardInterrupt exception). To - solve this one needs to somehow wake up the interpreter and make it close - the plot window. We do this by using the signal.set_wakeup_fd() function - which organizes a write of the signal number into a socketpair connected - to the QSocketNotifier (since it is part of the Qt backend, it can react - to that write event). Afterwards, the Qt handler empties the socketpair - by a recv() command to re-arm it (we need this if a signal different from - SIGINT was caught by set_wakeup_fd() and we shall continue waiting). If - the SIGINT was caught indeed, after exiting the on_signal() function the - interpreter reacts to the SIGINT according to the handle() function which - had been set up by a signal.signal() call: it causes the qt_object to - exit by calling its quit() method. Finally, we call the old SIGINT - handler with the same arguments that were given to our custom handle() - handler. - - We do this only if the old handler for SIGINT was not None, which means - that a non-python handler was installed, i.e. in Julia, and not SIG_IGN - which means we should ignore the interrupts. - """ - - old_sigint_handler = signal.getsignal(signal.SIGINT) - if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): - yield - return - - handler_args = None - wsock, rsock = socket.socketpair() - wsock.setblocking(False) - rsock.setblocking(False) - old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) - sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read) - - # We do not actually care about this value other than running some Python code to - # ensure that the interpreter has a chance to handle the signal in Python land. We - # also need to drain the socket because it will be written to as part of the wakeup! - # There are some cases where this may fire too soon / more than once on Windows so - # we should be forgiving about reading an empty socket. - # Clear the socket to re-arm the notifier. - @sn.activated.connect - def _may_clear_sock(*args): - try: - rsock.recv(1) - except BlockingIOError: - pass - - def handle(*args): - nonlocal handler_args - handler_args = args - if hasattr(qapp_or_eventloop, 'closeAllWindows'): - qapp_or_eventloop.closeAllWindows() - qapp_or_eventloop.quit() - - signal.signal(signal.SIGINT, handle) - try: - yield - finally: - wsock.close() - rsock.close() - sn.setEnabled(False) - signal.set_wakeup_fd(old_wakeup_fd) - signal.signal(signal.SIGINT, old_sigint_handler) - if handler_args is not None: - old_sigint_handler(*handler_args) From df4480c18a0f465fec0b4fc873f1dbb1a9576323 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 7 Nov 2023 09:01:05 +0100 Subject: [PATCH 0423/1120] Shorten dviread helper definitions. --- lib/matplotlib/dviread.py | 80 +++++++++++---------------------------- 1 file changed, 22 insertions(+), 58 deletions(-) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index b2177e5087bc..ee2032b17be3 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -125,64 +125,28 @@ def glyph_name_or_index(self): # Opcode argument parsing # -# Each of the following functions takes a Dvi object and delta, -# which is the difference between the opcode and the minimum opcode -# with the same meaning. Dvi opcodes often encode the number of -# argument bytes in this delta. - -def _arg_raw(dvi, delta): - """Return *delta* without reading anything more from the dvi file.""" - return delta - - -def _arg(nbytes, signed, dvi, _): - """ - Read *nbytes* bytes, returning the bytes interpreted as a signed integer - if *signed* is true, unsigned otherwise. - """ - return dvi._arg(nbytes, signed) - - -def _arg_slen(dvi, delta): - """ - Read *delta* bytes, returning None if *delta* is zero, and the bytes - interpreted as a signed integer otherwise. - """ - if delta == 0: - return None - return dvi._arg(delta, True) - - -def _arg_slen1(dvi, delta): - """ - Read *delta*+1 bytes, returning the bytes interpreted as signed. - """ - return dvi._arg(delta + 1, True) - - -def _arg_ulen1(dvi, delta): - """ - Read *delta*+1 bytes, returning the bytes interpreted as unsigned. - """ - return dvi._arg(delta + 1, False) - - -def _arg_olen1(dvi, delta): - """ - Read *delta*+1 bytes, returning the bytes interpreted as - unsigned integer for 0<=*delta*<3 and signed if *delta*==3. - """ - return dvi._arg(delta + 1, delta == 3) - - -_arg_mapping = dict(raw=_arg_raw, - u1=partial(_arg, 1, False), - u4=partial(_arg, 4, False), - s4=partial(_arg, 4, True), - slen=_arg_slen, - olen1=_arg_olen1, - slen1=_arg_slen1, - ulen1=_arg_ulen1) +# Each of the following functions takes a Dvi object and delta, which is the +# difference between the opcode and the minimum opcode with the same meaning. +# Dvi opcodes often encode the number of argument bytes in this delta. +_arg_mapping = dict( + # raw: Return delta as is. + raw=lambda dvi, delta: delta, + # u1: Read 1 byte as an unsigned number. + u1=lambda dvi, delta: dvi._arg(1, signed=False), + # u4: Read 4 bytes as an unsigned number. + u4=lambda dvi, delta: dvi._arg(4, signed=False), + # s4: Read 4 bytes as a signed number. + s4=lambda dvi, delta: dvi._arg(4, signed=True), + # slen: Read delta bytes as a signed number, or None if delta is None. + slen=lambda dvi, delta: dvi._arg(delta, signed=True) if delta else None, + # slen1: Read (delta + 1) bytes as a signed number. + slen1=lambda dvi, delta: dvi._arg(delta + 1, signed=True), + # ulen1: Read (delta + 1) bytes as an unsigned number. + ulen1=lambda dvi, delta: dvi._arg(delta + 1, signed=False), + # olen1: Read (delta + 1) bytes as an unsigned number if less than 4 bytes, + # as a signed number if 4 bytes. + olen1=lambda dvi, delta: dvi._arg(delta + 1, signed=(delta == 3)), +) def _dispatch(table, min, max=None, state=None, args=('raw',)): From 907f0b75b3742f7458fd832a0e14750e0e8ef902 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 8 Nov 2023 20:30:15 +0100 Subject: [PATCH 0424/1120] Use int.from_bytes instead of implementing the conversion ourselves. int.from_bytes has been around since Python 3.2. --- lib/matplotlib/dviread.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index b2177e5087bc..e60c684f8c3a 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -392,16 +392,10 @@ def _read(self): def _arg(self, nbytes, signed=False): """ - Read and return an integer argument *nbytes* long. + Read and return a big-endian integer *nbytes* long. Signedness is determined by the *signed* keyword. """ - buf = self.file.read(nbytes) - value = buf[0] - if signed and value >= 0x80: - value = value - 0x100 - for b in buf[1:]: - value = 0x100*value + b - return value + return int.from_bytes(self.file.read(nbytes), "big", signed=signed) @_dispatch(min=0, max=127, state=_dvistate.inpage) def _set_char_immediate(self, char): From ca62d0b4249fc7c238baf7cbbed04fc74774f821 Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 6 Nov 2023 11:17:05 -0500 Subject: [PATCH 0425/1120] MAINT: fix .yml in tag issue template Co-authored-by: Elliott Sales de Andrade --- .github/ISSUE_TEMPLATE/tag_proposal.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/tag_proposal.yml b/.github/ISSUE_TEMPLATE/tag_proposal.yml index 1e3bceedf70c..6876226f6240 100644 --- a/.github/ISSUE_TEMPLATE/tag_proposal.yml +++ b/.github/ISSUE_TEMPLATE/tag_proposal.yml @@ -23,4 +23,5 @@ body: id: solution attributes: label: Proposed solution - description: What should the tag be? All tags are in the format `subcategory: tag` + description: > + What should the tag be? All tags are in the format `subcategory: tag` From 072226aca1787cd57b577a76d69d5fe7ad3df19a Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 8 Nov 2023 16:29:05 -0600 Subject: [PATCH 0426/1120] Ensure GIL while releasing buffer --- src/_macosx.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_macosx.m b/src/_macosx.m index 6df00d0eca8e..a580362f676f 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -1132,8 +1132,10 @@ - (void)setCanvas: (PyObject*)newCanvas } static void _buffer_release(void* info, const void* data, size_t size) { + PyGILState_STATE gstate = PyGILState_Ensure(); PyBuffer_Release((Py_buffer *)info); free(info); + PyGILState_Release(gstate); } static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) From cde6baf25ca55aabb8ad031c7b42d38e37a6f1ac Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 8 Nov 2023 17:20:37 -0600 Subject: [PATCH 0427/1120] Expand 3D import to handle any exception not just ImportError Needed for the same reasons as #27178 (Namely that we should not be failing on import just because mpl_toolkits is broken), but mpl 3.6 presents with an AttributeError rather than ImportError See discussion in #26827 and #27289 Just expanded liberally in case there are other presentations we have not seen --- lib/matplotlib/projections/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 8ce118986065..4c5ef8e2508d 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -58,7 +58,7 @@ try: from mpl_toolkits.mplot3d import Axes3D -except ImportError: +except Exception: import warnings warnings.warn("Unable to import Axes3D. This may be due to multiple versions of " "Matplotlib being installed (e.g. as a system package and as a pip " From dd00bdee2ae211266818adca85d4071fe4d15719 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 7 Nov 2023 09:17:58 +0100 Subject: [PATCH 0428/1120] Remove idiosyncratic get_tick_iterator API. It's only ever used essentially to expose GridHelperCurveLinear._grid_info to FixedAxisArtistHelper.get_tick_iterators; it seems better to access it from the sole caller. (Note that floating_axes already does the same direct access to the private _grid_info.) Removing get_tick_iterator also removes the confusion with get_tick_iterators (plural), which is a common API in axisartist. Also note that the "minor" parameter of get_tick_iterator had a rather peculiar meaning (it only switched the labels off). --- .../deprecations/27300-AL.rst | 3 +++ .../axisartist/grid_helper_curvelinear.py | 22 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/27300-AL.rst diff --git a/doc/api/next_api_changes/deprecations/27300-AL.rst b/doc/api/next_api_changes/deprecations/27300-AL.rst new file mode 100644 index 000000000000..87f4bb259537 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27300-AL.rst @@ -0,0 +1,3 @@ +``GridHelperCurveLinear.get_tick_iterator`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated with no replacement. diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index 08031122c03b..8dbfa6adf90f 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -3,7 +3,6 @@ """ import functools -from itertools import chain import numpy as np @@ -76,10 +75,18 @@ def get_tick_iterators(self, axes): "top": "bottom", "bottom": "top"}[self.side] else: side = self.side - g = self.grid_helper - ti1 = g.get_tick_iterator(self.nth_coord_ticks, side) - ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True) - return chain(ti1, ti2), iter([]) + + angle_tangent = dict(left=90, right=90, bottom=0, top=0)[side] + + def iter_major(): + for nth_coord, show_labels in [ + (self.nth_coord_ticks, True), (1 - self.nth_coord_ticks, False)]: + gi = self.grid_helper._grid_info[["lon", "lat"][nth_coord]] + for (xy, angle_normal), l in zip( + gi["tick_locs"][side], gi["tick_labels"][side]): + yield xy, angle_normal, angle_tangent, (l if show_labels else "") + + return iter_major(), iter([]) class FloatingAxisArtistHelper(_FloatingAxisArtistHelperBase): @@ -211,14 +218,14 @@ def trf_xy(x, y): in_01 = functools.partial( mpl.transforms._interval_contains_close, (0, 1)) - def f1(): + def iter_major(): for x, y, normal, tangent, lab \ in zip(xx1, yy1, angle_normal, angle_tangent, labels): c2 = tick_to_axes.transform((x, y)) if in_01(c2[0]) and in_01(c2[1]): yield [x, y], *np.rad2deg([normal, tangent]), lab - return f1(), iter([]) + return iter_major(), iter([]) def get_line_transform(self, axes): return axes.transData @@ -309,6 +316,7 @@ def get_gridlines(self, which="major", axis="both"): grid_lines.extend(gl) return grid_lines + @_api.deprecated("3.9") def get_tick_iterator(self, nth_coord, axis_side, minor=False): angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] lon_or_lat = ["lon", "lat"][nth_coord] From d916aae54f098daee33fa311f226bc87f8e3099e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 9 Nov 2023 22:14:50 -0500 Subject: [PATCH 0429/1120] FIX: allow re-shown Qt windows to be re-destroyed When a Qt window is closed we do not strip off the Qt components and it is technically possible to re-show it. However, the latch we use to detect re-entrant destruction does not get reset so the figure can not be re-destroyed. --- lib/matplotlib/backends/backend_qt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index cbc490ef6cd1..b79698f1d661 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -602,6 +602,7 @@ def start_main_loop(cls): qt_compat._exec(qapp) def show(self): + self.window._destroying = False self.window.show() if mpl.rcParams['figure.raise_window']: self.window.activateWindow() From cc77c0b45deebbb02e26ba9f46c991c3b52ef172 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 12 Nov 2023 00:11:32 +0100 Subject: [PATCH 0430/1120] Simplify GridSpec setup in make_axes_gridspec. Currently, make_axes_gridspec uses two nested gridspecs, first a horizontal (1, 2) gridspec and second a vertical (3, 1) gridspec to position an axes A and the associated left-colorbar C as ``` A. AC A. ``` (and similarly for colorbars on other sides of the main axes). Instead, this can be done with a single (3, 2) gridspec to position both A and C. --- lib/matplotlib/colorbar.py | 55 ++++++++++++++------------------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 6c92f3795384..b54211654d13 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1518,45 +1518,30 @@ def make_axes_gridspec(parent, *, location=None, orientation=None, wh_space = 2 * pad / (1 - pad) if location in ('left', 'right'): - # for shrinking - height_ratios = [ - (1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)] - + gs = parent.get_subplotspec().subgridspec( + 3, 2, wspace=wh_space, hspace=0, + height_ratios=[(1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)]) if location == 'left': - gs = parent.get_subplotspec().subgridspec( - 1, 2, wspace=wh_space, - width_ratios=[fraction, 1-fraction-pad]) - ss_main = gs[1] - ss_cb = gs[0].subgridspec( - 3, 1, hspace=0, height_ratios=height_ratios)[1] + gs.set_width_ratios([fraction, 1 - fraction - pad]) + ss_main = gs[:, 1] + ss_cb = gs[1, 0] else: - gs = parent.get_subplotspec().subgridspec( - 1, 2, wspace=wh_space, - width_ratios=[1-fraction-pad, fraction]) - ss_main = gs[0] - ss_cb = gs[1].subgridspec( - 3, 1, hspace=0, height_ratios=height_ratios)[1] + gs.set_width_ratios([1 - fraction - pad, fraction]) + ss_main = gs[:, 0] + ss_cb = gs[1, 1] else: - # for shrinking - width_ratios = [ - anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)] - - if location == 'bottom': - gs = parent.get_subplotspec().subgridspec( - 2, 1, hspace=wh_space, - height_ratios=[1-fraction-pad, fraction]) - ss_main = gs[0] - ss_cb = gs[1].subgridspec( - 1, 3, wspace=0, width_ratios=width_ratios)[1] - aspect = 1 / aspect + gs = parent.get_subplotspec().subgridspec( + 2, 3, hspace=wh_space, wspace=0, + width_ratios=[anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)]) + if location == 'top': + gs.set_height_ratios([fraction, 1 - fraction - pad]) + ss_main = gs[1, :] + ss_cb = gs[0, 1] else: - gs = parent.get_subplotspec().subgridspec( - 2, 1, hspace=wh_space, - height_ratios=[fraction, 1-fraction-pad]) - ss_main = gs[1] - ss_cb = gs[0].subgridspec( - 1, 3, wspace=0, width_ratios=width_ratios)[1] - aspect = 1 / aspect + gs.set_height_ratios([1 - fraction - pad, fraction]) + ss_main = gs[0, :] + ss_cb = gs[1, 1] + aspect = 1 / aspect parent.set_subplotspec(ss_main) if panchor is not False: From 4e35ed6e1903dc937385025d8685fb5236ab81b2 Mon Sep 17 00:00:00 2001 From: hannah Date: Sun, 12 Nov 2023 19:50:29 -0500 Subject: [PATCH 0431/1120] added redirect for step --- galleries/plot_types/basic/stairs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/galleries/plot_types/basic/stairs.py b/galleries/plot_types/basic/stairs.py index e8ca455c0e2f..9bc5d025f1e1 100644 --- a/galleries/plot_types/basic/stairs.py +++ b/galleries/plot_types/basic/stairs.py @@ -4,6 +4,8 @@ ============== See `~matplotlib.axes.Axes.stairs`. + +.. redirect-from:: /plot_types/basic/step """ import matplotlib.pyplot as plt import numpy as np From 801fb26acb912a691feffefcf217ba1b8b200937 Mon Sep 17 00:00:00 2001 From: Stefan <96178532+stefan6419846@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:25:31 +0100 Subject: [PATCH 0432/1120] DOC: Synchronize LICENSE_STIX files --- LICENSE/LICENSE_STIX | 193 +++++++++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 70 deletions(-) diff --git a/LICENSE/LICENSE_STIX b/LICENSE/LICENSE_STIX index 2f7aeea331ce..6034d9474814 100644 --- a/LICENSE/LICENSE_STIX +++ b/LICENSE/LICENSE_STIX @@ -1,71 +1,124 @@ -TERMS AND CONDITIONS - - 1. Permission is hereby granted, free of charge, to any person -obtaining a copy of the STIX Fonts-TM set accompanying this license -(collectively, the "Fonts") and the associated documentation files -(collectively with the Fonts, the "Font Software"), to reproduce and -distribute the Font Software, including the rights to use, copy, merge -and publish copies of the Font Software, and to permit persons to whom -the Font Software is furnished to do so same, subject to the following -terms and conditions (the "License"). - - 2. The following copyright and trademark notice and these Terms and -Conditions shall be included in all copies of one or more of the Font -typefaces and any derivative work created as permitted under this -License: - - Copyright (c) 2001-2005 by the STI Pub Companies, consisting of -the American Institute of Physics, the American Chemical Society, the -American Mathematical Society, the American Physical Society, Elsevier, -Inc., and The Institute of Electrical and Electronic Engineers, Inc. -Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright -(c) 1990 by Elsevier, Inc. All rights reserved. STIX Fonts-TM is a -trademark of The Institute of Electrical and Electronics Engineers, Inc. - - 3. You may (a) convert the Fonts from one format to another (e.g., -from TrueType to PostScript), in which case the normal and reasonable -distortion that occurs during such conversion shall be permitted and (b) -embed or include a subset of the Fonts in a document for the purposes of -allowing users to read text in the document that utilizes the Fonts. In -each case, you may use the STIX Fonts-TM mark to designate the resulting -Fonts or subset of the Fonts. - - 4. You may also (a) add glyphs or characters to the Fonts, or modify -the shape of existing glyphs, so long as the base set of glyphs is not -removed and (b) delete glyphs or characters from the Fonts, provided -that the resulting font set is distributed with the following -disclaimer: "This [name] font does not include all the Unicode points -covered in the STIX Fonts-TM set but may include others." In each case, -the name used to denote the resulting font set shall not include the -term "STIX" or any similar term. - - 5. You may charge a fee in connection with the distribution of the -Font Software, provided that no copy of one or more of the individual -Font typefaces that form the STIX Fonts-TM set may be sold by itself. - - 6. THE FONT SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK OR OTHER RIGHT. IN NO EVENT SHALL -MICROPRESS OR ANY OF THE STI PUB COMPANIES BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, INCLUDING, BUT NOT LIMITED TO, ANY GENERAL, -SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM OR OUT OF THE USE OR -INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT -SOFTWARE. - - 7. Except as contained in the notice set forth in Section 2, the -names MicroPress Inc. and STI Pub Companies, as well as the names of the -companies/organizations that compose the STI Pub Companies, shall not be -used in advertising or otherwise to promote the sale, use or other -dealings in the Font Software without the prior written consent of the -respective company or organization. - - 8. This License shall become null and void in the event of any -material breach of the Terms and Conditions herein by licensee. - - 9. A substantial portion of the STIX Fonts set was developed by -MicroPress Inc. for the STI Pub Companies. To obtain additional -mathematical fonts, please contact MicroPress, Inc., 68-30 Harrow -Street, Forest Hills, NY 11375, USA - Phone: (718) 575-1816. +The STIX fonts distributed with matplotlib have been modified from +their canonical form. They have been converted from OTF to TTF format +using Fontforge and this script: + #!/usr/bin/env fontforge + i=1 + while ( i<$argc ) + Open($argv[i]) + Generate($argv[i]:r + ".ttf") + i = i+1 + endloop + +The original STIX Font License begins below. + +----------------------------------------------------------- + +STIX Font License + +24 May 2010 + +Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the American +Institute of Physics, the American Chemical Society, the American Mathematical +Society, the American Physical Society, Elsevier, Inc., and The Institute of +Electrical and Electronic Engineers, Inc. (www.stixfonts.org), with Reserved +Font Name STIX Fonts, STIX Fonts (TM) is a trademark of The Institute of +Electrical and Electronics Engineers, Inc. + +Portions copyright (c) 1998-2003 by MicroPress, Inc. (www.micropress-inc.com), +with Reserved Font Name TM Math. To obtain additional mathematical fonts, please +contact MicroPress, Inc., 68-30 Harrow Street, Forest Hills, NY 11375, USA, +Phone: (718) 575-1816. + +Portions copyright (c) 1990 by Elsevier, Inc. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. From dd81b6658e202614947b093794bacebafd374a0b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 4 Oct 2023 02:58:40 -0400 Subject: [PATCH 0433/1120] Convert TkAgg utilities to pybind11 --- lib/matplotlib/backends/_backend_tk.py | 13 +- lib/matplotlib/backends/_tkagg.pyi | 15 ++ lib/matplotlib/tests/test_backend_tk.py | 8 +- src/_tkagg.cpp | 278 +++++++++++------------- src/meson.build | 2 +- 5 files changed, 148 insertions(+), 168 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 89c380f8c96b..9a3ea70fbc24 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -23,6 +23,7 @@ CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent) from matplotlib._pylab_helpers import Gcf from . import _tkagg +from ._tkagg import TK_PHOTO_COMPOSITE_OVERLAY, TK_PHOTO_COMPOSITE_SET _log = logging.getLogger(__name__) @@ -51,9 +52,6 @@ def _restore_foreground_window_at_end(): # Initialize to a non-empty string that is not a Tcl command _blit_tcl_name = "mpl_blit_" + uuid.uuid4().hex -TK_PHOTO_COMPOSITE_OVERLAY = 0 # apply transparency rules pixel-wise -TK_PHOTO_COMPOSITE_SET = 1 # set image buffer directly - def _blit(argsid): """ @@ -62,11 +60,11 @@ def _blit(argsid): *argsid* is a unique string identifier to fetch the correct arguments from the ``_blit_args`` dict, since arguments cannot be passed directly. """ - photoimage, dataptr, offsets, bboxptr, comp_rule = _blit_args.pop(argsid) + photoimage, data, offsets, bbox, comp_rule = _blit_args.pop(argsid) if not photoimage.tk.call("info", "commands", photoimage): return - _tkagg.blit(photoimage.tk.interpaddr(), str(photoimage), dataptr, - comp_rule, offsets, bboxptr) + _tkagg.blit(photoimage.tk.interpaddr(), str(photoimage), data, comp_rule, offsets, + bbox) def blit(photoimage, aggimage, offsets, bbox=None): @@ -87,7 +85,6 @@ def blit(photoimage, aggimage, offsets, bbox=None): """ data = np.asarray(aggimage) height, width = data.shape[:2] - dataptr = (height, width, data.ctypes.data) if bbox is not None: (x1, y1), (x2, y2) = bbox.__array__() x1 = max(math.floor(x1), 0) @@ -109,7 +106,7 @@ def blit(photoimage, aggimage, offsets, bbox=None): # tkapp.call coerces all arguments to strings, so to avoid string parsing # within _blit, pack up the arguments into a global data structure. - args = photoimage, dataptr, offsets, bboxptr, comp_rule + args = photoimage, data, offsets, bboxptr, comp_rule # Need a unique key to avoid thread races. # Again, make the key a string to avoid string parsing in _blit. argsid = str(id(args)) diff --git a/lib/matplotlib/backends/_tkagg.pyi b/lib/matplotlib/backends/_tkagg.pyi index e69de29bb2d1..5c3f1112ec7c 100644 --- a/lib/matplotlib/backends/_tkagg.pyi +++ b/lib/matplotlib/backends/_tkagg.pyi @@ -0,0 +1,15 @@ +import numpy as np +from numpy.typing import NDArray + +TK_PHOTO_COMPOSITE_OVERLAY: int +TK_PHOTO_COMPOSITE_SET: int + +def blit( + interp: int, + photo_name: str, + data: NDArray[np.uint8], + comp_rule: int, + offset: tuple[int, int, int, int], + bbox: tuple[int, int, int, int], +) -> None: ... +def enable_dpi_awareness(frame_handle: int, interp: int) -> bool | None: ... diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index e44e5589452b..ee20a94042f7 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -81,9 +81,7 @@ def test_blit(): fig, ax = plt.subplots() photoimage = fig.canvas._tkphoto - data = np.ones((4, 4, 4)) - height, width = data.shape[:2] - dataptr = (height, width, data.ctypes.data) + data = np.ones((4, 4, 4), dtype=np.uint8) # Test out of bounds blitting. bad_boxes = ((-1, 2, 0, 2), (2, 0, 0, 2), @@ -94,8 +92,8 @@ def test_blit(): for bad_box in bad_boxes: try: _tkagg.blit( - photoimage.tk.interpaddr(), str(photoimage), dataptr, 0, - (0, 1, 2, 3), bad_box) + photoimage.tk.interpaddr(), str(photoimage), data, + _tkagg.TK_PHOTO_COMPOSITE_OVERLAY, (0, 1, 2, 3), bad_box) except ValueError: print("success") diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index a967845707d4..ecc87de1aff4 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -9,6 +9,13 @@ // rewritten, we have removed the PIL licensing information. If you want PIL, // you can get it at https://python-pillow.org/ +#include +#include +#include +#include +#include +#include + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN // Windows 8.1 @@ -16,8 +23,10 @@ #define _WIN32_WINNT 0x0603 #endif -#define PY_SSIZE_T_CLEAN -#include +#include +#include +namespace py = pybind11; +using namespace pybind11::literals; #ifdef _WIN32 #define WIN32_DLL @@ -38,24 +47,29 @@ static inline PyObject *PyErr_SetFromWindowsErr(int ierr) { #endif #ifdef WIN32_DLL -#include #include #include #define PSAPI_VERSION 1 #include // Must be linked with 'psapi' library #define dlsym GetProcAddress +#define UNUSED_ON_NON_WINDOWS(x) x #else #include +#define UNUSED_ON_NON_WINDOWS Py_UNUSED #endif // Include our own excerpts from the Tcl / Tk headers #include "_tkmini.h" -static int convert_voidptr(PyObject *obj, void *p) +template +static T +convert_voidptr(const py::object &obj) { - void **val = (void **)p; - *val = PyLong_AsVoidPtr(obj); - return *val != NULL ? 1 : !PyErr_Occurred(); + auto result = static_cast(PyLong_AsVoidPtr(obj.ptr())); + if (PyErr_Occurred()) { + throw py::error_already_set(); + } + return result; } // Global vars for Tk functions. We load these symbols from the tkinter @@ -66,61 +80,59 @@ static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; // extension module or loaded Tcl libraries at run-time. static Tcl_SetVar_t TCL_SETVAR; -static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) +static void +mpl_tk_blit(py::object interp_obj, const char *photo_name, + py::array_t data, int comp_rule, + std::tuple offset, std::tuple bbox) { - Tcl_Interp *interp; - char const *photo_name; - int height, width; - unsigned char *data_ptr; - int comp_rule; - int put_retval; - int o0, o1, o2, o3; - int x1, x2, y1, y2; + auto interp = convert_voidptr(interp_obj); + Tk_PhotoHandle photo; - Tk_PhotoImageBlock block; - if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit", - convert_voidptr, &interp, &photo_name, - &height, &width, convert_voidptr, &data_ptr, - &comp_rule, - &o0, &o1, &o2, &o3, - &x1, &x2, &y1, &y2)) { - goto exit; - } if (!(photo = TK_FIND_PHOTO(interp, photo_name))) { - PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle"); - goto exit; + throw py::value_error("Failed to extract Tk_PhotoHandle"); + } + + auto data_ptr = data.mutable_unchecked<3>(); // Checks ndim and writeable flag. + if (data.shape(2) != 4) { + throw py::value_error("Data pointer must be RGBA; last dimension is " + + std::to_string(data.shape(2)) + ", not 4"); + } + if (data.shape(0) > INT_MAX) { // Limited by Tk_PhotoPutBlock argument type. + throw std::range_error( + "Height (" + std::to_string(data.shape(0)) + + ") exceeds maximum allowable size (" + std::to_string(INT_MAX) + ")"); + } + if (data.shape(1) > INT_MAX / 4) { // Limited by Tk_PhotoImageBlock.pitch field. + throw std::range_error( + "Width (" + std::to_string(data.shape(1)) + + ") exceeds maximum allowable size (" + std::to_string(INT_MAX / 4) + ")"); } + const auto height = static_cast(data.shape(0)); + const auto width = static_cast(data.shape(1)); + int x1, x2, y1, y2; + std::tie(x1, x2, y1, y2) = bbox; if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) { - PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds"); - goto exit; + throw py::value_error("Attempting to draw out of bounds"); } if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) { - PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument"); - goto exit; + throw py::value_error("Invalid comp_rule argument"); } - Py_BEGIN_ALLOW_THREADS - block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1); + int put_retval; + Tk_PhotoImageBlock block; + block.pixelPtr = data_ptr.mutable_data(height - y2, x1, 0); block.width = x2 - x1; block.height = y2 - y1; block.pitch = 4 * width; block.pixelSize = 4; - block.offset[0] = o0; - block.offset[1] = o1; - block.offset[2] = o2; - block.offset[3] = o3; - put_retval = TK_PHOTO_PUT_BLOCK( - interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); - Py_END_ALLOW_THREADS - if (put_retval == TCL_ERROR) { - return PyErr_NoMemory(); + std::tie(block.offset[0], block.offset[1], block.offset[2], block.offset[3]) = offset; + { + py::gil_scoped_release release; + put_retval = TK_PHOTO_PUT_BLOCK( + interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); } - -exit: - if (PyErr_Occurred()) { - return NULL; - } else { - Py_RETURN_NONE; + if (put_retval == TCL_ERROR) { + throw std::bad_alloc(); } } @@ -159,27 +171,13 @@ DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, } #endif -static PyObject* -mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, - Py_ssize_t nargs) +static py::object +mpl_tk_enable_dpi_awareness(py::object UNUSED_ON_NON_WINDOWS(frame_handle_obj), + py::object UNUSED_ON_NON_WINDOWS(interp_obj)) { - if (nargs != 2) { - return PyErr_Format(PyExc_TypeError, - "enable_dpi_awareness() takes 2 positional " - "arguments but %zd were given", - nargs); - } - #ifdef WIN32_DLL - HWND frame_handle = NULL; - Tcl_Interp *interp = NULL; - - if (!convert_voidptr(args[0], &frame_handle)) { - return NULL; - } - if (!convert_voidptr(args[1], &interp)) { - return NULL; - } + auto frame_handle = convert_voidptr(frame_handle_obj); + auto interp = convert_voidptr(interp_obj); #ifdef _DPI_AWARENESS_CONTEXTS_ HMODULE user32 = LoadLibrary("user32.dll"); @@ -190,7 +188,7 @@ mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, user32, "GetWindowDpiAwarenessContext"); if (GetWindowDpiAwarenessContextPtr == NULL) { FreeLibrary(user32); - Py_RETURN_FALSE; + return py::cast(false); } typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual_t)(DPI_AWARENESS_CONTEXT, @@ -200,7 +198,7 @@ mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, user32, "AreDpiAwarenessContextsEqual"); if (AreDpiAwarenessContextsEqualPtr == NULL) { FreeLibrary(user32); - Py_RETURN_FALSE; + return py::cast(false); } DPI_AWARENESS_CONTEXT ctx = GetWindowDpiAwarenessContextPtr(frame_handle); @@ -217,20 +215,13 @@ mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, SetWindowSubclass(frame_handle, DpiSubclassProc, 0, (DWORD_PTR)interp); } FreeLibrary(user32); - return PyBool_FromLong(per_monitor); + return py::cast(per_monitor); #endif #endif - Py_RETURN_NONE; + return py::none(); } -static PyMethodDef functions[] = { - { "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS }, - { "enable_dpi_awareness", (PyCFunction)mpl_tk_enable_dpi_awareness, - METH_FASTCALL }, - { NULL, NULL } /* sentinel */ -}; - // Functions to fill global Tcl/Tk function pointers by dynamic loading. template @@ -259,30 +250,26 @@ bool load_tcl_tk(T lib) * names. */ -void load_tkinter_funcs(void) +static void +load_tkinter_funcs() { HANDLE process = GetCurrentProcess(); // Pseudo-handle, doesn't need closing. - HMODULE* modules = NULL; DWORD size; if (!EnumProcessModules(process, NULL, 0, &size)) { PyErr_SetFromWindowsErr(0); - goto exit; + throw py::error_already_set(); } - if (!(modules = static_cast(malloc(size)))) { - PyErr_NoMemory(); - goto exit; - } - if (!EnumProcessModules(process, modules, size, &size)) { + auto count = size / sizeof(HMODULE); + auto modules = std::vector(count); + if (!EnumProcessModules(process, modules.data(), size, &size)) { PyErr_SetFromWindowsErr(0); - goto exit; + throw py::error_already_set(); } - for (unsigned i = 0; i < size / sizeof(HMODULE); ++i) { - if (load_tcl_tk(modules[i])) { + for (auto mod: modules) { + if (load_tcl_tk(mod)) { return; } } -exit: - free(modules); } #else // not Windows @@ -293,85 +280,68 @@ void load_tkinter_funcs(void) * dynamic library (module). */ -void load_tkinter_funcs(void) +static void +load_tkinter_funcs() { // Load tkinter global funcs from tkinter compiled module. - void *main_program = NULL, *tkinter_lib = NULL; - PyObject *module = NULL, *py_path = NULL, *py_path_b = NULL; - char *path; // Try loading from the main program namespace first. - main_program = dlopen(NULL, RTLD_LAZY); - if (load_tcl_tk(main_program)) { - goto exit; + auto main_program = dlopen(NULL, RTLD_LAZY); + auto success = load_tcl_tk(main_program); + // We don't need to keep a reference open as the main program always exists. + if (dlclose(main_program)) { + throw std::runtime_error(dlerror()); + } + if (success) { + return; } - // Clear exception triggered when we didn't find symbols above. - PyErr_Clear(); + py::object module; // Handle PyPy first, as that import will correctly fail on CPython. - module = PyImport_ImportModule("_tkinter.tklib_cffi"); // PyPy - if (!module) { - PyErr_Clear(); - module = PyImport_ImportModule("_tkinter"); // CPython + try { + module = py::module_::import("_tkinter.tklib_cffi"); // PyPy + } catch (py::error_already_set &e) { + module = py::module_::import("_tkinter"); // CPython } - if (!(module && - (py_path = PyObject_GetAttrString(module, "__file__")) && - (py_path_b = PyUnicode_EncodeFSDefault(py_path)) && - (path = PyBytes_AsString(py_path_b)))) { - goto exit; - } - tkinter_lib = dlopen(path, RTLD_LAZY); + auto py_path = module.attr("__file__"); + py::bytes py_path_b = py_path.attr("encode")( + Py_FileSystemDefaultEncoding, Py_FileSystemDefaultEncodeErrors); + std::string path = py_path_b; + auto tkinter_lib = dlopen(path.c_str(), RTLD_LAZY); if (!tkinter_lib) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - goto exit; - } - if (load_tcl_tk(tkinter_lib)) { - goto exit; + throw std::runtime_error(dlerror()); } - -exit: - // We don't need to keep a reference open as the main program & tkinter - // have been imported. Try to close each library separately (otherwise the - // second dlclose could clear a dlerror from the first dlclose). - bool raised_dlerror = false; - if (main_program && dlclose(main_program) && !raised_dlerror) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - raised_dlerror = true; + load_tcl_tk(tkinter_lib); + // We don't need to keep a reference open as tkinter has been imported. + if (dlclose(tkinter_lib)) { + throw std::runtime_error(dlerror()); } - if (tkinter_lib && dlclose(tkinter_lib) && !raised_dlerror) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - raised_dlerror = true; - } - Py_XDECREF(module); - Py_XDECREF(py_path); - Py_XDECREF(py_path_b); } #endif // end not Windows -static PyModuleDef _tkagg_module = { - PyModuleDef_HEAD_INIT, "_tkagg", NULL, -1, functions -}; - -PyMODINIT_FUNC PyInit__tkagg(void) +PYBIND11_MODULE(_tkagg, m) { - load_tkinter_funcs(); - PyObject *type, *value, *traceback; - PyErr_Fetch(&type, &value, &traceback); - // Always raise ImportError (normalizing a previously set exception if - // needed) to interact properly with backend auto-fallback. - if (value) { - PyErr_NormalizeException(&type, &value, &traceback); - PyErr_SetObject(PyExc_ImportError, value); - return NULL; - } else if (!TCL_SETVAR) { - PyErr_SetString(PyExc_ImportError, "Failed to load Tcl_SetVar"); - return NULL; + try { + load_tkinter_funcs(); + } catch (py::error_already_set& e) { + // Always raise ImportError to interact properly with backend auto-fallback. + py::raise_from(e, PyExc_ImportError, "failed to load tkinter functions"); + throw py::error_already_set(); + } + + if (!TCL_SETVAR) { + throw py::import_error("Failed to load Tcl_SetVar"); } else if (!TK_FIND_PHOTO) { - PyErr_SetString(PyExc_ImportError, "Failed to load Tk_FindPhoto"); - return NULL; + throw py::import_error("Failed to load Tk_FindPhoto"); } else if (!TK_PHOTO_PUT_BLOCK) { - PyErr_SetString(PyExc_ImportError, "Failed to load Tk_PhotoPutBlock"); - return NULL; + throw py::import_error("Failed to load Tk_PhotoPutBlock"); } - return PyModule_Create(&_tkagg_module); + + m.def("blit", &mpl_tk_blit, + "interp"_a, "photo_name"_a, "data"_a, "comp_rule"_a, "offset"_a, "bbox"_a); + m.def("enable_dpi_awareness", &mpl_tk_enable_dpi_awareness, + "frame_handle"_a, "interp"_a); + + m.attr("TK_PHOTO_COMPOSITE_OVERLAY") = TK_PHOTO_COMPOSITE_OVERLAY; + m.attr("TK_PHOTO_COMPOSITE_SET") = TK_PHOTO_COMPOSITE_SET; } diff --git a/src/meson.build b/src/meson.build index 45f64e954866..db064a9c5ca1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -139,7 +139,7 @@ extension_data = { 'include_directories': include_directories('.'), # The dl/psapi libraries are needed for finding Tcl/Tk at run time. 'dependencies': [ - numpy_dep, agg_dep.partial_dependency(includes: true), dl, comctl32, psapi, + pybind11_dep, agg_dep.partial_dependency(includes: true), dl, comctl32, psapi, ], }, '_tri': { From 5aeb872ddd26fbe3634b1a4d386d7e6e5a4f3dbf Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 13 Nov 2023 21:39:48 -0500 Subject: [PATCH 0434/1120] BLD: Check for sufficient MinGW headers when using them --- src/_tkagg.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index ecc87de1aff4..567d7f88ba22 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -53,6 +53,11 @@ static inline PyObject *PyErr_SetFromWindowsErr(int ierr) { #include // Must be linked with 'psapi' library #define dlsym GetProcAddress #define UNUSED_ON_NON_WINDOWS(x) x +// Check for old headers that do not defined HiDPI functions and constants. +#if defined(__MINGW64_VERSION_MAJOR) +static_assert(__MINGW64_VERSION_MAJOR >= 6, + "mingw-w64-x86_64-headers >= 6 are required when compiling with MinGW"); +#endif #else #include #define UNUSED_ON_NON_WINDOWS Py_UNUSED From 63b064a7176201f953541912e629a2a4d92547be Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 14 Nov 2023 17:26:41 +0100 Subject: [PATCH 0435/1120] [DOC] Minor fixes for savefig-docstring --- lib/matplotlib/figure.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 86a9fdd1387e..cd26af84ccbe 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3186,10 +3186,10 @@ def savefig(self, fname, *, transparent=None, **kwargs): Call signature:: - savefig(fname, *, dpi='figure', format=None, metadata=None, - bbox_inches=None, pad_inches=0.1, - facecolor='auto', edgecolor='auto', - backend=None, **kwargs + savefig(fname, *, transparent=None, dpi='figure', format=None, + metadata=None, bbox_inches=None, pad_inches=0.1, + facecolor='auto', edgecolor='auto', backend=None, + **kwargs ) The available output formats depend on the backend being used. @@ -3214,6 +3214,22 @@ def savefig(self, fname, *, transparent=None, **kwargs): Other Parameters ---------------- + transparent : bool, default: :rc:`savefig.transparent` + If *True*, the Axes patches will all be transparent; the + Figure patch will also be transparent unless *facecolor* + and/or *edgecolor* are specified via kwargs. + + If *False* has no effect and the color of the Axes and + Figure patches are unchanged (unless the Figure patch + is specified via the *facecolor* and/or *edgecolor* keyword + arguments in which case those colors are used). + + The transparency of these patches will be restored to their + original values upon exit of this function. + + This is useful, for example, for displaying + a plot on top of a colored background on a web page. + dpi : float or 'figure', default: :rc:`savefig.dpi` The resolution in dots per inch. If 'figure', use the figure's dpi value. @@ -3273,22 +3289,6 @@ def savefig(self, fname, *, transparent=None, **kwargs): 'a10', 'b0' through 'b10'. Only supported for postscript output. - transparent : bool - If *True*, the Axes patches will all be transparent; the - Figure patch will also be transparent unless *facecolor* - and/or *edgecolor* are specified via kwargs. - - If *False* has no effect and the color of the Axes and - Figure patches are unchanged (unless the Figure patch - is specified via the *facecolor* and/or *edgecolor* keyword - arguments in which case those colors are used). - - The transparency of these patches will be restored to their - original values upon exit of this function. - - This is useful, for example, for displaying - a plot on top of a colored background on a web page. - bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional A list of extra artists that will be considered when the tight bbox is calculated. From 0c34e5e06fbaa3d8e5432df62f3628a576f9f353 Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 6 Nov 2023 22:19:31 -0500 Subject: [PATCH 0436/1120] swap xkcd script for humor sans add xkcd-script font to doc build Co-authored-by: Oscar Gustafsson --- .circleci/config.yml | 6 +++--- .devcontainer/devcontainer.json | 2 +- doc/devel/dependencies.rst | 3 +-- lib/matplotlib/mpl-data/matplotlibrc | 2 +- lib/matplotlib/mpl-data/stylelib/classic.mplstyle | 2 +- lib/matplotlib/pyplot.py | 12 ++++++------ lib/matplotlib/tests/test_axes.py | 2 +- lib/matplotlib/textpath.py | 2 +- 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e8c8c2fca394..2f79a6fbba5c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,7 +49,6 @@ commands: ffmpeg \ fonts-crosextra-carlito \ fonts-freefont-otf \ - fonts-humor-sans \ fonts-noto-cjk \ fonts-wqy-zenhei \ graphviz \ @@ -67,15 +66,16 @@ commands: fonts-install: steps: - restore_cache: - key: fonts-2 + key: fonts-4 - run: name: Install custom fonts command: | mkdir -p ~/.local/share/fonts wget -nc https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O ~/.local/share/fonts/Felipa-Regular.ttf || true + wget -nc https://github.com/ipython/xkcd-font/blob/master/xkcd-script/font/xkcd-script.ttf?raw=true -O ~/.local/share/fonts/xkcd-Script.ttf || true fc-cache -f -v - save_cache: - key: fonts-2 + key: fonts-4 paths: - ~/.local/share/fonts/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 814c066c43b1..ddec2754d03a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "features": { "ghcr.io/devcontainers/features/desktop-lite:1": {}, "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { - "packages": "inkscape,ffmpeg,dvipng,lmodern,cm-super,texlive-latex-base,texlive-latex-extra,texlive-fonts-recommended,texlive-latex-recommended,texlive-pictures,texlive-xetex,fonts-wqy-zenhei,graphviz,fonts-crosextra-carlito,fonts-freefont-otf,fonts-humor-sans,fonts-noto-cjk,optipng" + "packages": "inkscape,ffmpeg,dvipng,lmodern,cm-super,texlive-latex-base,texlive-latex-extra,texlive-fonts-recommended,texlive-latex-recommended,texlive-pictures,texlive-xetex,fonts-wqy-zenhei,graphviz,fonts-crosextra-carlito,fonts-freefont-otf,fonts-comic-neue,fonts-noto-cjk,optipng" } }, "onCreateCommand": ".devcontainer/setup.sh", diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 458fff51b82f..c0e761d6beb6 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -419,6 +419,5 @@ Optional * `Inkscape `_ * `optipng `_ -* the font "Humor Sans" (aka the "XKCD" font), or the free alternative - `Comic Neue `_ +* the font `xkcd script `_ or `Comic Neue `_ * the font "Times New Roman" diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 9bd8a622092e..301afc38456b 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -269,7 +269,7 @@ #font.serif: DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif #font.sans-serif: DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif #font.cursive: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, Comic Neue, Comic Sans MS, cursive -#font.fantasy: Chicago, Charcoal, Impact, Western, Humor Sans, xkcd, fantasy +#font.fantasy: Chicago, Charcoal, Impact, Western, xkcd script, fantasy #font.monospace: DejaVu Sans Mono, Bitstream Vera Sans Mono, Computer Modern Typewriter, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 8923ce6f0497..976ab291907b 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -91,7 +91,7 @@ font.size : 12.0 font.serif : DejaVu Serif, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif font.sans-serif: DejaVu Sans, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif font.cursive : Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, cursive -font.fantasy : Comic Sans MS, Chicago, Charcoal, ImpactWestern, Humor Sans, fantasy +font.fantasy : Comic Sans MS, Chicago, Charcoal, ImpactWestern, xkcd script, fantasy font.monospace : DejaVu Sans Mono, Andale Mono, Nimbus Mono L, Courier New, Courier, Fixed, Terminal, monospace ### TEXT diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 70ebd69cddb5..ddbb6cb36728 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -706,11 +706,12 @@ def xkcd( scale: float = 1, length: float = 100, randomness: float = 2 ) -> ExitStack: """ - Turn on `xkcd `_ sketch-style drawing mode. This will - only have effect on things drawn after this function is called. + Turn on `xkcd `_ sketch-style drawing mode. - For best results, the "Humor Sans" font should be installed: it is - not included with Matplotlib. + This will only have an effect on things drawn after this function is called. + + For best results, install the `xkcd script `_ + font; xkcd fonts are not packaged with Matplotlib. Parameters ---------- @@ -749,8 +750,7 @@ def xkcd( from matplotlib import patheffects rcParams.update({ - 'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue', - 'Comic Sans MS'], + 'font.family': ['xkcd', 'xkcd Script', 'Comic Neue', 'Comic Sans MS'], 'font.size': 14.0, 'path.sketch': (scale, length, randomness), 'path.effects': [ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 799bf5ddd774..dffbb2377a23 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8848,7 +8848,7 @@ def test_tick_param_labelfont(): fig, ax = plt.subplots() ax.plot([1, 2, 3, 4], [1, 2, 3, 4]) ax.set_xlabel('X label in Impact font', fontname='Impact') - ax.set_ylabel('Y label in Humor Sans', fontname='Humor Sans') + ax.set_ylabel('Y label in xkcd script', fontname='xkcd script') ax.tick_params(color='r', labelfontfamily='monospace') plt.title('Title in sans-serif') for text in ax.get_xticklabels(): diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index de97899f8a72..c00966d6e6c3 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -99,7 +99,7 @@ def get_text_path(self, prop, s, ismath=False): from matplotlib.text import TextToPath from matplotlib.font_manager import FontProperties - fp = FontProperties(family="Humor Sans", style="italic") + fp = FontProperties(family="Comic Neue", style="italic") verts, codes = TextToPath().get_text_path(fp, "ABC") path = Path(verts, codes, closed=False) From 52bf8e9e236176753ec0f416ef41ed1b9f1ba599 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 16 Nov 2023 12:19:42 -0600 Subject: [PATCH 0437/1120] Omit MOVETO lines from nearest contour logic Closes #27333 --- lib/matplotlib/contour.py | 21 ++++++++++++--------- lib/matplotlib/tests/test_contour.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index d3ae83c613b1..7404151e7699 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1339,15 +1339,18 @@ def _find_nearest_contour(self, xy, indices=None): for idx_level in indices: path = self._paths[idx_level] - if not len(path.vertices): - continue - lc = self.get_transform().transform(path.vertices) - d2, proj, leg = _find_closest_point_on_path(lc, xy) - if d2 < d2min: - d2min = d2 - idx_level_min = idx_level - idx_vtx_min = leg[1] - proj_min = proj + idx_vtx_start = 0 + for subpath in path._iter_connected_components(): + if not len(subpath.vertices): + continue + lc = self.get_transform().transform(subpath.vertices) + d2, proj, leg = _find_closest_point_on_path(lc, xy) + if d2 < d2min: + d2min = d2 + idx_level_min = idx_level + idx_vtx_min = leg[1] + idx_vtx_start + proj_min = proj + idx_vtx_start += len(subpath) return idx_level_min, idx_vtx_min, proj_min diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index db8ef03925cd..f79584be4086 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -125,6 +125,25 @@ def test_contour_manual_labels(split_collections): plt.clabel(cs, manual=pts, fontsize='small', colors=('r', 'g')) +def test_contour_manual_moveto(): + x = np.linspace(-10, 10) + y = np.linspace(-10, 10) + + X, Y = np.meshgrid(x, y) + + Z = X**2 * 1 / Y**2 - 1 + + contours = plt.contour(X, Y, Z, levels=[0, 100]) + + # This point lies on the `MOVETO` line for the 100 contour + # but is actually closest to the 0 contour + point = (1.3, 1) + clabels = plt.clabel(contours, manual=[point]) + + # Ensure that the 0 contour was chosen, not the 100 contour + assert clabels[0].get_text() == "0" + + @pytest.mark.parametrize("split_collections", [False, True]) @image_comparison(['contour_disconnected_segments'], remove_text=True, style='mpl20', extensions=['png']) From a15f571ed4dad5f0f604aa41420d91b4f979df95 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 17 Nov 2023 16:50:16 +0100 Subject: [PATCH 0438/1120] Fix draggable annotations on subfigures. - Picking on subfigures was broken because `ax = getattr(a, 'axes', None)` would return the SubFigure.axes *list* when `a` is a subfigure, so the axes identity test would just be completely wrong. Fix that by special-casing subfigures. (Ideally I would entirely remove the axes equality test because that also breaks in the case of overlapping axes, but that's a much bigger change.) - `ref_artist.figure._canvas_callbacks` would fail when the ref_artist is on a subfigure because subfigures don't have a _canvas_callbacks attribute; instead use `ref_artist.figure.canvas.callbacks` where accessing the canvas attribute correctly goes back to the toplevel figure first. See test for an example (which would previously fail with an AttributeError). --- lib/matplotlib/artist.py | 3 ++- lib/matplotlib/offsetbox.py | 3 +-- lib/matplotlib/tests/test_offsetbox.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 4a495531d900..d0db945ab3e5 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -537,7 +537,8 @@ def pick(self, mouseevent): for a in self.get_children(): # make sure the event happened in the same Axes ax = getattr(a, 'axes', None) - if (mouseevent.inaxes is None or ax is None + if (isinstance(a, mpl.figure.SubFigure) + or mouseevent.inaxes is None or ax is None or mouseevent.inaxes == ax): # we need to check if mouseevent.inaxes is None # because some objects associated with an Axes (e.g., a diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index acf93f5a34d9..0164638b1076 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -1458,7 +1458,7 @@ def __init__(self, ref_artist, use_blit=False): ref_artist.set_picker(True) self.got_artist = False self._use_blit = use_blit and self.canvas.supports_blit - callbacks = ref_artist.figure._canvas_callbacks + callbacks = self.canvas.callbacks self._disconnectors = [ functools.partial( callbacks.disconnect, callbacks._connect_picklable(name, func)) @@ -1471,7 +1471,6 @@ def __init__(self, ref_artist, use_blit=False): # A property, not an attribute, to maintain picklability. canvas = property(lambda self: self.ref_artist.figure.canvas) - cids = property(lambda self: [ disconnect.args[0] for disconnect in self._disconnectors[:2]]) diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py index 49b55e4c9326..f18fa7c777d1 100644 --- a/lib/matplotlib/tests/test_offsetbox.py +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -450,3 +450,13 @@ def test_remove_draggable(): an.draggable(True) an.remove() MouseEvent("button_release_event", fig.canvas, 1, 1)._process() + + +def test_draggable_in_subfigure(): + fig = plt.figure() + # Put annotation at lower left corner to make it easily pickable below. + ann = fig.subfigures().add_axes([0, 0, 1, 1]).annotate("foo", (0, 0)) + ann.draggable(True) + fig.canvas.draw() # Texts are non-pickable until the first draw. + MouseEvent("button_press_event", fig.canvas, 1, 1)._process() + assert ann._draggable.got_artist From d8595f2617714c002b015e5110303c5aba57c4d0 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 19 Nov 2023 09:38:46 -0700 Subject: [PATCH 0439/1120] FIX: scale norm of collections when first array is set Previously, Collections would defer auto-scaling the norm until draw time. Now the collection is scaled anytime an array is set and hasn't already been scaled previously. --- doc/api/next_api_changes/behavior/27347-GL.rst | 7 +++++++ lib/matplotlib/cm.py | 2 ++ lib/matplotlib/tests/test_collections.py | 10 ++++++++++ 3 files changed, 19 insertions(+) create mode 100644 doc/api/next_api_changes/behavior/27347-GL.rst diff --git a/doc/api/next_api_changes/behavior/27347-GL.rst b/doc/api/next_api_changes/behavior/27347-GL.rst new file mode 100644 index 000000000000..2cf8f65cd745 --- /dev/null +++ b/doc/api/next_api_changes/behavior/27347-GL.rst @@ -0,0 +1,7 @@ +ScalarMappables auto-scale their norm when an array is set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Collections previously deferred auto-scaling of the norm until draw time. +This has been changed to scale the norm whenever the first array is set +to align with the docstring and reduce unexpected behavior when +accessing the norm before drawing. diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index 3911986f3673..53c60c8a8883 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -536,6 +536,8 @@ def set_array(self, A): "converted to float") self._A = A + if not self.norm.scaled(): + self.norm.autoscale_None(A) def get_array(self): """ diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 1f7c104a4c07..5baaeaa5d388 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -292,6 +292,16 @@ def check_segments(coll, positions, linelength, lineoffset, orientation): assert segment[1, pos2] == positions[i] +def test_collection_norm_autoscale(): + # norm should be autoscaled when array is set, not deferred to draw time + lines = np.arange(24).reshape((4, 3, 2)) + coll = mcollections.LineCollection(lines, array=np.arange(4)) + assert coll.norm(2) == 2 / 3 + # setting a new array shouldn't update the already scaled limits + coll.set_array(np.arange(4) + 5) + assert coll.norm(2) == 2 / 3 + + def test_null_collection_datalim(): col = mcollections.PathCollection([]) col_data_lim = col.get_datalim(mtransforms.IdentityTransform()) From c905a650877b8b3bdca3f93ddc6618ea94329cb8 Mon Sep 17 00:00:00 2001 From: Raghuram Sirigiri Date: Sun, 19 Nov 2023 17:14:16 -0600 Subject: [PATCH 0440/1120] updated Api/animation documentation as per standards --- doc/api/animation_api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index c7134e302d2f..df39b5942199 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -166,7 +166,7 @@ A third method is to use closures to build up the required artists and functions. A fourth method is to create a class. Examples -~~~~~~~~ +^^^^^^^^ * :doc:`../gallery/animation/animate_decay` * :doc:`../gallery/animation/bayes_update` @@ -182,7 +182,7 @@ Examples ------------------- Examples -~~~~~~~~ +^^^^^^^^ * :doc:`../gallery/animation/dynamic_image` From 85e0f3ca9bcd8f110a23a5fa4f14338e8742cea0 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Mon, 20 Nov 2023 16:29:31 +0100 Subject: [PATCH 0441/1120] Refactor AxisArtistHelpers --- lib/mpl_toolkits/axisartist/axislines.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index ced2703d1ed6..6f28e5c61a2f 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -84,9 +84,15 @@ def get_tick_iterators(self, axes): return iter_major, iter_minor """ + def __init__(self, nth_coord): + self.nth_coord = nth_coord + def update_lim(self, axes): pass + def get_nth_coord(self): + return self.nth_coord + def _to_xy(self, values, const): """ Create a (*values.shape, 2)-shape array representing (x, y) pairs. @@ -115,17 +121,13 @@ class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): @_api.delete_parameter("3.9", "nth_coord") def __init__(self, loc, nth_coord=None): """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" - self.nth_coord = _api.check_getitem( - {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc) + super().__init__(_api.check_getitem( + {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) self._loc = loc self._pos = {"bottom": 0, "top": 1, "left": 0, "right": 1}[loc] - super().__init__() # axis line in transAxes self._path = Path(self._to_xy((0, 1), const=self._pos)) - def get_nth_coord(self): - return self.nth_coord - # LINE def get_line(self, axes): @@ -158,12 +160,8 @@ def get_tick_transform(self, axes): class _FloatingAxisArtistHelperBase(_AxisArtistHelperBase): def __init__(self, nth_coord, value): - self.nth_coord = nth_coord self._value = value - super().__init__() - - def get_nth_coord(self): - return self.nth_coord + super().__init__(nth_coord) def get_line(self, axes): raise RuntimeError("get_line method should be defined by the derived class") From cca5161e538e38a78709123802d8fb004107f346 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Mon, 20 Nov 2023 20:13:18 -0700 Subject: [PATCH 0442/1120] Update 3d axis limits whats new --- doc/users/next_whats_new/3d_axis_limits.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/users/next_whats_new/3d_axis_limits.rst b/doc/users/next_whats_new/3d_axis_limits.rst index 7e7d6b2e5133..b460cfdb4f73 100644 --- a/doc/users/next_whats_new/3d_axis_limits.rst +++ b/doc/users/next_whats_new/3d_axis_limits.rst @@ -12,7 +12,9 @@ automatically added. import matplotlib.pyplot as plt fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) - plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0 - axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior') + plt.rcParams['axes3d.automargin'] = True - axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior') + axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior') + + plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0 + axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior') From e08cbf197fde11619405895ec1067d1b1caf88a1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 21 Nov 2023 14:57:21 -0500 Subject: [PATCH 0443/1120] Fix build on PyPy It appears that `Py_FileSystemDefaultEncodeErrors` is not part of the limited API, and PyPy does not have it. Since pybind11 does not have a wrapper for `PyUnicode_EncodeFSDefault` (the main reason I had switched to a `encode` call earlier), we need to call it ourselves manually. --- src/_tkagg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 567d7f88ba22..3b58114a71c0 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -309,8 +309,8 @@ load_tkinter_funcs() module = py::module_::import("_tkinter"); // CPython } auto py_path = module.attr("__file__"); - py::bytes py_path_b = py_path.attr("encode")( - Py_FileSystemDefaultEncoding, Py_FileSystemDefaultEncodeErrors); + auto py_path_b = py::reinterpret_steal( + PyUnicode_EncodeFSDefault(py_path.ptr())); std::string path = py_path_b; auto tkinter_lib = dlopen(path.c_str(), RTLD_LAZY); if (!tkinter_lib) { From 483fdb472620e39fb3ba8b4c0f2087aa0e7c5834 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 16 Nov 2023 13:23:17 -0500 Subject: [PATCH 0444/1120] frame stackplot limits in billions to make numbers clearer Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../lines_bars_and_markers/stackplot_demo.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/stackplot_demo.py b/galleries/examples/lines_bars_and_markers/stackplot_demo.py index 890a827fa156..d02a9af73da3 100644 --- a/galleries/examples/lines_bars_and_markers/stackplot_demo.py +++ b/galleries/examples/lines_bars_and_markers/stackplot_demo.py @@ -16,15 +16,17 @@ import matplotlib.pyplot as plt import numpy as np +import matplotlib.ticker as mticker + # data from United Nations World Population Prospects (Revision 2019) # https://population.un.org/wpp/, license: CC BY 3.0 IGO year = [1950, 1960, 1970, 1980, 1990, 2000, 2010, 2018] population_by_continent = { - 'africa': [228, 284, 365, 477, 631, 814, 1044, 1275], - 'americas': [340, 425, 519, 619, 727, 840, 943, 1006], - 'asia': [1394, 1686, 2120, 2625, 3202, 3714, 4169, 4560], - 'europe': [220, 253, 276, 295, 310, 303, 294, 293], - 'oceania': [12, 15, 19, 22, 26, 31, 36, 39], + 'Africa': [.228, .284, .365, .477, .631, .814, 1.044, 1.275], + 'the Americas': [.340, .425, .519, .619, .727, .840, .943, 1.006], + 'Asia': [1.394, 1.686, 2.120, 2.625, 3.202, 3.714, 4.169, 4.560], + 'Europe': [.220, .253, .276, .295, .310, .303, .294, .293], + 'Oceania': [.012, .015, .019, .022, .026, .031, .036, .039], } fig, ax = plt.subplots() @@ -33,7 +35,9 @@ ax.legend(loc='upper left', reverse=True) ax.set_title('World population') ax.set_xlabel('Year') -ax.set_ylabel('Number of people (millions)') +ax.set_ylabel('Number of people (billions)') +# add tick at every 200 million people +ax.yaxis.set_minor_locator(mticker.MultipleLocator(.2)) plt.show() From 78791894c96e6892c1d4413bade9fc655a38492f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 22 Nov 2023 14:40:20 +0100 Subject: [PATCH 0445/1120] Fix removal of colorbars on nested subgridspecs. Indiscriminately going back up to the topmost gridspec is wrong in the presence of nested gridspecs. We may want to add an API to just go up one level in gridspecs (i.e. public access to _subplot_spec) but I'd like to first consider whether we could e.g. merge GridSpecFromSubplotSpec into GridSpec with the only difference that the former's get_parent() (or another similarly named new method) returns another gridspec whereas the latter's returns a figure. --- lib/matplotlib/colorbar.py | 9 +++------ lib/matplotlib/tests/test_colorbar.py | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index b54211654d13..920c0d67722a 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1035,14 +1035,11 @@ def remove(self): except AttributeError: return try: - gs = ax.get_subplotspec().get_gridspec() - subplotspec = gs.get_topmost_subplotspec() - except AttributeError: - # use_gridspec was False + subplotspec = self.ax.get_subplotspec().get_gridspec()._subplot_spec + except AttributeError: # use_gridspec was False pos = ax.get_position(original=True) ax._set_position(pos) - else: - # use_gridspec was True + else: # use_gridspec was True ax.set_subplotspec(subplotspec) def _process_values(self): diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 0cf098e787ee..509d08dae183 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -279,13 +279,16 @@ def test_colorbar_single_scatter(): plt.colorbar(cs) -@pytest.mark.parametrize('use_gridspec', [False, True], - ids=['no gridspec', 'with gridspec']) -def test_remove_from_figure(use_gridspec): - """ - Test `remove` with the specified ``use_gridspec`` setting - """ - fig, ax = plt.subplots() +@pytest.mark.parametrize('use_gridspec', [True, False]) +@pytest.mark.parametrize('nested_gridspecs', [True, False]) +def test_remove_from_figure(nested_gridspecs, use_gridspec): + """Test `remove` with the specified ``use_gridspec`` setting.""" + fig = plt.figure() + if nested_gridspecs: + gs = fig.add_gridspec(2, 2)[1, 1].subgridspec(2, 2) + ax = fig.add_subplot(gs[1, 1]) + else: + ax = fig.add_subplot() sc = ax.scatter([1, 2], [3, 4]) sc.set_array(np.array([5, 6])) pre_position = ax.get_position() @@ -298,9 +301,7 @@ def test_remove_from_figure(use_gridspec): def test_remove_from_figure_cl(): - """ - Test `remove` with constrained_layout - """ + """Test `remove` with constrained_layout.""" fig, ax = plt.subplots(constrained_layout=True) sc = ax.scatter([1, 2], [3, 4]) sc.set_array(np.array([5, 6])) From d9f9abee868b2c126e755d300c56432e7d4450b0 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 22 Nov 2023 23:02:10 +0100 Subject: [PATCH 0446/1120] Implement SubFigure.remove. --- lib/matplotlib/figure.py | 1 + lib/matplotlib/tests/test_figure.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index cd26af84ccbe..11b42b1e1ac7 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1577,6 +1577,7 @@ def add_subfigure(self, subplotspec, **kwargs): """ sf = SubFigure(self, subplotspec, **kwargs) self.subfigs += [sf] + sf._remove_method = self.subfigs.remove return sf def sca(self, a): diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 6d6a3d772f4e..24d4f1c0f059 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1482,6 +1482,13 @@ def test_subfigures_wspace_hspace(): np.testing.assert_allclose(sub_figs[1, 2].bbox.max, [w, h * 0.4]) +def test_subfigure_remove(): + fig = plt.figure() + sfs = fig.subfigures(2, 2) + sfs[1, 1].remove() + assert len(fig.subfigs) == 3 + + def test_add_subplot_kwargs(): # fig.add_subplot() always creates new axes, even if axes kwargs differ. fig = plt.figure() From 6a7cd0edb91e92645bed258b62fe7fe53ba32cfb Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Thu, 23 Nov 2023 13:19:09 +0100 Subject: [PATCH 0447/1120] [DOC]: Fix menu example Make example independent on dpi setting when saving the output with different resolutions to prevent shifting of texts and rects. --- galleries/examples/widgets/menu.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/galleries/examples/widgets/menu.py b/galleries/examples/widgets/menu.py index b8f5268a6477..8d3db3d1b9c3 100644 --- a/galleries/examples/widgets/menu.py +++ b/galleries/examples/widgets/menu.py @@ -3,13 +3,12 @@ Menu ==== +Using texts to construct a simple menu. """ - import matplotlib.pyplot as plt import matplotlib.artist as artist import matplotlib.patches as patches -from matplotlib.transforms import IdentityTransform class ItemProperties: @@ -22,8 +21,8 @@ def __init__(self, fontsize=14, labelcolor='black', bgcolor='yellow', class MenuItem(artist.Artist): - padx = 5 - pady = 5 + padx = 0.05 # inches + pady = 0.05 def __init__(self, fig, labelstr, props=None, hoverprops=None, on_select=None): @@ -41,14 +40,16 @@ def __init__(self, fig, labelstr, props=None, hoverprops=None, self.on_select = on_select - # Setting the transform to IdentityTransform() lets us specify - # coordinates directly in pixels. - self.label = fig.text(0, 0, labelstr, transform=IdentityTransform(), + # specify coordinates in inches. + self.label = fig.text(0, 0, labelstr, transform=fig.dpi_scale_trans, size=props.fontsize) self.text_bbox = self.label.get_window_extent( fig.canvas.get_renderer()) + self.text_bbox = fig.dpi_scale_trans.inverted().transform_bbox(self.text_bbox) - self.rect = patches.Rectangle((0, 0), 1, 1) # Will be updated later. + self.rect = patches.Rectangle( + (0, 0), 1, 1, transform=fig.dpi_scale_trans + ) # Will be updated later. self.set_hover_props(False) @@ -63,7 +64,7 @@ def check_select(self, event): def set_extent(self, x, y, w, h, depth): self.rect.set(x=x, y=y, width=w, height=h) - self.label.set(position=(x + self.padx, y + depth + self.pady/2)) + self.label.set(position=(x + self.padx, y + depth + self.pady / 2)) self.hover = False def draw(self, renderer): @@ -97,10 +98,10 @@ def __init__(self, fig, menuitems): maxh = max(item.text_bbox.height for item in menuitems) depth = max(-item.text_bbox.y0 for item in menuitems) - x0 = 100 - y0 = 400 + x0 = 1 + y0 = 4 - width = maxw + 2*MenuItem.padx + width = maxw + 2 * MenuItem.padx height = maxh + MenuItem.pady for item in menuitems: From 43e0692178fca4a30b380ac5fb4c7ae141e698c0 Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Thu, 23 Nov 2023 22:57:31 +0100 Subject: [PATCH 0448/1120] Fix _find_fonts_by_props docstring to match actual return value (list instead of dict). --- lib/matplotlib/font_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 68b22ccf7df1..9996c59ea1d8 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1339,10 +1339,10 @@ def _find_fonts_by_props(self, prop, fontext='ttf', directory=None, ----- This is an extension/wrapper of the original findfont API, which only returns a single font for given font properties. Instead, this API - returns a dict containing multiple fonts and their filepaths - which closely match the given font properties. Since this internally - uses the original API, there's no change to the logic of performing the - nearest neighbor search. See `findfont` for more details. + returns a list of filepaths of multiple fonts which closely match the + given font properties. Since this internally uses the original API, + there's no change to the logic of performing the nearest neighbor + search. See `findfont` for more details. """ prop = FontProperties._from_any(prop) From b1a7e7be88899f668bae0a100a0e6d1e4be67590 Mon Sep 17 00:00:00 2001 From: tobias Date: Sun, 26 Nov 2023 20:36:56 +0100 Subject: [PATCH 0449/1120] Fix get_path for 3d artists This is a slightly adapted version of commit 1868c05e622ad521efd5671e47eef6bd35e0b8c9 --- lib/mpl_toolkits/mplot3d/art3d.py | 5 +++++ lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 4aff115b0c96..ea43549c33fa 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -448,6 +448,11 @@ def set_3d_properties(self, verts, zs=0, zdir='z'): for ((x, y), z) in zip(verts, zs)] def get_path(self): + # docstring inherited + # self._path2d is not initialized until do_3d_projection + if not hasattr(self, '_path2d'): + self.axes.M = self.axes.get_proj() + self.do_3d_projection() return self._path2d def do_3d_projection(self): diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 4024eeddcf17..1c6bbdd91670 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2112,6 +2112,16 @@ def test_scatter_spiral(): fig.canvas.draw() +def test_Poly3DCollection_get_path(): + # Smoke test to see that get_path does not raise + # See GH#27361 + fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) + p = Circle((0, 0), 1.0) + ax.add_patch(p) + art3d.pathpatch_2d_to_3d(p) + p.get_path() + + def test_Poly3DCollection_get_facecolor(): # Smoke test to see that get_facecolor does not raise # See GH#4067 From 40d01466b3b526fe9915a1fd1badc2b9b536552e Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Mon, 27 Nov 2023 11:25:40 +0100 Subject: [PATCH 0450/1120] MNT: add _version.py to .gitignore _version.py is generated by setuptools_scm and should not be tracked in version control --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 471431dd3433..99e7a628cffd 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ pip-wheel-metadata/* # build subproject files subprojects/*/ !subprojects/packagefiles/ +# file generated by setuptools_scm +_version.py # OS generated files # ###################### From 7ff0760b49fd8912ea3eb11703e0df0a92a2ff6f Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Mon, 27 Nov 2023 13:31:09 +0100 Subject: [PATCH 0451/1120] TST: Make test_movie_writer_invalid_path locale-agnostic Filtering on the error message failed on locales different from English (just matching the error in brackets would also do). --- lib/matplotlib/tests/test_animation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index a4de96d77b62..d026dae59533 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -545,9 +545,9 @@ def test_disable_cache_warning(anim): def test_movie_writer_invalid_path(anim): if sys.platform == "win32": - match_str = re.escape("[WinError 3] The system cannot find the path specified:") + match_str = r"\[WinError 3] .*'\\\\foo\\\\bar\\\\aardvark'" else: - match_str = re.escape("[Errno 2] No such file or directory: '/foo") + match_str = r"\[Errno 2] .*'/foo" with pytest.raises(FileNotFoundError, match=match_str): anim.save("/foo/bar/aardvark/thiscannotreallyexist.mp4", writer=animation.FFMpegFileWriter()) From e2c9cd4ac69d86d496ca4f171c3b6d57c2781e09 Mon Sep 17 00:00:00 2001 From: Samuel Diebolt Date: Mon, 27 Nov 2023 17:22:21 +0100 Subject: [PATCH 0452/1120] [MNT] fix type annotations of `fignum_exists` (#27376) * fix: fix type annotations of `fignum_exists` * docs: expand documentation of `fignum_exists` * fix(docs): fix documentation of `fignum_exists` Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --------- Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- lib/matplotlib/pyplot.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ddbb6cb36728..1255818b41b5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -999,9 +999,24 @@ def gcf() -> Figure: return figure() -def fignum_exists(num: int) -> bool: - """Return whether the figure with the given id exists.""" - return _pylab_helpers.Gcf.has_fignum(num) or num in get_figlabels() +def fignum_exists(num: int | str) -> bool: + """Return whether the figure with the given id exists. + + Parameters + ---------- + num : int or str + A figure identifier. + + Returns + ------- + bool + Whether or not a figure with id *num* exists. + """ + return ( + _pylab_helpers.Gcf.has_fignum(num) + if isinstance(num, int) + else num in get_figlabels() + ) def get_fignums() -> list[int]: From 8b3eb71394985b471e733271f6a61880da55f772 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 22 Nov 2023 03:09:24 -0500 Subject: [PATCH 0453/1120] ci: Remove old condition This condition was when we had Ubuntu 18.04 and added 20.04. When we dropped 18.04, and moved to 20.04+22.04, this condition should've been enabled everywhere. --- .github/workflows/tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 614f9235a5a4..3826c7d5c412 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -286,11 +286,7 @@ jobs: # Set flag in a delayed manner to avoid issues with installing other # packages if [[ "${{ runner.os }}" != 'macOS' ]]; then - if [[ "$(lsb_release -r -s)" == "20.04" ]]; then - export CPPFLAGS='--coverage -fprofile-abs-path' - else - export CPPFLAGS='--coverage' - fi + export CPPFLAGS='--coverage -fprofile-abs-path' fi python -m pip install --no-deps --no-build-isolation --verbose \ From 05e7644a86992431e0d0bc50d80079eda1a3bf9e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 22 Nov 2023 04:00:28 -0500 Subject: [PATCH 0454/1120] ci: Get coverage working for compiled macOS code --- .github/workflows/tests.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3826c7d5c412..33260e6fc2a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -285,7 +285,10 @@ jobs: # Set flag in a delayed manner to avoid issues with installing other # packages - if [[ "${{ runner.os }}" != 'macOS' ]]; then + if [[ "${{ runner.os }}" == 'macOS' ]]; then + export CPPFLAGS='-fprofile-instr-generate=default.%m.profraw' + export CPPFLAGS="$CPPFLAGS -fcoverage-mapping" + else export CPPFLAGS='--coverage -fprofile-abs-path' fi @@ -310,12 +313,19 @@ jobs: - name: Filter C coverage run: | - lcov --rc lcov_branch_coverage=1 --capture --directory . --output-file coverage.info - lcov --rc lcov_branch_coverage=1 --output-file coverage.info \ - --extract coverage.info $PWD/src/'*' $PWD/lib/'*' - lcov --rc lcov_branch_coverage=1 --list coverage.info - find . -name '*.gc*' -delete - if: ${{ runner.os != 'macOS' }} + if [[ "${{ runner.os }}" != 'macOS' ]]; then + lcov --rc lcov_branch_coverage=1 --capture --directory . \ + --output-file coverage.info + lcov --rc lcov_branch_coverage=1 --output-file coverage.info \ + --extract coverage.info $PWD/src/'*' $PWD/lib/'*' + lcov --rc lcov_branch_coverage=1 --list coverage.info + find . -name '*.gc*' -delete + else + xcrun llvm-profdata merge -sparse default.*.profraw \ + -o default.profdata + xcrun llvm-cov export -format="lcov" build/*/src/*.so \ + -instr-profile default.profdata > info.lcov + fi - name: Upload code coverage uses: codecov/codecov-action@v3 From c41d5d81487ace3237b6a18b56338781cd505007 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 22 Nov 2023 21:35:18 -0500 Subject: [PATCH 0455/1120] ci: Add names to each codecov build Codecov shows the CI provider, but all jobs are tagged with the overall workflow ID. Passing a name allows differentiating between each build (i.e., Python version and system platform.) --- .appveyor.yml | 2 +- .github/workflows/tests.yml | 2 ++ azure-pipelines.yml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 6e71e94e58f5..ac9608ec56ea 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -94,7 +94,7 @@ artifacts: on_finish: - conda install codecov - - codecov -e PYTHON_VERSION PLATFORM + - codecov -e PYTHON_VERSION PLATFORM -n "$PYTHON_VERSION Windows" on_failure: # Generate a html for visual tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 33260e6fc2a9..0f9786430b35 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -328,6 +328,8 @@ jobs: fi - name: Upload code coverage uses: codecov/codecov-action@v3 + with: + name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}" - uses: actions/upload-artifact@v3 if: failure() diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8eb5cacfb55a..3a1006e1b273 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -157,7 +157,7 @@ stages: displayName: 'pytest' - bash: | - bash <(curl -s https://codecov.io/bash) -f "!*.gcov" -X gcov + bash <(curl -s https://codecov.io/bash) -n "$PYTHON_VERSION $AGENT_OS" -f "!*.gcov" -X gcov displayName: 'Upload to codecov.io' - task: PublishTestResults@2 From dc759a157e59d58585e9d5a95eb299aee25d2cb5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 22 Nov 2023 22:51:07 -0500 Subject: [PATCH 0456/1120] ci: Enable compiled code coverage on Azure --- azure-pipelines.yml | 130 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3a1006e1b273..44adc6f08422 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,8 +88,8 @@ stages: - bash: | set -e - case "$(python -c 'import sys; print(sys.platform)')" in - linux) + case "$AGENT_OS" in + Linux) echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries sudo apt update sudo apt install --no-install-recommends \ @@ -103,6 +103,7 @@ stages: gir1.2-gtk-3.0 \ graphviz \ inkscape \ + lcov \ libcairo2 \ libgirepository-1.0-1 \ lmodern \ @@ -116,13 +117,13 @@ stages: texlive-pictures \ texlive-xetex ;; - darwin) + Darwin) brew install --cask xquartz brew install ccache ffmpeg imagemagick mplayer ninja pkg-config brew tap homebrew/cask-fonts brew install font-noto-sans-cjk-sc ;; - win32) + Windows_NT) choco install ninja ;; *) @@ -139,8 +140,26 @@ stages: displayName: 'Install dependencies with pip' - bash: | + case "$AGENT_OS" in + Linux) + export CPPFLAGS='--coverage -fprofile-abs-path' + ;; + Darwin) + export CPPFLAGS='-fprofile-instr-generate=default.%m.profraw' + export CPPFLAGS="$CPPFLAGS -fcoverage-mapping" + ;; + Windows_NT) + CONFIG='--config-settings=setup-args=--vsenv' + CONFIG="$CONFIG --config-settings=setup-args=-Dcpp_link_args=-PROFILE" + CONFIG="$CONFIG --config-settings=setup-args=-Dbuildtype=debug" + ;; + *) + exit 1 + ;; + esac + python -m pip install \ - --no-build-isolation --config-settings=setup-args="--vsenv" \ + --no-build-isolation $CONFIG \ --verbose --editable .[dev] || [[ "$PYTHON_VERSION" = 'Pre' ]] displayName: "Install self" @@ -152,12 +171,109 @@ stages: displayName: 'print pip' - bash: | - PYTHONFAULTHANDLER=1 python -m pytest --junitxml=junit/test-results.xml -raR --maxfail=50 --timeout=300 --durations=25 --cov-report= --cov=lib -n 2 || + set -e + if [[ "$AGENT_OS" == 'Windows_NT' ]]; then + SESSION_ID=$(python -c "import uuid; print(uuid.uuid4(), end='')") + echo "Coverage session ID: ${SESSION_ID}" + VS=$(ls -d /c/Program\ Files*/Microsoft\ Visual\ Studio/*/Enterprise) + echo "Visual Studio: ${VS}" + DIR="$VS/Common7/IDE/Extensions/Microsoft/CodeCoverage.Console" + if [[ -d $DIR ]]; then + # This is for MSVC 2022 (on windows-latest). + TOOL="$DIR/Microsoft.CodeCoverage.Console.exe" + for f in build/cp*/src/*.pyd; do + echo $f + echo "==============================" + "$TOOL" instrument $f --session-id $SESSION_ID \ + --log-level Verbose --log-file instrument.log + cat instrument.log + rm instrument.log + done + echo "Starting $TOOL in server mode" + "$TOOL" collect \ + --session-id $SESSION_ID --server-mode \ + --output-format cobertura --output extensions.xml \ + --log-level Verbose --log-file extensions.log & + VS_VER=2022 + else + DIR="$VS"/Team\ Tools/Dynamic\ Code\ Coverage\ Tools/amd64 + if [[ -d $DIR ]]; then + # This is for MSVC 2019 (on windows-2019). + VSINSTR="$VS"/Team\ Tools/Performance\ Tools/vsinstr.exe + for f in build/cp*/src/*.pyd; do + "$VSINSTR" $f -Verbose -Coverage + done + TOOL="$DIR/CodeCoverage.exe" + cat > extensions.config << EOF + + true + + + .*\\.*\.pyd + + + + EOF + echo "Starting $TOOL in server mode" + "$TOOL" collect \ + -config:extensions.config -session:$SESSION_ID \ + -output:extensions.coverage -verbose & + echo "Started $TOOL" + VS_VER=2019 + fi + fi + echo "##vso[task.setvariable variable=VS_COVERAGE_TOOL]$TOOL" + fi + PYTHONFAULTHANDLER=1 python -m pytest -raR -n 2 \ + --maxfail=50 --timeout=300 --durations=25 \ + --junitxml=junit/test-results.xml --cov-report=xml --cov=lib || [[ "$PYTHON_VERSION" = 'Pre' ]] + if [[ -n $SESSION_ID ]]; then + if [[ $VS_VER == 2022 ]]; then + "$TOOL" shutdown $SESSION_ID + echo "Coverage collection log" + echo "=======================" + cat extensions.log + else + "$TOOL" shutdown -session:$SESSION_ID + fi + fi displayName: 'pytest' - bash: | - bash <(curl -s https://codecov.io/bash) -n "$PYTHON_VERSION $AGENT_OS" -f "!*.gcov" -X gcov + case "$AGENT_OS" in + Linux) + lcov --rc lcov_branch_coverage=1 --capture --directory . \ + --output-file coverage.info + lcov --rc lcov_branch_coverage=1 --output-file coverage.info \ + --extract coverage.info $PWD/src/'*' $PWD/lib/'*' + lcov --rc lcov_branch_coverage=1 --list coverage.info + find . -name '*.gc*' -delete + ;; + Darwin) + xcrun llvm-profdata merge -sparse default.*.profraw \ + -o default.profdata + xcrun llvm-cov export -format="lcov" build/*/src/*.so \ + -instr-profile default.profdata > info.lcov + ;; + Windows_NT) + if [[ -f extensions.coverage ]]; then + # For MSVC 2019. + "$VS_COVERAGE_TOOL" analyze -output:extensions.xml \ + -include_skipped_functions -include_skipped_modules \ + extensions.coverage + rm extensions.coverage + fi + ;; + *) + exit 1 + ;; + esac + displayName: 'Filter C coverage' + - bash: | + bash <(curl -s https://codecov.io/bash) \ + -n "$PYTHON_VERSION $AGENT_OS" \ + -f 'coverage.xml' -f 'extensions.xml' displayName: 'Upload to codecov.io' - task: PublishTestResults@2 From b06232512d0aabee11bec6dc3115ea0a6b1be493 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 28 Nov 2023 00:01:45 -0500 Subject: [PATCH 0457/1120] added version guarded admonishment --- doc/devel/index.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 95572aa661cf..5bb1d240611e 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -4,6 +4,15 @@ Contribute ########## +.. ifconfig:: releaselevel != 'dev' + + .. important:: + + If you plan to contribute to Matplotlib, please read the + `development version `_ + of this document as it will have the most up to date installation + instructions, workflow process, and contributing guidelines. + Thank you for your interest in helping to improve Matplotlib! There are various ways to contribute: optimizing and refactoring code, detailing unclear documentation and writing new examples, reporting and fixing bugs and requesting From ee37c24d28bff3870ff90d79ebebe6f1d3c01c98 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:17:59 +0000 Subject: [PATCH 0458/1120] Revert "MNT: add _version.py to .gitignore" --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 99e7a628cffd..471431dd3433 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,6 @@ pip-wheel-metadata/* # build subproject files subprojects/*/ !subprojects/packagefiles/ -# file generated by setuptools_scm -_version.py # OS generated files # ###################### From 047ad3b724df167c3e3bfdcfadc1d45059207b26 Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Wed, 29 Nov 2023 16:45:37 +0100 Subject: [PATCH 0459/1120] MNT: Fix doc makefiles - match make.bat with Makefile - clean sg_execution_times.rst and git ignore it (see sphinx gallery #1198) --- .gitignore | 1 + doc/Makefile | 1 + doc/make.bat | 3 +++ 3 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 471431dd3433..41c97b62c51e 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ result_images doc/_static/constrained_layout*.png doc/.mpl_skip_subdirs.yaml doc/_tags +sg_execution_times.rst # Nose/Pytest generated files # ############################### diff --git a/doc/Makefile b/doc/Makefile index 53b7c02b68fa..7eda39d87624 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -26,6 +26,7 @@ clean: rm -rf "$(SOURCEDIR)/savefig" rm -rf "$(SOURCEDIR)/sphinxext/__pycache__" rm -f $(SOURCEDIR)/_static/constrained_layout*.png + rm -f $(SOURCEDIR)/sg_execution_times.rst show: @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/build/html/index.html')" diff --git a/doc/make.bat b/doc/make.bat index 220523d74b60..37c74eb5079a 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -40,8 +40,11 @@ if "%1" == "clean" ( rmdir /s /q "%SOURCEDIR%\gallery" rmdir /s /q "%SOURCEDIR%\plot_types" rmdir /s /q "%SOURCEDIR%\tutorials" + rmdir /s /q "%SOURCEDIR%\users\explain" rmdir /s /q "%SOURCEDIR%\savefig" rmdir /s /q "%SOURCEDIR%\sphinxext\__pycache__" + del /q "%SOURCEDIR%\_static\constrained_layout*.png" + del /q "%SOURCEDIR%\sg_execution_times.rst" ) goto end From 631240929f08fd39fdb5cb28926a9e4bc61981e3 Mon Sep 17 00:00:00 2001 From: Joshua Stevenson Date: Wed, 29 Nov 2023 15:27:28 -0500 Subject: [PATCH 0460/1120] SpanSelector widget: Improve doc for `extents` --- lib/matplotlib/widgets.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index cd9716408303..354e18728447 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2695,7 +2695,7 @@ def _press(self, event): @property def direction(self): - """Direction of the span selector: 'vertical' or 'horizontal'.""" + """Direction of the span selector: 'vertical' or 'horizontal'. Writable.""" return self._direction @direction.setter @@ -2855,7 +2855,12 @@ def _snap(values, snap_values): @property def extents(self): - """Return extents of the span selector.""" + """ + (float, float) + The axis values for the start and end points of the current selection. + If there is no selection then the start and end values will be the same. + Writable. + """ if self.direction == 'horizontal': vmin = self._selection_artist.get_x() vmax = vmin + self._selection_artist.get_width() From 71a7abe70d99005bc2d3985b7ea51c1dbbde4066 Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 29 Nov 2023 13:56:29 -0500 Subject: [PATCH 0461/1120] Moved dependencies under install because its version dependent and cleaned up the headings a bit --- doc/devel/development_setup.rst | 12 +++++- doc/devel/index.rst | 6 --- .../installing}/dependencies.rst | 4 +- doc/users/installing/index.rst | 43 +++++++++++++------ 4 files changed, 44 insertions(+), 21 deletions(-) rename doc/{devel => users/installing}/dependencies.rst (99%) diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 745a74dcf8bb..6e24c085829d 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -155,9 +155,16 @@ Remember to activate the environment whenever you start working on Matplotlib. Install Dependencies ==================== + Most Python dependencies will be installed when :ref:`setting up the environment ` but non-Python dependencies like C++ compilers, LaTeX, and other system applications -must be installed separately. For a full list, see :ref:`dependencies`. +must be installed separately. + +.. toctree:: + :maxdepth: 2 + + ../users/installing/dependencies + .. _development-install: @@ -195,6 +202,9 @@ config during installation :: For more information on installation and other configuration options, see the Meson Python :external+meson-python:ref:`editable installs guide `. +For a list of the other environment variables you can set before install, see :ref:`environment-variables`. + + Verify the Installation ======================= diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 95572aa661cf..3e1148d25df5 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -88,12 +88,6 @@ Development environment development_setup - .. toctree:: - :maxdepth: 1 - - dependencies - ../users/installing/environment_variables_faq.rst - .. grid-item-card:: :shadow: none diff --git a/doc/devel/dependencies.rst b/doc/users/installing/dependencies.rst similarity index 99% rename from doc/devel/dependencies.rst rename to doc/users/installing/dependencies.rst index c0e761d6beb6..ce83454db08a 100644 --- a/doc/devel/dependencies.rst +++ b/doc/users/installing/dependencies.rst @@ -1,3 +1,5 @@ +.. redirect-from: /devel/dependencies + .. _dependencies: ************ @@ -387,7 +389,7 @@ The additional Python packages required to build the The content of :file:`doc-requirements.txt` is also shown below: -.. include:: ../../requirements/doc/doc-requirements.txt +.. include:: ../../../requirements/doc/doc-requirements.txt :literal: diff --git a/doc/users/installing/index.rst b/doc/users/installing/index.rst index 0d25ffbf76fa..bea9a47b582d 100644 --- a/doc/users/installing/index.rst +++ b/doc/users/installing/index.rst @@ -1,8 +1,8 @@ .. redirect-from:: /users/installing -============ +************ Installation -============ +************ Install an official release @@ -135,25 +135,42 @@ Configure build and behavior defaults ===================================== Aspects of the build and install process and some behaviorial defaults of the -library can be configured via :ref:`environment-variables`. Default plotting -appearance and behavior can be configured via the +library can be configured via: + +.. toctree:: + :maxdepth: 2 + + environment_variables_faq.rst + +Default plotting appearance and behavior can be configured via the :ref:`rcParams file ` +Dependencies +============ + +Mandatory dependencies should be installed automatically if you install Matplotlib using +a package manager such as ``pip`` or ``conda``; therefore this list is primarily for +reference and troubleshooting. + +.. toctree:: + :maxdepth: 2 + + dependencies + .. _installing-faq: -========================== Frequently asked questions -========================== +=========================== Report a compilation problem -============================ +---------------------------- See :ref:`reporting-problems`. Matplotlib compiled fine, but nothing shows up when I use it -============================================================ +------------------------------------------------------------ The first thing to try is a :ref:`clean install ` and see if that helps. If not, the best way to test your install is by running a script, @@ -175,7 +192,7 @@ If you are still having trouble, see :ref:`reporting-problems`. .. _clean-install: How to completely remove Matplotlib -=================================== +----------------------------------- Occasionally, problems with Matplotlib can be solved with a clean installation of the package. In order to fully remove an installed Matplotlib: @@ -187,12 +204,12 @@ installation of the package. In order to fully remove an installed Matplotlib: directory `. OSX Notes -========= +--------- .. _which-python-for-osx: Which python for OSX? ---------------------- +^^^^^^^^^^^^^^^^^^^^^ Apple ships OSX with its own Python, in ``/usr/bin/python``, and its own copy of Matplotlib. Unfortunately, the way Apple currently installs its own copies @@ -222,7 +239,7 @@ or Python.org Python. .. _install_osx_binaries: Installing OSX binary wheels ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you are using Python from https://www.python.org, Homebrew, or Macports, then you can use the standard pip installer to install Matplotlib binaries in @@ -242,7 +259,7 @@ You might also want to install IPython or the Jupyter notebook (``python3 -m pip install ipython notebook``). Checking your installation --------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^ The new version of Matplotlib should now be on your Python "path". Check this at the Terminal.app command line:: From a33125091cd08760e0f5a25801ca430a12b344a6 Mon Sep 17 00:00:00 2001 From: judfs <39133604+judfs@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:37:47 -0500 Subject: [PATCH 0462/1120] Update lib/matplotlib/widgets.py Co-authored-by: hannah --- lib/matplotlib/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 354e18728447..717d1be83050 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2857,7 +2857,7 @@ def _snap(values, snap_values): def extents(self): """ (float, float) - The axis values for the start and end points of the current selection. + The values, in data coordinates, for the start and end points of the current selection. If there is no selection then the start and end values will be the same. Writable. """ From 24842ec53f443e053723c79d2897f41dc4da84b4 Mon Sep 17 00:00:00 2001 From: ElisaHeck <62849498+ElisaHeck@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:43:03 +0100 Subject: [PATCH 0463/1120] Add smoke test for Axes.barbs in test_datetime.py (#27331) * added smoke test for Axes.barbs in test_datetime.py * changed to use deterministic data --- lib/matplotlib/tests/test_datetime.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 316be793e47c..a3be7e2840a8 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -146,11 +146,29 @@ def test_bar_label(self): fig, ax = plt.subplots() ax.bar_label(...) - @pytest.mark.xfail(reason="Test for barbs not written yet") @mpl.style.context("default") def test_barbs(self): - fig, ax = plt.subplots() - ax.barbs(...) + plt.rcParams["date.converter"] = 'concise' + + start_date = datetime.datetime(2022, 2, 8, 22) + dates = [start_date + datetime.timedelta(hours=i) for i in range(12)] + + numbers = np.sin(np.linspace(0, 2 * np.pi, 12)) + + u = np.ones(12) * 10 + v = np.arange(0, 120, 10) + + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 6)) + + axes[0].barbs(dates, numbers, u, v, length=7) + axes[0].set_title('Datetime vs. Numeric Data') + axes[0].set_xlabel('Datetime') + axes[0].set_ylabel('Numeric Data') + + axes[1].barbs(numbers, dates, u, v, length=7) + axes[1].set_title('Numeric vs. Datetime Data') + axes[1].set_xlabel('Numeric Data') + axes[1].set_ylabel('Datetime') @mpl.style.context("default") def test_barh(self): From c311ec5fd4a5397000d7fb3262fe29c9a2f63f97 Mon Sep 17 00:00:00 2001 From: Joshua Stevenson Date: Thu, 30 Nov 2023 10:00:32 -0500 Subject: [PATCH 0464/1120] Fix formatting --- lib/matplotlib/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 717d1be83050..ba91d96a2e83 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2857,9 +2857,9 @@ def _snap(values, snap_values): def extents(self): """ (float, float) - The values, in data coordinates, for the start and end points of the current selection. - If there is no selection then the start and end values will be the same. - Writable. + The values, in data coordinates, for the start and end points of the current + selection. If there is no selection then the start and end values will be + the same. Writable. """ if self.direction == 'horizontal': vmin = self._selection_artist.get_x() From 01176429412373d67b67e942dd7eaf457b0e9bc9 Mon Sep 17 00:00:00 2001 From: rawwash <112187975+rawwash@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:35:35 -0500 Subject: [PATCH 0465/1120] Remove test_spy from test_datetime.py (#27392) * added test case for spy plot * adding datetime functionality * removing plt.show() * removing spy test --- lib/matplotlib/tests/test_datetime.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index a3be7e2840a8..83f1aee4f8bb 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -510,12 +510,6 @@ def test_semilogy(self): fig, ax = plt.subplots() ax.semilogy(...) - @pytest.mark.xfail(reason="Test for spy not written yet") - @mpl.style.context("default") - def test_spy(self): - fig, ax = plt.subplots() - ax.spy(...) - @mpl.style.context("default") def test_stackplot(self): mpl.rcParams["date.converter"] = 'concise' From 3c8b337a9bb2e4a5acdd1b210ba38aaf1b67855a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 30 Nov 2023 23:32:13 -0500 Subject: [PATCH 0466/1120] ci: Block PyQt6 6.6.0 on Ubuntu This seems to be a broken wheel release from upstream: https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html --- .github/workflows/tests.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 614f9235a5a4..7f2e3266cb19 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,23 +61,30 @@ jobs: extra-requirements: '-r requirements/testing/extra.txt' CFLAGS: "-fno-lto" # Ensure that disabling LTO works. # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 - pyqt6-ver: '!=6.5.1' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.5.1,!=6.6.0' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: ubuntu-20.04 python-version: '3.10' extra-requirements: '-r requirements/testing/extra.txt' # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 - pyqt6-ver: '!=6.5.1' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.5.1,!=6.6.0' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: ubuntu-22.04 python-version: '3.11' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.6.0' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' extra-requirements: '-r requirements/testing/extra.txt' - os: ubuntu-22.04 python-version: '3.12' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.6.0' + # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: macos-latest python-version: 3.9 From ab67ee18426aaa48a189e32ff08ae539ca472a9e Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 30 Nov 2023 12:27:48 +0100 Subject: [PATCH 0467/1120] Check that xerr/yerr values are not None in errorbar --- lib/matplotlib/axes/_axes.py | 5 +++++ lib/matplotlib/tests/test_axes.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2136ecb2eb94..90faa2ffab18 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3682,6 +3682,11 @@ def apply_mask(arrays, mask): f"'{dep_axis}err' (shape: {np.shape(err)}) must be a " f"scalar or a 1D or (2, n) array-like whose shape matches " f"'{dep_axis}' (shape: {np.shape(dep)})") from None + if err.dtype is np.dtype(object) and np.any(err == None): # noqa: E711 + raise ValueError( + f"'{dep_axis}err' must not contain None. " + "Use NaN if you want to skip a value.") + res = np.zeros(err.shape, dtype=bool) # Default in case of nan if np.any(np.less(err, -err, out=res, where=(err == err))): # like err<0, but also works for timedelta and nan. diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index dffbb2377a23..d5424f80c9e9 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4127,6 +4127,20 @@ def test_xerr_yerr_not_negative(): yerr=datetime.timedelta(days=-10)) +def test_xerr_yerr_not_none(): + ax = plt.figure().subplots() + + with pytest.raises(ValueError, + match="'xerr' must not contain None"): + ax.errorbar(x=[0], y=[0], xerr=[[None], [1]], yerr=[[None], [1]]) + with pytest.raises(ValueError, + match="'xerr' must not contain None"): + ax.errorbar(x=[0], y=[0], xerr=[[None], [1]]) + with pytest.raises(ValueError, + match="'yerr' must not contain None"): + ax.errorbar(x=[0], y=[0], yerr=[[None], [1]]) + + @check_figures_equal() def test_errorbar_every(fig_test, fig_ref): x = np.linspace(0, 1, 15) From 1ccb66176636de3e290b6f07e434056b21eee206 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:02:43 +0100 Subject: [PATCH 0468/1120] Switch pytest fixture from tmpdir to tmp_path tmp_path is the more modern version providing standard pathlib.Path objects and preferred nowadays https://docs.pytest.org/en/7 .4.x/reference/reference.html#tmpdir. --- lib/matplotlib/tests/test_backend_ps.py | 4 ++-- lib/matplotlib/tests/test_figure.py | 5 ++-- lib/matplotlib/tests/test_font_manager.py | 10 ++++---- lib/matplotlib/tests/test_mathtext.py | 4 ++-- lib/matplotlib/tests/test_matplotlib.py | 14 +++++------ lib/matplotlib/tests/test_pyplot.py | 4 ++-- lib/matplotlib/tests/test_rcparams.py | 29 +++++++++++------------ lib/matplotlib/tests/test_style.py | 9 ++++--- 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index cbf33ccc5a1b..c587a00c0af9 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -124,11 +124,11 @@ def test_patheffects(): @needs_usetex @needs_ghostscript -def test_tilde_in_tempfilename(tmpdir): +def test_tilde_in_tempfilename(tmp_path): # Tilde ~ in the tempdir path (e.g. TMPDIR, TMP or TEMP on windows # when the username is very long and windows uses a short name) breaks # latex before https://github.com/matplotlib/matplotlib/pull/5928 - base_tempdir = Path(tmpdir, "short-1") + base_tempdir = tmp_path / "short-1" base_tempdir.mkdir() # Change the path for new tempdirs, which is used internally by the ps # backend to write a file. diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 24d4f1c0f059..99b2602bc4a7 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1,7 +1,6 @@ import copy from datetime import datetime import io -from pathlib import Path import pickle import platform from threading import Timer @@ -739,8 +738,8 @@ def test_add_artist(fig_test, fig_ref): @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"]) -def test_fspath(fmt, tmpdir): - out = Path(tmpdir, f"test.{fmt}") +def test_fspath(fmt, tmp_path): + out = tmp_path / f"test.{fmt}" plt.savefig(out) with out.open("rb") as file: # All the supported formats include the format name (case-insensitive) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index ec901452ee20..79121a794d12 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -46,12 +46,11 @@ def test_score_weight(): fontManager.score_weight(400, 400)) -def test_json_serialization(tmpdir): +def test_json_serialization(tmp_path): # Can't open a NamedTemporaryFile twice on Windows, so use a temporary # directory instead. - path = Path(tmpdir, "fontlist.json") - json_dump(fontManager, path) - copy = json_load(path) + json_dump(fontManager, tmp_path / "fontlist.json") + copy = json_load(tmp_path / "fontlist.json") with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'findfont: Font family.*not found') for prop in ({'family': 'STIXGeneral'}, @@ -133,8 +132,7 @@ def test_find_noto(): fig.savefig(BytesIO(), format=fmt) -def test_find_invalid(tmpdir): - tmp_path = Path(tmpdir) +def test_find_invalid(tmp_path): with pytest.raises(FileNotFoundError): get_font(tmp_path / 'non-existent-font-name.ttf') diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 7ebc76dabc1d..e3659245d0e7 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -459,8 +459,8 @@ def test_mathtext_fallback(fallback, fontlist): mpl.font_manager.fontManager.ttflist.pop() -def test_math_to_image(tmpdir): - mathtext.math_to_image('$x^2$', str(tmpdir.join('example.png'))) +def test_math_to_image(tmp_path): + mathtext.math_to_image('$x^2$', tmp_path / 'example.png') mathtext.math_to_image('$x^2$', io.BytesIO()) mathtext.math_to_image('$x^2$', io.BytesIO(), color='Maroon') diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index ac1c3455c3d9..9ed8c46615bf 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -21,26 +21,26 @@ def test_parse_to_version_info(version_str, version_tuple): reason="chmod() doesn't work as is on Windows") @pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0, reason="chmod() doesn't work as root") -def test_tmpconfigdir_warning(tmpdir): +def test_tmpconfigdir_warning(tmp_path): """Test that a warning is emitted if a temporary configdir must be used.""" - mode = os.stat(tmpdir).st_mode + mode = os.stat(tmp_path).st_mode try: - os.chmod(tmpdir, 0) + os.chmod(tmp_path, 0) proc = subprocess.run( [sys.executable, "-c", "import matplotlib"], - env={**os.environ, "MPLCONFIGDIR": str(tmpdir)}, + env={**os.environ, "MPLCONFIGDIR": str(tmp_path)}, stderr=subprocess.PIPE, text=True, check=True) assert "set the MPLCONFIGDIR" in proc.stderr finally: - os.chmod(tmpdir, mode) + os.chmod(tmp_path, mode) -def test_importable_with_no_home(tmpdir): +def test_importable_with_no_home(tmp_path): subprocess.run( [sys.executable, "-c", "import pathlib; pathlib.Path.home = lambda *args: 1/0; " "import matplotlib.pyplot"], - env={**os.environ, "MPLCONFIGDIR": str(tmpdir)}, check=True) + env={**os.environ, "MPLCONFIGDIR": str(tmp_path)}, check=True) def test_use_doc_standard_backends(): diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 68a1de24a561..6ebc24c46e29 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -11,14 +11,14 @@ from matplotlib import pyplot as plt -def test_pyplot_up_to_date(tmpdir): +def test_pyplot_up_to_date(tmp_path): pytest.importorskip("black") gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py" if not gen_script.exists(): pytest.skip("boilerplate.py not found") orig_contents = Path(plt.__file__).read_text() - plt_file = tmpdir.join('pyplot.py') + plt_file = tmp_path / 'pyplot.py' plt_file.write_text(orig_contents, 'utf-8') subprocess_run_for_testing( diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 65cd823f13a9..e3e10145533d 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -1,6 +1,5 @@ import copy import os -from pathlib import Path import subprocess import sys from unittest import mock @@ -32,14 +31,14 @@ _listify_validator) -def test_rcparams(tmpdir): +def test_rcparams(tmp_path): mpl.rc('text', usetex=False) mpl.rc('lines', linewidth=22) usetex = mpl.rcParams['text.usetex'] linewidth = mpl.rcParams['lines.linewidth'] - rcpath = Path(tmpdir) / 'test_rcparams.rc' + rcpath = tmp_path / 'test_rcparams.rc' rcpath.write_text('lines.linewidth: 33', encoding='utf-8') # test context given dictionary @@ -197,8 +196,8 @@ def test_axes_titlecolor_rcparams(): assert title.get_color() == 'r' -def test_Issue_1713(tmpdir): - rcpath = Path(tmpdir) / 'test_rcparams.rc' +def test_Issue_1713(tmp_path): + rcpath = tmp_path / 'test_rcparams.rc' rcpath.write_text('timezone: UTC', encoding='utf-8') with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'): rc = mpl.rc_params_from_file(rcpath, True, False) @@ -522,10 +521,10 @@ def test_rcparams_reset_after_fail(): @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") -def test_backend_fallback_headless(tmpdir): +def test_backend_fallback_headless(tmp_path): env = {**os.environ, "DISPLAY": "", "WAYLAND_DISPLAY": "", - "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)} + "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} with pytest.raises(subprocess.CalledProcessError): subprocess.run( [sys.executable, "-c", @@ -540,9 +539,9 @@ def test_backend_fallback_headless(tmpdir): @pytest.mark.skipif( sys.platform == "linux" and not _c_internal_utils.display_is_valid(), reason="headless") -def test_backend_fallback_headful(tmpdir): +def test_backend_fallback_headful(tmp_path): pytest.importorskip("tkinter") - env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)} + env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} backend = subprocess.check_output( [sys.executable, "-c", "import matplotlib as mpl; " @@ -620,12 +619,12 @@ def test_rcparams_legend_loc(value): (0.9, .7), (-0.9, .7), ]) -def test_rcparams_legend_loc_from_file(tmpdir, value): +def test_rcparams_legend_loc_from_file(tmp_path, value): # rcParams['legend.loc'] should be settable from matplotlibrc. # if any of these are not allowed, an exception will be raised. # test for gh issue #22338 - rc_path = tmpdir.join("matplotlibrc") - rc_path.write(f"legend.loc: {value}") + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text(f"legend.loc: {value}") with mpl.rc_context(fname=rc_path): assert mpl.rcParams["legend.loc"] == value @@ -647,8 +646,8 @@ def test_validate_sketch_error(value): @pytest.mark.parametrize("value", ['1, 2, 3', '(1,2,3)']) -def test_rcparams_path_sketch_from_file(tmpdir, value): - rc_path = tmpdir.join("matplotlibrc") - rc_path.write(f"path.sketch: {value}") +def test_rcparams_path_sketch_from_file(tmp_path, value): + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text(f"path.sketch: {value}") with mpl.rc_context(fname=rc_path): assert mpl.rcParams["path.sketch"] == (1, 2, 3) diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index 96715ac0492b..be038965e33d 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -58,8 +58,8 @@ def test_use(): assert mpl.rcParams[PARAM] == VALUE -def test_use_url(tmpdir): - path = Path(tmpdir, 'file') +def test_use_url(tmp_path): + path = tmp_path / 'file' path.write_text('axes.facecolor: adeade', encoding='utf-8') with temp_style('test', DUMMY_SETTINGS): url = ('file:' @@ -69,10 +69,9 @@ def test_use_url(tmpdir): assert mpl.rcParams['axes.facecolor'] == "#adeade" -def test_single_path(tmpdir): +def test_single_path(tmp_path): mpl.rcParams[PARAM] = 'gray' - temp_file = f'text.{STYLE_EXTENSION}' - path = Path(tmpdir, temp_file) + path = tmp_path / f'text.{STYLE_EXTENSION}' path.write_text(f'{PARAM} : {VALUE}', encoding='utf-8') with style.context(path): assert mpl.rcParams[PARAM] == VALUE From ee241ad35fc033560fb18fc414266bd5fdff824f Mon Sep 17 00:00:00 2001 From: Jacob Stevens-Haas <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:31:39 +0000 Subject: [PATCH 0469/1120] ENH: Change logging to warning when creating a legend with no labels (#27172) * ENH: Change logging to warning when creating a legend with no labels Fixes #27145 Also add test_legend_nolabels_warning() * TST: Add mock labels to mock data. * DOC: Note warning in legend() documentation * DOC: Correct examples to no longer raise UserWarning With modified explanation where appropriate * TST: Add test for drawing empty legend() * TST: Test legend for specific warning when no labels present Also: DOC: Correct Axes docstring when no labels in legend. * TST: Filter no labels warning by message. Also assert that a legend is at least created --- .../custom_legends.py | 5 +- lib/matplotlib/axes/_axes.py | 4 +- lib/matplotlib/legend.py | 2 +- lib/matplotlib/tests/test_legend.py | 88 +++++++++++-------- lib/matplotlib/tests/test_pickle.py | 2 +- 5 files changed, 59 insertions(+), 42 deletions(-) diff --git a/galleries/examples/text_labels_and_annotations/custom_legends.py b/galleries/examples/text_labels_and_annotations/custom_legends.py index f1714a21a481..18ace0513228 100644 --- a/galleries/examples/text_labels_and_annotations/custom_legends.py +++ b/galleries/examples/text_labels_and_annotations/custom_legends.py @@ -28,6 +28,7 @@ # Fixing random state for reproducibility np.random.seed(19680801) +# %% N = 10 data = (np.geomspace(1, 10, 100) + np.random.randn(N, 100)).T cmap = plt.cm.coolwarm @@ -35,10 +36,10 @@ fig, ax = plt.subplots() lines = ax.plot(data) -ax.legend() # %% -# Note that no legend entries were created. +# Since the data does not have any labels, creating a legend requires +# us to define the icons and labels. # In this case, we can compose a legend using Matplotlib objects that aren't # explicitly tied to the data that was plotted. For example: diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 90faa2ffab18..da22d5366479 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -239,8 +239,8 @@ def legend(self, *args, **kwargs): selection by using a label starting with an underscore, "_". A string starting with an underscore is the default label for all artists, so calling `.Axes.legend` without any arguments and - without setting the labels manually will result in no legend being - drawn. + without setting the labels manually will result in a ``UserWarning`` + and an empty legend being drawn. **2. Explicitly listing the artists and labels in the legend** diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 93ec3d32c0b7..ba9ee10ef10c 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1360,7 +1360,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): elif len(args) == 0: # 0 args: automatically detect labels and handles. handles, labels = _get_legend_handles_labels(axs, handlers) if not handles: - log.warning( + _api.warn_external( "No artists with labels found to put in legend. Note that " "artists whose label start with an underscore are ignored " "when legend() is called with no argument.") diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 90b0a3f38999..8532c0d68314 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -636,6 +636,7 @@ def test_handler_numpoints(): def test_text_nohandler_warning(): """Test that Text artists with labels raise a warning""" fig, ax = plt.subplots() + ax.plot([0], label="mock data") ax.text(x=0, y=0, s="text", label="label") with pytest.warns(UserWarning) as record: ax.legend() @@ -703,7 +704,7 @@ def test_legend_title_empty(): # it comes back as an empty string, and that it is not # visible: fig, ax = plt.subplots() - ax.plot(range(10)) + ax.plot(range(10), label="mock data") leg = ax.legend() assert leg.get_title().get_text() == "" assert not leg.get_title().get_visible() @@ -736,7 +737,7 @@ def test_window_extent_cached_renderer(): def test_legend_title_fontprop_fontsize(): # test the title_fontsize kwarg - plt.plot(range(10)) + plt.plot(range(10), label="mock data") with pytest.raises(ValueError): plt.legend(title='Aardvark', title_fontsize=22, title_fontproperties={'family': 'serif', 'size': 22}) @@ -747,27 +748,27 @@ def test_legend_title_fontprop_fontsize(): fig, axes = plt.subplots(2, 3, figsize=(10, 6)) axes = axes.flat - axes[0].plot(range(10)) + axes[0].plot(range(10), label="mock data") leg0 = axes[0].legend(title='Aardvark', title_fontsize=22) assert leg0.get_title().get_fontsize() == 22 - axes[1].plot(range(10)) + axes[1].plot(range(10), label="mock data") leg1 = axes[1].legend(title='Aardvark', title_fontproperties={'family': 'serif', 'size': 22}) assert leg1.get_title().get_fontsize() == 22 - axes[2].plot(range(10)) + axes[2].plot(range(10), label="mock data") mpl.rcParams['legend.title_fontsize'] = None leg2 = axes[2].legend(title='Aardvark', title_fontproperties={'family': 'serif'}) assert leg2.get_title().get_fontsize() == mpl.rcParams['font.size'] - axes[3].plot(range(10)) + axes[3].plot(range(10), label="mock data") leg3 = axes[3].legend(title='Aardvark') assert leg3.get_title().get_fontsize() == mpl.rcParams['font.size'] - axes[4].plot(range(10)) + axes[4].plot(range(10), label="mock data") mpl.rcParams['legend.title_fontsize'] = 20 leg4 = axes[4].legend(title='Aardvark', title_fontproperties={'family': 'serif'}) assert leg4.get_title().get_fontsize() == 20 - axes[5].plot(range(10)) + axes[5].plot(range(10), label="mock data") leg5 = axes[5].legend(title='Aardvark') assert leg5.get_title().get_fontsize() == 20 @@ -1071,6 +1072,7 @@ def test_legend_labelcolor_rcparam_markerfacecolor_short(): assert mpl.colors.same_color(text.get_color(), color) +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_get_set_draggable(): legend = plt.legend() assert not legend.get_draggable() @@ -1289,74 +1291,75 @@ def test_loc_invalid_tuple_exception(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(1.1,\\)')): - ax.legend(loc=(1.1, )) + ax.legend(loc=(1.1, ), labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(0.481, 0.4227, 0.4523\\)')): - ax.legend(loc=(0.481, 0.4227, 0.4523)) + ax.legend(loc=(0.481, 0.4227, 0.4523), labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(0.481, \'go blue\'\\)')): - ax.legend(loc=(0.481, "go blue")) + ax.legend(loc=(0.481, "go blue"), labels=["mock data"]) def test_loc_valid_tuple(): fig, ax = plt.subplots() - ax.legend(loc=(0.481, 0.442)) - ax.legend(loc=(1, 2)) + ax.legend(loc=(0.481, 0.442), labels=["mock data"]) + ax.legend(loc=(1, 2), labels=["mock data"]) def test_loc_valid_list(): fig, ax = plt.subplots() - ax.legend(loc=[0.481, 0.442]) - ax.legend(loc=[1, 2]) + ax.legend(loc=[0.481, 0.442], labels=["mock data"]) + ax.legend(loc=[1, 2], labels=["mock data"]) def test_loc_invalid_list_exception(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\[1.1, 2.2, 3.3\\]')): - ax.legend(loc=[1.1, 2.2, 3.3]) + ax.legend(loc=[1.1, 2.2, 3.3], labels=["mock data"]) def test_loc_invalid_type(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=("loc must be string, coordinate " "tuple, or an integer 0-10, not {'not': True}")): - ax.legend(loc={'not': True}) + ax.legend(loc={'not': True}, labels=["mock data"]) def test_loc_validation_numeric_value(): fig, ax = plt.subplots() - ax.legend(loc=0) - ax.legend(loc=1) - ax.legend(loc=5) - ax.legend(loc=10) + ax.legend(loc=0, labels=["mock data"]) + ax.legend(loc=1, labels=["mock data"]) + ax.legend(loc=5, labels=["mock data"]) + ax.legend(loc=10, labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not 11')): - ax.legend(loc=11) + ax.legend(loc=11, labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not -1')): - ax.legend(loc=-1) + ax.legend(loc=-1, labels=["mock data"]) def test_loc_validation_string_value(): fig, ax = plt.subplots() - ax.legend(loc='best') - ax.legend(loc='upper right') - ax.legend(loc='best') - ax.legend(loc='upper right') - ax.legend(loc='upper left') - ax.legend(loc='lower left') - ax.legend(loc='lower right') - ax.legend(loc='right') - ax.legend(loc='center left') - ax.legend(loc='center right') - ax.legend(loc='lower center') - ax.legend(loc='upper center') + labels = ["mock data"] + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='upper left', labels=labels) + ax.legend(loc='lower left', labels=labels) + ax.legend(loc='lower right', labels=labels) + ax.legend(loc='right', labels=labels) + ax.legend(loc='center left', labels=labels) + ax.legend(loc='center right', labels=labels) + ax.legend(loc='lower center', labels=labels) + ax.legend(loc='upper center', labels=labels) with pytest.raises(ValueError, match="'wrong' is not a valid value for"): - ax.legend(loc='wrong') + ax.legend(loc='wrong', labels=labels) def test_legend_handle_label_mismatch(): @@ -1375,3 +1378,16 @@ def test_legend_handle_label_mismatch_no_len(): labels=iter(["pl1", "pl2", "pl3"])) assert len(legend.legend_handles) == 2 assert len(legend.get_texts()) == 2 + + +def test_legend_nolabels_warning(): + plt.plot([1, 2, 3]) + with pytest.raises(UserWarning, match="No artists with labels found"): + plt.legend() + + +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") +def test_legend_nolabels_draw(): + plt.plot([1, 2, 3]) + plt.legend() + assert plt.gca().get_legend() is not None diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index e222a495e437..cab412bff561 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -90,7 +90,7 @@ def _generate_complete_test_figure(fig_ref): plt.legend(loc='upper left') plt.subplot(3, 3, 9) - plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4) + plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$') plt.legend(draggable=True) fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers. From 6896ffd0154d96797e38d7c58b576ce270308a85 Mon Sep 17 00:00:00 2001 From: Steffen Rehberg Date: Fri, 1 Dec 2023 23:31:03 +0100 Subject: [PATCH 0470/1120] DOC: Add AsinhScale to list of built-in scales --- lib/matplotlib/axis.py | 1 + lib/matplotlib/colorbar.py | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index e7f6724c4372..f89278323738 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -807,6 +807,7 @@ def _set_axes_scale(self, value, **kwargs): - `matplotlib.scale.SymmetricalLogScale` - `matplotlib.scale.LogitScale` - `matplotlib.scale.FuncScale` + - `matplotlib.scale.AsinhScale` Notes ----- diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 920c0d67722a..092d4afc54ac 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -998,6 +998,7 @@ def _set_scale(self, scale, **kwargs): - `matplotlib.scale.SymmetricalLogScale` - `matplotlib.scale.LogitScale` - `matplotlib.scale.FuncScale` + - `matplotlib.scale.AsinhScale` Notes ----- From 161d47b5fa1cef273bf93fea6dbb15ca7d8badba Mon Sep 17 00:00:00 2001 From: Matthew Morrison <120498834+mattymo30@users.noreply.github.com> Date: Fri, 1 Dec 2023 23:55:54 -0500 Subject: [PATCH 0471/1120] Fixing Sentence Case on Section Titles in users_explain (#27325) * Fixing sentence case usage inside user_explain/axes pages * Fixing sentence case usage inside user_explain/animations pages * Fixing sentence case usage inside user_explain/artists pages * Fixing sentence case usage inside user_explain/colors pages * Fixing sentence case usage inside user_explain/toolkits pages * Update galleries/users_explain/artists/paths.py Co-authored-by: hannah * Update galleries/users_explain/artists/transforms_tutorial.py Co-authored-by: hannah * Update galleries/users_explain/colors/colormaps.py Co-authored-by: hannah * Update galleries/users_explain/colors/colorbar_only.py Co-authored-by: hannah * Update galleries/users_explain/axes/autoscale.py Co-authored-by: hannah * Update galleries/users_explain/colors/colormap-manipulation.py Co-authored-by: hannah * Update galleries/users_explain/colors/colormaps.py Co-authored-by: hannah --------- Co-authored-by: hannah --- galleries/users_explain/animations/animations.py | 4 ++-- galleries/users_explain/artists/patheffects_guide.py | 4 ++-- galleries/users_explain/axes/autoscale.py | 4 ++-- galleries/users_explain/axes/axes_ticks.py | 2 +- galleries/users_explain/axes/colorbar_placement.py | 2 +- galleries/users_explain/axes/constrainedlayout_guide.py | 2 +- galleries/users_explain/axes/legend_guide.py | 2 +- galleries/users_explain/axes/tight_layout_guide.py | 5 +++-- galleries/users_explain/colors/colorbar_only.py | 6 +++--- galleries/users_explain/colors/colormap-manipulation.py | 6 +++--- galleries/users_explain/colors/colormapnorms.py | 2 +- galleries/users_explain/colors/colormaps.py | 6 +++--- galleries/users_explain/toolkits/axes_grid.rst | 2 +- galleries/users_explain/toolkits/axisartist.rst | 6 +++--- 14 files changed, 27 insertions(+), 26 deletions(-) diff --git a/galleries/users_explain/animations/animations.py b/galleries/users_explain/animations/animations.py index b022350c8985..fb8564f8318e 100644 --- a/galleries/users_explain/animations/animations.py +++ b/galleries/users_explain/animations/animations.py @@ -20,7 +20,7 @@ import matplotlib.animation as animation # %% -# Animation Classes +# Animation classes # ================= # # The animation process in Matplotlib can be thought of in 2 different ways: @@ -158,7 +158,7 @@ def update(frame): plt.show() # %% -# Animation Writers +# Animation writers # ================= # # Animation objects can be saved to disk using various multimedia writers diff --git a/galleries/users_explain/artists/patheffects_guide.py b/galleries/users_explain/artists/patheffects_guide.py index 259261fb09d4..28a6b9dd1d03 100644 --- a/galleries/users_explain/artists/patheffects_guide.py +++ b/galleries/users_explain/artists/patheffects_guide.py @@ -64,7 +64,7 @@ # automatically followed with the "normal" effect, whereas the latter # explicitly defines the two path effects to draw. # -# Making an artist stand out +# Making an Artist stand out # -------------------------- # # One nice way of making artists visually stand out is to draw an outline in @@ -93,7 +93,7 @@ # its user interface. # # -# Greater control of the path effect artist +# Greater control of the path effect Artist # ----------------------------------------- # # As already mentioned, some of the path effects operate at a lower level diff --git a/galleries/users_explain/axes/autoscale.py b/galleries/users_explain/axes/autoscale.py index a9d6b728866c..53435f4086c9 100644 --- a/galleries/users_explain/axes/autoscale.py +++ b/galleries/users_explain/axes/autoscale.py @@ -3,8 +3,8 @@ .. _autoscale: -Autoscaling -=========== +Autoscaling Axis +================ The limits on an axis can be set manually (e.g. ``ax.set_xlim(xmin, xmax)``) or Matplotlib can set them automatically based on the data already on the axes. diff --git a/galleries/users_explain/axes/axes_ticks.py b/galleries/users_explain/axes/axes_ticks.py index aaec87c6a239..3870c26af70e 100644 --- a/galleries/users_explain/axes/axes_ticks.py +++ b/galleries/users_explain/axes/axes_ticks.py @@ -2,7 +2,7 @@ .. _user_axes_ticks: ========== -Axis Ticks +Axis ticks ========== The x and y Axis on each Axes have default tick "locators" and "formatters" diff --git a/galleries/users_explain/axes/colorbar_placement.py b/galleries/users_explain/axes/colorbar_placement.py index 1e43d4940a98..8dbc2a356cb1 100644 --- a/galleries/users_explain/axes/colorbar_placement.py +++ b/galleries/users_explain/axes/colorbar_placement.py @@ -4,7 +4,7 @@ .. redirect-from:: /gallery/subplots_axes_and_figures/colorbar_placement ================= -Placing Colorbars +Placing colorbars ================= Colorbars indicate the quantitative extent of image data. Placing in diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py index 4581f5f67808..d96b829df372 100644 --- a/galleries/users_explain/axes/constrainedlayout_guide.py +++ b/galleries/users_explain/axes/constrainedlayout_guide.py @@ -5,7 +5,7 @@ .. _constrainedlayout_guide: ======================== -Constrained Layout Guide +Constrained layout guide ======================== Use *constrained layout* to fit plots within your figure cleanly. diff --git a/galleries/users_explain/axes/legend_guide.py b/galleries/users_explain/axes/legend_guide.py index 3b138fe8ada3..1482cdbc4a81 100644 --- a/galleries/users_explain/axes/legend_guide.py +++ b/galleries/users_explain/axes/legend_guide.py @@ -213,7 +213,7 @@ plt.show() # %% -# Legend Handlers +# Legend handlers # =============== # # In order to create legend entries, handles are given as an argument to an diff --git a/galleries/users_explain/axes/tight_layout_guide.py b/galleries/users_explain/axes/tight_layout_guide.py index 8525b9773f91..9074641d39ab 100644 --- a/galleries/users_explain/axes/tight_layout_guide.py +++ b/galleries/users_explain/axes/tight_layout_guide.py @@ -4,7 +4,7 @@ .. _tight_layout_guide: ================== -Tight Layout guide +Tight layout guide ================== How to use tight-layout to fit plots within your figure cleanly. @@ -17,6 +17,7 @@ An alternative to *tight_layout* is :ref:`constrained_layout `. + Simple example ============== @@ -209,7 +210,7 @@ def example_plot(ax, fontsize=12): # %% -# Legends and Annotations +# Legends and annotations # ======================= # # Pre Matplotlib 2.2, legends and annotations were excluded from the bounding diff --git a/galleries/users_explain/colors/colorbar_only.py b/galleries/users_explain/colors/colorbar_only.py index a47ced0a4ea6..4140ea454b99 100644 --- a/galleries/users_explain/colors/colorbar_only.py +++ b/galleries/users_explain/colors/colorbar_only.py @@ -1,9 +1,9 @@ """ .. redirect-from:: /tutorials/colors/colorbar_only -==================== -Standalone colorbars -==================== +============================= +Customized Colorbars Tutorial +============================= This tutorial shows how to build and customize standalone colorbars, i.e. without an attached plot. diff --git a/galleries/users_explain/colors/colormap-manipulation.py b/galleries/users_explain/colors/colormap-manipulation.py index 87269b87befa..0cd488857257 100644 --- a/galleries/users_explain/colors/colormap-manipulation.py +++ b/galleries/users_explain/colors/colormap-manipulation.py @@ -3,9 +3,9 @@ .. _colormap-manipulation: -****************** -Creating Colormaps -****************** +******************************** +Creating Colormaps in Matplotlib +******************************** Matplotlib has a number of built-in colormaps accessible via `.matplotlib.colormaps`. There are also external libraries like diff --git a/galleries/users_explain/colors/colormapnorms.py b/galleries/users_explain/colors/colormapnorms.py index f375b3af805b..3aa0ab729371 100644 --- a/galleries/users_explain/colors/colormapnorms.py +++ b/galleries/users_explain/colors/colormapnorms.py @@ -4,7 +4,7 @@ .. _colormapnorms: -Colormap Normalization +Colormap normalization ====================== Objects that use colormaps by default linearly map the colors in the diff --git a/galleries/users_explain/colors/colormaps.py b/galleries/users_explain/colors/colormaps.py index b5db551cb5b5..92b56d298976 100644 --- a/galleries/users_explain/colors/colormaps.py +++ b/galleries/users_explain/colors/colormaps.py @@ -3,9 +3,9 @@ .. _colormaps: -****************** -Choosing Colormaps -****************** +******************************** +Choosing Colormaps in Matplotlib +******************************** Matplotlib has a number of built-in colormaps accessible via `.matplotlib.colormaps`. There are also external libraries that diff --git a/galleries/users_explain/toolkits/axes_grid.rst b/galleries/users_explain/toolkits/axes_grid.rst index 6f7be49ed4c1..694fd1178c04 100644 --- a/galleries/users_explain/toolkits/axes_grid.rst +++ b/galleries/users_explain/toolkits/axes_grid.rst @@ -60,7 +60,7 @@ The examples below show what you can do with ImageGrid. :target: /gallery/axes_grid1/demo_axes_grid.html :align: center -AxesDivider Class +AxesDivider class ----------------- Behind the scenes, ImageGrid (and RGBAxes, described below) rely on diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst index 4927f9a3c940..eff2b575a63f 100644 --- a/galleries/users_explain/toolkits/axisartist.rst +++ b/galleries/users_explain/toolkits/axisartist.rst @@ -99,7 +99,7 @@ Here is an example that uses ParasiteAxes. :target: /gallery/axisartist/demo_parasite_axes2.html :align: center -Curvilinear Grid +Curvilinear grid ---------------- The motivation behind the AxisArtist module is to support a curvilinear grid @@ -298,7 +298,7 @@ HowTo To change the pad between ticklabels and axis label, axis.label.set_pad method. -Rotation and Alignment of TickLabels +Rotation and alignment of TickLabels ==================================== This is also quite different from standard Matplotlib and can be @@ -566,7 +566,7 @@ way is to add it as an item of Axes's axis attribute.:: See the first example of this page. -Current Limitations and TODO's +Current limitations and TODO's ============================== The code need more refinement. Here is a incomplete list of issues and TODO's From 73b2152954ee3ef3db59e38a5ed25221cc853ac1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 2 Dec 2023 04:30:59 -0500 Subject: [PATCH 0472/1120] TST: Cache available interactive backends There are currently 7 backends, and this function is called 8 times during test collection. This means that `XOpenDisplay` would be called 56 times (multiplied by process count if using xdist). Locally, I'm seeing a deadlock after too many calls to `XOpenDisplay` during collection. While this is not directly our bug, it seems prudent to cut this down to only the one check per collection process. I don't think we can cache this globally, as one might want to re-try a GUI figure after starting an X server (e.g., over SSH or similar), without restarting Python/Matplotlib. --- .../tests/test_backends_interactive.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6048b2647b17..3a714319ab81 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -1,3 +1,4 @@ +import functools import importlib import importlib.util import inspect @@ -52,7 +53,10 @@ def wait_for(self, terminator): # PyPI-installable on CI. They are not available for all tested Python # versions so we don't fail on missing backends. -def _get_testable_interactive_backends(): +@functools.lru_cache +def _get_available_interactive_backends(): + _is_linux_and_display_invalid = (sys.platform == "linux" and + not _c_internal_utils.display_is_valid()) envs = [] for deps, env in [ *[([qt_api], @@ -70,8 +74,7 @@ def _get_testable_interactive_backends(): ]: reason = None missing = [dep for dep in deps if not importlib.util.find_spec(dep)] - if (sys.platform == "linux" and - not _c_internal_utils.display_is_valid()): + if _is_linux_and_display_invalid: reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" elif missing: reason = "{} cannot be imported".format(", ".join(missing)) @@ -85,8 +88,7 @@ def _get_testable_interactive_backends(): reason = "no usable GTK bindings" marks = [] if reason: - marks.append(pytest.mark.skip( - reason=f"Skipping {env} because {reason}")) + marks.append(pytest.mark.skip(reason=f"Skipping {env} because {reason}")) elif env["MPLBACKEND"].startswith('wx') and sys.platform == 'darwin': # ignore on OSX because that's currently broken (github #16849) marks.append(pytest.mark.xfail(reason='github #16849')) @@ -97,15 +99,17 @@ def _get_testable_interactive_backends(): ): marks.append( # https://github.com/actions/setup-python/issues/649 pytest.mark.xfail(reason='Tk version mismatch on Azure macOS CI')) - envs.append( - pytest.param( - {**env, 'BACKEND_DEPS': ','.join(deps)}, - marks=marks, id=str(env) - ) - ) + envs.append(({**env, 'BACKEND_DEPS': ','.join(deps)}, marks)) return envs +def _get_testable_interactive_backends(): + # We re-create this because some of the callers below might modify the markers. + return [pytest.param({**env}, marks=[*marks], + id='-'.join(f'{k}={v}' for k, v in env.items())) + for env, marks in _get_available_interactive_backends()] + + def is_ci_environment(): # Common CI variables ci_environment_variables = [ From 17d8bf0e7adb2c17f882dd87e40ed80e25caff76 Mon Sep 17 00:00:00 2001 From: Nidaa Rabah <136858218+nidaa-7@users.noreply.github.com> Date: Sat, 2 Dec 2023 05:06:55 -0500 Subject: [PATCH 0473/1120] Add test_fill in test_datetime.py (#27401) * added a test for fill in test_datetime.py * Removed saving the picture from test code. * reverted .gitignore changes I had added my virtual environment to gitignore and pushed it. Reverted back before pull request * added seed --- lib/matplotlib/tests/test_datetime.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 83f1aee4f8bb..ca8e1cb90732 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -284,11 +284,32 @@ def test_eventplot(self): fig, ax = plt.subplots() ax.eventplot(...) - @pytest.mark.xfail(reason="Test for fill not written yet") @mpl.style.context("default") def test_fill(self): - fig, ax = plt.subplots() - ax.fill(...) + mpl.rcParams["date.converter"] = "concise" + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") + + np.random.seed(19680801) + + x_base_date = datetime.datetime(2023, 1, 1) + x_dates = [x_base_date] + for _ in range(1, 5): + x_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + x_dates.append(x_base_date) + + y_base_date = datetime.datetime(2023, 1, 1) + y_dates = [y_base_date] + for _ in range(1, 5): + y_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + y_dates.append(y_base_date) + + x_values = np.random.rand(5) * 5 + y_values = np.random.rand(5) * 5 - 2 + + ax1.fill(x_dates, y_values) + ax2.fill(x_values, y_dates) + ax3.fill(x_values, y_values) + ax4.fill(x_dates, y_dates) @pytest.mark.xfail(reason="Test for fill_between not written yet") @mpl.style.context("default") From 50e70a41763eb598f312f49ddf085984443d8ec2 Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Sat, 2 Dec 2023 11:14:08 +0100 Subject: [PATCH 0474/1120] Fix setting `_selection_completed` in `SpanSelector` when spanselector is initialised using `extents` --- lib/matplotlib/tests/test_widgets.py | 17 +++++++++++++++++ lib/matplotlib/widgets.py | 8 ++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index de17bb79bd4b..16770321dee9 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -974,6 +974,23 @@ def onselect(vmin, vmax): assert tool.extents == (17, 35) +def test_span_selector_extents(ax): + tool = widgets.SpanSelector( + ax, lambda a, b: None, "horizontal", ignore_event_outside=True + ) + tool.extents = (5, 10) + + assert tool.extents == (5, 10) + assert tool._selection_completed + + # Since `ignore_event_outside=True`, this event should be ignored + press_data = (12, 14) + release_data = (20, 14) + click_and_drag(tool, start=press_data, end=release_data) + + assert tool.extents == (5, 10) + + @pytest.mark.parametrize('kwargs', [ dict(), dict(useblit=False, props=dict(color='red')), diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index cd9716408303..6d1b2a2914a9 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2684,7 +2684,7 @@ def _press(self, event): # visibility to False and extents to (v, v) # update will be called when setting the extents self._visible = False - self.extents = v, v + self._set_extents((v, v)) # We need to set the visibility back, so the span selector will be # drawn when necessary (span width > 0) self._visible = True @@ -2797,7 +2797,7 @@ def _onmove(self, event): if vmin > vmax: vmin, vmax = vmax, vmin - self.extents = vmin, vmax + self._set_extents((vmin, vmax)) if self.onmove_callback is not None: self.onmove_callback(vmin, vmax) @@ -2866,6 +2866,10 @@ def extents(self): @extents.setter def extents(self, extents): + self._set_extents(extents) + self._selection_completed = True + + def _set_extents(self, extents): # Update displayed shape if self.snap_values is not None: extents = tuple(self._snap(extents, self.snap_values)) From d8e272fdff559c74199d2fd643560471b37ba9af Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sat, 2 Dec 2023 14:36:16 -0800 Subject: [PATCH 0475/1120] DOC: multilevel tick example (#27411) * DOC: multilevel tick example * Update galleries/examples/ticks/multilevel_ticks.py fix typo Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --------- Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- galleries/examples/ticks/multilevel_ticks.py | 99 ++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 galleries/examples/ticks/multilevel_ticks.py diff --git a/galleries/examples/ticks/multilevel_ticks.py b/galleries/examples/ticks/multilevel_ticks.py new file mode 100644 index 000000000000..5b8d0ada5ae2 --- /dev/null +++ b/galleries/examples/ticks/multilevel_ticks.py @@ -0,0 +1,99 @@ +""" +========================= +Multilevel (nested) ticks +========================= + +Sometimes we want another level of tick labels on an axis, perhaps to indicate +a grouping of the ticks. + +Matplotlib does not provide an automated way to do this, but it is relatively +straightforward to annotate below the main axis. + +These examples use `.Axes.secondary_xaxis`, which is one approach. It has the +advantage that we can use Matplotlib Locators and Formatters on the axis that +does the grouping if we want. + +This first example creates a secondary xaxis and manually adds the ticks and +labels using `.Axes.set_xticks`. Note that the tick labels have a newline +(e.g. ``"\nOughts"``) at the beginning of them to put the second-level tick +labels below the main tick labels. +""" + +import matplotlib.pyplot as plt +import numpy as np + +import matplotlib.dates as mdates + +rng = np.random.default_rng(19680801) + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) + +ax.plot(np.arange(30)) + +sec = ax.secondary_xaxis(location=0) +sec.set_xticks([5, 15, 25], labels=['\nOughts', '\nTeens', '\nTwenties']) + +# %% +# This second example adds a second level of annotation to a categorical axis. +# Here we need to note that each animal (category) is assigned an integer, so +# ``cats`` is at x=0, ``dogs`` at x=1 etc. Then we place the ticks on the +# second level on an x that is at the middle of the animal class we are trying +# to delineate. +# +# This example also adds tick marks between the classes by adding a second +# secondary xaxis, and placing long, wide ticks at the boundaries between the +# animal classes. + +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) + +ax.plot(['cats', 'dogs', 'pigs', 'snakes', 'lizards', 'chickens', + 'eagles', 'herons', 'buzzards'], + rng.normal(size=9), 'o') + +# label the classes: +sec = ax.secondary_xaxis(location=0) +sec.set_xticks([1, 3.5, 6.5], labels=['\n\nMammals', '\n\nReptiles', '\n\nBirds']) +sec.tick_params('x', length=0) + +# lines between the classes: +sec2 = ax.secondary_xaxis(location=0) +sec2.set_xticks([-0.5, 2.5, 4.5, 8.5], labels=[]) +sec2.tick_params('x', length=40, width=1.5) +ax.set_xlim(-0.6, 8.6) + +# %% +# Dates are another common place where we may want to have a second level of +# tick labels. In this last example, we take advantage of the ability to add +# an automatic locator and formatter to the secondary xaxis, which means we do +# not need to set the ticks manually. +# +# This example also differs from the above, in that we placed it at a location +# below the main axes ``location=-0.075`` and then we hide the spine by setting +# the line width to zero. That means that our formatter no longer needs the +# carriage returns of the previous two examples. + +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) + +time = np.arange(np.datetime64('2020-01-01'), np.datetime64('2020-03-31'), + np.timedelta64(1, 'D')) + +ax.plot(time, rng.random(size=len(time))) + +# just format the days: +ax.xaxis.set_major_formatter(mdates.DateFormatter('%d')) + +# label the months: +sec = ax.secondary_xaxis(location=-0.075) +sec.xaxis.set_major_locator(mdates.MonthLocator(bymonthday=1)) + +# note the extra spaces in the label to align the month label inside the month. +# Note that this could have been done by changing ``bymonthday`` above as well: +sec.xaxis.set_major_formatter(mdates.DateFormatter(' %b')) +sec.tick_params('x', length=0) +sec.spines['bottom'].set_linewidth(0) + +# label the xaxis, but note for this to look good, it needs to be on the +# secondary xaxis. +sec.set_xlabel('Dates (2020)') + +plt.show() From 9a88b368a39ab97cc53ac406ce5de6273f87a6b6 Mon Sep 17 00:00:00 2001 From: "ddale1128@gmail.com" Date: Sun, 3 Dec 2023 18:11:51 -0800 Subject: [PATCH 0476/1120] Expand stairs plot-type entry intro --- galleries/plot_types/basic/stairs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/galleries/plot_types/basic/stairs.py b/galleries/plot_types/basic/stairs.py index 9bc5d025f1e1..3bf90ce37e9f 100644 --- a/galleries/plot_types/basic/stairs.py +++ b/galleries/plot_types/basic/stairs.py @@ -3,7 +3,8 @@ stairs(values) ============== -See `~matplotlib.axes.Axes.stairs`. +See `~matplotlib.axes.Axes.stairs` when plotting $y$ between $(x_i, x_{i+1})$. +For plotting $y$ at $x$, see `~matplotlib.axes.Axes.step`. .. redirect-from:: /plot_types/basic/step """ From 7b8814be56db6793b213ab9a90a21856ed761060 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Dec 2023 00:18:05 -0500 Subject: [PATCH 0477/1120] Fix warning passing NumPy array to hexbin(bins=...) --- lib/matplotlib/axes/_axes.py | 4 ++-- lib/matplotlib/tests/test_axes.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index da22d5366479..6a9353f45146 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5103,10 +5103,10 @@ def reduce_C_function(C: array) -> float ) # Set normalizer if bins is 'log' - if bins == 'log': + if cbook._str_equal(bins, 'log'): if norm is not None: _api.warn_external("Only one of 'bins' and 'norm' arguments " - f"can be supplied, ignoring bins={bins}") + f"can be supplied, ignoring {bins=}") else: norm = mcolors.LogNorm(vmin=vmin, vmax=vmax) vmin = vmax = None diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index d5424f80c9e9..836485b434e5 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -962,17 +962,18 @@ def test_hexbin_extent(): ax.hexbin("x", "y", extent=[.1, .3, .6, .7], data=data) -@image_comparison(['hexbin_empty.png', 'hexbin_empty.png'], remove_text=True) +@image_comparison(['hexbin_empty.png'], remove_text=True) def test_hexbin_empty(): # From #3886: creating hexbin from empty dataset raises ValueError fig, ax = plt.subplots() ax.hexbin([], []) - fig, ax = plt.subplots() # From #23922: creating hexbin with log scaling from empty # dataset raises ValueError ax.hexbin([], [], bins='log') # From #27103: np.max errors when handed empty data ax.hexbin([], [], C=[], reduce_C_function=np.max) + # No string-comparison warning from NumPy. + ax.hexbin([], [], bins=np.arange(10)) def test_hexbin_pickable(): From 14dc7a8e63e432e2d53f4a0e34a2d6239934ec9d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Dec 2023 00:19:52 -0500 Subject: [PATCH 0478/1120] DOC: Fix colorbar label for log-scaled hexbin Fixes #27383 --- galleries/examples/statistics/hexbin_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/statistics/hexbin_demo.py b/galleries/examples/statistics/hexbin_demo.py index 4bd0e0401424..b9a6206a934f 100644 --- a/galleries/examples/statistics/hexbin_demo.py +++ b/galleries/examples/statistics/hexbin_demo.py @@ -29,7 +29,7 @@ hb = ax1.hexbin(x, y, gridsize=50, bins='log', cmap='inferno') ax1.set(xlim=xlim, ylim=ylim) ax1.set_title("With a log color scale") -cb = fig.colorbar(hb, ax=ax1, label='log10(N)') +cb = fig.colorbar(hb, ax=ax1, label='counts') plt.show() From 79ef13c2de771f97f6710efc9960a86ff0703c5e Mon Sep 17 00:00:00 2001 From: "ddale1128@gmail.com" Date: Mon, 4 Dec 2023 21:46:26 -0800 Subject: [PATCH 0479/1120] Rewrite with math directive --- galleries/plot_types/basic/stairs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/plot_types/basic/stairs.py b/galleries/plot_types/basic/stairs.py index 3bf90ce37e9f..d917d3d4e626 100644 --- a/galleries/plot_types/basic/stairs.py +++ b/galleries/plot_types/basic/stairs.py @@ -3,8 +3,8 @@ stairs(values) ============== -See `~matplotlib.axes.Axes.stairs` when plotting $y$ between $(x_i, x_{i+1})$. -For plotting $y$ at $x$, see `~matplotlib.axes.Axes.step`. +See `~matplotlib.axes.Axes.stairs` when plotting :math:`y$` between :math:`(x_i, x_{i+1})`. +For plotting :math:`y` at :math:`x`, see `~matplotlib.axes.Axes.step`. .. redirect-from:: /plot_types/basic/step """ From f3585ef56a4da75658c2f6feadf1ec16f077b951 Mon Sep 17 00:00:00 2001 From: linyilily <71032637+linyilily@users.noreply.github.com> Date: Tue, 5 Dec 2023 07:00:01 -0500 Subject: [PATCH 0480/1120] Add test for fill_between in test_datetime.py (#27423) * add test for fill_between in test_datetime.py * fix error in checks * make y values always increasing * second plot removed as requested * oops forgot to remove savefig --------- Co-authored-by: Linyi Li --- lib/matplotlib/tests/test_datetime.py | 35 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index ca8e1cb90732..ca9c09e45fe7 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -311,11 +311,40 @@ def test_fill(self): ax3.fill(x_values, y_values) ax4.fill(x_dates, y_dates) - @pytest.mark.xfail(reason="Test for fill_between not written yet") @mpl.style.context("default") def test_fill_between(self): - fig, ax = plt.subplots() - ax.fill_between(...) + mpl.rcParams["date.converter"] = "concise" + np.random.seed(19680801) + + y_base_date = datetime.datetime(2023, 1, 1) + y_dates1 = [y_base_date] + for i in range(1, 10): + y_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + y_dates1.append(y_base_date) + + y_dates2 = [y_base_date] + for i in range(1, 10): + y_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + y_dates2.append(y_base_date) + x_values = np.random.rand(10) * 10 + x_values.sort() + + y_values1 = np.random.rand(10) * 10 + y_values2 = y_values1 + np.random.rand(10) * 10 + y_values1.sort() + y_values2.sort() + + x_base_date = datetime.datetime(2023, 1, 1) + x_dates = [x_base_date] + for i in range(1, 10): + x_base_date += datetime.timedelta(days=np.random.randint(1, 10)) + x_dates.append(x_base_date) + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") + + ax1.fill_between(x_values, y_dates1, y_dates2) + ax2.fill_between(x_dates, y_values1, y_values2) + ax3.fill_between(x_dates, y_dates1, y_dates2) @pytest.mark.xfail(reason="Test for fill_betweenx not written yet") @mpl.style.context("default") From cad6d112f59663a03c51a2b9db2ca21ea91d0ae3 Mon Sep 17 00:00:00 2001 From: avramid9 <97982258+avramid9@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:55:59 +0000 Subject: [PATCH 0481/1120] Added test for broken_barh to test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index ca8e1cb90732..1af08ebee724 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -195,11 +195,18 @@ def test_boxplot(self): fig, ax = plt.subplots() ax.boxplot(...) - @pytest.mark.xfail(reason="Test for broken_barh not written yet") @mpl.style.context("default") def test_broken_barh(self): + # Horizontal bar plot with gaps + mpl.rcParams["date.converter"] = 'concise' fig, ax = plt.subplots() - ax.broken_barh(...) + + ax.broken_barh([(datetime.datetime(2023, 1, 4), datetime.timedelta(days=2)), + (datetime.datetime(2023, 1, 8), datetime.timedelta(days=3))], + (10, 9), facecolors='tab:blue') + ax.broken_barh([(datetime.datetime(2023, 1, 2), datetime.timedelta(days=1)), + (datetime.datetime(2023, 1, 4), datetime.timedelta(days=4))], + (20, 9), facecolors=('tab:red')) @pytest.mark.xfail(reason="Test for bxp not written yet") @mpl.style.context("default") From 72a18ccab6af9a7e833ed5afe9cbc5b1bb8958fc Mon Sep 17 00:00:00 2001 From: Joshua Stevenson Date: Tue, 5 Dec 2023 14:38:51 -0500 Subject: [PATCH 0482/1120] remove "Writable." note --- lib/matplotlib/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index ba91d96a2e83..0c00667ac936 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2695,7 +2695,7 @@ def _press(self, event): @property def direction(self): - """Direction of the span selector: 'vertical' or 'horizontal'. Writable.""" + """Direction of the span selector: 'vertical' or 'horizontal'.""" return self._direction @direction.setter @@ -2859,7 +2859,7 @@ def extents(self): (float, float) The values, in data coordinates, for the start and end points of the current selection. If there is no selection then the start and end values will be - the same. Writable. + the same. """ if self.direction == 'horizontal': vmin = self._selection_artist.get_x() From 273a2680de5bbea83b3935cd0697d01d2e7bc06c Mon Sep 17 00:00:00 2001 From: 0taj <89607583+0taj@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:27:07 -0500 Subject: [PATCH 0483/1120] Added smoke test for Axes.stem (#27135) * Added smoke test for Axes.stem * Updates to include bottom and orientation * Move code to stem function * Remove whitespace * Changed dates * Formatting Fixes --- lib/matplotlib/tests/test_datetime.py | 28 ++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 7ca6f121a8ae..4e0128843c2a 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -591,11 +591,33 @@ def test_stairs(self): fig, ax = plt.subplots() ax.stairs(...) - @pytest.mark.xfail(reason="Test for stem not written yet") @mpl.style.context("default") def test_stem(self): - fig, ax = plt.subplots() - ax.stem(...) + mpl.rcParams["date.converter"] = "concise" + + fig, (ax1, ax2, ax3, ax4, ax5, ax6) = plt.subplots(6, 1, layout="constrained") + + limit_value = 10 + above = datetime.datetime(2023, 9, 18) + below = datetime.datetime(2023, 11, 18) + + x_ranges = np.arange(1, limit_value) + y_ranges = np.arange(1, limit_value) + + x_dates = np.array( + [datetime.datetime(2023, 10, n) for n in range(1, limit_value)] + ) + y_dates = np.array( + [datetime.datetime(2023, 10, n) for n in range(1, limit_value)] + ) + + ax1.stem(x_dates, y_dates, bottom=above) + ax2.stem(x_dates, y_ranges, bottom=5) + ax3.stem(x_ranges, y_dates, bottom=below) + + ax4.stem(x_ranges, y_dates, orientation="horizontal", bottom=above) + ax5.stem(x_dates, y_ranges, orientation="horizontal", bottom=5) + ax6.stem(x_ranges, y_dates, orientation="horizontal", bottom=below) @mpl.style.context("default") def test_step(self): From 092a40086045f17465cfca07c15df069b8ca0c16 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Dec 2023 17:07:41 -0500 Subject: [PATCH 0484/1120] TST: Compress some hist geometry tests We don't want to replicate the entire calculation from the implementation, or they could both be buggy, but I think this is a reasonable compromise. --- lib/matplotlib/tests/test_axes.py | 151 +++++++++--------------------- 1 file changed, 46 insertions(+), 105 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index d5424f80c9e9..c7843faee1ec 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4410,134 +4410,75 @@ def test_hist_step_bottom(): ax.hist(d1, bottom=np.arange(10), histtype="stepfilled") -def test_hist_stepfilled_geometry(): - bins = [0, 1, 2, 3] - data = [0, 0, 1, 1, 1, 2] - _, _, (polygon, ) = plt.hist(data, - bins=bins, - histtype='stepfilled') - xy = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], - [3, 0], [2, 0], [2, 0], [1, 0], [1, 0], [0, 0]] - assert_array_equal(polygon.get_xy(), xy) - - def test_hist_step_geometry(): bins = [0, 1, 2, 3] data = [0, 0, 1, 1, 1, 2] - _, _, (polygon, ) = plt.hist(data, - bins=bins, - histtype='step') - xy = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]] - assert_array_equal(polygon.get_xy(), xy) + top = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]] + bottom = [[2, 0], [2, 0], [1, 0], [1, 0], [0, 0]] - -def test_hist_stepfilled_bottom_geometry(): - bins = [0, 1, 2, 3] - data = [0, 0, 1, 1, 1, 2] - _, _, (polygon, ) = plt.hist(data, - bins=bins, - bottom=[1, 2, 1.5], - histtype='stepfilled') - xy = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], - [3, 1.5], [2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]] - assert_array_equal(polygon.get_xy(), xy) + for histtype, xy in [('step', top), ('stepfilled', top + bottom)]: + _, _, (polygon, ) = plt.hist(data, bins=bins, histtype=histtype) + assert_array_equal(polygon.get_xy(), xy) def test_hist_step_bottom_geometry(): bins = [0, 1, 2, 3] data = [0, 0, 1, 1, 1, 2] - _, _, (polygon, ) = plt.hist(data, - bins=bins, - bottom=[1, 2, 1.5], - histtype='step') - xy = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]] - assert_array_equal(polygon.get_xy(), xy) - + top = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]] + bottom = [[2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]] -def test_hist_stacked_stepfilled_geometry(): - bins = [0, 1, 2, 3] - data_1 = [0, 0, 1, 1, 1, 2] - data_2 = [0, 1, 2] - _, _, patches = plt.hist([data_1, data_2], - bins=bins, - stacked=True, - histtype='stepfilled') - - assert len(patches) == 2 - - polygon, = patches[0] - xy = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], - [3, 0], [2, 0], [2, 0], [1, 0], [1, 0], [0, 0]] - assert_array_equal(polygon.get_xy(), xy) - - polygon, = patches[1] - xy = [[0, 2], [0, 3], [1, 3], [1, 4], [2, 4], [2, 2], [3, 2], - [3, 1], [2, 1], [2, 3], [1, 3], [1, 2], [0, 2]] - assert_array_equal(polygon.get_xy(), xy) + for histtype, xy in [('step', top), ('stepfilled', top + bottom)]: + _, _, (polygon, ) = plt.hist(data, bins=bins, bottom=[1, 2, 1.5], + histtype=histtype) + assert_array_equal(polygon.get_xy(), xy) def test_hist_stacked_step_geometry(): bins = [0, 1, 2, 3] data_1 = [0, 0, 1, 1, 1, 2] data_2 = [0, 1, 2] - _, _, patches = plt.hist([data_1, data_2], - bins=bins, - stacked=True, - histtype='step') - - assert len(patches) == 2 - - polygon, = patches[0] - xy = [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]] - assert_array_equal(polygon.get_xy(), xy) - - polygon, = patches[1] - xy = [[0, 2], [0, 3], [1, 3], [1, 4], [2, 4], [2, 2], [3, 2], [3, 1]] - assert_array_equal(polygon.get_xy(), xy) - - -def test_hist_stacked_stepfilled_bottom_geometry(): - bins = [0, 1, 2, 3] - data_1 = [0, 0, 1, 1, 1, 2] - data_2 = [0, 1, 2] - _, _, patches = plt.hist([data_1, data_2], - bins=bins, - stacked=True, - bottom=[1, 2, 1.5], - histtype='stepfilled') - - assert len(patches) == 2 - - polygon, = patches[0] - xy = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], - [3, 1.5], [2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]] - assert_array_equal(polygon.get_xy(), xy) + tops = [ + [[0, 0], [0, 2], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 0]], + [[0, 2], [0, 3], [1, 3], [1, 4], [2, 4], [2, 2], [3, 2], [3, 1]], + ] + bottoms = [ + [[2, 0], [2, 0], [1, 0], [1, 0], [0, 0]], + [[2, 1], [2, 3], [1, 3], [1, 2], [0, 2]], + ] + combined = [t + b for t, b in zip(tops, bottoms)] - polygon, = patches[1] - xy = [[0, 3], [0, 4], [1, 4], [1, 6], [2, 6], [2, 3.5], [3, 3.5], - [3, 2.5], [2, 2.5], [2, 5], [1, 5], [1, 3], [0, 3]] - assert_array_equal(polygon.get_xy(), xy) + for histtype, xy in [('step', tops), ('stepfilled', combined)]: + _, _, patches = plt.hist([data_1, data_2], bins=bins, stacked=True, + histtype=histtype) + assert len(patches) == 2 + polygon, = patches[0] + assert_array_equal(polygon.get_xy(), xy[0]) + polygon, = patches[1] + assert_array_equal(polygon.get_xy(), xy[1]) def test_hist_stacked_step_bottom_geometry(): bins = [0, 1, 2, 3] data_1 = [0, 0, 1, 1, 1, 2] data_2 = [0, 1, 2] - _, _, patches = plt.hist([data_1, data_2], - bins=bins, - stacked=True, - bottom=[1, 2, 1.5], - histtype='step') - - assert len(patches) == 2 - - polygon, = patches[0] - xy = [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]] - assert_array_equal(polygon.get_xy(), xy) - - polygon, = patches[1] - xy = [[0, 3], [0, 4], [1, 4], [1, 6], [2, 6], [2, 3.5], [3, 3.5], [3, 2.5]] - assert_array_equal(polygon.get_xy(), xy) + tops = [ + [[0, 1], [0, 3], [1, 3], [1, 5], [2, 5], [2, 2.5], [3, 2.5], [3, 1.5]], + [[0, 3], [0, 4], [1, 4], [1, 6], [2, 6], [2, 3.5], [3, 3.5], [3, 2.5]], + ] + bottoms = [ + [[2, 1.5], [2, 2], [1, 2], [1, 1], [0, 1]], + [[2, 2.5], [2, 5], [1, 5], [1, 3], [0, 3]], + ] + combined = [t + b for t, b in zip(tops, bottoms)] + + for histtype, xy in [('step', tops), ('stepfilled', combined)]: + _, _, patches = plt.hist([data_1, data_2], bins=bins, stacked=True, + bottom=[1, 2, 1.5], histtype=histtype) + assert len(patches) == 2 + polygon, = patches[0] + assert_array_equal(polygon.get_xy(), xy[0]) + polygon, = patches[1] + assert_array_equal(polygon.get_xy(), xy[1]) @image_comparison(['hist_stacked_bar']) From 580cda586deb17d46d83c68ee82573f12ecf83d7 Mon Sep 17 00:00:00 2001 From: Maggie Liu <72244627+mliu08@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:43:24 -0800 Subject: [PATCH 0485/1120] [TST] Add test_annotate in test_datetime.py (#27222) * smoke test for axes.annotate in datetime * fixed tests using numeric data instead of dates * smoke test for axes.annotate in test.datetime --- lib/matplotlib/tests/test_datetime.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 4e0128843c2a..62a2fe2f2da9 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -14,11 +14,25 @@ def test_acorr(self): fig, ax = plt.subplots() ax.acorr(...) - @pytest.mark.xfail(reason="Test for annotate not written yet") @mpl.style.context("default") def test_annotate(self): - fig, ax = plt.subplots() - ax.annotate(...) + mpl.rcParams["date.converter"] = 'concise' + fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, layout="constrained") + + start_date = datetime.datetime(2023, 10, 1) + dates = [start_date + datetime.timedelta(days=i) for i in range(31)] + data = list(range(1, 32)) + test_text = "Test Text" + + ax1.plot(dates, data) + ax1.annotate(text=test_text, xy=(dates[15], data[15])) + ax2.plot(data, dates) + ax2.annotate(text=test_text, xy=(data[5], dates[26])) + ax3.plot(dates, dates) + ax3.annotate(text=test_text, xy=(dates[15], dates[3])) + ax4.plot(dates, dates) + ax4.annotate(text=test_text, xy=(dates[5], dates[30]), + xytext=(dates[1], dates[7]), arrowprops=dict(facecolor='red')) @pytest.mark.xfail(reason="Test for arrow not written yet") @mpl.style.context("default") From 8be243c0e8f665d8365d9a6f263c4ffcd377f968 Mon Sep 17 00:00:00 2001 From: rawwash <112187975+rawwash@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:50:51 -0500 Subject: [PATCH 0486/1120] Add test_bxp in test_datetime.py (#27390) * added test case for test_bxp * adding datetime functionality * removing plt.show() --- lib/matplotlib/tests/test_datetime.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index e7bb668165f5..fc1c5f5fbd98 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -237,11 +237,24 @@ def test_broken_barh(self): (datetime.datetime(2023, 1, 4), datetime.timedelta(days=4))], (20, 9), facecolors=('tab:red')) - @pytest.mark.xfail(reason="Test for bxp not written yet") @mpl.style.context("default") def test_bxp(self): + mpl.rcParams["date.converter"] = 'concise' fig, ax = plt.subplots() - ax.bxp(...) + data = [{ + "med": datetime.datetime(2020, 1, 15), + "q1": datetime.datetime(2020, 1, 10), + "q3": datetime.datetime(2020, 1, 20), + "whislo": datetime.datetime(2020, 1, 5), + "whishi": datetime.datetime(2020, 1, 25), + "fliers": [ + datetime.datetime(2020, 1, 3), + datetime.datetime(2020, 1, 27) + ] + }] + ax.bxp(data, vert=False) + ax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%Y-%m-%d")) + ax.set_title('Box plot with datetime data') @pytest.mark.xfail(reason="Test for clabel not written yet") @mpl.style.context("default") From ec2ff7b41312bde77d61d8109af11f8bfdc6b65f Mon Sep 17 00:00:00 2001 From: Drew Kinneer Date: Tue, 5 Dec 2023 18:26:01 -0600 Subject: [PATCH 0487/1120] remove quiverkey datetime test --- lib/matplotlib/tests/test_datetime.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index ca9c09e45fe7..56b974ab75e1 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -520,12 +520,6 @@ def test_quiver(self): fig, ax = plt.subplots() ax.quiver(...) - @pytest.mark.xfail(reason="Test for quiverkey not written yet") - @mpl.style.context("default") - def test_quiverkey(self): - fig, ax = plt.subplots() - ax.quiverkey(...) - @mpl.style.context("default") def test_scatter(self): mpl.rcParams["date.converter"] = 'concise' From f579544cf50fa130ce3c480e0e9818873fd02ff7 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 3 Dec 2023 08:59:17 -0800 Subject: [PATCH 0488/1120] MNT: remove xcorr and acorr from test_datetime --- lib/matplotlib/tests/test_datetime.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index fc1c5f5fbd98..94a3947b5a4f 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -8,12 +8,16 @@ class TestDatetimePlotting: +<<<<<<< HEAD @pytest.mark.xfail(reason="Test for acorr not written yet") @mpl.style.context("default") def test_acorr(self): fig, ax = plt.subplots() ax.acorr(...) +======= + @pytest.mark.xfail(reason="Test for annotate not written yet") +>>>>>>> 151199a8ef (MNT: remove xcorr and acorr from test_datetime) @mpl.style.context("default") def test_annotate(self): mpl.rcParams["date.converter"] = 'concise' @@ -738,9 +742,3 @@ def test_violinplot(self): def test_vlines(self): fig, ax = plt.subplots() ax.vlines(...) - - @pytest.mark.xfail(reason="Test for xcorr not written yet") - @mpl.style.context("default") - def test_xcorr(self): - fig, ax = plt.subplots() - ax.xcorr(...) From 403873f6665dea73251c15198fb1df8e28ca8aad Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 Dec 2023 16:53:04 -0800 Subject: [PATCH 0489/1120] DOC: note lack of unit handling in docstring [ci doc] --- lib/matplotlib/axes/_axes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6a9353f45146..358b12ca563d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1979,6 +1979,8 @@ def acorr(self, x, **kwargs): Parameters ---------- x : array-like + Not run through Matplotlib's unit conversion, so this should + be a unit-less array. detrend : callable, default: `.mlab.detrend_none` (no detrending) A detrending function applied to *x*. It must have the @@ -2056,6 +2058,8 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, Parameters ---------- x, y : array-like of length n + Neither *x* nor *y* are run through Matplotlib's unit conversion, so + these should be unit-less arrays. detrend : callable, default: `.mlab.detrend_none` (no detrending) A detrending function applied to *x* and *y*. It must have the From f2da1f00b8ea16e32096f9bf1f323468730317fe Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 3 Dec 2023 08:54:22 -0800 Subject: [PATCH 0490/1120] DOC: normalizing histograms --- galleries/examples/statistics/hist.py | 4 +- .../examples/statistics/histogram_features.py | 60 ----- .../statistics/histogram_normalization.py | 255 ++++++++++++++++++ 3 files changed, 257 insertions(+), 62 deletions(-) delete mode 100644 galleries/examples/statistics/histogram_features.py create mode 100644 galleries/examples/statistics/histogram_normalization.py diff --git a/galleries/examples/statistics/hist.py b/galleries/examples/statistics/hist.py index 9e1a308153ef..8b06093913df 100644 --- a/galleries/examples/statistics/hist.py +++ b/galleries/examples/statistics/hist.py @@ -36,6 +36,8 @@ axs[0].hist(dist1, bins=n_bins) axs[1].hist(dist2, bins=n_bins) +plt.show() + # %% # Updating histogram colors @@ -99,8 +101,6 @@ # We can also define custom numbers of bins for each axis axs[2].hist2d(dist1, dist2, bins=(80, 10), norm=colors.LogNorm()) -plt.show() - # %% # # .. admonition:: References diff --git a/galleries/examples/statistics/histogram_features.py b/galleries/examples/statistics/histogram_features.py deleted file mode 100644 index 21f74fd1ac44..000000000000 --- a/galleries/examples/statistics/histogram_features.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -============================================== -Some features of the histogram (hist) function -============================================== - -In addition to the basic histogram, this demo shows a few optional features: - -* Setting the number of data bins. -* The *density* parameter, which normalizes bin heights so that the integral of - the histogram is 1. The resulting histogram is an approximation of the - probability density function. - -Selecting different bin counts and sizes can significantly affect the shape -of a histogram. The Astropy docs have a great section_ on how to select these -parameters. - -.. _section: http://docs.astropy.org/en/stable/visualization/histogram.html -""" - -import matplotlib.pyplot as plt -import numpy as np - -rng = np.random.default_rng(19680801) - -# example data -mu = 106 # mean of distribution -sigma = 17 # standard deviation of distribution -x = rng.normal(loc=mu, scale=sigma, size=420) - -num_bins = 42 - -fig, ax = plt.subplots() - -# the histogram of the data -n, bins, patches = ax.hist(x, num_bins, density=True) - -# add a 'best fit' line -y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * - np.exp(-0.5 * (1 / sigma * (bins - mu))**2)) -ax.plot(bins, y, '--') -ax.set_xlabel('Value') -ax.set_ylabel('Probability density') -ax.set_title('Histogram of normal distribution sample: ' - fr'$\mu={mu:.0f}$, $\sigma={sigma:.0f}$') - -# Tweak spacing to prevent clipping of ylabel -fig.tight_layout() -plt.show() - -# %% -# -# .. admonition:: References -# -# The use of the following functions, methods, classes and modules is shown -# in this example: -# -# - `matplotlib.axes.Axes.hist` / `matplotlib.pyplot.hist` -# - `matplotlib.axes.Axes.set_title` -# - `matplotlib.axes.Axes.set_xlabel` -# - `matplotlib.axes.Axes.set_ylabel` diff --git a/galleries/examples/statistics/histogram_normalization.py b/galleries/examples/statistics/histogram_normalization.py new file mode 100644 index 000000000000..9418b7af002b --- /dev/null +++ b/galleries/examples/statistics/histogram_normalization.py @@ -0,0 +1,255 @@ +""" +.. redirect-from:: /gallery/statistics/histogram_features + +=================================== +Histogram bins, density, and weight +=================================== + +The `.Axes.hist` method can flexibly create histograms in a few different ways, +which is flexible and helpful, but can also lead to confusion. In particular, +you can: + +- bin the data as you want, either with an automatically chosen number of + bins, or with fixed bin edges, +- normalize the histogram so that its integral is one, +- and assign weights to the data points, so that each data point affects the + count in its bin differently. + +The Matplotlib ``hist`` method calls `numpy.histogram` and plots the results, +therefore users should consult the numpy documentation for a definitive guide. + +Histograms are created by defining bin edges, and taking a dataset of values +and sorting them into the bins, and counting or summing how much data is in +each bin. In this simple example, 9 numbers between 1 and 4 are sorted into 3 +bins: +""" + +import matplotlib.pyplot as plt +import numpy as np + +rng = np.random.default_rng(19680801) + +xdata = np.array([1.2, 2.3, 3.3, 3.1, 1.7, 3.4, 2.1, 1.25, 1.3]) +xbins = np.array([1, 2, 3, 4]) + +# changing the style of the histogram bars just to make it +# very clear where the boundaries of the bins are: +style = {'facecolor': 'none', 'edgecolor': 'C0', 'linewidth': 3} + +fig, ax = plt.subplots() +ax.hist(xdata, bins=xbins, **style) + +# plot the xdata locations on the x axis: +ax.plot(xdata, 0*xdata, 'd') +ax.set_ylabel('Number per bin') +ax.set_xlabel('x bins (dx=1.0)') + +# %% +# Modifying bins +# ============== +# +# Changing the bin size changes the shape of this sparse histogram, so its a +# good idea to choose bins with some care with respect to your data. Here we +# make the bins half as wide. + +xbins = np.arange(1, 4.5, 0.5) + +fig, ax = plt.subplots() +ax.hist(xdata, bins=xbins, **style) +ax.plot(xdata, 0*xdata, 'd') +ax.set_ylabel('Number per bin') +ax.set_xlabel('x bins (dx=0.5)') + +# %% +# We can also let numpy (via Matplotlib) choose the bins automatically, or +# specify a number of bins to choose automatically: + +fig, ax = plt.subplot_mosaic([['auto', 'n4']], + sharex=True, sharey=True, layout='constrained') + +ax['auto'].hist(xdata, **style) +ax['auto'].plot(xdata, 0*xdata, 'd') +ax['auto'].set_ylabel('Number per bin') +ax['auto'].set_xlabel('x bins (auto)') + +ax['n4'].hist(xdata, bins=4, **style) +ax['n4'].plot(xdata, 0*xdata, 'd') +ax['n4'].set_xlabel('x bins ("bins=4")') + +# %% +# Normalizing histograms: density and weight +# ========================================== +# +# Counts-per-bin is the default length of each bar in the histogram. However, +# we can also normalize the bar lengths as a probability density function using +# the ``density`` parameter: + +fig, ax = plt.subplots() +ax.hist(xdata, bins=xbins, density=True, **style) +ax.set_ylabel('Probability density [$V^{-1}$])') +ax.set_xlabel('x bins (dx=0.5 $V$)') + +# %% +# This normalization can be a little hard to interpret when just exploring the +# data. The value attached to each bar is divided by the total number of data +# points *and* the width of the bin, and thus the values _integrate_ to one +# when integrating across the full range of data. +# e.g. :: +# +# density = counts / (sum(counts) * np.diff(bins)) +# np.sum(density * np.diff(bins)) == 1 +# +# This normalization is how `probability density functions +# `_ are defined in +# statistics. If :math:`X` is a random variable on :math:`x`, then :math:`f_X` +# is is the probability density function if :math:`P[a`_, and also calculate the +# known probability density function: + +xdata = rng.normal(size=1000) +xpdf = np.arange(-4, 4, 0.1) +pdf = 1 / (np.sqrt(2 * np.pi)) * np.exp(-xpdf**2 / 2) + +# %% +# If we don't use ``density=True``, we need to scale the expected probability +# distribution function by both the length of the data and the width of the +# bins: + +fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained') +dx = 0.1 +xbins = np.arange(-4, 4, dx) +ax['False'].hist(xdata, bins=xbins, density=False, histtype='step', label='Counts') + +# scale and plot the expected pdf: +ax['False'].plot(xpdf, pdf * len(xdata) * dx, label=r'$N\,f_X(x)\,\delta x$') +ax['False'].set_ylabel('Count per bin') +ax['False'].set_xlabel('x bins [V]') +ax['False'].legend() + +ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label='density') +ax['True'].plot(xpdf, pdf, label='$f_X(x)$') +ax['True'].set_ylabel('Probability density [$V^{-1}$]') +ax['True'].set_xlabel('x bins [$V$]') +ax['True'].legend() + +# %% +# One advantage of using the density is therefore that the shape and amplitude +# of the histogram does not depend on the size of the bins. Consider an +# extreme case where the bins do not have the same width. In this example, the +# bins below ``x=-1.25`` are six times wider than the rest of the bins. By +# normalizing by density, we preserve the shape of the distribution, whereas if +# we do not, then the wider bins have much higher counts than the thinner bins: + +fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained') +dx = 0.1 +xbins = np.hstack([np.arange(-4, -1.25, 6*dx), np.arange(-1.25, 4, dx)]) +ax['False'].hist(xdata, bins=xbins, density=False, histtype='step', label='Counts') +ax['False'].plot(xpdf, pdf * len(xdata) * dx, label=r'$N\,f_X(x)\,\delta x_0$') +ax['False'].set_ylabel('Count per bin') +ax['False'].set_xlabel('x bins [V]') +ax['False'].legend() + +ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label='density') +ax['True'].plot(xpdf, pdf, label='$f_X(x)$') +ax['True'].set_ylabel('Probability density [$V^{-1}$]') +ax['True'].set_xlabel('x bins [$V$]') +ax['True'].legend() + +# %% +# Similarly, if we want to compare histograms with different bin widths, we may +# want to use ``density=True``: + +fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained') + +# expected PDF +ax['True'].plot(xpdf, pdf, '--', label='$f_X(x)$', color='k') + +for nn, dx in enumerate([0.1, 0.4, 1.2]): + xbins = np.arange(-4, 4, dx) + # expected histogram: + ax['False'].plot(xpdf, pdf*1000*dx, '--', color=f'C{nn}') + ax['False'].hist(xdata, bins=xbins, density=False, histtype='step') + + ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label=dx) + +# Labels: +ax['False'].set_xlabel('x bins [$V$]') +ax['False'].set_ylabel('Count per bin') +ax['True'].set_ylabel('Probability density [$V^{-1}$]') +ax['True'].set_xlabel('x bins [$V$]') +ax['True'].legend(fontsize='small', title='bin width:') + +# %% +# Sometimes people want to normalize so that the sum of counts is one. This is +# analogous to a `probability mass function +# `_ for a discrete +# variable where the sum of probabilities for all the values equals one. Using +# ``hist``, we can get this normalization if we set the *weights* to 1/N. +# Note that the amplitude of this normalized histogram still depends on +# width and/or number of the bins: + +fig, ax = plt.subplots(layout='constrained', figsize=(3.5, 3)) + +for nn, dx in enumerate([0.1, 0.4, 1.2]): + xbins = np.arange(-4, 4, dx) + ax.hist(xdata, bins=xbins, weights=1/len(xdata) * np.ones(len(xdata)), + histtype='step', label=f'{dx}') +ax.set_xlabel('x bins [$V$]') +ax.set_ylabel('Bin count / N') +ax.legend(fontsize='small', title='bin width:') + +# %% +# The value of normalizing histograms is comparing two distributions that have +# different sized populations. Here we compare the distribution of ``xdata`` +# with a population of 1000, and ``xdata2`` with 100 members. + +xdata2 = rng.normal(size=100) + +fig, ax = plt.subplot_mosaic([['no_norm', 'density', 'weight']], + layout='constrained', figsize=(8, 4)) + +xbins = np.arange(-4, 4, 0.25) + +ax['no_norm'].hist(xdata, bins=xbins, histtype='step') +ax['no_norm'].hist(xdata2, bins=xbins, histtype='step') +ax['no_norm'].set_ylabel('Counts') +ax['no_norm'].set_xlabel('x bins [$V$]') +ax['no_norm'].set_title('No normalization') + +ax['density'].hist(xdata, bins=xbins, histtype='step', density=True) +ax['density'].hist(xdata2, bins=xbins, histtype='step', density=True) +ax['density'].set_ylabel('Probability density [$V^{-1}$]') +ax['density'].set_title('Density=True') +ax['density'].set_xlabel('x bins [$V$]') + +ax['weight'].hist(xdata, bins=xbins, histtype='step', + weights=1 / len(xdata) * np.ones(len(xdata)), + label='N=1000') +ax['weight'].hist(xdata2, bins=xbins, histtype='step', + weights=1 / len(xdata2) * np.ones(len(xdata2)), + label='N=100') +ax['weight'].set_xlabel('x bins [$V$]') +ax['weight'].set_ylabel('Counts / N') +ax['weight'].legend(fontsize='small') +ax['weight'].set_title('Weight = 1/N') + +plt.show() + +# %% +# +# .. admonition:: References +# +# The use of the following functions, methods, classes and modules is shown +# in this example: +# +# - `matplotlib.axes.Axes.hist` / `matplotlib.pyplot.hist` +# - `matplotlib.axes.Axes.set_title` +# - `matplotlib.axes.Axes.set_xlabel` +# - `matplotlib.axes.Axes.set_ylabel` +# - `matplotlib.axes.Axes.legend` From 444d473b560ee1616ab6ec54a8d1d07feba7817c Mon Sep 17 00:00:00 2001 From: "ddale1128@gmail.com" Date: Tue, 5 Dec 2023 21:20:28 -0800 Subject: [PATCH 0491/1120] Fix flake8 line length error --- galleries/plot_types/basic/stairs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/galleries/plot_types/basic/stairs.py b/galleries/plot_types/basic/stairs.py index d917d3d4e626..54c217a2dca0 100644 --- a/galleries/plot_types/basic/stairs.py +++ b/galleries/plot_types/basic/stairs.py @@ -3,8 +3,9 @@ stairs(values) ============== -See `~matplotlib.axes.Axes.stairs` when plotting :math:`y$` between :math:`(x_i, x_{i+1})`. -For plotting :math:`y` at :math:`x`, see `~matplotlib.axes.Axes.step`. +See `~matplotlib.axes.Axes.stairs` when plotting :math:`y$` between +:math:`(x_i, x_{i+1})`. For plotting :math:`y` at :math:`x`, see +`~matplotlib.axes.Axes.step`. .. redirect-from:: /plot_types/basic/step """ From 7cfd68888e08358a698e284d79d76734066d5c08 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 Dec 2023 21:59:31 -0800 Subject: [PATCH 0492/1120] DOC: note lack of unit handling in docstring [ci doc] --- lib/matplotlib/tests/test_datetime.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 94a3947b5a4f..244ee2af2a58 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -8,16 +8,6 @@ class TestDatetimePlotting: -<<<<<<< HEAD - @pytest.mark.xfail(reason="Test for acorr not written yet") - @mpl.style.context("default") - def test_acorr(self): - fig, ax = plt.subplots() - ax.acorr(...) - -======= - @pytest.mark.xfail(reason="Test for annotate not written yet") ->>>>>>> 151199a8ef (MNT: remove xcorr and acorr from test_datetime) @mpl.style.context("default") def test_annotate(self): mpl.rcParams["date.converter"] = 'concise' From 41afb903c4e8dabafe3d3344319056553112c81a Mon Sep 17 00:00:00 2001 From: Ruoyi Xie <105222584+xieruoyi@users.noreply.github.com> Date: Wed, 6 Dec 2023 02:15:24 -0500 Subject: [PATCH 0493/1120] Added test_fill_betweenx in test_datetime.py (#27425) * Added test for fill_betweenx * pull the newest update * Deleted the second figure and only leave datetime figures --------- Co-authored-by: Ruoyi --- lib/matplotlib/tests/test_datetime.py | 35 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 39a160d8ff03..61d1623c6bbc 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -389,11 +389,40 @@ def test_fill_between(self): ax2.fill_between(x_dates, y_values1, y_values2) ax3.fill_between(x_dates, y_dates1, y_dates2) - @pytest.mark.xfail(reason="Test for fill_betweenx not written yet") @mpl.style.context("default") def test_fill_betweenx(self): - fig, ax = plt.subplots() - ax.fill_betweenx(...) + mpl.rcParams["date.converter"] = "concise" + np.random.seed(19680801) + + x_base_date = datetime.datetime(2023, 1, 1) + x_dates1 = [x_base_date] + for i in range(1, 10): + x_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + x_dates1.append(x_base_date) + + x_dates2 = [x_base_date] + for i in range(1, 10): + x_base_date += datetime.timedelta(days=np.random.randint(1, 5)) + x_dates2.append(x_base_date) + y_values = np.random.rand(10) * 10 + y_values.sort() + + x_values1 = np.random.rand(10) * 10 + x_values2 = x_values1 + np.random.rand(10) * 10 + x_values1.sort() + x_values2.sort() + + y_base_date = datetime.datetime(2023, 1, 1) + y_dates = [y_base_date] + for i in range(1, 10): + y_base_date += datetime.timedelta(days=np.random.randint(1, 10)) + y_dates.append(y_base_date) + + fig, (ax1, ax2, ax3) = plt.subplots(1, 3, layout="constrained") + + ax1.fill_betweenx(y_values, x_dates1, x_dates2) + ax2.fill_betweenx(y_dates, x_values1, x_values2) + ax3.fill_betweenx(y_dates, x_dates1, x_dates2) @pytest.mark.xfail(reason="Test for hexbin not written yet") @mpl.style.context("default") From a4fd8d358ba6984d0578bdbe7263a20949bbc749 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 9 Nov 2023 08:43:02 +0100 Subject: [PATCH 0494/1120] Transpose grid_finder tick representation. Instead of representing the information about tick and gridlines as ``` { "lon_lines": [line, ...], # unused "lat_lines": [line, ...], "lon": [ "tick_levels": [level, ...], "tick_locs": {"left": [(xy, angle), ...], "bottom": [(xy, angle), ...], ...}, "tick_labels": {"left": [label, ...], "bottom": [label, ...], ...}, "lines": [line, ...], ], "lat": ..., # as for lon } ``` where the locs and labels are implicitly associated by the iteration order, group the information for each tick, and remove the redundant gridlines info as well: ``` { "lon": { "lines": [line, ...], "ticks": { "left": [ {"level": level, "loc": (xy, angle), "label": label}, ... ], "bottom": [...], ..., } } "lat": ..., # as for lon } ``` --- lib/mpl_toolkits/axisartist/grid_finder.py | 61 ++++++------------- .../axisartist/grid_helper_curvelinear.py | 18 +++--- 2 files changed, 27 insertions(+), 52 deletions(-) diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index ff61887f9ef2..897a4152b0e7 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -192,25 +192,28 @@ def get_grid_info(self, x1, y1, x2, y2): grid_info = { "extremes": extremes, - "lon_lines": lon_lines, - "lat_lines": lat_lines, - "lon": self._clip_grid_lines_and_find_ticks( - lon_lines, lon_values, lon_levs, bb), - "lat": self._clip_grid_lines_and_find_ticks( - lat_lines, lat_values, lat_levs, bb), + # "lon", "lat", filled below. } - tck_labels = grid_info["lon"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lon"]["tick_levels"][direction] - tck_labels[direction] = self._format_ticks( - 1, direction, lon_factor, levs) - - tck_labels = grid_info["lat"]["tick_labels"] = {} - for direction in ["left", "bottom", "right", "top"]: - levs = grid_info["lat"]["tick_levels"][direction] - tck_labels[direction] = self._format_ticks( - 2, direction, lat_factor, levs) + for idx, lon_or_lat, levs, factor, values, lines in [ + (1, "lon", lon_levs, lon_factor, lon_values, lon_lines), + (2, "lat", lat_levs, lat_factor, lat_values, lat_lines), + ]: + grid_info[lon_or_lat] = gi = { + "lines": [[l] for l in lines], + "ticks": {"left": [], "right": [], "bottom": [], "top": []}, + } + for (lx, ly), v, level in zip(lines, values, levs): + all_crossings = _find_line_box_crossings(np.column_stack([lx, ly]), bb) + for side, crossings in zip( + ["left", "right", "bottom", "top"], all_crossings): + for crossing in crossings: + gi["ticks"][side].append({"level": level, "loc": crossing}) + for side in gi["ticks"]: + levs = [tick["level"] for tick in gi["ticks"][side]] + labels = self._format_ticks(idx, side, factor, levs) + for tick, label in zip(gi["ticks"][side], labels): + tick["label"] = label return grid_info @@ -228,30 +231,6 @@ def _get_raw_grid_lines(self, return lon_lines, lat_lines - def _clip_grid_lines_and_find_ticks(self, lines, values, levs, bb): - gi = { - "values": [], - "levels": [], - "tick_levels": dict(left=[], bottom=[], right=[], top=[]), - "tick_locs": dict(left=[], bottom=[], right=[], top=[]), - "lines": [], - } - - tck_levels = gi["tick_levels"] - tck_locs = gi["tick_locs"] - for (lx, ly), v, lev in zip(lines, values, levs): - tcks = _find_line_box_crossings(np.column_stack([lx, ly]), bb) - gi["levels"].append(v) - gi["lines"].append([(lx, ly)]) - - for tck, direction in zip(tcks, - ["left", "right", "bottom", "top"]): - for t in tck: - tck_levels[direction].append(lev) - tck_locs[direction].append(t) - - return gi - def set_transform(self, aux_trans): if isinstance(aux_trans, Transform): self._aux_transform = aux_trans diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index 8dbfa6adf90f..a7eb9d5cfe21 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -82,9 +82,9 @@ def iter_major(): for nth_coord, show_labels in [ (self.nth_coord_ticks, True), (1 - self.nth_coord_ticks, False)]: gi = self.grid_helper._grid_info[["lon", "lat"][nth_coord]] - for (xy, angle_normal), l in zip( - gi["tick_locs"][side], gi["tick_labels"][side]): - yield xy, angle_normal, angle_tangent, (l if show_labels else "") + for tick in gi["ticks"][side]: + yield (*tick["loc"], angle_tangent, + (tick["label"] if show_labels else "")) return iter_major(), iter([]) @@ -321,12 +321,8 @@ def get_tick_iterator(self, nth_coord, axis_side, minor=False): angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] lon_or_lat = ["lon", "lat"][nth_coord] if not minor: # major ticks - for (xy, angle_normal), l in zip( - self._grid_info[lon_or_lat]["tick_locs"][axis_side], - self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - yield xy, angle_normal, angle_tangent, l + for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: + yield *tick["loc"], angle_tangent, tick["label"] else: - for (xy, angle_normal), l in zip( - self._grid_info[lon_or_lat]["tick_locs"][axis_side], - self._grid_info[lon_or_lat]["tick_labels"][axis_side]): - yield xy, angle_normal, angle_tangent, "" + for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: + yield *tick["loc"], angle_tangent, "" From 94ef1b002454647a722db79ce0ae00a1dea0004f Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:12:41 +0000 Subject: [PATCH 0495/1120] BLD: Update Qhull location --- .../{qhull-2020.2 => qhull-8.0.2}/meson.build | 0 subprojects/qhull.wrap | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename subprojects/packagefiles/{qhull-2020.2 => qhull-8.0.2}/meson.build (100%) diff --git a/subprojects/packagefiles/qhull-2020.2/meson.build b/subprojects/packagefiles/qhull-8.0.2/meson.build similarity index 100% rename from subprojects/packagefiles/qhull-2020.2/meson.build rename to subprojects/packagefiles/qhull-8.0.2/meson.build diff --git a/subprojects/qhull.wrap b/subprojects/qhull.wrap index f43ead300834..23f49da33d90 100644 --- a/subprojects/qhull.wrap +++ b/subprojects/qhull.wrap @@ -1,9 +1,9 @@ [wrap-file] # Also bump the cache key in `.circleci/config.yml`. # Also update the docs in `docs/devel/dependencies.rst`. -directory = qhull-2020.2 -source_url = http://www.qhull.org/download/qhull-2020-src-8.0.2.tgz +directory = qhull-8.0.2 +source_url = https://github.com/qhull/qhull/archive/refs/tags/v8.0.2.tar.gz source_filename = qhull-2020-src-8.0.2.tgz -source_hash = b5c2d7eb833278881b952c8a52d20179eab87766b00b865000469a45c1838b7e +source_hash = 8774e9a12c70b0180b95d6b0b563c5aa4bea8d5960c15e18ae3b6d2521d64f8b -patch_directory = qhull-2020.2 +patch_directory = qhull-8.0.2 From ff8d5f00b44ce50458bc17c6b008790a89b669ef Mon Sep 17 00:00:00 2001 From: Dale Dai <145884899+CouldNot@users.noreply.github.com> Date: Wed, 6 Dec 2023 08:03:43 -0800 Subject: [PATCH 0496/1120] Remove stray simple Co-authored-by: hannah --- galleries/plot_types/basic/stairs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/plot_types/basic/stairs.py b/galleries/plot_types/basic/stairs.py index 54c217a2dca0..de5761e3a7a5 100644 --- a/galleries/plot_types/basic/stairs.py +++ b/galleries/plot_types/basic/stairs.py @@ -3,7 +3,7 @@ stairs(values) ============== -See `~matplotlib.axes.Axes.stairs` when plotting :math:`y$` between +See `~matplotlib.axes.Axes.stairs` when plotting :math:`y` between :math:`(x_i, x_{i+1})`. For plotting :math:`y` at :math:`x`, see `~matplotlib.axes.Axes.step`. From 70dc609d4455986a7297b50409a2598d603becf7 Mon Sep 17 00:00:00 2001 From: madisonwong210 Date: Thu, 30 Nov 2023 17:40:29 -0500 Subject: [PATCH 0497/1120] Add test_vlines to test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 61d1623c6bbc..cec2fa891e80 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -750,8 +750,24 @@ def test_violinplot(self): fig, ax = plt.subplots() ax.violinplot(...) - @pytest.mark.xfail(reason="Test for vlines not written yet") @mpl.style.context("default") def test_vlines(self): - fig, ax = plt.subplots() - ax.vlines(...) + mpl.rcParams["date.converter"] = 'concise' + fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout='constrained') + ax1.set_xlim(left=datetime.datetime(2023, 1, 1), + right=datetime.datetime(2023, 6, 30)) + ax1.vlines(x=[datetime.datetime(2023, 2, 10), + datetime.datetime(2023, 5, 18), + datetime.datetime(2023, 6, 6)], + ymin=[0, 0.25, 0.5], + ymax=[0.25, 0.5, 0.75]) + ax2.set_xlim(left=0, + right=0.5) + ax2.vlines(x=[0.3, 0.35], + ymin=[np.datetime64('2023-03-20'), np.datetime64('2023-03-31')], + ymax=[np.datetime64('2023-05-01'), np.datetime64('2023-05-16')]) + ax3.set_xlim(left=datetime.datetime(2023, 7, 1), + right=datetime.datetime(2023, 12, 31)) + ax3.vlines(x=[datetime.datetime(2023, 9, 1), datetime.datetime(2023, 12, 10)], + ymin=datetime.datetime(2023, 1, 15), + ymax=datetime.datetime(2023, 1, 30)) From f80ff656e6095a1d322696bf8ddd59a47322d9d0 Mon Sep 17 00:00:00 2001 From: Issam Date: Thu, 7 Dec 2023 00:09:31 +0300 Subject: [PATCH 0498/1120] Remove backend 3.7-deprecated API (#26962) * remove deprecated class PsBackendHelper Signed-off-by: Issam Arabi * remove deprecated class ServerThread Signed-off-by: Issam Arabi * remove deprecated class PsBackendHelper dependency Signed-off-by: Issam Arabi * remove blank lines Signed-off-by: Issam Arabi * Delete deprecated C++ methods PyBufferRegion_to_string and PyBufferRegion_to_string_argb This commit removes two deprecated methods, PyBufferRegion_to_string and PyBufferRegion_to_string_argb, from src/_backend_agg_wrapper.cpp, deprecated since version 3.7. Signed-off-by: Issam Arabi * Update API change notes to document removal of deprecated classes and methods from 3.7 This commit updates the API change notes to reflect the removal of deprecated Python classes and C++ methods. Signed-off-by: Issam Arabi * Remove deprecated PyBufferRegion methods from _backend_agg_wrapper.cpp Removed `to_string` and `to_string_argb` methods from `PyBufferRegion_init_type` Signed-off-by: Issam Arabi * Update format of API change note Signed-off-by: Issam Arabi * remove to_string_argb Signed-off-by: Issam Arabi * Update doc format Co-authored-by: Kyle Sunden * Update doc/api/next_api_changes/removals/26962-IA.rst Update doc format Co-authored-by: Elliott Sales de Andrade * remove to_string_argb declaration Signed-off-by: Issam Arabi * refactor * Update formatting of note Co-authored-by: Kyle Sunden --------- Signed-off-by: Issam Arabi Co-authored-by: Kyle Sunden Co-authored-by: Elliott Sales de Andrade --- .../next_api_changes/removals/26962-IA.rst | 19 ++++++++++ lib/matplotlib/backends/backend_ps.py | 8 ----- lib/matplotlib/backends/backend_webagg.py | 6 ---- src/_backend_agg.cpp | 20 ----------- src/_backend_agg.h | 2 -- src/_backend_agg_wrapper.cpp | 36 ------------------- 6 files changed, 19 insertions(+), 72 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26962-IA.rst diff --git a/doc/api/next_api_changes/removals/26962-IA.rst b/doc/api/next_api_changes/removals/26962-IA.rst new file mode 100644 index 000000000000..ac1ab46944b6 --- /dev/null +++ b/doc/api/next_api_changes/removals/26962-IA.rst @@ -0,0 +1,19 @@ +Deprecated Classes Removed +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following deprecated classes from version 3.7 have been removed: + +- ``matplotlib.backends.backend_ps.PsBackendHelper`` +- ``matplotlib.backends.backend_webagg.ServerThread`` + +These classes were previously marked as deprecated and have now been removed in accordance with the deprecation cycle. + +Deprecated C++ Methods Removed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following deprecated C++ methods from :file:`src/_backend_agg_wrapper.cpp`, deprecated since version 3.7, have also been removed: + +- ``PyBufferRegion_to_string`` +- ``PyBufferRegion_to_string_argb`` + +These methods were previously marked as deprecated and have now been removed. diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index dad3049f51da..d760bef04d19 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -39,17 +39,9 @@ debugPS = False -@_api.deprecated("3.7") -class PsBackendHelper: - def __init__(self): - self._cached = {} - - @_api.caching_module_getattr class __getattr__: # module-level deprecations - ps_backend_helper = _api.deprecated("3.7", obj_type="")( - property(lambda self: PsBackendHelper())) psDefs = _api.deprecated("3.8", obj_type="")(property(lambda self: _psDefs)) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index 14c0b525fb8f..dfc5747ef77c 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -37,12 +37,6 @@ TimerAsyncio, TimerTornado) -@mpl._api.deprecated("3.7") -class ServerThread(threading.Thread): - def run(self): - tornado.ioloop.IOLoop.instance().start() - - webagg_server_thread = threading.Thread( target=lambda: tornado.ioloop.IOLoop.instance().start()) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index aec7676b7951..10a17f2dc9cb 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -5,26 +5,6 @@ #include "_backend_agg.h" #include "mplutils.h" -void BufferRegion::to_string_argb(uint8_t *buf) -{ - unsigned char *pix; - unsigned char tmp; - size_t i, j; - - memcpy(buf, data, (size_t) height * stride); - - for (i = 0; i < (size_t)height; ++i) { - pix = buf + i * stride; - for (j = 0; j < (size_t)width; ++j) { - // Convert rgba to argb - tmp = pix[2]; - pix[2] = pix[0]; - pix[0] = tmp; - pix += 4; - } - } -} - RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) : width(width), height(height), diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 9f67c77eab64..c191f3448144 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -86,8 +86,6 @@ class BufferRegion return stride; } - void to_string_argb(uint8_t *buf); - private: agg::int8u *data; agg::rect_i rect; diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index d4ce224d93c3..eaf4bf6f5f9d 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -44,18 +44,6 @@ static void PyBufferRegion_dealloc(PyBufferRegion *self) Py_TYPE(self)->tp_free((PyObject *)self); } -static PyObject *PyBufferRegion_to_string(PyBufferRegion *self, PyObject *args) -{ - char const* msg = - "BufferRegion.to_string is deprecated since Matplotlib 3.7 and will " - "be removed two minor releases later; use np.asarray(region) instead."; - if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { - return NULL; - } - return PyBytes_FromStringAndSize((const char *)self->x->get_data(), - (Py_ssize_t) self->x->get_height() * self->x->get_stride()); -} - /* TODO: This doesn't seem to be used internally. Remove? */ static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args) @@ -87,28 +75,6 @@ static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); } -static PyObject *PyBufferRegion_to_string_argb(PyBufferRegion *self, PyObject *args) -{ - char const* msg = - "BufferRegion.to_string_argb is deprecated since Matplotlib 3.7 and " - "will be removed two minor releases later; use " - "np.take(region, [2, 1, 0, 3], axis=2) instead."; - if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { - return NULL; - } - PyObject *bufobj; - uint8_t *buf; - Py_ssize_t height, stride; - height = self->x->get_height(); - stride = self->x->get_stride(); - bufobj = PyBytes_FromStringAndSize(NULL, height * stride); - buf = (uint8_t *)PyBytes_AS_STRING(bufobj); - - CALL_CPP_CLEANUP("to_string_argb", (self->x->to_string_argb(buf)), Py_DECREF(bufobj)); - - return bufobj; -} - int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) { Py_INCREF(self); @@ -136,8 +102,6 @@ int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) static PyTypeObject *PyBufferRegion_init_type() { static PyMethodDef methods[] = { - { "to_string", (PyCFunction)PyBufferRegion_to_string, METH_NOARGS, NULL }, - { "to_string_argb", (PyCFunction)PyBufferRegion_to_string_argb, METH_NOARGS, NULL }, { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, From 302fd39a0fe373aa905e8c273b9f6d9f0020485a Mon Sep 17 00:00:00 2001 From: KheshavKumar <59756754+KheshavKumar@users.noreply.github.com> Date: Wed, 6 Dec 2023 22:31:16 -0500 Subject: [PATCH 0499/1120] Added test for Axes.bar_label (#27431) * Added test for Axes.bar_label * Added test for Axes.bar_label after fixing typos * Added test for Axes.bar_label after fixing typos * Added test for Axes.bar_label after fixing typos * Updated code based on review * Update test_datetime.py * removed the .show line and added bar_label back * added bar_label, removed show, fixed linting issues * Update lib/matplotlib/tests/test_datetime.py Changed axs to ax Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/tests/test_datetime.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index cec2fa891e80..529244c9656b 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -163,11 +163,20 @@ def test_bar(self): ax1.bar(x_dates, x_ranges, width=np.timedelta64(4, "D")) ax2.bar(np.arange(4), x_dates - x, bottom=x) - @pytest.mark.xfail(reason="Test for bar_label not written yet") @mpl.style.context("default") def test_bar_label(self): - fig, ax = plt.subplots() - ax.bar_label(...) + # Generate some example data with dateTime inputs + date_list = [datetime.datetime(2023, 1, 1) + + datetime.timedelta(days=i) for i in range(5)] + values = [10, 20, 15, 25, 30] + + # Creating the plot + fig, ax = plt.subplots(1, 1, figsize=(10, 8), layout='constrained') + bars = ax.bar(date_list, values) + + # Add labels to the bars using bar_label + ax.bar_label(bars, labels=[f'{val}%' for val in values], + label_type='edge', color='black') @mpl.style.context("default") def test_barbs(self): From ea1b2df97b5b29ae65298ee73c2b92af5938ff58 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 6 Dec 2023 16:03:47 +0100 Subject: [PATCH 0500/1120] Cleanup unit_cube-methods --- lib/mpl_toolkits/mplot3d/axes3d.py | 36 +++++------------------------- lib/mpl_toolkits/mplot3d/axis3d.py | 2 +- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 84bbd763f882..da3d7906e2c0 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -225,9 +225,10 @@ def get_zaxis(self): get_zgridlines = _axis_method_wrapper("zaxis", "get_gridlines") get_zticklines = _axis_method_wrapper("zaxis", "get_ticklines") - def _unit_cube(self, vals=None): - minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims() - return [(minx, miny, minz), + def _transformed_cube(self, vals): + """Return cube with limits from *vals* transformed by self.M.""" + minx, maxx, miny, maxy, minz, maxz = vals + xyzs = [(minx, miny, minz), (maxx, miny, minz), (maxx, maxy, minz), (minx, maxy, minz), @@ -235,31 +236,7 @@ def _unit_cube(self, vals=None): (maxx, miny, maxz), (maxx, maxy, maxz), (minx, maxy, maxz)] - - def _tunit_cube(self, vals=None, M=None): - if M is None: - M = self.M - xyzs = self._unit_cube(vals) - tcube = proj3d._proj_points(xyzs, M) - return tcube - - def _tunit_edges(self, vals=None, M=None): - tc = self._tunit_cube(vals, M) - edges = [(tc[0], tc[1]), - (tc[1], tc[2]), - (tc[2], tc[3]), - (tc[3], tc[0]), - - (tc[0], tc[4]), - (tc[1], tc[5]), - (tc[2], tc[6]), - (tc[3], tc[7]), - - (tc[4], tc[5]), - (tc[5], tc[6]), - (tc[6], tc[7]), - (tc[7], tc[4])] - return edges + return proj3d._proj_points(xyzs, self.M) def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): """ @@ -487,8 +464,7 @@ def draw(self, renderer): super().draw(renderer) def get_axis_position(self): - vals = self.get_w_lims() - tc = self._tunit_cube(vals, self.M) + tc = self._transformed_cube(self.get_w_lims()) xhigh = tc[1][2] > tc[2][2] yhigh = tc[3][2] > tc[2][2] zhigh = tc[0][2] > tc[2][2] diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index dea831582288..7b738b705c2a 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -285,7 +285,7 @@ def _get_coord_info(self): # Project the bounds along the current position of the cube: bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] - bounds_proj = self.axes._tunit_cube(bounds, self.axes.M) + bounds_proj = self.axes._transformed_cube(bounds) # Determine which one of the parallel planes are higher up: means_z0 = np.zeros(3) From dd19976c2d96e693a5b1b139900f5ab2114571ca Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 6 Dec 2023 16:08:14 -0500 Subject: [PATCH 0501/1120] TST: adding tests of current clear behavior on ticks closes #23839 --- lib/matplotlib/tests/test_axes.py | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index d5424f80c9e9..37be1911d8e8 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8898,3 +8898,43 @@ def test_axhvlinespan_interpolation(): ax.axhline(1, c="C0", alpha=.5) ax.axhspan(.8, .9, fc="C1", alpha=.5) ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5) + + +@check_figures_equal(extensions=["png"]) +@pytest.mark.parametrize("which", ("x", "y")) +def test_axes_clear_behavior(fig_ref, fig_test, which): + ax_test = fig_test.subplots() + ax_ref = fig_ref.subplots() + target = { + "direction": "in", + "length": 10, + "width": 10, + "color": "xkcd:wine red", + "pad": 0, + "labelfontfamily": "serif", + "zorder": 7, + "labelrotation": 45, + "labelcolor": "xkcd:shocking pink", + # this overrides color + labelcolor, skip + # colors: , + "grid_color": "xkcd:fluorescent green", + "grid_alpha": 0.5, + "grid_linewidth": 3, + "grid_linestyle": ":", + "bottom": False, + "top": True, + "left": False, + "right": True, + "labelbottom": True, + "labeltop": True, + "labelleft": True, + "labelright": True, + } + + ax_ref.tick_params(axis=which, **target) + + ax_test.tick_params(axis=which, **target) + ax_test.clear() + + ax_ref.grid(True) + ax_test.grid(True) From 4e45a67c6a139a23a5c7e20faf595ec5331e4a20 Mon Sep 17 00:00:00 2001 From: Shriya Kalakata Date: Thu, 7 Dec 2023 17:49:30 -0500 Subject: [PATCH 0502/1120] Add test_eventplot to test_datetime.py --- lib/matplotlib/tests/test_datetime.py | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index b19124a1b764..54fd43bbb286 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -260,11 +260,36 @@ def test_errorbar(self): uplims=True, xuplims=True, label='Data') - @pytest.mark.xfail(reason="Test for eventplot not written yet") @mpl.style.context("default") def test_eventplot(self): - fig, ax = plt.subplots() - ax.eventplot(...) + mpl.rcParams["date.converter"] = "concise" + + fig, (ax1, ax2) = plt.subplots(2, 1, layout="constrained") + + x_dates1 = np.array([ + datetime.datetime(2020, 6, 30), + datetime.datetime(2020, 7, 22), + datetime.datetime(2020, 8, 3), + datetime.datetime(2020, 9, 14), + ], dtype=np.datetime64) + + ax1.eventplot(x_dates1) + + x_dates2 = np.array([ + [datetime.datetime(2020, 6, 30), datetime.datetime(2020, 7, 22), + datetime.datetime(2020, 8, 3), datetime.datetime(2020, 9, 14)], + [datetime.datetime(2020, 7, 18), datetime.datetime(2020, 7, 21), + datetime.datetime(2020, 8, 3), datetime.datetime(2020, 10, 14)] + ], dtype=np.datetime64) + + colors = ['C{}'.format(i) for i in range(2)] + lineoffsets = np.array([1, 6]) + linelengths = [5, 2] + + ax2.eventplot(x_dates2, + colors=colors, + lineoffsets=lineoffsets, + linelengths=linelengths) @pytest.mark.xfail(reason="Test for fill not written yet") @mpl.style.context("default") From a676dce422688d5d9b52b0f6b5aceabb8a5a5fd3 Mon Sep 17 00:00:00 2001 From: Shriya Kalakata Date: Thu, 7 Dec 2023 23:19:45 -0500 Subject: [PATCH 0503/1120] Modified test to check for input with varying lengths of data --- lib/matplotlib/tests/test_datetime.py | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 54fd43bbb286..11974d5782c4 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -266,30 +266,31 @@ def test_eventplot(self): fig, (ax1, ax2) = plt.subplots(2, 1, layout="constrained") - x_dates1 = np.array([ - datetime.datetime(2020, 6, 30), - datetime.datetime(2020, 7, 22), - datetime.datetime(2020, 8, 3), - datetime.datetime(2020, 9, 14), - ], dtype=np.datetime64) + x_dates1 = np.array([datetime.datetime(2020, 6, 30), + datetime.datetime(2020, 7, 22), + datetime.datetime(2020, 8, 3), + datetime.datetime(2020, 9, 14),], + dtype=np.datetime64, + ) ax1.eventplot(x_dates1) - x_dates2 = np.array([ - [datetime.datetime(2020, 6, 30), datetime.datetime(2020, 7, 22), - datetime.datetime(2020, 8, 3), datetime.datetime(2020, 9, 14)], - [datetime.datetime(2020, 7, 18), datetime.datetime(2020, 7, 21), - datetime.datetime(2020, 8, 3), datetime.datetime(2020, 10, 14)] - ], dtype=np.datetime64) - - colors = ['C{}'.format(i) for i in range(2)] - lineoffsets = np.array([1, 6]) - linelengths = [5, 2] - - ax2.eventplot(x_dates2, - colors=colors, - lineoffsets=lineoffsets, - linelengths=linelengths) + start_date = datetime.datetime(2020, 7, 1) + end_date = datetime.datetime(2020, 10, 15) + date_range = end_date - start_date + + dates1 = start_date + np.random.rand(30) * date_range + dates2 = start_date + np.random.rand(10) * date_range + dates3 = start_date + np.random.rand(50) * date_range + + colors1 = ['C1', 'C2', 'C3'] + lineoffsets1 = np.array([1, 6, 8]) + linelengths1 = [5, 2, 3] + + ax2.eventplot([dates1, dates2, dates3], + colors=colors1, + lineoffsets=lineoffsets1, + linelengths=linelengths1) @pytest.mark.xfail(reason="Test for fill not written yet") @mpl.style.context("default") From bf397911866e825ea80248e896bacdec3bfb07c4 Mon Sep 17 00:00:00 2001 From: Shriya Kalakata Date: Fri, 8 Dec 2023 00:47:51 -0500 Subject: [PATCH 0504/1120] Set random seed --- lib/matplotlib/tests/test_datetime.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 11974d5782c4..cf5bd82d2223 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -275,6 +275,8 @@ def test_eventplot(self): ax1.eventplot(x_dates1) + np.random.seed(19680801) + start_date = datetime.datetime(2020, 7, 1) end_date = datetime.datetime(2020, 10, 15) date_range = end_date - start_date From 60a21233dacb22315daffcae1addf9b52853cc82 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 8 Dec 2023 00:29:36 -0500 Subject: [PATCH 0505/1120] Fix test visualization tool for non-PNG files For non-PNG files, our test diffing code creates a failed diff with a suffix of `_{ext}-failed-diff.png`, but the expected image has a suffix of `-expected_{ext}.png`. The test visualization was not correctly handling this difference. --- tools/triage_tests.py | 2 +- tools/visualize_tests.py | 33 ++++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tools/triage_tests.py b/tools/triage_tests.py index d44d6fd743ea..5153b1c712cb 100644 --- a/tools/triage_tests.py +++ b/tools/triage_tests.py @@ -241,7 +241,7 @@ def __init__(self, path, root, source): if basename.endswith(f'_{ext}'): display_extension = f'_{ext}' extension = ext - basename = basename[:-4] + basename = basename[:-len(display_extension)] break else: display_extension = '' diff --git a/tools/visualize_tests.py b/tools/visualize_tests.py index 5ff3e0add97b..72948d67fb1f 100644 --- a/tools/visualize_tests.py +++ b/tools/visualize_tests.py @@ -10,6 +10,8 @@ import os from collections import defaultdict +# Non-png image extensions +NON_PNG_EXTENSIONS = ['pdf', 'svg', 'eps'] html_template = """ + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,10 +35,10 @@ L 518.4 388.8 L 518.4 43.2 L 72 43.2 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#pcfdea541ed)" style="fill: none; stroke: #0000ff; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> +" style="fill: none; stroke: #000000; stroke-linejoin: miter; stroke-linecap: square"/> - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - 0 + 0 - + - + - 1 + 1 - + - + - 2 + 2 - + - + - 3 + 3 - + - + - 4 + 4 - + - + - 5 + 5 - + - + - 6 + 6 - + - + - 7 + 7 - + - + - 8 + 8 - + - + - 9 + 9 - nonbold-xlabel + nonbold-xlabel - +" style="stroke: #000000; stroke-width: 0.5"/> - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - 0 + 0 - + - + - 1 + 1 - + - + - 2 + 2 - + - + - 3 + 3 - + - + - 4 + 4 - + - + - 5 + 5 - + - + - 6 + 6 - + - + - 7 + 7 - + - + - 8 + 8 - + - + - 9 + 9 - bold-ylabel + bold-ylabel - bold-title + bold-title - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg index 63cdeb8fbd47..cbc1fbb85717 100644 --- a/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg +++ b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg @@ -1,12 +1,23 @@ - - + + + + + + 2024-03-27T18:41:24.756455 + image/svg+xml + + + Matplotlib v3.9.0.dev1430+g883dca40e7, https://matplotlib.org/ + + + + + - + @@ -15,17 +26,17 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - 50% using `color` + 50% using `color` - 50% using `alpha` + 50% using `alpha` - 50% using `alpha` and 100% `color` + 50% using `alpha` and 100% `color` From 48bc62d6088c262b1b5507981f2948d117e63601 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 27 Mar 2024 23:50:57 -0400 Subject: [PATCH 0905/1120] ci: Build Apple Silicon wheels natively Now that we are running a (simple) test, we'll have to ensure the deployment target is correct. This would have been needed for a release anyway, but can be removed once meson-python 0.16.0 is out. --- .github/workflows/cibuildwheel.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 8a2f8b26a8d4..1c3837f25b44 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -116,10 +116,6 @@ jobs: CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- python {package}/ci/check_version_number.py - # Apple Silicon machines are not available for testing, so silence the - # warning from cibuildwheel. Remove the skip when they're available. - CIBW_TEST_SKIP: "*-macosx_arm64" - MACOSX_DEPLOYMENT_TARGET: "10.12" MPL_DISABLE_FH4: "yes" strategy: matrix: @@ -131,7 +127,15 @@ jobs: - os: windows-latest cibw_archs: "auto64" - os: macos-11 - cibw_archs: "x86_64 arm64" + cibw_archs: "x86_64" + # NOTE: macos_target can be moved back into global environment after + # meson-python 0.16.0 is released. + macos_target: "10.12" + - os: macos-14 + cibw_archs: "arm64" + # NOTE: macos_target can be moved back into global environment after + # meson-python 0.16.0 is released. + macos_target: "11.0" steps: - name: Set up QEMU @@ -153,6 +157,7 @@ jobs: env: CIBW_BUILD: "cp312-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} + MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.11 uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 @@ -161,6 +166,7 @@ jobs: env: CIBW_BUILD: "cp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} + MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.10 uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 @@ -169,6 +175,7 @@ jobs: env: CIBW_BUILD: "cp310-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} + MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.9 uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 @@ -177,6 +184,7 @@ jobs: env: CIBW_BUILD: "cp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} + MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for PyPy uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 @@ -185,6 +193,7 @@ jobs: env: CIBW_BUILD: "pp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} + MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" if: matrix.cibw_archs != 'aarch64' - uses: actions/upload-artifact@v4 From 89e5bd4466c50963cf0da9a661e4b68e37939a8c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 1 Apr 2024 14:58:20 +0200 Subject: [PATCH 0906/1120] Remove documentation that some backends don't support hatching. Even the builtin cairo backend supports it now. --- lib/matplotlib/collections.py | 3 --- lib/matplotlib/contour.py | 2 -- lib/matplotlib/patches.py | 3 --- lib/matplotlib/tri/_tricontour.py | 2 -- 4 files changed, 10 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index fb137cc503e1..b29f69606d9d 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -524,9 +524,6 @@ def set_hatch(self, hatch): hatchings are done. If same letter repeats, it increases the density of hatching of that pattern. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. - Unlike other properties such as linewidth and colors, hatching can only be specified for the collection as a whole, not separately for each member. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 90167bd320e8..0e6068c64b62 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1808,8 +1808,6 @@ def _initialize_x_y(self, z): A list of cross hatch patterns to use on the filled areas. If None, no hatching will be added to the contour. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional Which contouring algorithm to use to calculate the contour lines and diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 65e3822e5d02..2899952634a9 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -558,9 +558,6 @@ def set_hatch(self, hatch): hatchings are done. If same letter repeats, it increases the density of hatching of that pattern. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. - Parameters ---------- hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} diff --git a/lib/matplotlib/tri/_tricontour.py b/lib/matplotlib/tri/_tricontour.py index 5dc1eb81593c..1db3715d01af 100644 --- a/lib/matplotlib/tri/_tricontour.py +++ b/lib/matplotlib/tri/_tricontour.py @@ -255,8 +255,6 @@ def tricontourf(ax, *args, **kwargs): hatches : list[str], optional A list of crosshatch patterns to use on the filled areas. If None, no hatching will be added to the contour. - Hatching is supported in the PostScript, PDF, SVG and Agg - backends only. Notes ----- From e91241e26f374fa399517c75e2d9d0f343444c73 Mon Sep 17 00:00:00 2001 From: lkkmpn Date: Mon, 1 Apr 2024 15:35:52 +0200 Subject: [PATCH 0907/1120] Fix color sequence data for Set2 and Set3 --- lib/matplotlib/colors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 2c8f48623b8c..c4e5987fdf92 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -127,8 +127,8 @@ class ColorSequenceRegistry(Mapping): 'Accent': _cm._Accent_data, 'Dark2': _cm._Dark2_data, 'Set1': _cm._Set1_data, - 'Set2': _cm._Set1_data, - 'Set3': _cm._Set1_data, + 'Set2': _cm._Set2_data, + 'Set3': _cm._Set3_data, } def __init__(self): From e5eb0e5338f56a67774867716150d3e9fa095b4a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:39:29 +0000 Subject: [PATCH 0908/1120] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0) - [github.com/pycqa/flake8: 6.1.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0) - [github.com/adrienverge/yamllint: v1.29.0 → v1.35.1](https://github.com/adrienverge/yamllint/compare/v1.29.0...v1.35.1) - [github.com/python-jsonschema/check-jsonschema: 0.28.0 → 0.28.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.0...0.28.1) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38fbae18c920..2dc1ca5352c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: - id: trailing-whitespace exclude_types: [svg] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.9.0 hooks: - id: mypy additional_dependencies: @@ -42,7 +42,7 @@ repos: files: lib/matplotlib # Only run when files in lib/matplotlib are changed. pass_filenames: false - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 additional_dependencies: @@ -74,12 +74,12 @@ repos: - sphinx>=1.8.1 - tomli - repo: https://github.com/adrienverge/yamllint - rev: v1.29.0 + rev: v1.35.1 hooks: - id: yamllint args: ["--strict", "--config-file=.yamllint.yml"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.0 + rev: 0.28.1 hooks: # TODO: Re-enable this when https://github.com/microsoft/azure-pipelines-vscode/issues/567 is fixed. # - id: check-azure-pipelines From 0ca3e594939916ee8c2f749c9b3e085cd13ef6b8 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Sun, 4 Feb 2024 20:18:28 -0500 Subject: [PATCH 0909/1120] Move doc/users/installing/ to doc/install/ --- doc/{users/installing => install}/dependencies.rst | 0 doc/{users/installing => install}/environment_variables_faq.rst | 0 doc/{users/installing => install}/index.rst | 0 doc/{users/installing => install}/troubleshooting_faq.inc.rst | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename doc/{users/installing => install}/dependencies.rst (100%) rename doc/{users/installing => install}/environment_variables_faq.rst (100%) rename doc/{users/installing => install}/index.rst (100%) rename doc/{users/installing => install}/troubleshooting_faq.inc.rst (100%) diff --git a/doc/users/installing/dependencies.rst b/doc/install/dependencies.rst similarity index 100% rename from doc/users/installing/dependencies.rst rename to doc/install/dependencies.rst diff --git a/doc/users/installing/environment_variables_faq.rst b/doc/install/environment_variables_faq.rst similarity index 100% rename from doc/users/installing/environment_variables_faq.rst rename to doc/install/environment_variables_faq.rst diff --git a/doc/users/installing/index.rst b/doc/install/index.rst similarity index 100% rename from doc/users/installing/index.rst rename to doc/install/index.rst diff --git a/doc/users/installing/troubleshooting_faq.inc.rst b/doc/install/troubleshooting_faq.inc.rst similarity index 100% rename from doc/users/installing/troubleshooting_faq.inc.rst rename to doc/install/troubleshooting_faq.inc.rst From 5570d235ecea67b1122a86336e02c23c672e5644 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Sun, 4 Feb 2024 20:33:16 -0500 Subject: [PATCH 0910/1120] Add redirects --- doc/install/environment_variables_faq.rst | 1 + doc/install/index.rst | 1 + doc/install/troubleshooting_faq.inc.rst | 2 ++ 3 files changed, 4 insertions(+) diff --git a/doc/install/environment_variables_faq.rst b/doc/install/environment_variables_faq.rst index efbcd9980fd0..ba384343cc5a 100644 --- a/doc/install/environment_variables_faq.rst +++ b/doc/install/environment_variables_faq.rst @@ -3,6 +3,7 @@ .. redirect-from:: /faq/installing_faq .. redirect-from:: /users/faq/installing_faq +.. redirect-from:: /users/installing/environment_variables_faq ===================== Environment variables diff --git a/doc/install/index.rst b/doc/install/index.rst index fa5187081b2f..ea8e29d71565 100644 --- a/doc/install/index.rst +++ b/doc/install/index.rst @@ -1,4 +1,5 @@ .. redirect-from:: /users/installing +.. redirect-from:: /users/installing/index ************ Installation diff --git a/doc/install/troubleshooting_faq.inc.rst b/doc/install/troubleshooting_faq.inc.rst index 2314187a9542..d130813a80c6 100644 --- a/doc/install/troubleshooting_faq.inc.rst +++ b/doc/install/troubleshooting_faq.inc.rst @@ -1,5 +1,7 @@ .. _troubleshooting-install: +.. redirect-from:: /users/installing/troubleshooting_faq + Troubleshooting =============== From 41964fe88bf0c35c2d586a3bb6e1ca98c3a1ad77 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Wed, 13 Mar 2024 18:09:25 -0400 Subject: [PATCH 0911/1120] Add redirect for dependencies.rst --- doc/install/dependencies.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index fa666d280732..34eda8112cb8 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -1,4 +1,5 @@ .. redirect-from: /devel/dependencies +.. redirect-from:: /users/installing/dependencies.rst .. _dependencies: From 43c70c8b87d0843dbb3f186336269431e886d581 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Wed, 13 Mar 2024 18:14:36 -0400 Subject: [PATCH 0912/1120] Update relative path for doc-requirements.txt --- doc/install/dependencies.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index 34eda8112cb8..4a5d8c8dd4c4 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -392,7 +392,7 @@ The additional Python packages required to build the The content of :file:`doc-requirements.txt` is also shown below: -.. include:: ../../../requirements/doc/doc-requirements.txt +.. include:: ../../requirements/doc/doc-requirements.txt :literal: From 892a573ca6b2286abe6b5e0c0871bc49a4997474 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Wed, 13 Mar 2024 18:28:58 -0400 Subject: [PATCH 0913/1120] Change to --- INSTALL.rst | 2 +- README.md | 4 ++-- doc/api/prev_api_changes/api_changes_3.1.0.rst | 2 +- doc/devel/contribute.rst | 2 +- doc/devel/development_setup.rst | 2 +- doc/devel/document.rst | 6 +++--- doc/index.rst | 4 ++-- doc/users/getting_started/index.rst | 2 +- pyproject.toml | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index ac24c70ac518..3fb01c58d259 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1 +1 @@ -See doc/users/installing/index.rst +See doc/install/index.rst diff --git a/README.md b/README.md index 6751e9a56e35..78d4862800e5 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ and various graphical user interface toolkits. ## Install See the [install -documentation](https://matplotlib.org/stable/users/installing/index.html), -which is generated from `/doc/users/installing/index.rst` +documentation](https://matplotlib.org/stable/install/index.html), +which is generated from `/doc/install/index.rst` ## Contribute diff --git a/doc/api/prev_api_changes/api_changes_3.1.0.rst b/doc/api/prev_api_changes/api_changes_3.1.0.rst index 18f25d459200..5b06af781938 100644 --- a/doc/api/prev_api_changes/api_changes_3.1.0.rst +++ b/doc/api/prev_api_changes/api_changes_3.1.0.rst @@ -308,7 +308,7 @@ FreeType or libpng are not in the compiler or linker's default path, set the standard environment variables ``CFLAGS``/``LDFLAGS`` on Linux or OSX, or ``CL``/``LINK`` on Windows, to indicate the relevant paths. -See details in :doc:`/users/installing/index`. +See details in :doc:`/install/index`. Setting artist properties twice or more in the same call ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 510b67eead91..8c297beaddc1 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -266,7 +266,7 @@ document's URL roughly corresponds to its location in our folder structure: .. grid-item:: information about the library - * :file:`doc/users/installing/` + * :file:`doc/install/` * :file:`doc/project/` * :file:`doc/users/resources/` * :file:`doc/users/faq.rst` diff --git a/doc/devel/development_setup.rst b/doc/devel/development_setup.rst index 5b68172c2fc9..be99bed2fe5f 100644 --- a/doc/devel/development_setup.rst +++ b/doc/devel/development_setup.rst @@ -165,7 +165,7 @@ must be installed separately. .. toctree:: :maxdepth: 2 - ../users/installing/dependencies + ../install/dependencies .. _development-install: diff --git a/doc/devel/document.rst b/doc/devel/document.rst index d354ee5f238b..620c12c8db1c 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -239,7 +239,7 @@ Examples: .. code-block:: rst - See the :doc:`/users/installing/index` + See the :doc:`/install/index` See the tutorial :ref:`quick_start` @@ -247,14 +247,14 @@ Examples: will render as: - See the :doc:`/users/installing/index` + See the :doc:`/install/index` See the tutorial :ref:`quick_start` See the example :doc:`/gallery/lines_bars_and_markers/simple_plot` Sections can also be given reference labels. For instance from the -:doc:`/users/installing/index` link: +:doc:`/install/index` link: .. code-block:: rst diff --git a/doc/index.rst b/doc/index.rst index 00814b5ccc71..1a385d2330af 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -34,10 +34,10 @@ Install .. toctree:: :maxdepth: 2 - users/installing/index + install/index For more detailed instructions, see the -:doc:`installation guide `. +:doc:`installation guide `. Learn ===== diff --git a/doc/users/getting_started/index.rst b/doc/users/getting_started/index.rst index 68d62232ddc9..ac896687979d 100644 --- a/doc/users/getting_started/index.rst +++ b/doc/users/getting_started/index.rst @@ -22,7 +22,7 @@ Installation quick-start conda install -c conda-forge matplotlib -Further details are available in the :doc:`Installation Guide `. +Further details are available in the :doc:`Installation Guide `. Draw a first plot diff --git a/pyproject.toml b/pyproject.toml index 8d3ff39fc38e..0f21ac360e13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ dev = [ [project.urls] "Homepage" = "https://matplotlib.org" -"Download" = "https://matplotlib.org/stable/users/installing/index.html" +"Download" = "https://matplotlib.org/stable/install/index.html" "Documentation" = "https://matplotlib.org" "Source Code" = "https://github.com/matplotlib/matplotlib" "Bug Tracker" = "https://github.com/matplotlib/matplotlib/issues" From 34b53336db303a391229552320df461629334227 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Wed, 13 Mar 2024 18:32:04 -0400 Subject: [PATCH 0914/1120] Retain link in README to stable version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78d4862800e5..7b9c99597c0d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ and various graphical user interface toolkits. ## Install See the [install -documentation](https://matplotlib.org/stable/install/index.html), +documentation](https://matplotlib.org/stable/users/installing/index.html), which is generated from `/doc/install/index.rst` ## Contribute From 064acbbdf78f114a442294136d2e8f54f5ed1775 Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Thu, 14 Mar 2024 00:32:59 -0400 Subject: [PATCH 0915/1120] Empty commit to trigger tests From 61a85555813f499ff8b2e744b787ee9eeff947ed Mon Sep 17 00:00:00 2001 From: shriyakalakata Date: Thu, 14 Mar 2024 01:25:17 -0400 Subject: [PATCH 0916/1120] Update file path in users/index.rst --- doc/users/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/index.rst b/doc/users/index.rst index eacb72749f81..2991e7d2b324 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -102,4 +102,4 @@ Using Matplotlib :hidden: getting_started/index - installing/index + ../install/index From 07abbf24e162e7d3a0e205c629449aa80287e36e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 22 Feb 2024 22:02:33 -0500 Subject: [PATCH 0917/1120] Fix deprecation warnings in ft2font extension --- src/ft2font_wrapper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 25b2d173f5e9..0fdb0165b462 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -73,8 +73,8 @@ static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args) { char const* msg = "FT2Image.draw_rect is deprecated since Matplotlib 3.8 and will be removed " - "in Matplotlib 3.10 releases later as it is not used in the library. " - "If you rely on it, please let us know."; + "in Matplotlib 3.10 as it is not used in the library. If you rely on it, " + "please let us know."; if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { return NULL; } @@ -838,8 +838,8 @@ const char *PyFT2Font_get_xys__doc__ = static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds) { char const* msg = - "FT2Font.get_xys is deprecated since Matplotlib 3.8 and will be removed two " - "meso releases later as it is not used in the library. If you rely on it, " + "FT2Font.get_xys is deprecated since Matplotlib 3.8 and will be removed in " + "Matplotlib 3.10 as it is not used in the library. If you rely on it, " "please let us know."; if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) { return NULL; From bce36433f20a0539b5301b4244e913d9c28ffed4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 2 Apr 2024 04:16:03 -0400 Subject: [PATCH 0918/1120] wx: Fix file extension for toolmanager-style toolbar Fixes AppVeyor failure noted in https://github.com/matplotlib/matplotlib/pull/26710#issuecomment-2031349546 --- lib/matplotlib/backends/backend_wx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 0ae70707ac0d..7db9d686baae 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1161,6 +1161,8 @@ def set_history_buttons(self): # tools for matplotlib.backend_managers.ToolManager: class ToolbarWx(ToolContainerBase, wx.ToolBar): + _icon_extension = '.svg' + def __init__(self, toolmanager, parent=None, style=wx.TB_BOTTOM): if parent is None: parent = toolmanager.canvas.GetParent() From ae7f7697b264d204850e08a06e36eb53dccfd637 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 2 Apr 2024 14:06:46 +0200 Subject: [PATCH 0919/1120] Make Grouper return siblings in the order in which they have been seen. --- lib/matplotlib/cbook.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ee043f351dbb..a41bfe56744f 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -849,12 +849,18 @@ class Grouper: def __init__(self, init=()): self._mapping = weakref.WeakKeyDictionary( {x: weakref.WeakSet([x]) for x in init}) + self._ordering = weakref.WeakKeyDictionary() + for x in init: + if x not in self._ordering: + self._ordering[x] = len(self._ordering) + self._next_order = len(self._ordering) # Plain int to simplify pickling. def __getstate__(self): return { **vars(self), # Convert weak refs to strong ones. "_mapping": {k: set(v) for k, v in self._mapping.items()}, + "_ordering": {**self._ordering}, } def __setstate__(self, state): @@ -862,6 +868,7 @@ def __setstate__(self, state): # Convert strong refs to weak ones. self._mapping = weakref.WeakKeyDictionary( {k: weakref.WeakSet(v) for k, v in self._mapping.items()}) + self._ordering = weakref.WeakKeyDictionary(self._ordering) def __contains__(self, item): return item in self._mapping @@ -875,10 +882,19 @@ def join(self, a, *args): Join given arguments into the same set. Accepts one or more arguments. """ mapping = self._mapping - set_a = mapping.setdefault(a, weakref.WeakSet([a])) - + try: + set_a = mapping[a] + except KeyError: + set_a = mapping[a] = weakref.WeakSet([a]) + self._ordering[a] = self._next_order + self._next_order += 1 for arg in args: - set_b = mapping.get(arg, weakref.WeakSet([arg])) + try: + set_b = mapping[arg] + except KeyError: + set_b = mapping[arg] = weakref.WeakSet([arg]) + self._ordering[arg] = self._next_order + self._next_order += 1 if set_b is not set_a: if len(set_b) > len(set_a): set_a, set_b = set_b, set_a @@ -892,9 +908,8 @@ def joined(self, a, b): def remove(self, a): """Remove *a* from the grouper, doing nothing if it is not there.""" - set_a = self._mapping.pop(a, None) - if set_a: - set_a.remove(a) + self._mapping.pop(a, {a}).remove(a) + self._ordering.pop(a, None) def __iter__(self): """ @@ -904,12 +919,12 @@ def __iter__(self): """ unique_groups = {id(group): group for group in self._mapping.values()} for group in unique_groups.values(): - yield [x for x in group] + yield sorted(group, key=self._ordering.__getitem__) def get_siblings(self, a): """Return all of the items joined with *a*, including itself.""" siblings = self._mapping.get(a, [a]) - return [x for x in siblings] + return sorted(siblings, key=self._ordering.get) class GrouperView: From c5b2158e0d354b632d9ec4158cf25153fb267811 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 26 Mar 2023 23:45:00 +0200 Subject: [PATCH 0920/1120] Display cursor coordinates for all axes twinned with the current one. --- lib/matplotlib/axes/_base.py | 18 ++++++++++++++---- lib/matplotlib/tests/test_backend_bases.py | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 36baff85fa66..23cc1c869c07 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3976,10 +3976,20 @@ def format_ydata(self, y): def format_coord(self, x, y): """Return a format string formatting the *x*, *y* coordinates.""" - return "x={} y={}".format( - "???" if x is None else self.format_xdata(x), - "???" if y is None else self.format_ydata(y), - ) + twins = self._twinned_axes.get_siblings(self) + if len(twins) == 1: + return "(x, y) = ({}, {})".format( + "???" if x is None else self.format_xdata(x), + "???" if y is None else self.format_ydata(y)) + screen_xy = self.transData.transform((x, y)) + xy_strs = [] + # Retrieve twins in the order of self.figure.axes to sort tied zorders (which is + # the common case) by the order in which they are added to the figure. + for ax in sorted(twins, key=attrgetter("zorder")): + data_x, data_y = ax.transData.inverted().transform(screen_xy) + xy_strs.append( + "({}, {})".format(ax.format_xdata(data_x), ax.format_ydata(data_y))) + return "(x, y) = {}".format(" | ".join(xy_strs)) def minorticks_on(self): """ diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 399949c93bef..c264f01acdb2 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -1,5 +1,3 @@ -import re - from matplotlib import path, transforms from matplotlib.backend_bases import ( FigureCanvasBase, KeyEvent, LocationEvent, MouseButton, MouseEvent, @@ -123,11 +121,21 @@ def test_location_event_position(x, y): assert event.y == int(y) assert isinstance(event.y, int) if x is not None and y is not None: - assert re.match( - f"x={ax.format_xdata(x)} +y={ax.format_ydata(y)}", - ax.format_coord(x, y)) + assert (ax.format_coord(x, y) + == f"(x, y) = ({ax.format_xdata(x)}, {ax.format_ydata(y)})") ax.fmt_xdata = ax.fmt_ydata = lambda x: "foo" - assert re.match("x=foo +y=foo", ax.format_coord(x, y)) + assert ax.format_coord(x, y) == "(x, y) = (foo, foo)" + + +def test_location_event_position_twin(): + fig, ax = plt.subplots() + ax.set(xlim=(0, 10), ylim=(0, 20)) + assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00)" + ax.twinx().set(ylim=(0, 40)) + assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00) | (5.00, 10.0)" + ax.twiny().set(xlim=(0, 5)) + assert (ax.format_coord(5., 5.) + == "(x, y) = (5.00, 5.00) | (5.00, 10.0) | (2.50, 5.00)") def test_pick(): From 39f28ccfb806e1dd63c4e8665803db98d36455a6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Apr 2024 01:52:31 -0400 Subject: [PATCH 0921/1120] wx: Continue supporting non-SVG for toolmanager's sake This falls back to the previous dark-mode setup for any other formats. It won't be HiDPI compatible, but that's probably not possible for raster formats. --- lib/matplotlib/backends/backend_wx.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 7db9d686baae..cd2fe215464c 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -14,6 +14,9 @@ import sys import weakref +import numpy as np +import PIL.Image + import matplotlib as mpl from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, @@ -1071,7 +1074,6 @@ def _icon(name): *name*, including the extension and relative to Matplotlib's "images" data directory. """ - svg = cbook._get_data_path("images", name).read_bytes() try: dark = wx.SystemSettings.GetAppearance().IsDark() except AttributeError: # wxpython < 4.1 @@ -1082,10 +1084,24 @@ def _icon(name): bg_lum = (.299 * bg.red + .587 * bg.green + .114 * bg.blue) / 255 fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255 dark = fg_lum - bg_lum > .2 - if dark: - svg = svg.replace(b'fill:black;', b'fill:white;') - toolbarIconSize = wx.ArtProvider().GetDIPSizeHint(wx.ART_TOOLBAR) - return wx.BitmapBundle.FromSVG(svg, toolbarIconSize) + + path = cbook._get_data_path('images', name) + if path.suffix == '.svg': + svg = path.read_bytes() + if dark: + svg = svg.replace(b'fill:black;', b'fill:white;') + toolbarIconSize = wx.ArtProvider().GetDIPSizeHint(wx.ART_TOOLBAR) + return wx.BitmapBundle.FromSVG(svg, toolbarIconSize) + else: + pilimg = PIL.Image.open(path) + # ensure RGBA as wx BitMap expects RGBA format + image = np.array(pilimg.convert("RGBA")) + if dark: + fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + black_mask = (image[..., :3] == 0).all(axis=-1) + image[black_mask, :3] = (fg.Red(), fg.Green(), fg.Blue()) + return wx.Bitmap.FromBufferRGBA( + image.shape[1], image.shape[0], image.tobytes()) def _update_buttons_checked(self): if "Pan" in self.wx_ids: From 92f89784094c89805008b4e715b20a8403141274 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 3 Apr 2024 11:27:28 +0200 Subject: [PATCH 0922/1120] Avoid plt.xticks/plt.yticks in gallery examples. In particular, the ticklabels_rotation example is likely the one most easily found by those looking for how to do this; let's not suggest that plt.xticks is "the" way to rotate ticklabels. Also remove the margins() call (which was really only needed with the old round_numbers autolimits mode) and the subplots_adjust() call (the ticklabels already fit in, and the modern approach would be to use constrained_layout anyways). --- .../subplots_axes_and_figures/secondary_axis.py | 2 +- galleries/examples/ticks/ticklabels_rotation.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/galleries/examples/subplots_axes_and_figures/secondary_axis.py b/galleries/examples/subplots_axes_and_figures/secondary_axis.py index 8ae40ce48c84..ab42d3a6c182 100644 --- a/galleries/examples/subplots_axes_and_figures/secondary_axis.py +++ b/galleries/examples/subplots_axes_and_figures/secondary_axis.py @@ -154,7 +154,7 @@ def inverse(x): ax.plot(dates, temperature) ax.set_ylabel(r'$T\ [^oC]$') -plt.xticks(rotation=70) +ax.xaxis.set_tick_params(rotation=70) def date2yday(x): diff --git a/galleries/examples/ticks/ticklabels_rotation.py b/galleries/examples/ticks/ticklabels_rotation.py index 216b15d2762c..94924d0440f5 100644 --- a/galleries/examples/ticks/ticklabels_rotation.py +++ b/galleries/examples/ticks/ticklabels_rotation.py @@ -5,17 +5,16 @@ Demo of custom tick-labels with user-defined rotation. """ + import matplotlib.pyplot as plt x = [1, 2, 3, 4] y = [1, 4, 9, 6] labels = ['Frogs', 'Hogs', 'Bogs', 'Slogs'] -plt.plot(x, y) +fig, ax = plt.subplots() +ax.plot(x, y) # You can specify a rotation for the tick labels in degrees or with keywords. -plt.xticks(x, labels, rotation='vertical') -# Pad margins so that markers don't get clipped by the Axes -plt.margins(0.2) -# Tweak spacing to prevent clipping of tick-labels -plt.subplots_adjust(bottom=0.15) +ax.set_xticks(x, labels, rotation='vertical') + plt.show() From d8815fea31d272af198c691863d58ea73c45628a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Apr 2024 15:40:28 -0400 Subject: [PATCH 0923/1120] wx: Fix the zoom rubberband with toolmanager --- lib/matplotlib/backends/backend_wx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index cd2fe215464c..8064511ac28a 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1152,7 +1152,7 @@ def save_figure(self, *args): def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height - sf = 1 if wx.Platform == '__WXMSW__' else self.GetDPIScaleFactor() + sf = 1 if wx.Platform == '__WXMSW__' else self.canvas.GetDPIScaleFactor() self.canvas._rubberband_rect = (x0/sf, (height - y0)/sf, x1/sf, (height - y1)/sf) self.canvas.Refresh() From 62bac20d2309f88452105116cfaa035422c912b5 Mon Sep 17 00:00:00 2001 From: Vashesh08 Date: Wed, 30 Aug 2023 18:16:15 +0530 Subject: [PATCH 0924/1120] Auto-escape % symbol in LaTeX in pie labels Fixes Issue #26377 - Adding Support For % operator in Latex Excaping The % Operator in LaTeX for the Pie method Update _axes.py Added usetex key in textprops dictionary in pie method Removing Flake Issues and Prioritizing textprops parameter Fixes % operator in Latex in Pie Method Removed Trailing Whitespace Removed Unnecessary Label Test in Pie Update lib/matplotlib/tests/test_axes.py Skip Test when latex is not available Co-authored-by: Kyle Sunden Update lib/matplotlib/tests/test_axes.py Update test_name for % operator in Latex in Pie Method Co-authored-by: Kyle Sunden Added Dependency for needs_usetex testing for checking Latex dependency Comment For Using Regex To Support % Symbol in Latex Comments added to escape % if not already escaped using regex Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/users/next_whats_new/pie_percent_latex.rst | 11 +++++++++++ lib/matplotlib/axes/_axes.py | 4 ++++ lib/matplotlib/tests/test_axes.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 doc/users/next_whats_new/pie_percent_latex.rst diff --git a/doc/users/next_whats_new/pie_percent_latex.rst b/doc/users/next_whats_new/pie_percent_latex.rst new file mode 100644 index 000000000000..7ed547302789 --- /dev/null +++ b/doc/users/next_whats_new/pie_percent_latex.rst @@ -0,0 +1,11 @@ +Percent sign in pie labels auto-escaped with ``usetex=True`` +------------------------------------------------------------ + +It is common, with `.Axes.pie`, to specify labels that include a percent sign +(``%``), which denotes a comment for LaTeX. When enabling LaTeX with +:rc:`text.usetex` or passing ``textprops={"usetex": True}``, this would cause +the percent sign to disappear. + +Now, the percent sign is automatically escaped (by adding a preceding +backslash) so that it appears regardless of the ``usetex`` setting. If you have +pre-escaped the percent sign, this will be detected, and remain as is. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index dc7f0c433fb4..b65004b8c272 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4,6 +4,7 @@ import math from numbers import Integral, Number, Real +import re import numpy as np from numpy import ma @@ -3377,6 +3378,9 @@ def get_next_color(): else: raise TypeError( 'autopct must be callable or a format string') + if mpl._val_or_rc(textprops.get("usetex"), "text.usetex"): + # escape % (i.e. \%) if it is not already escaped + s = re.sub(r"([^\\])%", r"\1\\%", s) t = self.text(xt, yt, s, clip_on=False, horizontalalignment='center', diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index deb83a26033c..819f4eb3b598 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -38,6 +38,7 @@ assert_allclose, assert_array_equal, assert_array_almost_equal) from matplotlib.testing.decorators import ( image_comparison, check_figures_equal, remove_ticks_and_titles) +from matplotlib.testing._markers import needs_usetex # Note: Some test cases are run twice: once normally and once with labeled data # These two must be defined in the same test function or need to have @@ -9008,3 +9009,16 @@ def test_boxplot_tick_labels(): # Test the new tick_labels parameter axs[1].boxplot(data, tick_labels=['A', 'B', 'C']) assert [l.get_text() for l in axs[1].get_xticklabels()] == ['A', 'B', 'C'] + + +@needs_usetex +@check_figures_equal() +def test_latex_pie_percent(fig_test, fig_ref): + + data = [20, 10, 70] + + ax = fig_test.subplots() + ax.pie(data, autopct="%1.0f%%", textprops={'usetex': True}) + + ax1 = fig_ref.subplots() + ax1.pie(data, autopct=r"%1.0f\%%", textprops={'usetex': True}) From daa1ca992ab46919ab88f06eb9c99210c58cc279 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 3 Apr 2024 17:38:38 -0500 Subject: [PATCH 0925/1120] Bump from v3.8.4 tag From 4dcfcf8b7bffe9a92fdd5e42761208e4557a1add Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 3 Apr 2024 19:33:39 -0500 Subject: [PATCH 0926/1120] Zenodo for v3.8.4 --- doc/_static/zenodo_cache/10916799.svg | 35 +++++++++++++++++++++++++++ doc/users/project/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/10916799.svg diff --git a/doc/_static/zenodo_cache/10916799.svg b/doc/_static/zenodo_cache/10916799.svg new file mode 100644 index 000000000000..ca9c0a454251 --- /dev/null +++ b/doc/_static/zenodo_cache/10916799.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.10916799 + + + 10.5281/zenodo.10916799 + + + \ No newline at end of file diff --git a/doc/users/project/citing.rst b/doc/users/project/citing.rst index e162a427d8b4..87d30ee4cb67 100644 --- a/doc/users/project/citing.rst +++ b/doc/users/project/citing.rst @@ -29,6 +29,9 @@ By version .. START OF AUTOGENERATED +v3.8.4 + .. image:: ../../_static/zenodo_cache/10916799.svg + :target: https://doi.org/10.5281/zenodo.10916799 v3.8.3 .. image:: ../../_static/zenodo_cache/10661079.svg :target: https://doi.org/10.5281/zenodo.10661079 diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index b838511fe3b0..6a5dd01ed3c5 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,7 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.8.4": "10916799", "v3.8.3": "10661079", "v3.8.2": "10150955", "v3.8.1": "10059757", From 9411d7fd4ace7d40e1b8382ba851951bf6481365 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Thu, 4 Apr 2024 13:57:47 +0200 Subject: [PATCH 0927/1120] DOC: correct path to mpl_toolkits reference images --- doc/devel/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/testing.rst b/doc/devel/testing.rst index 46bcec91ac19..a9f52f0e62b6 100644 --- a/doc/devel/testing.rst +++ b/doc/devel/testing.rst @@ -315,7 +315,7 @@ The correct target folder can be found using:: python -c "import matplotlib.tests; print(matplotlib.tests.__file__.rsplit('/', 1)[0])" -An analogous copying of :file:`lib/mpl_toolkits/tests/baseline_images` +An analogous copying of :file:`lib/mpl_toolkits/*/tests/baseline_images` is necessary for testing ``mpl_toolkits``. Run the tests From 0aa101e87f8412cb5be32d7b1138bca5912b8e35 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 3 Apr 2024 11:51:29 +0200 Subject: [PATCH 0928/1120] Improve timeline example. - Make the timeline vertical, which avoids many overlaps between labels and lines. - Sort releases by dates, so that the oldest release starts with a long stemline rather than some random length depending on how many releases there are. - Make minor releases fainter, which improves the hierarchical impression. - Switch to yearly date labels, which are enough for this timeline. --- .../lines_bars_and_markers/timeline.py | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/timeline.py b/galleries/examples/lines_bars_and_markers/timeline.py index f94f00799caa..93b98a403620 100644 --- a/galleries/examples/lines_bars_and_markers/timeline.py +++ b/galleries/examples/lines_bars_and_markers/timeline.py @@ -28,22 +28,19 @@ data = json.loads(urllib.request.urlopen(url, timeout=1).read().decode()) dates = [] - names = [] + releases = [] for item in data: if 'rc' not in item['tag_name'] and 'b' not in item['tag_name']: dates.append(item['published_at'].split("T")[0]) - names.append(item['tag_name']) - # Convert date strings (e.g. 2014-10-18) to datetime - dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] + releases.append(item['tag_name'].lstrip("v")) except Exception: # In case the above fails, e.g. because of missing internet connection # use the following lists as fallback. - names = ['v2.2.4', 'v3.0.3', 'v3.0.2', 'v3.0.1', 'v3.0.0', 'v2.2.3', - 'v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', - 'v2.0.2', 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', - 'v1.5.0', 'v1.4.3', 'v1.4.2', 'v1.4.1', 'v1.4.0'] - + releases = ['2.2.4', '3.0.3', '3.0.2', '3.0.1', '3.0.0', '2.2.3', + '2.2.2', '2.2.1', '2.2.0', '2.1.2', '2.1.1', '2.1.0', + '2.0.2', '2.0.1', '2.0.0', '1.5.3', '1.5.2', '1.5.1', + '1.5.0', '1.4.3', '1.4.2', '1.4.1', '1.4.0'] dates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10', '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16', '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07', @@ -51,8 +48,8 @@ '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16', '2014-10-26', '2014-10-18', '2014-08-26'] - # Convert date strings (e.g. 2014-10-18) to datetime - dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] +dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] # Convert strs to dates. +dates, releases = zip(*sorted(zip(dates, releases))) # Sort by increasing date. # %% # Next, we'll create a stem plot with some variation in levels as to @@ -64,32 +61,45 @@ # # Note that Matplotlib will automatically plot datetime inputs. - -# Choose some nice levels -levels = np.tile([-5, 5, -3, 3, -1, 1], - int(np.ceil(len(dates)/6)))[:len(dates)] - -# Create figure and plot a stem plot with the date +# Choose some nice levels: alternate minor releases between top and bottom, and +# progressievly shorten the stems for bugfix releases. +levels = [] +major_minor_releases = sorted({release[:3] for release in releases}) +for release in releases: + major_minor = release[:3] + bugfix = int(release[4]) + h = 1 + 0.8 * (5 - bugfix) + level = h if major_minor_releases.index(major_minor) % 2 == 0 else -h + levels.append(level) + +# The figure and the axes. fig, ax = plt.subplots(figsize=(8.8, 4), layout="constrained") ax.set(title="Matplotlib release dates") -ax.vlines(dates, 0, levels, color="tab:red") # The vertical stems. -ax.plot(dates, np.zeros_like(dates), "-o", - color="k", markerfacecolor="w") # Baseline and markers on it. - -# annotate lines -for d, l, r in zip(dates, levels, names): - ax.annotate(r, xy=(d, l), - xytext=(-3, np.sign(l)*3), textcoords="offset points", - horizontalalignment="right", - verticalalignment="bottom" if l > 0 else "top") - -# format x-axis with 4-month intervals -ax.xaxis.set_major_locator(mdates.MonthLocator(interval=4)) -ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y")) -plt.setp(ax.get_xticklabels(), rotation=30, ha="right") - -# remove y-axis and spines +# The vertical stems. +ax.vlines(dates, 0, levels, + color=[("tab:red", 1 if release.endswith(".0") else .5) + for release in releases]) +# The baseline. +ax.axhline(0, c="black") +# The markers on the baseline. +minor_dates = [date for date, release in zip(dates, releases) if release[-1] == '0'] +bugfix_dates = [date for date, release in zip(dates, releases) if release[-1] != '0'] +ax.plot(bugfix_dates, np.zeros_like(bugfix_dates), "ko", mfc="white") +ax.plot(minor_dates, np.zeros_like(minor_dates), "ko", mfc="tab:red") + +# Annotate the lines. +for date, level, release in zip(dates, levels, releases): + ax.annotate(release, xy=(date, level), + xytext=(-3, np.sign(level)*3), textcoords="offset points", + verticalalignment="bottom" if level > 0 else "top", + weight="bold" if release.endswith(".0") else "normal", + bbox=dict(boxstyle='square', pad=0, lw=0, fc=(1, 1, 1, 0.7))) + +ax.yaxis.set(major_locator=mdates.YearLocator(), + major_formatter=mdates.DateFormatter("%Y")) + +# Remove the y-axis and some spines. ax.yaxis.set_visible(False) ax.spines[["left", "top", "right"]].set_visible(False) From ecf1553b4a520cbea42bd44d429648130e5d9597 Mon Sep 17 00:00:00 2001 From: trananso Date: Tue, 19 Mar 2024 16:11:22 -0400 Subject: [PATCH 0929/1120] Implement align_titles based on align_xlabels --- lib/matplotlib/axes/_base.py | 9 +++-- lib/matplotlib/figure.py | 66 ++++++++++++++++++++++++++++++++---- lib/matplotlib/figure.pyi | 1 + 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 23cc1c869c07..0164f4e11169 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2985,8 +2985,13 @@ def _update_title_position(self, renderer): titles = (self.title, self._left_title, self._right_title) - # Need to check all our twins too, and all the children as well. - axs = self._twinned_axes.get_siblings(self) + self.child_axes + # Need to check all our twins too, aligned axes, and all the children + # as well. + axs = set() + axs.update(self.child_axes) + axs.update(self._twinned_axes.get_siblings(self)) + axs.update(self.figure._align_label_groups['title'].get_siblings(self)) + for ax in self.child_axes: # Child positions must be updated first. locator = ax.get_axes_locator() ax.apply_aspect(locator(self, renderer) if locator else None) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 087c193d48c3..fe16997e2131 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -132,10 +132,15 @@ def __init__(self, **kwargs): self._supxlabel = None self._supylabel = None - # groupers to keep track of x and y labels we want to align. - # see self.align_xlabels and self.align_ylabels and - # axis._get_tick_boxes_siblings - self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} + # groupers to keep track of x, y labels and title we want to + # align. + # see self.align_xlabels, self.align_ylabels, + # self.align_titles, and axis._get_tick_boxes_siblings + self._align_label_groups = { + "x": cbook.Grouper(), + "y": cbook.Grouper(), + "title": cbook.Grouper() + } self._localaxes = [] # track all Axes self.artists = [] @@ -1293,7 +1298,7 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None, def align_xlabels(self, axs=None): """ - Align the xlabels of subplots in the same subplot column if label + Align the xlabels of subplots in the same subplot row if label alignment is being done automatically (i.e. the label position is not manually set). @@ -1314,6 +1319,7 @@ def align_xlabels(self, axs=None): See Also -------- matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_titles matplotlib.figure.Figure.align_labels Notes @@ -1375,6 +1381,7 @@ def align_ylabels(self, axs=None): See Also -------- matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_titles matplotlib.figure.Figure.align_labels Notes @@ -1412,6 +1419,53 @@ def align_ylabels(self, axs=None): # grouper for groups of ylabels to align self._align_label_groups['y'].join(ax, axc) + def align_titles(self, axs=None): + """ + Align the titles of subplots in the same subplot row if title + alignment is being done automatically (i.e. the title position is + not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or ndarray) `~matplotlib.axes.Axes` + to align the titles. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that ``axs`` are from the same `.GridSpec`, so that + their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with titles:: + + fig, axs = plt.subplots(1, 2) + axs[0].set_aspect('equal') + axs[0].set_title('Title 0') + axs[1].set_title('Title 1') + fig.align_titles() + """ + if axs is None: + axs = self.axes + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] + for ax in axs: + _log.debug(' Working on: %s', ax.get_title()) + rowspan = ax.get_subplotspec().rowspan + for axc in axs: + rowspanc = axc.get_subplotspec().rowspan + if (rowspan.start == rowspanc.start): + self._align_label_groups['title'].join(ax, axc) + def align_labels(self, axs=None): """ Align the xlabels and ylabels of subplots with the same subplots @@ -1430,8 +1484,8 @@ def align_labels(self, axs=None): See Also -------- matplotlib.figure.Figure.align_xlabels - matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_titles """ self.align_xlabels(axs=axs) self.align_ylabels(axs=axs) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index 687ae9e500d0..eae21c2614f0 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -161,6 +161,7 @@ class FigureBase(Artist): ) -> None: ... def align_xlabels(self, axs: Iterable[Axes] | None = ...) -> None: ... def align_ylabels(self, axs: Iterable[Axes] | None = ...) -> None: ... + def align_titles(self, axs: Iterable[Axes] | None = ...) -> None: ... def align_labels(self, axs: Iterable[Axes] | None = ...) -> None: ... def add_gridspec(self, nrows: int = ..., ncols: int = ..., **kwargs) -> GridSpec: ... @overload From 2ff72be64c65750b4f42706136e66f590f33e097 Mon Sep 17 00:00:00 2001 From: trananso Date: Wed, 20 Mar 2024 11:25:22 -0400 Subject: [PATCH 0930/1120] Add documentation for align_titles - Add align_titles to api doc --- doc/api/figure_api.rst | 2 + .../next_whats_new/figure_align_titles.rst | 7 ++++ .../align_labels_demo.py | 38 +++++++++++-------- lib/matplotlib/figure.py | 3 +- 4 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 doc/users/next_whats_new/figure_align_titles.rst diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 937020afd8fc..2371e5a9a863 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -71,6 +71,7 @@ Annotating Figure.align_labels Figure.align_xlabels Figure.align_ylabels + Figure.align_titles Figure.autofmt_xdate @@ -264,6 +265,7 @@ Annotating SubFigure.align_labels SubFigure.align_xlabels SubFigure.align_ylabels + SubFigure.align_titles Adding and getting Artists -------------------------- diff --git a/doc/users/next_whats_new/figure_align_titles.rst b/doc/users/next_whats_new/figure_align_titles.rst new file mode 100644 index 000000000000..230e5f0a8990 --- /dev/null +++ b/doc/users/next_whats_new/figure_align_titles.rst @@ -0,0 +1,7 @@ +subplot titles can now be automatically aligned +----------------------------------------------- + +Subplot axes titles can be misaligned vertically if tick labels or +xlabels are placed at the top of one subplot. The new method on the +`.Figure` class: `.Figure.align_titles` will now align the titles +vertically. diff --git a/galleries/examples/subplots_axes_and_figures/align_labels_demo.py b/galleries/examples/subplots_axes_and_figures/align_labels_demo.py index 88f443ca0076..ee7cac7be742 100644 --- a/galleries/examples/subplots_axes_and_figures/align_labels_demo.py +++ b/galleries/examples/subplots_axes_and_figures/align_labels_demo.py @@ -1,37 +1,43 @@ """ -=============== -Aligning Labels -=============== +========================== +Aligning Labels and Titles +========================== -Aligning xlabel and ylabel using `.Figure.align_xlabels` and -`.Figure.align_ylabels` +Aligning xlabel, ylabel, and title using `.Figure.align_xlabels`, +`.Figure.align_ylabels`, and `.Figure.align_titles`. -`.Figure.align_labels` wraps these two functions. +`.Figure.align_labels` wraps the x and y label functions. Note that the xlabel "XLabel1 1" would normally be much closer to the -x-axis, and "YLabel1 0" would be much closer to the y-axis of their -respective axes. +x-axis, "YLabel0 0" would be much closer to the y-axis, and title +"Title0 0" would be much closer to the top of their respective axes. """ import matplotlib.pyplot as plt import numpy as np -import matplotlib.gridspec as gridspec +fig, axs = plt.subplots(2, 2, layout='tight') -fig = plt.figure(tight_layout=True) -gs = gridspec.GridSpec(2, 2) - -ax = fig.add_subplot(gs[0, :]) +ax = axs[0][0] ax.plot(np.arange(0, 1e6, 1000)) -ax.set_ylabel('YLabel0') -ax.set_xlabel('XLabel0') +ax.set_title('Title0 0') +ax.set_ylabel('YLabel0 0') + +ax = axs[0][1] +ax.plot(np.arange(1., 0., -0.1) * 2000., np.arange(1., 0., -0.1)) +ax.set_title('Title0 1') +ax.xaxis.tick_top() +ax.tick_params(axis='x', rotation=55) + for i in range(2): - ax = fig.add_subplot(gs[1, i]) + ax = axs[1][i] ax.plot(np.arange(1., 0., -0.1) * 2000., np.arange(1., 0., -0.1)) ax.set_ylabel('YLabel1 %d' % i) ax.set_xlabel('XLabel1 %d' % i) if i == 0: ax.tick_params(axis='x', rotation=55) + fig.align_labels() # same as fig.align_xlabels(); fig.align_ylabels() +fig.align_titles() plt.show() diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index fe16997e2131..0a0ff01a2571 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -132,8 +132,7 @@ def __init__(self, **kwargs): self._supxlabel = None self._supylabel = None - # groupers to keep track of x, y labels and title we want to - # align. + # groupers to keep track of x, y labels and title we want to align. # see self.align_xlabels, self.align_ylabels, # self.align_titles, and axis._get_tick_boxes_siblings self._align_label_groups = { From 1dee172aefe6abd53a257fe3fc130baa5cfab37f Mon Sep 17 00:00:00 2001 From: trananso Date: Wed, 20 Mar 2024 14:00:17 -0400 Subject: [PATCH 0931/1120] Add unit test for align_titles --- .../figure_align_titles_constrained.png | Bin 0 -> 35117 bytes .../test_figure/figure_align_titles_tight.png | Bin 0 -> 33777 bytes lib/matplotlib/tests/test_figure.py | 26 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_constrained.png new file mode 100644 index 0000000000000000000000000000000000000000..78dffc18e20c987fd149ae3c6f8d17c25117e547 GIT binary patch literal 35117 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_titles_tight.png new file mode 100644 index 0000000000000000000000000000000000000000..f719ae6931f0b0bf9b17cf6964e0fed8ef818c8c GIT binary patch literal 33777 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

iIA^e6@czY%A*iWM-i>t(CBBAReBcNB1y77 zT395oU(R}h4`(+Zh*K*x#(+?}cQ}uaIMb;e9c(o2M7QweoE!tDOBa+-FA)m&B$}~= z5p2gM^*n6aoG4Mc1rDUp4@)=ZB*gO1%Wop-PJH+%#T542nVe&K&&(2!C=F!uqJm4)%m6f-)hGIr?605V0!3D2FuJkQNy+435!)RtU z3L+NU$eqJ(>v=>}Gk0%%RFoiubn!o23PIiH24IjZ6QGc(W1;RT!SO=t=^Hq$nFZa9 zYp{rriJsNIed45H6qh0?IyKEqK$D4$`pWtfElgw&j*Sf3%j}QoV3+?g+B6aj@P9$nz(z-F_Z(DU}S7eVYJZxq`UO8W$Nfg$KD56gx8kdrsM)K)W%{0 z(|kpJJqI3GYM~shdv&rN5Wi5nVhok1_a=zDkiQdDHrbdU6NkUvzYoU&{`$8fmeDeg xUNs7T)J~NRF6LQJJz`o+m4{LE|L6bEU2OUy^2g0Sl#)=?#`T*uv(zn*{}1D0 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 8ee6aae99361..ee9c54d18159 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -66,6 +66,32 @@ def test_align_labels(): fig.align_labels() +@image_comparison(['figure_align_titles_tight.png', + 'figure_align_titles_constrained.png'], + tol=0 if platform.machine() == 'x86_64' else 0.01, + style='mpl20') +def test_align_titles(): + for layout in ['tight', 'constrained']: + fig, axs = plt.subplots(1, 2, layout=layout, width_ratios=[2, 1]) + + ax = axs[0] + ax.plot(np.arange(0, 1e6, 1000)) + ax.set_title('Title0 left', loc='left') + ax.set_title('Title0 center', loc='center') + ax.set_title('Title0 right', loc='right') + + ax = axs[1] + ax.plot(np.arange(0, 1e4, 100)) + ax.set_title('Title1') + ax.set_xlabel('Xlabel0') + ax.xaxis.set_label_position("top") + ax.xaxis.tick_top() + for tick in ax.get_xticklabels(): + tick.set_rotation(90) + + fig.align_titles() + + def test_align_labels_stray_axes(): fig, axs = plt.subplots(2, 2) for nn, ax in enumerate(axs.flat): From c947700796ed0351bde362c16771d78d82196ff0 Mon Sep 17 00:00:00 2001 From: trananso Date: Wed, 3 Apr 2024 14:00:50 -0400 Subject: [PATCH 0932/1120] Switch gallery layout from tight to constrained --- .../examples/subplots_axes_and_figures/align_labels_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/subplots_axes_and_figures/align_labels_demo.py b/galleries/examples/subplots_axes_and_figures/align_labels_demo.py index ee7cac7be742..4935878ee027 100644 --- a/galleries/examples/subplots_axes_and_figures/align_labels_demo.py +++ b/galleries/examples/subplots_axes_and_figures/align_labels_demo.py @@ -15,7 +15,7 @@ import matplotlib.pyplot as plt import numpy as np -fig, axs = plt.subplots(2, 2, layout='tight') +fig, axs = plt.subplots(2, 2, layout='constrained') ax = axs[0][0] ax.plot(np.arange(0, 1e6, 1000)) From 0be9cca9683f7ca632f0ed35bf380d73b3d412c0 Mon Sep 17 00:00:00 2001 From: trananso Date: Thu, 4 Apr 2024 11:11:50 -0400 Subject: [PATCH 0933/1120] Increase error tolerance on image test to 0.022 --- lib/matplotlib/tests/test_figure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index ee9c54d18159..58aecd3dea8b 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -68,7 +68,7 @@ def test_align_labels(): @image_comparison(['figure_align_titles_tight.png', 'figure_align_titles_constrained.png'], - tol=0 if platform.machine() == 'x86_64' else 0.01, + tol=0 if platform.machine() == 'x86_64' else 0.022, style='mpl20') def test_align_titles(): for layout in ['tight', 'constrained']: From aa9e209eb278691d73b929a3386a35134f333823 Mon Sep 17 00:00:00 2001 From: shriyakalakata <87483933+shriyakalakata@users.noreply.github.com> Date: Thu, 4 Apr 2024 15:49:11 -0400 Subject: [PATCH 0934/1120] Update doc/install/dependencies.rst Co-authored-by: Elliott Sales de Andrade --- doc/install/dependencies.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index 4a5d8c8dd4c4..45dc249832ca 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -1,5 +1,5 @@ -.. redirect-from: /devel/dependencies -.. redirect-from:: /users/installing/dependencies.rst +.. redirect-from:: /devel/dependencies +.. redirect-from:: /users/installing/dependencies .. _dependencies: From 36965acc863a7303478a1a8a4e9461e4f26807c1 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:43:57 +0200 Subject: [PATCH 0935/1120] DOC: Move bug reports and feature requests to top of contributing index Partially adresses #28005. The two sections "Report a bug" and "Request a feature" were burried inside of contribute.rst. They were a bit foreign there because all other content is on direct involvement with the project, which needs much more information. To keep things focussed and directly redirect the "simple" bug report and feature request topics to GitHub, I've deleted the sections and instead created two cards at the top of devel/index.rst with essentially the content of the sections. Note that I've the list of things to include in bug reports because that's covered by our issue template on GitHub nowadays and does not need repetition here. Co-authored-by: hannah --- doc/devel/contribute.rst | 49 ----------------------------- doc/devel/index.rst | 68 ++++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 510b67eead91..4c6934b25fc4 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -163,55 +163,6 @@ a new PR; duplicate PRs are subject to being closed. However, if the existing PR is an outline, unlikely to work, or stalled, and the original author is unresponsive, feel free to open a new PR referencing the old one. -.. _submitting-a-bug-report: - -Submit a bug report -=================== - -If you find a bug in the code or documentation, do not hesitate to submit a -ticket to the -`Issue Tracker `_. You are -also welcome to post feature requests or pull requests. - -If you are reporting a bug, please do your best to include the following: - -#. A short, top-level summary of the bug. In most cases, this should be 1-2 - sentences. - -#. A short, self-contained code snippet to reproduce the bug, ideally allowing - a simple copy and paste to reproduce. Please do your best to reduce the code - snippet to the minimum required. - -#. The actual outcome of the code snippet. - -#. The expected outcome of the code snippet. - -#. The Matplotlib version, Python version and platform that you are using. You - can grab the version with the following commands:: - - >>> import matplotlib - >>> matplotlib.__version__ - '3.4.1' - >>> import platform - >>> platform.python_version() - '3.9.2' - -We have preloaded the issue creation page with a Markdown form that you can -use to organize this information. - -Thank you for your help in keeping bug reports complete, targeted and descriptive. - -.. _request-a-new-feature: - -Request a new feature -===================== - -Please post feature requests to the -`Issue Tracker `_. - -The Matplotlib developers will give feedback on the feature proposal. Since -Matplotlib is an open source project with limited resources, we encourage -users to then also :ref:`participate in the implementation `. .. _contribute_code: diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 3ddbcba198a0..3525510cad18 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -13,10 +13,52 @@ Contribute of this document as it will have the most up to date installation instructions, workflow process, and contributing guidelines. -Thank you for your interest in helping to improve Matplotlib! There are various -ways to contribute: optimizing and refactoring code, detailing unclear -documentation and writing new examples, reporting and fixing bugs and requesting -and implementing new features, helping the community... +:octicon:`heart;1em;sd-text-info` Thank you for your interest in helping to improve +Matplotlib! :octicon:`heart;1em;sd-text-info` + +.. _submitting-a-bug-report: +.. _request-a-new-feature: + +.. grid:: 1 1 2 2 + + .. grid-item-card:: + :class-header: sd-fs-5 + + :octicon:`bug;1em;sd-text-info` **Submit a bug report** + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + We have preloaded the issue creation page with a Markdown form that you can + use to provide relevant context. Thank you for your help in keeping bug reports + complete, targeted and descriptive. + + .. button-link:: https://github.com/matplotlib/matplotlib/issues/new/choose + :expand: + :color: primary + + Report a bug + + .. grid-item-card:: + :class-header: sd-fs-5 + + :octicon:`light-bulb;1em;sd-text-info` **Request a new feature** + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + We will give feedback on the feature proposal. Since + Matplotlib is an open source project with limited resources, we encourage + users to then also :ref:`participate in the implementation `. + + .. button-link:: https://github.com/matplotlib/matplotlib/issues/new/choose + :expand: + :color: primary + + Request a feature + + +We welcome you to get more involved with the Matplotlib project. +There are various ways to contribute: +optimizing and refactoring code, detailing unclear documentation and writing new +examples, helping the community, reporting and fixing bugs and requesting and +implementing new features... New contributors ================ @@ -48,32 +90,26 @@ New contributors :class-row: sd-fs-5 .. grid-item-card:: - :link: request-a-new-feature + :link: contribute_code :link-type: ref :shadow: none - :octicon:`light-bulb;1em;sd-text-info` Request new feature + :octicon:`code;1em;sd-text-info` Contribute code .. grid-item-card:: - :link: submitting-a-bug-report + :link: contribute_documentation :link-type: ref :shadow: none - :octicon:`bug;1em;sd-text-info` Submit bug report + :octicon:`note;1em;sd-text-info` Write documentation .. grid-item-card:: - :link: contribute_code + :link: other_ways_to_contribute :link-type: ref :shadow: none - :octicon:`code;1em;sd-text-info` Contribute code + :octicon:`paper-airplane;1em;sd-text-info` Other ways to contribute - .. grid-item-card:: - :link: contribute_documentation - :link-type: ref - :shadow: none - - :octicon:`note;1em;sd-text-info` Write documentation If you are new to contributing, we recommend that you first read our :ref:`contributing guide`. If you are contributing code or From 04c322de850ba74ff282fafa94d69d53b06ca18a Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:04:55 +0200 Subject: [PATCH 0936/1120] DOC: Rewrite "Work on an issue" section Make that section more understandable and actionable for people unfamiliar with the GitHub interface and the pull request process. --- doc/devel/contribute.rst | 23 +++++++++++++++-------- doc/devel/index.rst | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 4230d68c26ce..e6263d317f73 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -154,12 +154,19 @@ Work on an issue ---------------- In general, the Matplotlib project does not assign issues. Issues are -"assigned" or "claimed" by opening a PR; there is no other assignment -mechanism. If you have opened such a PR, please comment on the issue thread to -avoid duplication of work. Please check if there is an existing PR for the -issue you are addressing. If there is, try to work with the author by +"assigned" or "claimed" by +:ref:`proposing a solution via a pull request ` (PR). +We ask PR authors to +`link the PR to the issue +`_. +Such links are be displayed in the discussion thread as well as the sidebar +on the issue page on GitHub. + +Before starting to work on an issue, please check if there is already +a linked PR. If there is, try to work with the author by submitting reviews of their code or commenting on the PR rather than opening -a new PR; duplicate PRs are subject to being closed. However, if the existing +a new PR; duplicate PRs are unnecessary concurrent work and thus are subject +to being closed. However, if the existing PR is an outline, unlikely to work, or stalled, and the original author is unresponsive, feel free to open a new PR referencing the old one. @@ -183,7 +190,7 @@ our code consistent and mitigating the impact of changes. * :ref:`pr-guidelines` Code is contributed through pull requests, so we recommend that you start at -:ref:`how-to-contribute` If you get stuck, please reach out on the +:ref:`how-to-pull-request` If you get stuck, please reach out on the :ref:`contributor_incubator` .. _contribute_documentation: @@ -236,7 +243,7 @@ Instructions and guidelines for contributing documentation are found in: * :doc:`tag_guidelines` Documentation is contributed through pull requests, so we recommend that you start -at :ref:`how-to-contribute`. If that feels intimidating, we encourage you to +at :ref:`how-to-pull-request`. If that feels intimidating, we encourage you to `open an issue`_ describing what improvements you would make. If you get stuck, please reach out on the :ref:`contributor_incubator` @@ -260,7 +267,7 @@ please follow the :doc:`/project/citing` guidelines. If you have developed an extension to Matplotlib, please consider adding it to our `third party package `_ list. -.. _how-to-contribute: +.. _how-to-pull-request: How to contribute via pull request ================================== diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 3525510cad18..f6075384c11a 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -81,7 +81,7 @@ New contributors :octicon:`git-pull-request;1em;sd-text-info` :ref:`How do I claim an issue? ` - :octicon:`codespaces;1em;sd-text-info` :ref:`How do I start a pull request? ` + :octicon:`codespaces;1em;sd-text-info` :ref:`How do I start a pull request? ` .. grid-item:: From 22b4a52f9c2740bf1c4850c8938962aceddedc8f Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:48:24 +0200 Subject: [PATCH 0937/1120] Apply suggestions from code review Co-authored-by: hannah --- doc/devel/contribute.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index e6263d317f73..9d476bfd81b4 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -155,12 +155,10 @@ Work on an issue In general, the Matplotlib project does not assign issues. Issues are "assigned" or "claimed" by -:ref:`proposing a solution via a pull request ` (PR). -We ask PR authors to -`link the PR to the issue -`_. -Such links are be displayed in the discussion thread as well as the sidebar -on the issue page on GitHub. +:ref:`proposing a solution via a pull request `. +We ask pull request (PR) authors to +`link to the issue in the PR +`_ because then Github adds corresponding links to the PR to the discussion and the sidebar on the linked issue page on GitHub. Before starting to work on an issue, please check if there is already a linked PR. If there is, try to work with the author by From e3062cb78a4d3c8298e57ef9611b9fec2265a274 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 5 Apr 2024 01:16:28 -0400 Subject: [PATCH 0938/1120] DOC: Prepare release notes for 3.9.0rc1 --- doc/_static/switcher.json | 13 +- doc/users/github_stats.rst | 660 +++++++++++++++++- .../prev_whats_new/github_stats_3.8.4.rst | 78 +++ doc/users/release_notes.rst | 9 + 4 files changed, 719 insertions(+), 41 deletions(-) create mode 100644 doc/users/prev_whats_new/github_stats_3.8.4.rst diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index 75301fbb142a..b49e478f28e4 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -1,14 +1,19 @@ [ { - "name": "3.8 (stable)", - "version": "stable", - "url": "https://matplotlib.org/stable/" + "name": "3.9 (rc)", + "version": "rc", + "url": "https://matplotlib.org/3.9.0/" }, { - "name": "3.9 (dev)", + "name": "3.10 (dev)", "version": "dev", "url": "https://matplotlib.org/devdocs/" }, + { + "name": "3.8", + "version": "3.8.4", + "url": "https://matplotlib.org/3.8.4/" + }, { "name": "3.7", "version": "3.7.5", diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 8dab8484d6fd..e52a85a3f840 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,81 +1,667 @@ .. _github-stats: -GitHub statistics for 3.8.4 (Apr 03, 2024) +GitHub statistics for 3.9.0 (Apr 05, 2024) ========================================== -GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/03 +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/05 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 3 issues and merged 27 pull requests. -The full list can be seen `on GitHub `__ +We closed 85 issues and merged 387 pull requests. +The full list can be seen `on GitHub `__ -The following 26 authors contributed 351 commits. +The following 170 authors contributed 2480 commits. * 0taj +* Abdul Razak Taha +* Adam J. Stewart +* Adam Turner +* Aditi Gautam +* agautam478 +* Alan Lau +* Albert Y. Shih * Alec Vercruysse * Alexander Volkov +* Alice Descoeudres +* Allan Haldane +* Amirreza Aflakparast +* Ananya Devarakonda +* ananya314 +* Anja Beck +* Anjini2004 +* Ant Lockyer * Antony Lee * Anvi Verma +* Artyom Romanov +* Augusto Borges +* avramid9 +* Ben Root +* bersbersbers +* Binaya Sharma +* Cameron +* Chaoyi Hu +* chaoyihu * Chiraag Balu +* Christoph Hasse +* ConstableCatnip +* CozyFrog +* Cyril Gadal +* Dale Dai +* Daniel Bergman +* Daniel Hitchcock +* danielcobej * David Gilbertson * David Stansby +* ddale1128@gmail.com * dependabot[bot] +* Devilsaint +* dohyun +* Drew Kinneer +* DWesl +* Elisa Heckelmann +* ElisaHeck * Elliott Sales de Andrade * Eric Firing +* Eric Prestat +* esibinga +* Eva Sibinga +* Evgenii Radchenko +* Faisal Fawad +* Garrett Sward +* Gaurav-Kumar-Soni +* Gauri Chaudhari +* Gautam Sagar * Greg Lucas * Gurudatta Shanbhag * hannah +* Haoying Zhang +* Hugues Hoppe +* i-jey +* iamfaham +* Ian Hunt-Isaak +* Ian Thomas +* ifEricReturnTrue +* Issam +* Issam Arabi +* Jacob Stevens-Haas +* Jacob Tomlinson +* Jake +* Jake Stevens-Haas * James Salsman +* Jaroza727 +* Jeremy Farrell +* Jirka * Jody Klymak +* Jorge Moraleda +* Joshua Stevenson +* jovianw +* João Andrade +* jpgianfaldoni +* jsdodge +* jsjeelshah +* judfs +* Juhan Oskar Hennoste +* Junpei Ota +* Katherine Turk +* katotaisei +* KheshavKumar +* Koustav Ghosh +* Kritika Verma * Kyle Sunden +* Linyi Li +* linyilily * lkkmpn * Lucia Korpas +* madisonwong210 +* Maggie Liu +* Marc Bresson +* Matthew Feickert * Matthew Morrison +* Matthias Bussonnier +* Melissa Weber Mendonça +* melissawm +* mliu08 +* Mostafa Noah +* MostafaNouh0011 +* n-aswin +* nbarlowATI +* Nidaa Rabah +* Nivedita Chaudhari * Oscar Gustafsson +* patel-zeel +* Pavel Liavonau +* Pedro +* Pedro Peçanha +* Pradeep Reddy Raamana +* Prajwal Agrawal +* Pranav Raghu +* prateetishah +* pre-commit-ci[bot] +* QuadroTec +* Rafael Tsuha +* Raghuram Sirigiri +* Raphael +* Raphael Quast +* Ratnabali Dutta +* rawwash +* rsp2210 +* Ruoyi +* Ruoyi Xie +* Rushikesh Pandya * Ruth Comer +* samGreer * Samuel Diebolt +* saranti +* Scott Shambaugh +* Sebastian Berg +* Seohyeon Lee +* Sheepfan0828 +* ShivamPathak99 +* Shriya Kalakata +* shriyakalakata +* Stefan +* Steffen Rehberg +* stevezhang1999 +* Sudhanshu Pandey +* Talha Irfan +* thehappycheese * Thomas A Caswell * Tim Hoffmann +* tobias +* Tom Sarantis +* trananso +* turnipseason +* tusharkulkarni008 +* UFEddy +* Vashesh08 +* vicky6 +* vigneshvetrivel8 * wemi3 +* yangyangdotcom +* YiLun Fan +* Zach Champion +* zachjweiner +* zoehcycy GitHub issues and pull requests: -Pull Requests (27): +Pull Requests (387): -* :ghpull:`28015`: Backport PR #27955 on branch v3.8.x (Add a draw during show for macos backend) -* :ghpull:`27993`: Unpin numpy 2, build against prerelease numpy in CIBW -* :ghpull:`27955`: Add a draw during show for macos backend -* :ghpull:`28001`: Backport PR #28000 on branch v3.8.x (Fix color sequence data for Set2 and Set3) -* :ghpull:`28000`: Fix color sequence data for Set2 and Set3 -* :ghpull:`27990`: Backport PR #27988 on branch v3.8.x (gtk: Ensure pending draws are done before GTK draw) -* :ghpull:`27988`: gtk: Ensure pending draws are done before GTK draw -* :ghpull:`27986`: Backport PR #27985 on branch v3.8.x (TST: Remove superfluous chdir from tests) -* :ghpull:`27985`: TST: Remove superfluous chdir from tests -* :ghpull:`27976`: Backport PR #27975 on branch v3.8.x (DOC: Fix typo in ``ax.transData.inversed()``) -* :ghpull:`27934`: Backport PR #27933 on branch v3.8.x (Update "Created with" url in hand.svg) -* :ghpull:`27933`: Update "Created with" url in hand.svg -* :ghpull:`27926`: Backport PR #27875 on branch v3.8.x (macosx: Clean up single-shot timers correctly) -* :ghpull:`27925`: Backport PR #27921 on branch v3.8.x (Avoid modifying user input to Axes.bar) -* :ghpull:`27875`: macosx: Clean up single-shot timers correctly -* :ghpull:`27921`: Avoid modifying user input to Axes.bar -* :ghpull:`27903`: Merge 3.8.3-doc into 3.8.x -* :ghpull:`27889`: Backport PR #27888 on branch v3.8.x (DOC: fix stray release note entry) -* :ghpull:`27888`: DOC: fix stray release note entry -* :ghpull:`27849`: Backport PR #27754 on branch v3.8.x (fix quiver3d incorrect arrow colors) -* :ghpull:`27859`: Backport PR #27858 on branch v3.8.x (pin pytest) -* :ghpull:`27858`: pin pytest -* :ghpull:`27754`: fix quiver3d incorrect arrow colors -* :ghpull:`27847`: Backport PR #27846 on branch v3.8.x (Make example in legend_elements doc more generalisable) -* :ghpull:`27846`: Make example in legend_elements doc more generalisable -* :ghpull:`27802`: Backport PR #27794 on branch v3.8.x (Remove old reference to 72 DPI in figure_size_units.py) -* :ghpull:`27794`: Remove old reference to 72 DPI in figure_size_units.py +* :ghpull:`28024`: DOC: Rewrite "Work on an issue" section +* :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index +* :ghpull:`27747`: Move doc/users/installing/ to doc/install/ +* :ghpull:`27952`: ENH: Align titles +* :ghpull:`28017`: Merge up v3.8.4 +* :ghpull:`28014`: Improve timeline example. +* :ghpull:`28019`: DOC: correct path to mpl_toolkits reference images +* :ghpull:`26981`: Fixes Issue #26377 - Auto-escape % Symbol in Latex in pie labels +* :ghpull:`28007`: wx: Fix file extension for toolmanager-style toolbar +* :ghpull:`25556`: Display cursor coordinates for all axes twinned with the current one. +* :ghpull:`23597`: Always use PyQT/PySide6 for GitHub CI +* :ghpull:`28013`: Avoid plt.xticks/plt.yticks in gallery examples. +* :ghpull:`28006`: Fix deprecation warnings in ft2font extension +* :ghpull:`27723`: ci: Enable testing on M1 macOS +* :ghpull:`26375`: Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` +* :ghpull:`27999`: Remove documentation that some backends don't support hatching. +* :ghpull:`26710`: Add support for High DPI displays to wxAgg backend +* :ghpull:`27148`: Correctly treat pan/zoom events of overlapping axes. +* :ghpull:`27981`: DOC: Fix label type specification in parameter descriptions +* :ghpull:`27979`: Clarify error message for bad-dimensionality in pcolorfast(). +* :ghpull:`27962`: DOC: Document axes_grid1.Grid attributes +* :ghpull:`27968`: MNT: Remove remaining 3.7 deprecations +* :ghpull:`27965`: DOC: Rewrite the example illustrating bxp() +* :ghpull:`26453`: add documentation for reloading font cache +* :ghpull:`26131`: Tst/restore old tests +* :ghpull:`27730`: Add an rcparam for image.interpolation_stage. +* :ghpull:`27956`: Use PyOS_setsig in macos backend +* :ghpull:`27829`: Simplify color/marker disambiguation logic in _process_plot_format. +* :ghpull:`27840`: Add legend support for boxplots +* :ghpull:`27943`: Support Cn, n>9 in plot() shorthand format. +* :ghpull:`27950`: ci: Fix condition for publishing wheels +* :ghpull:`27909`: Add a note to pyplot docstrings referencing the corresponding object methods +* :ghpull:`27929`: DOC: Add summary lines to plot types +* :ghpull:`27915`: [BUG] Fix redirect-from Sphinx extension +* :ghpull:`27945`: DOC: Explain leading dot in object references +* :ghpull:`27947`: Update docs for ``FancyArrowPatch`` & ``Annotation`` to make it clear that ShrinkA/B parameters are in points and not fractional. +* :ghpull:`27944`: Bump the actions group with 2 updates +* :ghpull:`27932`: Fix pickling of make_axes_area_auto_adjustable'd axes. +* :ghpull:`26500`: closes #26477 ENH: Add interpolation_stage in qt figureoptions +* :ghpull:`27927`: Update docs +* :ghpull:`27916`: Revert renaming labels to tick_labels in boxplot_stats() +* :ghpull:`27931`: Highlight development_setup code snippets as bash, not python. +* :ghpull:`27856`: Support hatching in cairo backends. +* :ghpull:`27922`: Fix cbook style +* :ghpull:`27668`: MNT: prevent merging using labels + branch protection rules +* :ghpull:`27857`: Documentation edit for matshow function +* :ghpull:`27928`: DOC: Fix syntax for ToolBase.image docstring +* :ghpull:`27873`: Simplify the LineCollection example +* :ghpull:`27492`: Fix semantics of MEP22 image names. +* :ghpull:`27918`: Fix new flake8 errors from old merge +* :ghpull:`27874`: Modernize macosx backend a bit +* :ghpull:`25887`: Update ``_unpack_to_numpy`` function to convert JAX and PyTorch arrays to NumPy +* :ghpull:`27685`: Work around pyparsing diagnostic warnings +* :ghpull:`26594`: Added optional props argument to Lasso Widget __init__ to customize Lasso line +* :ghpull:`22761`: Add minor ticks on and off in Axis +* :ghpull:`22407`: Add ``set_XY`` and ``set_data`` to ``Quiver`` +* :ghpull:`27901`: Rename boxplot's tick label parameter +* :ghpull:`27883`: Fix build on older macOS deployment targets +* :ghpull:`27900`: Remove empty user guide tutorials page +* :ghpull:`27885`: Clean up headers in extensions +* :ghpull:`27910`: DOC: Fix dead link in README +* :ghpull:`26567`: Use SVG inheritance diagrams now that linking has been fixed +* :ghpull:`27899`: Merge up 3.8.x into main +* :ghpull:`27905`: Improved error message for malformed colors +* :ghpull:`27906`: Override open_group, close_group methods in PathEffectRenderer +* :ghpull:`27904`: FIX: Restore D213 in flake8 +* :ghpull:`27895`: Remove versions from sidebar in docs +* :ghpull:`27894`: Mark triangulation classes as final +* :ghpull:`27557`: Use :mpltype:``color`` for color types +* :ghpull:`27845`: Make sure custom alpha param does not change 'none' colors in a list of colors +* :ghpull:`27719`: Add BackendRegistry singleton class +* :ghpull:`27890`: DOC: State approximate documentation build time +* :ghpull:`27887`: BLD: Add a fallback URL for FreeType +* :ghpull:`25224`: Allow passing a transformation to secondary_xaxis/_yaxis +* :ghpull:`27886`: Fix devdocs version switcher +* :ghpull:`27884`: FIX: don't copy twice on RGB input +* :ghpull:`27087`: Convert path extension to pybind11 +* :ghpull:`27867`: DOC: Update some animation related topics +* :ghpull:`27848`: FIX: handle nans in RGBA input with ScalarMappables +* :ghpull:`27821`: BLD,Cygwin: Include Python.h first in various C++ files +* :ghpull:`27457`: TST: adding tests of current clear behavior on ticks +* :ghpull:`27872`: doc: add description of ``**kwargs`` usage to collections +* :ghpull:`27868`: Use pybind11 string formatter for exception messages +* :ghpull:`27862`: Add dtype/copy args to internal testing class +* :ghpull:`27658`: Bump pydata-sphinx-theme +* :ghpull:`27303`: FIX: also exclude np.nan in RGB(A) in color mapping +* :ghpull:`27860`: Bump the actions group with 2 updates +* :ghpull:`27869`: Correctly set temporary pdf/pgf backends +* :ghpull:`27850`: Deprecate ``plot_date`` +* :ghpull:`27815`: Add side option to violinplot +* :ghpull:`27836`: DOC: use ... for continuation prompt in docstrings +* :ghpull:`27819`: MNT: remove draw method args and kwargs +* :ghpull:`27813`: DOC: Update violinplot() docs +* :ghpull:`27698`: Add linting and validation of all YAML files +* :ghpull:`27811`: Fix Annulus width check +* :ghpull:`27667`: Change return type of ``ion`` and ``ioff`` to fix unbound variable errors with Pyright +* :ghpull:`27807`: Expand CI pytest reporting config to ignore xfails +* :ghpull:`27806`: Remove self._renderer from AnnotationBbox and ConnectionPatch +* :ghpull:`27799`: Clarify that set_ticks() affects major/minor ticks independently +* :ghpull:`27787`: Improve documentation on boxplot and violinplot +* :ghpull:`27800`: Deactivate sidebar for release notes +* :ghpull:`27798`: Fix sphinx-gallery CSS +* :ghpull:`27462`: DOC: clarify the default value of *radius* in Patch.contains_point +* :ghpull:`27565`: MNT: arghandling subplotspec +* :ghpull:`27796`: Make mypy a bit stricter +* :ghpull:`27767`: Update handling of sequence labels for plot +* :ghpull:`27795`: Add EffVer badge +* :ghpull:`27780`: Partly revert #27711 +* :ghpull:`27768`: MNT: deprecate draw method args and kwargs +* :ghpull:`27783`: Update README.md to fix citation link +* :ghpull:`27726`: TST: always set a (long) timeout for subprocess and always use our wrapper +* :ghpull:`27781`: Simplify example: Box plots with custom fill colors +* :ghpull:`27750`: Bump the actions group with 2 updates +* :ghpull:`27771`: Add marker-only and line+marker visuals to the plot() plot types +* :ghpull:`27764`: Increase size of legend in Legend guide example +* :ghpull:`26800`: Bump minimum NumPy version to 1.23 +* :ghpull:`27752`: Update some Meson internals +* :ghpull:`27702`: GOV: adopt EffVer +* :ghpull:`26965`: Removal of deprecated API cm +* :ghpull:`27758`: [Doc] Remove special casing for removed method +* :ghpull:`25815`: [TST] Make jpl units instantiated with datetimes consistent with mpl converters +* :ghpull:`27729`: DOC: Improve colormap normalization example +* :ghpull:`27732`: TST: Remove memory leak test +* :ghpull:`27733`: ci: Simplify CodeQL setup +* :ghpull:`27692`: Add method to update position of arrow patch +* :ghpull:`27736`: Fix incorrect API reference in docs +* :ghpull:`27731`: DOC: Create explicit rename legend entry section in guide +* :ghpull:`27560`: Moved /users/project to /doc/project +* :ghpull:`27728`: Simplify Figure._suplabels. +* :ghpull:`27715`: Bump the actions group with 3 updates +* :ghpull:`27711`: Fix boxplot legend entries part 2 +* :ghpull:`27696`: DOC: clean up automated tests section of workflow docs +* :ghpull:`27686`: Improve Locator docstrings +* :ghpull:`27704`: ci: Remove prerelease conditions from Azure Pipelines +* :ghpull:`27568`: Fix boxplot legend entries +* :ghpull:`27694`: MNT: fix labeller +* :ghpull:`26953`: MNT: test that table doesn't try to convert unitized data +* :ghpull:`27690`: Remove "Past versions" section from release notes +* :ghpull:`26926`: Closes #22011: Changes to SubFigures so it behaves like a regular artist +* :ghpull:`27469`: Fixed legend with legend location "best" when legend overlaps shaded area and text +* :ghpull:`27684`: Bump the actions group with 1 update +* :ghpull:`27665`: Axes.inset_axes - warning message removed +* :ghpull:`27688`: CI: skip code coverage upload on scheduled tests +* :ghpull:`27689`: ci: Don't include API/what's new notes in general doc labels +* :ghpull:`27640`: Add ``get_cursor_data`` to ``NonUniformImage`` +* :ghpull:`27676`: BLD: Downgrade FreeType to 2.6.1 on Windows ARM +* :ghpull:`27619`: Use GH action to install reviewdog +* :ghpull:`27552`: TST: Use importlib for importing in pytest +* :ghpull:`27650`: DOC: Added call out to API guidelines to contribute + small API guidelines reorg +* :ghpull:`27618`: Add option of running stubtest using tox +* :ghpull:`27656`: Bump the actions group with 1 update +* :ghpull:`27415`: Use class form of data classes +* :ghpull:`27649`: Check for latex binary before building docs +* :ghpull:`27641`: MNT: fix api changes link in PR template +* :ghpull:`27644`: ci: Fix mpl_toolkits label +* :ghpull:`27230`: Query macOS for available system fonts. +* :ghpull:`27643`: ci: Update nightly upload for artifacts v4 +* :ghpull:`27642`: Fix auto-labeler configuration +* :ghpull:`27639`: Doc: typo fix for #22699 +* :ghpull:`26978`: [pre-commit.ci] pre-commit autoupdate +* :ghpull:`27563`: Enable PyPI publishing from GitHub Actions +* :ghpull:`22699`: Proof of concept for adding kwdoc content to properties using a decorator +* :ghpull:`27633`: Auto-label PRs based on changed files +* :ghpull:`27607`: Error on bad input to hexbin extents +* :ghpull:`27629`: Don't run CI twice on dependabot branches +* :ghpull:`27562`: Avoid an extra copy/resample if imshow input has no alpha +* :ghpull:`27628`: Bump the actions group with 2 updates +* :ghpull:`27626`: CI: Group dependabot updates +* :ghpull:`27589`: Don't clip PowerNorm inputs < vmin +* :ghpull:`27613`: Fix marker validator with cycler (allow mix of classes) +* :ghpull:`27615`: MNT: add spaces to PR template +* :ghpull:`27614`: DOC: Updated link in annotation API docs to point to annotation user guide +* :ghpull:`27605`: Ignore masked values in boxplot +* :ghpull:`26884`: Remove deprecated code from _fontconfig_patterns +* :ghpull:`27602`: Let FormatStrFormatter respect axes.unicode_minus. +* :ghpull:`27601`: Clarify dollar_ticks example and FormatStrFormatter docs. +* :ghpull:`24834`: Deprecate apply_theta_transforms=True to PolarTransform +* :ghpull:`27591`: Use macOS instead of OSX in comments/docs +* :ghpull:`27577`: MNT: add the running version to pickle warning message +* :ghpull:`25191`: Deprecate 'prune' kwarg to MaxNLocator +* :ghpull:`27566`: DOC: changed tag ``plot type`` to ``plot-type`` +* :ghpull:`27105`: Use Axes instead of axes core library code +* :ghpull:`27575`: Add quotes round .[dev] in editable install command +* :ghpull:`27104`: Use Axes instead of axes in galleries +* :ghpull:`27373`: Transpose grid_finder tick representation. +* :ghpull:`27363`: ci: Improve coverage for compiled code +* :ghpull:`27200`: DOC: Add role for custom informal types like color +* :ghpull:`27548`: DOC: typo fix in contribute doc +* :ghpull:`27458`: Check if the mappable is in a different Figure than the one fig.color… +* :ghpull:`27546`: MNT: Clean up some style exceptions +* :ghpull:`27514`: Improve check for bbox +* :ghpull:`27265`: DOC: reorganizing contributing docs to clean up toc, better separate topics +* :ghpull:`27517`: Best-legend-location microoptimization +* :ghpull:`27540`: Bump github/codeql-action from 2 to 3 +* :ghpull:`27520`: [Doc] Minor consistency changes and correction of Marker docs +* :ghpull:`27505`: Download Qhull source from Github, not Qhull servers, in meson build +* :ghpull:`27518`: Micro-optimizations related to list handling +* :ghpull:`27495`: Bump actions/stale from 8 to 9 +* :ghpull:`27523`: Changes for stale GHA v9 +* :ghpull:`27519`: [Doc] Improve/correct docs for 3D +* :ghpull:`27447`: TST: Compress some hist geometry tests +* :ghpull:`27513`: Fix docs and add tests for transform and deprecate ``BboxTransformToMaxOnly`` +* :ghpull:`27511`: TST: Add tests for Affine2D +* :ghpull:`27424`: Added Axes.stairs test in test_datetime.py +* :ghpull:`27267`: Fix/restore secondary axis support for Transform-type functions +* :ghpull:`27013`: Add test_contour under test_datetime.py +* :ghpull:`27497`: Clarify that set_axisbelow doesn't move grids below images. +* :ghpull:`27498`: Remove unnecessary del local variables at end of Gcf.destroy. +* :ghpull:`27466`: Add test_eventplot to test_datetime.py +* :ghpull:`25905`: Use annotate coordinate systems to simplify label_subplots. +* :ghpull:`27471`: Doc: visualizing_tests and ``triage_tests`` tools +* :ghpull:`27474`: Added smoke test for Axes.matshow to test_datetime.py +* :ghpull:`27470`: Fix test visualization tool for non-PNG files +* :ghpull:`27426`: DOC: normalizing histograms +* :ghpull:`27452`: Cleanup unit_cube-methods +* :ghpull:`27431`: Added test for Axes.bar_label +* :ghpull:`26962`: Remove backend 3.7-deprecated API +* :ghpull:`27410`: Add test_vlines to test_datetime.py +* :ghpull:`27425`: Added test_fill_betweenx in test_datetime.py +* :ghpull:`27449`: Remove test_quiverkey from test_datetime.py +* :ghpull:`27427`: MNT/TST: remove xcorr and acorr from test_datetime +* :ghpull:`27390`: Add test_bxp in test_datetime.py +* :ghpull:`27428`: Added test for broken_barh to test_datetime.py +* :ghpull:`27222`: [TST] Added test_annotate in test_datetime.py +* :ghpull:`27135`: Added smoke test for Axes.stem +* :ghpull:`27343`: Fix draggable annotations on subfigures. +* :ghpull:`27033`: Add test_bar in test_datetime +* :ghpull:`27423`: Add test for fill_between in test_datetime.py +* :ghpull:`27409`: Fix setting ``_selection_completed`` in ``SpanSelector`` when spanselector is initialised using ``extents`` +* :ghpull:`27440`: Fix get_path for 3d artists +* :ghpull:`27422`: TST: Cache available interactive backends +* :ghpull:`27401`: Add test_fill in test_datetime.py +* :ghpull:`27419`: DOC: Add AsinhScale to list of built-in scales +* :ghpull:`27417`: Switch pytest fixture from tmpdir to tmp_path +* :ghpull:`27172`: ENH: Change logging to warning when creating a legend with no labels +* :ghpull:`27405`: Check that xerr/yerr values are not None in errorbar +* :ghpull:`27392`: Remove test_spy from test_datetime.py +* :ghpull:`27331`: Added smoke test for Axes.barbs in test_datetime.py +* :ghpull:`27393`: MNT: Fix doc makefiles +* :ghpull:`27387`: Revert "MNT: add _version.py to .gitignore" +* :ghpull:`27347`: FIX: scale norm of collections when first array is set +* :ghpull:`27374`: MNT: add _version.py to .gitignore +* :ghpull:`19011`: Simplify tk tooltip setup. +* :ghpull:`27367`: Fix _find_fonts_by_props docstring +* :ghpull:`27359`: Fix build on PyPy +* :ghpull:`27362`: Implement SubFigure.remove. +* :ghpull:`27360`: Fix removal of colorbars on nested subgridspecs. +* :ghpull:`27211`: Add test_hlines to test_datetimes.py +* :ghpull:`27353`: Refactor AxisArtistHelpers +* :ghpull:`27357`: [DOC]: Update 3d axis limits what's new +* :ghpull:`26992`: Convert TkAgg utilities to pybind11 +* :ghpull:`27215`: Add ``@QtCore.Slot()`` decorations to ``NavigationToolbar2QT`` +* :ghpull:`26907`: Removal of deprecations for Contour +* :ghpull:`27285`: Factor out common parts of qt and macos interrupt handling. +* :ghpull:`27306`: Simplify GridSpec setup in make_axes_gridspec. +* :ghpull:`27313`: FIX: allow re-shown Qt windows to be re-destroyed +* :ghpull:`27184`: Use pybind11 for qhull wrapper +* :ghpull:`26794`: Use pybind11 in _c_internal_utils module +* :ghpull:`27300`: Remove idiosyncratic get_tick_iterator API. +* :ghpull:`27275`: MAINT: fix .yml in tag issue template +* :ghpull:`27288`: Use int.from_bytes instead of implementing the conversion ourselves. +* :ghpull:`27286`: Various cleanups +* :ghpull:`27279`: Tweak a few docstrings. +* :ghpull:`27256`: merge up v3.8.1 +* :ghpull:`27254`: Remove redundant axes_grid colorbar examples. +* :ghpull:`27251`: webagg: Don't resize canvas if WebSocket isn't connected +* :ghpull:`27236`: Tagging Example - Tags for multiple figs demo +* :ghpull:`27245`: MNT: be more careful in Qt backend that there is actually a Figure +* :ghpull:`27158`: First attempt for individual hatching styles for stackplot +* :ghpull:`26851`: Establish draft Tag glossary and Tagging guidelines +* :ghpull:`27083`: DOC: Add tags infrastructure for gallery examples +* :ghpull:`27204`: BLD: Use NumPy nightly wheels for non-release builds +* :ghpull:`27208`: Add test_axvline to test_datetime.py +* :ghpull:`26989`: MNT: print fontname in missing glyph warning +* :ghpull:`27177`: Add test_axhline in test_datetime.py +* :ghpull:`27164`: docs: adding explanation for color in ``set_facecolor`` +* :ghpull:`27175`: Deprecate mixing positional and keyword args for legend(handles, labels) +* :ghpull:`27199`: DOC: clean up links under table formatting docs +* :ghpull:`27185`: Added smoke tests for Axes.errorbar in test_datetime.py +* :ghpull:`27091`: Add test_step to test_datetime.py +* :ghpull:`27182`: Add example for plotting a bihistogram +* :ghpull:`27130`: added test_axvspan in test.datetime.py +* :ghpull:`27094`: MNT: move pytest.ini configs to .toml +* :ghpull:`27139`: added test_axhspan in test_datetime.py +* :ghpull:`27058`: DOC: concise dependency heading + small clarifications +* :ghpull:`27053`: Added info for getting compilation output from meson on autorebuild +* :ghpull:`26906`: Fix masking for Axes3D.plot() +* :ghpull:`27142`: Added smoke test for Axes.text in test_datetime.py +* :ghpull:`27024`: Add test_contourf in test_datetime.py +* :ghpull:`22347`: correctly treat pan/zoom events of overlapping axes +* :ghpull:`26900`: #26865 removing deprecations to axislines.py +* :ghpull:`26696`: DOC: Fix colLoc default +* :ghpull:`27064`: Close all plot windows of a blocking show() on Ctrl+C +* :ghpull:`26882`: Add scatter test for datetime units +* :ghpull:`27114`: add test_stackplot in test_datetime.py +* :ghpull:`27084`: Add test_barh to test_datetime.py +* :ghpull:`27110`: DOC: Move figure member sections one level down +* :ghpull:`27127`: BLD: use python3 for shebang consistent with pep-394 +* :ghpull:`27111`: BLD: Fix setting FreeType build type in extension +* :ghpull:`26921`: MNT: clarify path.sketch rcparam format + test validate_sketch +* :ghpull:`27109`: TST: Use importlib for subprocess tests +* :ghpull:`27119`: Update clabel comment. +* :ghpull:`27117`: Remove datetime test for axes.pie +* :ghpull:`27095`: Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. +* :ghpull:`27066`: Tweak array_view to be more like pybind11 +* :ghpull:`27090`: Restore figaspect() API documentation +* :ghpull:`27074`: Issue #26990: Split the histogram image into two for each code block. +* :ghpull:`27086`: Rename py namespace to mpl in extension code +* :ghpull:`27082`: MAINT: Update environment.yml to match requirements files +* :ghpull:`27072`: Remove datetime test stubs for spectral methods/table +* :ghpull:`26830`: Update stix table with Unicode names +* :ghpull:`26969`: DOC: add units to user/explain [ci doc] +* :ghpull:`27028`: Added test_hist in test_datetime.py +* :ghpull:`26876`: issue: 26871 - Remove SimplePath class from patches.py +* :ghpull:`26875`: Fix Deprecation in patches.py +* :ghpull:`26890`: Removing deprecated api from patches +* :ghpull:`27037`: add test_plot_date in test_datetime.py +* :ghpull:`27012`: Bump required C++ standard to c++17 +* :ghpull:`27021`: Add a section to Highlight past winners for JDH plotting contest in docs +* :ghpull:`27004`: Warning if handles and labels have a len mismatch +* :ghpull:`24061`: #24050 No error was thrown even number of handles mismatched labels +* :ghpull:`26754`: DOC: separate and clarify axisartist default tables +* :ghpull:`27020`: CI: Update scientific-python/upload-nightly-action to 0.2.0 +* :ghpull:`26951`: Clarify that explicit ticklabels are used without further formatting. +* :ghpull:`26894`: Deprecate setting the timer interval while starting it. +* :ghpull:`13401`: New clear() method for Radio and Check buttons +* :ghpull:`23829`: Start transitioning to pyproject.toml +* :ghpull:`26621`: Port build system to Meson +* :ghpull:`26928`: [TYP] Add tool for running stubtest +* :ghpull:`26917`: Deprecate ContourLabeler.add_label_clabeltext. +* :ghpull:`26960`: Deprecate backend_ps.get_bbox_header, and split it for internal use. +* :ghpull:`26967`: Minor cleanups. +* :ghpull:`26909`: deprecated api tri +* :ghpull:`26946`: Inline Cursor._update into its sole caller. +* :ghpull:`26915`: DOC: Clarify description and add examples in colors.Normalize +* :ghpull:`26874`: Cleaned up the span_where class method from Polycollections. +* :ghpull:`26586`: Support standard formatters in axisartist. +* :ghpull:`26788`: Fix axh{line,span} on polar axes. +* :ghpull:`26935`: add tomli to rstcheck extras +* :ghpull:`26275`: Use pybind11 in image module +* :ghpull:`26887`: DOC: improve removal for julian dates [ci doc] +* :ghpull:`26929`: DOC: Fix removal doc for Animation attributes +* :ghpull:`26918`: 26865 Removed deprecations from quiver.py +* :ghpull:`26902`: Fixed deprecated APIs in lines.py +* :ghpull:`26903`: Simplify CheckButtons and RadioButtons click handler. +* :ghpull:`26899`: MNT: only account for Artists once in fig.get_tightbbox +* :ghpull:`26861`: QT/NavigationToolbar2: configure subplots dialog should be modal +* :ghpull:`26885`: Removed deprecated code from gridspec.py +* :ghpull:`26880`: Updated offsetbox.py +* :ghpull:`26910`: Removed the deprecated code from offsetbox.py +* :ghpull:`26905`: Add users/explain to default skip subdirs +* :ghpull:`26853`: Widgets: Remove deprecations and make arguments keyword only +* :ghpull:`26877`: Fixes deprecation in lines.py +* :ghpull:`26871`: Removed the deprecated code from ``axis.py`` +* :ghpull:`26872`: Deprecated code removed in animation.py +* :ghpull:`26859`: Add datetime testing skeleton +* :ghpull:`26848`: ci: Don't install recommended packages on Circle +* :ghpull:`26852`: Remove Julian date support +* :ghpull:`26801`: [MNT]: Cleanup ticklabel_format (style=) +* :ghpull:`26840`: Reduce redundant information in _process_plot_var_args. +* :ghpull:`26731`: Explicitly set foreground color to black in svg icons +* :ghpull:`26826`: [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in +* :ghpull:`26742`: [TYP] Add typing for some private methods and modules +* :ghpull:`26819`: Reorder safe_first_element() and _safe_first_finite() code +* :ghpull:`26813`: Bump docker/setup-qemu-action from 2 to 3 +* :ghpull:`26797`: Remove deprecated draw_gouraud_triangle +* :ghpull:`26815`: Remove plt.Axes from tests +* :ghpull:`26818`: Fix doc build (alternative) +* :ghpull:`26785`: merge up v3.8.0 +* :ghpull:`25272`: Do not add padding to 3D axis limits when limits are manually set +* :ghpull:`26798`: Remove deprecated methods and attributed in Axes3D +* :ghpull:`26744`: Use cbook methods for string checking +* :ghpull:`26802`: specify input range in logs when image data must be clipped +* :ghpull:`26787`: Remove unused Axis private init helpers. +* :ghpull:`26629`: DOC: organize figure API +* :ghpull:`26690`: Make generated pgf code more robust against later changes of tex engine. +* :ghpull:`26577`: Bugfix: data sanitizing for barh +* :ghpull:`26684`: Update PR template doc links +* :ghpull:`26686`: PR template: shorten comment and pull up top +* :ghpull:`26670`: Added sanitize_sequence to kwargs in _preprocess_data +* :ghpull:`26634`: [MNT] Move SubplotParams from figure to gridspec +* :ghpull:`26609`: Cleanup AutoMinorLocator implementation. +* :ghpull:`26293`: Added get_xmargin(), get_ymargin() and get_zmargin() and tests. +* :ghpull:`26516`: Replace reference to %pylab by %matplotlib. +* :ghpull:`26483`: Improve legend(loc='best') warning and test +* :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions +* :ghpull:`23787`: Use pybind11 for C/C++ extensions -Issues (3): +Issues (85): -* :ghissue:`27953`: [Bug]: Pyplot can no longer set axes properties -* :ghissue:`11759`: The color of the 3D arrow head does not match that of the arrow body -* :ghissue:`27826`: [Bug]: Unexpected behavior of scatter.legend_elements +* :ghissue:`22376`: [ENH]: align_titles +* :ghissue:`5506`: Confusing status bar values in presence of multiple axes +* :ghissue:`4284`: Twin axis message coordinates +* :ghissue:`18940`: WxAgg backend draws the wrong size when wxpython app is high DPI aware on Windows +* :ghissue:`27792`: [ENH]: Legend entries for boxplot +* :ghissue:`27828`: [Bug]: ".C10" does not work as plot shorthand format spec +* :ghissue:`27911`: redirect not working for updated contribute page +* :ghissue:`21876`: [Doc]: redirect-from directive appears broken? +* :ghissue:`27941`: [Bug]: ShrinkA and ShrinkB are ignored in ax.annotate(arrowprops=...) +* :ghissue:`26477`: [ENH]: Add interpolation_stage selector for images in qt figureoptions +* :ghissue:`363`: Enable hatches for Cairo backend +* :ghissue:`27852`: [Bug]: matplotlib.pyplot.matshow "(first dimension of the array) are displayed horizontally" but are displayed vertically +* :ghissue:`27400`: [Bug]: tk backend confused by presence of file named "move" in current working directory +* :ghissue:`25882`: [Bug]: plt.hist takes significantly more time with torch and jax arrays +* :ghissue:`25204`: [Bug]: Pyparsing warnings emitted in mathtext +* :ghissue:`17707`: getpwuid(): uid not found: 99 +* :ghissue:`27896`: [Doc]: Empty "User guide tutorials page" in docs +* :ghissue:`27824`: [Bug]: polygon from axvspan not correct in polar plot after set_xy +* :ghissue:`27378`: [ENH]: Suggest 'CN' if color is an integer +* :ghissue:`27843`: [Bug]: close_group is not called when using patheffects +* :ghissue:`27839`: [Bug]: PathCollection using alpha ignores 'none' facecolors +* :ghissue:`25119`: [ENH]: secondary_x/yaxis accept transform argument +* :ghissue:`27876`: [Doc]: Fix version switcher in devdocs +* :ghissue:`27301`: [Bug]: ``imshow`` allows RGB(A) images with ``np.nan`` values to pass +* :ghissue:`23839`: [MNT]: Add tests to codify ``ax.clear`` +* :ghissue:`27652`: [Doc]: Low contrast on clicked links in dark mode +* :ghissue:`27865`: [Bug]: Zoom und pan not working after writing pdf pages. +* :ghissue:`25871`: [Bug]: Colorbar cannot be added to another figure +* :ghissue:`8072`: plot_date() ignores timezone in matplotlib version 2.0.0 +* :ghissue:`27812`: [ENH]: Add split feature for violin plots +* :ghissue:`27659`: [MNT]: Improve return type of ``ioff`` and ``ion`` to improve Pyright analysis of bound variables +* :ghissue:`27805`: [Bug]: Saving a figure with indicate_inset_zoom to pdf and then pickling it causes TypeError +* :ghissue:`27701`: [Bug]: axis set_xscale('log') interferes with set_xticks +* :ghissue:`19807`: radius modification in contains_point function when linewidth is specified +* :ghissue:`27762`: [Bug]: Inconsistent treatment of list of labels in ``plot`` when the input is a dataframe +* :ghissue:`27745`: [MNT]: ``_ImageBase.draw`` and ``Axis.draw`` args and kwargs +* :ghissue:`27782`: [Doc]: Link to citation page in read me broken +* :ghissue:`8789`: legend handle size does not automatically scale with linewidth +* :ghissue:`27746`: [Doc]: Citation link in the readme.md points to 404 +* :ghissue:`20853`: Add deprecation for colormaps +* :ghissue:`26865`: [MNT]: Remove 3.7-deprecated API +* :ghissue:`24168`: [Bug]: ``subprocess-exited-with-error`` when trying to build on M1 mac +* :ghissue:`27727`: [Doc]: Text in the colormap normalization gallery doesn't match the code +* :ghissue:`27635`: [Bug]: test_figure_leak_20490 repeatedly failing on CI +* :ghissue:`14217`: [Feature request] Add a way to update the position of the Arrow patch. +* :ghissue:`20512`: Bad boxplot legend entries +* :ghissue:`22011`: [Bug]: subfigures messes up with fig.legend zorder +* :ghissue:`27414`: [Bug]: Legend overlaps shaded area in fill_between with legend location "best" +* :ghissue:`23323`: Legend with "loc=best" does not try to avoid text +* :ghissue:`27648`: [Doc]: ``Axes.inset_axes`` is still experimental +* :ghissue:`27277`: [Doc]: Two license pages in docs +* :ghissue:`24648`: [Doc]: make html fail early if latex not present +* :ghissue:`27554`: [Bug]: Large image draw performance deterioration in recent releases +* :ghissue:`25239`: [Bug]: colors.PowerNorm results in incorrect colorbar +* :ghissue:`13533`: Boxplotting Masked Arrays +* :ghissue:`25967`: [Doc]: dollar_ticks example refers to unused formatter classes +* :ghissue:`24859`: [Doc]: Document color in a consistent way, including link +* :ghissue:`27159`: [Bug]: Meson build fails due to qhull link issue. +* :ghissue:`25691`: [Bug]: Secondary axis does not support Transform as functions +* :ghissue:`25860`: [Bug]: canvas pick events not working when Axes belongs to a subfigure +* :ghissue:`27361`: [Bug]: (Tight) layout engine breaks for 3D patches +* :ghissue:`27145`: [ENH]: Make "No artists with labels found to put in legend" a warning +* :ghissue:`27399`: [Bug]: None in y or yerr arrays leads to TypeError when using errorbar +* :ghissue:`13887`: Accessing default ``norm`` of a Collection removes its colors. +* :ghissue:`26593`: [ENH]: Support SubFigure.remove() +* :ghissue:`27329`: [Bug]: Removing a colorbar for an axes positioned in a subgridspec restores the axes' position to the wrong place. +* :ghissue:`27214`: [Bug]: ``NavigationToolbar2QT`` should use ``@Slot`` annotation +* :ghissue:`27146`: [ENH]: Multi hatching in ``ax.stackplot()`` +* :ghissue:`27168`: [Doc]: Instructions for editable installation on Windows potentially missing a step +* :ghissue:`27174`: [MNT]: Build nightly wheels with NumPy nightly wheels +* :ghissue:`25043`: [ENH]: Plotting masked arrays correctly in 3D line plot +* :ghissue:`26990`: [Doc]: Histogram path example renders poorly in HTML +* :ghissue:`25738`: [MNT]: Improve readability of _mathtext_data.stix_virtual_fonts table +* :ghissue:`11129`: Highlight past winners for JDH plotting contest in docs +* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles +* :ghissue:`10922`: ENH: clear() method for widgets.RadioButtons +* :ghissue:`18295`: How to modify ticklabels in axisartist? +* :ghissue:`24996`: [Bug]: for non-rectilinear axes, axvline/axhline should behave as "draw a gridline at that x/y" +* :ghissue:`26841`: [Bug]: Global legend weird behaviors +* :ghissue:`25974`: [MNT]: Cleanup ticklabel_format(..., style=) +* :ghissue:`26786`: Please upload new dev wheel so we pick up 3.9.dev after 3.8 release +* :ghissue:`18052`: the limits of axes are inexact with mplot3d +* :ghissue:`25596`: [MNT]: Consistency on Interface +* :ghissue:`26557`: [ENH]: Nightly Python 3.12 builds +* :ghissue:`26281`: [ENH]: Add get_xmargin, get_ymargin, get_zmargin axes methods Previous GitHub statistics diff --git a/doc/users/prev_whats_new/github_stats_3.8.4.rst b/doc/users/prev_whats_new/github_stats_3.8.4.rst new file mode 100644 index 000000000000..324393b12f9d --- /dev/null +++ b/doc/users/prev_whats_new/github_stats_3.8.4.rst @@ -0,0 +1,78 @@ +.. _github-stats-3-8-4: + +GitHub statistics for 3.8.4 (Apr 03, 2024) +========================================== + +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/03 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 3 issues and merged 27 pull requests. +The full list can be seen `on GitHub `__ + +The following 26 authors contributed 351 commits. + +* 0taj +* Alec Vercruysse +* Alexander Volkov +* Antony Lee +* Anvi Verma +* Chiraag Balu +* David Gilbertson +* David Stansby +* dependabot[bot] +* Elliott Sales de Andrade +* Eric Firing +* Greg Lucas +* Gurudatta Shanbhag +* hannah +* James Salsman +* Jody Klymak +* Kyle Sunden +* lkkmpn +* Lucia Korpas +* Matthew Morrison +* Oscar Gustafsson +* Ruth Comer +* Samuel Diebolt +* Thomas A Caswell +* Tim Hoffmann +* wemi3 + +GitHub issues and pull requests: + +Pull Requests (27): + +* :ghpull:`28015`: Backport PR #27955 on branch v3.8.x (Add a draw during show for macos backend) +* :ghpull:`27993`: Unpin numpy 2, build against prerelease numpy in CIBW +* :ghpull:`27955`: Add a draw during show for macos backend +* :ghpull:`28001`: Backport PR #28000 on branch v3.8.x (Fix color sequence data for Set2 and Set3) +* :ghpull:`28000`: Fix color sequence data for Set2 and Set3 +* :ghpull:`27990`: Backport PR #27988 on branch v3.8.x (gtk: Ensure pending draws are done before GTK draw) +* :ghpull:`27988`: gtk: Ensure pending draws are done before GTK draw +* :ghpull:`27986`: Backport PR #27985 on branch v3.8.x (TST: Remove superfluous chdir from tests) +* :ghpull:`27985`: TST: Remove superfluous chdir from tests +* :ghpull:`27976`: Backport PR #27975 on branch v3.8.x (DOC: Fix typo in ``ax.transData.inversed()``) +* :ghpull:`27934`: Backport PR #27933 on branch v3.8.x (Update "Created with" url in hand.svg) +* :ghpull:`27933`: Update "Created with" url in hand.svg +* :ghpull:`27926`: Backport PR #27875 on branch v3.8.x (macosx: Clean up single-shot timers correctly) +* :ghpull:`27925`: Backport PR #27921 on branch v3.8.x (Avoid modifying user input to Axes.bar) +* :ghpull:`27875`: macosx: Clean up single-shot timers correctly +* :ghpull:`27921`: Avoid modifying user input to Axes.bar +* :ghpull:`27903`: Merge 3.8.3-doc into 3.8.x +* :ghpull:`27889`: Backport PR #27888 on branch v3.8.x (DOC: fix stray release note entry) +* :ghpull:`27888`: DOC: fix stray release note entry +* :ghpull:`27849`: Backport PR #27754 on branch v3.8.x (fix quiver3d incorrect arrow colors) +* :ghpull:`27859`: Backport PR #27858 on branch v3.8.x (pin pytest) +* :ghpull:`27858`: pin pytest +* :ghpull:`27754`: fix quiver3d incorrect arrow colors +* :ghpull:`27847`: Backport PR #27846 on branch v3.8.x (Make example in legend_elements doc more generalisable) +* :ghpull:`27846`: Make example in legend_elements doc more generalisable +* :ghpull:`27802`: Backport PR #27794 on branch v3.8.x (Remove old reference to 72 DPI in figure_size_units.py) +* :ghpull:`27794`: Remove old reference to 72 DPI in figure_size_units.py + +Issues (3): + +* :ghissue:`27953`: [Bug]: Pyplot can no longer set axes properties +* :ghissue:`11759`: The color of the 3D arrow head does not match that of the arrow body +* :ghissue:`27826`: [Bug]: Unexpected behavior of scatter.legend_elements diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 17d1f55316b3..63f417e6a26d 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -13,6 +13,15 @@ Release notes .. include:: release_notes_next.rst +Version 3.9 +^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + next_whats_new + ../api/next_api_changes + github_stats.rst + Version 3.8 ^^^^^^^^^^^ .. toctree:: From 3a1bc7df1750716b7eac85eed57c4a41fb27d92d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 5 Apr 2024 03:21:37 -0400 Subject: [PATCH 0939/1120] REL: v3.9.0rc1 This is the first release candidate for the meso release 3.9.0. From 3b2522c134acff8b63d3950fef0f51010a15c11f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 5 Apr 2024 03:24:40 -0400 Subject: [PATCH 0940/1120] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 122a58a4edfbd29fe1b89df64ec857b6c2ab9468 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 6 Apr 2024 09:10:40 +0200 Subject: [PATCH 0941/1120] Backport PR #28026: [DOC] reshuffle of contributing --- doc/devel/contribute.rst | 353 ++++++++++++++++++++------------------- doc/devel/index.rst | 29 ++-- doc/project/citing.rst | 2 + 3 files changed, 194 insertions(+), 190 deletions(-) diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 9d476bfd81b4..7b2b0e774ec7 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -30,151 +30,64 @@ existing issue and pull request discussions, and following the conversations during pull request reviews to get context. Or you can deep-dive into a subset of the code-base to understand what is going on. -There are a few typical new contributor profiles: - -* **You are a Matplotlib user, and you see a bug, a potential improvement, or - something that annoys you, and you can fix it.** - - You can search our issue tracker for an existing issue that describes your problem or - open a new issue to inform us of the problem you observed and discuss the best approach - to fix it. If your contributions would not be captured on GitHub (social media, - communication, educational content), you can also reach out to us on gitter_, - `Discourse `__ or attend any of our `community - meetings `__. - -* **You are not a regular Matplotlib user but a domain expert: you know about - visualization, 3D plotting, design, technical writing, statistics, or some - other field where Matplotlib could be improved.** - - Awesome -- you have a focus on a specific application and domain and can - start there. In this case, maintainers can help you figure out the best - implementation; open an issue or pull request with a starting point, and we'll - be happy to discuss technical approaches. - - If you prefer, you can use the `GitHub functionality for "draft" pull requests - `__ - and request early feedback on whatever you are working on, but you should be - aware that maintainers may not review your contribution unless it has the - "Ready to review" state on GitHub. - -* **You are new to Matplotlib, both as a user and contributor, and want to start - contributing but have yet to develop a particular interest.** - - Having some previous experience or relationship with the library can be very - helpful when making open-source contributions. It helps you understand why - things are the way they are and how they *should* be. Having first-hand - experience and context is valuable both for what you can bring to the - conversation (and given the breadth of Matplotlib's usage, there is a good - chance it is a unique context in any given conversation) and make it easier to - understand where other people are coming from. - - Understanding the entire codebase is a long-term project, and nobody expects - you to do this right away. If you are determined to get started with - Matplotlib and want to learn, going through the basic functionality, - choosing something to focus on (3d, testing, documentation, animations, etc.) - and gaining context on this area by reading the issues and pull requests - touching these subjects is a reasonable approach. - -.. _get_connected: - -Get connected -============= - -Do I really have something to contribute to Matplotlib? -------------------------------------------------------- - -100% yes. There are so many ways to contribute to our community. - -When in doubt, we recommend going together! Get connected with our community of -active contributors, many of whom felt just like you when they started out and -are happy to welcome you and support you as you get to know how we work, and -where things are. Take a look at the next sections to learn more. - -.. _contributor_incubator: - -Contributor incubator ---------------------- - -The incubator is our non-public communication channel for new contributors. It -is a private gitter_ (chat) room moderated by core Matplotlib developers where -you can get guidance and support for your first few PRs. It's a place where you -can ask questions about anything: how to use git, GitHub, how our PR review -process works, technical questions about the code, what makes for good -documentation or a blog post, how to get involved in community work, or get a -"pre-review" on your PR. - -To join, please go to our public community_ channel, and ask to be added to -``#incubator``. One of our core developers will see your message and will add you. - -.. _gitter: https://gitter.im/matplotlib/matplotlib -.. _community: https://gitter.im/matplotlib/community - -New Contributors Meeting ------------------------- - -Once a month, we host a meeting to discuss topics that interest new -contributors. Anyone can attend, present, or sit in and listen to the call. -Among our attendees are fellow new contributors, as well as maintainers, and -veteran contributors, who are keen to support onboarding of new folks and -share their experience. You can find our community calendar link at the -`Scientific Python website `_, and -you can browse previous meeting notes on `GitHub -`_. -We recommend joining the meeting to clarify any doubts, or lingering -questions you might have, and to get to know a few of the people behind the -GitHub handles 😉. You can reach out to us on gitter_ for any clarifications or -suggestions. We ❤ feedback! - -.. _new_contributors: - -Good first issues ------------------ - -While any contributions are welcome, we have marked some issues as -particularly suited for new contributors by the label `good first issue -`_. These -are well documented issues, that do not require a deep understanding of the -internals of Matplotlib. The issues may additionally be tagged with a -difficulty. ``Difficulty: Easy`` is suited for people with little Python -experience. ``Difficulty: Medium`` and ``Difficulty: Hard`` require more -programming experience. This could be for a variety of reasons, among them, -though not necessarily all at the same time: - -- The issue is in areas of the code base which have more interdependencies, - or legacy code. -- It has less clearly defined tasks, which require some independent - exploration, making suggestions, or follow-up discussions to clarify a good - path to resolve the issue. -- It involves Python features such as decorators and context managers, which - have subtleties due to our implementation decisions. - -.. _managing_issues_prs: - -Work on an issue ----------------- - -In general, the Matplotlib project does not assign issues. Issues are -"assigned" or "claimed" by -:ref:`proposing a solution via a pull request `. -We ask pull request (PR) authors to -`link to the issue in the PR -`_ because then Github adds corresponding links to the PR to the discussion and the sidebar on the linked issue page on GitHub. - -Before starting to work on an issue, please check if there is already -a linked PR. If there is, try to work with the author by -submitting reviews of their code or commenting on the PR rather than opening -a new PR; duplicate PRs are unnecessary concurrent work and thus are subject -to being closed. However, if the existing -PR is an outline, unlikely to work, or stalled, and the original author is -unresponsive, feel free to open a new PR referencing the old one. - +.. dropdown:: Do I really have something to contribute to Matplotlib? + :open: + :icon: person-fill + + 100% yes! There are so many ways to contribute to our community. Take a look + at the following sections to learn more. + + There are a few typical new contributor profiles: + + * **You are a Matplotlib user, and you see a bug, a potential improvement, or + something that annoys you, and you can fix it.** + + You can search our issue tracker for an existing issue that describes your problem or + open a new issue to inform us of the problem you observed and discuss the best approach + to fix it. If your contributions would not be captured on GitHub (social media, + communication, educational content), you can also reach out to us on gitter_, + `Discourse `__ or attend any of our `community + meetings `__. + + * **You are not a regular Matplotlib user but a domain expert: you know about + visualization, 3D plotting, design, technical writing, statistics, or some + other field where Matplotlib could be improved.** + + Awesome -- you have a focus on a specific application and domain and can + start there. In this case, maintainers can help you figure out the best + implementation; open an issue or pull request with a starting point, and we'll + be happy to discuss technical approaches. + + If you prefer, you can use the `GitHub functionality for "draft" pull requests + `__ + and request early feedback on whatever you are working on, but you should be + aware that maintainers may not review your contribution unless it has the + "Ready to review" state on GitHub. + + * **You are new to Matplotlib, both as a user and contributor, and want to start + contributing but have yet to develop a particular interest.** + + Having some previous experience or relationship with the library can be very + helpful when making open-source contributions. It helps you understand why + things are the way they are and how they *should* be. Having first-hand + experience and context is valuable both for what you can bring to the + conversation (and given the breadth of Matplotlib's usage, there is a good + chance it is a unique context in any given conversation) and make it easier to + understand where other people are coming from. + + Understanding the entire codebase is a long-term project, and nobody expects + you to do this right away. If you are determined to get started with + Matplotlib and want to learn, going through the basic functionality, + choosing something to focus on (3d, testing, documentation, animations, etc.) + and gaining context on this area by reading the issues and pull requests + touching these subjects is a reasonable approach. .. _contribute_code: -Contribute code -=============== +Code +---- You want to implement a feature or fix a bug or help with maintenance - much -appreciated! Our library source code is found in +appreciated! Our library source code is found in: * Python library code: :file:`lib/` * C-extension code: :file:`src/` @@ -193,8 +106,8 @@ Code is contributed through pull requests, so we recommend that you start at .. _contribute_documentation: -Contribute documentation -======================== +Documentation +------------- You as an end-user of Matplotlib can make a valuable contribution because you more clearly see the potential for improvement than a core developer. For example, @@ -218,15 +131,16 @@ document's URL roughly corresponds to its location in our folder structure: * :file:`galleries/user_explain/` * :file:`galleries/tutorials/` * :file:`galleries/examples/` - * :file:`doc/api` + * :file:`doc/api/` .. grid-item:: information about the library * :file:`doc/install/` * :file:`doc/project/` - * :file:`doc/users/resources/` - * :file:`doc/users/faq.rst` * :file:`doc/devel/` + * :file:`doc/users/resources/index.rst` + * :file:`doc/users/faq.rst` + Other documentation is generated from the following external sources: @@ -250,25 +164,111 @@ please reach out on the :ref:`contributor_incubator` .. _other_ways_to_contribute: -Other ways to contribute -======================== - -It also helps us if you spread the word: reference the project from your blog -and articles or link to it from your website! - +Community +--------- Matplotlib's community is built by its members, if you would like to help out see our :ref:`communications-guidelines`. +It helps us if you spread the word: reference the project from your blog +and articles or link to it from your website! + If Matplotlib contributes to a project that leads to a scientific publication, -please follow the :doc:`/project/citing` guidelines. +please cite us following the :doc:`/project/citing` guidelines. If you have developed an extension to Matplotlib, please consider adding it to our `third party package `_ list. + +.. _get_connected: + +Get connected +============= +When in doubt, we recommend going together! Get connected with our community of +active contributors, many of whom felt just like you when they started out and +are happy to welcome you and support you as you get to know how we work, and +where things are. + +.. _contributor_incubator: + +Contributor incubator +--------------------- + +The incubator is our non-public communication channel for new contributors. It +is a private gitter_ (chat) room moderated by core Matplotlib developers where +you can get guidance and support for your first few PRs. It's a place where you +can ask questions about anything: how to use git, GitHub, how our PR review +process works, technical questions about the code, what makes for good +documentation or a blog post, how to get involved in community work, or get a +"pre-review" on your PR. + +To join, please go to our public community_ channel, and ask to be added to +``#incubator``. One of our core developers will see your message and will add you. + +.. _gitter: https://gitter.im/matplotlib/matplotlib +.. _community: https://gitter.im/matplotlib/community + + +.. _new_contributors: + +New Contributors Meeting +------------------------ + +Once a month, we host a meeting to discuss topics that interest new +contributors. Anyone can attend, present, or sit in and listen to the call. +Among our attendees are fellow new contributors, as well as maintainers, and +veteran contributors, who are keen to support onboarding of new folks and +share their experience. You can find our community calendar link at the +`Scientific Python website `_, and +you can browse previous meeting notes on `GitHub +`_. +We recommend joining the meeting to clarify any doubts, or lingering +questions you might have, and to get to know a few of the people behind the +GitHub handles 😉. You can reach out to us on gitter_ for any clarifications or +suggestions. We ❤ feedback! + +.. _managing_issues_prs: + +Work on an issue +================ + +In general, the Matplotlib project does not assign issues. Issues are +"assigned" or "claimed" by opening a PR; there is no other assignment +mechanism. If you have opened such a PR, please comment on the issue thread to +avoid duplication of work. Please check if there is an existing PR for the +issue you are addressing. If there is, try to work with the author by +submitting reviews of their code or commenting on the PR rather than opening +a new PR; duplicate PRs are subject to being closed. However, if the existing +PR is an outline, unlikely to work, or stalled, and the original author is +unresponsive, feel free to open a new PR referencing the old one. + +.. _good_first_issues: + +Good first issues +----------------- + +While any contributions are welcome, we have marked some issues as +particularly suited for new contributors by the label `good first issue +`_. These +are well documented issues, that do not require a deep understanding of the +internals of Matplotlib. The issues may additionally be tagged with a +difficulty. ``Difficulty: Easy`` is suited for people with little Python +experience. ``Difficulty: Medium`` and ``Difficulty: Hard`` require more +programming experience. This could be for a variety of reasons, among them, +though not necessarily all at the same time: + +- The issue is in areas of the code base which have more interdependencies, + or legacy code. +- It has less clearly defined tasks, which require some independent + exploration, making suggestions, or follow-up discussions to clarify a good + path to resolve the issue. +- It involves Python features such as decorators and context managers, which + have subtleties due to our implementation decisions. + + .. _how-to-pull-request: -How to contribute via pull request -================================== +Start a pull request +==================== The preferred way to contribute to Matplotlib is to fork the `main repository `__ on GitHub, @@ -346,26 +346,29 @@ A brief overview of the workflow is as follows. GitHub Codespaces workflows ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* If you need to open a GUI window with Matplotlib output on Codespaces, our - configuration includes a `light-weight Fluxbox-based desktop - `_. - You can use it by connecting to this desktop via your web browser. To do this: - - #. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select - ``Ports: Focus on Ports View`` in the VSCode session to bring it into - focus. Open the ports view in your tool, select the ``noVNC`` port, and - click the Globe icon. - #. In the browser that appears, click the Connect button and enter the desktop - password (``vscode`` by default). - - Check the `GitHub instructions - `_ - for more details on connecting to the desktop. - -* If you also built the documentation pages, you can view them using Codespaces. - Use the "Extensions" icon in the activity bar to install the "Live Server" - extension. Locate the ``doc/build/html`` folder in the Explorer, right click - the file you want to open and select "Open with Live Server." +If you need to open a GUI window with Matplotlib output on Codespaces, our +configuration includes a `light-weight Fluxbox-based desktop +`_. +You can use it by connecting to this desktop via your web browser. To do this: + +#. Press ``F1`` or ``Ctrl/Cmd+Shift+P`` and select + ``Ports: Focus on Ports View`` in the VSCode session to bring it into + focus. Open the ports view in your tool, select the ``noVNC`` port, and + click the Globe icon. +#. In the browser that appears, click the Connect button and enter the desktop + password (``vscode`` by default). + +Check the `GitHub instructions +`_ +for more details on connecting to the desktop. + +View documentation +"""""""""""""""""" + +If you also built the documentation pages, you can view them using Codespaces. +Use the "Extensions" icon in the activity bar to install the "Live Server" +extension. Locate the ``doc/build/html`` folder in the Explorer, right click +the file you want to open and select "Open with Live Server." Open a pull request on Matplotlib diff --git a/doc/devel/index.rst b/doc/devel/index.rst index f6075384c11a..9744d757c342 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -16,6 +16,10 @@ Contribute :octicon:`heart;1em;sd-text-info` Thank you for your interest in helping to improve Matplotlib! :octicon:`heart;1em;sd-text-info` +There are various ways to contribute: optimizing and refactoring code, detailing +unclear documentation and writing new examples, helping the community, reporting +and fixing bugs and requesting and implementing new features... + .. _submitting-a-bug-report: .. _request-a-new-feature: @@ -54,14 +58,17 @@ Matplotlib! :octicon:`heart;1em;sd-text-info` Request a feature -We welcome you to get more involved with the Matplotlib project. -There are various ways to contribute: -optimizing and refactoring code, detailing unclear documentation and writing new -examples, helping the community, reporting and fixing bugs and requesting and -implementing new features... +We welcome you to get more involved with the Matplotlib project! If you are new +to contributing, we recommend that you first read our +:ref:`contributing guide`. If you are contributing code or +documentation, please follow our guides for setting up and managing a +:ref:`development environment and workflow`. +For code, documentation, or triage, please follow the corresponding +:ref:`contribution guidelines `. New contributors ================ + .. toctree:: :hidden: @@ -77,9 +84,7 @@ New contributors :octicon:`question;1em;sd-text-info` :ref:`Where should I ask questions? ` - :octicon:`issue-opened;1em;sd-text-info` :ref:`What are "good-first-issues"? ` - - :octicon:`git-pull-request;1em;sd-text-info` :ref:`How do I claim an issue? ` + :octicon:`git-pull-request;1em;sd-text-info` :ref:`How do I work on an issue? ` :octicon:`codespaces;1em;sd-text-info` :ref:`How do I start a pull request? ` @@ -108,15 +113,9 @@ New contributors :link-type: ref :shadow: none - :octicon:`paper-airplane;1em;sd-text-info` Other ways to contribute + :octicon:`globe;1em;sd-text-info` Build community -If you are new to contributing, we recommend that you first read our -:ref:`contributing guide`. If you are contributing code or -documentation, please follow our guides for setting up and managing a -:ref:`development environment and workflow`. -For code, documentation, or triage, please follow the corresponding -:ref:`contribution guidelines `. .. _development_environment: diff --git a/doc/project/citing.rst b/doc/project/citing.rst index 4a1021ea2701..008ee794a41b 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -1,6 +1,8 @@ .. redirect-from:: /citing .. redirect-from:: /users/project/citing +.. _citing_matplotlib: + Citing Matplotlib ================= From a9e5ee3e8bb38928a65edca1445183a744295b21 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sun, 7 Apr 2024 15:16:58 +0200 Subject: [PATCH 0942/1120] Backport PR #28023: ci: Update merge conflict labeler --- .github/workflows/conflictcheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index 48be8ba510c5..3eb384fa6585 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@releases/2.x + uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" From a47be775c99cdb0cbfac7ffbcd8aaf5a7e818daa Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 9 Apr 2024 05:28:44 -0400 Subject: [PATCH 0943/1120] Backport PR #28036: BLD: Fetch version from setuptools_scm at build time --- .circleci/config.yml | 2 +- azure-pipelines.yml | 2 +- meson.build | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 468bec6eee7d..1ab22d302314 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -103,7 +103,7 @@ commands: - run: name: Install Python dependencies command: | - python -m pip install --user meson-python pybind11 + python -m pip install --user meson-python numpy pybind11 setuptools-scm python -m pip install --user \ numpy<< parameters.numpy_version >> \ -r requirements/doc/doc-requirements.txt diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cb5be2379091..2ad9a7821b5c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -135,7 +135,7 @@ stages: - bash: | python -m pip install --upgrade pip - python -m pip install --upgrade meson-python pybind11 + python -m pip install --upgrade meson-python numpy pybind11 setuptools-scm python -m pip install -r requirements/testing/all.txt -r requirements/testing/extra.txt displayName: 'Install dependencies with pip' diff --git a/meson.build b/meson.build index 21bc0ae95580..c022becfd9d9 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'matplotlib', 'c', 'cpp', - version: '3.9.0.dev0', + version: run_command(find_program('python3'), '-m', 'setuptools_scm', check: true).stdout().strip(), # qt_editor backend is MIT # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 # Carlogo, STIX and Computer Modern is OFL From 29c15c32d54f4a6988c5c728fc0374d182668dd8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 9 Apr 2024 13:42:11 -0400 Subject: [PATCH 0944/1120] DOC: Prepare release notes for 3.9.0rc2 --- doc/users/github_stats.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index e52a85a3f840..794798a3b59d 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,16 +1,16 @@ .. _github-stats: -GitHub statistics for 3.9.0 (Apr 05, 2024) +GitHub statistics for 3.9.0 (Apr 09, 2024) ========================================== -GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/05 +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/09 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 85 issues and merged 387 pull requests. +We closed 86 issues and merged 393 pull requests. The full list can be seen `on GitHub `__ -The following 170 authors contributed 2480 commits. +The following 170 authors contributed 2496 commits. * 0taj * Abdul Razak Taha @@ -185,8 +185,14 @@ The following 170 authors contributed 2480 commits. GitHub issues and pull requests: -Pull Requests (387): +Pull Requests (393): +* :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) +* :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time +* :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) +* :ghpull:`28023`: ci: Update merge conflict labeler +* :ghpull:`28035`: Backport PR #28026 on branch v3.9.x ([DOC] reshuffle of contributing) +* :ghpull:`28026`: [DOC] reshuffle of contributing * :ghpull:`28024`: DOC: Rewrite "Work on an issue" section * :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index * :ghpull:`27747`: Move doc/users/installing/ to doc/install/ @@ -575,8 +581,9 @@ Pull Requests (387): * :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions * :ghpull:`23787`: Use pybind11 for C/C++ extensions -Issues (85): +Issues (86): +* :ghissue:`28005`: [Doc]: Improve contribute instructions * :ghissue:`22376`: [ENH]: align_titles * :ghissue:`5506`: Confusing status bar values in presence of multiple axes * :ghissue:`4284`: Twin axis message coordinates From eb1db59c77a6e74bed94e9197a7de323454749c9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 9 Apr 2024 13:42:51 -0400 Subject: [PATCH 0945/1120] REL: v3.9.0rc2 This is the second release candidate for the meso release 3.9.0. From ad64904c63fd1d11f1bda49f8801c0a4421f4e18 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 9 Apr 2024 13:44:54 -0400 Subject: [PATCH 0946/1120] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 28ab92ef7915b14eb80be52c8254b02b1a5d9083 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:35:39 +0200 Subject: [PATCH 0947/1120] Backport PR #28056: Strip trailing spaces from log-formatter cursor output. --- lib/matplotlib/tests/test_ticker.py | 10 +++++----- lib/matplotlib/ticker.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 565f32105cea..36b83c95b3d3 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1177,11 +1177,11 @@ def test_pprint(self, value, domain, expected): assert label == expected @pytest.mark.parametrize('value, long, short', [ - (0.0, "0", "0 "), - (0, "0", "0 "), - (-1.0, "-10^0", "-1 "), - (2e-10, "2x10^-10", "2e-10 "), - (1e10, "10^10", "1e+10 "), + (0.0, "0", "0"), + (0, "0", "0"), + (-1.0, "-10^0", "-1"), + (2e-10, "2x10^-10", "2e-10"), + (1e10, "10^10", "1e+10"), ]) def test_format_data(self, value, long, short): fig, ax = plt.subplots() diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 16949204a218..f042372a7be9 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1024,7 +1024,7 @@ def format_data(self, value): def format_data_short(self, value): # docstring inherited - return '%-12g' % value + return ('%-12g' % value).rstrip() def _pprint_val(self, x, d): # If the number is not too big and it's an int, format it as an int. From 8824ac6922724f3af56c21b0d774b9a7449b67a0 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:50:01 +0100 Subject: [PATCH 0948/1120] Backport PR #28055: DOC: Improve inverted axis example --- .../subplots_axes_and_figures/invert_axes.py | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/galleries/examples/subplots_axes_and_figures/invert_axes.py b/galleries/examples/subplots_axes_and_figures/invert_axes.py index 15ec55d430bd..31f4d75680ce 100644 --- a/galleries/examples/subplots_axes_and_figures/invert_axes.py +++ b/galleries/examples/subplots_axes_and_figures/invert_axes.py @@ -1,25 +1,35 @@ """ -=========== -Invert Axes -=========== +============= +Inverted axis +============= -You can use decreasing axes by flipping the normal order of the axis -limits +This example demonstrates two ways to invert the direction of an axis: + +- If you want to set *explicit axis limits* anyway, e.g. via `~.Axes.set_xlim`, you + can swap the limit values: ``set_xlim(4, 0)`` instead of ``set_xlim(0, 4)``. +- Use `.Axis.set_inverted` if you only want to invert the axis *without modifying + the limits*, i.e. keep existing limits or existing autoscaling behavior. """ import matplotlib.pyplot as plt import numpy as np -t = np.arange(0.01, 5.0, 0.01) -s = np.exp(-t) +x = np.arange(0.01, 4.0, 0.01) +y = np.exp(-x) + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6.4, 4), layout="constrained") +fig.suptitle('Inverted axis with ...') -fig, ax = plt.subplots() +ax1.plot(x, y) +ax1.set_xlim(4, 0) # inverted fixed limits +ax1.set_title('fixed limits: set_xlim(4, 0)') +ax1.set_xlabel('decreasing x ⟶') +ax1.grid(True) -ax.plot(t, s) -ax.set_xlim(5, 0) # decreasing time -ax.set_xlabel('decreasing time (s)') -ax.set_ylabel('voltage (mV)') -ax.set_title('Should be growing...') -ax.grid(True) +ax2.plot(x, y) +ax2.xaxis.set_inverted(True) # inverted axis with autoscaling +ax2.set_title('autoscaling: set_inverted(True)') +ax2.set_xlabel('decreasing x ⟶') +ax2.grid(True) plt.show() From 4c140989cc446b62ad3f196a5363923b783642fd Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:38:33 +0200 Subject: [PATCH 0949/1120] Backport PR #28077: Parent tk StringVar to the canvas widget, not to the toolbar. --- lib/matplotlib/backends/_backend_tk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 5eca9229b61d..693499f4ca01 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -843,7 +843,7 @@ def save_figure(self, *args): default_extension = self.canvas.get_default_filetype() default_filetype = self.canvas.get_supported_filetypes()[default_extension] - filetype_variable = tk.StringVar(self, default_filetype) + filetype_variable = tk.StringVar(self.canvas.get_tk_widget(), default_filetype) # adding a default extension seems to break the # asksaveasfilename dialog when you choose various save types From 37fbeb87299dd3b8cc25c2d530df6bec89bbd9e9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 15 Apr 2024 09:46:43 -0400 Subject: [PATCH 0950/1120] Backport PR #28078: Clarify that findfont & _find_fonts_by_props return paths. --- lib/matplotlib/font_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 73da3c418dd7..312e8ee97246 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1235,7 +1235,7 @@ def score_size(self, size1, size2): def findfont(self, prop, fontext='ttf', directory=None, fallback_to_default=True, rebuild_if_missing=True): """ - Find a font that most closely matches the given font properties. + Find the path to the font file most closely matching the given font properties. Parameters ---------- @@ -1305,7 +1305,7 @@ def get_font_names(self): def _find_fonts_by_props(self, prop, fontext='ttf', directory=None, fallback_to_default=True, rebuild_if_missing=True): """ - Find font families that most closely match the given properties. + Find the paths to the font files most closely matching the given properties. Parameters ---------- @@ -1335,7 +1335,7 @@ def _find_fonts_by_props(self, prop, fontext='ttf', directory=None, Returns ------- list[str] - The paths of the fonts found + The paths of the fonts found. Notes ----- From 9fadef9b30672e6ff9e8bdb65566d6cfa8688f2b Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:40:04 +0200 Subject: [PATCH 0951/1120] Backport PR #28087: Document Qt5 minimal version. --- doc/install/dependencies.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index 45dc249832ca..93c1990b9472 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -54,7 +54,8 @@ and the capabilities they provide. * Tk_ (>= 8.5, != 8.6.0 or 8.6.1): for the Tk-based backends. Tk is part of most standard Python installations, but it's not part of Python itself and thus may not be present in rare cases. -* PyQt6_ (>= 6.1), PySide6_, PyQt5_, or PySide2_: for the Qt-based backends. +* PyQt6_ (>= 6.1), PySide6_, PyQt5_ (>= 5.12), or PySide2_: for the Qt-based + backends. * PyGObject_ and pycairo_ (>= 1.14.0): for the GTK-based backends. If using pip (but not conda or system package manager) PyGObject must be built from source; see `pygobject documentation From 87ca28745340b25f05b91945f93888d08ba69d23 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 17 Apr 2024 07:11:24 +0200 Subject: [PATCH 0952/1120] Backport PR #28032: FIX: ensure images are C order before passing to pillow --- lib/matplotlib/image.py | 1 + lib/matplotlib/tests/test_image.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 5b0152505397..2e13293028ca 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1640,6 +1640,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, # we modify this below, so make a copy (don't modify caller's dict) pil_kwargs = pil_kwargs.copy() pil_shape = (rgba.shape[1], rgba.shape[0]) + rgba = np.require(rgba, requirements='C') image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) if format == "png": diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index fdbba7299d2b..1602f86716cb 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -205,6 +205,14 @@ def test_imsave(fmt): assert_array_equal(arr_dpi1, arr_dpi100) +@pytest.mark.parametrize("origin", ["upper", "lower"]) +def test_imsave_rgba_origin(origin): + # test that imsave always passes c-contiguous arrays down to pillow + buf = io.BytesIO() + result = np.zeros((10, 10, 4), dtype='uint8') + mimage.imsave(buf, arr=result, format="png", origin=origin) + + @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"]) def test_imsave_fspath(fmt): plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt) From 2ea48d39da0a219ee17faaf8ce3ecf67f71e9e89 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 17 Apr 2024 09:56:07 -0700 Subject: [PATCH 0953/1120] Backport PR #28094: DOC: exclude sphinx 7.3.* --- requirements/doc/doc-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 173fa30eafbf..8f8e01a34e4d 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -7,7 +7,7 @@ # Install the documentation requirements with: # pip install -r requirements/doc/doc-requirements.txt # -sphinx>=3.0.0,!=6.1.2 +sphinx>=3.0.0,!=6.1.2,!=7.3.* colorspacious ipython ipywidgets From df5ccceca2cb5c6b8be21b5638d31e2017e9d133 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:49:09 +0200 Subject: [PATCH 0954/1120] Backport PR #28107: [DOC] Fix description in CapStyle example --- galleries/examples/lines_bars_and_markers/capstyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/capstyle.py b/galleries/examples/lines_bars_and_markers/capstyle.py index f573aaa871b8..d17f86c6be58 100644 --- a/galleries/examples/lines_bars_and_markers/capstyle.py +++ b/galleries/examples/lines_bars_and_markers/capstyle.py @@ -3,8 +3,8 @@ CapStyle ========= -The `matplotlib._enums.CapStyle` controls how Matplotlib draws the corners -where two different line segments meet. For more details, see the +The `matplotlib._enums.CapStyle` controls how Matplotlib draws the two +endpoints (caps) of an unclosed line. For more details, see the `~matplotlib._enums.CapStyle` docs. """ From 077e6436192f5aaae6c942b5bee834f06a5264b7 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 19 Apr 2024 21:43:16 +0200 Subject: [PATCH 0955/1120] Backport PR #28100: TST: wxcairo sometimes raises OSError on missing cairo libraries --- lib/matplotlib/tests/test_getattr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_getattr.py b/lib/matplotlib/tests/test_getattr.py index a34e82ed81ba..f0f5823600ca 100644 --- a/lib/matplotlib/tests/test_getattr.py +++ b/lib/matplotlib/tests/test_getattr.py @@ -26,7 +26,7 @@ def test_getattr(module_name): """ try: module = import_module(module_name) - except (ImportError, RuntimeError) as e: + 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}') From 92fc90db29960473589a57e33b0f16bf77c88ca9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Apr 2024 20:48:15 -0400 Subject: [PATCH 0956/1120] Backport PR #28102: Fix typo in color mapping documentation in quick_start.py --- galleries/users_explain/quick_start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/users_explain/quick_start.py b/galleries/users_explain/quick_start.py index cf5e73555e27..1970f71f737c 100644 --- a/galleries/users_explain/quick_start.py +++ b/galleries/users_explain/quick_start.py @@ -503,7 +503,7 @@ def my_plotter(ax, data1, data2, param_dict): # Color mapped data # ================= # -# Often we want to have a third dimension in a plot represented by a colors in +# Often we want to have a third dimension in a plot represented by colors in # a colormap. Matplotlib has a number of plot types that do this: X, Y = np.meshgrid(np.linspace(-3, 3, 128), np.linspace(-3, 3, 128)) From 282e58a9452282cbb64fb2c8bfa5e55b1b316232 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:29:24 +0200 Subject: [PATCH 0957/1120] Backport PR #28085: Clarify that the pgf backend is never actually used interactively. --- galleries/users_explain/text/pgf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/galleries/users_explain/text/pgf.py b/galleries/users_explain/text/pgf.py index 0c63ec368043..9bcfe34a24b7 100644 --- a/galleries/users_explain/text/pgf.py +++ b/galleries/users_explain/text/pgf.py @@ -30,7 +30,9 @@ The last method allows you to keep using regular interactive backends and to save xelatex, lualatex or pdflatex compiled PDF files from the graphical user -interface. +interface. Note that, in that case, the interactive display will still use the +standard interactive backends (e.g., QtAgg), and in particular use latex to +compile relevant text snippets. Matplotlib's pgf support requires a recent LaTeX_ installation that includes the TikZ/PGF packages (such as TeXLive_), preferably with XeLaTeX or LuaLaTeX From 03b23838d2ad9475f96a3c73dca6182ffe3cac3e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:52:14 +0200 Subject: [PATCH 0958/1120] Backport PR #28134: DOC: Minor improvements on quickstart --- .flake8 | 1 + galleries/users_explain/quick_start.py | 48 ++++++++++++++------------ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.flake8 b/.flake8 index b568af917f46..36e8bcf5476f 100644 --- a/.flake8 +++ b/.flake8 @@ -47,6 +47,7 @@ per-file-ignores = lib/mpl_toolkits/axisartist/angle_helper.py: E221 doc/conf.py: E402 + galleries/users_explain/quick_start.py: E402 galleries/users_explain/artists/paths.py: E402 galleries/users_explain/artists/patheffects_guide.py: E402 galleries/users_explain/artists/transforms_tutorial.py: E402, E501 diff --git a/galleries/users_explain/quick_start.py b/galleries/users_explain/quick_start.py index 1970f71f737c..30740f74b898 100644 --- a/galleries/users_explain/quick_start.py +++ b/galleries/users_explain/quick_start.py @@ -17,7 +17,6 @@ import numpy as np # sphinx_gallery_thumbnail_number = 3 -import matplotlib as mpl # %% # @@ -29,16 +28,18 @@ # area where points can be specified in terms of x-y coordinates (or theta-r # in a polar plot, x-y-z in a 3D plot, etc.). The simplest way of # creating a Figure with an Axes is using `.pyplot.subplots`. We can then use -# `.Axes.plot` to draw some data on the Axes: +# `.Axes.plot` to draw some data on the Axes, and `~.pyplot.show` to display +# the figure: -fig, ax = plt.subplots() # Create a figure containing a single Axes. +fig, ax = plt.subplots() # Create a figure containing a single Axes. ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # Plot some data on the Axes. +plt.show() # Show the figure. # %% # -# Note that to get this Figure to display, you may have to call ``plt.show()``, -# depending on your backend. For more details of Figures and backends, see -# :ref:`figure-intro`. +# Depending on the environment you are working in, ``plt.show()`` can be left +# out. This is for example the case with Jupyter notebooks, which +# automatically show all figures created in a code cell. # # .. _figure_parts: # @@ -54,24 +55,24 @@ # # The **whole** figure. The Figure keeps # track of all the child :class:`~matplotlib.axes.Axes`, a group of -# 'special' Artists (titles, figure legends, colorbars, etc), and +# 'special' Artists (titles, figure legends, colorbars, etc.), and # even nested subfigures. # -# The easiest way to create a new Figure is with pyplot:: +# Typically, you'll create a new Figure through one of the following +# functions:: # -# fig = plt.figure() # an empty figure with no Axes -# fig, ax = plt.subplots() # a figure with a single Axes +# fig = plt.figure() # an empty figure with no Axes +# fig, ax = plt.subplots() # a figure with a single Axes # fig, axs = plt.subplots(2, 2) # a figure with a 2x2 grid of Axes # # a figure with one Axes on the left, and two on the right: # fig, axs = plt.subplot_mosaic([['left', 'right_top'], # ['left', 'right_bottom']]) # -# It is often convenient to create the Axes together with the Figure, but you -# can also manually add Axes later on. Note that many -# :ref:`Matplotlib backends ` support zooming and -# panning on figure windows. +# `~.pyplot.subplots()` and `~.pyplot.subplot_mosaic` are convenience functions +# that additionally create Axes objects inside the Figure, but you can also +# manually add Axes later on. # -# For more on Figures, see :ref:`figure-intro`. +# For more on Figures, including panning and zooming, see :ref:`figure-intro`. # # :class:`~matplotlib.axes.Axes` # ------------------------------ @@ -86,10 +87,9 @@ # :meth:`~matplotlib.axes.Axes.set_xlabel`), and a y-label set via # :meth:`~matplotlib.axes.Axes.set_ylabel`). # -# The :class:`~.axes.Axes` class and its member functions are the primary -# entry point to working with the OOP interface, and have most of the -# plotting methods defined on them (e.g. ``ax.plot()``, shown above, uses -# the `~.Axes.plot` method) +# The `~.axes.Axes` methods are the primary interface for configuring +# most parts of your plot (adding data, controlling axis scales and +# limits, adding labels etc.). # # :class:`~matplotlib.axis.Axis` # ------------------------------ @@ -446,13 +446,14 @@ def my_plotter(ax, data1, data2, param_dict): # well as floating point numbers. These get special locators and formatters # as appropriate. For dates: +from matplotlib.dates import ConciseDateFormatter + fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained') dates = np.arange(np.datetime64('2021-11-15'), np.datetime64('2021-12-25'), np.timedelta64(1, 'h')) data = np.cumsum(np.random.randn(len(dates))) ax.plot(dates, data) -cdf = mpl.dates.ConciseDateFormatter(ax.xaxis.get_major_locator()) -ax.xaxis.set_major_formatter(cdf) +ax.xaxis.set_major_formatter(ConciseDateFormatter(ax.xaxis.get_major_locator())) # %% # For more information see the date examples @@ -506,6 +507,8 @@ def my_plotter(ax, data1, data2, param_dict): # Often we want to have a third dimension in a plot represented by colors in # a colormap. Matplotlib has a number of plot types that do this: +from matplotlib.colors import LogNorm + X, Y = np.meshgrid(np.linspace(-3, 3, 128), np.linspace(-3, 3, 128)) Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2) @@ -518,8 +521,7 @@ def my_plotter(ax, data1, data2, param_dict): fig.colorbar(co, ax=axs[0, 1]) axs[0, 1].set_title('contourf()') -pc = axs[1, 0].imshow(Z**2 * 100, cmap='plasma', - norm=mpl.colors.LogNorm(vmin=0.01, vmax=100)) +pc = axs[1, 0].imshow(Z**2 * 100, cmap='plasma', norm=LogNorm(vmin=0.01, vmax=100)) fig.colorbar(pc, ax=axs[1, 0], extend='both') axs[1, 0].set_title('imshow() with LogNorm()') From db8abc20c22a375465abe301d421ec23ed6220df Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 25 Apr 2024 14:39:48 -0500 Subject: [PATCH 0959/1120] Backport PR #28068: [TYP] Add possible type hint to `colors` argument in `LinearSegmentedColormap.from_list` --- lib/matplotlib/colors.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 9bb1725f4f78..514801b714b8 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -124,7 +124,7 @@ class LinearSegmentedColormap(Colormap): def set_gamma(self, gamma: float) -> None: ... @staticmethod def from_list( - name: str, colors: ArrayLike, N: int = ..., gamma: float = ... + name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ... ) -> LinearSegmentedColormap: ... def resampled(self, lutsize: int) -> LinearSegmentedColormap: ... def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ... From 191b3399e07069ef15bb399aea73b70ec4619340 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 25 Apr 2024 11:53:38 +0200 Subject: [PATCH 0960/1120] Backport PR #28136: Appease pycodestyle. --- lib/matplotlib/backends/backend_ps.py | 3 +-- lib/matplotlib/projections/polar.py | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index d760bef04d19..5f224f38af1e 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -362,8 +362,7 @@ def create_hatch(self, hatch): /PaintProc {{ pop {linewidth:g} setlinewidth -{self._convert_path( - Path.hatch(hatch), Affine2D().scale(sidelen), simplify=False)} +{self._convert_path(Path.hatch(hatch), Affine2D().scale(sidelen), simplify=False)} gsave fill grestore diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 6bd72d2e35e0..8d3e03f64e7c 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -270,11 +270,7 @@ def __call__(self, x, pos=None): vmin, vmax = self.axis.get_view_interval() d = np.rad2deg(abs(vmax - vmin)) digits = max(-int(np.log10(d) - 1.5), 0) - # Use Unicode rather than mathtext with \circ, so that it will work - # correctly with any arbitrary font (assuming it has a degree sign), - # whereas $5\circ$ will only work correctly with one of the supported - # math fonts (Computer Modern and STIX). - return f"{np.rad2deg(x):0.{digits:d}f}\N{DEGREE SIGN}" + return f"{np.rad2deg(x):0.{digits}f}\N{DEGREE SIGN}" class _AxisWrapper: From 3005cd53ab1e02e242df0da49397ea6869676056 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 25 Apr 2024 17:31:29 -0500 Subject: [PATCH 0961/1120] Backport PR #27960: Update AppVeyor config --- .appveyor.yml | 10 ++++------ lib/matplotlib/tests/test_tightlayout.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 01f2a2fb6e21..87f6cbde6384 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -29,12 +29,9 @@ environment: --cov-report= --cov=lib --log-level=DEBUG matrix: - - PYTHON_VERSION: "3.9" + - PYTHON_VERSION: "3.11" CONDA_INSTALL_LOCN: "C:\\Miniconda3-x64" - TEST_ALL: "no" - - PYTHON_VERSION: "3.10" - CONDA_INSTALL_LOCN: "C:\\Miniconda3-x64" - TEST_ALL: "no" + TEST_ALL: "yes" # We always use a 64-bit machine, but can build x86 distributions # with the PYTHON_ARCH variable @@ -77,7 +74,8 @@ test_script: - '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' # this are optional dependencies so that we don't skip so many tests... - - if x%TEST_ALL% == xyes conda install -q ffmpeg inkscape miktex + - if x%TEST_ALL% == xyes conda install -q ffmpeg inkscape + # miktex is available on conda, but seems to fail with permission errors. # missing packages on conda-forge for imagemagick # This install sometimes failed randomly :-( # - choco install imagemagick diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 0f83cca6b642..9c654f4d1f48 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -130,7 +130,7 @@ def test_tight_layout7(): plt.tight_layout() -@image_comparison(['tight_layout8']) +@image_comparison(['tight_layout8'], tol=0.005) def test_tight_layout8(): """Test automatic use of tight_layout.""" fig = plt.figure() From 46b6f23f0d6f92068e959c058dea80b331c4e2af Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 May 2024 10:31:05 -0400 Subject: [PATCH 0962/1120] Backport PR #28157: Remove call to non-existent method _default_contains in Artist --- lib/matplotlib/artist.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index b79d3cc62338..d5b8631e95df 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -493,9 +493,6 @@ def contains(self, mouseevent): such as which points are contained in the pick radius. See the individual Artist subclasses for details. """ - inside, info = self._default_contains(mouseevent) - if inside is not None: - return inside, info _log.warning("%r needs 'contains' method", self.__class__.__name__) return False, {} From f7975549dac1727081acdf5f5560be3b61b56673 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Wed, 1 May 2024 22:18:51 +0200 Subject: [PATCH 0963/1120] Backport PR #28039: Respect vertical_axis when rotating plot interactively --- lib/mpl_toolkits/mplot3d/axes3d.py | 14 ++++++++--- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 9ca5692c40ab..d0f5c8d2b23b 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1147,7 +1147,8 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", if roll is None: roll = self.initial_roll vertical_axis = _api.check_getitem( - dict(x=0, y=1, z=2), vertical_axis=vertical_axis + {name: idx for idx, name in enumerate(self._axis_names)}, + vertical_axis=vertical_axis, ) if share: @@ -1318,7 +1319,7 @@ def shareview(self, other): raise ValueError("view angles are already shared") self._shared_axes["view"].join(self, other) self._shareview = other - vertical_axis = {0: "x", 1: "y", 2: "z"}[other._vertical_axis] + vertical_axis = self._axis_names[other._vertical_axis] self.view_init(elev=other.elev, azim=other.azim, roll=other.roll, vertical_axis=vertical_axis, share=True) @@ -1523,7 +1524,14 @@ def _on_move(self, event): dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) elev = self.elev + delev azim = self.azim + dazim - self.view_init(elev=elev, azim=azim, roll=roll, share=True) + vertical_axis = self._axis_names[self._vertical_axis] + self.view_init( + elev=elev, + azim=azim, + roll=roll, + vertical_axis=vertical_axis, + share=True, + ) self.stale = True # Pan diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 7662509dd9cf..731b0413bf65 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2250,6 +2250,31 @@ def test_view_init_vertical_axis( np.testing.assert_array_equal(tickdir_expected, tickdir_actual) +@pytest.mark.parametrize("vertical_axis", ["x", "y", "z"]) +def test_on_move_vertical_axis(vertical_axis: str) -> None: + """ + Test vertical axis is respected when rotating the plot interactively. + """ + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.figure.canvas.draw() + + proj_before = ax.get_proj() + event_click = mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=1) + ax._button_press(event_click) + + event_move = mock_event(ax, button=MouseButton.LEFT, xdata=0.5, ydata=0.8) + ax._on_move(event_move) + + assert ax._axis_names.index(vertical_axis) == ax._vertical_axis + + # Make sure plot has actually moved: + proj_after = ax.get_proj() + np.testing.assert_raises( + AssertionError, np.testing.assert_allclose, proj_before, proj_after + ) + + @image_comparison(baseline_images=['arc_pathpatch.png'], remove_text=True, style='mpl20') From 2fec35ac6007eabd5d84244fe096297b723c98d4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 2 May 2024 15:06:23 -0400 Subject: [PATCH 0964/1120] Backport PR #27948: Move IPython backend mapping to Matplotlib and support entry points --- .github/workflows/tests.yml | 3 +- doc/users/next_whats_new/backend_registry.rst | 13 +- galleries/users_explain/figure/backends.rst | 14 +- .../users_explain/figure/figure_intro.rst | 31 +- .../writing_a_backend_pyplot_interface.rst | 44 ++ lib/matplotlib/__init__.py | 4 +- lib/matplotlib/backend_bases.py | 15 +- lib/matplotlib/backends/registry.py | 376 ++++++++++++++++-- lib/matplotlib/backends/registry.pyi | 23 +- lib/matplotlib/cbook.py | 9 - lib/matplotlib/cbook.pyi | 1 - lib/matplotlib/pyplot.py | 31 +- lib/matplotlib/rcsetup.py | 12 +- lib/matplotlib/testing/__init__.py | 38 ++ lib/matplotlib/testing/__init__.pyi | 5 + lib/matplotlib/tests/test_backend_inline.py | 46 +++ lib/matplotlib/tests/test_backend_macosx.py | 5 + lib/matplotlib/tests/test_backend_nbagg.py | 10 + lib/matplotlib/tests/test_backend_qt.py | 6 +- lib/matplotlib/tests/test_backend_registry.py | 105 ++++- .../tests/test_backends_interactive.py | 2 +- lib/matplotlib/tests/test_inline_01.ipynb | 79 ++++ lib/matplotlib/tests/test_matplotlib.py | 2 +- lib/matplotlib/tests/test_nbagg_01.ipynb | 27 +- 24 files changed, 803 insertions(+), 98 deletions(-) create mode 100644 lib/matplotlib/tests/test_backend_inline.py create mode 100644 lib/matplotlib/tests/test_inline_01.ipynb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1cead94098a8..13f6e8352d73 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,7 +59,8 @@ jobs: delete-font-cache: true - os: ubuntu-20.04 python-version: 3.9 - extra-requirements: '-r requirements/testing/extra.txt' + # One CI run tests ipython/matplotlib-inline before backend mapping moved to mpl + extra-requirements: '-r requirements/testing/extra.txt "ipython<8.24" "matplotlib-inline<0.1.7"' CFLAGS: "-fno-lto" # Ensure that disabling LTO works. # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html diff --git a/doc/users/next_whats_new/backend_registry.rst b/doc/users/next_whats_new/backend_registry.rst index 61b65a9d6470..7632c978f9c5 100644 --- a/doc/users/next_whats_new/backend_registry.rst +++ b/doc/users/next_whats_new/backend_registry.rst @@ -3,4 +3,15 @@ BackendRegistry New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single source of truth for available backends. The singleton instance is -``matplotlib.backends.backend_registry``. +``matplotlib.backends.backend_registry``. It is used internally by Matplotlib, +and also IPython (and therefore Jupyter) starting with IPython 8.24.0. + +There are three sources of backends: built-in (source code is within the +Matplotlib repository), explicit ``module://some.backend`` syntax (backend is +obtained by loading the module), or via an entry point (self-registering +backend in an external package). + +To obtain a list of all registered backends use: + + >>> from matplotlib.backends import backend_registry + >>> backend_registry.list_all() diff --git a/galleries/users_explain/figure/backends.rst b/galleries/users_explain/figure/backends.rst index 0aa20fc58862..dc6d8a89457d 100644 --- a/galleries/users_explain/figure/backends.rst +++ b/galleries/users_explain/figure/backends.rst @@ -175,7 +175,8 @@ QtAgg Agg rendering in a Qt_ canvas (requires PyQt_ or `Qt for Python`_, more details. ipympl Agg rendering embedded in a Jupyter widget (requires ipympl_). This backend can be enabled in a Jupyter notebook with - ``%matplotlib ipympl``. + ``%matplotlib ipympl`` or ``%matplotlib widget``. Works with + Jupyter ``lab`` and ``notebook>=7``. GTK3Agg Agg rendering to a GTK_ 3.x canvas (requires PyGObject_ and pycairo_). This backend can be activated in IPython with ``%matplotlib gtk3``. @@ -188,7 +189,8 @@ TkAgg Agg rendering to a Tk_ canvas (requires TkInter_). This backend can be activated in IPython with ``%matplotlib tk``. nbAgg Embed an interactive figure in a Jupyter classic notebook. This backend can be enabled in Jupyter notebooks via - ``%matplotlib notebook``. + ``%matplotlib notebook`` or ``%matplotlib nbagg``. Works with + Jupyter ``notebook<7`` and ``nbclassic``. WebAgg On ``show()`` will start a tornado server with an interactive figure. GTK3Cairo Cairo rendering to a GTK_ 3.x canvas (requires PyGObject_ and @@ -200,7 +202,7 @@ wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4). ========= ================================================================ .. note:: - The names of builtin backends case-insensitive; e.g., 'QtAgg' and + The names of builtin backends are case-insensitive; e.g., 'QtAgg' and 'qtagg' are equivalent. .. _`Anti-Grain Geometry`: http://agg.sourceforge.net/antigrain.com/ @@ -222,11 +224,13 @@ wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4). .. _wxWidgets: https://www.wxwidgets.org/ .. _ipympl: https://www.matplotlib.org/ipympl +.. _ipympl_install: + ipympl ^^^^^^ -The Jupyter widget ecosystem is moving too fast to support directly in -Matplotlib. To install ipympl: +The ipympl backend is in a separate package that must be explicitly installed +if you wish to use it, for example: .. code-block:: bash diff --git a/galleries/users_explain/figure/figure_intro.rst b/galleries/users_explain/figure/figure_intro.rst index 462a3fc848dc..80cbb3aeeb45 100644 --- a/galleries/users_explain/figure/figure_intro.rst +++ b/galleries/users_explain/figure/figure_intro.rst @@ -52,14 +52,20 @@ Notebooks and IDEs If you are using a Notebook (e.g. `Jupyter `_) or an IDE that renders Notebooks (PyCharm, VSCode, etc), then they have a backend that -will render the Matplotlib Figure when a code cell is executed. One thing to -be aware of is that the default Jupyter backend (``%matplotlib inline``) will +will render the Matplotlib Figure when a code cell is executed. The default +Jupyter backend (``%matplotlib inline``) creates static plots that by default trim or expand the figure size to have a tight box around Artists -added to the Figure (see :ref:`saving_figures`, below). If you use a backend -other than the default "inline" backend, you will likely need to use an ipython -"magic" like ``%matplotlib notebook`` for the Matplotlib :ref:`notebook -` or ``%matplotlib widget`` for the `ipympl -`_ backend. +added to the Figure (see :ref:`saving_figures`, below). For interactive plots +in Jupyter you will need to use an ipython "magic" like ``%matplotlib widget`` +for the `ipympl `_ backend in ``jupyter lab`` +or ``notebook>=7``, or ``%matplotlib notebook`` for the Matplotlib +:ref:`notebook ` in ``notebook<7`` or +``nbclassic``. + +.. note:: + + The `ipympl `_ backend is in a separate + package, see :ref:`Installing ipympl `. .. figure:: /_static/FigureNotebook.png :alt: Image of figure generated in Jupyter Notebook with notebook @@ -75,15 +81,6 @@ other than the default "inline" backend, you will likely need to use an ipython .. seealso:: :ref:`interactive_figures`. -.. note:: - - If you only need to use the classic notebook (i.e. ``notebook<7``), - you can use: - - .. sourcecode:: ipython - - %matplotlib notebook - .. _standalone-scripts-and-interactive-use: Standalone scripts and interactive use @@ -104,7 +101,7 @@ backend. These are typically chosen either in the user's :ref:`matplotlibrc QtAgg backend. When run from a script, or interactively (e.g. from an -`iPython shell `_) the Figure +`IPython shell `_) the Figure will not be shown until we call ``plt.show()``. The Figure will appear in a new GUI window, and usually will have a toolbar with Zoom, Pan, and other tools for interacting with the Figure. By default, ``plt.show()`` blocks diff --git a/galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst b/galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst index 452f4d7610bb..c8dccc24da43 100644 --- a/galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst +++ b/galleries/users_explain/figure/writing_a_backend_pyplot_interface.rst @@ -84,3 +84,47 @@ Function-based API 2. **Showing figures**: `.pyplot.show()` calls a module-level ``show()`` function, which is typically generated via the ``ShowBase`` class and its ``mainloop`` method. + +Registering a backend +--------------------- + +For a new backend to be usable via ``matplotlib.use()`` or IPython +``%matplotlib`` magic command, it must be compatible with one of the three ways +supported by the :class:`~matplotlib.backends.registry.BackendRegistry`: + +Built-in +^^^^^^^^ + +A backend built into Matplotlib must have its name and +``FigureCanvas.required_interactive_framework`` hard-coded in the +:class:`~matplotlib.backends.registry.BackendRegistry`. If the backend module +is not ``f"matplotlib.backends.backend_{backend_name.lower()}"`` then there +must also be an entry in the ``BackendRegistry._name_to_module``. + +module:// syntax +^^^^^^^^^^^^^^^^ + +Any backend in a separate module (not built into Matplotlib) can be used by +specifying the path to the module in the form ``module://some.backend.module``. +An example is ``module://mplcairo.qt`` for +`mplcairo `_. The backend's +interactive framework will be taken from its +``FigureCanvas.required_interactive_framework``. + +Entry point +^^^^^^^^^^^ + +An external backend module can self-register as a backend using an +``entry point`` in its ``pyproject.toml`` such as the one used by +``matplotlib-inline``: + +.. code-block:: toml + + [project.entry-points."matplotlib.backend"] + inline = "matplotlib_inline.backend_inline" + +The backend's interactive framework will be taken from its +``FigureCanvas.required_interactive_framework``. All entry points are loaded +together but only when first needed, such as when a backend name is not +recognised as a built-in backend, or when +:meth:`~matplotlib.backends.registry.BackendRegistry.list_all` is first called. diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index cc94e530133b..9e9325a27d73 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1208,7 +1208,7 @@ def use(backend, *, force=True): backend names, which are case-insensitive: - interactive backends: - GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, QtAgg, + GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, notebook, QtAgg, QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo - non-interactive backends: @@ -1216,6 +1216,8 @@ def use(backend, *, force=True): or a string of the form: ``module://my.module.name``. + notebook is a synonym for nbAgg. + Switching to an interactive backend is not possible if an unrelated event loop has already been started (e.g., switching to GTK3Agg if a TkAgg window has already been opened). Switching to a non-interactive diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e90c110c193b..d7430a4494fd 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1766,8 +1766,16 @@ def _fix_ipython_backend2gui(cls): # `ipython --auto`). This cannot be done at import time due to # ordering issues, so we do it when creating a canvas, and should only # be done once per class (hence the `cache`). - if sys.modules.get("IPython") is None: + + # This function will not be needed when Python 3.12, the latest version + # supported by IPython < 8.24, reaches end-of-life in late 2028. + # At that time this function can be made a no-op and deprecated. + mod_ipython = sys.modules.get("IPython") + if mod_ipython is None or mod_ipython.version_info[:2] >= (8, 24): + # Use of backend2gui is not needed for IPython >= 8.24 as the + # functionality has been moved to Matplotlib. return + import IPython ip = IPython.get_ipython() if not ip: @@ -2030,9 +2038,8 @@ def _switch_canvas_and_return_print_method(self, fmt, backend=None): canvas = None if backend is not None: # Return a specific canvas class, if requested. - canvas_class = ( - importlib.import_module(cbook._backend_module_name(backend)) - .FigureCanvas) + from .backends.registry import backend_registry + canvas_class = backend_registry.load_backend_module(backend).FigureCanvas if not hasattr(canvas_class, f"print_{fmt}"): raise ValueError( f"The {backend!r} backend does not support {fmt} output") diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 484d6ed5f26d..19b4cba254ab 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -1,4 +1,5 @@ from enum import Enum +import importlib class BackendFilter(Enum): @@ -20,36 +21,168 @@ class BackendRegistry: All use of ``BackendRegistry`` should be via the singleton instance ``backend_registry`` which can be imported from ``matplotlib.backends``. + Each backend has a name, a module name containing the backend code, and an + optional GUI framework that must be running if the backend is interactive. + There are three sources of backends: built-in (source code is within the + Matplotlib repository), explicit ``module://some.backend`` syntax (backend is + obtained by loading the module), or via an entry point (self-registering + backend in an external package). + .. versionadded:: 3.9 """ - # Built-in backends are those which are included in the Matplotlib repo. - # A backend with name 'name' is located in the module - # f'matplotlib.backends.backend_{name.lower()}' - - # The capitalized forms are needed for ipython at present; this may - # change for later versions. - _BUILTIN_INTERACTIVE = [ - "GTK3Agg", "GTK3Cairo", "GTK4Agg", "GTK4Cairo", - "MacOSX", - "nbAgg", - "QtAgg", "QtCairo", "Qt5Agg", "Qt5Cairo", - "TkAgg", "TkCairo", - "WebAgg", - "WX", "WXAgg", "WXCairo", - ] - _BUILTIN_NOT_INTERACTIVE = [ - "agg", "cairo", "pdf", "pgf", "ps", "svg", "template", - ] - _GUI_FRAMEWORK_TO_BACKEND_MAPPING = { - "qt": "qtagg", + # Mapping of built-in backend name to GUI framework, or "headless" for no + # GUI framework. Built-in backends are those which are included in the + # Matplotlib repo. A backend with name 'name' is located in the module + # f"matplotlib.backends.backend_{name.lower()}" + _BUILTIN_BACKEND_TO_GUI_FRAMEWORK = { + "gtk3agg": "gtk3", + "gtk3cairo": "gtk3", + "gtk4agg": "gtk4", + "gtk4cairo": "gtk4", + "macosx": "macosx", + "nbagg": "nbagg", + "notebook": "nbagg", + "qtagg": "qt", + "qtcairo": "qt", + "qt5agg": "qt5", + "qt5cairo": "qt5", + "tkagg": "tk", + "tkcairo": "tk", + "webagg": "webagg", + "wx": "wx", + "wxagg": "wx", + "wxcairo": "wx", + "agg": "headless", + "cairo": "headless", + "pdf": "headless", + "pgf": "headless", + "ps": "headless", + "svg": "headless", + "template": "headless", + } + + # Reverse mapping of gui framework to preferred built-in backend. + _GUI_FRAMEWORK_TO_BACKEND = { "gtk3": "gtk3agg", "gtk4": "gtk4agg", - "wx": "wxagg", - "tk": "tkagg", - "macosx": "macosx", "headless": "agg", + "macosx": "macosx", + "qt": "qtagg", + "qt5": "qt5agg", + "qt6": "qtagg", + "tk": "tkagg", + "wx": "wxagg", } + def __init__(self): + # Only load entry points when first needed. + self._loaded_entry_points = False + + # Mapping of non-built-in backend to GUI framework, added dynamically from + # entry points and from matplotlib.use("module://some.backend") format. + # New entries have an "unknown" GUI framework that is determined when first + # needed by calling _get_gui_framework_by_loading. + self._backend_to_gui_framework = {} + + # Mapping of backend name to module name, where different from + # f"matplotlib.backends.backend_{backend_name.lower()}". These are either + # hardcoded for backward compatibility, or loaded from entry points or + # "module://some.backend" syntax. + self._name_to_module = { + "notebook": "nbagg", + } + + def _backend_module_name(self, backend): + # Return name of module containing the specified backend. + # Does not check if the backend is valid, use is_valid_backend for that. + backend = backend.lower() + + # Check if have specific name to module mapping. + backend = self._name_to_module.get(backend, backend) + + return (backend[9:] if backend.startswith("module://") + else f"matplotlib.backends.backend_{backend}") + + def _clear(self): + # Clear all dynamically-added data, used for testing only. + self.__init__() + + def _ensure_entry_points_loaded(self): + # Load entry points, if they have not already been loaded. + if not self._loaded_entry_points: + entries = self._read_entry_points() + self._validate_and_store_entry_points(entries) + self._loaded_entry_points = True + + def _get_gui_framework_by_loading(self, backend): + # Determine GUI framework for a backend by loading its module and reading the + # FigureCanvas.required_interactive_framework attribute. + # Returns "headless" if there is no GUI framework. + module = self.load_backend_module(backend) + canvas_class = module.FigureCanvas + return canvas_class.required_interactive_framework or "headless" + + def _read_entry_points(self): + # Read entry points of modules that self-advertise as Matplotlib backends. + # Expects entry points like this one from matplotlib-inline (in pyproject.toml + # format): + # [project.entry-points."matplotlib.backend"] + # inline = "matplotlib_inline.backend_inline" + import importlib.metadata as im + import sys + + # entry_points group keyword not available before Python 3.10 + group = "matplotlib.backend" + if sys.version_info >= (3, 10): + entry_points = im.entry_points(group=group) + else: + entry_points = im.entry_points().get(group, ()) + entries = [(entry.name, entry.value) for entry in entry_points] + + # For backward compatibility, if matplotlib-inline and/or ipympl are installed + # but too old to include entry points, create them. Do not import ipympl + # directly as this calls matplotlib.use() whilst in this function. + def backward_compatible_entry_points( + entries, module_name, threshold_version, names, target): + from matplotlib import _parse_to_version_info + try: + module_version = im.version(module_name) + if _parse_to_version_info(module_version) < threshold_version: + for name in names: + entries.append((name, target)) + except im.PackageNotFoundError: + pass + + names = [entry[0] for entry in entries] + if "inline" not in names: + backward_compatible_entry_points( + entries, "matplotlib_inline", (0, 1, 7), ["inline"], + "matplotlib_inline.backend_inline") + if "ipympl" not in names: + backward_compatible_entry_points( + entries, "ipympl", (0, 9, 4), ["ipympl", "widget"], + "ipympl.backend_nbagg") + + return entries + + def _validate_and_store_entry_points(self, entries): + # Validate and store entry points so that they can be used via matplotlib.use() + # in the normal manner. Entry point names cannot be of module:// format, cannot + # shadow a built-in backend name, and cannot be duplicated. + for name, module in entries: + name = name.lower() + if name.startswith("module://"): + raise RuntimeError( + f"Entry point name '{name}' cannot start with 'module://'") + if name in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK: + raise RuntimeError(f"Entry point name '{name}' is a built-in backend") + if name in self._backend_to_gui_framework: + raise RuntimeError(f"Entry point name '{name}' duplicated") + + self._name_to_module[name] = "module://" + module + # Do not yet know backend GUI framework, determine it only when necessary. + self._backend_to_gui_framework[name] = "unknown" + def backend_for_gui_framework(self, framework): """ Return the name of the backend corresponding to the specified GUI framework. @@ -61,10 +194,74 @@ def backend_for_gui_framework(self, framework): Returns ------- - str - Backend name. + str or None + Backend name or None if GUI framework not recognised. + """ + return self._GUI_FRAMEWORK_TO_BACKEND.get(framework.lower()) + + def is_valid_backend(self, backend): + """ + Return True if the backend name is valid, False otherwise. + + A backend name is valid if it is one of the built-in backends or has been + dynamically added via an entry point. Those beginning with ``module://`` are + always considered valid and are added to the current list of all backends + within this function. + + Even if a name is valid, it may not be importable or usable. This can only be + determined by loading and using the backend module. + + Parameters + ---------- + backend : str + Name of backend. + + Returns + ------- + bool + True if backend is valid, False otherwise. + """ + backend = backend.lower() + + # For backward compatibility, convert ipympl and matplotlib-inline long + # module:// names to their shortened forms. + backwards_compat = { + "module://ipympl.backend_nbagg": "widget", + "module://matplotlib_inline.backend_inline": "inline", + } + backend = backwards_compat.get(backend, backend) + + if (backend in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK or + backend in self._backend_to_gui_framework): + return True + + if backend.startswith("module://"): + self._backend_to_gui_framework[backend] = "unknown" + return True + + # Only load entry points if really need to and not already done so. + self._ensure_entry_points_loaded() + if backend in self._backend_to_gui_framework: + return True + + return False + + def list_all(self): + """ + Return list of all known backends. + + These include built-in backends and those obtained at runtime either from entry + points or explicit ``module://some.backend`` syntax. + + Entry points will be loaded if they haven't been already. + + Returns + ------- + list of str + Backend names. """ - return self._GUI_FRAMEWORK_TO_BACKEND_MAPPING.get(framework) + self._ensure_entry_points_loaded() + return [*self.list_builtin(), *self._backend_to_gui_framework] def list_builtin(self, filter_=None): """ @@ -82,11 +279,132 @@ def list_builtin(self, filter_=None): Backend names. """ if filter_ == BackendFilter.INTERACTIVE: - return self._BUILTIN_INTERACTIVE + return [k for k, v in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.items() + if v != "headless"] elif filter_ == BackendFilter.NON_INTERACTIVE: - return self._BUILTIN_NOT_INTERACTIVE + return [k for k, v in self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.items() + if v == "headless"] + + return [*self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK] + + def list_gui_frameworks(self): + """ + Return list of GUI frameworks used by Matplotlib backends. + + Returns + ------- + list of str + GUI framework names. + """ + return [k for k in self._GUI_FRAMEWORK_TO_BACKEND if k != "headless"] + + def load_backend_module(self, backend): + """ + Load and return the module containing the specified backend. + + Parameters + ---------- + backend : str + Name of backend to load. + + Returns + ------- + Module + Module containing backend. + """ + module_name = self._backend_module_name(backend) + return importlib.import_module(module_name) + + def resolve_backend(self, backend): + """ + Return the backend and GUI framework for the specified backend name. + + If the GUI framework is not yet known then it will be determined by loading the + backend module and checking the ``FigureCanvas.required_interactive_framework`` + attribute. + + This function only loads entry points if they have not already been loaded and + the backend is not built-in and not of ``module://some.backend`` format. + + Parameters + ---------- + backend : str or None + Name of backend, or None to use the default backend. + + Returns + ------- + backend : str + The backend name. + framework : str or None + The GUI framework, which will be None for a backend that is non-interactive. + """ + if isinstance(backend, str): + backend = backend.lower() + else: # Might be _auto_backend_sentinel or None + # Use whatever is already running... + from matplotlib import get_backend + backend = get_backend() + + # Is backend already known (built-in or dynamically loaded)? + gui = (self._BUILTIN_BACKEND_TO_GUI_FRAMEWORK.get(backend) or + self._backend_to_gui_framework.get(backend)) + + # Is backend "module://something"? + if gui is None and isinstance(backend, str) and backend.startswith("module://"): + gui = "unknown" + + # Is backend a possible entry point? + if gui is None and not self._loaded_entry_points: + self._ensure_entry_points_loaded() + gui = self._backend_to_gui_framework.get(backend) + + # Backend known but not its gui framework. + if gui == "unknown": + gui = self._get_gui_framework_by_loading(backend) + self._backend_to_gui_framework[backend] = gui + + if gui is None: + raise RuntimeError(f"'{backend}' is not a recognised backend name") + + return backend, gui if gui != "headless" else None + + def resolve_gui_or_backend(self, gui_or_backend): + """ + Return the backend and GUI framework for the specified string that may be + either a GUI framework or a backend name, tested in that order. + + This is for use with the IPython %matplotlib magic command which may be a GUI + framework such as ``%matplotlib qt`` or a backend name such as + ``%matplotlib qtagg``. + + This function only loads entry points if they have not already been loaded and + the backend is not built-in and not of ``module://some.backend`` format. + + Parameters + ---------- + gui_or_backend : str or None + Name of GUI framework or backend, or None to use the default backend. + + Returns + ------- + backend : str + The backend name. + framework : str or None + The GUI framework, which will be None for a backend that is non-interactive. + """ + gui_or_backend = gui_or_backend.lower() + + # First check if it is a gui loop name. + backend = self.backend_for_gui_framework(gui_or_backend) + if backend is not None: + return backend, gui_or_backend if gui_or_backend != "headless" else None - return self._BUILTIN_INTERACTIVE + self._BUILTIN_NOT_INTERACTIVE + # Then check if it is a backend name. + try: + return self.resolve_backend(gui_or_backend) + except Exception: # KeyError ? + raise RuntimeError( + f"'{gui_or_backend} is not a recognised GUI loop or backend name") # Singleton diff --git a/lib/matplotlib/backends/registry.pyi b/lib/matplotlib/backends/registry.pyi index e48531be471d..e1ae5b3e7d3a 100644 --- a/lib/matplotlib/backends/registry.pyi +++ b/lib/matplotlib/backends/registry.pyi @@ -1,4 +1,5 @@ from enum import Enum +from types import ModuleType class BackendFilter(Enum): @@ -7,8 +8,28 @@ class BackendFilter(Enum): class BackendRegistry: - def backend_for_gui_framework(self, interactive_framework: str) -> str | None: ... + _BUILTIN_BACKEND_TO_GUI_FRAMEWORK: dict[str, str] + _GUI_FRAMEWORK_TO_BACKEND: dict[str, str] + + _loaded_entry_points: bool + _backend_to_gui_framework: dict[str, str] + _name_to_module: dict[str, str] + + def _backend_module_name(self, backend: str) -> str: ... + def _clear(self) -> None: ... + def _ensure_entry_points_loaded(self) -> None: ... + def _get_gui_framework_by_loading(self, backend: str) -> str: ... + def _read_entry_points(self) -> list[tuple[str, str]]: ... + def _validate_and_store_entry_points(self, entries: list[tuple[str, str]]) -> None: ... + + def backend_for_gui_framework(self, framework: str) -> str | None: ... + def is_valid_backend(self, backend: str) -> bool: ... + def list_all(self) -> list[str]: ... def list_builtin(self, filter_: BackendFilter | None) -> list[str]: ... + def list_gui_frameworks(self) -> list[str]: ... + def load_backend_module(self, backend: str) -> ModuleType: ... + def resolve_backend(self, backend: str | None) -> tuple[str, str | None]: ... + def resolve_gui_or_backend(self, gui_or_backend: str | None) -> tuple[str, str | None]: ... backend_registry: BackendRegistry diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index a41bfe56744f..a156ac200abf 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2224,15 +2224,6 @@ def _check_and_log_subprocess(command, logger, **kwargs): return proc.stdout -def _backend_module_name(name): - """ - Convert a backend name (either a standard backend -- "Agg", "TkAgg", ... -- - or a custom backend -- "module://...") to the corresponding module name). - """ - return (name[9:] if name.startswith("module://") - else f"matplotlib.backends.backend_{name.lower()}") - - def _setup_new_guiapp(): """ Perform OS-dependent setup when Matplotlib creates a new GUI application. diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index 3216c4c92b9e..d727b8065b7a 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -176,7 +176,6 @@ class _OrderedSet(collections.abc.MutableSet): def add(self, key) -> None: ... def discard(self, key) -> None: ... -def _backend_module_name(name: str) -> str: ... def _setup_new_guiapp() -> None: ... def _format_approx(number: float, precision: int) -> str: ... def _g_sig_digits(value: float, delta: float) -> int: ... diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 2376c6243929..b1354341617d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -295,11 +295,16 @@ def install_repl_displayhook() -> None: ip.events.register("post_execute", _draw_all_if_interactive) _REPL_DISPLAYHOOK = _ReplDisplayHook.IPYTHON - from IPython.core.pylabtools import backend2gui - # trigger IPython's eventloop integration, if available - ipython_gui_name = backend2gui.get(get_backend()) - if ipython_gui_name: - ip.enable_gui(ipython_gui_name) + if mod_ipython.version_info[:2] < (8, 24): + # Use of backend2gui is not needed for IPython >= 8.24 as that functionality + # has been moved to Matplotlib. + # This code can be removed when Python 3.12, the latest version supported by + # IPython < 8.24, reaches end-of-life in late 2028. + from IPython.core.pylabtools import backend2gui + # trigger IPython's eventloop integration, if available + ipython_gui_name = backend2gui.get(get_backend()) + if ipython_gui_name: + ip.enable_gui(ipython_gui_name) def uninstall_repl_displayhook() -> None: @@ -402,7 +407,7 @@ def switch_backend(newbackend: str) -> None: # have to escape the switch on access logic old_backend = dict.__getitem__(rcParams, 'backend') - module = importlib.import_module(cbook._backend_module_name(newbackend)) + module = backend_registry.load_backend_module(newbackend) canvas_class = module.FigureCanvas required_framework = canvas_class.required_interactive_framework @@ -477,6 +482,18 @@ def draw_if_interactive() -> None: _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) + if newbackend in ("ipympl", "widget"): + # ipympl < 0.9.4 expects rcParams["backend"] to be the fully-qualified backend + # name "module://ipympl.backend_nbagg" not short names "ipympl" or "widget". + import importlib.metadata as im + from matplotlib import _parse_to_version_info # type: ignore[attr-defined] + try: + module_version = im.version("ipympl") + if _parse_to_version_info(module_version) < (0, 9, 4): + newbackend = "module://ipympl.backend_nbagg" + except im.PackageNotFoundError: + pass + rcParams['backend'] = rcParamsDefault['backend'] = newbackend _backend_mod = backend_mod for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: @@ -2586,7 +2603,7 @@ def polar(*args, **kwargs) -> list[Line2D]: if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore[attr-defined] set(backend_registry.list_builtin(BackendFilter.INTERACTIVE)) - - {'WebAgg', 'nbAgg'}) + {'webagg', 'nbagg'}) and cbook._get_running_interactive_framework()): rcParams._set("backend", rcsetup._auto_backend_sentinel) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a326d22f039a..b0cd22098489 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -266,16 +266,16 @@ def validate_fonttype(s): return fonttype -_validate_standard_backends = ValidateInStrings( - 'backend', backend_registry.list_builtin(), ignorecase=True) _auto_backend_sentinel = object() def validate_backend(s): - backend = ( - s if s is _auto_backend_sentinel or s.startswith("module://") - else _validate_standard_backends(s)) - return backend + if s is _auto_backend_sentinel or backend_registry.is_valid_backend(s): + return s + else: + msg = (f"'{s}' is not a valid value for backend; supported values are " + f"{backend_registry.list_all()}") + raise ValueError(msg) def _validate_toolbar(s): diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 685b98cd99ec..779149dec2dc 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -177,3 +177,41 @@ def _has_tex_package(package): return True except FileNotFoundError: return False + + +def ipython_in_subprocess( + requested_backend_or_gui_framework, + expected_backend_old_ipython, # IPython < 8.24 + expected_backend_new_ipython, # IPython >= 8.24 +): + import pytest + IPython = pytest.importorskip("IPython") + + if sys.platform == "win32": + pytest.skip("Cannot change backend running IPython in subprocess on Windows") + + if (IPython.version_info[:3] == (8, 24, 0) and + requested_backend_or_gui_framework == "osx"): + pytest.skip("Bug using macosx backend in IPython 8.24.0 fixed in 8.24.1") + + if IPython.version_info[:2] >= (8, 24): + expected_backend = expected_backend_new_ipython + else: + # This code can be removed when Python 3.12, the latest version supported by + # IPython < 8.24, reaches end-of-life in late 2028. + expected_backend = expected_backend_old_ipython + + code = ("import matplotlib as mpl, matplotlib.pyplot as plt;" + "fig, ax=plt.subplots(); ax.plot([1, 3, 2]); mpl.get_backend()") + proc = subprocess_run_for_testing( + [ + "ipython", + "--no-simple-prompt", + f"--matplotlib={requested_backend_or_gui_framework}", + "-c", code, + ], + check=True, + capture_output=True, + ) + + assert proc.stdout.strip() == f"Out[1]: '{expected_backend}'" diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi index 30cfd9a9ed2e..b0399476b6aa 100644 --- a/lib/matplotlib/testing/__init__.pyi +++ b/lib/matplotlib/testing/__init__.pyi @@ -47,3 +47,8 @@ def subprocess_run_helper( ) -> subprocess.CompletedProcess[str]: ... def _check_for_pgf(texsystem: str) -> bool: ... def _has_tex_package(package: str) -> bool: ... +def ipython_in_subprocess( + requested_backend_or_gui_framework: str, + expected_backend_old_ipython: str, + expected_backend_new_ipython: str, +) -> None: ... diff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py new file mode 100644 index 000000000000..6f0d67d51756 --- /dev/null +++ b/lib/matplotlib/tests/test_backend_inline.py @@ -0,0 +1,46 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory + +import pytest + +from matplotlib.testing import subprocess_run_for_testing + +nbformat = pytest.importorskip('nbformat') +pytest.importorskip('nbconvert') +pytest.importorskip('ipykernel') +pytest.importorskip('matplotlib_inline') + + +def test_ipynb(): + nb_path = Path(__file__).parent / 'test_inline_01.ipynb' + + with TemporaryDirectory() as tmpdir: + out_path = Path(tmpdir, "out.ipynb") + + subprocess_run_for_testing( + ["jupyter", "nbconvert", "--to", "notebook", + "--execute", "--ExecutePreprocessor.timeout=500", + "--output", str(out_path), str(nb_path)], + env={**os.environ, "IPYTHONDIR": tmpdir}, + check=True) + with out_path.open() as out: + nb = nbformat.read(out, nbformat.current_nbformat) + + errors = [output for cell in nb.cells for output in cell.get("outputs", []) + if output.output_type == "error"] + assert not errors + + import IPython + if IPython.version_info[:2] >= (8, 24): + expected_backend = "inline" + else: + # This code can be removed when Python 3.12, the latest version supported by + # IPython < 8.24, reaches end-of-life in late 2028. + expected_backend = "module://matplotlib_inline.backend_inline" + backend_outputs = nb.cells[2]["outputs"] + assert backend_outputs[0]["data"]["text/plain"] == f"'{expected_backend}'" + + image = nb.cells[1]["outputs"][1]["data"] + assert image["text/plain"] == "

" + assert "image/png" in image diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index c460da374c8c..a4350fe3b6c6 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -44,3 +44,8 @@ def new_choose_save_file(title, directory, filename): # Check the savefig.directory rcParam got updated because # we added a subdirectory "test" assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test" + + +def test_ipython(): + from matplotlib.testing import ipython_in_subprocess + ipython_in_subprocess("osx", "MacOSX", "macosx") diff --git a/lib/matplotlib/tests/test_backend_nbagg.py b/lib/matplotlib/tests/test_backend_nbagg.py index 40bee8f85c43..23af88d95086 100644 --- a/lib/matplotlib/tests/test_backend_nbagg.py +++ b/lib/matplotlib/tests/test_backend_nbagg.py @@ -30,3 +30,13 @@ def test_ipynb(): errors = [output for cell in nb.cells for output in cell.get("outputs", []) if output.output_type == "error"] assert not errors + + import IPython + if IPython.version_info[:2] >= (8, 24): + expected_backend = "notebook" + else: + # This code can be removed when Python 3.12, the latest version supported by + # IPython < 8.24, reaches end-of-life in late 2028. + expected_backend = "nbAgg" + backend_outputs = nb.cells[2]["outputs"] + assert backend_outputs[0]["data"]["text/plain"] == f"'{expected_backend}'" diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index f4a7ef6755f2..026a49b1441e 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -14,7 +14,6 @@ from matplotlib._pylab_helpers import Gcf from matplotlib import _c_internal_utils - try: from matplotlib.backends.qt_compat import QtGui, QtWidgets # type: ignore # noqa from matplotlib.backends.qt_editor import _formlayout @@ -375,3 +374,8 @@ def custom_handler(signum, frame): finally: # Reset SIGINT handler to what it was before the test signal.signal(signal.SIGINT, original_handler) + + +def test_ipython(): + from matplotlib.testing import ipython_in_subprocess + ipython_in_subprocess("qt", "QtAgg", "qtagg") diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index aed258f36413..eaf8417e7a5f 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -7,6 +7,15 @@ from matplotlib.backends import BackendFilter, backend_registry +@pytest.fixture +def clear_backend_registry(): + # Fixture that clears the singleton backend_registry before and after use + # so that the test state remains isolated. + backend_registry._clear() + yield + backend_registry._clear() + + def has_duplicates(seq: Sequence[Any]) -> bool: return len(seq) > len(set(seq)) @@ -33,9 +42,10 @@ def test_list_builtin(): assert not has_duplicates(backends) # Compare using sets as order is not important assert {*backends} == { - 'GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', - 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', - 'WXCairo', 'agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template', + 'gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook', + 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg', + 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo', 'agg', 'cairo', 'pdf', 'pgf', + 'ps', 'svg', 'template', } @@ -43,9 +53,9 @@ def test_list_builtin(): 'filter,expected', [ (BackendFilter.INTERACTIVE, - ['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', - 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', - 'WXCairo']), + ['gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook', + 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg', + 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo']), (BackendFilter.NON_INTERACTIVE, ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']), ] @@ -57,6 +67,25 @@ def test_list_builtin_with_filter(filter, expected): assert {*backends} == {*expected} +def test_list_gui_frameworks(): + frameworks = backend_registry.list_gui_frameworks() + assert not has_duplicates(frameworks) + # Compare using sets as order is not important + assert {*frameworks} == { + "gtk3", "gtk4", "macosx", "qt", "qt5", "qt6", "tk", "wx", + } + + +@pytest.mark.parametrize("backend, is_valid", [ + ("agg", True), + ("QtAgg", True), + ("module://anything", True), + ("made-up-name", False), +]) +def test_is_valid_backend(backend, is_valid): + assert backend_registry.is_valid_backend(backend) == is_valid + + def test_deprecated_rcsetup_attributes(): match = "was deprecated in Matplotlib 3.9" with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): @@ -65,3 +94,67 @@ def test_deprecated_rcsetup_attributes(): mpl.rcsetup.non_interactive_bk with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): mpl.rcsetup.all_backends + + +def test_entry_points_inline(): + pytest.importorskip('matplotlib_inline') + backends = backend_registry.list_all() + assert 'inline' in backends + + +def test_entry_points_ipympl(): + pytest.importorskip('ipympl') + backends = backend_registry.list_all() + assert 'ipympl' in backends + assert 'widget' in backends + + +def test_entry_point_name_shadows_builtin(clear_backend_registry): + with pytest.raises(RuntimeError): + backend_registry._validate_and_store_entry_points( + [('qtagg', 'module1')]) + + +def test_entry_point_name_duplicate(clear_backend_registry): + with pytest.raises(RuntimeError): + backend_registry._validate_and_store_entry_points( + [('some_name', 'module1'), ('some_name', 'module2')]) + + +def test_entry_point_name_is_module(clear_backend_registry): + with pytest.raises(RuntimeError): + backend_registry._validate_and_store_entry_points( + [('module://backend.something', 'module1')]) + + +@pytest.mark.parametrize('backend', [ + 'agg', + 'module://matplotlib.backends.backend_agg', +]) +def test_load_entry_points_only_if_needed(clear_backend_registry, backend): + assert not backend_registry._loaded_entry_points + check = backend_registry.resolve_backend(backend) + assert check == (backend, None) + assert not backend_registry._loaded_entry_points + backend_registry.list_all() # Force load of entry points + assert backend_registry._loaded_entry_points + + +@pytest.mark.parametrize( + 'gui_or_backend, expected_backend, expected_gui', + [ + ('agg', 'agg', None), + ('qt', 'qtagg', 'qt'), + ('TkCairo', 'tkcairo', 'tk'), + ] +) +def test_resolve_gui_or_backend(gui_or_backend, expected_backend, expected_gui): + backend, gui = backend_registry.resolve_gui_or_backend(gui_or_backend) + assert backend == expected_backend + assert gui == expected_gui + + +def test_resolve_gui_or_backend_invalid(): + match = "is not a recognised GUI loop or backend name" + with pytest.raises(RuntimeError, match=match): + backend_registry.resolve_gui_or_backend('no-such-name') diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index e021405c56b7..6830e7d5c845 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -291,7 +291,7 @@ def _test_thread_impl(): plt.pause(0.5) # flush_events fails here on at least Tkagg (bpo-41176) future.result() # Joins the thread; rethrows any exception. plt.close() # backend is responsible for flushing any events here - if plt.rcParams["backend"].startswith("WX"): + if plt.rcParams["backend"].lower().startswith("wx"): # TODO: debug why WX needs this only on py >= 3.8 fig.canvas.flush_events() diff --git a/lib/matplotlib/tests/test_inline_01.ipynb b/lib/matplotlib/tests/test_inline_01.ipynb new file mode 100644 index 000000000000..b87ae095bdbe --- /dev/null +++ b/lib/matplotlib/tests/test_inline_01.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(3, 2))\n", + "ax.plot([1, 3, 2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib\n", + "matplotlib.get_backend()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + }, + "toc": { + "colors": { + "hover_highlight": "#DAA520", + "running_highlight": "#FF0000", + "selected_highlight": "#FFD700" + }, + "moveMenuLeft": true, + "nav_menu": { + "height": "12px", + "width": "252px" + }, + "navigate_menu": true, + "number_sections": true, + "sideBar": true, + "threshold": 4, + "toc_cell": false, + "toc_section_display": "block", + "toc_window_display": false, + "widenNotebook": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index a2f467ac48de..37b41fafdb78 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -54,7 +54,7 @@ def parse(key): for line in matplotlib.use.__doc__.split(key)[1].split('\n'): if not line.strip(): break - backends += [e.strip() for e in line.split(',') if e] + backends += [e.strip().lower() for e in line.split(',') if e] return backends from matplotlib.backends import BackendFilter, backend_registry diff --git a/lib/matplotlib/tests/test_nbagg_01.ipynb b/lib/matplotlib/tests/test_nbagg_01.ipynb index 8505e057fdc3..bd18aa4192b7 100644 --- a/lib/matplotlib/tests/test_nbagg_01.ipynb +++ b/lib/matplotlib/tests/test_nbagg_01.ipynb @@ -8,9 +8,8 @@ }, "outputs": [], "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib notebook\n" + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" ] }, { @@ -826,17 +825,31 @@ ], "source": [ "fig, ax = plt.subplots()\n", - "ax.plot(range(10))\n" + "ax.plot(range(10))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "'notebook'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import matplotlib\n", + "matplotlib.get_backend()" + ] } ], "metadata": { From 7130c9c7ecc49cb379e94fecbb51f8278d8c3145 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 2 May 2024 21:35:19 +0200 Subject: [PATCH 0965/1120] Backport PR #28144: DOC: Refactor code in the fishbone diagram example --- .../specialty_plots/ishikawa_diagram.py | 157 +++++++++--------- 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/galleries/examples/specialty_plots/ishikawa_diagram.py b/galleries/examples/specialty_plots/ishikawa_diagram.py index 18761ca36043..072d7b463c00 100644 --- a/galleries/examples/specialty_plots/ishikawa_diagram.py +++ b/galleries/examples/specialty_plots/ishikawa_diagram.py @@ -9,11 +9,12 @@ Source: https://en.wikipedia.org/wiki/Ishikawa_diagram """ +import math + import matplotlib.pyplot as plt from matplotlib.patches import Polygon, Wedge -# Create the fishbone diagram fig, ax = plt.subplots(figsize=(10, 6), layout='constrained') ax.set_xlim(-5, 5) ax.set_ylim(-5, 5) @@ -22,18 +23,18 @@ def problems(data: str, problem_x: float, problem_y: float, - prob_angle_x: float, prob_angle_y: float): + angle_x: float, angle_y: float): """ Draw each problem section of the Ishikawa plot. Parameters ---------- data : str - The category name. + The name of the problem category. problem_x, problem_y : float, optional The `X` and `Y` positions of the problem arrows (`Y` defaults to zero). - prob_angle_x, prob_angle_y : float, optional - The angle of the problem annotations. They are angled towards + angle_x, angle_y : float, optional + The angle of the problem annotations. They are always angled towards the tail of the plot. Returns @@ -42,8 +43,8 @@ def problems(data: str, """ ax.annotate(str.upper(data), xy=(problem_x, problem_y), - xytext=(prob_angle_x, prob_angle_y), - fontsize='10', + xytext=(angle_x, angle_y), + fontsize=10, color='white', weight='bold', xycoords='data', @@ -56,7 +57,8 @@ def problems(data: str, pad=0.8)) -def causes(data: list, cause_x: float, cause_y: float, +def causes(data: list, + cause_x: float, cause_y: float, cause_xytext=(-9, -0.3), top: bool = True): """ Place each cause to a position relative to the problems @@ -72,7 +74,9 @@ def causes(data: list, cause_x: float, cause_y: float, cause_xytext : tuple, optional Adjust to set the distance of the cause text from the problem arrow in fontsize units. - top : bool + top : bool, default: True + Determines whether the next cause annotation will be + plotted above or below the previous one. Returns ------- @@ -80,26 +84,23 @@ def causes(data: list, cause_x: float, cause_y: float, """ for index, cause in enumerate(data): - # First cause annotation is placed in the middle of the problems arrow + # [, ] + coords = [[0.02, 0], + [0.23, 0.5], + [-0.46, -1], + [0.69, 1.5], + [-0.92, -2], + [1.15, 2.5]] + + # First 'cause' annotation is placed in the middle of the 'problems' arrow # and each subsequent cause is plotted above or below it in succession. - - # [, [, ]] - coords = [[0, [0, 0]], - [0.23, [0.5, -0.5]], - [-0.46, [-1, 1]], - [0.69, [1.5, -1.5]], - [-0.92, [-2, 2]], - [1.15, [2.5, -2.5]]] - if top: - cause_y += coords[index][1][0] - else: - cause_y += coords[index][1][1] cause_x -= coords[index][0] + cause_y += coords[index][1] if top else -coords[index][1] ax.annotate(cause, xy=(cause_x, cause_y), horizontalalignment='center', xytext=cause_xytext, - fontsize='9', + fontsize=9, xycoords='data', textcoords='offset fontsize', arrowprops=dict(arrowstyle="->", @@ -108,82 +109,74 @@ def causes(data: list, cause_x: float, cause_y: float, def draw_body(data: dict): """ - Place each section in its correct place by changing + Place each problem section in its correct place by changing the coordinates on each loop. Parameters ---------- data : dict - The input data (can be list or tuple). ValueError is - raised if more than six arguments are passed. + The input data (can be a dict of lists or tuples). ValueError + is raised if more than six arguments are passed. Returns ------- None. """ - second_sections = [] - third_sections = [] - # Resize diagram to automatically scale in response to the number - # of problems in the input data. - if len(data) == 1 or len(data) == 2: - spine_length = (-2.1, 2) - head_pos = (2, 0) - tail_pos = ((-2.8, 0.8), (-2.8, -0.8), (-2.0, -0.01)) - first_section = [1.6, 0.8] - elif len(data) == 3 or len(data) == 4: - spine_length = (-3.1, 3) - head_pos = (3, 0) - tail_pos = ((-3.8, 0.8), (-3.8, -0.8), (-3.0, -0.01)) - first_section = [2.6, 1.8] - second_sections = [-0.4, -1.2] - else: # len(data) == 5 or 6 - spine_length = (-4.1, 4) - head_pos = (4, 0) - tail_pos = ((-4.8, 0.8), (-4.8, -0.8), (-4.0, -0.01)) - first_section = [3.5, 2.7] - second_sections = [1, 0.2] - third_sections = [-1.5, -2.3] - - # Change the coordinates of the annotations on each loop. + # Set the length of the spine according to the number of 'problem' categories. + length = (math.ceil(len(data) / 2)) - 1 + draw_spine(-2 - length, 2 + length) + + # Change the coordinates of the 'problem' annotations after each one is rendered. + offset = 0 + prob_section = [1.55, 0.8] for index, problem in enumerate(data.values()): - top_row = True - cause_arrow_y = 1.7 - if index % 2 != 0: # Plot problems below the spine. - top_row = False - y_prob_angle = -16 - cause_arrow_y = -1.7 - else: # Plot problems above the spine. - y_prob_angle = 16 - # Plot the 3 sections in pairs along the main spine. - if index in (0, 1): - prob_arrow_x = first_section[0] - cause_arrow_x = first_section[1] - elif index in (2, 3): - prob_arrow_x = second_sections[0] - cause_arrow_x = second_sections[1] - else: - prob_arrow_x = third_sections[0] - cause_arrow_x = third_sections[1] + plot_above = index % 2 == 0 + cause_arrow_y = 1.7 if plot_above else -1.7 + y_prob_angle = 16 if plot_above else -16 + + # Plot each section in pairs along the main spine. + prob_arrow_x = prob_section[0] + length + offset + cause_arrow_x = prob_section[1] + length + offset + if not plot_above: + offset -= 2.5 if index > 5: raise ValueError(f'Maximum number of problems is 6, you have entered ' f'{len(data)}') - # draw main spine - ax.plot(spine_length, [0, 0], color='tab:blue', linewidth=2) - # draw fish head - ax.text(head_pos[0] + 0.1, head_pos[1] - 0.05, 'PROBLEM', fontsize=10, - weight='bold', color='white') - semicircle = Wedge(head_pos, 1, 270, 90, fc='tab:blue') - ax.add_patch(semicircle) - # draw fishtail - triangle = Polygon(tail_pos, fc='tab:blue') - ax.add_patch(triangle) - # Pass each category name to the problems function as a string on each loop. problems(list(data.keys())[index], prob_arrow_x, 0, -12, y_prob_angle) - # Start the cause function with the first annotation being plotted at - # the cause_arrow_x, cause_arrow_y coordinates. - causes(problem, cause_arrow_x, cause_arrow_y, top=top_row) + causes(problem, cause_arrow_x, cause_arrow_y, top=plot_above) + + +def draw_spine(xmin: int, xmax: int): + """ + Draw main spine, head and tail. + + Parameters + ---------- + xmin : int + The default position of the head of the spine's + x-coordinate. + xmax : int + The default position of the tail of the spine's + x-coordinate. + + Returns + ------- + None. + + """ + # draw main spine + ax.plot([xmin - 0.1, xmax], [0, 0], color='tab:blue', linewidth=2) + # draw fish head + ax.text(xmax + 0.1, - 0.05, 'PROBLEM', fontsize=10, + weight='bold', color='white') + semicircle = Wedge((xmax, 0), 1, 270, 90, fc='tab:blue') + ax.add_patch(semicircle) + # draw fish tail + tail_pos = [[xmin - 0.8, 0.8], [xmin - 0.8, -0.8], [xmin, -0.01]] + triangle = Polygon(tail_pos, fc='tab:blue') + ax.add_patch(triangle) # Input data From 8308d91c5c038719ad9e26c1b93b955a4d6357da Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 6 May 2024 00:07:26 +0200 Subject: [PATCH 0966/1120] Backport PR #28169: Clarify public-ness of some ToolContainerBase APIs. --- lib/matplotlib/backend_bases.py | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d7430a4494fd..740c01226f7d 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3395,11 +3395,17 @@ def trigger_tool(self, name): def add_toolitem(self, name, group, position, image, description, toggle): """ - Add a toolitem to the container. + A hook to add a toolitem to the container. - This method must be implemented per backend. + This hook must be implemented in each backend and contains the + backend-specific code to add an element to the toolbar. - The callback associated with the button click event, + .. warning:: + This is part of the backend implementation and should + not be called by end-users. They should instead call + `.ToolContainerBase.add_tool`. + + The callback associated with the button click event must be *exactly* ``self.trigger_tool(name)``. Parameters @@ -3425,7 +3431,16 @@ def add_toolitem(self, name, group, position, image, description, toggle): def toggle_toolitem(self, name, toggled): """ - Toggle the toolitem without firing event. + A hook to toggle a toolitem without firing an event. + + This hook must be implemented in each backend and contains the + backend-specific code to silently toggle a toolbar element. + + .. warning:: + This is part of the backend implementation and should + not be called by end-users. They should instead call + `.ToolManager.trigger_tool` or `.ToolContainerBase.trigger_tool` + (which are equivalent). Parameters ---------- @@ -3438,11 +3453,16 @@ def toggle_toolitem(self, name, toggled): def remove_toolitem(self, name): """ - Remove a toolitem from the `ToolContainer`. + A hook to remove a toolitem from the container. - This method must get implemented per backend. + This hook must be implemented in each backend and contains the + backend-specific code to remove an element from the toolbar; it is + called when `.ToolManager` emits a `tool_removed_event`. - Called when `.ToolManager` emits a `tool_removed_event`. + .. warning:: + This is part of the backend implementation and should + not be called by end-users. They should instead call + `.ToolManager.remove_tool`. Parameters ---------- From 5da78fda125be8bc14e20bd59adb872c9530a8e9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 6 May 2024 13:16:21 -0400 Subject: [PATCH 0967/1120] Backport PR #28171: Support removing absent tools from ToolContainerBase. --- lib/matplotlib/backend_bases.py | 4 ++++ lib/matplotlib/backends/_backend_tk.py | 3 +-- lib/matplotlib/backends/backend_gtk3.py | 9 ++------- lib/matplotlib/backends/backend_gtk4.py | 9 ++------- lib/matplotlib/backends/backend_qt.py | 3 +-- lib/matplotlib/backends/backend_wx.py | 3 +-- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 740c01226f7d..f4273bc03919 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3459,6 +3459,10 @@ def remove_toolitem(self, name): backend-specific code to remove an element from the toolbar; it is called when `.ToolManager` emits a `tool_removed_event`. + Because some tools are present only on the `.ToolManager` but not on + the `ToolContainer`, this method must be a no-op when called on a tool + absent from the container. + .. warning:: This is part of the backend implementation and should not be called by end-users. They should instead call diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 693499f4ca01..295f6c41372d 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -1011,9 +1011,8 @@ def toggle_toolitem(self, name, toggled): toolitem.deselect() def remove_toolitem(self, name): - for toolitem in self._toolitems[name]: + for toolitem in self._toolitems.pop(name, []): toolitem.pack_forget() - del self._toolitems[name] def set_message(self, s): self._message.set(s) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d6acd5547b85..49d34f5794e4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -446,15 +446,10 @@ def toggle_toolitem(self, name, toggled): toolitem.handler_unblock(signal) def remove_toolitem(self, name): - if name not in self._toolitems: - self.toolmanager.message_event(f'{name} not in toolbar', self) - return - - for group in self._groups: - for toolitem, _signal in self._toolitems[name]: + for toolitem, _signal in self._toolitems.pop(name, []): + for group in self._groups: if toolitem in self._groups[group]: self._groups[group].remove(toolitem) - del self._toolitems[name] def _add_separator(self): sep = Gtk.Separator() diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 7e73a4863212..256a8ec9e864 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -475,15 +475,10 @@ def toggle_toolitem(self, name, toggled): toolitem.handler_unblock(signal) def remove_toolitem(self, name): - if name not in self._toolitems: - self.toolmanager.message_event(f'{name} not in toolbar', self) - return - - for group in self._groups: - for toolitem, _signal in self._toolitems[name]: + for toolitem, _signal in self._toolitems.pop(name, []): + for group in self._groups: if toolitem in self._groups[group]: self._groups[group].remove(toolitem) - del self._toolitems[name] def _add_separator(self): sep = Gtk.Separator() diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index db593ae77ded..a93b37799971 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -1007,9 +1007,8 @@ def toggle_toolitem(self, name, toggled): button.toggled.connect(handler) def remove_toolitem(self, name): - for button, handler in self._toolitems[name]: + for button, handler in self._toolitems.pop(name, []): button.setParent(None) - del self._toolitems[name] def set_message(self, s): self.widgetForAction(self._message_action).setText(s) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 8064511ac28a..d39edf40f151 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1257,9 +1257,8 @@ def toggle_toolitem(self, name, toggled): self.Refresh() def remove_toolitem(self, name): - for tool, handler in self._toolitems[name]: + for tool, handler in self._toolitems.pop(name, []): self.DeleteTool(tool.Id) - del self._toolitems[name] def set_message(self, s): self._label_text.SetLabel(s) From 604961fb9d907a7a6b593c870694a477095947a1 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 8 May 2024 15:10:30 +0200 Subject: [PATCH 0968/1120] Backport PR #28182: Bump custom hatch deprecation expiration --- lib/matplotlib/hatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/hatch.py b/lib/matplotlib/hatch.py index 9ec88776cfd3..7a4b283c1dbe 100644 --- a/lib/matplotlib/hatch.py +++ b/lib/matplotlib/hatch.py @@ -188,7 +188,7 @@ def _validate_hatch_pattern(hatch): invalids = ''.join(sorted(invalids)) _api.warn_deprecated( '3.4', - removal='3.9', # one release after custom hatches (#20690) + removal='3.11', # one release after custom hatches (#20690) message=f'hatch must consist of a string of "{valid}" or ' 'None, but found the following invalid values ' f'"{invalids}". Passing invalid values is deprecated ' From fe614effc77c3873c65caf321ad6d06260888f69 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 8 May 2024 17:03:42 -0400 Subject: [PATCH 0969/1120] Backport PR #28181: DOC: Prepare release notes for 3.9 --- .../next_api_changes/behavior/22347-RQ.rst | 13 - .../next_api_changes/behavior/26634-TH.rst | 5 - .../next_api_changes/behavior/26696-SR.rst | 6 - .../next_api_changes/behavior/26788-AL.rst | 6 - .../next_api_changes/behavior/26902-RP.rst | 5 - .../next_api_changes/behavior/26917-AL.rst | 3 - .../next_api_changes/behavior/27179-KS.rst | 7 - .../next_api_changes/behavior/27347-GL.rst | 7 - .../next_api_changes/behavior/27469-AL.rst | 11 - .../next_api_changes/behavior/27492-AL.rst | 12 - .../next_api_changes/behavior/27514-OG.rst | 5 - .../next_api_changes/behavior/27589-DS.rst | 5 - .../next_api_changes/behavior/27605-DS.rst | 4 - .../next_api_changes/behavior/27767-REC.rst | 7 - .../next_api_changes/behavior/27943-AL.rst | 10 - .../deprecations/24834-DS.rst | 17 - .../deprecations/26894-AL.rst | 6 - .../deprecations/26917-AL.rst | 3 - .../deprecations/26960-AL.rst | 3 - .../deprecations/27088-JK.rst | 5 - .../deprecations/27095-AL.rst | 10 - .../deprecations/27175-AL.rst | 5 - .../deprecations/27300-AL.rst | 3 - .../deprecations/27513-OG.rst | 5 - .../deprecations/27514-OG.rst | 4 - .../deprecations/27719-IT.rst | 11 - .../deprecations/27767-REC.rst | 9 - .../deprecations/27850-REC.rst | 10 - .../deprecations/27901-TS.rst | 3 - .../next_api_changes/development/26800-OG.rst | 14 - .../next_api_changes/development/26849-KS.rst | 5 - .../next_api_changes/development/27012-ES.rst | 7 - .../next_api_changes/development/27676-ES.rst | 6 - .../next_api_changes/removals/26797-OG.rst | 17 - .../next_api_changes/removals/26798-OG.rst | 9 - .../next_api_changes/removals/26852-OG.rst | 12 - .../next_api_changes/removals/26853-OG.rst | 26 -- .../next_api_changes/removals/26871-AG.rst | 3 - .../next_api_changes/removals/26872-AD.rst | 5 - .../next_api_changes/removals/26874-AG.rst | 4 - .../next_api_changes/removals/26884-JS.rst | 5 - .../next_api_changes/removals/26885-AD.rst | 4 - .../next_api_changes/removals/26889-GC.rst | 3 - .../next_api_changes/removals/26900-jf.rst | 4 - .../next_api_changes/removals/26907-DCH.rst | 14 - .../next_api_changes/removals/26909-VV.rst | 4 - .../next_api_changes/removals/26910-JP.rst | 13 - .../next_api_changes/removals/26918-EW.rst | 3 - .../next_api_changes/removals/26962-IA.rst | 19 - .../next_api_changes/removals/26965-ER.rst | 22 - .../next_api_changes/removals/27095-AL.rst | 5 - .../next_api_changes/removals/27968-ES.rst | 14 - .../prev_api_changes/api_changes_3.9.0.rst | 14 + .../api_changes_3.9.0/behaviour.rst | 120 +++++ .../api_changes_3.9.0/deprecations.rst | 93 ++++ .../api_changes_3.9.0/development.rst} | 46 +- .../api_changes_3.9.0/removals.rst | 159 +++++++ doc/users/next_whats_new/3d_axis_limits.rst | 20 - .../add_EllipseCollection_setters.rst | 40 -- .../next_whats_new/axis_minorticks_toggle.rst | 6 - doc/users/next_whats_new/backend_registry.rst | 17 - .../next_whats_new/boxplot_legend_support.rst | 60 --- .../next_whats_new/figure_align_titles.rst | 7 - .../formatter_unicode_minus.rst | 4 - doc/users/next_whats_new/inset_axes.rst | 4 - .../next_whats_new/interpolation_stage_rc.rst | 4 - doc/users/next_whats_new/margin_getters.rst | 4 - .../next_whats_new/mathtext_documentation.rst | 5 - doc/users/next_whats_new/mathtext_spacing.rst | 5 - .../nonuniformimage_mousover.rst | 4 - .../next_whats_new/pie_percent_latex.rst | 11 - doc/users/next_whats_new/polar-line-spans.rst | 5 - doc/users/next_whats_new/sides_violinplot.rst | 4 - doc/users/next_whats_new/stackplot_hatch.rst | 27 -- .../next_whats_new/stdfmt-axisartist.rst | 3 - doc/users/next_whats_new/subfigure_zorder.rst | 22 - .../next_whats_new/update_arrow_patch.rst | 30 -- .../next_whats_new/widget_button_clear.rst | 6 - doc/users/prev_whats_new/whats_new_3.9.0.rst | 409 ++++++++++++++++++ doc/users/release_notes.rst | 5 +- 80 files changed, 839 insertions(+), 713 deletions(-) delete mode 100644 doc/api/next_api_changes/behavior/22347-RQ.rst delete mode 100644 doc/api/next_api_changes/behavior/26634-TH.rst delete mode 100644 doc/api/next_api_changes/behavior/26696-SR.rst delete mode 100644 doc/api/next_api_changes/behavior/26788-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/26902-RP.rst delete mode 100644 doc/api/next_api_changes/behavior/26917-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/27179-KS.rst delete mode 100644 doc/api/next_api_changes/behavior/27347-GL.rst delete mode 100644 doc/api/next_api_changes/behavior/27469-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/27492-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/27514-OG.rst delete mode 100644 doc/api/next_api_changes/behavior/27589-DS.rst delete mode 100644 doc/api/next_api_changes/behavior/27605-DS.rst delete mode 100644 doc/api/next_api_changes/behavior/27767-REC.rst delete mode 100644 doc/api/next_api_changes/behavior/27943-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/24834-DS.rst delete mode 100644 doc/api/next_api_changes/deprecations/26894-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/26917-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/26960-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/27088-JK.rst delete mode 100644 doc/api/next_api_changes/deprecations/27095-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/27175-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/27300-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/27513-OG.rst delete mode 100644 doc/api/next_api_changes/deprecations/27514-OG.rst delete mode 100644 doc/api/next_api_changes/deprecations/27719-IT.rst delete mode 100644 doc/api/next_api_changes/deprecations/27767-REC.rst delete mode 100644 doc/api/next_api_changes/deprecations/27850-REC.rst delete mode 100644 doc/api/next_api_changes/deprecations/27901-TS.rst delete mode 100644 doc/api/next_api_changes/development/26800-OG.rst delete mode 100644 doc/api/next_api_changes/development/26849-KS.rst delete mode 100644 doc/api/next_api_changes/development/27012-ES.rst delete mode 100644 doc/api/next_api_changes/development/27676-ES.rst delete mode 100644 doc/api/next_api_changes/removals/26797-OG.rst delete mode 100644 doc/api/next_api_changes/removals/26798-OG.rst delete mode 100644 doc/api/next_api_changes/removals/26852-OG.rst delete mode 100644 doc/api/next_api_changes/removals/26853-OG.rst delete mode 100644 doc/api/next_api_changes/removals/26871-AG.rst delete mode 100644 doc/api/next_api_changes/removals/26872-AD.rst delete mode 100644 doc/api/next_api_changes/removals/26874-AG.rst delete mode 100644 doc/api/next_api_changes/removals/26884-JS.rst delete mode 100644 doc/api/next_api_changes/removals/26885-AD.rst delete mode 100644 doc/api/next_api_changes/removals/26889-GC.rst delete mode 100644 doc/api/next_api_changes/removals/26900-jf.rst delete mode 100644 doc/api/next_api_changes/removals/26907-DCH.rst delete mode 100644 doc/api/next_api_changes/removals/26909-VV.rst delete mode 100644 doc/api/next_api_changes/removals/26910-JP.rst delete mode 100644 doc/api/next_api_changes/removals/26918-EW.rst delete mode 100644 doc/api/next_api_changes/removals/26962-IA.rst delete mode 100644 doc/api/next_api_changes/removals/26965-ER.rst delete mode 100644 doc/api/next_api_changes/removals/27095-AL.rst delete mode 100644 doc/api/next_api_changes/removals/27968-ES.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.9.0.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.9.0/behaviour.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.9.0/deprecations.rst rename doc/api/{next_api_changes/development/26621-ES.rst => prev_api_changes/api_changes_3.9.0/development.rst} (60%) create mode 100644 doc/api/prev_api_changes/api_changes_3.9.0/removals.rst delete mode 100644 doc/users/next_whats_new/3d_axis_limits.rst delete mode 100644 doc/users/next_whats_new/add_EllipseCollection_setters.rst delete mode 100644 doc/users/next_whats_new/axis_minorticks_toggle.rst delete mode 100644 doc/users/next_whats_new/backend_registry.rst delete mode 100644 doc/users/next_whats_new/boxplot_legend_support.rst delete mode 100644 doc/users/next_whats_new/figure_align_titles.rst delete mode 100644 doc/users/next_whats_new/formatter_unicode_minus.rst delete mode 100644 doc/users/next_whats_new/inset_axes.rst delete mode 100644 doc/users/next_whats_new/interpolation_stage_rc.rst delete mode 100644 doc/users/next_whats_new/margin_getters.rst delete mode 100644 doc/users/next_whats_new/mathtext_documentation.rst delete mode 100644 doc/users/next_whats_new/mathtext_spacing.rst delete mode 100644 doc/users/next_whats_new/nonuniformimage_mousover.rst delete mode 100644 doc/users/next_whats_new/pie_percent_latex.rst delete mode 100644 doc/users/next_whats_new/polar-line-spans.rst delete mode 100644 doc/users/next_whats_new/sides_violinplot.rst delete mode 100644 doc/users/next_whats_new/stackplot_hatch.rst delete mode 100644 doc/users/next_whats_new/stdfmt-axisartist.rst delete mode 100644 doc/users/next_whats_new/subfigure_zorder.rst delete mode 100644 doc/users/next_whats_new/update_arrow_patch.rst delete mode 100644 doc/users/next_whats_new/widget_button_clear.rst create mode 100644 doc/users/prev_whats_new/whats_new_3.9.0.rst diff --git a/doc/api/next_api_changes/behavior/22347-RQ.rst b/doc/api/next_api_changes/behavior/22347-RQ.rst deleted file mode 100644 index b99d183943a5..000000000000 --- a/doc/api/next_api_changes/behavior/22347-RQ.rst +++ /dev/null @@ -1,13 +0,0 @@ -Correctly treat pan/zoom events of overlapping Axes ---------------------------------------------------- - -The forwarding of pan/zoom events is now determined by the visibility of the -background-patch (e.g. ``ax.patch.get_visible()``) and by the ``zorder`` of the axes. - -- Axes with a visible patch capture the event and do not pass it on to axes below. - Only the Axes with the highest ``zorder`` that contains the event is triggered - (if there are multiple Axes with the same ``zorder``, the last added Axes counts) -- Axes with an invisible patch are also invisible to events and they are passed on to the axes below. - -To override the default behavior and explicitly set whether an Axes -should forward navigation events, use `.Axes.set_forward_navigation_events`. diff --git a/doc/api/next_api_changes/behavior/26634-TH.rst b/doc/api/next_api_changes/behavior/26634-TH.rst deleted file mode 100644 index 4961722078d6..000000000000 --- a/doc/api/next_api_changes/behavior/26634-TH.rst +++ /dev/null @@ -1,5 +0,0 @@ -``SubplotParams`` has been moved from ``matplotlib.figure`` to ``matplotlib.gridspec`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It is still importable from ``matplotlib.figure``, so does not require any changes to -existing code. diff --git a/doc/api/next_api_changes/behavior/26696-SR.rst b/doc/api/next_api_changes/behavior/26696-SR.rst deleted file mode 100644 index 231f412e426d..000000000000 --- a/doc/api/next_api_changes/behavior/26696-SR.rst +++ /dev/null @@ -1,6 +0,0 @@ -*loc* parameter of ``Cell`` doesn't accept ``None`` anymore -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The default value of the *loc* parameter has been changed from ``None`` to ``right``, -which already was the default location. The behavior of `.Cell` didn't change when -called without an explicit *loc* parameter. diff --git a/doc/api/next_api_changes/behavior/26788-AL.rst b/doc/api/next_api_changes/behavior/26788-AL.rst deleted file mode 100644 index 14573e870843..000000000000 --- a/doc/api/next_api_changes/behavior/26788-AL.rst +++ /dev/null @@ -1,6 +0,0 @@ -``axvspan`` and ``axhspan`` now return ``Rectangle``\s, not ``Polygons`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This change allows using `~.Axes.axhspan` to draw an annulus on polar axes. - -This change also affects other elements built via `~.Axes.axvspan` and -`~.Axes.axhspan`, such as ``Slider.poly``. diff --git a/doc/api/next_api_changes/behavior/26902-RP.rst b/doc/api/next_api_changes/behavior/26902-RP.rst deleted file mode 100644 index 3106de94fbd5..000000000000 --- a/doc/api/next_api_changes/behavior/26902-RP.rst +++ /dev/null @@ -1,5 +0,0 @@ -``Line2D`` -~~~~~~~~~~ - -When creating a Line2D or using `.Line2D.set_xdata` and `.Line2D.set_ydata`, -passing x/y data as non sequence is now an error. diff --git a/doc/api/next_api_changes/behavior/26917-AL.rst b/doc/api/next_api_changes/behavior/26917-AL.rst deleted file mode 100644 index 7872caf3204d..000000000000 --- a/doc/api/next_api_changes/behavior/26917-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``ContourLabeler.add_label`` now respects *use_clabeltext* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... and sets `.Text.set_transform_rotates_text` accordingly. diff --git a/doc/api/next_api_changes/behavior/27179-KS.rst b/doc/api/next_api_changes/behavior/27179-KS.rst deleted file mode 100644 index 873cd622bbd4..000000000000 --- a/doc/api/next_api_changes/behavior/27179-KS.rst +++ /dev/null @@ -1,7 +0,0 @@ -Default behavior of ``hexbin`` with *C* provided requires at least 1 point -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The behavior changed in 3.8.0 to be inclusive of *mincnt*. However that resulted in -errors or warnings with some reduction functions, so now the default is to require at -least 1 point to call the reduction function. This effectively restores the default -behavior to match that of Matplotlib 3.7 and before. diff --git a/doc/api/next_api_changes/behavior/27347-GL.rst b/doc/api/next_api_changes/behavior/27347-GL.rst deleted file mode 100644 index 2cf8f65cd745..000000000000 --- a/doc/api/next_api_changes/behavior/27347-GL.rst +++ /dev/null @@ -1,7 +0,0 @@ -ScalarMappables auto-scale their norm when an array is set -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Collections previously deferred auto-scaling of the norm until draw time. -This has been changed to scale the norm whenever the first array is set -to align with the docstring and reduce unexpected behavior when -accessing the norm before drawing. diff --git a/doc/api/next_api_changes/behavior/27469-AL.rst b/doc/api/next_api_changes/behavior/27469-AL.rst deleted file mode 100644 index c47397e873b7..000000000000 --- a/doc/api/next_api_changes/behavior/27469-AL.rst +++ /dev/null @@ -1,11 +0,0 @@ -``loc='best'`` for ``legend`` now considers ``Text`` and ``PolyCollections`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The location selection ``legend`` now considers the existence of ``Text`` -and ``PolyCollections`` in the ``badness`` calculation. - -Note: The ``best`` option can already be quite slow for plots with large -amounts of data. For ``PolyCollections``, it only considers the ``Path`` -of ``PolyCollections`` and not the enclosed area when checking for overlap -to reduce additional latency. However, it can still be quite slow when -there are large amounts of ``PolyCollections`` in the plot to check for. diff --git a/doc/api/next_api_changes/behavior/27492-AL.rst b/doc/api/next_api_changes/behavior/27492-AL.rst deleted file mode 100644 index 98a4900fa67d..000000000000 --- a/doc/api/next_api_changes/behavior/27492-AL.rst +++ /dev/null @@ -1,12 +0,0 @@ -Image path semantics of toolmanager-based tools -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Previously, MEP22 ("toolmanager-based") Tools would try to load their icon -(``tool.image``) relative to the current working directory, or, as a fallback, -from Matplotlib's own image directory. Because both approaches are problematic -for third-party tools (the end-user may change the current working directory -at any time, and third-parties cannot add new icons in Matplotlib's image -directory), this behavior is deprecated; instead, ``tool.image`` is now -interpreted relative to the directory containing the source file where the -``Tool.image`` class attribute is defined. (Defining ``tool.image`` as an -absolute path also works and is compatible with both the old and the new -semantics.) diff --git a/doc/api/next_api_changes/behavior/27514-OG.rst b/doc/api/next_api_changes/behavior/27514-OG.rst deleted file mode 100644 index 8b2a7ab9ef2e..000000000000 --- a/doc/api/next_api_changes/behavior/27514-OG.rst +++ /dev/null @@ -1,5 +0,0 @@ -Exception when not passing a Bbox to BboxTransform*-classes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The exception when not passing a Bbox to BboxTransform*-classes that expect one, e.g., -`~matplotlib.transforms.BboxTransform` has changed from ``ValueError`` to ``TypeError``. diff --git a/doc/api/next_api_changes/behavior/27589-DS.rst b/doc/api/next_api_changes/behavior/27589-DS.rst deleted file mode 100644 index 314df582600b..000000000000 --- a/doc/api/next_api_changes/behavior/27589-DS.rst +++ /dev/null @@ -1,5 +0,0 @@ -PowerNorm no longer clips values below vmin -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When ``clip=False`` is set (the default) on `~matplotlib.colors.PowerNorm`, -values below ``vmin`` are now linearly normalised. Previously they were clipped -to zero. This fixes issues with the display of colorbars associated with a power norm. diff --git a/doc/api/next_api_changes/behavior/27605-DS.rst b/doc/api/next_api_changes/behavior/27605-DS.rst deleted file mode 100644 index a4bc04ccfb04..000000000000 --- a/doc/api/next_api_changes/behavior/27605-DS.rst +++ /dev/null @@ -1,4 +0,0 @@ -Boxplots now ignore masked data points -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`~matplotlib.axes.Axes.boxplot` and `~matplotlib.cbook.boxplot_stats` now ignore -any masked points in the input data. diff --git a/doc/api/next_api_changes/behavior/27767-REC.rst b/doc/api/next_api_changes/behavior/27767-REC.rst deleted file mode 100644 index f6b4dc156732..000000000000 --- a/doc/api/next_api_changes/behavior/27767-REC.rst +++ /dev/null @@ -1,7 +0,0 @@ -Legend labels for ``plot`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously if a sequence was passed to the *label* parameter of `~.Axes.plot` when -plotting a single dataset, the sequence was automatically cast to string for the legend -label. Now, if the sequence has only one element, that element will be the legend -label. To keep the old behavior, cast the sequence to string before passing. diff --git a/doc/api/next_api_changes/behavior/27943-AL.rst b/doc/api/next_api_changes/behavior/27943-AL.rst deleted file mode 100644 index 1314b763987e..000000000000 --- a/doc/api/next_api_changes/behavior/27943-AL.rst +++ /dev/null @@ -1,10 +0,0 @@ -plot() shorthand format interprets "Cn" (n>9) as a color-cycle color -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Previously, ``plot(..., "-C11")`` would be interpreted as requesting a plot -using linestyle "-", color "C1" (color #1 of the color cycle), and marker "1" -("tri-down"). It is now interpreted as requesting linestyle "-" and color -"C11" (color #11 of the color cycle). - -It is recommended to pass ambiguous markers (such as "1") explicitly using the -*marker* keyword argument. If the shorthand form is desired, such markers can -also be unambiguously set by putting them *before* the color string. diff --git a/doc/api/next_api_changes/deprecations/24834-DS.rst b/doc/api/next_api_changes/deprecations/24834-DS.rst deleted file mode 100644 index 3761daaf1275..000000000000 --- a/doc/api/next_api_changes/deprecations/24834-DS.rst +++ /dev/null @@ -1,17 +0,0 @@ -Applying theta transforms in ``PolarTransform`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Applying theta transforms in `~matplotlib.projections.polar.PolarTransform` -and `~matplotlib.projections.polar.InvertedPolarTransform` -is deprecated, and will be removed in a future version of Matplotlib. This -is currently the default behaviour when these transforms are used externally, -but only takes affect when: - -- An axis is associated with the transform. -- The axis has a non-zero theta offset or has theta values increasing in - a clockwise direction. - -To silence this warning and adopt future behaviour, -set ``apply_theta_transforms=False``. If you need to retain the behaviour -where theta values are transformed, chain the ``PolarTransform`` with -a `~matplotlib.transforms.Affine2D` transform that performs the theta shift -and/or sign shift. diff --git a/doc/api/next_api_changes/deprecations/26894-AL.rst b/doc/api/next_api_changes/deprecations/26894-AL.rst deleted file mode 100644 index b156fa843917..000000000000 --- a/doc/api/next_api_changes/deprecations/26894-AL.rst +++ /dev/null @@ -1,6 +0,0 @@ -*interval* parameter of ``TimerBase.start`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Setting the timer *interval* while starting it is deprecated. The interval can -be specified instead in the timer constructor, or by setting the -``timer.interval`` attribute. diff --git a/doc/api/next_api_changes/deprecations/26917-AL.rst b/doc/api/next_api_changes/deprecations/26917-AL.rst deleted file mode 100644 index d3cf16f5c511..000000000000 --- a/doc/api/next_api_changes/deprecations/26917-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``ContourLabeler.add_label_clabeltext`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. diff --git a/doc/api/next_api_changes/deprecations/26960-AL.rst b/doc/api/next_api_changes/deprecations/26960-AL.rst deleted file mode 100644 index cbde4cbba424..000000000000 --- a/doc/api/next_api_changes/deprecations/26960-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``backend_ps.get_bbox_header`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated, as it is considered an internal helper. diff --git a/doc/api/next_api_changes/deprecations/27088-JK.rst b/doc/api/next_api_changes/deprecations/27088-JK.rst deleted file mode 100644 index ea7fef5abf64..000000000000 --- a/doc/api/next_api_changes/deprecations/27088-JK.rst +++ /dev/null @@ -1,5 +0,0 @@ -Deprecations removed in ``contour`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``contour.allsegs``, ``contour.allkinds``, and ``contour.find_nearest_contour`` are no -longer marked for deprecation. diff --git a/doc/api/next_api_changes/deprecations/27095-AL.rst b/doc/api/next_api_changes/deprecations/27095-AL.rst deleted file mode 100644 index 2e5b2e1ea5e5..000000000000 --- a/doc/api/next_api_changes/deprecations/27095-AL.rst +++ /dev/null @@ -1,10 +0,0 @@ -*nth_coord* parameter to axisartist helpers for fixed axis -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Helper APIs in `.axisartist` for generating a "fixed" axis on rectilinear axes -(`.FixedAxisArtistHelperRectilinear`) no longer take a *nth_coord* parameter, -as that parameter is entirely inferred from the (required) *loc* parameter and -having inconsistent *nth_coord* and *loc* is an error. - -For curvilinear axes, the *nth_coord* parameter remains supported (it affects -the *ticks*, not the axis position itself), but that parameter will become -keyword-only, for consistency with the rectilinear case. diff --git a/doc/api/next_api_changes/deprecations/27175-AL.rst b/doc/api/next_api_changes/deprecations/27175-AL.rst deleted file mode 100644 index 3fce05765a59..000000000000 --- a/doc/api/next_api_changes/deprecations/27175-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -Mixing positional and keyword arguments for ``legend`` handles and labels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This previously only raised a warning, but is now formally deprecated. If -passing *handles* and *labels*, they must be passed either both positionally or -both as keyword. diff --git a/doc/api/next_api_changes/deprecations/27300-AL.rst b/doc/api/next_api_changes/deprecations/27300-AL.rst deleted file mode 100644 index 87f4bb259537..000000000000 --- a/doc/api/next_api_changes/deprecations/27300-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``GridHelperCurveLinear.get_tick_iterator`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated with no replacement. diff --git a/doc/api/next_api_changes/deprecations/27513-OG.rst b/doc/api/next_api_changes/deprecations/27513-OG.rst deleted file mode 100644 index 46414744f59d..000000000000 --- a/doc/api/next_api_changes/deprecations/27513-OG.rst +++ /dev/null @@ -1,5 +0,0 @@ -``BboxTransformToMaxOnly`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is deprecated without replacement. If you rely on this, please make a copy of the -code. diff --git a/doc/api/next_api_changes/deprecations/27514-OG.rst b/doc/api/next_api_changes/deprecations/27514-OG.rst deleted file mode 100644 index f318ec8aa4bb..000000000000 --- a/doc/api/next_api_changes/deprecations/27514-OG.rst +++ /dev/null @@ -1,4 +0,0 @@ -``TransformNode.is_bbox`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is deprecated. Instead check the object using ``isinstance(..., BboxBase)``. diff --git a/doc/api/next_api_changes/deprecations/27719-IT.rst b/doc/api/next_api_changes/deprecations/27719-IT.rst deleted file mode 100644 index c41e9d2c396f..000000000000 --- a/doc/api/next_api_changes/deprecations/27719-IT.rst +++ /dev/null @@ -1,11 +0,0 @@ -``rcsetup.interactive_bk``, ``rcsetup.non_interactive_bk`` and ``rcsetup.all_backends`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... are deprecated and replaced by ``matplotlib.backends.backend_registry.list_builtin`` -with the following arguments - -- ``matplotlib.backends.BackendFilter.INTERACTIVE`` -- ``matplotlib.backends.BackendFilter.NON_INTERACTIVE`` -- ``None`` - -respectively. diff --git a/doc/api/next_api_changes/deprecations/27767-REC.rst b/doc/api/next_api_changes/deprecations/27767-REC.rst deleted file mode 100644 index 68781090df0a..000000000000 --- a/doc/api/next_api_changes/deprecations/27767-REC.rst +++ /dev/null @@ -1,9 +0,0 @@ -Legend labels for ``plot`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously if a sequence was passed to the *label* parameter of `~.Axes.plot` when -plotting a single dataset, the sequence was automatically cast to string for the legend -label. This behavior is now deprecated and in future will error if the sequence length -is not one (consistent with multi-dataset behavior, where the number of elements must -match the number of datasets). To keep the old behavior, cast the sequence to string -before passing. diff --git a/doc/api/next_api_changes/deprecations/27850-REC.rst b/doc/api/next_api_changes/deprecations/27850-REC.rst deleted file mode 100644 index 2021c2737ecd..000000000000 --- a/doc/api/next_api_changes/deprecations/27850-REC.rst +++ /dev/null @@ -1,10 +0,0 @@ -``plot_date`` -~~~~~~~~~~~~~ - -Use of `~.Axes.plot_date` has been discouraged since Matplotlib 3.5 and the -function is now formally deprecated. - -- ``datetime``-like data should directly be plotted using `~.Axes.plot`. -- If you need to plot plain numeric data as :ref:`date-format` or need to set - a timezone, call ``ax.xaxis.axis_date`` / ``ax.yaxis.axis_date`` before - `~.Axes.plot`. See `.Axis.axis_date`. diff --git a/doc/api/next_api_changes/deprecations/27901-TS.rst b/doc/api/next_api_changes/deprecations/27901-TS.rst deleted file mode 100644 index e31b77e2c6f8..000000000000 --- a/doc/api/next_api_changes/deprecations/27901-TS.rst +++ /dev/null @@ -1,3 +0,0 @@ -``boxplot`` tick labels -~~~~~~~~~~~~~~~~~~~~~~~ -The parameter *labels* has been renamed to *tick_labels* for clarity and consistency with `~.Axes.bar`. diff --git a/doc/api/next_api_changes/development/26800-OG.rst b/doc/api/next_api_changes/development/26800-OG.rst deleted file mode 100644 index d536f8240c76..000000000000 --- a/doc/api/next_api_changes/development/26800-OG.rst +++ /dev/null @@ -1,14 +0,0 @@ -Increase to minimum supported versions of dependencies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For Matplotlib 3.9, the :ref:`minimum supported versions ` are -being bumped: - -+------------+-----------------+---------------+ -| Dependency | min in mpl3.8 | min in mpl3.9 | -+============+=================+===============+ -| NumPy | 1.21.0 | 1.23.0 | -+------------+-----------------+---------------+ - -This is consistent with our :ref:`min_deps_policy` and `NEP29 -`__ diff --git a/doc/api/next_api_changes/development/26849-KS.rst b/doc/api/next_api_changes/development/26849-KS.rst deleted file mode 100644 index 1a1deda40fca..000000000000 --- a/doc/api/next_api_changes/development/26849-KS.rst +++ /dev/null @@ -1,5 +0,0 @@ -Minimum version of setuptools bumped to 64 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To comply with requirements of ``setuptools_scm``, the minimum version of ``setuptools`` -has been increased from 42 to 64. diff --git a/doc/api/next_api_changes/development/27012-ES.rst b/doc/api/next_api_changes/development/27012-ES.rst deleted file mode 100644 index 1bec3e7d91df..000000000000 --- a/doc/api/next_api_changes/development/27012-ES.rst +++ /dev/null @@ -1,7 +0,0 @@ -Extensions require C++17 -~~~~~~~~~~~~~~~~~~~~~~~~ - -Matplotlib now requires a compiler that supports C++17 in order to build its extensions. -According to `SciPy's analysis -`_, this -should be available on all supported platforms. diff --git a/doc/api/next_api_changes/development/27676-ES.rst b/doc/api/next_api_changes/development/27676-ES.rst deleted file mode 100644 index 5242c5cba943..000000000000 --- a/doc/api/next_api_changes/development/27676-ES.rst +++ /dev/null @@ -1,6 +0,0 @@ -Windows on ARM64 support -~~~~~~~~~~~~~~~~~~~~~~~~ - -Windows on ARM64 bundles FreeType 2.6.1 instead of 2.11.1 when building from source. -This may cause small changes to text rendering, but should become consistent with all -other platforms. diff --git a/doc/api/next_api_changes/removals/26797-OG.rst b/doc/api/next_api_changes/removals/26797-OG.rst deleted file mode 100644 index 680f69e01a96..000000000000 --- a/doc/api/next_api_changes/removals/26797-OG.rst +++ /dev/null @@ -1,17 +0,0 @@ -``draw_gouraud_triangle`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is removed. Use `~.RendererBase.draw_gouraud_triangles` instead. - -A ``draw_gouraud_triangle`` call in a custom `~matplotlib.artist.Artist` can readily be -replaced as:: - - self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)), - colors.reshape((1, 3, 4)), trans) - -A `~.RendererBase.draw_gouraud_triangles` method can be implemented from an -existing ``draw_gouraud_triangle`` method as:: - - transform = transform.frozen() - for tri, col in zip(triangles_array, colors_array): - self.draw_gouraud_triangle(gc, tri, col, transform) diff --git a/doc/api/next_api_changes/removals/26798-OG.rst b/doc/api/next_api_changes/removals/26798-OG.rst deleted file mode 100644 index 0d7d0a11faf2..000000000000 --- a/doc/api/next_api_changes/removals/26798-OG.rst +++ /dev/null @@ -1,9 +0,0 @@ -``unit_cube``, ``tunit_cube``, and ``tunit_edges`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... of `.Axes3D` are removed without replacements. - -``axes3d.vvec``, ``axes3d.eye``, ``axes3d.sx``, and ``axes3d.sy`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... are removed without replacement. diff --git a/doc/api/next_api_changes/removals/26852-OG.rst b/doc/api/next_api_changes/removals/26852-OG.rst deleted file mode 100644 index 08ad0105b70a..000000000000 --- a/doc/api/next_api_changes/removals/26852-OG.rst +++ /dev/null @@ -1,12 +0,0 @@ -``num2julian``, ``julian2num`` and ``JULIAN_OFFSET`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... of the `.dates` module are removed without replacements. These were -undocumented and not exported. - -Julian dates in Matplotlib were calculated from a Julian date epoch: ``jdate = -(date - np.datetime64(EPOCH)) / np.timedelta64(1, 'D')``. Conversely, a Julian -date was converted to datetime as ``date = np.timedelta64(int(jdate * 24 * -3600), 's') + np.datetime64(EPOCH)``. Matplotlib was using -``EPOCH='-4713-11-24T12:00'`` so that 2000-01-01 at 12:00 is 2_451_545.0 (see -`). diff --git a/doc/api/next_api_changes/removals/26853-OG.rst b/doc/api/next_api_changes/removals/26853-OG.rst deleted file mode 100644 index dc5c37e38db5..000000000000 --- a/doc/api/next_api_changes/removals/26853-OG.rst +++ /dev/null @@ -1,26 +0,0 @@ -Most arguments to widgets have been made keyword-only -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Passing all but the very few first arguments positionally in the constructors -of Widgets is now keyword-only. In general, all optional arguments are keyword-only. - -``RadioButtons.circles`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -... is removed. (``RadioButtons`` now draws itself using `~.Axes.scatter`.) - -``CheckButtons.rectangles`` and ``CheckButtons.lines`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``CheckButtons.rectangles`` and ``CheckButtons.lines`` are removed. -(``CheckButtons`` now draws itself using `~.Axes.scatter`.) - -Remove unused parameter *x* to ``TextBox.begin_typing`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This parameter was unused in the method, but was a required argument. - -``MultiCursor.needclear`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is removed. diff --git a/doc/api/next_api_changes/removals/26871-AG.rst b/doc/api/next_api_changes/removals/26871-AG.rst deleted file mode 100644 index 9c24ac3215a1..000000000000 --- a/doc/api/next_api_changes/removals/26871-AG.rst +++ /dev/null @@ -1,3 +0,0 @@ -``matplotlib.axis.Axis.set_ticklabels`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... a param was renamed to labels from ticklabels. diff --git a/doc/api/next_api_changes/removals/26872-AD.rst b/doc/api/next_api_changes/removals/26872-AD.rst deleted file mode 100644 index 411359813e51..000000000000 --- a/doc/api/next_api_changes/removals/26872-AD.rst +++ /dev/null @@ -1,5 +0,0 @@ -``Animation`` attributes -~~~~~~~~~~~~~~~~~~~~~~~~ - -The attributes ``repeat`` of `.TimedAnimation` and subclasses and -``save_count`` of `.FuncAnimation` are considered private and removed. diff --git a/doc/api/next_api_changes/removals/26874-AG.rst b/doc/api/next_api_changes/removals/26874-AG.rst deleted file mode 100644 index ad305cf9d96c..000000000000 --- a/doc/api/next_api_changes/removals/26874-AG.rst +++ /dev/null @@ -1,4 +0,0 @@ -``collections.PolyCollection.span_where`` and ``collections.BrokenBarHCollection`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... removed as it was deprecated during 3.7. Use ``fill_between`` instead diff --git a/doc/api/next_api_changes/removals/26884-JS.rst b/doc/api/next_api_changes/removals/26884-JS.rst deleted file mode 100644 index 71608b8d94be..000000000000 --- a/doc/api/next_api_changes/removals/26884-JS.rst +++ /dev/null @@ -1,5 +0,0 @@ -``parse_fontconfig_pattern`` raises on unknown constant names -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously, in a fontconfig pattern like ``DejaVu Sans:foo``, the unknown -``foo`` constant name would be silently ignored. This now raises an error. diff --git a/doc/api/next_api_changes/removals/26885-AD.rst b/doc/api/next_api_changes/removals/26885-AD.rst deleted file mode 100644 index c617f10d07ed..000000000000 --- a/doc/api/next_api_changes/removals/26885-AD.rst +++ /dev/null @@ -1,4 +0,0 @@ -``raw`` parameter -~~~~~~~~~~~~~~~~~ - -... of `.GridSpecBase.get_grid_positions` is removed without replacements. diff --git a/doc/api/next_api_changes/removals/26889-GC.rst b/doc/api/next_api_changes/removals/26889-GC.rst deleted file mode 100644 index 2cccc9fee113..000000000000 --- a/doc/api/next_api_changes/removals/26889-GC.rst +++ /dev/null @@ -1,3 +0,0 @@ -Removing Deprecated API SimpleEvent -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``matplotlib.patches.ConnectionStyle._Base.SimpleEvent`` diff --git a/doc/api/next_api_changes/removals/26900-jf.rst b/doc/api/next_api_changes/removals/26900-jf.rst deleted file mode 100644 index 5f14c1543ad8..000000000000 --- a/doc/api/next_api_changes/removals/26900-jf.rst +++ /dev/null @@ -1,4 +0,0 @@ -``passthru_pt`` -~~~~~~~~~~~~~~~ - -This attribute of ``AxisArtistHelper``\s has been removed. diff --git a/doc/api/next_api_changes/removals/26907-DCH.rst b/doc/api/next_api_changes/removals/26907-DCH.rst deleted file mode 100644 index 889743ba9cb0..000000000000 --- a/doc/api/next_api_changes/removals/26907-DCH.rst +++ /dev/null @@ -1,14 +0,0 @@ -``contour.ClabelText`` and ``ContourLabeler.set_label_props`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are removed. - -Use ``Text(..., transform_rotates_text=True)`` as a replacement for -``contour.ClabelText(...)`` and ``text.set(text=text, color=color, -fontproperties=labeler.labelFontProps, clip_box=labeler.axes.bbox)`` as a -replacement for the ``ContourLabeler.set_label_props(label, text, color)``. - -``ContourLabeler`` attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``labelFontProps``, ``labelFontSizeList``, and ``labelTextsList`` -attributes of `.ContourLabeler` have been removed. Use the ``labelTexts`` -attribute and the font properties of the corresponding text objects instead. diff --git a/doc/api/next_api_changes/removals/26909-VV.rst b/doc/api/next_api_changes/removals/26909-VV.rst deleted file mode 100644 index bdb815eed322..000000000000 --- a/doc/api/next_api_changes/removals/26909-VV.rst +++ /dev/null @@ -1,4 +0,0 @@ -``matplotlib.tri`` submodules are removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``matplotlib.tri.*`` submodules are removed. All functionality is -available in ``matplotlib.tri`` directly and should be imported from there. diff --git a/doc/api/next_api_changes/removals/26910-JP.rst b/doc/api/next_api_changes/removals/26910-JP.rst deleted file mode 100644 index 0de12cd89ad5..000000000000 --- a/doc/api/next_api_changes/removals/26910-JP.rst +++ /dev/null @@ -1,13 +0,0 @@ -``offsetbox.bbox_artist`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is removed. This was just a wrapper to call `.patches.bbox_artist` if a flag is set in the file, so use that directly if you need the behavior. - -``offsetBox.get_extent_offsets`` and ``offsetBox.get_extent`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... are removed; these methods are also removed on all subclasses of `.OffsetBox`. - -... To get the offsetbox extents, instead of ``get_extent``, use `.OffsetBox.get_bbox`, which directly returns a `.Bbox` instance. - -... To also get the child offsets, instead of ``get_extent_offsets``, separately call `~.OffsetBox.get_offset` on each children after triggering a draw. diff --git a/doc/api/next_api_changes/removals/26918-EW.rst b/doc/api/next_api_changes/removals/26918-EW.rst deleted file mode 100644 index 454f35d5e200..000000000000 --- a/doc/api/next_api_changes/removals/26918-EW.rst +++ /dev/null @@ -1,3 +0,0 @@ -``Quiver.quiver_doc`` and ``Barbs.barbs_doc`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are removed. These are the doc-string and should not be accessible as a named class member. diff --git a/doc/api/next_api_changes/removals/26962-IA.rst b/doc/api/next_api_changes/removals/26962-IA.rst deleted file mode 100644 index ac1ab46944b6..000000000000 --- a/doc/api/next_api_changes/removals/26962-IA.rst +++ /dev/null @@ -1,19 +0,0 @@ -Deprecated Classes Removed -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following deprecated classes from version 3.7 have been removed: - -- ``matplotlib.backends.backend_ps.PsBackendHelper`` -- ``matplotlib.backends.backend_webagg.ServerThread`` - -These classes were previously marked as deprecated and have now been removed in accordance with the deprecation cycle. - -Deprecated C++ Methods Removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following deprecated C++ methods from :file:`src/_backend_agg_wrapper.cpp`, deprecated since version 3.7, have also been removed: - -- ``PyBufferRegion_to_string`` -- ``PyBufferRegion_to_string_argb`` - -These methods were previously marked as deprecated and have now been removed. diff --git a/doc/api/next_api_changes/removals/26965-ER.rst b/doc/api/next_api_changes/removals/26965-ER.rst deleted file mode 100644 index b4ae71be3bff..000000000000 --- a/doc/api/next_api_changes/removals/26965-ER.rst +++ /dev/null @@ -1,22 +0,0 @@ -Removal of top-level cmap registration and access functions in ``mpl.cm`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -As part of the `multi-step refactoring of colormap registration -`_, the following functions have -been removed: - -- ``matplotlib.cm.get_cmap``; use ``matplotlib.colormaps[name]`` instead if you - have a `str`. - - Use `matplotlib.cm.ColormapRegistry.get_cmap` if you have a `str`, `None` or a - `matplotlib.colors.Colormap` object that you want to convert to a `.Colormap` - object. -- ``matplotlib.cm.register_cmap``; use `matplotlib.colormaps.register - <.ColormapRegistry.register>` instead. -- ``matplotlib.cm.unregister_cmap``; use `matplotlib.colormaps.unregister - <.ColormapRegistry.unregister>` instead. -- ``matplotlib.pyplot.register_cmap``; use `matplotlib.colormaps.register - <.ColormapRegistry.register>` instead. - -The `matplotlib.pyplot.get_cmap` function will stay available for backward -compatibility. diff --git a/doc/api/next_api_changes/removals/27095-AL.rst b/doc/api/next_api_changes/removals/27095-AL.rst deleted file mode 100644 index 7b8e5981ca79..000000000000 --- a/doc/api/next_api_changes/removals/27095-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -Inconsistent *nth_coord* and *loc* passed to ``_FixedAxisArtistHelperBase`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The value of the *nth_coord* parameter of ``_FixedAxisArtistHelperBase`` and -its subclasses is now inferred from the value of *loc*; passing inconsistent -values (e.g., requesting a "top y axis" or a "left x axis") has no more effect. diff --git a/doc/api/next_api_changes/removals/27968-ES.rst b/doc/api/next_api_changes/removals/27968-ES.rst deleted file mode 100644 index 99b3b1527506..000000000000 --- a/doc/api/next_api_changes/removals/27968-ES.rst +++ /dev/null @@ -1,14 +0,0 @@ -``legend.legendHandles`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -... was undocumented and has been renamed to ``legend_handles``. - -Passing undefined *label_mode* to ``Grid`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... is no longer allowed. This includes `mpl_toolkits.axes_grid1.axes_grid.Grid`, -`mpl_toolkits.axes_grid1.axes_grid.AxesGrid`, and -`mpl_toolkits.axes_grid1.axes_grid.ImageGrid` as well as the corresponding classes -imported from `mpl_toolkits.axisartist.axes_grid`. - -Pass ``label_mode='keep'`` instead to get the previous behavior of not modifying labels. diff --git a/doc/api/prev_api_changes/api_changes_3.9.0.rst b/doc/api/prev_api_changes/api_changes_3.9.0.rst new file mode 100644 index 000000000000..8bd2628c90dc --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.9.0.rst @@ -0,0 +1,14 @@ +API Changes for 3.9.0 +===================== + +.. contents:: + :local: + :depth: 1 + +.. include:: /api/prev_api_changes/api_changes_3.9.0/behaviour.rst + +.. include:: /api/prev_api_changes/api_changes_3.9.0/deprecations.rst + +.. include:: /api/prev_api_changes/api_changes_3.9.0/removals.rst + +.. include:: /api/prev_api_changes/api_changes_3.9.0/development.rst diff --git a/doc/api/prev_api_changes/api_changes_3.9.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.9.0/behaviour.rst new file mode 100644 index 000000000000..498dfb766922 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.9.0/behaviour.rst @@ -0,0 +1,120 @@ +Behaviour Changes +----------------- + +plot() shorthand format interprets "Cn" (n>9) as a color-cycle color +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, ``plot(..., "-C11")`` would be interpreted as requesting a plot using +linestyle "-", color "C1" (color #1 of the color cycle), and marker "1" ("tri-down"). +It is now interpreted as requesting linestyle "-" and color "C11" (color #11 of the +color cycle). + +It is recommended to pass ambiguous markers (such as "1") explicitly using the *marker* +keyword argument. If the shorthand form is desired, such markers can also be +unambiguously set by putting them *before* the color string. + +Legend labels for ``plot`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously if a sequence was passed to the *label* parameter of `~.Axes.plot` when +plotting a single dataset, the sequence was automatically cast to string for the legend +label. Now, if the sequence has only one element, that element will be the legend label. +To keep the old behavior, cast the sequence to string before passing. + +Boxplots now ignore masked data points +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`~matplotlib.axes.Axes.boxplot` and `~matplotlib.cbook.boxplot_stats` now ignore any +masked points in the input data. + +``axhspan`` and ``axvspan`` now return ``Rectangle``\s, not ``Polygon``\s +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This change allows using `~.Axes.axhspan` to draw an annulus on polar axes. + +This change also affects other elements built via `~.Axes.axhspan` and `~.Axes.axvspan`, +such as ``Slider.poly``. + +Improved handling of pan/zoom events of overlapping Axes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The forwarding of pan/zoom events is now determined by the visibility of the +background-patch (e.g. ``ax.patch.get_visible()``) and by the ``zorder`` of the axes. + +- Axes with a visible patch capture the event and do not pass it on to axes below. Only + the Axes with the highest ``zorder`` that contains the event is triggered (if there + are multiple Axes with the same ``zorder``, the last added Axes counts) +- Axes with an invisible patch are also invisible to events and they are passed on to + the axes below. + +To override the default behavior and explicitly set whether an Axes should forward +navigation events, use `.Axes.set_forward_navigation_events`. + +``loc='best'`` for ``legend`` now considers ``Text`` and ``PolyCollections`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The location selection ``legend`` now considers the existence of ``Text`` and +``PolyCollections`` in the ``badness`` calculation. + +Note: The ``best`` option can already be quite slow for plots with large amounts of +data. For ``PolyCollections``, it only considers the ``Path`` of ``PolyCollections`` and +not the enclosed area when checking for overlap to reduce additional latency. However, +it can still be quite slow when there are large amounts of ``PolyCollections`` in the +plot to check for. + +Exception when not passing a Bbox to BboxTransform*-classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The exception when not passing a Bbox to BboxTransform*-classes that expect one, e.g., +`~matplotlib.transforms.BboxTransform` has changed from ``ValueError`` to ``TypeError``. + +*loc* parameter of ``Cell`` no longer accepts ``None`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The default value of the *loc* parameter has been changed from ``None`` to ``right``, +which already was the default location. The behavior of `.Cell` didn't change when +called without an explicit *loc* parameter. + +``ContourLabeler.add_label`` now respects *use_clabeltext* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +... and sets `.Text.set_transform_rotates_text` accordingly. + +``Line2D`` +^^^^^^^^^^ + +When creating a Line2D or using `.Line2D.set_xdata` and `.Line2D.set_ydata`, +passing x/y data as non sequence is now an error. + +``ScalarMappable``\s auto-scale their norm when an array is set +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Collections previously deferred auto-scaling of the norm until draw time. This has been +changed to scale the norm whenever the first array is set to align with the docstring +and reduce unexpected behavior when accessing the norm before drawing. + +``SubplotParams`` moved from ``matplotlib.figure`` to ``matplotlib.gridspec`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It is still importable from ``matplotlib.figure``, so does not require any changes to +existing code. + +``PowerNorm`` no longer clips values below vmin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When ``clip=False`` is set (the default) on `~matplotlib.colors.PowerNorm`, values below +``vmin`` are now linearly normalised. Previously they were clipped to zero. This fixes +issues with the display of colorbars associated with a power norm. + +Image path semantics of toolmanager-based tools +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, MEP22 ("toolmanager-based") Tools would try to load their icon +(``tool.image``) relative to the current working directory, or, as a fallback, from +Matplotlib's own image directory. Because both approaches are problematic for +third-party tools (the end-user may change the current working directory at any time, +and third-parties cannot add new icons in Matplotlib's image directory), this behavior +is deprecated; instead, ``tool.image`` is now interpreted relative to the directory +containing the source file where the ``Tool.image`` class attribute is defined. +(Defining ``tool.image`` as an absolute path also works and is compatible with both the +old and the new semantics.) diff --git a/doc/api/prev_api_changes/api_changes_3.9.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.9.0/deprecations.rst new file mode 100644 index 000000000000..00469459d20a --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.9.0/deprecations.rst @@ -0,0 +1,93 @@ +Deprecations +------------ + +``plot_date`` +^^^^^^^^^^^^^ + +Use of `~.Axes.plot_date` has been discouraged since Matplotlib 3.5 and the function is +now formally deprecated. + +- ``datetime``-like data should directly be plotted using `~.Axes.plot`. +- If you need to plot plain numeric data as :ref:`date-format` or need to set a + timezone, call ``ax.xaxis.axis_date`` / ``ax.yaxis.axis_date`` before `~.Axes.plot`. + See `.Axis.axis_date`. + +Legend labels for ``plot`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously if a sequence was passed to the *label* parameter of `~.Axes.plot` when +plotting a single dataset, the sequence was automatically cast to string for the legend +label. This behavior is now deprecated and in future will error if the sequence length +is not one (consistent with multi-dataset behavior, where the number of elements must +match the number of datasets). To keep the old behavior, cast the sequence to string +before passing. + +``boxplot`` tick labels +^^^^^^^^^^^^^^^^^^^^^^^ + +The parameter *labels* has been renamed to *tick_labels* for clarity and consistency +with `~.Axes.bar`. + +Mixing positional and keyword arguments for ``legend`` handles and labels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This previously only raised a warning, but is now formally deprecated. If passing +*handles* and *labels*, they must be passed either both positionally or both as keyword. + +Applying theta transforms in ``PolarTransform`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Applying theta transforms in `~matplotlib.projections.polar.PolarTransform` and +`~matplotlib.projections.polar.InvertedPolarTransform` is deprecated, and will be +removed in a future version of Matplotlib. This is currently the default behaviour when +these transforms are used externally, but only takes affect when: + +- An axis is associated with the transform. +- The axis has a non-zero theta offset or has theta values increasing in a clockwise + direction. + +To silence this warning and adopt future behaviour, set +``apply_theta_transforms=False``. If you need to retain the behaviour where theta values +are transformed, chain the ``PolarTransform`` with a `~matplotlib.transforms.Affine2D` +transform that performs the theta shift and/or sign shift. + +*interval* parameter of ``TimerBase.start`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Setting the timer *interval* while starting it is deprecated. The interval can be +specified instead in the timer constructor, or by setting the ``timer.interval`` +attribute. + +*nth_coord* parameter to axisartist helpers for fixed axis +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Helper APIs in `.axisartist` for generating a "fixed" axis on rectilinear axes +(`.FixedAxisArtistHelperRectilinear`) no longer take a *nth_coord* parameter, as that +parameter is entirely inferred from the (required) *loc* parameter and having +inconsistent *nth_coord* and *loc* is an error. + +For curvilinear axes, the *nth_coord* parameter remains supported (it affects the +*ticks*, not the axis position itself), but that parameter will become keyword-only, for +consistency with the rectilinear case. + +``rcsetup.interactive_bk``, ``rcsetup.non_interactive_bk`` and ``rcsetup.all_backends`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +... are deprecated and replaced by ``matplotlib.backends.backend_registry.list_builtin`` +with the following arguments + +- ``matplotlib.backends.BackendFilter.INTERACTIVE`` +- ``matplotlib.backends.BackendFilter.NON_INTERACTIVE`` +- ``None`` + +respectively. + +Miscellaneous deprecations +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- ``backend_ps.get_bbox_header`` is considered an internal helper +- ``BboxTransformToMaxOnly``; if you rely on this, please make a copy of the code +- ``ContourLabeler.add_label_clabeltext`` +- ``TransformNode.is_bbox``; instead check the object using ``isinstance(..., + BboxBase)`` +- ``GridHelperCurveLinear.get_tick_iterator`` diff --git a/doc/api/next_api_changes/development/26621-ES.rst b/doc/api/prev_api_changes/api_changes_3.9.0/development.rst similarity index 60% rename from doc/api/next_api_changes/development/26621-ES.rst rename to doc/api/prev_api_changes/api_changes_3.9.0/development.rst index ff87f53b3573..c16e8e98ecc4 100644 --- a/doc/api/next_api_changes/development/26621-ES.rst +++ b/doc/api/prev_api_changes/api_changes_3.9.0/development.rst @@ -1,5 +1,8 @@ +Development changes +------------------- + Build system ported to Meson -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The build system of Matplotlib has been ported from setuptools to `meson-python `_ and `Meson `_. @@ -26,7 +29,7 @@ Consequently, there have been a few changes for development and packaging purpos may be replaced by passing the following arguments to ``pip``:: - --config-settings=setup-args="-DrcParams-backend=Agg" \ + --config-settings=setup-args="-DrcParams-backend=Agg" --config-settings=setup-args="-Dsystem-qhull=true" Note that you must use ``pip`` >= 23.1 in order to pass more than one setting. @@ -37,10 +40,45 @@ Consequently, there have been a few changes for development and packaging purpos `_ if you wish to change the priority of chosen compilers. 5. Installation of test data was previously controlled by :file:`mplsetup.cfg`, but has - now been moved to Meson's install tags. To install test data, add the ``tests`` - tag to the requested install (be sure to include the existing tags as below):: + now been moved to Meson's install tags. To install test data, add the ``tests`` tag + to the requested install (be sure to include the existing tags as below):: --config-settings=install-args="--tags=data,python-runtime,runtime,tests" 6. Checking typing stubs with ``stubtest`` does not work easily with editable install. For the time being, we suggest using a normal (non-editable) install if you wish to run ``stubtest``. + +Increase to minimum supported versions of dependencies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For Matplotlib 3.9, the :ref:`minimum supported versions ` are being +bumped: + ++------------+-----------------+---------------+ +| Dependency | min in mpl3.8 | min in mpl3.9 | ++============+=================+===============+ +| NumPy | 1.21.0 | 1.23.0 | ++------------+-----------------+---------------+ +| setuptools | 42 | 64 | ++------------+-----------------+---------------+ + +This is consistent with our :ref:`min_deps_policy` and `SPEC 0 +`__. + +To comply with requirements of ``setuptools_scm``, the minimum version of ``setuptools`` +has been increased from 42 to 64. + +Extensions require C++17 +^^^^^^^^^^^^^^^^^^^^^^^^ + +Matplotlib now requires a compiler that supports C++17 in order to build its extensions. +According to `SciPy's analysis +`_, this +should be available on all supported platforms. + +Windows on ARM64 support +^^^^^^^^^^^^^^^^^^^^^^^^ + +Windows on ARM64 now bundles FreeType 2.6.1 instead of 2.11.1 when building from source. +This may cause small changes to text rendering, but should become consistent with all +other platforms. diff --git a/doc/api/prev_api_changes/api_changes_3.9.0/removals.rst b/doc/api/prev_api_changes/api_changes_3.9.0/removals.rst new file mode 100644 index 000000000000..b9aa03cfbf92 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.9.0/removals.rst @@ -0,0 +1,159 @@ +Removals +-------- + +Top-level cmap registration and access functions in ``mpl.cm`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As part of the `multi-step refactoring of colormap registration +`_, the following functions have +been removed: + +- ``matplotlib.cm.get_cmap``; use ``matplotlib.colormaps[name]`` instead if you have a + `str`. + + Use `matplotlib.cm.ColormapRegistry.get_cmap` if you have a `str`, `None` or a + `matplotlib.colors.Colormap` object that you want to convert to a `.Colormap` object. +- ``matplotlib.cm.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead. +- ``matplotlib.cm.unregister_cmap``; use `matplotlib.colormaps.unregister + <.ColormapRegistry.unregister>` instead. +- ``matplotlib.pyplot.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead. + +The `matplotlib.pyplot.get_cmap` function will stay available for backward +compatibility. + +Contour labels +^^^^^^^^^^^^^^ + +``contour.ClabelText`` and ``ContourLabeler.set_label_props`` are removed. Use +``Text(..., transform_rotates_text=True)`` as a replacement for +``contour.ClabelText(...)`` and ``text.set(text=text, color=color, +fontproperties=labeler.labelFontProps, clip_box=labeler.axes.bbox)`` as a replacement +for the ``ContourLabeler.set_label_props(label, text, color)``. + +The ``labelFontProps``, ``labelFontSizeList``, and ``labelTextsList`` attributes of +`.ContourLabeler` have been removed. Use the ``labelTexts`` attribute and the font +properties of the corresponding text objects instead. + +``num2julian``, ``julian2num`` and ``JULIAN_OFFSET`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +... of the `.dates` module are removed without replacements. These were undocumented and +not exported. + +Julian dates in Matplotlib were calculated from a Julian date epoch: ``jdate = (date - +np.datetime64(EPOCH)) / np.timedelta64(1, 'D')``. Conversely, a Julian date was +converted to datetime as ``date = np.timedelta64(int(jdate * 24 * 3600), 's') + +np.datetime64(EPOCH)``. Matplotlib was using ``EPOCH='-4713-11-24T12:00'`` so that +2000-01-01 at 12:00 is 2_451_545.0 (see https://en.wikipedia.org/wiki/Julian_day). + +``offsetbox`` methods +^^^^^^^^^^^^^^^^^^^^^ + +``offsetbox.bbox_artist`` is removed. This was just a wrapper to call +`.patches.bbox_artist` if a flag is set in the file, so use that directly if you need +the behavior. + +``OffsetBox.get_extent_offsets`` and ``OffsetBox.get_extent`` are removed; these methods +are also removed on all subclasses of `.OffsetBox`. To get the offsetbox extents, +instead of ``get_extent``, use `.OffsetBox.get_bbox`, which directly returns a `.Bbox` +instance. To also get the child offsets, instead of ``get_extent_offsets``, separately +call `~.OffsetBox.get_offset` on each children after triggering a draw. + +``parse_fontconfig_pattern`` raises on unknown constant names +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, in a fontconfig pattern like ``DejaVu Sans:foo``, the unknown ``foo`` +constant name would be silently ignored. This now raises an error. + +``tri`` submodules +^^^^^^^^^^^^^^^^^^ + +The ``matplotlib.tri.*`` submodules are removed. All functionality is available in +``matplotlib.tri`` directly and should be imported from there. + +Widget API +^^^^^^^^^^ + +- ``CheckButtons.rectangles`` and ``CheckButtons.lines`` are removed; `.CheckButtons` + now draws itself using `~.Axes.scatter`. +- ``RadioButtons.circles`` is removed; `.RadioButtons` now draws itself using + `~.Axes.scatter`. +- ``MultiCursor.needclear`` is removed with no replacement. +- The unused parameter *x* to ``TextBox.begin_typing`` was a required argument, and is + now removed. + +Most arguments to widgets have been made keyword-only +""""""""""""""""""""""""""""""""""""""""""""""""""""" + +Passing all but the very few first arguments positionally in the constructors of Widgets +is now keyword-only. In general, all optional arguments are keyword-only. + +``Axes3D`` API +^^^^^^^^^^^^^^ + +- ``Axes3D.unit_cube``, ``Axes3D.tunit_cube``, and ``Axes3D.tunit_edges`` are removed + without replacement. +- ``axes3d.vvec``, ``axes3d.eye``, ``axes3d.sx``, and ``axes3d.sy`` are removed without + replacement. + +Inconsistent *nth_coord* and *loc* passed to ``_FixedAxisArtistHelperBase`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The value of the *nth_coord* parameter of ``_FixedAxisArtistHelperBase`` and its +subclasses is now inferred from the value of *loc*; passing inconsistent values (e.g., +requesting a "top y axis" or a "left x axis") has no more effect. + +Passing undefined *label_mode* to ``Grid`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +... is no longer allowed. This includes `mpl_toolkits.axes_grid1.axes_grid.Grid`, +`mpl_toolkits.axes_grid1.axes_grid.AxesGrid`, and +`mpl_toolkits.axes_grid1.axes_grid.ImageGrid` as well as the corresponding classes +imported from `mpl_toolkits.axisartist.axes_grid`. + +Pass ``label_mode='keep'`` instead to get the previous behavior of not modifying labels. + +``draw_gouraud_triangle`` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +... is removed. Use `~.RendererBase.draw_gouraud_triangles` instead. + +A ``draw_gouraud_triangle`` call in a custom `~matplotlib.artist.Artist` can readily be +replaced as:: + + self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)), + colors.reshape((1, 3, 4)), trans) + +A `~.RendererBase.draw_gouraud_triangles` method can be implemented from an +existing ``draw_gouraud_triangle`` method as:: + + transform = transform.frozen() + for tri, col in zip(triangles_array, colors_array): + self.draw_gouraud_triangle(gc, tri, col, transform) + +Miscellaneous removals +^^^^^^^^^^^^^^^^^^^^^^ + +The following items have previously been replaced, and are now removed: + +- *ticklabels* parameter of ``matplotlib.axis.Axis.set_ticklabels`` has been renamed to + *labels*. +- ``Barbs.barbs_doc`` and ``Quiver.quiver_doc`` are removed. These are the doc-strings + and should not be accessible as a named class member, but as normal doc-strings would. +- ``collections.PolyCollection.span_where`` and ``collections.BrokenBarHCollection``; + use ``fill_between`` instead. +- ``Legend.legendHandles`` was undocumented and has been renamed to ``legend_handles``. + +The following items have been removed without replacements: + +- The attributes ``repeat`` of `.TimedAnimation` and subclasses and ``save_count`` of + `.FuncAnimation` are considered private and removed. +- ``matplotlib.backend.backend_agg.BufferRegion.to_string`` +- ``matplotlib.backend.backend_agg.BufferRegion.to_string_argb`` +- ``matplotlib.backends.backend_ps.PsBackendHelper`` +- ``matplotlib.backends.backend_webagg.ServerThread`` +- *raw* parameter of `.GridSpecBase.get_grid_positions` +- ``matplotlib.patches.ConnectionStyle._Base.SimpleEvent`` +- ``passthru_pt`` attribute of ``mpl_toolkits.axisartist.AxisArtistHelper`` diff --git a/doc/users/next_whats_new/3d_axis_limits.rst b/doc/users/next_whats_new/3d_axis_limits.rst deleted file mode 100644 index b460cfdb4f73..000000000000 --- a/doc/users/next_whats_new/3d_axis_limits.rst +++ /dev/null @@ -1,20 +0,0 @@ -Setting 3D axis limits now set the limits exactly -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously, setting the limits of a 3D axis would always add a small margin to -the limits. Limits are now set exactly by default. The newly introduced rcparam -``axes3d.automargin`` can be used to revert to the old behavior where margin is -automatically added. - -.. plot:: - :include-source: true - :alt: Example of the new behavior of 3D axis limits, and how setting the rcparam reverts to the old behavior. - - import matplotlib.pyplot as plt - fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) - - plt.rcParams['axes3d.automargin'] = True - axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior') - - plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0 - axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior') diff --git a/doc/users/next_whats_new/add_EllipseCollection_setters.rst b/doc/users/next_whats_new/add_EllipseCollection_setters.rst deleted file mode 100644 index d3f7b3d85f15..000000000000 --- a/doc/users/next_whats_new/add_EllipseCollection_setters.rst +++ /dev/null @@ -1,40 +0,0 @@ -Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` --------------------------------------------------------------------------- - -The ``widths``, ``heights`` and ``angles`` values of the `~matplotlib.collections.EllipseCollection` -can now be changed after the collection has been created. - -.. plot:: - :include-source: true - - import matplotlib.pyplot as plt - from matplotlib.collections import EllipseCollection - import numpy as np - - rng = np.random.default_rng(0) - - widths = (2, ) - heights = (3, ) - angles = (45, ) - offsets = rng.random((10, 2)) * 10 - - fig, ax = plt.subplots() - - ec = EllipseCollection( - widths=widths, - heights=heights, - angles=angles, - offsets=offsets, - units='x', - offset_transform=ax.transData, - ) - - ax.add_collection(ec) - ax.set_xlim(-2, 12) - ax.set_ylim(-2, 12) - - new_widths = rng.random((10, 2)) * 2 - new_heights = rng.random((10, 2)) * 3 - new_angles = rng.random((10, 2)) * 180 - - ec.set(widths=new_widths, heights=new_heights, angles=new_angles) diff --git a/doc/users/next_whats_new/axis_minorticks_toggle.rst b/doc/users/next_whats_new/axis_minorticks_toggle.rst deleted file mode 100644 index bb6545e5cb4c..000000000000 --- a/doc/users/next_whats_new/axis_minorticks_toggle.rst +++ /dev/null @@ -1,6 +0,0 @@ -Toggle minorticks on Axis ------------------------------- - -Minor ticks on an `~matplotlib.axis.Axis` can be displayed or removed using -`~matplotlib.axis.Axis.minorticks_on` and `~matplotlib.axis.Axis.minorticks_off`; -e.g.: ``ax.xaxis.minorticks_on()``. See also `~matplotlib.axes.Axes.minorticks_on`. diff --git a/doc/users/next_whats_new/backend_registry.rst b/doc/users/next_whats_new/backend_registry.rst deleted file mode 100644 index 7632c978f9c5..000000000000 --- a/doc/users/next_whats_new/backend_registry.rst +++ /dev/null @@ -1,17 +0,0 @@ -BackendRegistry -~~~~~~~~~~~~~~~ - -New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single -source of truth for available backends. The singleton instance is -``matplotlib.backends.backend_registry``. It is used internally by Matplotlib, -and also IPython (and therefore Jupyter) starting with IPython 8.24.0. - -There are three sources of backends: built-in (source code is within the -Matplotlib repository), explicit ``module://some.backend`` syntax (backend is -obtained by loading the module), or via an entry point (self-registering -backend in an external package). - -To obtain a list of all registered backends use: - - >>> from matplotlib.backends import backend_registry - >>> backend_registry.list_all() diff --git a/doc/users/next_whats_new/boxplot_legend_support.rst b/doc/users/next_whats_new/boxplot_legend_support.rst deleted file mode 100644 index 44802960d9bb..000000000000 --- a/doc/users/next_whats_new/boxplot_legend_support.rst +++ /dev/null @@ -1,60 +0,0 @@ -Legend support for Boxplot -~~~~~~~~~~~~~~~~~~~~~~~~~~ -Boxplots now support a *label* parameter to create legend entries. - -Legend labels can be passed as a list of strings to label multiple boxes in a single -`.Axes.boxplot` call: - - -.. plot:: - :include-source: true - :alt: Example of creating 3 boxplots and assigning legend labels as a sequence. - - import matplotlib.pyplot as plt - import numpy as np - - np.random.seed(19680801) - fruit_weights = [ - np.random.normal(130, 10, size=100), - np.random.normal(125, 20, size=100), - np.random.normal(120, 30, size=100), - ] - labels = ['peaches', 'oranges', 'tomatoes'] - colors = ['peachpuff', 'orange', 'tomato'] - - fig, ax = plt.subplots() - ax.set_ylabel('fruit weight (g)') - - bplot = ax.boxplot(fruit_weights, - patch_artist=True, # fill with color - label=labels) - - # fill with colors - for patch, color in zip(bplot['boxes'], colors): - patch.set_facecolor(color) - - ax.set_xticks([]) - ax.legend() - - -Or as a single string to each individual `.Axes.boxplot`: - -.. plot:: - :include-source: true - :alt: Example of creating 2 boxplots and assigning each legend label as a string. - - import matplotlib.pyplot as plt - import numpy as np - - fig, ax = plt.subplots() - - data_A = np.random.random((100, 3)) - data_B = np.random.random((100, 3)) + 0.2 - pos = np.arange(3) - - ax.boxplot(data_A, positions=pos - 0.2, patch_artist=True, label='Box A', - boxprops={'facecolor': 'steelblue'}) - ax.boxplot(data_B, positions=pos + 0.2, patch_artist=True, label='Box B', - boxprops={'facecolor': 'lightblue'}) - - ax.legend() diff --git a/doc/users/next_whats_new/figure_align_titles.rst b/doc/users/next_whats_new/figure_align_titles.rst deleted file mode 100644 index 230e5f0a8990..000000000000 --- a/doc/users/next_whats_new/figure_align_titles.rst +++ /dev/null @@ -1,7 +0,0 @@ -subplot titles can now be automatically aligned ------------------------------------------------ - -Subplot axes titles can be misaligned vertically if tick labels or -xlabels are placed at the top of one subplot. The new method on the -`.Figure` class: `.Figure.align_titles` will now align the titles -vertically. diff --git a/doc/users/next_whats_new/formatter_unicode_minus.rst b/doc/users/next_whats_new/formatter_unicode_minus.rst deleted file mode 100644 index 1b12b216240e..000000000000 --- a/doc/users/next_whats_new/formatter_unicode_minus.rst +++ /dev/null @@ -1,4 +0,0 @@ -``StrMethodFormatter`` now respects ``axes.unicode_minus`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When formatting negative values, `.StrMethodFormatter` will now use unicode -minus signs if :rc:`axes.unicode_minus` is set. diff --git a/doc/users/next_whats_new/inset_axes.rst b/doc/users/next_whats_new/inset_axes.rst deleted file mode 100644 index d283dfc91b30..000000000000 --- a/doc/users/next_whats_new/inset_axes.rst +++ /dev/null @@ -1,4 +0,0 @@ -``Axes.inset_axes`` is no longer experimental -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Axes.inset_axes is considered stable for use. diff --git a/doc/users/next_whats_new/interpolation_stage_rc.rst b/doc/users/next_whats_new/interpolation_stage_rc.rst deleted file mode 100644 index bd3ecc563e5d..000000000000 --- a/doc/users/next_whats_new/interpolation_stage_rc.rst +++ /dev/null @@ -1,4 +0,0 @@ -``image.interpolation_stage`` rcParam -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This new rcParam controls whether image interpolation occurs in "data" space or -in "rgba" space. diff --git a/doc/users/next_whats_new/margin_getters.rst b/doc/users/next_whats_new/margin_getters.rst deleted file mode 100644 index c43709a17d52..000000000000 --- a/doc/users/next_whats_new/margin_getters.rst +++ /dev/null @@ -1,4 +0,0 @@ -Getters for xmargin, ymargin and zmargin ----------------------------------------- -``.Axes.get_xmargin()``, ``.Axes.get_ymargin()`` and ``.Axes3D.get_zmargin()`` methods have been added to return -the margin values set by ``.Axes.set_xmargin()``, ``.Axes.set_ymargin()`` and ``.Axes3D.set_zmargin()``, respectively. diff --git a/doc/users/next_whats_new/mathtext_documentation.rst b/doc/users/next_whats_new/mathtext_documentation.rst deleted file mode 100644 index 2b7cd51b702c..000000000000 --- a/doc/users/next_whats_new/mathtext_documentation.rst +++ /dev/null @@ -1,5 +0,0 @@ -``mathtext`` documentation improvements ---------------------------------------- - -The documentation is updated to take information directly from the parser. This -means that (almost) all supported symbols, operators etc are shown at :ref:`mathtext`. diff --git a/doc/users/next_whats_new/mathtext_spacing.rst b/doc/users/next_whats_new/mathtext_spacing.rst deleted file mode 100644 index 42da810c3a39..000000000000 --- a/doc/users/next_whats_new/mathtext_spacing.rst +++ /dev/null @@ -1,5 +0,0 @@ -``mathtext`` spacing corrections --------------------------------- - -As consequence of the updated documentation, the spacing on a number of relational and -operator symbols were classified like that and therefore will be spaced properly. diff --git a/doc/users/next_whats_new/nonuniformimage_mousover.rst b/doc/users/next_whats_new/nonuniformimage_mousover.rst deleted file mode 100644 index e5a7ab1bd155..000000000000 --- a/doc/users/next_whats_new/nonuniformimage_mousover.rst +++ /dev/null @@ -1,4 +0,0 @@ -NonUniformImage now has mouseover support ------------------------------------------ -When mousing over a `~matplotlib.image.NonUniformImage` the data values are now -displayed. diff --git a/doc/users/next_whats_new/pie_percent_latex.rst b/doc/users/next_whats_new/pie_percent_latex.rst deleted file mode 100644 index 7ed547302789..000000000000 --- a/doc/users/next_whats_new/pie_percent_latex.rst +++ /dev/null @@ -1,11 +0,0 @@ -Percent sign in pie labels auto-escaped with ``usetex=True`` ------------------------------------------------------------- - -It is common, with `.Axes.pie`, to specify labels that include a percent sign -(``%``), which denotes a comment for LaTeX. When enabling LaTeX with -:rc:`text.usetex` or passing ``textprops={"usetex": True}``, this would cause -the percent sign to disappear. - -Now, the percent sign is automatically escaped (by adding a preceding -backslash) so that it appears regardless of the ``usetex`` setting. If you have -pre-escaped the percent sign, this will be detected, and remain as is. diff --git a/doc/users/next_whats_new/polar-line-spans.rst b/doc/users/next_whats_new/polar-line-spans.rst deleted file mode 100644 index 47bb382dbdbf..000000000000 --- a/doc/users/next_whats_new/polar-line-spans.rst +++ /dev/null @@ -1,5 +0,0 @@ -``axhline`` and ``axhspan`` on polar axes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -... now draw circles and circular arcs (`~.Axes.axhline`) or annuli and wedges -(`~.Axes.axhspan`). diff --git a/doc/users/next_whats_new/sides_violinplot.rst b/doc/users/next_whats_new/sides_violinplot.rst deleted file mode 100644 index f1643de8e322..000000000000 --- a/doc/users/next_whats_new/sides_violinplot.rst +++ /dev/null @@ -1,4 +0,0 @@ -Add option to plot only one half of violin plot -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Setting the parameter *side* to 'low' or 'high' allows to only plot one half of the violin plot. diff --git a/doc/users/next_whats_new/stackplot_hatch.rst b/doc/users/next_whats_new/stackplot_hatch.rst deleted file mode 100644 index 8fd4ca0f81b0..000000000000 --- a/doc/users/next_whats_new/stackplot_hatch.rst +++ /dev/null @@ -1,27 +0,0 @@ -``hatch`` parameter for stackplot -------------------------------------------- - -The `~.Axes.stackplot` *hatch* parameter now accepts a list of strings describing hatching styles that will be applied sequentially to the layers in the stack: - -.. plot:: - :include-source: true - :alt: Two charts, identified as ax1 and ax2, showing "stackplots", i.e. one-dimensional distributions of data stacked on top of one another. The first plot, ax1 has cross-hatching on all slices, having been given a single string as the "hatch" argument. The second plot, ax2 has different styles of hatching on each slice - diagonal hatching in opposite directions on the first two slices, cross-hatching on the third slice, and open circles on the fourth. - - import matplotlib.pyplot as plt - fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10,5)) - - cols = 10 - rows = 4 - data = ( - np.reshape(np.arange(0, cols, 1), (1, -1)) ** 2 - + np.reshape(np.arange(0, rows), (-1, 1)) - + np.random.random((rows, cols))*5 - ) - x = range(data.shape[1]) - ax1.stackplot(x, data, hatch="x") - ax2.stackplot(x, data, hatch=["//","\\","x","o"]) - - ax1.set_title("hatch='x'") - ax2.set_title("hatch=['//','\\\\','x','o']") - - plt.show() diff --git a/doc/users/next_whats_new/stdfmt-axisartist.rst b/doc/users/next_whats_new/stdfmt-axisartist.rst deleted file mode 100644 index 9cb014413042..000000000000 --- a/doc/users/next_whats_new/stdfmt-axisartist.rst +++ /dev/null @@ -1,3 +0,0 @@ -``axisartist`` can now be used together with standard ``Formatters`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... instead of being limited to axisartist-specific ones. diff --git a/doc/users/next_whats_new/subfigure_zorder.rst b/doc/users/next_whats_new/subfigure_zorder.rst deleted file mode 100644 index a740bbda8eb6..000000000000 --- a/doc/users/next_whats_new/subfigure_zorder.rst +++ /dev/null @@ -1,22 +0,0 @@ -Subfigures have now controllable zorders -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously, setting the zorder of a subfigure had no effect, and those were plotted on top of any figure-level artists (i.e for example on top of fig-level legends). Now, subfigures behave like any other artists, and their zorder can be controlled, with default a zorder of 0. - -.. plot:: - :include-source: true - :alt: Example on controlling the zorder of a subfigure - - import matplotlib.pyplot as plt - import numpy as np - x = np.linspace(1, 10, 10) - y1, y2 = x, -x - fig = plt.figure(constrained_layout=True) - subfigs = fig.subfigures(nrows=1, ncols=2) - for subfig in subfigs: - axarr = subfig.subplots(2, 1) - for ax in axarr.flatten(): - (l1,) = ax.plot(x, y1, label="line1") - (l2,) = ax.plot(x, y2, label="line2") - subfigs[0].set_zorder(6) - l = fig.legend(handles=[l1, l2], loc="upper center", ncol=2) diff --git a/doc/users/next_whats_new/update_arrow_patch.rst b/doc/users/next_whats_new/update_arrow_patch.rst deleted file mode 100644 index 894090587b5d..000000000000 --- a/doc/users/next_whats_new/update_arrow_patch.rst +++ /dev/null @@ -1,30 +0,0 @@ -Update the position of arrow patch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Adds a setter method that allows the user to update the position of the -`.patches.Arrow` object without requiring a full re-draw. - -.. plot:: - :include-source: true - :alt: Example of changing the position of the arrow with the new ``set_data`` method. - - import matplotlib as mpl - import matplotlib.pyplot as plt - from matplotlib.patches import Arrow - import matplotlib.animation as animation - - fig, ax = plt.subplots() - ax.set_xlim(0, 10) - ax.set_ylim(0, 10) - - a = mpl.patches.Arrow(2, 0, 0, 10) - ax.add_patch(a) - - - # code for modifying the arrow - def update(i): - a.set_data(x=.5, dx=i, dy=6, width=2) - - - ani = animation.FuncAnimation(fig, update, frames=15, interval=90, blit=False) - - plt.show() diff --git a/doc/users/next_whats_new/widget_button_clear.rst b/doc/users/next_whats_new/widget_button_clear.rst deleted file mode 100644 index 2d16cf281e7c..000000000000 --- a/doc/users/next_whats_new/widget_button_clear.rst +++ /dev/null @@ -1,6 +0,0 @@ -Check and Radio Button widgets support clearing -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `.CheckButtons` and `.RadioButtons` widgets now support clearing their -state by calling their ``.clear`` method. Note that it is not possible to have -no selected radio buttons, so the selected option at construction time is selected. diff --git a/doc/users/prev_whats_new/whats_new_3.9.0.rst b/doc/users/prev_whats_new/whats_new_3.9.0.rst new file mode 100644 index 000000000000..c111455f8ef8 --- /dev/null +++ b/doc/users/prev_whats_new/whats_new_3.9.0.rst @@ -0,0 +1,409 @@ +============================================= +What's new in Matplotlib 3.9.0 (Apr 09, 2024) +============================================= + +For a list of all of the issues and pull requests since the last revision, see the +:ref:`github-stats`. + +.. contents:: Table of Contents + :depth: 4 + +.. toctree:: + :maxdepth: 4 + +Plotting and Annotation improvements +==================================== + +``Axes.inset_axes`` is no longer experimental +--------------------------------------------- + +`.Axes.inset_axes` is considered stable for use. + +Legend support for Boxplot +-------------------------- + +Boxplots now support a *label* parameter to create legend entries. Legend labels can be +passed as a list of strings to label multiple boxes in a single `.Axes.boxplot` call: + +.. plot:: + :include-source: + :alt: Example of creating 3 boxplots and assigning legend labels as a sequence. + + np.random.seed(19680801) + fruit_weights = [ + np.random.normal(130, 10, size=100), + np.random.normal(125, 20, size=100), + np.random.normal(120, 30, size=100), + ] + labels = ['peaches', 'oranges', 'tomatoes'] + colors = ['peachpuff', 'orange', 'tomato'] + + fig, ax = plt.subplots() + ax.set_ylabel('fruit weight (g)') + + bplot = ax.boxplot(fruit_weights, + patch_artist=True, # fill with color + label=labels) + + # fill with colors + for patch, color in zip(bplot['boxes'], colors): + patch.set_facecolor(color) + + ax.set_xticks([]) + ax.legend() + + +Or as a single string to each individual `.Axes.boxplot`: + +.. plot:: + :include-source: + :alt: Example of creating 2 boxplots and assigning each legend label as a string. + + fig, ax = plt.subplots() + + data_A = np.random.random((100, 3)) + data_B = np.random.random((100, 3)) + 0.2 + pos = np.arange(3) + + ax.boxplot(data_A, positions=pos - 0.2, patch_artist=True, label='Box A', + boxprops={'facecolor': 'steelblue'}) + ax.boxplot(data_B, positions=pos + 0.2, patch_artist=True, label='Box B', + boxprops={'facecolor': 'lightblue'}) + + ax.legend() + +Percent sign in pie labels auto-escaped with ``usetex=True`` +------------------------------------------------------------ + +It is common, with `.Axes.pie`, to specify labels that include a percent sign (``%``), +which denotes a comment for LaTeX. When enabling LaTeX with :rc:`text.usetex` or passing +``textprops={"usetex": True}``, this used to cause the percent sign to disappear. + +Now, the percent sign is automatically escaped (by adding a preceding backslash) so that +it appears regardless of the ``usetex`` setting. If you have pre-escaped the percent +sign, this will be detected, and remain as is. + +``hatch`` parameter for stackplot +--------------------------------- + +The `~.Axes.stackplot` *hatch* parameter now accepts a list of strings describing +hatching styles that will be applied sequentially to the layers in the stack: + +.. plot:: + :include-source: + :alt: Two charts, identified as ax1 and ax2, showing "stackplots", i.e. one-dimensional distributions of data stacked on top of one another. The first plot, ax1 has cross-hatching on all slices, having been given a single string as the "hatch" argument. The second plot, ax2 has different styles of hatching on each slice - diagonal hatching in opposite directions on the first two slices, cross-hatching on the third slice, and open circles on the fourth. + + fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10,5)) + + cols = 10 + rows = 4 + data = ( + np.reshape(np.arange(0, cols, 1), (1, -1)) ** 2 + + np.reshape(np.arange(0, rows), (-1, 1)) + + np.random.random((rows, cols))*5 + ) + x = range(data.shape[1]) + ax1.stackplot(x, data, hatch="x") + ax2.stackplot(x, data, hatch=["//","\\","x","o"]) + + ax1.set_title("hatch='x'") + ax2.set_title("hatch=['//','\\\\','x','o']") + + plt.show() + +Add option to plot only one half of violin plot +----------------------------------------------- + +Setting the parameter *side* to 'low' or 'high' allows to only plot one half of the +`.Axes.violinplot`. + +.. plot:: + :include-source: + :alt: Three copies of a vertical violin plot; first in blue showing the default of both sides, followed by an orange copy that only shows the "low" (or left, in this case) side, and finally a green copy that only shows the "high" (or right) side. + + # Fake data with reproducible random state. + np.random.seed(19680801) + data = np.random.normal(0, 8, size=100) + + fig, ax = plt.subplots() + + ax.violinplot(data, [0], showmeans=True, showextrema=True) + ax.violinplot(data, [1], showmeans=True, showextrema=True, side='low') + ax.violinplot(data, [2], showmeans=True, showextrema=True, side='high') + + ax.set_title('Violin Sides Example') + ax.set_xticks([0, 1, 2], ['Default', 'side="low"', 'side="high"']) + ax.set_yticklabels([]) + +``axhline`` and ``axhspan`` on polar axes +----------------------------------------- + +... now draw circles and circular arcs (`~.Axes.axhline`) or annuli and wedges +(`~.Axes.axhspan`). + +.. plot:: + :include-source: + :alt: A sample polar plot, that contains an axhline at radius 1, an axhspan annulus between radius 0.8 and 0.9, and an axhspan wedge between radius 0.6 and 0.7 and 288° and 324°. + + fig = plt.figure() + ax = fig.add_subplot(projection="polar") + ax.set_rlim(0, 1.2) + + ax.axhline(1, c="C0", alpha=.5) + ax.axhspan(.8, .9, fc="C1", alpha=.5) + ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5) + +Subplot titles can now be automatically aligned +----------------------------------------------- + +Subplot axes titles can be misaligned vertically if tick labels or xlabels are placed at +the top of one subplot. The new `~.Figure.align_titles` method on the `.Figure` class +will now align the titles vertically. + +.. plot:: + :include-source: + :alt: A figure with two Axes side-by-side, the second of which with ticks on top. The Axes titles and x-labels appear unaligned with each other due to these ticks. + + fig, axs = plt.subplots(1, 2, layout='constrained') + + axs[0].plot(np.arange(0, 1e6, 1000)) + axs[0].set_title('Title 0') + axs[0].set_xlabel('XLabel 0') + + axs[1].plot(np.arange(1, 0, -0.1) * 2000, np.arange(1, 0, -0.1)) + axs[1].set_title('Title 1') + axs[1].set_xlabel('XLabel 1') + axs[1].xaxis.tick_top() + axs[1].tick_params(axis='x', rotation=55) + +.. plot:: + :include-source: + :alt: A figure with two Axes side-by-side, the second of which with ticks on top. Unlike the previous figure, the Axes titles and x-labels appear aligned. + + fig, axs = plt.subplots(1, 2, layout='constrained') + + axs[0].plot(np.arange(0, 1e6, 1000)) + axs[0].set_title('Title 0') + axs[0].set_xlabel('XLabel 0') + + axs[1].plot(np.arange(1, 0, -0.1) * 2000, np.arange(1, 0, -0.1)) + axs[1].set_title('Title 1') + axs[1].set_xlabel('XLabel 1') + axs[1].xaxis.tick_top() + axs[1].tick_params(axis='x', rotation=55) + + fig.align_labels() + fig.align_titles() + +``axisartist`` can now be used together with standard ``Formatters`` +-------------------------------------------------------------------- + +... instead of being limited to axisartist-specific ones. + +Toggle minorticks on Axis +------------------------- + +Minor ticks on an `~matplotlib.axis.Axis` can be displayed or removed using +`~matplotlib.axis.Axis.minorticks_on` and `~matplotlib.axis.Axis.minorticks_off`; e.g., +``ax.xaxis.minorticks_on()``. See also `~matplotlib.axes.Axes.minorticks_on`. + +``StrMethodFormatter`` now respects ``axes.unicode_minus`` +---------------------------------------------------------- + +When formatting negative values, `.StrMethodFormatter` will now use unicode minus signs +if :rc:`axes.unicode_minus` is set. + + >>> from matplotlib.ticker import StrMethodFormatter + >>> with plt.rc_context({'axes.unicode_minus': False}): + ... formatter = StrMethodFormatter('{x}') + ... print(formatter.format_data(-10)) + -10 + + >>> with plt.rc_context({'axes.unicode_minus': True}): + ... formatter = StrMethodFormatter('{x}') + ... print(formatter.format_data(-10)) + −10 + +Figure, Axes, and Legend Layout +=============================== + +Subfigures now have controllable zorders +---------------------------------------- + +Previously, setting the zorder of a subfigure had no effect, and those were plotted on +top of any figure-level artists (i.e for example on top of fig-level legends). Now, +subfigures behave like any other artists, and their zorder can be controlled, with +default a zorder of 0. + +.. plot:: + :include-source: + :alt: Example on controlling the zorder of a subfigure + + x = np.linspace(1, 10, 10) + y1, y2 = x, -x + fig = plt.figure(constrained_layout=True) + subfigs = fig.subfigures(nrows=1, ncols=2) + for subfig in subfigs: + axarr = subfig.subplots(2, 1) + for ax in axarr.flatten(): + (l1,) = ax.plot(x, y1, label="line1") + (l2,) = ax.plot(x, y2, label="line2") + subfigs[0].set_zorder(6) + l = fig.legend(handles=[l1, l2], loc="upper center", ncol=2) + +Getters for xmargin, ymargin and zmargin +---------------------------------------- + +`.Axes.get_xmargin`, `.Axes.get_ymargin` and `.Axes3D.get_zmargin` methods have been +added to return the margin values set by `.Axes.set_xmargin`, `.Axes.set_ymargin` and +`.Axes3D.set_zmargin`, respectively. + +Mathtext improvements +===================== + +``mathtext`` documentation improvements +--------------------------------------- + +The documentation is updated to take information directly from the parser. This means +that (almost) all supported symbols, operators, etc. are shown at :ref:`mathtext`. + +``mathtext`` spacing corrections +-------------------------------- + +As consequence of the updated documentation, the spacing on a number of relational and +operator symbols were correctly classified and therefore will be spaced properly. + +Widget Improvements +=================== + +Check and Radio Button widgets support clearing +----------------------------------------------- + +The `.CheckButtons` and `.RadioButtons` widgets now support clearing their state by +calling their ``.clear`` method. Note that it is not possible to have no selected radio +buttons, so the selected option at construction time is selected. + +3D plotting improvements +======================== + +Setting 3D axis limits now set the limits exactly +------------------------------------------------- + +Previously, setting the limits of a 3D axis would always add a small margin to the +limits. Limits are now set exactly by default. The newly introduced rcparam +``axes3d.automargin`` can be used to revert to the old behavior where margin is +automatically added. + +.. plot:: + :include-source: + :alt: Example of the new behavior of 3D axis limits, and how setting the rcParam reverts to the old behavior. + + fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) + + plt.rcParams['axes3d.automargin'] = True + axs[0].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='Old Behavior') + + plt.rcParams['axes3d.automargin'] = False # the default in 3.9.0 + axs[1].set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), title='New Behavior') + +Other improvements +================== + +BackendRegistry +--------------- + +New :class:`~matplotlib.backends.registry.BackendRegistry` class is the single source of +truth for available backends. The singleton instance is +``matplotlib.backends.backend_registry``. It is used internally by Matplotlib, and also +IPython (and therefore Jupyter) starting with IPython 8.24.0. + +There are three sources of backends: built-in (source code is within the Matplotlib +repository), explicit ``module://some.backend`` syntax (backend is obtained by loading +the module), or via an entry point (self-registering backend in an external package). + +To obtain a list of all registered backends use: + + >>> from matplotlib.backends import backend_registry + >>> backend_registry.list_all() + +Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` +-------------------------------------------------------------------------- + +The ``widths``, ``heights`` and ``angles`` values of the +`~matplotlib.collections.EllipseCollection` can now be changed after the collection has +been created. + +.. plot:: + :include-source: + + from matplotlib.collections import EllipseCollection + + rng = np.random.default_rng(0) + + widths = (2, ) + heights = (3, ) + angles = (45, ) + offsets = rng.random((10, 2)) * 10 + + fig, ax = plt.subplots() + + ec = EllipseCollection( + widths=widths, + heights=heights, + angles=angles, + offsets=offsets, + units='x', + offset_transform=ax.transData, + ) + + ax.add_collection(ec) + ax.set_xlim(-2, 12) + ax.set_ylim(-2, 12) + + new_widths = rng.random((10, 2)) * 2 + new_heights = rng.random((10, 2)) * 3 + new_angles = rng.random((10, 2)) * 180 + + ec.set(widths=new_widths, heights=new_heights, angles=new_angles) + +``image.interpolation_stage`` rcParam +------------------------------------- + +This new rcParam controls whether image interpolation occurs in "data" space or in +"rgba" space. + +Arrow patch position is now modifiable +-------------------------------------- + +A setter method has been added that allows updating the position of the `.patches.Arrow` +object without requiring a full re-draw. + +.. plot:: + :include-source: + :alt: Example of changing the position of the arrow with the new ``set_data`` method. + + from matplotlib import animation + from matplotlib.patches import Arrow + + fig, ax = plt.subplots() + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + + a = Arrow(2, 0, 0, 10) + ax.add_patch(a) + + + # code for modifying the arrow + def update(i): + a.set_data(x=.5, dx=i, dy=6, width=2) + + + ani = animation.FuncAnimation(fig, update, frames=15, interval=90, blit=False) + + plt.show() + +NonUniformImage now has mouseover support +----------------------------------------- + +When mousing over a `~matplotlib.image.NonUniformImage`, the data values are now +displayed. diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 63f417e6a26d..3befbeee5b77 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -18,8 +18,8 @@ Version 3.9 .. toctree:: :maxdepth: 1 - next_whats_new - ../api/next_api_changes + prev_whats_new/whats_new_3.9.0.rst + ../api/prev_api_changes/api_changes_3.9.0.rst github_stats.rst Version 3.8 @@ -30,7 +30,6 @@ Version 3.8 prev_whats_new/whats_new_3.8.0.rst ../api/prev_api_changes/api_changes_3.8.1.rst ../api/prev_api_changes/api_changes_3.8.0.rst - github_stats.rst prev_whats_new/github_stats_3.8.3.rst prev_whats_new/github_stats_3.8.2.rst prev_whats_new/github_stats_3.8.1.rst From 29e77e369d8b72f6161c1e4211256a48bd6bc19a Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 8 May 2024 16:08:47 -0500 Subject: [PATCH 0970/1120] Backport PR #28103: [DOC]: Fix compatibility with sphinx-gallery 0.16 --- doc/conf.py | 51 +++++++++++---------------- doc/sphinxext/gallery_order.py | 2 +- doc/sphinxext/util.py | 21 +++++++++++ lib/matplotlib/tests/test_doc.py | 5 +-- requirements/doc/doc-requirements.txt | 4 +-- 5 files changed, 48 insertions(+), 35 deletions(-) create mode 100644 doc/sphinxext/util.py diff --git a/doc/conf.py b/doc/conf.py index bc9b1ff7c1fa..c9a475aecf9c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,6 +22,7 @@ from urllib.parse import urlsplit, urlunsplit import warnings +from packaging.version import parse as parse_version import sphinx import yaml @@ -178,9 +179,20 @@ def _check_dependencies(): # Import only after checking for dependencies. -# gallery_order.py from the sphinxext folder provides the classes that -# allow custom ordering of sections and subsections of the gallery -import sphinxext.gallery_order as gallery_order +import sphinx_gallery + +if parse_version(sphinx_gallery.__version__) >= parse_version('0.16.0'): + gallery_order_sectionorder = 'sphinxext.gallery_order.sectionorder' + gallery_order_subsectionorder = 'sphinxext.gallery_order.subsectionorder' + clear_basic_units = 'sphinxext.util.clear_basic_units' + matplotlib_reduced_latex_scraper = 'sphinxext.util.matplotlib_reduced_latex_scraper' +else: + # gallery_order.py from the sphinxext folder provides the classes that + # allow custom ordering of sections and subsections of the gallery + from sphinxext.gallery_order import ( + sectionorder as gallery_order_sectionorder, + subsectionorder as gallery_order_subsectionorder) + from sphinxext.util import clear_basic_units, matplotlib_reduced_latex_scraper # The following import is only necessary to monkey patch the signature later on from sphinx_gallery import gen_rst @@ -228,22 +240,6 @@ def _check_dependencies(): } -# Sphinx gallery configuration - -def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, - **kwargs): - """ - Reduce srcset when creating a PDF. - - Because sphinx-gallery runs *very* early, we cannot modify this even in the - earliest builder-inited signal. Thus we do it at scraping time. - """ - from sphinx_gallery.scrapers import matplotlib_scraper - - if gallery_conf['builder_name'] == 'latex': - gallery_conf['image_srcset'] = [] - return matplotlib_scraper(block, block_vars, gallery_conf, **kwargs) - gallery_dirs = [f'{ed}' for ed in ['gallery', 'tutorials', 'plot_types', 'users/explain'] if f'{ed}/*' not in skip_subdirs] @@ -254,7 +250,7 @@ def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, example_dirs += [f'../galleries/{gd}'] sphinx_gallery_conf = { - 'backreferences_dir': Path('api') / Path('_as_gen'), + 'backreferences_dir': Path('api', '_as_gen'), # Compression is a significant effort that we skip for local and CI builds. 'compress_images': ('thumbnails', 'images') if is_release_build else (), 'doc_module': ('matplotlib', 'mpl_toolkits'), @@ -269,14 +265,10 @@ def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, 'plot_gallery': 'True', # sphinx-gallery/913 'reference_url': {'matplotlib': None}, 'remove_config_comments': True, - 'reset_modules': ( - 'matplotlib', - # clear basic_units module to re-register with unit registry on import - lambda gallery_conf, fname: sys.modules.pop('basic_units', None) - ), - 'subsection_order': gallery_order.sectionorder, + 'reset_modules': ('matplotlib', clear_basic_units), + 'subsection_order': gallery_order_sectionorder, 'thumbnail_size': (320, 224), - 'within_subsection_order': gallery_order.subsectionorder, + 'within_subsection_order': gallery_order_subsectionorder, 'capture_repr': (), 'copyfile_regex': r'.*\.rst', } @@ -333,7 +325,7 @@ def gallery_image_warning_filter(record): :class: sphx-glr-download-link-note :ref:`Go to the end ` - to download the full example code{2} + to download the full example code.{2} .. rst-class:: sphx-glr-example-title @@ -758,7 +750,6 @@ def js_tag_with_cache_busting(js): if link_github: import inspect - from packaging.version import parse extensions.append('sphinx.ext.linkcode') @@ -814,7 +805,7 @@ def linkcode_resolve(domain, info): if not fn.startswith(('matplotlib/', 'mpl_toolkits/')): return None - version = parse(matplotlib.__version__) + version = parse_version(matplotlib.__version__) tag = 'main' if version.is_devrelease else f'v{version.public}' return ("https://github.com/matplotlib/matplotlib/blob" f"/{tag}/lib/{fn}{linespec}") diff --git a/doc/sphinxext/gallery_order.py b/doc/sphinxext/gallery_order.py index 70a018750537..378cb394d37b 100644 --- a/doc/sphinxext/gallery_order.py +++ b/doc/sphinxext/gallery_order.py @@ -105,7 +105,7 @@ def __call__(self, item): explicit_subsection_order = [item + ".py" for item in list_all] -class MplExplicitSubOrder: +class MplExplicitSubOrder(ExplicitOrder): """For use within the 'within_subsection_order' key.""" def __init__(self, src_dir): self.src_dir = src_dir # src_dir is unused here diff --git a/doc/sphinxext/util.py b/doc/sphinxext/util.py new file mode 100644 index 000000000000..14097ba9396a --- /dev/null +++ b/doc/sphinxext/util.py @@ -0,0 +1,21 @@ +import sys + + +def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, + **kwargs): + """ + Reduce srcset when creating a PDF. + + Because sphinx-gallery runs *very* early, we cannot modify this even in the + earliest builder-inited signal. Thus we do it at scraping time. + """ + from sphinx_gallery.scrapers import matplotlib_scraper + + if gallery_conf['builder_name'] == 'latex': + gallery_conf['image_srcset'] = [] + return matplotlib_scraper(block, block_vars, gallery_conf, **kwargs) + + +# Clear basic_units module to re-register with unit registry on import. +def clear_basic_units(gallery_conf, fname): + return sys.modules.pop('basic_units', None) diff --git a/lib/matplotlib/tests/test_doc.py b/lib/matplotlib/tests/test_doc.py index 592a24198d1b..3e28fd1b8eb7 100644 --- a/lib/matplotlib/tests/test_doc.py +++ b/lib/matplotlib/tests/test_doc.py @@ -9,7 +9,8 @@ def test_sphinx_gallery_example_header(): EXAMPLE_HEADER, this test will start to fail. In that case, please update the monkey-patching of EXAMPLE_HEADER in conf.py. """ - gen_rst = pytest.importorskip('sphinx_gallery.gen_rst') + pytest.importorskip('sphinx_gallery', minversion='0.16.0') + from sphinx_gallery import gen_rst EXAMPLE_HEADER = """ .. DO NOT EDIT. @@ -24,7 +25,7 @@ def test_sphinx_gallery_example_header(): :class: sphx-glr-download-link-note :ref:`Go to the end ` - to download the full example code{2} + to download the full example code.{2} .. rst-class:: sphx-glr-example-title diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 8f8e01a34e4d..e7fc207a739c 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -7,7 +7,7 @@ # Install the documentation requirements with: # pip install -r requirements/doc/doc-requirements.txt # -sphinx>=3.0.0,!=6.1.2,!=7.3.* +sphinx>=3.0.0,!=6.1.2 colorspacious ipython ipywidgets @@ -18,7 +18,7 @@ pydata-sphinx-theme~=0.15.0 mpl-sphinx-theme~=3.8.0 pyyaml sphinxcontrib-svg2pdfconverter>=1.1.0 -sphinx-gallery>=0.12.0 sphinx-copybutton sphinx-design +sphinx-gallery>=0.12.0 sphinx-tags>=0.3.0 From 4d4c8e35c98daabb8276a32e88f1478ffab4b9af Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 9 May 2024 03:12:49 +0200 Subject: [PATCH 0971/1120] Backport PR #28185: DOC: Bump mpl-sphinx-theme to 3.9 --- requirements/doc/doc-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 8f8e01a34e4d..b3ab5e4858bf 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -15,7 +15,7 @@ ipykernel numpydoc>=1.0 packaging>=20 pydata-sphinx-theme~=0.15.0 -mpl-sphinx-theme~=3.8.0 +mpl-sphinx-theme~=3.9.0 pyyaml sphinxcontrib-svg2pdfconverter>=1.1.0 sphinx-gallery>=0.12.0 From ae5f9c16fe5cfeb9614822b9c8bfd8202ad3b193 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 9 May 2024 03:14:58 +0200 Subject: [PATCH 0972/1120] Backport PR #28188: [TST] Bump some tolerances for Macos ARM --- lib/matplotlib/tests/test_axes.py | 2 +- lib/matplotlib/tests/test_lines.py | 2 +- lib/matplotlib/tests/test_patheffects.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 819f4eb3b598..0ed5a11c1398 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5829,7 +5829,7 @@ def test_pie_linewidth_0(): plt.axis('equal') -@image_comparison(['pie_center_radius.png'], style='mpl20', tol=0.007) +@image_comparison(['pie_center_radius.png'], style='mpl20', tol=0.01) def test_pie_center_radius(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index c7b7353fa0db..531237b2ba28 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -185,7 +185,7 @@ def test_set_drawstyle(): @image_comparison( ['line_collection_dashes'], remove_text=True, style='mpl20', - tol=0.65 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) + tol=0 if platform.machine() == 'x86_64' else 0.65) def test_set_line_coll_dash_image(): fig, ax = plt.subplots() np.random.seed(0) diff --git a/lib/matplotlib/tests/test_patheffects.py b/lib/matplotlib/tests/test_patheffects.py index 7c4c82751240..bf067b2abbfd 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -30,7 +30,7 @@ def test_patheffect1(): @image_comparison(['patheffect2'], remove_text=True, style='mpl20', - tol=0.052 if platform.machine() == 'arm64' else 0) + tol=0.06 if platform.machine() == 'arm64' else 0) def test_patheffect2(): ax2 = plt.subplot() diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 731b0413bf65..ed56e5505d8e 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -113,7 +113,8 @@ def test_axes3d_repr(): "title={'center': 'title'}, xlabel='x', ylabel='y', zlabel='z'>") -@mpl3d_image_comparison(['axes3d_primary_views.png'], style='mpl20') +@mpl3d_image_comparison(['axes3d_primary_views.png'], style='mpl20', + tol=0.05 if platform.machine() == "arm64" else 0) def test_axes3d_primary_views(): # (elev, azim, roll) views = [(90, -90, 0), # XY @@ -1589,7 +1590,7 @@ def test_errorbar3d_errorevery(): @mpl3d_image_comparison(['errorbar3d.png'], style='mpl20', - tol=0.014 if platform.machine() == 'arm64' else 0) + tol=0.02 if platform.machine() == 'arm64' else 0) def test_errorbar3d(): """Tests limits, color styling, and legend for 3D errorbars.""" fig = plt.figure() From 065a188aeb4702eee67b111cbe18d59bbb84de8b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 8 May 2024 17:08:54 -0400 Subject: [PATCH 0973/1120] DOC: Use released mpl-sphinx-theme on v3.9.x --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ab22d302314..eef9ca87f30d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,8 +107,6 @@ commands: python -m pip install --user \ numpy<< parameters.numpy_version >> \ -r requirements/doc/doc-requirements.txt - python -m pip install --no-deps --user \ - git+https://github.com/matplotlib/mpl-sphinx-theme.git mpl-install: steps: From 1b526c3286becf1e7fdfe9291d437aa19c8f3bb5 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 9 May 2024 13:29:38 -0500 Subject: [PATCH 0974/1120] Backport PR #28164: CI: Ensure code coverage is always uploaded --- .github/workflows/cygwin.yml | 7 +------ .github/workflows/tests.yml | 3 ++- azure-pipelines.yml | 2 ++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 3e2a6144dece..58c132315b6f 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -245,9 +245,4 @@ jobs: run: | xvfb-run pytest-3.${{ matrix.python-minor-version }} -rfEsXR -n auto \ --maxfail=50 --timeout=300 --durations=25 \ - --cov-report=xml --cov=lib --log-level=DEBUG --color=yes - - - name: Upload code coverage - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} + --cov-report=term --cov=lib --log-level=DEBUG --color=yes diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13f6e8352d73..a24f8cdc2f5b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -316,6 +316,7 @@ jobs: --cov-report=xml --cov=lib --log-level=DEBUG --color=yes - name: Filter C coverage + if: ${{ !cancelled() && github.event_name != 'schedule' }} run: | if [[ "${{ runner.os }}" != 'macOS' ]]; then lcov --rc lcov_branch_coverage=1 --capture --directory . \ @@ -331,7 +332,7 @@ jobs: -instr-profile default.profdata > info.lcov fi - name: Upload code coverage - if: ${{ github.event_name != 'schedule' }} + if: ${{ !cancelled() && github.event_name != 'schedule' }} uses: codecov/codecov-action@v4 with: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2ad9a7821b5c..bf055d0eaa16 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -268,11 +268,13 @@ stages: ;; esac displayName: 'Filter C coverage' + condition: succeededOrFailed() - bash: | bash <(curl -s https://codecov.io/bash) \ -n "$PYTHON_VERSION $AGENT_OS" \ -f 'coverage.xml' -f 'extensions.xml' displayName: 'Upload to codecov.io' + condition: succeededOrFailed() - task: PublishTestResults@2 inputs: From 3b65546e92166b9ed5b4e026f6bec08234a38eb5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 9 May 2024 16:45:58 -0400 Subject: [PATCH 0975/1120] Backport PR #28195: TST: Prepare for pytest 9 --- lib/matplotlib/tests/test_backend_bases.py | 6 +++--- lib/matplotlib/tests/test_backend_gtk3.py | 3 --- lib/matplotlib/tests/test_backend_qt.py | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index c264f01acdb2..3a49f0ec08ec 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -1,3 +1,5 @@ +import importlib + from matplotlib import path, transforms from matplotlib.backend_bases import ( FigureCanvasBase, KeyEvent, LocationEvent, MouseButton, MouseEvent, @@ -325,9 +327,7 @@ def test_toolbar_home_restores_autoscale(): def test_draw(backend): from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvas - test_backend = pytest.importorskip( - f'matplotlib.backends.backend_{backend}' - ) + test_backend = importlib.import_module(f'matplotlib.backends.backend_{backend}') TestCanvas = test_backend.FigureCanvas fig_test = Figure(constrained_layout=True) TestCanvas(fig_test) diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py index 6a95f47e1ddd..d7fa4329cfc8 100644 --- a/lib/matplotlib/tests/test_backend_gtk3.py +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -3,9 +3,6 @@ import pytest -pytest.importorskip("matplotlib.backends.backend_gtk3agg") - - @pytest.mark.backend("gtk3agg", skip_on_importerror=True) def test_correct_key(): pytest.xfail("test_widget_send_event is not triggering key_press_event") diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 026a49b1441e..a105b88c7449 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -26,9 +26,7 @@ @pytest.fixture def qt_core(request): - qt_compat = pytest.importorskip('matplotlib.backends.qt_compat') - QtCore = qt_compat.QtCore - + from matplotlib.backends.qt_compat import QtCore return QtCore From d8f301644fa839c0f0ae2a438d42dbc55b4dc223 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 10 May 2024 14:04:38 -0400 Subject: [PATCH 0976/1120] Backport PR #28205: TST: Fix tests with older versions of ipython --- .github/workflows/tests.yml | 2 +- lib/matplotlib/testing/__init__.py | 18 +++++++----------- lib/matplotlib/testing/__init__.pyi | 3 +-- lib/matplotlib/tests/test_backend_macosx.py | 2 +- lib/matplotlib/tests/test_backend_qt.py | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a24f8cdc2f5b..126693beafa7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,7 +60,7 @@ jobs: - os: ubuntu-20.04 python-version: 3.9 # One CI run tests ipython/matplotlib-inline before backend mapping moved to mpl - extra-requirements: '-r requirements/testing/extra.txt "ipython<8.24" "matplotlib-inline<0.1.7"' + extra-requirements: '-r requirements/testing/extra.txt "ipython==7.19" "matplotlib-inline<0.1.7"' CFLAGS: "-fno-lto" # Ensure that disabling LTO works. # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 779149dec2dc..c3af36230d4b 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -179,11 +179,7 @@ def _has_tex_package(package): return False -def ipython_in_subprocess( - requested_backend_or_gui_framework, - expected_backend_old_ipython, # IPython < 8.24 - expected_backend_new_ipython, # IPython >= 8.24 -): +def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backends): import pytest IPython = pytest.importorskip("IPython") @@ -194,12 +190,12 @@ def ipython_in_subprocess( requested_backend_or_gui_framework == "osx"): pytest.skip("Bug using macosx backend in IPython 8.24.0 fixed in 8.24.1") - if IPython.version_info[:2] >= (8, 24): - expected_backend = expected_backend_new_ipython - else: - # This code can be removed when Python 3.12, the latest version supported by - # IPython < 8.24, reaches end-of-life in late 2028. - expected_backend = expected_backend_old_ipython + # This code can be removed when Python 3.12, the latest version supported + # by IPython < 8.24, reaches end-of-life in late 2028. + for min_version, backend in all_expected_backends.items(): + if IPython.version_info[:2] >= min_version: + expected_backend = backend + break code = ("import matplotlib as mpl, matplotlib.pyplot as plt;" "fig, ax=plt.subplots(); ax.plot([1, 3, 2]); mpl.get_backend()") diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi index b0399476b6aa..1f52a8ccb8ee 100644 --- a/lib/matplotlib/testing/__init__.pyi +++ b/lib/matplotlib/testing/__init__.pyi @@ -49,6 +49,5 @@ def _check_for_pgf(texsystem: str) -> bool: ... def _has_tex_package(package: str) -> bool: ... def ipython_in_subprocess( requested_backend_or_gui_framework: str, - expected_backend_old_ipython: str, - expected_backend_new_ipython: str, + all_expected_backends: dict[tuple[int, int], str], ) -> None: ... diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index a4350fe3b6c6..3041bda9f423 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -48,4 +48,4 @@ def new_choose_save_file(title, directory, filename): def test_ipython(): from matplotlib.testing import ipython_in_subprocess - ipython_in_subprocess("osx", "MacOSX", "macosx") + ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index a105b88c7449..12d3238e0af4 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -376,4 +376,4 @@ def custom_handler(signum, frame): def test_ipython(): from matplotlib.testing import ipython_in_subprocess - ipython_in_subprocess("qt", "QtAgg", "qtagg") + ipython_in_subprocess("qt", {(8, 24): "qtagg", (8, 15): "QtAgg", (7, 0): "Qt5Agg"}) From 196c8db2074c9a3d91a9e144c21f7ef204905988 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 10 May 2024 14:18:56 -0400 Subject: [PATCH 0977/1120] TST: Followup corrections to #28205 Make the changes suggested by @ianthomas23, and also mark the ipython tests as using their backend. While the backend is already checked for availability at the top of the respective files, that only checks whether it can be imported, not whether it can be set as the Matplotlib backend. Adding the marker causes our pytest configuration to actually check and skip the test if unavailable (e.g., on Linux without `(WAYLAND_)DISPLAY` set fails to set an interactive backend). --- lib/matplotlib/testing/__init__.py | 2 +- lib/matplotlib/tests/test_backend_inline.py | 2 ++ lib/matplotlib/tests/test_backend_macosx.py | 1 + lib/matplotlib/tests/test_backend_qt.py | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index c3af36230d4b..8e60267ed608 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -210,4 +210,4 @@ def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backe capture_output=True, ) - assert proc.stdout.strip() == f"Out[1]: '{expected_backend}'" + assert proc.stdout.strip().endswith(f"'{expected_backend}'") diff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py index 6f0d67d51756..4112eb213e2c 100644 --- a/lib/matplotlib/tests/test_backend_inline.py +++ b/lib/matplotlib/tests/test_backend_inline.py @@ -1,6 +1,7 @@ import os from pathlib import Path from tempfile import TemporaryDirectory +import sys import pytest @@ -12,6 +13,7 @@ pytest.importorskip('matplotlib_inline') +@pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason="Requires Python 3.10+") def test_ipynb(): nb_path = Path(__file__).parent / 'test_inline_01.ipynb' diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index 3041bda9f423..7431481de8ae 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -46,6 +46,7 @@ def new_choose_save_file(title, directory, filename): assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test" +@pytest.mark.backend('macosx') def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 12d3238e0af4..5eb1ea77554d 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -374,6 +374,7 @@ def custom_handler(signum, frame): signal.signal(signal.SIGINT, original_handler) +@pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("qt", {(8, 24): "qtagg", (8, 15): "QtAgg", (7, 0): "Qt5Agg"}) From 846ce8a4889b7cc4e755de5af0d041a742be7282 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 15:06:02 -0400 Subject: [PATCH 0978/1120] DOC: Finish documentation for 3.9.0 --- SECURITY.md | 3 +- doc/_static/switcher.json | 6 +- doc/users/github_stats.rst | 85 ++++++++++++++++++-- doc/users/prev_whats_new/whats_new_3.9.0.rst | 2 +- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 88d523bec637..ce022ca60a0f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,8 +8,9 @@ versions. | Version | Supported | | ------- | ------------------ | +| 3.9.x | :white_check_mark: | | 3.8.x | :white_check_mark: | -| 3.7.x | :white_check_mark: | +| 3.7.x | :x: | | 3.6.x | :x: | | 3.5.x | :x: | | 3.4.x | :x: | diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index b49e478f28e4..6996d79bc22a 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -1,8 +1,8 @@ [ { - "name": "3.9 (rc)", - "version": "rc", - "url": "https://matplotlib.org/3.9.0/" + "name": "3.9 (stable)", + "version": "stable", + "url": "https://matplotlib.org/stable/" }, { "name": "3.10 (dev)", diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 794798a3b59d..a7f909b30cf5 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,16 +1,16 @@ .. _github-stats: -GitHub statistics for 3.9.0 (Apr 09, 2024) +GitHub statistics for 3.9.0 (May 15, 2024) ========================================== -GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/04/09 +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/05/15 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 86 issues and merged 393 pull requests. +We closed 97 issues and merged 450 pull requests. The full list can be seen `on GitHub `__ -The following 170 authors contributed 2496 commits. +The following 175 authors contributed 2584 commits. * 0taj * Abdul Razak Taha @@ -67,6 +67,7 @@ The following 170 authors contributed 2496 commits. * Eva Sibinga * Evgenii Radchenko * Faisal Fawad +* Felipe Cybis Pereira * Garrett Sward * Gaurav-Kumar-Soni * Gauri Chaudhari @@ -81,6 +82,7 @@ The following 170 authors contributed 2496 commits. * Ian Hunt-Isaak * Ian Thomas * ifEricReturnTrue +* Illviljan * Issam * Issam Arabi * Jacob Stevens-Haas @@ -124,6 +126,7 @@ The following 170 authors contributed 2496 commits. * Mostafa Noah * MostafaNouh0011 * n-aswin +* Nabil * nbarlowATI * Nidaa Rabah * Nivedita Chaudhari @@ -132,6 +135,7 @@ The following 170 authors contributed 2496 commits. * Pavel Liavonau * Pedro * Pedro Peçanha +* Peter Talley * Pradeep Reddy Raamana * Prajwal Agrawal * Pranav Raghu @@ -166,6 +170,7 @@ The following 170 authors contributed 2496 commits. * Talha Irfan * thehappycheese * Thomas A Caswell +* Tiago Lubiana * Tim Hoffmann * tobias * Tom Sarantis @@ -185,8 +190,65 @@ The following 170 authors contributed 2496 commits. GitHub issues and pull requests: -Pull Requests (393): +Pull Requests (450): +* :ghpull:`28206`: Backport PR #28205 on branch v3.9.x (TST: Fix tests with older versions of ipython) +* :ghpull:`28207`: TST: Followup corrections to #28205 +* :ghpull:`28205`: TST: Fix tests with older versions of ipython +* :ghpull:`28203`: Backport PR #28164 on branch v3.9.x (CI: Ensure code coverage is always uploaded) +* :ghpull:`28204`: Backport PR #28195 on branch v3.9.x (TST: Prepare for pytest 9) +* :ghpull:`28191`: DOC: Use released mpl-sphinx-theme on v3.9.x +* :ghpull:`28195`: TST: Prepare for pytest 9 +* :ghpull:`28193`: Backport PR #28185 on branch v3.9.x (DOC: Bump mpl-sphinx-theme to 3.9) +* :ghpull:`28190`: Backport PR #28103 on branch v3.9.x ([DOC]: Fix compatibility with sphinx-gallery 0.16) +* :ghpull:`28164`: CI: Ensure code coverage is always uploaded +* :ghpull:`28194`: Backport PR #28188 on branch v3.9.x ([TST] Bump some tolerances for Macos ARM) +* :ghpull:`28188`: [TST] Bump some tolerances for Macos ARM +* :ghpull:`28185`: DOC: Bump mpl-sphinx-theme to 3.9 +* :ghpull:`28189`: Backport PR #28181 on branch v3.9.x (DOC: Prepare release notes for 3.9) +* :ghpull:`28103`: [DOC]: Fix compatibility with sphinx-gallery 0.16 +* :ghpull:`28181`: DOC: Prepare release notes for 3.9 +* :ghpull:`28184`: Backport PR #28182 on branch v3.9.x (Bump custom hatch deprecation expiration) +* :ghpull:`28182`: Bump custom hatch deprecation expiration +* :ghpull:`28178`: Backport PR #28171 on branch v3.9.x (Support removing absent tools from ToolContainerBase.) +* :ghpull:`28171`: Support removing absent tools from ToolContainerBase. +* :ghpull:`28174`: Backport PR #28169 on branch v3.9.x (Clarify public-ness of some ToolContainerBase APIs.) +* :ghpull:`28169`: Clarify public-ness of some ToolContainerBase APIs. +* :ghpull:`28160`: Backport PR #28039 on branch v3.9.x (Respect vertical_axis when rotating plot interactively) +* :ghpull:`28159`: Backport PR #28157 on branch v3.9.x (Remove call to non-existent method _default_contains in Artist) +* :ghpull:`28162`: Backport PR #27948 on branch v3.9.x (Move IPython backend mapping to Matplotlib and support entry points) +* :ghpull:`28163`: Backport PR #28144 on branch v3.9.x (DOC: Refactor code in the fishbone diagram example) +* :ghpull:`28144`: DOC: Refactor code in the fishbone diagram example +* :ghpull:`27948`: Move IPython backend mapping to Matplotlib and support entry points +* :ghpull:`28039`: Respect vertical_axis when rotating plot interactively +* :ghpull:`28157`: Remove call to non-existent method _default_contains in Artist +* :ghpull:`28141`: Backport PR #27960 on branch v3.9.x (Update AppVeyor config) +* :ghpull:`28138`: Backport PR #28068 on branch v3.9.x ([TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list``) +* :ghpull:`28140`: Backport PR #28136 on branch v3.9.x (Appease pycodestyle.) +* :ghpull:`27960`: Update AppVeyor config +* :ghpull:`28068`: [TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list`` +* :ghpull:`28136`: Appease pycodestyle. +* :ghpull:`28135`: Backport PR #28134 on branch v3.9.x (DOC: Minor improvements on quickstart) +* :ghpull:`28134`: DOC: Minor improvements on quickstart +* :ghpull:`28121`: Backport PR #28085 on branch v3.9.x (Clarify that the pgf backend is never actually used interactively.) +* :ghpull:`28120`: Backport PR #28102 on branch v3.9.x (Fix typo in color mapping documentation in quick_start.py) +* :ghpull:`28109`: Backport PR #28100 on branch v3.9.x (TST: wxcairo sometimes raises OSError on missing cairo libraries) +* :ghpull:`28100`: TST: wxcairo sometimes raises OSError on missing cairo libraries +* :ghpull:`28108`: Backport PR #28107 on branch v3.9.x ([DOC] Fix description in CapStyle example) +* :ghpull:`28107`: [DOC] Fix description in CapStyle example +* :ghpull:`28102`: Fix typo in color mapping documentation in quick_start.py +* :ghpull:`28095`: Backport PR #28094 on branch v3.9.x (DOC: exclude sphinx 7.3.*) +* :ghpull:`28081`: Backport PR #28078 on branch v3.9.x (Clarify that findfont & _find_fonts_by_props return paths.) +* :ghpull:`28080`: Backport PR #28077 on branch v3.9.x (Parent tk StringVar to the canvas widget, not to the toolbar.) +* :ghpull:`28092`: Backport PR #28032 on branch v3.9.x (FIX: ensure images are C order before passing to pillow) +* :ghpull:`28032`: FIX: ensure images are C order before passing to pillow +* :ghpull:`28088`: Backport PR #28087 on branch v3.9.x (Document Qt5 minimal version.) +* :ghpull:`28085`: Clarify that the pgf backend is never actually used interactively. +* :ghpull:`28078`: Clarify that findfont & _find_fonts_by_props return paths. +* :ghpull:`28077`: Parent tk StringVar to the canvas widget, not to the toolbar. +* :ghpull:`28062`: Backport PR #28056 on branch v3.9.x (Strip trailing spaces from log-formatter cursor output.) +* :ghpull:`28063`: Backport PR #28055 on branch v3.9.x (DOC: Improve inverted axis example) +* :ghpull:`28056`: Strip trailing spaces from log-formatter cursor output. * :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) * :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time * :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) @@ -581,8 +643,19 @@ Pull Requests (393): * :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions * :ghpull:`23787`: Use pybind11 for C/C++ extensions -Issues (86): +Issues (97): +* :ghissue:`28202`: [Bug]: Qt test_ipython fails on older ipython +* :ghissue:`28145`: [TST] Upcoming dependency test failures +* :ghissue:`28034`: [TST] Upcoming dependency test failures +* :ghissue:`28168`: [TST] Upcoming dependency test failures +* :ghissue:`28040`: [Bug]: vertical_axis not respected when rotating plots interactively +* :ghissue:`28146`: [Bug]: Useless recursive group in SVG output when using path_effects +* :ghissue:`28067`: [Bug]: ``LinearSegmentedColormap.from_list`` does not have all type hints for argument ``colors`` +* :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy +* :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower +* :ghissue:`7720`: WXAgg backend not rendering nicely on retina +* :ghissue:`28069`: [Bug]: Cant save with custom toolbar * :ghissue:`28005`: [Doc]: Improve contribute instructions * :ghissue:`22376`: [ENH]: align_titles * :ghissue:`5506`: Confusing status bar values in presence of multiple axes diff --git a/doc/users/prev_whats_new/whats_new_3.9.0.rst b/doc/users/prev_whats_new/whats_new_3.9.0.rst index c111455f8ef8..e0190cca3f27 100644 --- a/doc/users/prev_whats_new/whats_new_3.9.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.9.0.rst @@ -1,5 +1,5 @@ ============================================= -What's new in Matplotlib 3.9.0 (Apr 09, 2024) +What's new in Matplotlib 3.9.0 (May 15, 2024) ============================================= For a list of all of the issues and pull requests since the last revision, see the From be56634d682bed257cb941369d8d3600635ddadf Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 18:43:56 -0400 Subject: [PATCH 0979/1120] REL: v3.9.0 Highlights of this release include: - Plotting and Annotation improvements - Axes.inset_axes is no longer experimental - Legend support for Boxplot - Percent sign in pie labels auto-escaped with usetex=True - hatch parameter for stackplot - Add option to plot only one half of violin plot - axhline and axhspan on polar axes - Subplot titles can now be automatically aligned - axisartist can now be used together with standard Formatters - Toggle minorticks on Axis - StrMethodFormatter now respects axes.unicode_minus - Figure, Axes, and Legend Layout - Subfigures now have controllable zorders - Getters for xmargin, ymargin and zmargin - Mathtext improvements - mathtext documentation improvements - mathtext spacing corrections - Widget Improvements - Check and Radio Button widgets support clearing - 3D plotting improvements - Setting 3D axis limits now set the limits exactly - Other improvements - New BackendRegistry for plotting backends - Add widths, heights and angles setter to EllipseCollection - image.interpolation_stage rcParam - Arrow patch position is now modifiable - NonUniformImage now has mouseover support From fe276395739773e2fa558bf98533f310a77fdb97 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 18:48:18 -0400 Subject: [PATCH 0980/1120] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 43c95480c6e9ff8787b5ead9fc9daec745b719a5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 19:54:13 -0400 Subject: [PATCH 0981/1120] DOC: Add Zenodo DOI for 3.9.0 --- doc/_static/zenodo_cache/11201097.svg | 35 +++++++++++++++++++++++++++ doc/project/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/11201097.svg diff --git a/doc/_static/zenodo_cache/11201097.svg b/doc/_static/zenodo_cache/11201097.svg new file mode 100644 index 000000000000..70f35a7a659f --- /dev/null +++ b/doc/_static/zenodo_cache/11201097.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.11201097 + + + 10.5281/zenodo.11201097 + + + \ No newline at end of file diff --git a/doc/project/citing.rst b/doc/project/citing.rst index 008ee794a41b..fd013231b6c5 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -32,6 +32,9 @@ By version .. START OF AUTOGENERATED +v3.9.0 + .. image:: ../_static/zenodo_cache/11201097.svg + :target: https://doi.org/10.5281/zenodo.11201097 v3.8.4 .. image:: ../_static/zenodo_cache/10916799.svg :target: https://doi.org/10.5281/zenodo.10916799 diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index fbdbceaf0fbb..600e87efc498 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,7 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.9.0": "11201097", "v3.8.4": "10916799", "v3.8.3": "10661079", "v3.8.2": "10150955", From ac17d9fa20166cd1363b9486e868d899a5b3a88e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 16 May 2024 20:16:05 +0200 Subject: [PATCH 0982/1120] Backport PR #28231: DOC: we do not need the blit call in on_draw --- galleries/examples/event_handling/path_editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/galleries/examples/event_handling/path_editor.py b/galleries/examples/event_handling/path_editor.py index d6e84b454008..2af54bad53ed 100644 --- a/galleries/examples/event_handling/path_editor.py +++ b/galleries/examples/event_handling/path_editor.py @@ -94,7 +94,6 @@ def on_draw(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.pathpatch) self.ax.draw_artist(self.line) - self.canvas.blit(self.ax.bbox) def on_button_press(self, event): """Callback for mouse button presses.""" From 6558e560947bfcfcd2c1bebd0bec788aecbddc0c Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 16 May 2024 13:26:22 -0500 Subject: [PATCH 0983/1120] Backport PR #28233: CI: Fix font install on macOS/Homebrew --- .github/workflows/tests.yml | 7 +++---- azure-pipelines.yml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 126693beafa7..daa07e62b2e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -160,10 +160,9 @@ jobs: fi ;; macOS) - brew install ccache - brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk ghostscript gobject-introspection gtk4 ninja - brew install --cask inkscape + brew update + brew install ccache ghostscript gobject-introspection gtk4 ninja + brew install --cask font-noto-sans-cjk inkscape ;; esac diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf055d0eaa16..4c50c543846a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -119,10 +119,10 @@ stages: texlive-xetex ;; Darwin) + brew update brew install --cask xquartz brew install ccache ffmpeg imagemagick mplayer ninja pkg-config - brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk-sc + brew install --cask font-noto-sans-cjk-sc ;; Windows_NT) choco install ninja From 492f4bd95c8d68e60e78523eee2c8c17f2a34bac Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 16 May 2024 16:02:28 -0400 Subject: [PATCH 0984/1120] DOC: Fix a typo in GitHub stats --- doc/users/github_stats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index a7f909b30cf5..629d9319fc57 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -655,7 +655,7 @@ Issues (97): * :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy * :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower * :ghissue:`7720`: WXAgg backend not rendering nicely on retina -* :ghissue:`28069`: [Bug]: Cant save with custom toolbar +* :ghissue:`28069`: [Bug]: Can't save with custom toolbar * :ghissue:`28005`: [Doc]: Improve contribute instructions * :ghissue:`22376`: [ENH]: align_titles * :ghissue:`5506`: Confusing status bar values in presence of multiple axes From c35a0e576c172a3a89bfafd63c372e2b6afaa5a5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 16 May 2024 21:49:04 -0400 Subject: [PATCH 0985/1120] Backport PR #28219: Bump the actions group with 2 updates --- .github/workflows/cibuildwheel.yml | 10 +++++----- .github/workflows/conflictcheck.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 41d28d864dbe..9c327aba22c2 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -140,7 +140,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -149,7 +149,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -158,7 +158,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -176,7 +176,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for PyPy - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index 3eb384fa6585..fc759f52a6b0 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 + uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" From 9f70709254bb3229662c6e568d5fe0f3c016fd0a Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 17 May 2024 10:19:42 +0200 Subject: [PATCH 0986/1120] Backport PR #28243: DOC: Add more 3D plot types --- galleries/plot_types/3D/bar3d_simple.py | 29 ++++++++++++++++++++ galleries/plot_types/3D/plot3d_simple.py | 27 ++++++++++++++++++ galleries/plot_types/3D/quiver3d_simple.py | 32 ++++++++++++++++++++++ galleries/plot_types/3D/stem3d.py | 27 ++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 galleries/plot_types/3D/bar3d_simple.py create mode 100644 galleries/plot_types/3D/plot3d_simple.py create mode 100644 galleries/plot_types/3D/quiver3d_simple.py create mode 100644 galleries/plot_types/3D/stem3d.py diff --git a/galleries/plot_types/3D/bar3d_simple.py b/galleries/plot_types/3D/bar3d_simple.py new file mode 100644 index 000000000000..aa75560de8f2 --- /dev/null +++ b/galleries/plot_types/3D/bar3d_simple.py @@ -0,0 +1,29 @@ +""" +========================== +bar3d(x, y, z, dx, dy, dz) +========================== + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.bar3d`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +x = [1, 1, 2, 2] +y = [1, 2, 1, 2] +z = [0, 0, 0, 0] +dx = np.ones_like(x)*0.5 +dy = np.ones_like(x)*0.5 +dz = [2, 3, 1, 4] + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.bar3d(x, y, z, dx, dy, dz) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/plot3d_simple.py b/galleries/plot_types/3D/plot3d_simple.py new file mode 100644 index 000000000000..108dbecfbd87 --- /dev/null +++ b/galleries/plot_types/3D/plot3d_simple.py @@ -0,0 +1,27 @@ +""" +================ +plot(xs, ys, zs) +================ + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 100 +xs = np.linspace(0, 1, n) +ys = np.sin(xs * 6 * np.pi) +zs = np.cos(xs * 6 * np.pi) + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.plot(xs, ys, zs) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/quiver3d_simple.py b/galleries/plot_types/3D/quiver3d_simple.py new file mode 100644 index 000000000000..6f4aaa9cad90 --- /dev/null +++ b/galleries/plot_types/3D/quiver3d_simple.py @@ -0,0 +1,32 @@ +""" +======================== +quiver(X, Y, Z, U, V, W) +======================== + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.quiver`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 4 +x = np.linspace(-1, 1, n) +y = np.linspace(-1, 1, n) +z = np.linspace(-1, 1, n) +X, Y, Z = np.meshgrid(x, y, z) +U = (X + Y)/5 +V = (Y - X)/5 +W = Z*0 + + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.quiver(X, Y, Z, U, V, W) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/stem3d.py b/galleries/plot_types/3D/stem3d.py new file mode 100644 index 000000000000..50aa80146bdc --- /dev/null +++ b/galleries/plot_types/3D/stem3d.py @@ -0,0 +1,27 @@ +""" +============= +stem(x, y, z) +============= + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.stem`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 20 +x = np.sin(np.linspace(0, 2*np.pi, n)) +y = np.cos(np.linspace(0, 2*np.pi, n)) +z = np.linspace(0, 1, n) + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.stem(x, y, z) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() From 63351f2f8d9c7566634b87dd94cd4ae730c701c0 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 17 May 2024 11:41:07 +0200 Subject: [PATCH 0987/1120] Backport PR #28230: Add extra imports to improve typing --- lib/matplotlib/pyplot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index b1354341617d..925322cdd1e5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -90,6 +90,9 @@ import PIL.Image from numpy.typing import ArrayLike + import matplotlib.axes + import matplotlib.artist + import matplotlib.backend_bases from matplotlib.axis import Tick from matplotlib.axes._base import _AxesBase from matplotlib.backend_bases import RendererBase, Event From fefe7d298f71bda84e2bcc707dc7c6004de14ad9 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 17 May 2024 15:16:37 -0400 Subject: [PATCH 0988/1120] Backport PR #28252: DOC: Flip the imshow plot types example to match the other examples --- galleries/plot_types/arrays/imshow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/plot_types/arrays/imshow.py b/galleries/plot_types/arrays/imshow.py index c28278c6c657..b2920e7fd80c 100644 --- a/galleries/plot_types/arrays/imshow.py +++ b/galleries/plot_types/arrays/imshow.py @@ -19,6 +19,6 @@ # plot fig, ax = plt.subplots() -ax.imshow(Z) +ax.imshow(Z, origin='lower') plt.show() From 82d14c8183f3c4831ee94a27f33aacf676871a53 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 17 May 2024 15:50:09 -0400 Subject: [PATCH 0989/1120] Backport PR #28254: [DOC] plot type heading consistency --- galleries/plot_types/arrays/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/plot_types/arrays/README.rst b/galleries/plot_types/arrays/README.rst index d9dbfd10ead7..aba457a69940 100644 --- a/galleries/plot_types/arrays/README.rst +++ b/galleries/plot_types/arrays/README.rst @@ -1,7 +1,7 @@ .. _array_plots: -Gridded data: -------------- +Gridded data +------------ Plots of arrays and images :math:`Z_{i, j}` and fields :math:`U_{i, j}, V_{i, j}` on `regular grids `_ and From f3b34c1137a12280c1619183f9c5cdc5b087d7f0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 20 May 2024 16:19:11 -0500 Subject: [PATCH 0990/1120] Backport PR #28257: Clean up some Meson-related leftovers --- .../api_changes_3.5.0/development.rst | 6 +++--- doc/install/environment_variables_faq.rst | 7 ------- doc/install/index.rst | 18 ++++++++---------- pyproject.toml | 1 - src/checkdep_freetype2.c | 4 ++-- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/development.rst b/doc/api/prev_api_changes/api_changes_3.5.0/development.rst index 2db21237a699..b42e6eff3423 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0/development.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0/development.rst @@ -77,6 +77,6 @@ In order to avoid conflicting with the use of :file:`setup.cfg` by ``setup.cfg`` to ``mplsetup.cfg``. The :file:`setup.cfg.template` has been correspondingly been renamed to :file:`mplsetup.cfg.template`. -Note that the path to this configuration file can still be set via the -:envvar:`MPLSETUPCFG` environment variable, which allows one to keep using the -same file before and after this change. +Note that the path to this configuration file can still be set via the ``MPLSETUPCFG`` +environment variable, which allows one to keep using the same file before and after this +change. diff --git a/doc/install/environment_variables_faq.rst b/doc/install/environment_variables_faq.rst index ba384343cc5a..38e0d0ef0c63 100644 --- a/doc/install/environment_variables_faq.rst +++ b/doc/install/environment_variables_faq.rst @@ -29,13 +29,6 @@ Environment variables used to find a base directory in which the :file:`matplotlib` subdirectory is created. -.. envvar:: MPLSETUPCFG - - This optional variable can be set to the full path of a :file:`mplsetup.cfg` - configuration file used to customize the Matplotlib build. By default, a - :file:`mplsetup.cfg` file in the root of the Matplotlib source tree will be - read. Supported build options are listed in :file:`mplsetup.cfg.template`. - .. envvar:: PATH The list of directories searched to find executable programs. diff --git a/doc/install/index.rst b/doc/install/index.rst index ea8e29d71565..867e4600a77e 100644 --- a/doc/install/index.rst +++ b/doc/install/index.rst @@ -121,22 +121,20 @@ Before trying to install Matplotlib, please install the :ref:`dependencies`. To build from a tarball, download the latest *tar.gz* release file from `the PyPI files page `_. -We provide a `mplsetup.cfg`_ file which you can use to customize the build -process. For example, which default backend to use, whether some of the -optional libraries that Matplotlib ships with are installed, and so on. This -file will be particularly useful to those packaging Matplotlib. - -.. _mplsetup.cfg: https://raw.githubusercontent.com/matplotlib/matplotlib/main/mplsetup.cfg.template - If you are building your own Matplotlib wheels (or sdists) on Windows, note that any DLLs that you copy into the source tree will be packaged too. - Configure build and behavior defaults ===================================== -Aspects of the build and install process and some behaviorial defaults of the -library can be configured via: +We provide a `meson.options`_ file containing options with which you can use to +customize the build process. For example, which default backend to use, whether some of +the optional libraries that Matplotlib ships with are installed, and so on. These +options will be particularly useful to those packaging Matplotlib. + +.. _meson.options: https://github.com/matplotlib/matplotlib/blob/main/meson.options + +Aspects of some behaviorial defaults of the library can be configured via: .. toctree:: :maxdepth: 2 diff --git a/pyproject.toml b/pyproject.toml index fe75b325dc89..a9fb7df68450 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,6 @@ install = ['--tags=data,python-runtime,runtime'] [tool.setuptools_scm] version_scheme = "release-branch-semver" local_scheme = "node-and-date" -write_to = "lib/matplotlib/_version.py" parentdir_prefix_version = "matplotlib-" fallback_version = "0.0+UNKNOWN" diff --git a/src/checkdep_freetype2.c b/src/checkdep_freetype2.c index 8d9d8ca24a07..16e8ac23919e 100644 --- a/src/checkdep_freetype2.c +++ b/src/checkdep_freetype2.c @@ -1,7 +1,7 @@ #ifdef __has_include #if !__has_include() #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif #endif @@ -15,5 +15,5 @@ You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib downlo XSTR(FREETYPE_MAJOR) "." XSTR(FREETYPE_MINOR) "." XSTR(FREETYPE_PATCH) ".") #if FREETYPE_MAJOR << 16 + FREETYPE_MINOR << 8 + FREETYPE_PATCH < 0x020300 #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif From 8cb17f8b2ae55ab8374650d41cf93bfbf6a3e093 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 21 May 2024 14:15:04 -0400 Subject: [PATCH 0991/1120] Backport PR #28269: Handle GetForegroundWindow() returning NULL. --- lib/matplotlib/backends/_backend_tk.py | 2 +- src/_c_internal_utils.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 295f6c41372d..df06440a9826 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -44,7 +44,7 @@ def _restore_foreground_window_at_end(): try: yield finally: - if mpl.rcParams['tk.window_focus']: + if foreground and mpl.rcParams['tk.window_focus']: _c_internal_utils.Win32_SetForegroundWindow(foreground) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 464aabcb2e3a..e118183ecc8b 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -111,7 +111,11 @@ static py::object mpl_GetForegroundWindow(void) { #ifdef _WIN32 - return py::capsule(GetForegroundWindow(), "HWND"); + if (HWND hwnd = GetForegroundWindow()) { + return py::capsule(hwnd, "HWND"); + } else { + return py::none(); + } #else return py::none(); #endif From 425b4d3de90f49c6dc84a4396e9ca4c901df1f52 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 22 May 2024 07:37:36 +0200 Subject: [PATCH 0992/1120] Backport PR #28274: ci: Remove deprecated codeql option --- .github/workflows/codeql-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 29d2859999bd..203b0eee9ca4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,6 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - setup-python-dependencies: false - name: Build compiled code if: matrix.language == 'c-cpp' From 7bd803f06f07d0156903c5fafe7f5735b844ed4d Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 22 May 2024 07:59:18 +0200 Subject: [PATCH 0993/1120] Backport PR #28272: BLD: Move macos builders from 11 to 12 --- .github/workflows/cibuildwheel.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9c327aba22c2..2fa9569f3fb6 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -105,6 +105,7 @@ jobs: CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- python {package}/ci/check_version_number.py + MACOSX_DEPLOYMENT_TARGET: "10.12" MPL_DISABLE_FH4: "yes" strategy: matrix: @@ -115,16 +116,10 @@ jobs: cibw_archs: "aarch64" - os: windows-latest cibw_archs: "auto64" - - os: macos-11 + - os: macos-12 cibw_archs: "x86_64" - # NOTE: macos_target can be moved back into global environment after - # meson-python 0.16.0 is released. - macos_target: "10.12" - os: macos-14 cibw_archs: "arm64" - # NOTE: macos_target can be moved back into global environment after - # meson-python 0.16.0 is released. - macos_target: "11.0" steps: - name: Set up QEMU @@ -146,7 +141,6 @@ jobs: env: CIBW_BUILD: "cp312-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.11 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -155,7 +149,6 @@ jobs: env: CIBW_BUILD: "cp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.10 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -164,7 +157,6 @@ jobs: env: CIBW_BUILD: "cp310-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.9 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -173,7 +165,6 @@ jobs: env: CIBW_BUILD: "cp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for PyPy uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -182,7 +173,6 @@ jobs: env: CIBW_BUILD: "pp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" if: matrix.cibw_archs != 'aarch64' - uses: actions/upload-artifact@v4 From bb1dcc372e5b507e5601ead97eb18c6e206307d8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 23 May 2024 10:33:21 +0200 Subject: [PATCH 0994/1120] Backport PR #28280: DOC: Add an example for 2D images in 3D plots --- galleries/examples/mplot3d/imshow3d.py | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 galleries/examples/mplot3d/imshow3d.py diff --git a/galleries/examples/mplot3d/imshow3d.py b/galleries/examples/mplot3d/imshow3d.py new file mode 100644 index 000000000000..557d96e1bce5 --- /dev/null +++ b/galleries/examples/mplot3d/imshow3d.py @@ -0,0 +1,88 @@ +""" +=============== +2D images in 3D +=============== + +This example demonstrates how to plot 2D color coded images (similar to +`.Axes.imshow`) as a plane in 3D. + +Matplotlib does not have a native function for this. Below we build one by relying +on `.Axes3D.plot_surface`. For simplicity, there are some differences to +`.Axes.imshow`: This function does not set the aspect of the Axes, hence pixels are +not necessarily square. Also, pixel edges are on integer values rather than pixel +centers. Furthermore, many optional parameters of `.Axes.imshow` are not implemented. + +Multiple calls of ``imshow3d`` use independent norms and thus different color scales +by default. If you want to have a single common color scale, you need to construct +a suitable norm beforehand and pass it to all ``imshow3d`` calls. + +A fundamental limitation of the 3D plotting engine is that intersecting objects cannot +be drawn correctly. One object will always be drawn after the other. Therefore, +multiple image planes can well be used in the background as shown in this example. +But this approach is not suitable if the planes intersect. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from matplotlib.colors import Normalize + + +def imshow3d(ax, array, value_direction='z', pos=0, norm=None, cmap=None): + """ + Display a 2D array as a color-coded 2D image embedded in 3d. + + The image will be in a plane perpendicular to the coordinate axis *value_direction*. + + Parameters + ---------- + ax : Axes3D + The 3D Axes to plot into. + array : 2D numpy array + The image values. + value_direction : {'x', 'y', 'z'} + The axis normal to the image plane. + pos : float + The numeric value on the *value_direction* axis at which the image plane is + located. + norm : `~matplotlib.colors.Normalize`, default: Normalize + The normalization method used to scale scalar data. See `imshow()`. + cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` + The Colormap instance or registered colormap name used to map scalar data + to colors. + """ + if norm is None: + norm = Normalize() + colors = plt.get_cmap(cmap)(norm(array)) + + if value_direction == 'x': + nz, ny = array.shape + zi, yi = np.mgrid[0:nz + 1, 0:ny + 1] + xi = np.full_like(yi, pos) + elif value_direction == 'y': + nx, nz = array.shape + xi, zi = np.mgrid[0:nx + 1, 0:nz + 1] + yi = np.full_like(zi, pos) + elif value_direction == 'z': + ny, nx = array.shape + yi, xi = np.mgrid[0:ny + 1, 0:nx + 1] + zi = np.full_like(xi, pos) + else: + raise ValueError(f"Invalid value_direction: {value_direction!r}") + ax.plot_surface(xi, yi, zi, rstride=1, cstride=1, facecolors=colors, shade=False) + + +fig = plt.figure() +ax = fig.add_subplot(projection='3d') +ax.set(xlabel="x", ylabel="y", zlabel="z") + +nx, ny, nz = 8, 10, 5 +data_xy = np.arange(ny * nx).reshape(ny, nx) + 15 * np.random.random((ny, nx)) +data_yz = np.arange(nz * ny).reshape(nz, ny) + 10 * np.random.random((nz, ny)) +data_zx = np.arange(nx * nz).reshape(nx, nz) + 8 * np.random.random((nx, nz)) + +imshow3d(ax, data_xy) +imshow3d(ax, data_yz, value_direction='x', cmap='magma') +imshow3d(ax, data_zx, value_direction='y', pos=ny, cmap='plasma') + +plt.show() From 042e1bb7f9d5b4295448e7fcf3bbe3b9fbcd9e4f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Fri, 24 May 2024 23:49:19 +0200 Subject: [PATCH 0995/1120] Backport PR #28261: Correct roll angle units, issue #28256 --- lib/mpl_toolkits/mplot3d/axes3d.py | 1 + lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d0f5c8d2b23b..677c2668d4e9 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1524,6 +1524,7 @@ def _on_move(self, event): dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) elev = self.elev + delev azim = self.azim + dazim + roll = self.roll vertical_axis = self._axis_names[self._vertical_axis] self.view_init( elev=elev, diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ed56e5505d8e..c339e35e903c 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1766,6 +1766,31 @@ def test_shared_axes_retick(): assert ax2.get_zlim() == (-0.5, 2.5) +def test_rotate(): + """Test rotating using the left mouse button.""" + for roll in [0, 30]: + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.view_init(0, 0, roll) + ax.figure.canvas.draw() + + # drag mouse horizontally to change azimuth + dx = 0.1 + dy = 0.2 + ax._button_press( + mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) + ax._on_move( + mock_event(ax, button=MouseButton.LEFT, + xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h)) + ax.figure.canvas.draw() + roll_radians = np.deg2rad(ax.roll) + cs = np.cos(roll_radians) + sn = np.sin(roll_radians) + assert ax.elev == (-dy*180*cs + dx*180*sn) + assert ax.azim == (-dy*180*sn - dx*180*cs) + assert ax.roll == roll + + def test_pan(): """Test mouse panning using the middle mouse button.""" From a4a0a9571f2a257b9bc8224e6b266253991be02e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 25 May 2024 20:20:22 +0200 Subject: [PATCH 0996/1120] Backport PR #28297: Solved #28296 Added missing comma --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b65004b8c272..34c4023a256e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1586,7 +1586,7 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs): >>> plot(x1, y1, 'bo') >>> plot(x2, y2, 'go') - - If *x* and/or *y* are 2D arrays a separate data set will be drawn + - If *x* and/or *y* are 2D arrays, a separate data set will be drawn for every column. If both *x* and *y* are 2D, they must have the same shape. If only one of them is 2D with shape (N, m) the other must have length N and will be used for every data set m. From 3a55c47f4bf09a247cd420b2cf00f52f9a04629a Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 26 May 2024 18:41:05 +0100 Subject: [PATCH 0997/1120] Backport PR #28303: Removed drawedges repeated definition from function doc string --- lib/matplotlib/colorbar.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index af61e4671ff4..156ea2ff6497 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -257,10 +257,6 @@ class Colorbar: *location* is None, the ticks will be at the bottom for a horizontal colorbar and at the right for a vertical. - drawedges : bool - Whether to draw lines at color boundaries. - - %(_colormap_kw_doc)s location : None or {'left', 'right', 'top', 'bottom'} From 7462f5e932a7bb99c804c9b19de5fcca48b046bd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 29 May 2024 14:50:35 -0400 Subject: [PATCH 0998/1120] Backport PR #28273: CI: Add GitHub artifact attestations to package distribution --- .github/workflows/cibuildwheel.yml | 7 +++++++ .pre-commit-config.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 2fa9569f3fb6..04c70a767ce0 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -189,6 +189,8 @@ jobs: environment: release permissions: id-token: write + attestations: write + contents: read steps: - name: Download packages uses: actions/download-artifact@v4 @@ -200,5 +202,10 @@ jobs: - name: Print out packages run: ls dist + - name: Generate artifact attestation for sdist and wheel + uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + with: + subject-path: dist/matplotlib-* + - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2dc1ca5352c0..14817e95929f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,7 +79,7 @@ repos: - id: yamllint args: ["--strict", "--config-file=.yamllint.yml"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.1 + rev: 0.28.4 hooks: # TODO: Re-enable this when https://github.com/microsoft/azure-pipelines-vscode/issues/567 is fixed. # - id: check-azure-pipelines From ccc61cb2b818bbc2c9e9e2e83ebf11000760a7da Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 29 May 2024 20:08:27 -0500 Subject: [PATCH 0999/1120] Backport PR #27001: [TYP] Add overload of `pyplot.subplots` --- lib/matplotlib/figure.pyi | 34 +++++++++++++++++-------- lib/matplotlib/gridspec.pyi | 2 +- lib/matplotlib/pyplot.py | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index eae21c2614f0..21de9159d56c 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -1,12 +1,12 @@ from collections.abc import Callable, Hashable, Iterable import os -from typing import Any, IO, Literal, TypeVar, overload +from typing import Any, IO, Literal, Sequence, TypeVar, overload import numpy as np from numpy.typing import ArrayLike from matplotlib.artist import Artist -from matplotlib.axes import Axes, SubplotBase +from matplotlib.axes import Axes from matplotlib.backend_bases import ( FigureCanvasBase, MouseButton, @@ -92,6 +92,20 @@ class FigureBase(Artist): @overload def add_subplot(self, **kwargs) -> Axes: ... @overload + def subplots( + self, + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> Axes: ... + @overload def subplots( self, nrows: int = ..., @@ -100,11 +114,11 @@ class FigureBase(Artist): sharex: bool | Literal["none", "all", "row", "col"] = ..., sharey: bool | Literal["none", "all", "row", "col"] = ..., squeeze: Literal[False], - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ... - ) -> np.ndarray: ... + gridspec_kw: dict[str, Any] | None = ..., + ) -> np.ndarray: ... # TODO numpy/numpy#24738 @overload def subplots( self, @@ -114,11 +128,11 @@ class FigureBase(Artist): sharex: bool | Literal["none", "all", "row", "col"] = ..., sharey: bool | Literal["none", "all", "row", "col"] = ..., squeeze: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ... - ) -> np.ndarray | SubplotBase | Axes: ... + gridspec_kw: dict[str, Any] | None = ..., + ) -> Axes | np.ndarray: ... def delaxes(self, ax: Axes) -> None: ... def clear(self, keep_observers: bool = ...) -> None: ... def clf(self, keep_observers: bool = ...) -> None: ... diff --git a/lib/matplotlib/gridspec.pyi b/lib/matplotlib/gridspec.pyi index 1ac1bb0b40e7..b6732ad8fafa 100644 --- a/lib/matplotlib/gridspec.pyi +++ b/lib/matplotlib/gridspec.pyi @@ -54,7 +54,7 @@ class GridSpecBase: sharey: bool | Literal["all", "row", "col", "none"] = ..., squeeze: Literal[True] = ..., subplot_kw: dict[str, Any] | None = ... - ) -> np.ndarray | SubplotBase | Axes: ... + ) -> np.ndarray | Axes: ... class GridSpec(GridSpecBase): left: float | None diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 925322cdd1e5..a3ce60f01ef5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1548,6 +1548,57 @@ def subplot(*args, **kwargs) -> Axes: return ax +@overload +def subplots( + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Axes]: + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738 + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Axes | np.ndarray]: + ... + + def subplots( nrows: int = 1, ncols: int = 1, *, sharex: bool | Literal["none", "all", "row", "col"] = False, From ecdbc277d3577d1f57401a19151f1a39816472b5 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:55:43 +0200 Subject: [PATCH 1000/1120] Backport PR #28041: [BUG]: Shift box_aspect according to vertical_axis --- lib/mpl_toolkits/mplot3d/axes3d.py | 22 +++++++++++++++---- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 18 +++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 677c2668d4e9..18b823ccccb3 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -383,7 +383,7 @@ def set_box_aspect(self, aspect, *, zoom=1): # of the axes in mpl3.8. aspect *= 1.8294640721620434 * 25/24 * zoom / np.linalg.norm(aspect) - self._box_aspect = aspect + self._box_aspect = self._roll_to_vertical(aspect, reverse=True) self.stale = True def apply_aspect(self, position=None): @@ -1191,9 +1191,23 @@ def set_proj_type(self, proj_type, focal_length=None): f"None for proj_type = {proj_type}") self._focal_length = np.inf - def _roll_to_vertical(self, arr): - """Roll arrays to match the different vertical axis.""" - return np.roll(arr, self._vertical_axis - 2) + def _roll_to_vertical( + self, arr: "np.typing.ArrayLike", reverse: bool = False + ) -> np.ndarray: + """ + Roll arrays to match the different vertical axis. + + Parameters + ---------- + arr : ArrayLike + Array to roll. + reverse : bool, default: False + Reverse the direction of the roll. + """ + if reverse: + return np.roll(arr, (self._vertical_axis - 2) * -1) + else: + return np.roll(arr, (self._vertical_axis - 2)) def get_proj(self): """Create the projection matrix from the current viewing position.""" diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index c339e35e903c..34f10cb9fd63 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2301,6 +2301,24 @@ def test_on_move_vertical_axis(vertical_axis: str) -> None: ) +@pytest.mark.parametrize( + "vertical_axis, aspect_expected", + [ + ("x", [1.190476, 0.892857, 1.190476]), + ("y", [0.892857, 1.190476, 1.190476]), + ("z", [1.190476, 1.190476, 0.892857]), + ], +) +def test_set_box_aspect_vertical_axis(vertical_axis, aspect_expected): + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.figure.canvas.draw() + + ax.set_box_aspect(None) + + np.testing.assert_allclose(aspect_expected, ax._box_aspect, rtol=1e-6) + + @image_comparison(baseline_images=['arc_pathpatch.png'], remove_text=True, style='mpl20') From 623445641b3a9bbe92c01d8b454b812bdd9f6ccb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 2 Jun 2024 10:47:35 -0400 Subject: [PATCH 1001/1120] Backport PR #28292: Resolve MaxNLocator IndexError when no large steps --- lib/matplotlib/tests/test_ticker.py | 8 ++++++++ lib/matplotlib/ticker.py | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 36b83c95b3d3..ac68a5d90b14 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -130,6 +130,14 @@ def test_view_limits_round_numbers_with_offset(self): loc = mticker.MultipleLocator(base=3.147, offset=1.3) assert_almost_equal(loc.view_limits(-4, 4), (-4.994, 4.447)) + def test_view_limits_single_bin(self): + """ + Test that 'round_numbers' works properly with a single bin. + """ + with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}): + loc = mticker.MaxNLocator(nbins=1) + assert_almost_equal(loc.view_limits(-2.3, 2.3), (-4, 4)) + def test_set_params(self): """ Create multiple locator with 0.7 base, and change it to something else. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index f042372a7be9..2b00937f9e29 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2137,7 +2137,10 @@ def _raw_ticks(self, vmin, vmax): large_steps = large_steps & (floored_vmaxs >= _vmax) # Find index of smallest large step - istep = np.nonzero(large_steps)[0][0] + if any(large_steps): + istep = np.nonzero(large_steps)[0][0] + else: + istep = len(steps) - 1 # Start at smallest of the steps greater than the raw step, and check # if it provides enough ticks. If not, work backwards through From a251d42c1eedef63d5d14e731acc4c850e2aa08e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:03:21 +0200 Subject: [PATCH 1002/1120] Backport PR #28329: DOC: Add example for 3D intersecting planes --- .../examples/mplot3d/intersecting_planes.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 galleries/examples/mplot3d/intersecting_planes.py diff --git a/galleries/examples/mplot3d/intersecting_planes.py b/galleries/examples/mplot3d/intersecting_planes.py new file mode 100644 index 000000000000..b8aa08fd7e18 --- /dev/null +++ b/galleries/examples/mplot3d/intersecting_planes.py @@ -0,0 +1,89 @@ +""" +=================== +Intersecting planes +=================== + +This examples demonstrates drawing intersecting planes in 3D. It is a generalization +of :doc:`/gallery/mplot3d/imshow3d`. + +Drawing intersecting planes in `.mplot3d` is complicated, because `.mplot3d` is not a +real 3D renderer, but only projects the Artists into 3D and draws them in the right +order. This does not work correctly if Artists overlap each other mutually. In this +example, we lift the problem of mutual overlap by segmenting the planes at their +intersections, making four parts out of each plane. + +This examples only works correctly for planes that cut each other in haves. This +limitation is intentional to keep the code more readable. Cutting at arbitrary +positions would of course be possible but makes the code even more complex. +Thus, this example is more a demonstration of the concept how to work around +limitations of the 3D visualization, it's not a refined solution for drawing +arbitrary intersecting planes, which you can copy-and-paste as is. +""" +import matplotlib.pyplot as plt +import numpy as np + + +def plot_quadrants(ax, array, fixed_coord, cmap): + """For a given 3d *array* plot a plane with *fixed_coord*, using four quadrants.""" + nx, ny, nz = array.shape + index = { + 'x': (nx // 2, slice(None), slice(None)), + 'y': (slice(None), ny // 2, slice(None)), + 'z': (slice(None), slice(None), nz // 2), + }[fixed_coord] + plane_data = array[index] + + n0, n1 = plane_data.shape + quadrants = [ + plane_data[:n0 // 2, :n1 // 2], + plane_data[:n0 // 2, n1 // 2:], + plane_data[n0 // 2:, :n1 // 2], + plane_data[n0 // 2:, n1 // 2:] + ] + + min_val = array.min() + max_val = array.max() + + cmap = plt.get_cmap(cmap) + + for i, quadrant in enumerate(quadrants): + facecolors = cmap((quadrant - min_val) / (max_val - min_val)) + if fixed_coord == 'x': + Y, Z = np.mgrid[0:ny // 2, 0:nz // 2] + X = nx // 2 * np.ones_like(Y) + Y_offset = (i // 2) * ny // 2 + Z_offset = (i % 2) * nz // 2 + ax.plot_surface(X, Y + Y_offset, Z + Z_offset, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + elif fixed_coord == 'y': + X, Z = np.mgrid[0:nx // 2, 0:nz // 2] + Y = ny // 2 * np.ones_like(X) + X_offset = (i // 2) * nx // 2 + Z_offset = (i % 2) * nz // 2 + ax.plot_surface(X + X_offset, Y, Z + Z_offset, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + elif fixed_coord == 'z': + X, Y = np.mgrid[0:nx // 2, 0:ny // 2] + Z = nz // 2 * np.ones_like(X) + X_offset = (i // 2) * nx // 2 + Y_offset = (i % 2) * ny // 2 + ax.plot_surface(X + X_offset, Y + Y_offset, Z, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + + +def figure_3D_array_slices(array, cmap=None): + """Plot a 3d array using three intersecting centered planes.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_box_aspect(array.shape) + plot_quadrants(ax, array, 'x', cmap=cmap) + plot_quadrants(ax, array, 'y', cmap=cmap) + plot_quadrants(ax, array, 'z', cmap=cmap) + return fig, ax + + +nx, ny, nz = 70, 100, 50 +r_square = (np.mgrid[-1:1:1j * nx, -1:1:1j * ny, -1:1:1j * nz] ** 2).sum(0) + +figure_3D_array_slices(r_square, cmap='viridis_r') +plt.show() From 75ac1bdaab50c617a151b7a1d76450a4b05b596f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 3 Jun 2024 12:14:13 -0400 Subject: [PATCH 1003/1120] Backport PR #28332: Call IPython.enable_gui when install repl displayhook --- lib/matplotlib/pyplot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index a3ce60f01ef5..1a8298212709 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -304,10 +304,12 @@ def install_repl_displayhook() -> None: # This code can be removed when Python 3.12, the latest version supported by # IPython < 8.24, reaches end-of-life in late 2028. from IPython.core.pylabtools import backend2gui - # trigger IPython's eventloop integration, if available ipython_gui_name = backend2gui.get(get_backend()) - if ipython_gui_name: - ip.enable_gui(ipython_gui_name) + else: + _, ipython_gui_name = backend_registry.resolve_backend(get_backend()) + # trigger IPython's eventloop integration, if available + if ipython_gui_name: + ip.enable_gui(ipython_gui_name) def uninstall_repl_displayhook() -> None: From 0fae2283dfe2ba5653166c44fe8a03772254c85b Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 3 Jun 2024 19:51:19 -0400 Subject: [PATCH 1004/1120] Backport PR #28336: DOC: Add version warning banner for docs versions different from stable --- doc/_static/switcher.json | 3 ++- doc/conf.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index 6996d79bc22a..3d712e4ff8e9 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -2,7 +2,8 @@ { "name": "3.9 (stable)", "version": "stable", - "url": "https://matplotlib.org/stable/" + "url": "https://matplotlib.org/stable/", + "preferred": true }, { "name": "3.10 (dev)", diff --git a/doc/conf.py b/doc/conf.py index c9a475aecf9c..92d78f896ca2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -508,6 +508,7 @@ def js_tag_with_cache_busting(js): # this special value indicates the use of the unreleased banner. If we need # an actual announcement, then just place the text here as usual. "announcement": "unreleased" if not is_release_build else "", + "show_version_warning_banner": True, } include_analytics = is_release_build if include_analytics: From 7278380444dd50bfa41302ced547ea779b5d3418 Mon Sep 17 00:00:00 2001 From: Eytan Adler <63426601+eytanadler@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:34:21 -0400 Subject: [PATCH 1005/1120] Backport PR #28307: DOC: New color line by value example --- .../multicolored_line.py | 205 +++++++++++++++--- 1 file changed, 173 insertions(+), 32 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/multicolored_line.py b/galleries/examples/lines_bars_and_markers/multicolored_line.py index 5d0727e69181..3d14ecaf8567 100644 --- a/galleries/examples/lines_bars_and_markers/multicolored_line.py +++ b/galleries/examples/lines_bars_and_markers/multicolored_line.py @@ -3,47 +3,188 @@ Multicolored lines ================== -This example shows how to make a multicolored line. In this example, the line -is colored based on its derivative. +The example shows two ways to plot a line with the a varying color defined by +a third value. The first example defines the color at each (x, y) point. +The second example defines the color between pairs of points, so the length +of the color value list is one less than the length of the x and y lists. + +Color values at points +---------------------- + """ +import warnings + import matplotlib.pyplot as plt import numpy as np from matplotlib.collections import LineCollection -from matplotlib.colors import BoundaryNorm, ListedColormap + +def colored_line(x, y, c, ax, **lc_kwargs): + """ + Plot a line with a color specified along the line by a third value. + + It does this by creating a collection of line segments. Each line segment is + made up of two straight lines each connecting the current (x, y) point to the + midpoints of the lines connecting the current point with its two neighbors. + This creates a smooth line with no gaps between the line segments. + + Parameters + ---------- + x, y : array-like + The horizontal and vertical coordinates of the data points. + c : array-like + The color values, which should be the same size as x and y. + ax : Axes + Axis object on which to plot the colored line. + **lc_kwargs + Any additional arguments to pass to matplotlib.collections.LineCollection + constructor. This should not include the array keyword argument because + that is set to the color argument. If provided, it will be overridden. + + Returns + ------- + matplotlib.collections.LineCollection + The generated line collection representing the colored line. + """ + if "array" in lc_kwargs: + warnings.warn('The provided "array" keyword argument will be overridden') + + # Default the capstyle to butt so that the line segments smoothly line up + default_kwargs = {"capstyle": "butt"} + default_kwargs.update(lc_kwargs) + + # Compute the midpoints of the line segments. Include the first and last points + # twice so we don't need any special syntax later to handle them. + x = np.asarray(x) + y = np.asarray(y) + x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1])) + y_midpts = np.hstack((y[0], 0.5 * (y[1:] + y[:-1]), y[-1])) + + # Determine the start, middle, and end coordinate pair of each line segment. + # Use the reshape to add an extra dimension so each pair of points is in its + # own list. Then concatenate them to create: + # [ + # [(x1_start, y1_start), (x1_mid, y1_mid), (x1_end, y1_end)], + # [(x2_start, y2_start), (x2_mid, y2_mid), (x2_end, y2_end)], + # ... + # ] + coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1]))[:, np.newaxis, :] + coord_mid = np.column_stack((x, y))[:, np.newaxis, :] + coord_end = np.column_stack((x_midpts[1:], y_midpts[1:]))[:, np.newaxis, :] + segments = np.concatenate((coord_start, coord_mid, coord_end), axis=1) + + lc = LineCollection(segments, **default_kwargs) + lc.set_array(c) # set the colors of each segment + + return ax.add_collection(lc) + + +# -------------- Create and show plot -------------- +# Some arbitrary function that gives x, y, and color values +t = np.linspace(-7.4, -0.5, 200) +x = 0.9 * np.sin(t) +y = 0.9 * np.cos(1.6 * t) +color = np.linspace(0, 2, t.size) + +# Create a figure and plot the line on it +fig1, ax1 = plt.subplots() +lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma") +fig1.colorbar(lines) # add a color legend + +# Set the axis limits and tick positions +ax1.set_xlim(-1, 1) +ax1.set_ylim(-1, 1) +ax1.set_xticks((-1, 0, 1)) +ax1.set_yticks((-1, 0, 1)) +ax1.set_title("Color at each point") + +plt.show() + +#################################################################### +# This method is designed to give a smooth impression when distances and color +# differences between adjacent points are not too large. The following example +# does not meet this criteria and by that serves to illustrate the segmentation +# and coloring mechanism. +x = [0, 1, 2, 3, 4] +y = [0, 1, 2, 1, 1] +c = [1, 2, 3, 4, 5] +fig, ax = plt.subplots() +ax.scatter(x, y, c=c, cmap='rainbow') +colored_line(x, y, c=c, ax=ax, cmap='rainbow') + +plt.show() + +#################################################################### +# Color values between points +# --------------------------- +# + + +def colored_line_between_pts(x, y, c, ax, **lc_kwargs): + """ + Plot a line with a color specified between (x, y) points by a third value. + + It does this by creating a collection of line segments between each pair of + neighboring points. The color of each segment is determined by the + made up of two straight lines each connecting the current (x, y) point to the + midpoints of the lines connecting the current point with its two neighbors. + This creates a smooth line with no gaps between the line segments. + + Parameters + ---------- + x, y : array-like + The horizontal and vertical coordinates of the data points. + c : array-like + The color values, which should have a size one less than that of x and y. + ax : Axes + Axis object on which to plot the colored line. + **lc_kwargs + Any additional arguments to pass to matplotlib.collections.LineCollection + constructor. This should not include the array keyword argument because + that is set to the color argument. If provided, it will be overridden. + + Returns + ------- + matplotlib.collections.LineCollection + The generated line collection representing the colored line. + """ + if "array" in lc_kwargs: + warnings.warn('The provided "array" keyword argument will be overridden') + + # Check color array size (LineCollection still works, but values are unused) + if len(c) != len(x) - 1: + warnings.warn( + "The c argument should have a length one less than the length of x and y. " + "If it has the same length, use the colored_line function instead." + ) + + # Create a set of line segments so that we can color them individually + # This creates the points as an N x 1 x 2 array so that we can stack points + # together easily to get the segments. The segments array for line collection + # needs to be (numlines) x (points per line) x 2 (for x and y) + points = np.array([x, y]).T.reshape(-1, 1, 2) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + lc = LineCollection(segments, **lc_kwargs) + + # Set the values used for colormapping + lc.set_array(c) + + return ax.add_collection(lc) + + +# -------------- Create and show plot -------------- x = np.linspace(0, 3 * np.pi, 500) y = np.sin(x) dydx = np.cos(0.5 * (x[:-1] + x[1:])) # first derivative -# Create a set of line segments so that we can color them individually -# This creates the points as an N x 1 x 2 array so that we can stack points -# together easily to get the segments. The segments array for line collection -# needs to be (numlines) x (points per line) x 2 (for x and y) -points = np.array([x, y]).T.reshape(-1, 1, 2) -segments = np.concatenate([points[:-1], points[1:]], axis=1) - -fig, axs = plt.subplots(2, 1, sharex=True, sharey=True) - -# Create a continuous norm to map from data points to colors -norm = plt.Normalize(dydx.min(), dydx.max()) -lc = LineCollection(segments, cmap='viridis', norm=norm) -# Set the values used for colormapping -lc.set_array(dydx) -lc.set_linewidth(2) -line = axs[0].add_collection(lc) -fig.colorbar(line, ax=axs[0]) - -# Use a boundary norm instead -cmap = ListedColormap(['r', 'g', 'b']) -norm = BoundaryNorm([-1, -0.5, 0.5, 1], cmap.N) -lc = LineCollection(segments, cmap=cmap, norm=norm) -lc.set_array(dydx) -lc.set_linewidth(2) -line = axs[1].add_collection(lc) -fig.colorbar(line, ax=axs[1]) - -axs[0].set_xlim(x.min(), x.max()) -axs[0].set_ylim(-1.1, 1.1) +fig2, ax2 = plt.subplots() +line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis") +fig2.colorbar(line, ax=ax2, label="dy/dx") + +ax2.set_xlim(x.min(), x.max()) +ax2.set_ylim(-1.1, 1.1) +ax2.set_title("Color between points") + plt.show() From 60197c50f948967e82d16023460d3fb3f13d4698 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sat, 8 Jun 2024 15:14:30 -0600 Subject: [PATCH 1006/1120] Backport PR #28337: Bump the actions group across 1 directory with 3 updates --- .github/workflows/cibuildwheel.yml | 12 ++++++------ .github/workflows/conflictcheck.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 04c70a767ce0..41f5bca65f18 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -203,7 +203,7 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 with: subject-path: dist/matplotlib-* diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index fc759f52a6b0..3110839e5150 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1 + uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" From 394b5539dc337b54f8a730ec126cc7fcb4fdf73c Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:39:28 -0600 Subject: [PATCH 1007/1120] Backport PR #28359: Document that axes unsharing is impossible. --- lib/matplotlib/axes/_base.py | 7 ++++--- lib/matplotlib/figure.py | 2 ++ lib/matplotlib/pyplot.py | 5 +++-- lib/mpl_toolkits/mplot3d/axes3d.py | 14 ++++++++------ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 0164f4e11169..30c4efe80c49 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -597,7 +597,8 @@ def __init__(self, fig, sharex, sharey : `~matplotlib.axes.Axes`, optional The x- or y-`~.matplotlib.axis` is shared with the x- or y-axis in - the input `~.axes.Axes`. + the input `~.axes.Axes`. Note that it is not possible to unshare + axes. frameon : bool, default: True Whether the Axes frame is visible. @@ -1221,7 +1222,7 @@ def sharex(self, other): This is equivalent to passing ``sharex=other`` when constructing the Axes, and cannot be used if the x-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(_AxesBase, other=other) if self._sharex is not None and other is not self._sharex: @@ -1240,7 +1241,7 @@ def sharey(self, other): This is equivalent to passing ``sharey=other`` when constructing the Axes, and cannot be used if the y-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(_AxesBase, other=other) if self._sharey is not None and other is not self._sharey: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 0a0ff01a2571..e5f4bb9421cf 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -755,6 +755,8 @@ def subplots(self, nrows=1, ncols=1, *, sharex=False, sharey=False, When subplots have a shared axis that has units, calling `.Axis.set_units` will update each axis with the new units. + Note that it is not possible to unshare axes. + squeeze : bool, default: True - If True, extra dimensions are squeezed out from the returned array of Axes: diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 1a8298212709..7f8d0bbc6e7f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1639,8 +1639,9 @@ def subplots( on, use `~matplotlib.axes.Axes.tick_params`. When subplots have a shared axis that has units, calling - `~matplotlib.axis.Axis.set_units` will update each axis with the - new units. + `.Axis.set_units` will update each axis with the new units. + + Note that it is not possible to unshare axes. squeeze : bool, default: True - If True, extra dimensions are squeezed out from the returned diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 18b823ccccb3..91845748880b 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -83,7 +83,8 @@ def __init__( axis. A positive angle spins the camera clockwise, causing the scene to rotate counter-clockwise. sharez : Axes3D, optional - Other Axes to share z-limits with. + Other Axes to share z-limits with. Note that it is not possible to + unshare axes. proj_type : {'persp', 'ortho'} The projection type, default 'persp'. box_aspect : 3-tuple of floats, default: None @@ -107,7 +108,8 @@ def __init__( The focal length can be computed from a desired Field Of View via the equation: focal_length = 1/tan(FOV/2) shareview : Axes3D, optional - Other Axes to share view angles with. + Other Axes to share view angles with. Note that it is not possible + to unshare axes. **kwargs Other optional keyword arguments: @@ -1307,7 +1309,7 @@ def sharez(self, other): This is equivalent to passing ``sharez=other`` when constructing the Axes, and cannot be used if the z-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(Axes3D, other=other) if self._sharez is not None and other is not self._sharez: @@ -1324,9 +1326,9 @@ def shareview(self, other): """ Share the view angles with *other*. - This is equivalent to passing ``shareview=other`` when - constructing the Axes, and cannot be used if the view angles are - already being shared with another Axes. + This is equivalent to passing ``shareview=other`` when constructing the + Axes, and cannot be used if the view angles are already being shared + with another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(Axes3D, other=other) if self._shareview is not None and other is not self._shareview: From 35f38de2359963bbb5bb193070547fc059ae15b3 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 12 Jun 2024 16:20:13 -0500 Subject: [PATCH 1008/1120] Backport PR #28377: DOC: Clarify scope of wrap. --- lib/matplotlib/text.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 40cd8c8cd6f7..7fc19c042a1f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -606,6 +606,10 @@ def set_wrap(self, wrap): """ Set whether the text can be wrapped. + Wrapping makes sure the text is completely within the figure box, i.e. + it does not extend beyond the drawing area. It does not take into + account any other artists. + Parameters ---------- wrap : bool From 1099476c3ae3b855607f3a6c6d454d0a2c44aaf8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:36:01 +0200 Subject: [PATCH 1009/1120] Backport PR #28380: Remove outdated docstring section in RendererBase.draw_text. --- lib/matplotlib/backend_bases.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index f4273bc03919..53e5f6b23213 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -513,21 +513,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): If True, use mathtext parser. If "TeX", use tex for rendering. mtext : `~matplotlib.text.Text` The original text object to be rendered. - - Notes - ----- - **Note for backend implementers:** - - When you are trying to determine if you have gotten your bounding box - right (which is what enables the text layout/alignment to work - properly), it helps to change the line in text.py:: - - if 0: bbox_artist(self, renderer) - - to if 1, and then the actual bounding box will be plotted along with - your text. """ - self._draw_text_as_path(gc, x, y, s, prop, angle, ismath) def _get_text_path_transform(self, x, y, s, prop, angle, ismath): From 0ffc8c12352dd59cd08a36710888a945512a37a9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 13 Jun 2024 17:36:34 -0400 Subject: [PATCH 1010/1120] Backport PR #28388: Allow duplicate (name, value) entry points for backends --- lib/matplotlib/backends/registry.py | 7 +++++-- lib/matplotlib/tests/test_backend_registry.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 19b4cba254ab..47d5f65e350e 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -168,8 +168,11 @@ def backward_compatible_entry_points( def _validate_and_store_entry_points(self, entries): # Validate and store entry points so that they can be used via matplotlib.use() # in the normal manner. Entry point names cannot be of module:// format, cannot - # shadow a built-in backend name, and cannot be duplicated. - for name, module in entries: + # shadow a built-in backend name, and there cannot be multiple entry points + # with the same name but different modules. Multiple entry points with the same + # name and value are permitted (it can sometimes happen outside of our control, + # see https://github.com/matplotlib/matplotlib/issues/28367). + for name, module in set(entries): name = name.lower() if name.startswith("module://"): raise RuntimeError( diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index eaf8417e7a5f..141ffd69c266 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -121,6 +121,17 @@ def test_entry_point_name_duplicate(clear_backend_registry): [('some_name', 'module1'), ('some_name', 'module2')]) +def test_entry_point_identical(clear_backend_registry): + # Issue https://github.com/matplotlib/matplotlib/issues/28367 + # Multiple entry points with the same name and value (value is the module) + # are acceptable. + n = len(backend_registry._name_to_module) + backend_registry._validate_and_store_entry_points( + [('some_name', 'some.module'), ('some_name', 'some.module')]) + assert len(backend_registry._name_to_module) == n+1 + assert backend_registry._name_to_module['some_name'] == 'module://some.module' + + def test_entry_point_name_is_module(clear_backend_registry): with pytest.raises(RuntimeError): backend_registry._validate_and_store_entry_points( From da5c20f637b2c35b157b0da7f2d20bd5d689ddf2 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:17:53 +0100 Subject: [PATCH 1011/1120] Backport PR #28413: CI: update action that got moved org --- .github/workflows/circleci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 8f9e3190c5e2..cfdf184b8666 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,7 +10,7 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: larsoner/circleci-artifacts-redirector-action@master + uses: scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} From 2c59ec6de4a13ac14343fc67aa57643bd9d674b9 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 18 Jun 2024 20:06:17 -0500 Subject: [PATCH 1012/1120] Backport PR #28414: Clean up obsolete widget code --- lib/matplotlib/widgets.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index eaa35e25440b..ed130e6854f2 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1001,14 +1001,8 @@ class CheckButtons(AxesWidget): ---------- ax : `~matplotlib.axes.Axes` The parent Axes for the widget. - labels : list of `~matplotlib.text.Text` - - rectangles : list of `~matplotlib.patches.Rectangle` - - lines : list of (`.Line2D`, `.Line2D`) pairs - List of lines for the x's in the checkboxes. These lines exist for - each box, but have ``set_visible(False)`` when its box is not checked. + The text label objects of the check buttons. """ def __init__(self, ax, labels, actives=None, *, useblit=True, @@ -1571,8 +1565,6 @@ class RadioButtons(AxesWidget): The color of the selected button. labels : list of `.Text` The button labels. - circles : list of `~.patches.Circle` - The buttons. value_selected : str The label text of the currently selected button. index_selected : int @@ -1751,11 +1743,6 @@ def activecolor(self, activecolor): colors._check_color_like(activecolor=activecolor) self._activecolor = activecolor self.set_radio_props({'facecolor': activecolor}) - # Make sure the deprecated version is updated. - # Remove once circles is removed. - labels = [label.get_text() for label in self.labels] - with cbook._setattr_cm(self, eventson=False): - self.set_active(labels.index(self.value_selected)) def set_active(self, index): """ From f8f82060b0884645dc7b149ae22818160ca94b23 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 19 Jun 2024 11:57:02 +0200 Subject: [PATCH 1013/1120] Backport PR #28401: FIX: Fix text wrapping --- lib/matplotlib/tests/test_text.py | 9 +++++++-- lib/matplotlib/text.py | 13 ++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index f8837d8a5f1b..8904337f68ba 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -15,6 +15,7 @@ from matplotlib.font_manager import FontProperties import matplotlib.patches as mpatches import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec import matplotlib.transforms as mtransforms from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing._markers import needs_usetex @@ -707,9 +708,13 @@ def test_large_subscript_title(): (0.3, 0, 'right'), (0.3, 185, 'left')]) def test_wrap(x, rotation, halign): - fig = plt.figure(figsize=(6, 6)) + fig = plt.figure(figsize=(18, 18)) + gs = GridSpec(nrows=3, ncols=3, figure=fig) + subfig = fig.add_subfigure(gs[1, 1]) + # we only use the central subfigure, which does not align with any + # figure boundary, to ensure only subfigure boundaries are relevant s = 'This is a very long text that should be wrapped multiple times.' - text = fig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign) + text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign) fig.canvas.draw() assert text._get_wrapped_text() == ('This is a very long\n' 'text that should be\n' diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 7fc19c042a1f..af990ec1bf9f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -606,9 +606,8 @@ def set_wrap(self, wrap): """ Set whether the text can be wrapped. - Wrapping makes sure the text is completely within the figure box, i.e. - it does not extend beyond the drawing area. It does not take into - account any other artists. + Wrapping makes sure the text is confined to the (sub)figure box. It + does not take into account any other artists. Parameters ---------- @@ -657,16 +656,16 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box): """ if rotation > 270: quad = rotation - 270 - h1 = y0 / math.cos(math.radians(quad)) + h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad)) h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad)) elif rotation > 180: quad = rotation - 180 - h1 = x0 / math.cos(math.radians(quad)) - h2 = y0 / math.cos(math.radians(90 - quad)) + h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad)) + h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad)) elif rotation > 90: quad = rotation - 90 h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad)) - h2 = x0 / math.cos(math.radians(90 - quad)) + h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad)) else: h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation)) h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation)) From f8ff4e1a9efceccf4b3bf14ade86d76851dfc678 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:32:17 +0100 Subject: [PATCH 1014/1120] Backport PR #28423: Update return type for Axes.axhspan and Axes.axvspan --- lib/matplotlib/axes/_axes.py | 12 ++++++------ lib/matplotlib/axes/_axes.pyi | 4 ++-- lib/matplotlib/pyplot.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 34c4023a256e..fdafc2dcb0bc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1006,14 +1006,14 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): Returns ------- - `~matplotlib.patches.Polygon` + `~matplotlib.patches.Rectangle` Horizontal span (rectangle) from (xmin, ymin) to (xmax, ymax). Other Parameters ---------------- - **kwargs : `~matplotlib.patches.Polygon` properties + **kwargs : `~matplotlib.patches.Rectangle` properties - %(Polygon:kwdoc)s + %(Rectangle:kwdoc)s See Also -------- @@ -1061,14 +1061,14 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): Returns ------- - `~matplotlib.patches.Polygon` + `~matplotlib.patches.Rectangle` Vertical span (rectangle) from (xmin, ymin) to (xmax, ymax). Other Parameters ---------------- - **kwargs : `~matplotlib.patches.Polygon` properties + **kwargs : `~matplotlib.patches.Rectangle` properties - %(Polygon:kwdoc)s + %(Rectangle:kwdoc)s See Also -------- diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index b70d330aa442..76aaee77aff8 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -155,10 +155,10 @@ class Axes(_AxesBase): ) -> AxLine: ... def axhspan( self, ymin: float, ymax: float, xmin: float = ..., xmax: float = ..., **kwargs - ) -> Polygon: ... + ) -> Rectangle: ... def axvspan( self, xmin: float, xmax: float, ymin: float = ..., ymax: float = ..., **kwargs - ) -> Polygon: ... + ) -> Rectangle: ... def hlines( self, y: float | ArrayLike, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f8d0bbc6e7f..8fe8b000bf49 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2870,7 +2870,7 @@ def axhline(y: float = 0, xmin: float = 0, xmax: float = 1, **kwargs) -> Line2D: @_copy_docstring_and_deprecators(Axes.axhspan) def axhspan( ymin: float, ymax: float, xmin: float = 0, xmax: float = 1, **kwargs -) -> Polygon: +) -> Rectangle: return gca().axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs) @@ -2908,7 +2908,7 @@ def axvline(x: float = 0, ymin: float = 0, ymax: float = 1, **kwargs) -> Line2D: @_copy_docstring_and_deprecators(Axes.axvspan) def axvspan( xmin: float, xmax: float, ymin: float = 0, ymax: float = 1, **kwargs -) -> Polygon: +) -> Rectangle: return gca().axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs) From 8bf8cf59dca11764f44d14a461879ca34fe2991d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 19 Jun 2024 14:06:20 -0500 Subject: [PATCH 1015/1120] Backport PR #28425: Fix Circle yaml line length --- .github/workflows/circleci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index cfdf184b8666..4e5733e03466 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,7 +10,8 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 + uses: | #v1.0.0 + scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} From f8d15bdac2c85a55e976278392fbe7022ef521f7 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:14:59 +0100 Subject: [PATCH 1016/1120] Backport PR #28427: Fix circleci yaml --- .github/workflows/circleci.yml | 4 ++-- .yamllint.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 4e5733e03466..3aead720cf20 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,8 +10,8 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: | #v1.0.0 - scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 + uses: + scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} diff --git a/.yamllint.yml b/.yamllint.yml index 3b30533ececa..2be81b28c7fb 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -3,7 +3,7 @@ extends: default rules: line-length: - max: 111 + max: 120 allow-non-breakable-words: true truthy: check-keys: false From c747ac444db273bdd93f894825b4d32671c998b4 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:29:12 -0600 Subject: [PATCH 1017/1120] Backport PR #28403: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection --- lib/mpl_toolkits/mplot3d/axes3d.py | 29 +++++++++++++-- .../test_axes3d/voxels-named-colors.png | Bin 59278 -> 54173 bytes lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 34 +++++++++++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 91845748880b..71cd8f062d40 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2578,7 +2578,7 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs): self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) return cset - def add_collection3d(self, col, zs=0, zdir='z'): + def add_collection3d(self, col, zs=0, zdir='z', autolim=True): """ Add a 3D collection object to the plot. @@ -2590,8 +2590,21 @@ def add_collection3d(self, col, zs=0, zdir='z'): - `.PolyCollection` - `.LineCollection` - - `.PatchCollection` + - `.PatchCollection` (currently not supporting *autolim*) + + Parameters + ---------- + col : `.Collection` + A 2D collection object. + zs : float or array-like, default: 0 + The z-positions to be used for the 2D objects. + zdir : {'x', 'y', 'z'}, default: 'z' + The direction to use for the z-positions. + autolim : bool, default: True + Whether to update the data limits. """ + had_data = self.has_data() + zvals = np.atleast_1d(zs) zsortval = (np.min(zvals) if zvals.size else 0) # FIXME: arbitrary default @@ -2609,6 +2622,18 @@ def add_collection3d(self, col, zs=0, zdir='z'): art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir) col.set_sort_zpos(zsortval) + if autolim: + if isinstance(col, art3d.Line3DCollection): + self.auto_scale_xyz(*np.array(col._segments3d).transpose(), + had_data=had_data) + elif isinstance(col, art3d.Poly3DCollection): + self.auto_scale_xyz(*col._vec[:-1], had_data=had_data) + elif isinstance(col, art3d.Patch3DCollection): + pass + # FIXME: Implement auto-scaling function for Patch3DCollection + # Currently unable to do so due to issues with Patch3DCollection + # See https://github.com/matplotlib/matplotlib/issues/14298 for details + collection = super().add_collection(col) return collection diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png index b71ad19c1608b06b33e51a5ae3ada23fbbc06278..33dfc2f2313ad100797b89136555b8fc9e593a2f 100644 GIT binary patch literal 54173 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 Jzf-jg|3BzpibMba literal 59278 zcmeFYWmuHa*F8K8HFQWxx0G~ugGhsbNQ2VSF!az}ihwjI-6A1fGAaThDIFq6BPsbm z-+t=t{cSw&N=r!d#|5ehK#Ygc!Q0#2OOlVz^?$yc*Ui(OPrs%@4g3fkclF0!5D1Yq z>I1D@uFMev`L*ysS-}vVyL0X5ZTC222`6Mfn7t$H0b}QAwfCdfuO7d8{Q5`Nk7(_k z{FTaz#@rS92NU{g>F?83ziUXb-1loGd#W03lRy^pR=Gx%G^W^f_W6s2VW;M*Jq^aR zMJdLxNSw%&jlBgqdJ9HKq(9%P3Y6nYc4OI)jo4!;iSk$QnthiXdr3MYEFAST;|Fz$ z$rEEJfTzPu9YXr=GZ%sX`|SVD07K#b&fx#g8BClkLKKK2(@B%pAP_PYG+UQEKV@Q! z!^LO}Mg`*Scw%VA(<0LvMGc%031O{>Vh-I$JWP^}kK`>=aWV9+>J+xcJd* zP8qg{Oe2OudZ?=K6dye*;=sjG3}=ibW<*mb9(bsxfiv`O%h3{IVqzlCLlIHxcrpi< zV};>_GxkNHei$D)6Nct^Br!DCYoojd46UDEw`#IS0(&DDgGqr{8Jh#DPOQ8{ zqG)Apjiv|#Yk>6h#}FMZ_oCBEcwz*e?M1oHrRq<8%uXk5G9~z6R(oSR=W9;<%TofM zfWUgGEBItitTZ8PJze168N5n`u)>Pn;Nf52@yvJl-^umbS?XpmuCq|7G3x(4UY7RR zZc8LA30nWdbkeDb7t7w>9u4xtrJ0^dvqXP6ciL^AZs|#;fYCnh>CN_aJ|oPATLGHN z7~92I66rcu?@9x4zdUs{ta=1?Y6FK;-P6uRR92qM`H~{4KKSlZGpE698cfW|G&s|S zpKTZOztX6Qv{~}B zZTA;Di4_Y?c&4_o_ZQl+RrHKteQ@b6RbtZ&%yHrElsMG8-N!`Li7}z7&TCp^?{JRT z;p#`cT-ji178aJ)z_aI1dMVNieFAd5>;&(E2lbv5VPv?NKRqSBe*L<)K0)l+ibI-j6_FAKS8e2%{8`s@Y*08$&+ePB@Qqj1hJ53+-^Q#k+Vl>pE*4amCAzbA#F?)S^c6hL& zL@N_O25z7$w?!X?XvmZh56x}L?e$S=6Z6e{$;S#mpH9WojQ%>5yQ*4JX)r^BTzy`sgLEIOxFmSxN(sl`?giJaMA#=E^VE09 zfZLLmMv|DA2!UvhSL5|vp6$QUuV4h1jyN)jZV?spdbvz{r1ev2fSCGszO3<@a~*1g&aAjR$-PETTel5mIrCUM*%`2Gl@Cbt(@$8MaIdXH8Lsd-;4H9iVp*FSf!3!IFg1OSu0}*_!Y5vonK2raSNcguL zeef>CH&EAW6BiX=!X+NLG#j*vu=zG0JOcv*agS9D-@W;}4xO1KXzyLZZs+W8A^Y3a7|2V@n`3Xxb=n*#(T)cmid|wNEH7SepOoE!|A%OCe^-`#G&_l1T9a5Tt%hi_vfeeUfcwR2}GOlpdN5x6*b~CO7shCuXcV2 zi6=*BvKvrFRv0=ZG4j09X!Tsj1rkd^@ss9iH{U^%h(wcoT9h>Z*jYk?fdm@~Zkz2qUcYN5}=84-jI#h3Gf zkacsip{8$B$Eo^?hJTh1H6MlzP({SSo^N10->5tv?!GM`wiiu)uCpd(A(uEB^poe^ z=jDn&Mo10zm+I^d#czWf}(C4gUAR))$FW*zW`-%B(!+E)ji zw>y3wXDWBUH!}`%G4AM|a=PB>6D%Ci@2p|LcoCnpCe?WUlSb`uDRh&EbnF-Vk51+i z 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

  • G@`d=>P7mn? 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 +sMvbnEJG?Z$bu9Pl6jY?ku%^&PiH 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 ZG`Ib9x_e|QYtjNgQ4!n18$^4){0jl~Hn0Ey diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 34f10cb9fd63..ecb51b724c27 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -961,8 +961,8 @@ def test_poly3dcollection_closed(): facecolor=(0.5, 0.5, 1, 0.5), closed=True) c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k', facecolor=(1, 0.5, 0.5, 0.5), closed=False) - ax.add_collection3d(c1) - ax.add_collection3d(c2) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) def test_poly_collection_2d_to_3d_empty(): @@ -995,8 +995,8 @@ def test_poly3dcollection_alpha(): c2.set_facecolor((1, 0.5, 0.5)) c2.set_edgecolor('k') c2.set_alpha(0.5) - ax.add_collection3d(c1) - ax.add_collection3d(c2) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) @mpl3d_image_comparison(['add_collection3d_zs_array.png'], style='mpl20') @@ -1055,6 +1055,32 @@ def test_add_collection3d_zs_scalar(): ax.set_zlim(0, 2) +def test_line3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + lines = [[(0, 0, 0), (1, 4, 2)], + [(1, 1, 3), (2, 0, 2)], + [(1, 0, 4), (1, 4, 5)]] + + lc = art3d.Line3DCollection(lines) + ax.add_collection3d(lc) + assert np.allclose(ax.get_xlim3d(), (-0.041666666666666664, 2.0416666666666665)) + assert np.allclose(ax.get_ylim3d(), (-0.08333333333333333, 4.083333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.10416666666666666, 5.104166666666667)) + + +def test_poly3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + poly = np.array([[0, 0, 0], [1, 1, 3], [1, 0, 4]]) + col = art3d.Poly3DCollection([poly]) + ax.add_collection3d(col) + assert np.allclose(ax.get_xlim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_ylim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.0833333333333333, 4.083333333333333)) + + @mpl3d_image_comparison(['axes3d_labelpad.png'], remove_text=False, style='mpl20') def test_axes3d_labelpad(): From e46044db0fc6f159b461974b8e57c3e3a7f4e366 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:03:22 +0100 Subject: [PATCH 1018/1120] Backport PR #28441: MNT: Update basic units example to work with numpy 2.0 --- galleries/examples/units/basic_units.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/galleries/examples/units/basic_units.py b/galleries/examples/units/basic_units.py index 0b493ab7216c..f9a94bcf6e37 100644 --- a/galleries/examples/units/basic_units.py +++ b/galleries/examples/units/basic_units.py @@ -146,10 +146,10 @@ def __getattribute__(self, name): return getattr(variable, name) return object.__getattribute__(self, name) - def __array__(self, dtype=object): + def __array__(self, dtype=object, copy=False): return np.asarray(self.value, dtype) - def __array_wrap__(self, array, context): + def __array_wrap__(self, array, context=None, return_scalar=False): return TaggedValue(array, self.unit) def __repr__(self): @@ -222,10 +222,10 @@ def __mul__(self, rhs): def __rmul__(self, lhs): return self*lhs - def __array_wrap__(self, array, context): + def __array_wrap__(self, array, context=None, return_scalar=False): return TaggedValue(array, self) - def __array__(self, t=None, context=None): + def __array__(self, t=None, context=None, copy=False): ret = np.array(1) if t is not None: return ret.astype(t) From b0dba39b9082cf70f5c481775a03000d9b83dea4 Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Mon, 24 Jun 2024 06:31:13 -0700 Subject: [PATCH 1019/1120] Backport PR #28436: Fix `is_color_like` for 2-tuple of strings and fix `to_rgba` for `(nth_color, alpha)` (#28438) Co-authored-by: hannah --- lib/matplotlib/colors.py | 12 ++++++------ lib/matplotlib/tests/test_colors.py | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c4e5987fdf92..177557b371a6 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -225,7 +225,7 @@ def is_color_like(c): return True try: to_rgba(c) - except ValueError: + except (TypeError, ValueError): return False else: return True @@ -296,6 +296,11 @@ def to_rgba(c, alpha=None): Tuple of floats ``(r, g, b, a)``, where each channel (red, green, blue, alpha) can assume values between 0 and 1. """ + if isinstance(c, tuple) and len(c) == 2: + if alpha is None: + c, alpha = c + else: + c = c[0] # Special-case nth color syntax because it should not be cached. if _is_nth_color(c): prop_cycler = mpl.rcParams['axes.prop_cycle'] @@ -325,11 +330,6 @@ def _to_rgba_no_colorcycle(c, alpha=None): *alpha* is ignored for the color value ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``. """ - if isinstance(c, tuple) and len(c) == 2: - if alpha is None: - c, alpha = c - else: - c = c[0] if alpha is not None and not 0 <= alpha <= 1: raise ValueError("'alpha' must be between 0 and 1, inclusive") orig_c = c diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 63f2d4f00399..4fd9f86c06e3 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -19,7 +19,7 @@ import matplotlib.scale as mscale from matplotlib.rcsetup import cycler from matplotlib.testing.decorators import image_comparison, check_figures_equal -from matplotlib.colors import to_rgba_array +from matplotlib.colors import is_color_like, to_rgba_array @pytest.mark.parametrize('N, result', [ @@ -1697,3 +1697,16 @@ def test_to_rgba_array_none_color_with_alpha_param(): assert_array_equal( to_rgba_array(c, alpha), [[0., 0., 1., 1.], [0., 0., 0., 0.]] ) + + +@pytest.mark.parametrize('input, expected', + [('red', True), + (('red', 0.5), True), + (('red', 2), False), + (['red', 0.5], False), + (('red', 'blue'), False), + (['red', 'blue'], False), + ('C3', True), + (('C3', 0.5), True)]) +def test_is_color_like(input, expected): + assert is_color_like(input) is expected From 9fd68931b5b7d07aa8c74c23cf65e10b6e5e5ef7 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 16 Jun 2024 06:50:16 -0600 Subject: [PATCH 1020/1120] Merge pull request #28371 from matplotlib/dependabot/github_actions/actions-795b56d292 Bump pypa/cibuildwheel from 2.18.1 to 2.19.0 in the actions group (cherry picked from commit b3d29fb036b0d9de2fb66ee7bcad6887654b3706) --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 41f5bca65f18..165f496c0b6e 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From 8f996742b0afd6aaa1eeedf28ba87376acd1da80 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 18 Jun 2024 20:08:00 -0500 Subject: [PATCH 1021/1120] Merge pull request #28411 from matplotlib/dependabot/github_actions/actions-39ddd2ba80 Bump the actions group with 3 updates (cherry picked from commit 3bd23db8690707ecfb8cfa0ab12ff7258f031ad5) --- .github/workflows/cibuildwheel.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 165f496c0b6e..a4c0c0781813 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -203,9 +203,9 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 + uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-path: dist/matplotlib-* - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 From b14dc233fa189811b7092e1f478cdd7ae3db08a1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 17:41:27 -0400 Subject: [PATCH 1022/1120] Backport PR #28459: DOC: Document kwargs scope for tick setter functions --- lib/matplotlib/axis.py | 4 +++- lib/matplotlib/pyplot.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index d317f6ec0567..3afc98fac60b 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -2028,7 +2028,9 @@ def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs): .. warning:: - This only sets the properties of the current ticks. + This only sets the properties of the current ticks, which is + only sufficient for static plots. + Ticks are not guaranteed to be persistent. Various operations can create, delete and modify the Tick instances. There is an imminent risk that these settings can get lost if you work on diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8fe8b000bf49..8b4769342c7d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2170,6 +2170,21 @@ def xticks( **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + + Returns ------- locs @@ -2241,6 +2256,20 @@ def yticks( **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + Returns ------- locs From b8042eed7a106fb1a5bec645f6a830d9f4615c4b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 17:48:32 -0400 Subject: [PATCH 1023/1120] Backport PR #28458: Correct numpy dtype comparisons in image_resample --- lib/matplotlib/tests/test_image.py | 17 +++++++++++++++++ src/_image_wrapper.cpp | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 1602f86716cb..a043d3aec983 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1585,3 +1585,20 @@ def test_non_transdata_image_does_not_touch_aspect(): assert ax.get_aspect() == 1 ax.imshow(im, transform=ax.transAxes, aspect=2) assert ax.get_aspect() == 2 + + +@pytest.mark.parametrize( + 'dtype', + ('float64', 'float32', 'int16', 'uint16', 'int8', 'uint8'), +) +@pytest.mark.parametrize('ndim', (2, 3)) +def test_resample_dtypes(dtype, ndim): + # Issue 28448, incorrect dtype comparisons in C++ image_resample can raise + # ValueError: arrays must be of dtype byte, short, float32 or float64 + rng = np.random.default_rng(4181) + shape = (2, 2) if ndim == 2 else (2, 2, 3) + data = rng.uniform(size=shape).astype(np.dtype(dtype, copy=True)) + fig, ax = plt.subplots() + axes_image = ax.imshow(data) + # Before fix the following raises ValueError for some dtypes. + axes_image.make_image(None)[0] diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 65c8c8324ebc..856dcf4ea3ce 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -173,20 +173,20 @@ image_resample(py::array input_array, if (auto resampler = (ndim == 2) ? ( - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : nullptr) : ( // ndim == 3 - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : nullptr)) { Py_BEGIN_ALLOW_THREADS resampler( From 0caf58a617d2c9c022bb98acd67e730814193a75 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 18:31:41 -0400 Subject: [PATCH 1024/1120] Backport PR #28440: DOC: Add note about simplification of to_polygons --- lib/matplotlib/path.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index e72eb1a9ca73..94fd97d7b599 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -697,6 +697,9 @@ def to_polygons(self, transform=None, width=0, height=0, closed_only=True): be simplified so that vertices outside of (0, 0), (width, height) will be clipped. + The resulting polygons will be simplified if the + :attr:`Path.should_simplify` attribute of the path is `True`. + If *closed_only* is `True` (default), only closed polygons, with the last point being the same as the first point, will be returned. Any unclosed polylines in the path will be From 14711fac10f959162a054b2b3eb43fa3ef742d49 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 11:43:43 -0400 Subject: [PATCH 1025/1120] Backport PR #28465: Fix pickling of SubFigures --- lib/matplotlib/figure.py | 5 ++++- lib/matplotlib/figure.pyi | 3 ++- lib/matplotlib/tests/test_pickle.py | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index e5f4bb9421cf..d75ff527a008 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2224,7 +2224,6 @@ def __init__(self, parent, subplotspec, *, self.subplotpars = parent.subplotpars self.dpi_scale_trans = parent.dpi_scale_trans self._axobservers = parent._axobservers - self.canvas = parent.canvas self.transFigure = parent.transFigure self.bbox_relative = Bbox.null() self._redo_transform_rel_fig() @@ -2241,6 +2240,10 @@ def __init__(self, parent, subplotspec, *, self._set_artist_props(self.patch) self.patch.set_antialiased(False) + @property + def canvas(self): + return self._parent.canvas + @property def dpi(self): return self._parent.dpi diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index 21de9159d56c..b079312695c1 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -263,7 +263,6 @@ class SubFigure(FigureBase): figure: Figure subplotpars: SubplotParams dpi_scale_trans: Affine2D - canvas: FigureCanvasBase transFigure: Transform bbox_relative: Bbox figbbox: BboxBase @@ -282,6 +281,8 @@ class SubFigure(FigureBase): **kwargs ) -> None: ... @property + def canvas(self) -> FigureCanvasBase: ... + @property def dpi(self) -> float: ... @dpi.setter def dpi(self, value: float) -> None: ... diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 7e7ccc14bf8f..0cba4f392035 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -93,6 +93,11 @@ def _generate_complete_test_figure(fig_ref): plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$') plt.legend(draggable=True) + # Ensure subfigure parenting works. + subfigs = fig_ref.subfigures(2) + subfigs[0].subplots(1, 2) + subfigs[1].subplots(1, 2) + fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers. From 5ad70281f58b6cdda2359359369759e2158b38e5 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 26 Jun 2024 14:18:55 -0500 Subject: [PATCH 1026/1120] Backport PR #28355: MNT: Re-add matplotlib.cm.get_cmap --- lib/matplotlib/cm.py | 38 ++++++++++++++++++++++++++++++++++++++ lib/matplotlib/cm.pyi | 2 ++ 2 files changed, 40 insertions(+) diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index c14973560ac3..b0cb3e9a7ec1 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -232,6 +232,44 @@ def get_cmap(self, cmap): globals().update(_colormaps) +# This is an exact copy of pyplot.get_cmap(). It was removed in 3.9, but apparently +# caused more user trouble than expected. Re-added for 3.9.1 and extended the +# deprecation period for two additional minor releases. +@_api.deprecated( + '3.7', + removal='3.11', + alternative="``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()``" + " or ``pyplot.get_cmap()``" + ) +def get_cmap(name=None, lut=None): + """ + Get a colormap instance, defaulting to rc values if *name* is None. + + Parameters + ---------- + name : `~matplotlib.colors.Colormap` or str or None, default: None + If a `.Colormap` instance, it will be returned. Otherwise, the name of + a colormap known to Matplotlib, which will be resampled by *lut*. The + default, None, means :rc:`image.cmap`. + lut : int or None, default: None + If *name* is not already a Colormap instance and *lut* is not None, the + colormap will be resampled to have *lut* entries in the lookup table. + + Returns + ------- + Colormap + """ + if name is None: + name = mpl.rcParams['image.cmap'] + if isinstance(name, colors.Colormap): + return name + _api.check_in_list(sorted(_colormaps), name=name) + if lut is None: + return _colormaps[name] + else: + return _colormaps[name].resampled(lut) + + def _auto_norm_from_scale(scale_cls): """ Automatically generate a norm class from *scale_cls*. diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index da78d940ba4a..be8f10b39cb6 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -19,6 +19,8 @@ class ColormapRegistry(Mapping[str, colors.Colormap]): _colormaps: ColormapRegistry = ... +def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... + class ScalarMappable: cmap: colors.Colormap | None colorbar: Colorbar | None From 29637c5b45b345bf443035f1bc47a8194e92b3cb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 26 Jun 2024 15:24:00 -0400 Subject: [PATCH 1027/1120] Backport PR #28398: Add GIL Release to flush_events in macosx backend --- src/_macosx.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/_macosx.m b/src/_macosx.m index 656d502fa17c..fda928536ab5 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -77,6 +77,9 @@ static int wait_for_stdin() { // continuously run an event loop until the stdin_received flag is set to exit while (!stdin_received && !stdin_sigint) { + // This loop is similar to the main event loop and flush_events which have + // Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop. + // This should not be necessary here because PyOS_InputHook releases the GIL for us. while (true) { NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: [NSDate distantPast] @@ -380,6 +383,9 @@ static CGFloat _get_device_scale(CGContextRef cr) // to process, breaking out of the loop when no events remain and // displaying the canvas if needed. NSEvent *event; + + Py_BEGIN_ALLOW_THREADS + while (true) { event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: [NSDate distantPast] @@ -390,6 +396,9 @@ static CGFloat _get_device_scale(CGContextRef cr) } [NSApp sendEvent:event]; } + + Py_END_ALLOW_THREADS + [self->view displayIfNeeded]; Py_RETURN_NONE; } From bdad968ae24188ab1300b1b387a5f0c69ca1a714 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 15:57:31 -0400 Subject: [PATCH 1028/1120] Backport PR #28342: DOC: Document the parameter *position* of apply_aspect() as internal --- lib/matplotlib/axes/_base.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 30c4efe80c49..96e497a3316b 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1882,6 +1882,11 @@ def apply_aspect(self, position=None): Parameters ---------- position : None or .Bbox + + .. note:: + This parameter exists for historic reasons and is considered + internal. End users should not use it. + If not ``None``, this defines the position of the Axes within the figure as a Bbox. See `~.Axes.get_position` for further details. @@ -1892,6 +1897,10 @@ def apply_aspect(self, position=None): to call it yourself if you need to update the Axes position and/or view limits before the Figure is drawn. + An alternative with a broader scope is `.Figure.draw_without_rendering`, + which updates all stale components of a figure, not only the positioning / + view limits of a single Axes. + See Also -------- matplotlib.axes.Axes.set_aspect @@ -1900,6 +1909,24 @@ def apply_aspect(self, position=None): Set how the Axes adjusts to achieve the required aspect ratio. matplotlib.axes.Axes.set_anchor Set the position in case of extra space. + matplotlib.figure.Figure.draw_without_rendering + Update all stale components of a figure. + + Examples + -------- + A typical usage example would be the following. `~.Axes.imshow` sets the + aspect to 1, but adapting the Axes position and extent to reflect this is + deferred until rendering for performance reasons. If you want to know the + Axes size before, you need to call `.apply_aspect` to get the correct + values. + + >>> fig, ax = plt.subplots() + >>> ax.imshow(np.zeros((3, 3))) + >>> ax.bbox.width, ax.bbox.height + (496.0, 369.59999999999997) + >>> ax.apply_aspect() + >>> ax.bbox.width, ax.bbox.height + (369.59999999999997, 369.59999999999997) """ if position is None: position = self.get_position(original=True) From 8709791007cf4c8cbc745e314ac6d3d5f6d24ccf Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 26 Jun 2024 14:57:38 -0500 Subject: [PATCH 1029/1120] Backport PR #28289: Promote mpltype Sphinx role to a public extension --- doc/api/index.rst | 1 + .../next_api_changes/development/28289-ES.rst | 7 + doc/api/sphinxext_roles.rst | 7 + doc/conf.py | 2 +- doc/sphinxext/custom_roles.py | 89 ----------- lib/matplotlib/sphinxext/meson.build | 1 + lib/matplotlib/sphinxext/roles.py | 147 ++++++++++++++++++ pyproject.toml | 4 +- 8 files changed, 166 insertions(+), 92 deletions(-) create mode 100644 doc/api/next_api_changes/development/28289-ES.rst create mode 100644 doc/api/sphinxext_roles.rst delete mode 100644 doc/sphinxext/custom_roles.py create mode 100644 lib/matplotlib/sphinxext/roles.py diff --git a/doc/api/index.rst b/doc/api/index.rst index e55a0ed3c5b2..70c3b5343e7a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -126,6 +126,7 @@ Alphabetical list of modules: sphinxext_mathmpl_api.rst sphinxext_plot_directive_api.rst sphinxext_figmpl_directive_api.rst + sphinxext_roles.rst spines_api.rst style_api.rst table_api.rst diff --git a/doc/api/next_api_changes/development/28289-ES.rst b/doc/api/next_api_changes/development/28289-ES.rst new file mode 100644 index 000000000000..f891c63a64bf --- /dev/null +++ b/doc/api/next_api_changes/development/28289-ES.rst @@ -0,0 +1,7 @@ +Documentation-specific custom Sphinx roles are now semi-public +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For third-party packages that derive types from Matplotlib, our use of custom roles may +prevent Sphinx from building their docs. These custom Sphinx roles are now public solely +for the purposes of use within projects that derive from Matplotlib types. See +:mod:`matplotlib.sphinxext.roles` for details. diff --git a/doc/api/sphinxext_roles.rst b/doc/api/sphinxext_roles.rst new file mode 100644 index 000000000000..99959ff05d14 --- /dev/null +++ b/doc/api/sphinxext_roles.rst @@ -0,0 +1,7 @@ +============================== +``matplotlib.sphinxext.roles`` +============================== + +.. automodule:: matplotlib.sphinxext.roles + :no-undoc-members: + :private-members: _rcparam_role, _mpltype_role diff --git a/doc/conf.py b/doc/conf.py index 92d78f896ca2..f43806a8b4c0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -116,9 +116,9 @@ def _parse_skip_subdirs_file(): 'sphinx_gallery.gen_gallery', 'matplotlib.sphinxext.mathmpl', 'matplotlib.sphinxext.plot_directive', + 'matplotlib.sphinxext.roles', 'matplotlib.sphinxext.figmpl_directive', 'sphinxcontrib.inkscapeconverter', - 'sphinxext.custom_roles', 'sphinxext.github', 'sphinxext.math_symbol_table', 'sphinxext.missing_references', diff --git a/doc/sphinxext/custom_roles.py b/doc/sphinxext/custom_roles.py deleted file mode 100644 index d76c92709865..000000000000 --- a/doc/sphinxext/custom_roles.py +++ /dev/null @@ -1,89 +0,0 @@ -from urllib.parse import urlsplit, urlunsplit - -from docutils import nodes - -from matplotlib import rcParamsDefault - - -class QueryReference(nodes.Inline, nodes.TextElement): - """ - Wraps a reference or pending reference to add a query string. - - The query string is generated from the attributes added to this node. - - Also equivalent to a `~docutils.nodes.literal` node. - """ - - def to_query_string(self): - """Generate query string from node attributes.""" - return '&'.join(f'{name}={value}' for name, value in self.attlist()) - - -def visit_query_reference_node(self, node): - """ - Resolve *node* into query strings on its ``reference`` children. - - Then act as if this is a `~docutils.nodes.literal`. - """ - query = node.to_query_string() - for refnode in node.findall(nodes.reference): - uri = urlsplit(refnode['refuri'])._replace(query=query) - refnode['refuri'] = urlunsplit(uri) - - self.visit_literal(node) - - -def depart_query_reference_node(self, node): - """ - Act as if this is a `~docutils.nodes.literal`. - """ - self.depart_literal(node) - - -def rcparam_role(name, rawtext, text, lineno, inliner, options={}, content=[]): - # Generate a pending cross-reference so that Sphinx will ensure this link - # isn't broken at some point in the future. - title = f'rcParams["{text}"]' - target = 'matplotlibrc-sample' - ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', - 'ref', lineno) - - qr = QueryReference(rawtext, highlight=text) - qr += ref_nodes - node_list = [qr] - - # The default backend would be printed as "agg", but that's not correct (as - # the default is actually determined by fallback). - if text in rcParamsDefault and text != "backend": - node_list.extend([ - nodes.Text(' (default: '), - nodes.literal('', repr(rcParamsDefault[text])), - nodes.Text(')'), - ]) - - return node_list, messages - - -def mpltype_role(name, rawtext, text, lineno, inliner, options={}, content=[]): - mpltype = text - type_to_link_target = { - 'color': 'colors_def', - } - if mpltype not in type_to_link_target: - raise ValueError(f"Unknown mpltype: {mpltype!r}") - - node_list, messages = inliner.interpreted( - mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) - return node_list, messages - - -def setup(app): - app.add_role("rc", rcparam_role) - app.add_role("mpltype", mpltype_role) - app.add_node( - QueryReference, - html=(visit_query_reference_node, depart_query_reference_node), - latex=(visit_query_reference_node, depart_query_reference_node), - text=(visit_query_reference_node, depart_query_reference_node), - ) - return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/lib/matplotlib/sphinxext/meson.build b/lib/matplotlib/sphinxext/meson.build index 5dc7388384eb..35bb96fecbe1 100644 --- a/lib/matplotlib/sphinxext/meson.build +++ b/lib/matplotlib/sphinxext/meson.build @@ -3,6 +3,7 @@ python_sources = [ 'figmpl_directive.py', 'mathmpl.py', 'plot_directive.py', + 'roles.py', ] typing_sources = [ diff --git a/lib/matplotlib/sphinxext/roles.py b/lib/matplotlib/sphinxext/roles.py new file mode 100644 index 000000000000..301adcd8a5f5 --- /dev/null +++ b/lib/matplotlib/sphinxext/roles.py @@ -0,0 +1,147 @@ +""" +Custom roles for the Matplotlib documentation. + +.. warning:: + + These roles are considered semi-public. They are only intended to be used in + the Matplotlib documentation. + +However, it can happen that downstream packages end up pulling these roles into +their documentation, which will result in documentation build errors. The following +describes the exact mechanism and how to fix the errors. + +There are two ways, Matplotlib docstrings can end up in downstream documentation. +You have to subclass a Matplotlib class and either use the ``:inherited-members:`` +option in your autodoc configuration, or you have to override a method without +specifying a new docstring; the new method will inherit the original docstring and +still render in your autodoc. If the docstring contains one of the custom sphinx +roles, you'll see one of the following error messages: + +.. code-block:: none + + Unknown interpreted text role "mpltype". + Unknown interpreted text role "rc". + +To fix this, you can add this module as extension to your sphinx :file:`conf.py`:: + + extensions = [ + 'matplotlib.sphinxext.roles', + # Other extensions. + ] + +.. warning:: + + Direct use of these roles in other packages is not officially supported. We + reserve the right to modify or remove these roles without prior notification. +""" + +from urllib.parse import urlsplit, urlunsplit + +from docutils import nodes + +import matplotlib +from matplotlib import rcParamsDefault + + +class _QueryReference(nodes.Inline, nodes.TextElement): + """ + Wraps a reference or pending reference to add a query string. + + The query string is generated from the attributes added to this node. + + Also equivalent to a `~docutils.nodes.literal` node. + """ + + def to_query_string(self): + """Generate query string from node attributes.""" + return '&'.join(f'{name}={value}' for name, value in self.attlist()) + + +def _visit_query_reference_node(self, node): + """ + Resolve *node* into query strings on its ``reference`` children. + + Then act as if this is a `~docutils.nodes.literal`. + """ + query = node.to_query_string() + for refnode in node.findall(nodes.reference): + uri = urlsplit(refnode['refuri'])._replace(query=query) + refnode['refuri'] = urlunsplit(uri) + + self.visit_literal(node) + + +def _depart_query_reference_node(self, node): + """ + Act as if this is a `~docutils.nodes.literal`. + """ + self.depart_literal(node) + + +def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries. + + Usage: Give the desired ``rcParams`` key as parameter. + + :code:`:rc:`figure.dpi`` will render as: :rc:`figure.dpi` + """ + # Generate a pending cross-reference so that Sphinx will ensure this link + # isn't broken at some point in the future. + title = f'rcParams["{text}"]' + target = 'matplotlibrc-sample' + ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', + 'ref', lineno) + + qr = _QueryReference(rawtext, highlight=text) + qr += ref_nodes + node_list = [qr] + + # The default backend would be printed as "agg", but that's not correct (as + # the default is actually determined by fallback). + if text in rcParamsDefault and text != "backend": + node_list.extend([ + nodes.Text(' (default: '), + nodes.literal('', repr(rcParamsDefault[text])), + nodes.Text(')'), + ]) + + return node_list, messages + + +def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:mpltype:`` for custom matplotlib types. + + In Matplotlib, there are a number of type-like concepts that do not have a + direct type representation; example: color. This role allows to properly + highlight them in the docs and link to their definition. + + Currently supported values: + + - :code:`:mpltype:`color`` will render as: :mpltype:`color` + + """ + mpltype = text + type_to_link_target = { + 'color': 'colors_def', + } + if mpltype not in type_to_link_target: + raise ValueError(f"Unknown mpltype: {mpltype!r}") + + node_list, messages = inliner.interpreted( + mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) + return node_list, messages + + +def setup(app): + app.add_role("rc", _rcparam_role) + app.add_role("mpltype", _mpltype_role) + app.add_node( + _QueryReference, + html=(_visit_query_reference_node, _depart_query_reference_node), + latex=(_visit_query_reference_node, _depart_query_reference_node), + text=(_visit_query_reference_node, _depart_query_reference_node), + ) + return {"version": matplotlib.__version__, + "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/pyproject.toml b/pyproject.toml index a9fb7df68450..52bbe308c0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -283,11 +283,11 @@ ignore_directives = [ "include" ] ignore_roles = [ - # sphinxext.custom_roles - "rc", # matplotlib.sphinxext.mathmpl "mathmpl", "math-stix", + # matplotlib.sphinxext.roles + "rc", # sphinxext.github "ghissue", "ghpull", From bbb0d01bfa333746c77c4490164e9e3682fd56b2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 17:03:20 -0400 Subject: [PATCH 1030/1120] Merge pull request #28397 from rcomer/subfigure-stale Backport PR #28397: FIX: stale root Figure when adding/updating subfigures (cherry picked from commit d347c3227f8de8a99aa327390fee619310452a96) --- lib/matplotlib/figure.py | 2 ++ lib/matplotlib/tests/test_figure.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d75ff527a008..0d939190a0a9 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1633,6 +1633,8 @@ def add_subfigure(self, subplotspec, **kwargs): sf = SubFigure(self, subplotspec, **kwargs) self.subfigs += [sf] sf._remove_method = self.subfigs.remove + sf.stale_callback = _stale_figure_callback + self.stale = True return sf def sca(self, a): diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 58aecd3dea8b..6e6daa77062d 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1733,3 +1733,27 @@ def test_warn_colorbar_mismatch(): subfig3_1.colorbar(im3_2) # should not warn with pytest.warns(UserWarning, match="different Figure"): subfig3_1.colorbar(im4_1) + + +def test_subfigure_stale_propagation(): + fig = plt.figure() + + fig.draw_without_rendering() + assert not fig.stale + + sfig1 = fig.subfigures() + assert fig.stale + + fig.draw_without_rendering() + assert not fig.stale + assert not sfig1.stale + + sfig2 = sfig1.subfigures() + assert fig.stale + + fig.draw_without_rendering() + assert not fig.stale + assert not sfig2.stale + + sfig2.stale = True + assert fig.stale From a92824c6ee5dee1132df32b970a42d31846ce596 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 27 Jun 2024 12:04:36 -0500 Subject: [PATCH 1031/1120] Backport PR #28474: Fix typing and docs for containers --- lib/matplotlib/axes/_axes.py | 6 +++--- lib/matplotlib/container.py | 20 ++++++++++---------- lib/matplotlib/container.pyi | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index fdafc2dcb0bc..ffeecdcbd029 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3527,11 +3527,11 @@ def errorbar(self, x, y, yerr=None, xerr=None, `.ErrorbarContainer` The container contains: - - plotline: `~matplotlib.lines.Line2D` instance of x, y plot markers + - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers and/or line. - - caplines: A tuple of `~matplotlib.lines.Line2D` instances of the error + - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error bar caps. - - barlinecols: A tuple of `.LineCollection` with the horizontal and + - barlinecols : A tuple of `.LineCollection` with the horizontal and vertical error ranges. Other Parameters diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 0f082e298afc..b6dd43724f34 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -87,12 +87,12 @@ class ErrorbarContainer(Container): lines : tuple Tuple of ``(data_line, caplines, barlinecols)``. - - data_line : :class:`~matplotlib.lines.Line2D` instance of - x, y plot markers and/or line. - - caplines : tuple of :class:`~matplotlib.lines.Line2D` instances of - the error bar caps. - - barlinecols : list of :class:`~matplotlib.collections.LineCollection` - with the horizontal and vertical error ranges. + - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers + and/or line. + - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error + bar caps. + - barlinecols : A tuple of `~matplotlib.collections.LineCollection` with the + horizontal and vertical error ranges. has_xerr, has_yerr : bool ``True`` if the errorbar has x/y errors. @@ -115,13 +115,13 @@ class StemContainer(Container): Attributes ---------- - markerline : :class:`~matplotlib.lines.Line2D` + markerline : `~matplotlib.lines.Line2D` The artist of the markers at the stem heads. - stemlines : list of :class:`~matplotlib.lines.Line2D` + stemlines : `~matplotlib.collections.LineCollection` The artists of the vertical lines for all stems. - baseline : :class:`~matplotlib.lines.Line2D` + baseline : `~matplotlib.lines.Line2D` The artist of the horizontal baseline. """ def __init__(self, markerline_stemlines_baseline, **kwargs): @@ -130,7 +130,7 @@ def __init__(self, markerline_stemlines_baseline, **kwargs): ---------- markerline_stemlines_baseline : tuple Tuple of ``(markerline, stemlines, baseline)``. - ``markerline`` contains the `.LineCollection` of the markers, + ``markerline`` contains the `.Line2D` of the markers, ``stemlines`` is a `.LineCollection` of the main lines, ``baseline`` is the `.Line2D` of the baseline. """ diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi index 9cc2e1ac2acc..c66e7ba4b4c3 100644 --- a/lib/matplotlib/container.pyi +++ b/lib/matplotlib/container.pyi @@ -34,12 +34,12 @@ class BarContainer(Container): ) -> None: ... class ErrorbarContainer(Container): - lines: tuple[Line2D, Line2D, LineCollection] + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]] has_xerr: bool has_yerr: bool def __init__( self, - lines: tuple[Line2D, Line2D, LineCollection], + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]], has_xerr: bool = ..., has_yerr: bool = ..., **kwargs From b7423af305df3dd0ad55e56d38e751dbed02db60 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 27 Jun 2024 18:13:23 -0400 Subject: [PATCH 1032/1120] Backport PR #28473: Do not lowercase module:// backends --- lib/matplotlib/backends/registry.py | 12 +++++++++--- lib/matplotlib/tests/test_backend_registry.py | 9 +++++++++ lib/matplotlib/tests/test_backend_template.py | 11 +++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 47d5f65e350e..e08817bb089b 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -93,6 +93,9 @@ def __init__(self): } def _backend_module_name(self, backend): + if backend.startswith("module://"): + return backend[9:] + # Return name of module containing the specified backend. # Does not check if the backend is valid, use is_valid_backend for that. backend = backend.lower() @@ -224,7 +227,8 @@ def is_valid_backend(self, backend): bool True if backend is valid, False otherwise. """ - backend = backend.lower() + if not backend.startswith("module://"): + backend = backend.lower() # For backward compatibility, convert ipympl and matplotlib-inline long # module:// names to their shortened forms. @@ -342,7 +346,8 @@ def resolve_backend(self, backend): The GUI framework, which will be None for a backend that is non-interactive. """ if isinstance(backend, str): - backend = backend.lower() + if not backend.startswith("module://"): + backend = backend.lower() else: # Might be _auto_backend_sentinel or None # Use whatever is already running... from matplotlib import get_backend @@ -395,7 +400,8 @@ def resolve_gui_or_backend(self, gui_or_backend): framework : str or None The GUI framework, which will be None for a backend that is non-interactive. """ - gui_or_backend = gui_or_backend.lower() + if not gui_or_backend.startswith("module://"): + gui_or_backend = gui_or_backend.lower() # First check if it is a gui loop name. backend = self.backend_for_gui_framework(gui_or_backend) diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 141ffd69c266..80c2ce4fc51a 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -86,6 +86,15 @@ def test_is_valid_backend(backend, is_valid): assert backend_registry.is_valid_backend(backend) == is_valid +@pytest.mark.parametrize("backend, normalized", [ + ("agg", "matplotlib.backends.backend_agg"), + ("QtAgg", "matplotlib.backends.backend_qtagg"), + ("module://Anything", "Anything"), +]) +def test_backend_normalization(backend, normalized): + assert backend_registry._backend_module_name(backend) == normalized + + def test_deprecated_rcsetup_attributes(): match = "was deprecated in Matplotlib 3.9" with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): diff --git a/lib/matplotlib/tests/test_backend_template.py b/lib/matplotlib/tests/test_backend_template.py index d7e2a5cd1266..964d15c1559a 100644 --- a/lib/matplotlib/tests/test_backend_template.py +++ b/lib/matplotlib/tests/test_backend_template.py @@ -49,3 +49,14 @@ def test_show_old_global_api(monkeypatch): mpl.use("module://mpl_test_backend") plt.show() mock_show.assert_called_with() + + +def test_load_case_sensitive(monkeypatch): + mpl_test_backend = SimpleNamespace(**vars(backend_template)) + mock_show = MagicMock() + monkeypatch.setattr( + mpl_test_backend.FigureManagerTemplate, "pyplot_show", mock_show) + monkeypatch.setitem(sys.modules, "mpl_Test_Backend", mpl_test_backend) + mpl.use("module://mpl_Test_Backend") + plt.show() + mock_show.assert_called_with() From c7997bed82e43e04cc9d67a97e78bc70646004e7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 27 Jun 2024 18:58:16 -0400 Subject: [PATCH 1033/1120] Backport PR #28393: Make sticky edges only apply if the sticky edge is the most extreme limit point --- lib/matplotlib/axes/_base.py | 6 ++++++ .../test_axes/sticky_tolerance.png | Bin 0 -> 3941 bytes lib/matplotlib/tests/test_axes.py | 19 ++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 96e497a3316b..f83999436cbb 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2969,9 +2969,15 @@ def handle_single_axis( # Index of largest element < x0 + tol, if any. i0 = stickies.searchsorted(x0 + tol) - 1 x0bound = stickies[i0] if i0 != -1 else None + # Ensure the boundary acts only if the sticky is the extreme value + if x0bound is not None and x0bound > x0: + x0bound = None # Index of smallest element > x1 - tol, if any. i1 = stickies.searchsorted(x1 - tol) x1bound = stickies[i1] if i1 != len(stickies) else None + # Ensure the boundary acts only if the sticky is the extreme value + if x1bound is not None and x1bound < x1: + x1bound = None # Add the margin in figure space and then transform back, to handle # non-linear scales. diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png new file mode 100644 index 0000000000000000000000000000000000000000..a3fb13d0716aed8de5596a838828e103d7b23d5d GIT binary patch literal 3941 zcmd^?Yfw{X8pq#+G@?b&I#o(BI3jfwxf2@-0W7v44B*0c!GwDWS4kp-01-&oZNV*< z>^dwgh6R}&p=y&N8W2M+6*Wpk>Sj%(5E9BIg-8g5G$0A=LEEm~wS3W;b!R`EIWzB^ z_ni0nKhN{~Kj#leBZ4n=kKJ=Ty z#u>#SqNDuH^83N>i!9)$2)$m12&u5aVC2YA%(Uzjzs0J9c|b&lH4c8azb0#Un(9yd341y}2||J3A}M;$~s3Z}^~?6J$(nMMXtNf>JwhS?^b0)D-z;b91xA zO=yY|ENgvv7Iv|H+NXdhUQNb)yUf0`p`X~EO0aM>yl0r=`UDK%}^#muXPX1>AFqtvtzq}d8otv)aWuyh14aNRNxFx=r=z^*Fr zB1?I48UP8#u{H-B6Z8Z{toLev)_4Hm`Yqyx5B)@dVJ-@&_$+nzU41bP8y?-8 z2^s-BPC@K)j+g(HBbdcA5ua>hfS67NQG%&y%eF0-Il@N$;K-{Txa{-XPLze}#>Q8# zyPnkgU47hSkw3qw_57$e0Q9PDBCkhO-rw^TQ`5@=LkIxr+ZgE&iCqMHu@r3;r1=v7 z_a?XM-pXR1lYK+80*p$`i&x73sKQ#D4FIzvVvYgf<6pzx8;jVx9yJ{-^2&>vBz^;J za;Z1E5eZbxt&p}e)QPF7^9I{1O-Ihm)ZJ7FIwcei2(!vTf`<>eo^ zFgDj<3&4&O@Rx(>wE%P@{m+&Fe2Z$D%8M_e(Xy~emlAt?Bv`%an8a`%dZq>#WT<^P zU&wG-Y_=F+o_8D}BDZX8E;e+oA>r4PA8R(|7Js!oJ;685C&X>yS$u<=))t1rb?}Pb zmfEOLrLlT%S;2SF#+&p7h_jsK`2gh^1*|JMlQ-tVuDP>xvPG>{cPM8iW^>6g_HWtZ zqCBbhp9QlLcPql>!7C{B{^Dv0)ej{ZCI8CC#)iO8xTnl08sLp#=|a8^?W&9_5aV;< zg=Gd>Jj%fV&peUO>Yt2vWvq^8$8nri7Mj|9M>NJE`aIfo=nd%EzPky4k@S>$;~#Nl z^P{~L1!iIUJo@@j=whiJJ&#&++5&-bU5>ptHGhe~P{$0C!K|CsL<~A z6B}At_<5?_n8bsuYA5T>E;Ak%eoPL*e-Q~R-}z6Nk99H1^d*mQk-CdZ4{7k*RA-** zrudQpQ91gOu>6vI>0b&Lus6h>ezrklp-R1qj(Flcq4NB;i?hoR*k4a709_w~rk{1_ zc99kktAj;9+7IK?OTG;J*ee+78f#re#P{#)T1T3+ zd_NSET@m})sxi7)X@rcUWAM0L1hSzubAP;CVLJJ>mNC>(t2hx3VXox|hHSs}RKInP zpnuc6M52?h*Xc`hQX6(KBZI!AV|H-pS=jP;PzIUuD>=7Latt8Mb0Duly|h3((^@VC zVaZSl-P)*n3nJ~*H7&(ZqNMFff<3ZU$t*=)$Ge6+YsRMR%3BN}BKX6dBKDl+q7_#b zm7Rk{w!2d|kh5RGQ2)Y{S?qb57eKrn(7b{>$O_kZFam#xkc%#=*CAjd>Nk>3}3i5)eP-q~Y+w;enHDA!+m~%HvSC-UKpDAE$mz;v0a}PXx z=xz%pQ{4B5}UkFyCYznA7K!iRhD6lY~(SD(2^Q9r_Rg0|H*@PC@JZ7;}h{>6(&A(ss9_R;pw z=Fwf%D8W7cQi*mzGtAQeS1shF^Yb}ww+inL6?=JNOwQ0xEjaQ)#9_|+Nx%Ci*GT0M literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0ed5a11c1398..ff4d698fbb6e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -682,6 +682,25 @@ def test_sticky_shared_axes(fig_test, fig_ref): ax0.pcolormesh(Z) +@image_comparison(['sticky_tolerance.png'], remove_text=True, style="mpl20") +def test_sticky_tolerance(): + fig, axs = plt.subplots(2, 2) + + width = .1 + + axs.flat[0].bar(x=0, height=width, bottom=20000.6) + axs.flat[0].bar(x=1, height=width, bottom=20000.1) + + axs.flat[1].bar(x=0, height=-width, bottom=20000.6) + axs.flat[1].bar(x=1, height=-width, bottom=20000.1) + + axs.flat[2].barh(y=0, width=-width, left=-20000.6) + axs.flat[2].barh(y=1, width=-width, left=-20000.1) + + axs.flat[3].barh(y=0, width=width, left=-20000.6) + axs.flat[3].barh(y=1, width=width, left=-20000.1) + + def test_nargs_stem(): with pytest.raises(TypeError, match='0 were given'): # stem() takes 1-3 arguments. From b7d25c4778e6e87874b8d8cc3a0c1af52992c124 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 29 Jun 2024 14:50:57 -0400 Subject: [PATCH 1034/1120] Backport PR #28486: Fix CompositeGenericTransform.contains_branch_seperately --- lib/matplotlib/tests/test_transforms.py | 7 +++++++ lib/matplotlib/transforms.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 959814de82db..3d12b90d5210 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -667,6 +667,13 @@ def test_contains_branch(self): assert not self.stack1.contains_branch(self.tn1 + self.ta2) + blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2) + x, y = blend.contains_branch_seperately(self.stack2_subset) + stack_blend = self.tn3 + blend + sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset) + assert x is sx is False + assert y is sy is True + def test_affine_simplification(self): # tests that a transform stack only calls as much is absolutely # necessary "non-affine" allowing the best possible optimization with diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 5003e2113930..3575bd1fc14d 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -1423,7 +1423,7 @@ def contains_branch_seperately(self, other_transform): 'transforms with 2 output dimensions') # for a non-blended transform each separate dimension is the same, so # just return the appropriate shape. - return [self.contains_branch(other_transform)] * 2 + return (self.contains_branch(other_transform), ) * 2 def __sub__(self, other): """ @@ -2404,6 +2404,15 @@ def _iter_break_from_left_to_right(self): for left, right in self._b._iter_break_from_left_to_right(): yield self._a + left, right + def contains_branch_seperately(self, other_transform): + # docstring inherited + if self.output_dims != 2: + raise ValueError('contains_branch_seperately only supports ' + 'transforms with 2 output dimensions') + if self == other_transform: + return (True, True) + return self._b.contains_branch_seperately(other_transform) + depth = property(lambda self: self._a.depth + self._b.depth) is_affine = property(lambda self: self._a.is_affine and self._b.is_affine) is_separable = property( From 475d5b70daa5f1b0e66dc89c9cba857d6930ea61 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 29 Jun 2024 14:52:32 -0400 Subject: [PATCH 1035/1120] Backport PR #28487: Fix autoscaling with axhspan --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/tests/test_axes.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index ffeecdcbd029..040c5a4ba4e9 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1028,7 +1028,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): # For Rectangles and non-separable transforms, add_patch can be buggy # and update the x limits even though it shouldn't do so for an # yaxis_transformed patch, so undo that update. - ix = self.dataLim.intervalx + ix = self.dataLim.intervalx.copy() mx = self.dataLim.minposx self.add_patch(p) self.dataLim.intervalx = ix diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ff4d698fbb6e..3c0407ee4098 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8195,10 +8195,10 @@ def test_relative_ticklabel_sizes(size): def test_multiplot_autoscale(): fig = plt.figure() ax1, ax2 = fig.subplots(2, 1, sharex='all') - ax1.scatter([1, 2, 3, 4], [2, 3, 2, 3]) + ax1.plot([18000, 18250, 18500, 18750], [2, 3, 2, 3]) ax2.axhspan(-5, 5) xlim = ax1.get_xlim() - assert np.allclose(xlim, [0.5, 4.5]) + assert np.allclose(xlim, [18000, 18800]) def test_sharing_does_not_link_positions(): From 5552302c3fef08f8e29b7f4d1340a723df63962a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Jul 2024 13:14:34 -0400 Subject: [PATCH 1036/1120] Backport PR #28498: Don't fail if we can't query system fonts on macOS --- lib/matplotlib/font_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 312e8ee97246..813bee6eb623 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -266,8 +266,11 @@ def _get_fontconfig_fonts(): @lru_cache def _get_macos_fonts(): """Cache and list the font paths known to ``system_profiler SPFontsDataType``.""" - d, = plistlib.loads( - subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + try: + d, = plistlib.loads( + subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + except (OSError, subprocess.CalledProcessError, plistlib.InvalidFileException): + return [] return [Path(entry["path"]) for entry in d["_items"]] From c43313a77ccb165b7b26de7e9b3cd9b9c1b50700 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 3 Jul 2024 15:08:51 -0500 Subject: [PATCH 1037/1120] Backport PR #28451: Fix GTK cairo backends --- lib/matplotlib/backends/backend_gtk3cairo.py | 16 ++++++++++------ lib/matplotlib/backends/backend_gtk4.py | 10 ++-------- lib/matplotlib/backends/backend_gtk4cairo.py | 9 ++++----- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 24a26111f062..371b8dc1b31f 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -13,15 +13,19 @@ def on_draw_event(self, widget, ctx): with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar else nullcontext()): - self._renderer.set_context(ctx) - scale = self.device_pixel_ratio - # Scale physical drawing to logical size. - ctx.scale(1 / scale, 1 / scale) allocation = self.get_allocation() + # Render the background before scaling, as the allocated size here is in + # logical pixels. Gtk.render_background( self.get_style_context(), ctx, - allocation.x, allocation.y, - allocation.width, allocation.height) + 0, 0, allocation.width, allocation.height) + scale = self.device_pixel_ratio + # Scale physical drawing to logical size. + ctx.scale(1 / scale, 1 / scale) + self._renderer.set_context(ctx) + # Set renderer to physical size so it renders in full resolution. + self._renderer.width = allocation.width * scale + self._renderer.height = allocation.height * scale self._renderer.dpi = self.figure.dpi self.figure.draw(self._renderer) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 256a8ec9e864..dd86ab628ce7 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -34,7 +34,6 @@ class FigureCanvasGTK4(_FigureCanvasGTK, Gtk.DrawingArea): required_interactive_framework = "gtk4" supports_blit = False manager_class = _api.classproperty(lambda cls: FigureManagerGTK4) - _context_is_scaled = False def __init__(self, figure=None): super().__init__(figure=figure) @@ -228,13 +227,8 @@ def _post_draw(self, widget, ctx): lw = 1 dash = 3 - if not self._context_is_scaled: - x0, y0, w, h = (dim / self.device_pixel_ratio - for dim in self._rubberband_rect) - else: - x0, y0, w, h = self._rubberband_rect - lw *= self.device_pixel_ratio - dash *= self.device_pixel_ratio + x0, y0, w, h = (dim / self.device_pixel_ratio + for dim in self._rubberband_rect) x1 = x0 + w y1 = y0 + h diff --git a/lib/matplotlib/backends/backend_gtk4cairo.py b/lib/matplotlib/backends/backend_gtk4cairo.py index b1d543704351..838ea03fcce6 100644 --- a/lib/matplotlib/backends/backend_gtk4cairo.py +++ b/lib/matplotlib/backends/backend_gtk4cairo.py @@ -5,7 +5,10 @@ class FigureCanvasGTK4Cairo(FigureCanvasCairo, FigureCanvasGTK4): - _context_is_scaled = True + def _set_device_pixel_ratio(self, ratio): + # Cairo in GTK4 always uses logical pixels, so we don't need to do anything for + # changes to the device pixel ratio. + return False def on_draw_event(self, widget, ctx): if self._idle_draw_id: @@ -16,15 +19,11 @@ def on_draw_event(self, widget, ctx): with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar else nullcontext()): self._renderer.set_context(ctx) - scale = self.device_pixel_ratio - # Scale physical drawing to logical size. - ctx.scale(1 / scale, 1 / scale) allocation = self.get_allocation() Gtk.render_background( self.get_style_context(), ctx, allocation.x, allocation.y, allocation.width, allocation.height) - self._renderer.dpi = self.figure.dpi self.figure.draw(self._renderer) From 06189c2b237e9dc4e0de456b80e8ae098cdde4ae Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Jul 2024 20:15:51 -0400 Subject: [PATCH 1038/1120] Backport PR #28430: Fix pickling of AxesWidgets. --- lib/matplotlib/testing/__init__.py | 21 ++++++++++ lib/matplotlib/testing/__init__.pyi | 1 + .../tests/test_backends_interactive.py | 23 +---------- lib/matplotlib/tests/test_pickle.py | 24 ++++++++++- lib/matplotlib/widgets.py | 40 ++++++------------- lib/matplotlib/widgets.pyi | 12 ++++-- 6 files changed, 67 insertions(+), 54 deletions(-) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 8e60267ed608..19113d399626 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -211,3 +211,24 @@ def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backe ) assert proc.stdout.strip().endswith(f"'{expected_backend}'") + + +def is_ci_environment(): + # Common CI variables + ci_environment_variables = [ + 'CI', # Generic CI environment variable + 'CONTINUOUS_INTEGRATION', # Generic CI environment variable + 'TRAVIS', # Travis CI + 'CIRCLECI', # CircleCI + 'JENKINS', # Jenkins + 'GITLAB_CI', # GitLab CI + 'GITHUB_ACTIONS', # GitHub Actions + 'TEAMCITY_VERSION' # TeamCity + # Add other CI environment variables as needed + ] + + for env_var in ci_environment_variables: + if os.getenv(env_var): + return True + + return False diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi index 1f52a8ccb8ee..6917b6a5a380 100644 --- a/lib/matplotlib/testing/__init__.pyi +++ b/lib/matplotlib/testing/__init__.pyi @@ -51,3 +51,4 @@ def ipython_in_subprocess( requested_backend_or_gui_framework: str, all_expected_backends: dict[tuple[int, int], str], ) -> None: ... +def is_ci_environment() -> bool: ... diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6830e7d5c845..d624b5db0ac2 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -19,7 +19,7 @@ import matplotlib as mpl from matplotlib import _c_internal_utils from matplotlib.backend_tools import ToolToggleBase -from matplotlib.testing import subprocess_run_helper as _run_helper +from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment class _WaitForStringPopen(subprocess.Popen): @@ -110,27 +110,6 @@ def _get_testable_interactive_backends(): for env, marks in _get_available_interactive_backends()] -def is_ci_environment(): - # Common CI variables - ci_environment_variables = [ - 'CI', # Generic CI environment variable - 'CONTINUOUS_INTEGRATION', # Generic CI environment variable - 'TRAVIS', # Travis CI - 'CIRCLECI', # CircleCI - 'JENKINS', # Jenkins - 'GITLAB_CI', # GitLab CI - 'GITHUB_ACTIONS', # GitHub Actions - 'TEAMCITY_VERSION' # TeamCity - # Add other CI environment variables as needed - ] - - for env_var in ci_environment_variables: - if os.getenv(env_var): - return True - - return False - - # Reasonable safe values for slower CI/Remote and local architectures. _test_timeout = 120 if is_ci_environment() else 20 diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 0cba4f392035..1474a67d28aa 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -1,5 +1,7 @@ from io import BytesIO import ast +import os +import sys import pickle import pickletools @@ -8,7 +10,7 @@ import matplotlib as mpl from matplotlib import cm -from matplotlib.testing import subprocess_run_helper +from matplotlib.testing import subprocess_run_helper, is_ci_environment from matplotlib.testing.decorators import check_figures_equal from matplotlib.dates import rrulewrapper from matplotlib.lines import VertexSelector @@ -307,3 +309,23 @@ def test_cycler(): ax = pickle.loads(pickle.dumps(ax)) l, = ax.plot([3, 4]) assert l.get_color() == "m" + + +# Run under an interactive backend to test that we don't try to pickle the +# (interactive and non-picklable) canvas. +def _test_axeswidget_interactive(): + ax = plt.figure().add_subplot() + pickle.dumps(mpl.widgets.Button(ax, "button")) + + +@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649 + ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and + sys.platform == 'darwin' and sys.version_info[:2] < (3, 11), + reason='Tk version mismatch on Azure macOS CI' + ) +def test_axeswidget_interactive(): + subprocess_run_helper( + _test_axeswidget_interactive, + timeout=120 if is_ci_environment() else 20, + extra_env={'MPLBACKEND': 'tkagg'} + ) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index ed130e6854f2..a298f3ae3d6a 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -90,22 +90,6 @@ def ignore(self, event): """ return not self.active - def _changed_canvas(self): - """ - Someone has switched the canvas on us! - - This happens if `savefig` needs to save to a format the previous - backend did not support (e.g. saving a figure using an Agg based - backend saved to a vector format). - - Returns - ------- - bool - True if the canvas has been changed. - - """ - return self.canvas is not self.ax.figure.canvas - class AxesWidget(Widget): """ @@ -131,9 +115,10 @@ class AxesWidget(Widget): def __init__(self, ax): self.ax = ax - self.canvas = ax.figure.canvas self._cids = [] + canvas = property(lambda self: self.ax.figure.canvas) + def connect_event(self, event, callback): """ Connect a callback function with an event. @@ -1100,7 +1085,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) @@ -1677,7 +1662,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) @@ -1933,7 +1918,7 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, def clear(self, event): """Internal event handler to clear the cursor.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) @@ -2573,9 +2558,7 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, self.drag_from_anywhere = drag_from_anywhere self.ignore_event_outside = ignore_event_outside - # Reset canvas so that `new_axes` connects events. - self.canvas = None - self.new_axes(ax, _props=props) + self.new_axes(ax, _props=props, _init=True) # Setup handles self._handle_props = { @@ -2588,14 +2571,15 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, self._active_handle = None - def new_axes(self, ax, *, _props=None): + def new_axes(self, ax, *, _props=None, _init=False): """Set SpanSelector to operate on a new Axes.""" - self.ax = ax - if self.canvas is not ax.figure.canvas: + reconnect = False + if _init or self.canvas is not ax.figure.canvas: if self.canvas is not None: self.disconnect_events() - - self.canvas = ax.figure.canvas + reconnect = True + self.ax = ax + if reconnect: self.connect_default_events() # Reset diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index c85ad2158ee7..58adf85aae60 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -33,8 +33,9 @@ class Widget: class AxesWidget(Widget): ax: Axes - canvas: FigureCanvasBase | None def __init__(self, ax: Axes) -> None: ... + @property + def canvas(self) -> FigureCanvasBase | None: ... def connect_event(self, event: Event, callback: Callable) -> None: ... def disconnect_events(self) -> None: ... @@ -310,7 +311,6 @@ class SpanSelector(_SelectorWidget): grab_range: float drag_from_anywhere: bool ignore_event_outside: bool - canvas: FigureCanvasBase | None def __init__( self, ax: Axes, @@ -330,7 +330,13 @@ class SpanSelector(_SelectorWidget): ignore_event_outside: bool = ..., snap_values: ArrayLike | None = ..., ) -> None: ... - def new_axes(self, ax: Axes, *, _props: dict[str, Any] | None = ...) -> None: ... + def new_axes( + self, + ax: Axes, + *, + _props: dict[str, Any] | None = ..., + _init: bool = ..., + ) -> None: ... def connect_default_events(self) -> None: ... @property def direction(self) -> Literal["horizontal", "vertical"]: ... From 53254253095b40b76336afe583820245bfb8a9db Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 01:43:44 -0400 Subject: [PATCH 1039/1120] DOC: Create release notes for 3.9.1 --- .../api_changes_3.9.1.rst} | 6 + doc/users/github_stats.rst | 876 ++++-------------- .../prev_whats_new/github_stats_3.9.0.rst | 744 +++++++++++++++ doc/users/release_notes.rst | 2 + 4 files changed, 914 insertions(+), 714 deletions(-) rename doc/api/{next_api_changes/development/28289-ES.rst => prev_api_changes/api_changes_3.9.1.rst} (86%) create mode 100644 doc/users/prev_whats_new/github_stats_3.9.0.rst diff --git a/doc/api/next_api_changes/development/28289-ES.rst b/doc/api/prev_api_changes/api_changes_3.9.1.rst similarity index 86% rename from doc/api/next_api_changes/development/28289-ES.rst rename to doc/api/prev_api_changes/api_changes_3.9.1.rst index f891c63a64bf..4a9a1fc6669c 100644 --- a/doc/api/next_api_changes/development/28289-ES.rst +++ b/doc/api/prev_api_changes/api_changes_3.9.1.rst @@ -1,3 +1,9 @@ +API Changes for 3.9.1 +===================== + +Development +----------- + Documentation-specific custom Sphinx roles are now semi-public ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 629d9319fc57..0c8f29687afb 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,747 +1,195 @@ .. _github-stats: -GitHub statistics for 3.9.0 (May 15, 2024) +GitHub statistics for 3.9.1 (Jul 04, 2024) ========================================== -GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/05/15 +GitHub statistics for 2024/05/15 (tag: v3.9.0) - 2024/07/04 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 97 issues and merged 450 pull requests. -The full list can be seen `on GitHub `__ +We closed 30 issues and merged 111 pull requests. +The full list can be seen `on GitHub `__ -The following 175 authors contributed 2584 commits. +The following 29 authors contributed 184 commits. -* 0taj -* Abdul Razak Taha -* Adam J. Stewart -* Adam Turner -* Aditi Gautam -* agautam478 -* Alan Lau -* Albert Y. Shih -* Alec Vercruysse -* Alexander Volkov -* Alice Descoeudres -* Allan Haldane -* Amirreza Aflakparast -* Ananya Devarakonda -* ananya314 -* Anja Beck -* Anjini2004 -* Ant Lockyer * Antony Lee -* Anvi Verma -* Artyom Romanov -* Augusto Borges -* avramid9 -* Ben Root -* bersbersbers -* Binaya Sharma -* Cameron -* Chaoyi Hu -* chaoyihu -* Chiraag Balu -* Christoph Hasse -* ConstableCatnip -* CozyFrog -* Cyril Gadal -* Dale Dai -* Daniel Bergman -* Daniel Hitchcock -* danielcobej -* David Gilbertson -* David Stansby -* ddale1128@gmail.com +* Brigitta Sipőcz +* Christian Mattsson +* dale * dependabot[bot] -* Devilsaint -* dohyun -* Drew Kinneer -* DWesl -* Elisa Heckelmann -* ElisaHeck * Elliott Sales de Andrade -* Eric Firing -* Eric Prestat -* esibinga -* Eva Sibinga -* Evgenii Radchenko -* Faisal Fawad -* Felipe Cybis Pereira -* Garrett Sward -* Gaurav-Kumar-Soni -* Gauri Chaudhari -* Gautam Sagar +* Eytan Adler * Greg Lucas -* Gurudatta Shanbhag +* haaris * hannah -* Haoying Zhang -* Hugues Hoppe -* i-jey -* iamfaham -* Ian Hunt-Isaak * Ian Thomas -* ifEricReturnTrue * Illviljan -* Issam -* Issam Arabi -* Jacob Stevens-Haas -* Jacob Tomlinson -* Jake -* Jake Stevens-Haas -* James Salsman -* Jaroza727 -* Jeremy Farrell -* Jirka -* Jody Klymak -* Jorge Moraleda -* Joshua Stevenson -* jovianw -* João Andrade -* jpgianfaldoni -* jsdodge -* jsjeelshah -* judfs -* Juhan Oskar Hennoste -* Junpei Ota -* Katherine Turk -* katotaisei -* KheshavKumar -* Koustav Ghosh -* Kritika Verma +* K900 * Kyle Sunden -* Linyi Li -* linyilily -* lkkmpn -* Lucia Korpas -* madisonwong210 -* Maggie Liu -* Marc Bresson +* Lumberbot (aka Jack) +* malhar2460 * Matthew Feickert -* Matthew Morrison -* Matthias Bussonnier * Melissa Weber Mendonça -* melissawm -* mliu08 -* Mostafa Noah -* MostafaNouh0011 -* n-aswin -* Nabil -* nbarlowATI -* Nidaa Rabah -* Nivedita Chaudhari +* MischaMegens2 * Oscar Gustafsson -* patel-zeel -* Pavel Liavonau -* Pedro -* Pedro Peçanha -* Peter Talley -* Pradeep Reddy Raamana -* Prajwal Agrawal -* Pranav Raghu -* prateetishah -* pre-commit-ci[bot] -* QuadroTec -* Rafael Tsuha -* Raghuram Sirigiri -* Raphael -* Raphael Quast -* Ratnabali Dutta -* rawwash -* rsp2210 -* Ruoyi -* Ruoyi Xie -* Rushikesh Pandya * Ruth Comer -* samGreer -* Samuel Diebolt -* saranti * Scott Shambaugh -* Sebastian Berg -* Seohyeon Lee -* Sheepfan0828 -* ShivamPathak99 -* Shriya Kalakata -* shriyakalakata -* Stefan -* Steffen Rehberg -* stevezhang1999 -* Sudhanshu Pandey -* Talha Irfan -* thehappycheese +* simond07 +* SjoerdB93 +* Takumasa N +* Takumasa N. +* Takumasa Nakamura * Thomas A Caswell -* Tiago Lubiana * Tim Hoffmann -* tobias -* Tom Sarantis -* trananso -* turnipseason -* tusharkulkarni008 -* UFEddy -* Vashesh08 -* vicky6 -* vigneshvetrivel8 -* wemi3 -* yangyangdotcom -* YiLun Fan -* Zach Champion -* zachjweiner -* zoehcycy GitHub issues and pull requests: -Pull Requests (450): +Pull Requests (111): -* :ghpull:`28206`: Backport PR #28205 on branch v3.9.x (TST: Fix tests with older versions of ipython) -* :ghpull:`28207`: TST: Followup corrections to #28205 -* :ghpull:`28205`: TST: Fix tests with older versions of ipython -* :ghpull:`28203`: Backport PR #28164 on branch v3.9.x (CI: Ensure code coverage is always uploaded) -* :ghpull:`28204`: Backport PR #28195 on branch v3.9.x (TST: Prepare for pytest 9) -* :ghpull:`28191`: DOC: Use released mpl-sphinx-theme on v3.9.x -* :ghpull:`28195`: TST: Prepare for pytest 9 -* :ghpull:`28193`: Backport PR #28185 on branch v3.9.x (DOC: Bump mpl-sphinx-theme to 3.9) -* :ghpull:`28190`: Backport PR #28103 on branch v3.9.x ([DOC]: Fix compatibility with sphinx-gallery 0.16) -* :ghpull:`28164`: CI: Ensure code coverage is always uploaded -* :ghpull:`28194`: Backport PR #28188 on branch v3.9.x ([TST] Bump some tolerances for Macos ARM) -* :ghpull:`28188`: [TST] Bump some tolerances for Macos ARM -* :ghpull:`28185`: DOC: Bump mpl-sphinx-theme to 3.9 -* :ghpull:`28189`: Backport PR #28181 on branch v3.9.x (DOC: Prepare release notes for 3.9) -* :ghpull:`28103`: [DOC]: Fix compatibility with sphinx-gallery 0.16 -* :ghpull:`28181`: DOC: Prepare release notes for 3.9 -* :ghpull:`28184`: Backport PR #28182 on branch v3.9.x (Bump custom hatch deprecation expiration) -* :ghpull:`28182`: Bump custom hatch deprecation expiration -* :ghpull:`28178`: Backport PR #28171 on branch v3.9.x (Support removing absent tools from ToolContainerBase.) -* :ghpull:`28171`: Support removing absent tools from ToolContainerBase. -* :ghpull:`28174`: Backport PR #28169 on branch v3.9.x (Clarify public-ness of some ToolContainerBase APIs.) -* :ghpull:`28169`: Clarify public-ness of some ToolContainerBase APIs. -* :ghpull:`28160`: Backport PR #28039 on branch v3.9.x (Respect vertical_axis when rotating plot interactively) -* :ghpull:`28159`: Backport PR #28157 on branch v3.9.x (Remove call to non-existent method _default_contains in Artist) -* :ghpull:`28162`: Backport PR #27948 on branch v3.9.x (Move IPython backend mapping to Matplotlib and support entry points) -* :ghpull:`28163`: Backport PR #28144 on branch v3.9.x (DOC: Refactor code in the fishbone diagram example) -* :ghpull:`28144`: DOC: Refactor code in the fishbone diagram example -* :ghpull:`27948`: Move IPython backend mapping to Matplotlib and support entry points -* :ghpull:`28039`: Respect vertical_axis when rotating plot interactively -* :ghpull:`28157`: Remove call to non-existent method _default_contains in Artist -* :ghpull:`28141`: Backport PR #27960 on branch v3.9.x (Update AppVeyor config) -* :ghpull:`28138`: Backport PR #28068 on branch v3.9.x ([TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list``) -* :ghpull:`28140`: Backport PR #28136 on branch v3.9.x (Appease pycodestyle.) -* :ghpull:`27960`: Update AppVeyor config -* :ghpull:`28068`: [TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list`` -* :ghpull:`28136`: Appease pycodestyle. -* :ghpull:`28135`: Backport PR #28134 on branch v3.9.x (DOC: Minor improvements on quickstart) -* :ghpull:`28134`: DOC: Minor improvements on quickstart -* :ghpull:`28121`: Backport PR #28085 on branch v3.9.x (Clarify that the pgf backend is never actually used interactively.) -* :ghpull:`28120`: Backport PR #28102 on branch v3.9.x (Fix typo in color mapping documentation in quick_start.py) -* :ghpull:`28109`: Backport PR #28100 on branch v3.9.x (TST: wxcairo sometimes raises OSError on missing cairo libraries) -* :ghpull:`28100`: TST: wxcairo sometimes raises OSError on missing cairo libraries -* :ghpull:`28108`: Backport PR #28107 on branch v3.9.x ([DOC] Fix description in CapStyle example) -* :ghpull:`28107`: [DOC] Fix description in CapStyle example -* :ghpull:`28102`: Fix typo in color mapping documentation in quick_start.py -* :ghpull:`28095`: Backport PR #28094 on branch v3.9.x (DOC: exclude sphinx 7.3.*) -* :ghpull:`28081`: Backport PR #28078 on branch v3.9.x (Clarify that findfont & _find_fonts_by_props return paths.) -* :ghpull:`28080`: Backport PR #28077 on branch v3.9.x (Parent tk StringVar to the canvas widget, not to the toolbar.) -* :ghpull:`28092`: Backport PR #28032 on branch v3.9.x (FIX: ensure images are C order before passing to pillow) -* :ghpull:`28032`: FIX: ensure images are C order before passing to pillow -* :ghpull:`28088`: Backport PR #28087 on branch v3.9.x (Document Qt5 minimal version.) -* :ghpull:`28085`: Clarify that the pgf backend is never actually used interactively. -* :ghpull:`28078`: Clarify that findfont & _find_fonts_by_props return paths. -* :ghpull:`28077`: Parent tk StringVar to the canvas widget, not to the toolbar. -* :ghpull:`28062`: Backport PR #28056 on branch v3.9.x (Strip trailing spaces from log-formatter cursor output.) -* :ghpull:`28063`: Backport PR #28055 on branch v3.9.x (DOC: Improve inverted axis example) -* :ghpull:`28056`: Strip trailing spaces from log-formatter cursor output. -* :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) -* :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time -* :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) -* :ghpull:`28023`: ci: Update merge conflict labeler -* :ghpull:`28035`: Backport PR #28026 on branch v3.9.x ([DOC] reshuffle of contributing) -* :ghpull:`28026`: [DOC] reshuffle of contributing -* :ghpull:`28024`: DOC: Rewrite "Work on an issue" section -* :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index -* :ghpull:`27747`: Move doc/users/installing/ to doc/install/ -* :ghpull:`27952`: ENH: Align titles -* :ghpull:`28017`: Merge up v3.8.4 -* :ghpull:`28014`: Improve timeline example. -* :ghpull:`28019`: DOC: correct path to mpl_toolkits reference images -* :ghpull:`26981`: Fixes Issue #26377 - Auto-escape % Symbol in Latex in pie labels -* :ghpull:`28007`: wx: Fix file extension for toolmanager-style toolbar -* :ghpull:`25556`: Display cursor coordinates for all axes twinned with the current one. -* :ghpull:`23597`: Always use PyQT/PySide6 for GitHub CI -* :ghpull:`28013`: Avoid plt.xticks/plt.yticks in gallery examples. -* :ghpull:`28006`: Fix deprecation warnings in ft2font extension -* :ghpull:`27723`: ci: Enable testing on M1 macOS -* :ghpull:`26375`: Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` -* :ghpull:`27999`: Remove documentation that some backends don't support hatching. -* :ghpull:`26710`: Add support for High DPI displays to wxAgg backend -* :ghpull:`27148`: Correctly treat pan/zoom events of overlapping axes. -* :ghpull:`27981`: DOC: Fix label type specification in parameter descriptions -* :ghpull:`27979`: Clarify error message for bad-dimensionality in pcolorfast(). -* :ghpull:`27962`: DOC: Document axes_grid1.Grid attributes -* :ghpull:`27968`: MNT: Remove remaining 3.7 deprecations -* :ghpull:`27965`: DOC: Rewrite the example illustrating bxp() -* :ghpull:`26453`: add documentation for reloading font cache -* :ghpull:`26131`: Tst/restore old tests -* :ghpull:`27730`: Add an rcparam for image.interpolation_stage. -* :ghpull:`27956`: Use PyOS_setsig in macos backend -* :ghpull:`27829`: Simplify color/marker disambiguation logic in _process_plot_format. -* :ghpull:`27840`: Add legend support for boxplots -* :ghpull:`27943`: Support Cn, n>9 in plot() shorthand format. -* :ghpull:`27950`: ci: Fix condition for publishing wheels -* :ghpull:`27909`: Add a note to pyplot docstrings referencing the corresponding object methods -* :ghpull:`27929`: DOC: Add summary lines to plot types -* :ghpull:`27915`: [BUG] Fix redirect-from Sphinx extension -* :ghpull:`27945`: DOC: Explain leading dot in object references -* :ghpull:`27947`: Update docs for ``FancyArrowPatch`` & ``Annotation`` to make it clear that ShrinkA/B parameters are in points and not fractional. -* :ghpull:`27944`: Bump the actions group with 2 updates -* :ghpull:`27932`: Fix pickling of make_axes_area_auto_adjustable'd axes. -* :ghpull:`26500`: closes #26477 ENH: Add interpolation_stage in qt figureoptions -* :ghpull:`27927`: Update docs -* :ghpull:`27916`: Revert renaming labels to tick_labels in boxplot_stats() -* :ghpull:`27931`: Highlight development_setup code snippets as bash, not python. -* :ghpull:`27856`: Support hatching in cairo backends. -* :ghpull:`27922`: Fix cbook style -* :ghpull:`27668`: MNT: prevent merging using labels + branch protection rules -* :ghpull:`27857`: Documentation edit for matshow function -* :ghpull:`27928`: DOC: Fix syntax for ToolBase.image docstring -* :ghpull:`27873`: Simplify the LineCollection example -* :ghpull:`27492`: Fix semantics of MEP22 image names. -* :ghpull:`27918`: Fix new flake8 errors from old merge -* :ghpull:`27874`: Modernize macosx backend a bit -* :ghpull:`25887`: Update ``_unpack_to_numpy`` function to convert JAX and PyTorch arrays to NumPy -* :ghpull:`27685`: Work around pyparsing diagnostic warnings -* :ghpull:`26594`: Added optional props argument to Lasso Widget __init__ to customize Lasso line -* :ghpull:`22761`: Add minor ticks on and off in Axis -* :ghpull:`22407`: Add ``set_XY`` and ``set_data`` to ``Quiver`` -* :ghpull:`27901`: Rename boxplot's tick label parameter -* :ghpull:`27883`: Fix build on older macOS deployment targets -* :ghpull:`27900`: Remove empty user guide tutorials page -* :ghpull:`27885`: Clean up headers in extensions -* :ghpull:`27910`: DOC: Fix dead link in README -* :ghpull:`26567`: Use SVG inheritance diagrams now that linking has been fixed -* :ghpull:`27899`: Merge up 3.8.x into main -* :ghpull:`27905`: Improved error message for malformed colors -* :ghpull:`27906`: Override open_group, close_group methods in PathEffectRenderer -* :ghpull:`27904`: FIX: Restore D213 in flake8 -* :ghpull:`27895`: Remove versions from sidebar in docs -* :ghpull:`27894`: Mark triangulation classes as final -* :ghpull:`27557`: Use :mpltype:``color`` for color types -* :ghpull:`27845`: Make sure custom alpha param does not change 'none' colors in a list of colors -* :ghpull:`27719`: Add BackendRegistry singleton class -* :ghpull:`27890`: DOC: State approximate documentation build time -* :ghpull:`27887`: BLD: Add a fallback URL for FreeType -* :ghpull:`25224`: Allow passing a transformation to secondary_xaxis/_yaxis -* :ghpull:`27886`: Fix devdocs version switcher -* :ghpull:`27884`: FIX: don't copy twice on RGB input -* :ghpull:`27087`: Convert path extension to pybind11 -* :ghpull:`27867`: DOC: Update some animation related topics -* :ghpull:`27848`: FIX: handle nans in RGBA input with ScalarMappables -* :ghpull:`27821`: BLD,Cygwin: Include Python.h first in various C++ files -* :ghpull:`27457`: TST: adding tests of current clear behavior on ticks -* :ghpull:`27872`: doc: add description of ``**kwargs`` usage to collections -* :ghpull:`27868`: Use pybind11 string formatter for exception messages -* :ghpull:`27862`: Add dtype/copy args to internal testing class -* :ghpull:`27658`: Bump pydata-sphinx-theme -* :ghpull:`27303`: FIX: also exclude np.nan in RGB(A) in color mapping -* :ghpull:`27860`: Bump the actions group with 2 updates -* :ghpull:`27869`: Correctly set temporary pdf/pgf backends -* :ghpull:`27850`: Deprecate ``plot_date`` -* :ghpull:`27815`: Add side option to violinplot -* :ghpull:`27836`: DOC: use ... for continuation prompt in docstrings -* :ghpull:`27819`: MNT: remove draw method args and kwargs -* :ghpull:`27813`: DOC: Update violinplot() docs -* :ghpull:`27698`: Add linting and validation of all YAML files -* :ghpull:`27811`: Fix Annulus width check -* :ghpull:`27667`: Change return type of ``ion`` and ``ioff`` to fix unbound variable errors with Pyright -* :ghpull:`27807`: Expand CI pytest reporting config to ignore xfails -* :ghpull:`27806`: Remove self._renderer from AnnotationBbox and ConnectionPatch -* :ghpull:`27799`: Clarify that set_ticks() affects major/minor ticks independently -* :ghpull:`27787`: Improve documentation on boxplot and violinplot -* :ghpull:`27800`: Deactivate sidebar for release notes -* :ghpull:`27798`: Fix sphinx-gallery CSS -* :ghpull:`27462`: DOC: clarify the default value of *radius* in Patch.contains_point -* :ghpull:`27565`: MNT: arghandling subplotspec -* :ghpull:`27796`: Make mypy a bit stricter -* :ghpull:`27767`: Update handling of sequence labels for plot -* :ghpull:`27795`: Add EffVer badge -* :ghpull:`27780`: Partly revert #27711 -* :ghpull:`27768`: MNT: deprecate draw method args and kwargs -* :ghpull:`27783`: Update README.md to fix citation link -* :ghpull:`27726`: TST: always set a (long) timeout for subprocess and always use our wrapper -* :ghpull:`27781`: Simplify example: Box plots with custom fill colors -* :ghpull:`27750`: Bump the actions group with 2 updates -* :ghpull:`27771`: Add marker-only and line+marker visuals to the plot() plot types -* :ghpull:`27764`: Increase size of legend in Legend guide example -* :ghpull:`26800`: Bump minimum NumPy version to 1.23 -* :ghpull:`27752`: Update some Meson internals -* :ghpull:`27702`: GOV: adopt EffVer -* :ghpull:`26965`: Removal of deprecated API cm -* :ghpull:`27758`: [Doc] Remove special casing for removed method -* :ghpull:`25815`: [TST] Make jpl units instantiated with datetimes consistent with mpl converters -* :ghpull:`27729`: DOC: Improve colormap normalization example -* :ghpull:`27732`: TST: Remove memory leak test -* :ghpull:`27733`: ci: Simplify CodeQL setup -* :ghpull:`27692`: Add method to update position of arrow patch -* :ghpull:`27736`: Fix incorrect API reference in docs -* :ghpull:`27731`: DOC: Create explicit rename legend entry section in guide -* :ghpull:`27560`: Moved /users/project to /doc/project -* :ghpull:`27728`: Simplify Figure._suplabels. -* :ghpull:`27715`: Bump the actions group with 3 updates -* :ghpull:`27711`: Fix boxplot legend entries part 2 -* :ghpull:`27696`: DOC: clean up automated tests section of workflow docs -* :ghpull:`27686`: Improve Locator docstrings -* :ghpull:`27704`: ci: Remove prerelease conditions from Azure Pipelines -* :ghpull:`27568`: Fix boxplot legend entries -* :ghpull:`27694`: MNT: fix labeller -* :ghpull:`26953`: MNT: test that table doesn't try to convert unitized data -* :ghpull:`27690`: Remove "Past versions" section from release notes -* :ghpull:`26926`: Closes #22011: Changes to SubFigures so it behaves like a regular artist -* :ghpull:`27469`: Fixed legend with legend location "best" when legend overlaps shaded area and text -* :ghpull:`27684`: Bump the actions group with 1 update -* :ghpull:`27665`: Axes.inset_axes - warning message removed -* :ghpull:`27688`: CI: skip code coverage upload on scheduled tests -* :ghpull:`27689`: ci: Don't include API/what's new notes in general doc labels -* :ghpull:`27640`: Add ``get_cursor_data`` to ``NonUniformImage`` -* :ghpull:`27676`: BLD: Downgrade FreeType to 2.6.1 on Windows ARM -* :ghpull:`27619`: Use GH action to install reviewdog -* :ghpull:`27552`: TST: Use importlib for importing in pytest -* :ghpull:`27650`: DOC: Added call out to API guidelines to contribute + small API guidelines reorg -* :ghpull:`27618`: Add option of running stubtest using tox -* :ghpull:`27656`: Bump the actions group with 1 update -* :ghpull:`27415`: Use class form of data classes -* :ghpull:`27649`: Check for latex binary before building docs -* :ghpull:`27641`: MNT: fix api changes link in PR template -* :ghpull:`27644`: ci: Fix mpl_toolkits label -* :ghpull:`27230`: Query macOS for available system fonts. -* :ghpull:`27643`: ci: Update nightly upload for artifacts v4 -* :ghpull:`27642`: Fix auto-labeler configuration -* :ghpull:`27639`: Doc: typo fix for #22699 -* :ghpull:`26978`: [pre-commit.ci] pre-commit autoupdate -* :ghpull:`27563`: Enable PyPI publishing from GitHub Actions -* :ghpull:`22699`: Proof of concept for adding kwdoc content to properties using a decorator -* :ghpull:`27633`: Auto-label PRs based on changed files -* :ghpull:`27607`: Error on bad input to hexbin extents -* :ghpull:`27629`: Don't run CI twice on dependabot branches -* :ghpull:`27562`: Avoid an extra copy/resample if imshow input has no alpha -* :ghpull:`27628`: Bump the actions group with 2 updates -* :ghpull:`27626`: CI: Group dependabot updates -* :ghpull:`27589`: Don't clip PowerNorm inputs < vmin -* :ghpull:`27613`: Fix marker validator with cycler (allow mix of classes) -* :ghpull:`27615`: MNT: add spaces to PR template -* :ghpull:`27614`: DOC: Updated link in annotation API docs to point to annotation user guide -* :ghpull:`27605`: Ignore masked values in boxplot -* :ghpull:`26884`: Remove deprecated code from _fontconfig_patterns -* :ghpull:`27602`: Let FormatStrFormatter respect axes.unicode_minus. -* :ghpull:`27601`: Clarify dollar_ticks example and FormatStrFormatter docs. -* :ghpull:`24834`: Deprecate apply_theta_transforms=True to PolarTransform -* :ghpull:`27591`: Use macOS instead of OSX in comments/docs -* :ghpull:`27577`: MNT: add the running version to pickle warning message -* :ghpull:`25191`: Deprecate 'prune' kwarg to MaxNLocator -* :ghpull:`27566`: DOC: changed tag ``plot type`` to ``plot-type`` -* :ghpull:`27105`: Use Axes instead of axes core library code -* :ghpull:`27575`: Add quotes round .[dev] in editable install command -* :ghpull:`27104`: Use Axes instead of axes in galleries -* :ghpull:`27373`: Transpose grid_finder tick representation. -* :ghpull:`27363`: ci: Improve coverage for compiled code -* :ghpull:`27200`: DOC: Add role for custom informal types like color -* :ghpull:`27548`: DOC: typo fix in contribute doc -* :ghpull:`27458`: Check if the mappable is in a different Figure than the one fig.color… -* :ghpull:`27546`: MNT: Clean up some style exceptions -* :ghpull:`27514`: Improve check for bbox -* :ghpull:`27265`: DOC: reorganizing contributing docs to clean up toc, better separate topics -* :ghpull:`27517`: Best-legend-location microoptimization -* :ghpull:`27540`: Bump github/codeql-action from 2 to 3 -* :ghpull:`27520`: [Doc] Minor consistency changes and correction of Marker docs -* :ghpull:`27505`: Download Qhull source from Github, not Qhull servers, in meson build -* :ghpull:`27518`: Micro-optimizations related to list handling -* :ghpull:`27495`: Bump actions/stale from 8 to 9 -* :ghpull:`27523`: Changes for stale GHA v9 -* :ghpull:`27519`: [Doc] Improve/correct docs for 3D -* :ghpull:`27447`: TST: Compress some hist geometry tests -* :ghpull:`27513`: Fix docs and add tests for transform and deprecate ``BboxTransformToMaxOnly`` -* :ghpull:`27511`: TST: Add tests for Affine2D -* :ghpull:`27424`: Added Axes.stairs test in test_datetime.py -* :ghpull:`27267`: Fix/restore secondary axis support for Transform-type functions -* :ghpull:`27013`: Add test_contour under test_datetime.py -* :ghpull:`27497`: Clarify that set_axisbelow doesn't move grids below images. -* :ghpull:`27498`: Remove unnecessary del local variables at end of Gcf.destroy. -* :ghpull:`27466`: Add test_eventplot to test_datetime.py -* :ghpull:`25905`: Use annotate coordinate systems to simplify label_subplots. -* :ghpull:`27471`: Doc: visualizing_tests and ``triage_tests`` tools -* :ghpull:`27474`: Added smoke test for Axes.matshow to test_datetime.py -* :ghpull:`27470`: Fix test visualization tool for non-PNG files -* :ghpull:`27426`: DOC: normalizing histograms -* :ghpull:`27452`: Cleanup unit_cube-methods -* :ghpull:`27431`: Added test for Axes.bar_label -* :ghpull:`26962`: Remove backend 3.7-deprecated API -* :ghpull:`27410`: Add test_vlines to test_datetime.py -* :ghpull:`27425`: Added test_fill_betweenx in test_datetime.py -* :ghpull:`27449`: Remove test_quiverkey from test_datetime.py -* :ghpull:`27427`: MNT/TST: remove xcorr and acorr from test_datetime -* :ghpull:`27390`: Add test_bxp in test_datetime.py -* :ghpull:`27428`: Added test for broken_barh to test_datetime.py -* :ghpull:`27222`: [TST] Added test_annotate in test_datetime.py -* :ghpull:`27135`: Added smoke test for Axes.stem -* :ghpull:`27343`: Fix draggable annotations on subfigures. -* :ghpull:`27033`: Add test_bar in test_datetime -* :ghpull:`27423`: Add test for fill_between in test_datetime.py -* :ghpull:`27409`: Fix setting ``_selection_completed`` in ``SpanSelector`` when spanselector is initialised using ``extents`` -* :ghpull:`27440`: Fix get_path for 3d artists -* :ghpull:`27422`: TST: Cache available interactive backends -* :ghpull:`27401`: Add test_fill in test_datetime.py -* :ghpull:`27419`: DOC: Add AsinhScale to list of built-in scales -* :ghpull:`27417`: Switch pytest fixture from tmpdir to tmp_path -* :ghpull:`27172`: ENH: Change logging to warning when creating a legend with no labels -* :ghpull:`27405`: Check that xerr/yerr values are not None in errorbar -* :ghpull:`27392`: Remove test_spy from test_datetime.py -* :ghpull:`27331`: Added smoke test for Axes.barbs in test_datetime.py -* :ghpull:`27393`: MNT: Fix doc makefiles -* :ghpull:`27387`: Revert "MNT: add _version.py to .gitignore" -* :ghpull:`27347`: FIX: scale norm of collections when first array is set -* :ghpull:`27374`: MNT: add _version.py to .gitignore -* :ghpull:`19011`: Simplify tk tooltip setup. -* :ghpull:`27367`: Fix _find_fonts_by_props docstring -* :ghpull:`27359`: Fix build on PyPy -* :ghpull:`27362`: Implement SubFigure.remove. -* :ghpull:`27360`: Fix removal of colorbars on nested subgridspecs. -* :ghpull:`27211`: Add test_hlines to test_datetimes.py -* :ghpull:`27353`: Refactor AxisArtistHelpers -* :ghpull:`27357`: [DOC]: Update 3d axis limits what's new -* :ghpull:`26992`: Convert TkAgg utilities to pybind11 -* :ghpull:`27215`: Add ``@QtCore.Slot()`` decorations to ``NavigationToolbar2QT`` -* :ghpull:`26907`: Removal of deprecations for Contour -* :ghpull:`27285`: Factor out common parts of qt and macos interrupt handling. -* :ghpull:`27306`: Simplify GridSpec setup in make_axes_gridspec. -* :ghpull:`27313`: FIX: allow re-shown Qt windows to be re-destroyed -* :ghpull:`27184`: Use pybind11 for qhull wrapper -* :ghpull:`26794`: Use pybind11 in _c_internal_utils module -* :ghpull:`27300`: Remove idiosyncratic get_tick_iterator API. -* :ghpull:`27275`: MAINT: fix .yml in tag issue template -* :ghpull:`27288`: Use int.from_bytes instead of implementing the conversion ourselves. -* :ghpull:`27286`: Various cleanups -* :ghpull:`27279`: Tweak a few docstrings. -* :ghpull:`27256`: merge up v3.8.1 -* :ghpull:`27254`: Remove redundant axes_grid colorbar examples. -* :ghpull:`27251`: webagg: Don't resize canvas if WebSocket isn't connected -* :ghpull:`27236`: Tagging Example - Tags for multiple figs demo -* :ghpull:`27245`: MNT: be more careful in Qt backend that there is actually a Figure -* :ghpull:`27158`: First attempt for individual hatching styles for stackplot -* :ghpull:`26851`: Establish draft Tag glossary and Tagging guidelines -* :ghpull:`27083`: DOC: Add tags infrastructure for gallery examples -* :ghpull:`27204`: BLD: Use NumPy nightly wheels for non-release builds -* :ghpull:`27208`: Add test_axvline to test_datetime.py -* :ghpull:`26989`: MNT: print fontname in missing glyph warning -* :ghpull:`27177`: Add test_axhline in test_datetime.py -* :ghpull:`27164`: docs: adding explanation for color in ``set_facecolor`` -* :ghpull:`27175`: Deprecate mixing positional and keyword args for legend(handles, labels) -* :ghpull:`27199`: DOC: clean up links under table formatting docs -* :ghpull:`27185`: Added smoke tests for Axes.errorbar in test_datetime.py -* :ghpull:`27091`: Add test_step to test_datetime.py -* :ghpull:`27182`: Add example for plotting a bihistogram -* :ghpull:`27130`: added test_axvspan in test.datetime.py -* :ghpull:`27094`: MNT: move pytest.ini configs to .toml -* :ghpull:`27139`: added test_axhspan in test_datetime.py -* :ghpull:`27058`: DOC: concise dependency heading + small clarifications -* :ghpull:`27053`: Added info for getting compilation output from meson on autorebuild -* :ghpull:`26906`: Fix masking for Axes3D.plot() -* :ghpull:`27142`: Added smoke test for Axes.text in test_datetime.py -* :ghpull:`27024`: Add test_contourf in test_datetime.py -* :ghpull:`22347`: correctly treat pan/zoom events of overlapping axes -* :ghpull:`26900`: #26865 removing deprecations to axislines.py -* :ghpull:`26696`: DOC: Fix colLoc default -* :ghpull:`27064`: Close all plot windows of a blocking show() on Ctrl+C -* :ghpull:`26882`: Add scatter test for datetime units -* :ghpull:`27114`: add test_stackplot in test_datetime.py -* :ghpull:`27084`: Add test_barh to test_datetime.py -* :ghpull:`27110`: DOC: Move figure member sections one level down -* :ghpull:`27127`: BLD: use python3 for shebang consistent with pep-394 -* :ghpull:`27111`: BLD: Fix setting FreeType build type in extension -* :ghpull:`26921`: MNT: clarify path.sketch rcparam format + test validate_sketch -* :ghpull:`27109`: TST: Use importlib for subprocess tests -* :ghpull:`27119`: Update clabel comment. -* :ghpull:`27117`: Remove datetime test for axes.pie -* :ghpull:`27095`: Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. -* :ghpull:`27066`: Tweak array_view to be more like pybind11 -* :ghpull:`27090`: Restore figaspect() API documentation -* :ghpull:`27074`: Issue #26990: Split the histogram image into two for each code block. -* :ghpull:`27086`: Rename py namespace to mpl in extension code -* :ghpull:`27082`: MAINT: Update environment.yml to match requirements files -* :ghpull:`27072`: Remove datetime test stubs for spectral methods/table -* :ghpull:`26830`: Update stix table with Unicode names -* :ghpull:`26969`: DOC: add units to user/explain [ci doc] -* :ghpull:`27028`: Added test_hist in test_datetime.py -* :ghpull:`26876`: issue: 26871 - Remove SimplePath class from patches.py -* :ghpull:`26875`: Fix Deprecation in patches.py -* :ghpull:`26890`: Removing deprecated api from patches -* :ghpull:`27037`: add test_plot_date in test_datetime.py -* :ghpull:`27012`: Bump required C++ standard to c++17 -* :ghpull:`27021`: Add a section to Highlight past winners for JDH plotting contest in docs -* :ghpull:`27004`: Warning if handles and labels have a len mismatch -* :ghpull:`24061`: #24050 No error was thrown even number of handles mismatched labels -* :ghpull:`26754`: DOC: separate and clarify axisartist default tables -* :ghpull:`27020`: CI: Update scientific-python/upload-nightly-action to 0.2.0 -* :ghpull:`26951`: Clarify that explicit ticklabels are used without further formatting. -* :ghpull:`26894`: Deprecate setting the timer interval while starting it. -* :ghpull:`13401`: New clear() method for Radio and Check buttons -* :ghpull:`23829`: Start transitioning to pyproject.toml -* :ghpull:`26621`: Port build system to Meson -* :ghpull:`26928`: [TYP] Add tool for running stubtest -* :ghpull:`26917`: Deprecate ContourLabeler.add_label_clabeltext. -* :ghpull:`26960`: Deprecate backend_ps.get_bbox_header, and split it for internal use. -* :ghpull:`26967`: Minor cleanups. -* :ghpull:`26909`: deprecated api tri -* :ghpull:`26946`: Inline Cursor._update into its sole caller. -* :ghpull:`26915`: DOC: Clarify description and add examples in colors.Normalize -* :ghpull:`26874`: Cleaned up the span_where class method from Polycollections. -* :ghpull:`26586`: Support standard formatters in axisartist. -* :ghpull:`26788`: Fix axh{line,span} on polar axes. -* :ghpull:`26935`: add tomli to rstcheck extras -* :ghpull:`26275`: Use pybind11 in image module -* :ghpull:`26887`: DOC: improve removal for julian dates [ci doc] -* :ghpull:`26929`: DOC: Fix removal doc for Animation attributes -* :ghpull:`26918`: 26865 Removed deprecations from quiver.py -* :ghpull:`26902`: Fixed deprecated APIs in lines.py -* :ghpull:`26903`: Simplify CheckButtons and RadioButtons click handler. -* :ghpull:`26899`: MNT: only account for Artists once in fig.get_tightbbox -* :ghpull:`26861`: QT/NavigationToolbar2: configure subplots dialog should be modal -* :ghpull:`26885`: Removed deprecated code from gridspec.py -* :ghpull:`26880`: Updated offsetbox.py -* :ghpull:`26910`: Removed the deprecated code from offsetbox.py -* :ghpull:`26905`: Add users/explain to default skip subdirs -* :ghpull:`26853`: Widgets: Remove deprecations and make arguments keyword only -* :ghpull:`26877`: Fixes deprecation in lines.py -* :ghpull:`26871`: Removed the deprecated code from ``axis.py`` -* :ghpull:`26872`: Deprecated code removed in animation.py -* :ghpull:`26859`: Add datetime testing skeleton -* :ghpull:`26848`: ci: Don't install recommended packages on Circle -* :ghpull:`26852`: Remove Julian date support -* :ghpull:`26801`: [MNT]: Cleanup ticklabel_format (style=) -* :ghpull:`26840`: Reduce redundant information in _process_plot_var_args. -* :ghpull:`26731`: Explicitly set foreground color to black in svg icons -* :ghpull:`26826`: [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in -* :ghpull:`26742`: [TYP] Add typing for some private methods and modules -* :ghpull:`26819`: Reorder safe_first_element() and _safe_first_finite() code -* :ghpull:`26813`: Bump docker/setup-qemu-action from 2 to 3 -* :ghpull:`26797`: Remove deprecated draw_gouraud_triangle -* :ghpull:`26815`: Remove plt.Axes from tests -* :ghpull:`26818`: Fix doc build (alternative) -* :ghpull:`26785`: merge up v3.8.0 -* :ghpull:`25272`: Do not add padding to 3D axis limits when limits are manually set -* :ghpull:`26798`: Remove deprecated methods and attributed in Axes3D -* :ghpull:`26744`: Use cbook methods for string checking -* :ghpull:`26802`: specify input range in logs when image data must be clipped -* :ghpull:`26787`: Remove unused Axis private init helpers. -* :ghpull:`26629`: DOC: organize figure API -* :ghpull:`26690`: Make generated pgf code more robust against later changes of tex engine. -* :ghpull:`26577`: Bugfix: data sanitizing for barh -* :ghpull:`26684`: Update PR template doc links -* :ghpull:`26686`: PR template: shorten comment and pull up top -* :ghpull:`26670`: Added sanitize_sequence to kwargs in _preprocess_data -* :ghpull:`26634`: [MNT] Move SubplotParams from figure to gridspec -* :ghpull:`26609`: Cleanup AutoMinorLocator implementation. -* :ghpull:`26293`: Added get_xmargin(), get_ymargin() and get_zmargin() and tests. -* :ghpull:`26516`: Replace reference to %pylab by %matplotlib. -* :ghpull:`26483`: Improve legend(loc='best') warning and test -* :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions -* :ghpull:`23787`: Use pybind11 for C/C++ extensions +* :ghpull:`28507`: Backport PR #28430 on branch v3.9.x (Fix pickling of AxesWidgets.) +* :ghpull:`28506`: Backport PR #28451 on branch v3.9.x (Fix GTK cairo backends) +* :ghpull:`28430`: Fix pickling of AxesWidgets. +* :ghpull:`25861`: Fix Hidpi scaling for GTK4Cairo +* :ghpull:`28451`: Fix GTK cairo backends +* :ghpull:`28499`: Backport PR #28498 on branch v3.9.x (Don't fail if we can't query system fonts on macOS) +* :ghpull:`28498`: Don't fail if we can't query system fonts on macOS +* :ghpull:`28491`: Backport PR #28487 on branch v3.9.x (Fix autoscaling with axhspan) +* :ghpull:`28490`: Backport PR #28486 on branch v3.9.x (Fix CompositeGenericTransform.contains_branch_seperately) +* :ghpull:`28487`: Fix autoscaling with axhspan +* :ghpull:`28486`: Fix CompositeGenericTransform.contains_branch_seperately +* :ghpull:`28483`: Backport PR #28393 on branch v3.9.x (Make sticky edges only apply if the sticky edge is the most extreme limit point) +* :ghpull:`28482`: Backport PR #28473 on branch v3.9.x (Do not lowercase module:// backends) +* :ghpull:`28393`: Make sticky edges only apply if the sticky edge is the most extreme limit point +* :ghpull:`28473`: Do not lowercase module:// backends +* :ghpull:`28480`: Backport PR #28474 on branch v3.9.x (Fix typing and docs for containers) +* :ghpull:`28479`: Backport PR #28397 (FIX: stale root Figure when adding/updating subfigures) +* :ghpull:`28474`: Fix typing and docs for containers +* :ghpull:`28472`: Backport PR #28289 on branch v3.9.x (Promote mpltype Sphinx role to a public extension) +* :ghpull:`28471`: Backport PR #28342 on branch v3.9.x (DOC: Document the parameter *position* of apply_aspect() as internal) +* :ghpull:`28470`: Backport PR #28398 on branch v3.9.x (Add GIL Release to flush_events in macosx backend) +* :ghpull:`28469`: Backport PR #28355 on branch v3.9.x (MNT: Re-add matplotlib.cm.get_cmap) +* :ghpull:`28397`: FIX: stale root Figure when adding/updating subfigures +* :ghpull:`28289`: Promote mpltype Sphinx role to a public extension +* :ghpull:`28342`: DOC: Document the parameter *position* of apply_aspect() as internal +* :ghpull:`28398`: Add GIL Release to flush_events in macosx backend +* :ghpull:`28355`: MNT: Re-add matplotlib.cm.get_cmap +* :ghpull:`28468`: Backport PR #28465 on branch v3.9.x (Fix pickling of SubFigures) +* :ghpull:`28465`: Fix pickling of SubFigures +* :ghpull:`28462`: Backport PR #28440 on branch v3.9.x (DOC: Add note about simplification of to_polygons) +* :ghpull:`28460`: Backport PR #28459 on branch v3.9.x (DOC: Document kwargs scope for tick setter functions) +* :ghpull:`28461`: Backport PR #28458 on branch v3.9.x (Correct numpy dtype comparisons in image_resample) +* :ghpull:`28440`: DOC: Add note about simplification of to_polygons +* :ghpull:`28458`: Correct numpy dtype comparisons in image_resample +* :ghpull:`28459`: DOC: Document kwargs scope for tick setter functions +* :ghpull:`28450`: Backport of 28371 and 28411 +* :ghpull:`28446`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28445`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection) +* :ghpull:`28438`: Backport PR #28436 on branch v3.9.x (Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)``) +* :ghpull:`28403`: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28443`: Backport PR #28441 on branch v3.9.x (MNT: Update basic units example to work with numpy 2.0) +* :ghpull:`28441`: MNT: Update basic units example to work with numpy 2.0 +* :ghpull:`28436`: Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)`` +* :ghpull:`28426`: Backport PR #28425 on branch v3.9.x (Fix Circle yaml line length) +* :ghpull:`28427`: Fix circleci yaml +* :ghpull:`28425`: Fix Circle yaml line length +* :ghpull:`28422`: Backport PR #28401 on branch v3.9.x (FIX: Fix text wrapping) +* :ghpull:`28424`: Backport PR #28423 on branch v3.9.x (Update return type for Axes.axhspan and Axes.axvspan) +* :ghpull:`28423`: Update return type for Axes.axhspan and Axes.axvspan +* :ghpull:`28401`: FIX: Fix text wrapping +* :ghpull:`28419`: Backport PR #28414 on branch v3.9.x (Clean up obsolete widget code) +* :ghpull:`28411`: Bump the actions group with 3 updates +* :ghpull:`28414`: Clean up obsolete widget code +* :ghpull:`28415`: Backport PR #28413 on branch v3.9.x (CI: update action that got moved org) +* :ghpull:`28413`: CI: update action that got moved org +* :ghpull:`28392`: Backport PR #28388 on branch v3.9.x (Allow duplicate (name, value) entry points for backends) +* :ghpull:`28362`: Backport PR #28337 on branch v3.9.x (Bump the actions group across 1 directory with 3 updates) +* :ghpull:`28388`: Allow duplicate (name, value) entry points for backends +* :ghpull:`28389`: Backport PR #28380 on branch v3.9.x (Remove outdated docstring section in RendererBase.draw_text.) +* :ghpull:`28380`: Remove outdated docstring section in RendererBase.draw_text. +* :ghpull:`28385`: Backport PR #28377 on branch v3.9.x (DOC: Clarify scope of wrap.) +* :ghpull:`28377`: DOC: Clarify scope of wrap. +* :ghpull:`28368`: Backport PR #28359 on branch v3.9.x (Document that axes unsharing is impossible.) +* :ghpull:`28359`: Document that axes unsharing is impossible. +* :ghpull:`28337`: Bump the actions group across 1 directory with 3 updates +* :ghpull:`28351`: Backport PR #28307 on branch v3.9.x (DOC: New color line by value example) +* :ghpull:`28307`: DOC: New color line by value example +* :ghpull:`28339`: Backport PR #28336 on branch v3.9.x (DOC: Add version warning banner for docs versions different from stable) +* :ghpull:`28336`: DOC: Add version warning banner for docs versions different from stable +* :ghpull:`28334`: Backport PR #28332 on branch v3.9.x (Call IPython.enable_gui when install repl displayhook) +* :ghpull:`28332`: Call IPython.enable_gui when install repl displayhook +* :ghpull:`28331`: Backport PR #28329 on branch v3.9.x (DOC: Add example for 3D intersecting planes) +* :ghpull:`28329`: DOC: Add example for 3D intersecting planes +* :ghpull:`28327`: Backport PR #28292 on branch v3.9.x (Resolve MaxNLocator IndexError when no large steps) +* :ghpull:`28292`: Resolve MaxNLocator IndexError when no large steps +* :ghpull:`28326`: Backport PR #28041 on branch v3.9.x ([BUG]: Shift box_aspect according to vertical_axis) +* :ghpull:`28041`: [BUG]: Shift box_aspect according to vertical_axis +* :ghpull:`28320`: Backport PR #27001 on branch v3.9.x ([TYP] Add overload of ``pyplot.subplots``) +* :ghpull:`27001`: [TYP] Add overload of ``pyplot.subplots`` +* :ghpull:`28318`: Backport PR #28273 on branch v3.9.x (CI: Add GitHub artifact attestations to package distribution) +* :ghpull:`28273`: CI: Add GitHub artifact attestations to package distribution +* :ghpull:`28305`: Backport PR #28303 on branch v3.9.x (Removed drawedges repeated definition from function doc string) +* :ghpull:`28303`: Removed drawedges repeated definition from function doc string +* :ghpull:`28299`: Backport PR #28297 on branch v3.9.x (Solved #28296 Added missing comma) +* :ghpull:`28297`: Solved #28296 Added missing comma +* :ghpull:`28294`: Backport PR #28261 on branch v3.9.x (Correct roll angle units, issue #28256) +* :ghpull:`28261`: Correct roll angle units, issue #28256 +* :ghpull:`28283`: Backport PR #28280 on branch v3.9.x (DOC: Add an example for 2D images in 3D plots) +* :ghpull:`28280`: DOC: Add an example for 2D images in 3D plots +* :ghpull:`28278`: Backport PR #28272 on branch v3.9.x (BLD: Move macos builders from 11 to 12) +* :ghpull:`28277`: Backport PR #28274 on branch v3.9.x (ci: Remove deprecated codeql option) +* :ghpull:`28272`: BLD: Move macos builders from 11 to 12 +* :ghpull:`28274`: ci: Remove deprecated codeql option +* :ghpull:`28270`: Backport PR #28269 on branch v3.9.x (Handle GetForegroundWindow() returning NULL.) +* :ghpull:`28269`: Handle GetForegroundWindow() returning NULL. +* :ghpull:`28266`: Backport PR #28257 on branch v3.9.x (Clean up some Meson-related leftovers) +* :ghpull:`28257`: Clean up some Meson-related leftovers +* :ghpull:`28255`: Backport PR #28254 on branch v3.9.x ([DOC] plot type heading consistency) +* :ghpull:`28254`: [DOC] plot type heading consistency +* :ghpull:`28253`: Backport PR #28252 on branch v3.9.x (DOC: Flip the imshow plot types example to match the other examples) +* :ghpull:`28252`: DOC: Flip the imshow plot types example to match the other examples +* :ghpull:`28247`: Backport PR #28230 on branch v3.9.x (Add extra imports to improve typing) +* :ghpull:`28230`: Add extra imports to improve typing +* :ghpull:`28246`: Backport PR #28243 on branch v3.9.x (DOC: Add more 3D plot types) +* :ghpull:`28243`: DOC: Add more 3D plot types +* :ghpull:`28241`: Backport PR #28219 on branch v3.9.x (Bump the actions group with 2 updates) +* :ghpull:`28219`: Bump the actions group with 2 updates +* :ghpull:`28237`: Backport PR #28233 on branch v3.9.x (CI: Fix font install on macOS/Homebrew) +* :ghpull:`28236`: Backport PR #28231 on branch v3.9.x (DOC: we do not need the blit call in on_draw) +* :ghpull:`28233`: CI: Fix font install on macOS/Homebrew +* :ghpull:`28231`: DOC: we do not need the blit call in on_draw -Issues (97): +Issues (30): -* :ghissue:`28202`: [Bug]: Qt test_ipython fails on older ipython -* :ghissue:`28145`: [TST] Upcoming dependency test failures -* :ghissue:`28034`: [TST] Upcoming dependency test failures -* :ghissue:`28168`: [TST] Upcoming dependency test failures -* :ghissue:`28040`: [Bug]: vertical_axis not respected when rotating plots interactively -* :ghissue:`28146`: [Bug]: Useless recursive group in SVG output when using path_effects -* :ghissue:`28067`: [Bug]: ``LinearSegmentedColormap.from_list`` does not have all type hints for argument ``colors`` -* :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy -* :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower -* :ghissue:`7720`: WXAgg backend not rendering nicely on retina -* :ghissue:`28069`: [Bug]: Can't save with custom toolbar -* :ghissue:`28005`: [Doc]: Improve contribute instructions -* :ghissue:`22376`: [ENH]: align_titles -* :ghissue:`5506`: Confusing status bar values in presence of multiple axes -* :ghissue:`4284`: Twin axis message coordinates -* :ghissue:`18940`: WxAgg backend draws the wrong size when wxpython app is high DPI aware on Windows -* :ghissue:`27792`: [ENH]: Legend entries for boxplot -* :ghissue:`27828`: [Bug]: ".C10" does not work as plot shorthand format spec -* :ghissue:`27911`: redirect not working for updated contribute page -* :ghissue:`21876`: [Doc]: redirect-from directive appears broken? -* :ghissue:`27941`: [Bug]: ShrinkA and ShrinkB are ignored in ax.annotate(arrowprops=...) -* :ghissue:`26477`: [ENH]: Add interpolation_stage selector for images in qt figureoptions -* :ghissue:`363`: Enable hatches for Cairo backend -* :ghissue:`27852`: [Bug]: matplotlib.pyplot.matshow "(first dimension of the array) are displayed horizontally" but are displayed vertically -* :ghissue:`27400`: [Bug]: tk backend confused by presence of file named "move" in current working directory -* :ghissue:`25882`: [Bug]: plt.hist takes significantly more time with torch and jax arrays -* :ghissue:`25204`: [Bug]: Pyparsing warnings emitted in mathtext -* :ghissue:`17707`: getpwuid(): uid not found: 99 -* :ghissue:`27896`: [Doc]: Empty "User guide tutorials page" in docs -* :ghissue:`27824`: [Bug]: polygon from axvspan not correct in polar plot after set_xy -* :ghissue:`27378`: [ENH]: Suggest 'CN' if color is an integer -* :ghissue:`27843`: [Bug]: close_group is not called when using patheffects -* :ghissue:`27839`: [Bug]: PathCollection using alpha ignores 'none' facecolors -* :ghissue:`25119`: [ENH]: secondary_x/yaxis accept transform argument -* :ghissue:`27876`: [Doc]: Fix version switcher in devdocs -* :ghissue:`27301`: [Bug]: ``imshow`` allows RGB(A) images with ``np.nan`` values to pass -* :ghissue:`23839`: [MNT]: Add tests to codify ``ax.clear`` -* :ghissue:`27652`: [Doc]: Low contrast on clicked links in dark mode -* :ghissue:`27865`: [Bug]: Zoom und pan not working after writing pdf pages. -* :ghissue:`25871`: [Bug]: Colorbar cannot be added to another figure -* :ghissue:`8072`: plot_date() ignores timezone in matplotlib version 2.0.0 -* :ghissue:`27812`: [ENH]: Add split feature for violin plots -* :ghissue:`27659`: [MNT]: Improve return type of ``ioff`` and ``ion`` to improve Pyright analysis of bound variables -* :ghissue:`27805`: [Bug]: Saving a figure with indicate_inset_zoom to pdf and then pickling it causes TypeError -* :ghissue:`27701`: [Bug]: axis set_xscale('log') interferes with set_xticks -* :ghissue:`19807`: radius modification in contains_point function when linewidth is specified -* :ghissue:`27762`: [Bug]: Inconsistent treatment of list of labels in ``plot`` when the input is a dataframe -* :ghissue:`27745`: [MNT]: ``_ImageBase.draw`` and ``Axis.draw`` args and kwargs -* :ghissue:`27782`: [Doc]: Link to citation page in read me broken -* :ghissue:`8789`: legend handle size does not automatically scale with linewidth -* :ghissue:`27746`: [Doc]: Citation link in the readme.md points to 404 -* :ghissue:`20853`: Add deprecation for colormaps -* :ghissue:`26865`: [MNT]: Remove 3.7-deprecated API -* :ghissue:`24168`: [Bug]: ``subprocess-exited-with-error`` when trying to build on M1 mac -* :ghissue:`27727`: [Doc]: Text in the colormap normalization gallery doesn't match the code -* :ghissue:`27635`: [Bug]: test_figure_leak_20490 repeatedly failing on CI -* :ghissue:`14217`: [Feature request] Add a way to update the position of the Arrow patch. -* :ghissue:`20512`: Bad boxplot legend entries -* :ghissue:`22011`: [Bug]: subfigures messes up with fig.legend zorder -* :ghissue:`27414`: [Bug]: Legend overlaps shaded area in fill_between with legend location "best" -* :ghissue:`23323`: Legend with "loc=best" does not try to avoid text -* :ghissue:`27648`: [Doc]: ``Axes.inset_axes`` is still experimental -* :ghissue:`27277`: [Doc]: Two license pages in docs -* :ghissue:`24648`: [Doc]: make html fail early if latex not present -* :ghissue:`27554`: [Bug]: Large image draw performance deterioration in recent releases -* :ghissue:`25239`: [Bug]: colors.PowerNorm results in incorrect colorbar -* :ghissue:`13533`: Boxplotting Masked Arrays -* :ghissue:`25967`: [Doc]: dollar_ticks example refers to unused formatter classes -* :ghissue:`24859`: [Doc]: Document color in a consistent way, including link -* :ghissue:`27159`: [Bug]: Meson build fails due to qhull link issue. -* :ghissue:`25691`: [Bug]: Secondary axis does not support Transform as functions -* :ghissue:`25860`: [Bug]: canvas pick events not working when Axes belongs to a subfigure -* :ghissue:`27361`: [Bug]: (Tight) layout engine breaks for 3D patches -* :ghissue:`27145`: [ENH]: Make "No artists with labels found to put in legend" a warning -* :ghissue:`27399`: [Bug]: None in y or yerr arrays leads to TypeError when using errorbar -* :ghissue:`13887`: Accessing default ``norm`` of a Collection removes its colors. -* :ghissue:`26593`: [ENH]: Support SubFigure.remove() -* :ghissue:`27329`: [Bug]: Removing a colorbar for an axes positioned in a subgridspec restores the axes' position to the wrong place. -* :ghissue:`27214`: [Bug]: ``NavigationToolbar2QT`` should use ``@Slot`` annotation -* :ghissue:`27146`: [ENH]: Multi hatching in ``ax.stackplot()`` -* :ghissue:`27168`: [Doc]: Instructions for editable installation on Windows potentially missing a step -* :ghissue:`27174`: [MNT]: Build nightly wheels with NumPy nightly wheels -* :ghissue:`25043`: [ENH]: Plotting masked arrays correctly in 3D line plot -* :ghissue:`26990`: [Doc]: Histogram path example renders poorly in HTML -* :ghissue:`25738`: [MNT]: Improve readability of _mathtext_data.stix_virtual_fonts table -* :ghissue:`11129`: Highlight past winners for JDH plotting contest in docs -* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles -* :ghissue:`10922`: ENH: clear() method for widgets.RadioButtons -* :ghissue:`18295`: How to modify ticklabels in axisartist? -* :ghissue:`24996`: [Bug]: for non-rectilinear axes, axvline/axhline should behave as "draw a gridline at that x/y" -* :ghissue:`26841`: [Bug]: Global legend weird behaviors -* :ghissue:`25974`: [MNT]: Cleanup ticklabel_format(..., style=) -* :ghissue:`26786`: Please upload new dev wheel so we pick up 3.9.dev after 3.8 release -* :ghissue:`18052`: the limits of axes are inexact with mplot3d -* :ghissue:`25596`: [MNT]: Consistency on Interface -* :ghissue:`26557`: [ENH]: Nightly Python 3.12 builds -* :ghissue:`26281`: [ENH]: Add get_xmargin, get_ymargin, get_zmargin axes methods +* :ghissue:`22482`: [ENH]: pickle (or save) matplotlib figure with insteractive slider +* :ghissue:`25847`: [Bug]: Graph gets cut off with scaled resolution using gtk4cairo backend +* :ghissue:`28341`: [Bug]: Incorrect X-axis scaling with date values +* :ghissue:`28383`: [Bug]: axvspan no longer participating in limit calculations +* :ghissue:`28223`: [Bug]: Inconsistent Visualization of Intervals in ax.barh for Different Duration Widths +* :ghissue:`28432`: [Bug]: Backend name specified as module gets lowercased since 3.9 +* :ghissue:`28467`: [Bug]: Incorrect type stub for ``ErrorbarContainer``'s ``lines`` attribute. +* :ghissue:`28384`: [Bug]: subfigure artists not drawn interactively +* :ghissue:`28234`: [Bug]: mpltype custom role breaks sphinx build for third-party projects that have intersphinx links to matplotlib +* :ghissue:`28464`: [Bug]: figure with subfigures cannot be pickled +* :ghissue:`28448`: [Bug]: Making an RGB image from pickled data throws error +* :ghissue:`23317`: [Bug]: ``add_collection3d`` does not update view limits +* :ghissue:`17130`: autoscale_view is not working with Line3DCollection +* :ghissue:`28434`: [Bug]: Setting exactly 2 colors with tuple in ``plot`` method gives confusing error +* :ghissue:`28417`: [Doc]: axhspan and axvspan now return Rectangles, not Polygons. +* :ghissue:`28378`: [ENH]: Switch text wrapping boundary to subfigure +* :ghissue:`28404`: [Doc]: matplotlib.widgets.CheckButtons no longer has .rectangles attribute, needs removed. +* :ghissue:`28367`: [Bug]: Backend entry points can be erroneously duplicated +* :ghissue:`28358`: [Bug]: Labels don't get wrapped when set_yticks() is used in subplots +* :ghissue:`28374`: [Bug]: rcParam ``tk.window_focus: True`` is causes crash on Linux in version 3.9.0. +* :ghissue:`28324`: [Bug]: show(block=False) freezes +* :ghissue:`28239`: [Doc]: Gallery example showing 3D slice planes +* :ghissue:`27603`: [Bug]: _raw_ticker() istep +* :ghissue:`24328`: [Bug]: class Axes3D.set_box_aspect() sets wrong aspect ratios when Axes3D.view_init( vertical_axis='y') is enabled. +* :ghissue:`28221`: [Doc]: drawedges attribute described twice in matplotlib.colorbar documentation +* :ghissue:`28296`: [Doc]: Missing comma +* :ghissue:`28256`: [Bug]: axes3d.py's _on_move() converts the roll angle to radians, but then passes it to view_init() as if it were still in degrees +* :ghissue:`28267`: [Bug]: for Python 3.11.9 gor error ValueError: PyCapsule_New called with null pointer +* :ghissue:`28022`: [Bug]: Type of Axes is unknown pyright +* :ghissue:`28002`: Segfault from path editor example with QtAgg Previous GitHub statistics diff --git a/doc/users/prev_whats_new/github_stats_3.9.0.rst b/doc/users/prev_whats_new/github_stats_3.9.0.rst new file mode 100644 index 000000000000..b1d229ffbfa1 --- /dev/null +++ b/doc/users/prev_whats_new/github_stats_3.9.0.rst @@ -0,0 +1,744 @@ +.. _github-stats-3-9.0: + +GitHub statistics for 3.9.0 (May 15, 2024) +========================================== + +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/05/15 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 97 issues and merged 450 pull requests. +The full list can be seen `on GitHub `__ + +The following 175 authors contributed 2584 commits. + +* 0taj +* Abdul Razak Taha +* Adam J. Stewart +* Adam Turner +* Aditi Gautam +* agautam478 +* Alan Lau +* Albert Y. Shih +* Alec Vercruysse +* Alexander Volkov +* Alice Descoeudres +* Allan Haldane +* Amirreza Aflakparast +* Ananya Devarakonda +* ananya314 +* Anja Beck +* Anjini2004 +* Ant Lockyer +* Antony Lee +* Anvi Verma +* Artyom Romanov +* Augusto Borges +* avramid9 +* Ben Root +* bersbersbers +* Binaya Sharma +* Cameron +* Chaoyi Hu +* chaoyihu +* Chiraag Balu +* Christoph Hasse +* ConstableCatnip +* CozyFrog +* Cyril Gadal +* Dale Dai +* Daniel Bergman +* Daniel Hitchcock +* danielcobej +* David Gilbertson +* David Stansby +* ddale1128@gmail.com +* dependabot[bot] +* Devilsaint +* dohyun +* Drew Kinneer +* DWesl +* Elisa Heckelmann +* ElisaHeck +* Elliott Sales de Andrade +* Eric Firing +* Eric Prestat +* esibinga +* Eva Sibinga +* Evgenii Radchenko +* Faisal Fawad +* Felipe Cybis Pereira +* Garrett Sward +* Gaurav-Kumar-Soni +* Gauri Chaudhari +* Gautam Sagar +* Greg Lucas +* Gurudatta Shanbhag +* hannah +* Haoying Zhang +* Hugues Hoppe +* i-jey +* iamfaham +* Ian Hunt-Isaak +* Ian Thomas +* ifEricReturnTrue +* Illviljan +* Issam +* Issam Arabi +* Jacob Stevens-Haas +* Jacob Tomlinson +* Jake +* Jake Stevens-Haas +* James Salsman +* Jaroza727 +* Jeremy Farrell +* Jirka +* Jody Klymak +* Jorge Moraleda +* Joshua Stevenson +* jovianw +* João Andrade +* jpgianfaldoni +* jsdodge +* jsjeelshah +* judfs +* Juhan Oskar Hennoste +* Junpei Ota +* Katherine Turk +* katotaisei +* KheshavKumar +* Koustav Ghosh +* Kritika Verma +* Kyle Sunden +* Linyi Li +* linyilily +* lkkmpn +* Lucia Korpas +* madisonwong210 +* Maggie Liu +* Marc Bresson +* Matthew Feickert +* Matthew Morrison +* Matthias Bussonnier +* Melissa Weber Mendonça +* melissawm +* mliu08 +* Mostafa Noah +* MostafaNouh0011 +* n-aswin +* Nabil +* nbarlowATI +* Nidaa Rabah +* Nivedita Chaudhari +* Oscar Gustafsson +* patel-zeel +* Pavel Liavonau +* Pedro +* Pedro Peçanha +* Peter Talley +* Pradeep Reddy Raamana +* Prajwal Agrawal +* Pranav Raghu +* prateetishah +* pre-commit-ci[bot] +* QuadroTec +* Rafael Tsuha +* Raghuram Sirigiri +* Raphael +* Raphael Quast +* Ratnabali Dutta +* rawwash +* rsp2210 +* Ruoyi +* Ruoyi Xie +* Rushikesh Pandya +* Ruth Comer +* samGreer +* Samuel Diebolt +* saranti +* Scott Shambaugh +* Sebastian Berg +* Seohyeon Lee +* Sheepfan0828 +* ShivamPathak99 +* Shriya Kalakata +* shriyakalakata +* Stefan +* Steffen Rehberg +* stevezhang1999 +* Sudhanshu Pandey +* Talha Irfan +* thehappycheese +* Thomas A Caswell +* Tiago Lubiana +* Tim Hoffmann +* tobias +* Tom Sarantis +* trananso +* turnipseason +* tusharkulkarni008 +* UFEddy +* Vashesh08 +* vicky6 +* vigneshvetrivel8 +* wemi3 +* yangyangdotcom +* YiLun Fan +* Zach Champion +* zachjweiner +* zoehcycy + +GitHub issues and pull requests: + +Pull Requests (450): + +* :ghpull:`28206`: Backport PR #28205 on branch v3.9.x (TST: Fix tests with older versions of ipython) +* :ghpull:`28207`: TST: Followup corrections to #28205 +* :ghpull:`28205`: TST: Fix tests with older versions of ipython +* :ghpull:`28203`: Backport PR #28164 on branch v3.9.x (CI: Ensure code coverage is always uploaded) +* :ghpull:`28204`: Backport PR #28195 on branch v3.9.x (TST: Prepare for pytest 9) +* :ghpull:`28191`: DOC: Use released mpl-sphinx-theme on v3.9.x +* :ghpull:`28195`: TST: Prepare for pytest 9 +* :ghpull:`28193`: Backport PR #28185 on branch v3.9.x (DOC: Bump mpl-sphinx-theme to 3.9) +* :ghpull:`28190`: Backport PR #28103 on branch v3.9.x ([DOC]: Fix compatibility with sphinx-gallery 0.16) +* :ghpull:`28164`: CI: Ensure code coverage is always uploaded +* :ghpull:`28194`: Backport PR #28188 on branch v3.9.x ([TST] Bump some tolerances for Macos ARM) +* :ghpull:`28188`: [TST] Bump some tolerances for Macos ARM +* :ghpull:`28185`: DOC: Bump mpl-sphinx-theme to 3.9 +* :ghpull:`28189`: Backport PR #28181 on branch v3.9.x (DOC: Prepare release notes for 3.9) +* :ghpull:`28103`: [DOC]: Fix compatibility with sphinx-gallery 0.16 +* :ghpull:`28181`: DOC: Prepare release notes for 3.9 +* :ghpull:`28184`: Backport PR #28182 on branch v3.9.x (Bump custom hatch deprecation expiration) +* :ghpull:`28182`: Bump custom hatch deprecation expiration +* :ghpull:`28178`: Backport PR #28171 on branch v3.9.x (Support removing absent tools from ToolContainerBase.) +* :ghpull:`28171`: Support removing absent tools from ToolContainerBase. +* :ghpull:`28174`: Backport PR #28169 on branch v3.9.x (Clarify public-ness of some ToolContainerBase APIs.) +* :ghpull:`28169`: Clarify public-ness of some ToolContainerBase APIs. +* :ghpull:`28160`: Backport PR #28039 on branch v3.9.x (Respect vertical_axis when rotating plot interactively) +* :ghpull:`28159`: Backport PR #28157 on branch v3.9.x (Remove call to non-existent method _default_contains in Artist) +* :ghpull:`28162`: Backport PR #27948 on branch v3.9.x (Move IPython backend mapping to Matplotlib and support entry points) +* :ghpull:`28163`: Backport PR #28144 on branch v3.9.x (DOC: Refactor code in the fishbone diagram example) +* :ghpull:`28144`: DOC: Refactor code in the fishbone diagram example +* :ghpull:`27948`: Move IPython backend mapping to Matplotlib and support entry points +* :ghpull:`28039`: Respect vertical_axis when rotating plot interactively +* :ghpull:`28157`: Remove call to non-existent method _default_contains in Artist +* :ghpull:`28141`: Backport PR #27960 on branch v3.9.x (Update AppVeyor config) +* :ghpull:`28138`: Backport PR #28068 on branch v3.9.x ([TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list``) +* :ghpull:`28140`: Backport PR #28136 on branch v3.9.x (Appease pycodestyle.) +* :ghpull:`27960`: Update AppVeyor config +* :ghpull:`28068`: [TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list`` +* :ghpull:`28136`: Appease pycodestyle. +* :ghpull:`28135`: Backport PR #28134 on branch v3.9.x (DOC: Minor improvements on quickstart) +* :ghpull:`28134`: DOC: Minor improvements on quickstart +* :ghpull:`28121`: Backport PR #28085 on branch v3.9.x (Clarify that the pgf backend is never actually used interactively.) +* :ghpull:`28120`: Backport PR #28102 on branch v3.9.x (Fix typo in color mapping documentation in quick_start.py) +* :ghpull:`28109`: Backport PR #28100 on branch v3.9.x (TST: wxcairo sometimes raises OSError on missing cairo libraries) +* :ghpull:`28100`: TST: wxcairo sometimes raises OSError on missing cairo libraries +* :ghpull:`28108`: Backport PR #28107 on branch v3.9.x ([DOC] Fix description in CapStyle example) +* :ghpull:`28107`: [DOC] Fix description in CapStyle example +* :ghpull:`28102`: Fix typo in color mapping documentation in quick_start.py +* :ghpull:`28095`: Backport PR #28094 on branch v3.9.x (DOC: exclude sphinx 7.3.*) +* :ghpull:`28081`: Backport PR #28078 on branch v3.9.x (Clarify that findfont & _find_fonts_by_props return paths.) +* :ghpull:`28080`: Backport PR #28077 on branch v3.9.x (Parent tk StringVar to the canvas widget, not to the toolbar.) +* :ghpull:`28092`: Backport PR #28032 on branch v3.9.x (FIX: ensure images are C order before passing to pillow) +* :ghpull:`28032`: FIX: ensure images are C order before passing to pillow +* :ghpull:`28088`: Backport PR #28087 on branch v3.9.x (Document Qt5 minimal version.) +* :ghpull:`28085`: Clarify that the pgf backend is never actually used interactively. +* :ghpull:`28078`: Clarify that findfont & _find_fonts_by_props return paths. +* :ghpull:`28077`: Parent tk StringVar to the canvas widget, not to the toolbar. +* :ghpull:`28062`: Backport PR #28056 on branch v3.9.x (Strip trailing spaces from log-formatter cursor output.) +* :ghpull:`28063`: Backport PR #28055 on branch v3.9.x (DOC: Improve inverted axis example) +* :ghpull:`28056`: Strip trailing spaces from log-formatter cursor output. +* :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) +* :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time +* :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) +* :ghpull:`28023`: ci: Update merge conflict labeler +* :ghpull:`28035`: Backport PR #28026 on branch v3.9.x ([DOC] reshuffle of contributing) +* :ghpull:`28026`: [DOC] reshuffle of contributing +* :ghpull:`28024`: DOC: Rewrite "Work on an issue" section +* :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index +* :ghpull:`27747`: Move doc/users/installing/ to doc/install/ +* :ghpull:`27952`: ENH: Align titles +* :ghpull:`28017`: Merge up v3.8.4 +* :ghpull:`28014`: Improve timeline example. +* :ghpull:`28019`: DOC: correct path to mpl_toolkits reference images +* :ghpull:`26981`: Fixes Issue #26377 - Auto-escape % Symbol in Latex in pie labels +* :ghpull:`28007`: wx: Fix file extension for toolmanager-style toolbar +* :ghpull:`25556`: Display cursor coordinates for all axes twinned with the current one. +* :ghpull:`23597`: Always use PyQT/PySide6 for GitHub CI +* :ghpull:`28013`: Avoid plt.xticks/plt.yticks in gallery examples. +* :ghpull:`28006`: Fix deprecation warnings in ft2font extension +* :ghpull:`27723`: ci: Enable testing on M1 macOS +* :ghpull:`26375`: Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` +* :ghpull:`27999`: Remove documentation that some backends don't support hatching. +* :ghpull:`26710`: Add support for High DPI displays to wxAgg backend +* :ghpull:`27148`: Correctly treat pan/zoom events of overlapping axes. +* :ghpull:`27981`: DOC: Fix label type specification in parameter descriptions +* :ghpull:`27979`: Clarify error message for bad-dimensionality in pcolorfast(). +* :ghpull:`27962`: DOC: Document axes_grid1.Grid attributes +* :ghpull:`27968`: MNT: Remove remaining 3.7 deprecations +* :ghpull:`27965`: DOC: Rewrite the example illustrating bxp() +* :ghpull:`26453`: add documentation for reloading font cache +* :ghpull:`26131`: Tst/restore old tests +* :ghpull:`27730`: Add an rcparam for image.interpolation_stage. +* :ghpull:`27956`: Use PyOS_setsig in macos backend +* :ghpull:`27829`: Simplify color/marker disambiguation logic in _process_plot_format. +* :ghpull:`27840`: Add legend support for boxplots +* :ghpull:`27943`: Support Cn, n>9 in plot() shorthand format. +* :ghpull:`27950`: ci: Fix condition for publishing wheels +* :ghpull:`27909`: Add a note to pyplot docstrings referencing the corresponding object methods +* :ghpull:`27929`: DOC: Add summary lines to plot types +* :ghpull:`27915`: [BUG] Fix redirect-from Sphinx extension +* :ghpull:`27945`: DOC: Explain leading dot in object references +* :ghpull:`27947`: Update docs for ``FancyArrowPatch`` & ``Annotation`` to make it clear that ShrinkA/B parameters are in points and not fractional. +* :ghpull:`27944`: Bump the actions group with 2 updates +* :ghpull:`27932`: Fix pickling of make_axes_area_auto_adjustable'd axes. +* :ghpull:`26500`: closes #26477 ENH: Add interpolation_stage in qt figureoptions +* :ghpull:`27927`: Update docs +* :ghpull:`27916`: Revert renaming labels to tick_labels in boxplot_stats() +* :ghpull:`27931`: Highlight development_setup code snippets as bash, not python. +* :ghpull:`27856`: Support hatching in cairo backends. +* :ghpull:`27922`: Fix cbook style +* :ghpull:`27668`: MNT: prevent merging using labels + branch protection rules +* :ghpull:`27857`: Documentation edit for matshow function +* :ghpull:`27928`: DOC: Fix syntax for ToolBase.image docstring +* :ghpull:`27873`: Simplify the LineCollection example +* :ghpull:`27492`: Fix semantics of MEP22 image names. +* :ghpull:`27918`: Fix new flake8 errors from old merge +* :ghpull:`27874`: Modernize macosx backend a bit +* :ghpull:`25887`: Update ``_unpack_to_numpy`` function to convert JAX and PyTorch arrays to NumPy +* :ghpull:`27685`: Work around pyparsing diagnostic warnings +* :ghpull:`26594`: Added optional props argument to Lasso Widget __init__ to customize Lasso line +* :ghpull:`22761`: Add minor ticks on and off in Axis +* :ghpull:`22407`: Add ``set_XY`` and ``set_data`` to ``Quiver`` +* :ghpull:`27901`: Rename boxplot's tick label parameter +* :ghpull:`27883`: Fix build on older macOS deployment targets +* :ghpull:`27900`: Remove empty user guide tutorials page +* :ghpull:`27885`: Clean up headers in extensions +* :ghpull:`27910`: DOC: Fix dead link in README +* :ghpull:`26567`: Use SVG inheritance diagrams now that linking has been fixed +* :ghpull:`27899`: Merge up 3.8.x into main +* :ghpull:`27905`: Improved error message for malformed colors +* :ghpull:`27906`: Override open_group, close_group methods in PathEffectRenderer +* :ghpull:`27904`: FIX: Restore D213 in flake8 +* :ghpull:`27895`: Remove versions from sidebar in docs +* :ghpull:`27894`: Mark triangulation classes as final +* :ghpull:`27557`: Use :mpltype:``color`` for color types +* :ghpull:`27845`: Make sure custom alpha param does not change 'none' colors in a list of colors +* :ghpull:`27719`: Add BackendRegistry singleton class +* :ghpull:`27890`: DOC: State approximate documentation build time +* :ghpull:`27887`: BLD: Add a fallback URL for FreeType +* :ghpull:`25224`: Allow passing a transformation to secondary_xaxis/_yaxis +* :ghpull:`27886`: Fix devdocs version switcher +* :ghpull:`27884`: FIX: don't copy twice on RGB input +* :ghpull:`27087`: Convert path extension to pybind11 +* :ghpull:`27867`: DOC: Update some animation related topics +* :ghpull:`27848`: FIX: handle nans in RGBA input with ScalarMappables +* :ghpull:`27821`: BLD,Cygwin: Include Python.h first in various C++ files +* :ghpull:`27457`: TST: adding tests of current clear behavior on ticks +* :ghpull:`27872`: doc: add description of ``**kwargs`` usage to collections +* :ghpull:`27868`: Use pybind11 string formatter for exception messages +* :ghpull:`27862`: Add dtype/copy args to internal testing class +* :ghpull:`27658`: Bump pydata-sphinx-theme +* :ghpull:`27303`: FIX: also exclude np.nan in RGB(A) in color mapping +* :ghpull:`27860`: Bump the actions group with 2 updates +* :ghpull:`27869`: Correctly set temporary pdf/pgf backends +* :ghpull:`27850`: Deprecate ``plot_date`` +* :ghpull:`27815`: Add side option to violinplot +* :ghpull:`27836`: DOC: use ... for continuation prompt in docstrings +* :ghpull:`27819`: MNT: remove draw method args and kwargs +* :ghpull:`27813`: DOC: Update violinplot() docs +* :ghpull:`27698`: Add linting and validation of all YAML files +* :ghpull:`27811`: Fix Annulus width check +* :ghpull:`27667`: Change return type of ``ion`` and ``ioff`` to fix unbound variable errors with Pyright +* :ghpull:`27807`: Expand CI pytest reporting config to ignore xfails +* :ghpull:`27806`: Remove self._renderer from AnnotationBbox and ConnectionPatch +* :ghpull:`27799`: Clarify that set_ticks() affects major/minor ticks independently +* :ghpull:`27787`: Improve documentation on boxplot and violinplot +* :ghpull:`27800`: Deactivate sidebar for release notes +* :ghpull:`27798`: Fix sphinx-gallery CSS +* :ghpull:`27462`: DOC: clarify the default value of *radius* in Patch.contains_point +* :ghpull:`27565`: MNT: arghandling subplotspec +* :ghpull:`27796`: Make mypy a bit stricter +* :ghpull:`27767`: Update handling of sequence labels for plot +* :ghpull:`27795`: Add EffVer badge +* :ghpull:`27780`: Partly revert #27711 +* :ghpull:`27768`: MNT: deprecate draw method args and kwargs +* :ghpull:`27783`: Update README.md to fix citation link +* :ghpull:`27726`: TST: always set a (long) timeout for subprocess and always use our wrapper +* :ghpull:`27781`: Simplify example: Box plots with custom fill colors +* :ghpull:`27750`: Bump the actions group with 2 updates +* :ghpull:`27771`: Add marker-only and line+marker visuals to the plot() plot types +* :ghpull:`27764`: Increase size of legend in Legend guide example +* :ghpull:`26800`: Bump minimum NumPy version to 1.23 +* :ghpull:`27752`: Update some Meson internals +* :ghpull:`27702`: GOV: adopt EffVer +* :ghpull:`26965`: Removal of deprecated API cm +* :ghpull:`27758`: [Doc] Remove special casing for removed method +* :ghpull:`25815`: [TST] Make jpl units instantiated with datetimes consistent with mpl converters +* :ghpull:`27729`: DOC: Improve colormap normalization example +* :ghpull:`27732`: TST: Remove memory leak test +* :ghpull:`27733`: ci: Simplify CodeQL setup +* :ghpull:`27692`: Add method to update position of arrow patch +* :ghpull:`27736`: Fix incorrect API reference in docs +* :ghpull:`27731`: DOC: Create explicit rename legend entry section in guide +* :ghpull:`27560`: Moved /users/project to /doc/project +* :ghpull:`27728`: Simplify Figure._suplabels. +* :ghpull:`27715`: Bump the actions group with 3 updates +* :ghpull:`27711`: Fix boxplot legend entries part 2 +* :ghpull:`27696`: DOC: clean up automated tests section of workflow docs +* :ghpull:`27686`: Improve Locator docstrings +* :ghpull:`27704`: ci: Remove prerelease conditions from Azure Pipelines +* :ghpull:`27568`: Fix boxplot legend entries +* :ghpull:`27694`: MNT: fix labeller +* :ghpull:`26953`: MNT: test that table doesn't try to convert unitized data +* :ghpull:`27690`: Remove "Past versions" section from release notes +* :ghpull:`26926`: Closes #22011: Changes to SubFigures so it behaves like a regular artist +* :ghpull:`27469`: Fixed legend with legend location "best" when legend overlaps shaded area and text +* :ghpull:`27684`: Bump the actions group with 1 update +* :ghpull:`27665`: Axes.inset_axes - warning message removed +* :ghpull:`27688`: CI: skip code coverage upload on scheduled tests +* :ghpull:`27689`: ci: Don't include API/what's new notes in general doc labels +* :ghpull:`27640`: Add ``get_cursor_data`` to ``NonUniformImage`` +* :ghpull:`27676`: BLD: Downgrade FreeType to 2.6.1 on Windows ARM +* :ghpull:`27619`: Use GH action to install reviewdog +* :ghpull:`27552`: TST: Use importlib for importing in pytest +* :ghpull:`27650`: DOC: Added call out to API guidelines to contribute + small API guidelines reorg +* :ghpull:`27618`: Add option of running stubtest using tox +* :ghpull:`27656`: Bump the actions group with 1 update +* :ghpull:`27415`: Use class form of data classes +* :ghpull:`27649`: Check for latex binary before building docs +* :ghpull:`27641`: MNT: fix api changes link in PR template +* :ghpull:`27644`: ci: Fix mpl_toolkits label +* :ghpull:`27230`: Query macOS for available system fonts. +* :ghpull:`27643`: ci: Update nightly upload for artifacts v4 +* :ghpull:`27642`: Fix auto-labeler configuration +* :ghpull:`27639`: Doc: typo fix for #22699 +* :ghpull:`26978`: [pre-commit.ci] pre-commit autoupdate +* :ghpull:`27563`: Enable PyPI publishing from GitHub Actions +* :ghpull:`22699`: Proof of concept for adding kwdoc content to properties using a decorator +* :ghpull:`27633`: Auto-label PRs based on changed files +* :ghpull:`27607`: Error on bad input to hexbin extents +* :ghpull:`27629`: Don't run CI twice on dependabot branches +* :ghpull:`27562`: Avoid an extra copy/resample if imshow input has no alpha +* :ghpull:`27628`: Bump the actions group with 2 updates +* :ghpull:`27626`: CI: Group dependabot updates +* :ghpull:`27589`: Don't clip PowerNorm inputs < vmin +* :ghpull:`27613`: Fix marker validator with cycler (allow mix of classes) +* :ghpull:`27615`: MNT: add spaces to PR template +* :ghpull:`27614`: DOC: Updated link in annotation API docs to point to annotation user guide +* :ghpull:`27605`: Ignore masked values in boxplot +* :ghpull:`26884`: Remove deprecated code from _fontconfig_patterns +* :ghpull:`27602`: Let FormatStrFormatter respect axes.unicode_minus. +* :ghpull:`27601`: Clarify dollar_ticks example and FormatStrFormatter docs. +* :ghpull:`24834`: Deprecate apply_theta_transforms=True to PolarTransform +* :ghpull:`27591`: Use macOS instead of OSX in comments/docs +* :ghpull:`27577`: MNT: add the running version to pickle warning message +* :ghpull:`25191`: Deprecate 'prune' kwarg to MaxNLocator +* :ghpull:`27566`: DOC: changed tag ``plot type`` to ``plot-type`` +* :ghpull:`27105`: Use Axes instead of axes core library code +* :ghpull:`27575`: Add quotes round .[dev] in editable install command +* :ghpull:`27104`: Use Axes instead of axes in galleries +* :ghpull:`27373`: Transpose grid_finder tick representation. +* :ghpull:`27363`: ci: Improve coverage for compiled code +* :ghpull:`27200`: DOC: Add role for custom informal types like color +* :ghpull:`27548`: DOC: typo fix in contribute doc +* :ghpull:`27458`: Check if the mappable is in a different Figure than the one fig.color… +* :ghpull:`27546`: MNT: Clean up some style exceptions +* :ghpull:`27514`: Improve check for bbox +* :ghpull:`27265`: DOC: reorganizing contributing docs to clean up toc, better separate topics +* :ghpull:`27517`: Best-legend-location microoptimization +* :ghpull:`27540`: Bump github/codeql-action from 2 to 3 +* :ghpull:`27520`: [Doc] Minor consistency changes and correction of Marker docs +* :ghpull:`27505`: Download Qhull source from Github, not Qhull servers, in meson build +* :ghpull:`27518`: Micro-optimizations related to list handling +* :ghpull:`27495`: Bump actions/stale from 8 to 9 +* :ghpull:`27523`: Changes for stale GHA v9 +* :ghpull:`27519`: [Doc] Improve/correct docs for 3D +* :ghpull:`27447`: TST: Compress some hist geometry tests +* :ghpull:`27513`: Fix docs and add tests for transform and deprecate ``BboxTransformToMaxOnly`` +* :ghpull:`27511`: TST: Add tests for Affine2D +* :ghpull:`27424`: Added Axes.stairs test in test_datetime.py +* :ghpull:`27267`: Fix/restore secondary axis support for Transform-type functions +* :ghpull:`27013`: Add test_contour under test_datetime.py +* :ghpull:`27497`: Clarify that set_axisbelow doesn't move grids below images. +* :ghpull:`27498`: Remove unnecessary del local variables at end of Gcf.destroy. +* :ghpull:`27466`: Add test_eventplot to test_datetime.py +* :ghpull:`25905`: Use annotate coordinate systems to simplify label_subplots. +* :ghpull:`27471`: Doc: visualizing_tests and ``triage_tests`` tools +* :ghpull:`27474`: Added smoke test for Axes.matshow to test_datetime.py +* :ghpull:`27470`: Fix test visualization tool for non-PNG files +* :ghpull:`27426`: DOC: normalizing histograms +* :ghpull:`27452`: Cleanup unit_cube-methods +* :ghpull:`27431`: Added test for Axes.bar_label +* :ghpull:`26962`: Remove backend 3.7-deprecated API +* :ghpull:`27410`: Add test_vlines to test_datetime.py +* :ghpull:`27425`: Added test_fill_betweenx in test_datetime.py +* :ghpull:`27449`: Remove test_quiverkey from test_datetime.py +* :ghpull:`27427`: MNT/TST: remove xcorr and acorr from test_datetime +* :ghpull:`27390`: Add test_bxp in test_datetime.py +* :ghpull:`27428`: Added test for broken_barh to test_datetime.py +* :ghpull:`27222`: [TST] Added test_annotate in test_datetime.py +* :ghpull:`27135`: Added smoke test for Axes.stem +* :ghpull:`27343`: Fix draggable annotations on subfigures. +* :ghpull:`27033`: Add test_bar in test_datetime +* :ghpull:`27423`: Add test for fill_between in test_datetime.py +* :ghpull:`27409`: Fix setting ``_selection_completed`` in ``SpanSelector`` when spanselector is initialised using ``extents`` +* :ghpull:`27440`: Fix get_path for 3d artists +* :ghpull:`27422`: TST: Cache available interactive backends +* :ghpull:`27401`: Add test_fill in test_datetime.py +* :ghpull:`27419`: DOC: Add AsinhScale to list of built-in scales +* :ghpull:`27417`: Switch pytest fixture from tmpdir to tmp_path +* :ghpull:`27172`: ENH: Change logging to warning when creating a legend with no labels +* :ghpull:`27405`: Check that xerr/yerr values are not None in errorbar +* :ghpull:`27392`: Remove test_spy from test_datetime.py +* :ghpull:`27331`: Added smoke test for Axes.barbs in test_datetime.py +* :ghpull:`27393`: MNT: Fix doc makefiles +* :ghpull:`27387`: Revert "MNT: add _version.py to .gitignore" +* :ghpull:`27347`: FIX: scale norm of collections when first array is set +* :ghpull:`27374`: MNT: add _version.py to .gitignore +* :ghpull:`19011`: Simplify tk tooltip setup. +* :ghpull:`27367`: Fix _find_fonts_by_props docstring +* :ghpull:`27359`: Fix build on PyPy +* :ghpull:`27362`: Implement SubFigure.remove. +* :ghpull:`27360`: Fix removal of colorbars on nested subgridspecs. +* :ghpull:`27211`: Add test_hlines to test_datetimes.py +* :ghpull:`27353`: Refactor AxisArtistHelpers +* :ghpull:`27357`: [DOC]: Update 3d axis limits what's new +* :ghpull:`26992`: Convert TkAgg utilities to pybind11 +* :ghpull:`27215`: Add ``@QtCore.Slot()`` decorations to ``NavigationToolbar2QT`` +* :ghpull:`26907`: Removal of deprecations for Contour +* :ghpull:`27285`: Factor out common parts of qt and macos interrupt handling. +* :ghpull:`27306`: Simplify GridSpec setup in make_axes_gridspec. +* :ghpull:`27313`: FIX: allow re-shown Qt windows to be re-destroyed +* :ghpull:`27184`: Use pybind11 for qhull wrapper +* :ghpull:`26794`: Use pybind11 in _c_internal_utils module +* :ghpull:`27300`: Remove idiosyncratic get_tick_iterator API. +* :ghpull:`27275`: MAINT: fix .yml in tag issue template +* :ghpull:`27288`: Use int.from_bytes instead of implementing the conversion ourselves. +* :ghpull:`27286`: Various cleanups +* :ghpull:`27279`: Tweak a few docstrings. +* :ghpull:`27256`: merge up v3.8.1 +* :ghpull:`27254`: Remove redundant axes_grid colorbar examples. +* :ghpull:`27251`: webagg: Don't resize canvas if WebSocket isn't connected +* :ghpull:`27236`: Tagging Example - Tags for multiple figs demo +* :ghpull:`27245`: MNT: be more careful in Qt backend that there is actually a Figure +* :ghpull:`27158`: First attempt for individual hatching styles for stackplot +* :ghpull:`26851`: Establish draft Tag glossary and Tagging guidelines +* :ghpull:`27083`: DOC: Add tags infrastructure for gallery examples +* :ghpull:`27204`: BLD: Use NumPy nightly wheels for non-release builds +* :ghpull:`27208`: Add test_axvline to test_datetime.py +* :ghpull:`26989`: MNT: print fontname in missing glyph warning +* :ghpull:`27177`: Add test_axhline in test_datetime.py +* :ghpull:`27164`: docs: adding explanation for color in ``set_facecolor`` +* :ghpull:`27175`: Deprecate mixing positional and keyword args for legend(handles, labels) +* :ghpull:`27199`: DOC: clean up links under table formatting docs +* :ghpull:`27185`: Added smoke tests for Axes.errorbar in test_datetime.py +* :ghpull:`27091`: Add test_step to test_datetime.py +* :ghpull:`27182`: Add example for plotting a bihistogram +* :ghpull:`27130`: added test_axvspan in test.datetime.py +* :ghpull:`27094`: MNT: move pytest.ini configs to .toml +* :ghpull:`27139`: added test_axhspan in test_datetime.py +* :ghpull:`27058`: DOC: concise dependency heading + small clarifications +* :ghpull:`27053`: Added info for getting compilation output from meson on autorebuild +* :ghpull:`26906`: Fix masking for Axes3D.plot() +* :ghpull:`27142`: Added smoke test for Axes.text in test_datetime.py +* :ghpull:`27024`: Add test_contourf in test_datetime.py +* :ghpull:`22347`: correctly treat pan/zoom events of overlapping axes +* :ghpull:`26900`: #26865 removing deprecations to axislines.py +* :ghpull:`26696`: DOC: Fix colLoc default +* :ghpull:`27064`: Close all plot windows of a blocking show() on Ctrl+C +* :ghpull:`26882`: Add scatter test for datetime units +* :ghpull:`27114`: add test_stackplot in test_datetime.py +* :ghpull:`27084`: Add test_barh to test_datetime.py +* :ghpull:`27110`: DOC: Move figure member sections one level down +* :ghpull:`27127`: BLD: use python3 for shebang consistent with pep-394 +* :ghpull:`27111`: BLD: Fix setting FreeType build type in extension +* :ghpull:`26921`: MNT: clarify path.sketch rcparam format + test validate_sketch +* :ghpull:`27109`: TST: Use importlib for subprocess tests +* :ghpull:`27119`: Update clabel comment. +* :ghpull:`27117`: Remove datetime test for axes.pie +* :ghpull:`27095`: Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. +* :ghpull:`27066`: Tweak array_view to be more like pybind11 +* :ghpull:`27090`: Restore figaspect() API documentation +* :ghpull:`27074`: Issue #26990: Split the histogram image into two for each code block. +* :ghpull:`27086`: Rename py namespace to mpl in extension code +* :ghpull:`27082`: MAINT: Update environment.yml to match requirements files +* :ghpull:`27072`: Remove datetime test stubs for spectral methods/table +* :ghpull:`26830`: Update stix table with Unicode names +* :ghpull:`26969`: DOC: add units to user/explain [ci doc] +* :ghpull:`27028`: Added test_hist in test_datetime.py +* :ghpull:`26876`: issue: 26871 - Remove SimplePath class from patches.py +* :ghpull:`26875`: Fix Deprecation in patches.py +* :ghpull:`26890`: Removing deprecated api from patches +* :ghpull:`27037`: add test_plot_date in test_datetime.py +* :ghpull:`27012`: Bump required C++ standard to c++17 +* :ghpull:`27021`: Add a section to Highlight past winners for JDH plotting contest in docs +* :ghpull:`27004`: Warning if handles and labels have a len mismatch +* :ghpull:`24061`: #24050 No error was thrown even number of handles mismatched labels +* :ghpull:`26754`: DOC: separate and clarify axisartist default tables +* :ghpull:`27020`: CI: Update scientific-python/upload-nightly-action to 0.2.0 +* :ghpull:`26951`: Clarify that explicit ticklabels are used without further formatting. +* :ghpull:`26894`: Deprecate setting the timer interval while starting it. +* :ghpull:`13401`: New clear() method for Radio and Check buttons +* :ghpull:`23829`: Start transitioning to pyproject.toml +* :ghpull:`26621`: Port build system to Meson +* :ghpull:`26928`: [TYP] Add tool for running stubtest +* :ghpull:`26917`: Deprecate ContourLabeler.add_label_clabeltext. +* :ghpull:`26960`: Deprecate backend_ps.get_bbox_header, and split it for internal use. +* :ghpull:`26967`: Minor cleanups. +* :ghpull:`26909`: deprecated api tri +* :ghpull:`26946`: Inline Cursor._update into its sole caller. +* :ghpull:`26915`: DOC: Clarify description and add examples in colors.Normalize +* :ghpull:`26874`: Cleaned up the span_where class method from Polycollections. +* :ghpull:`26586`: Support standard formatters in axisartist. +* :ghpull:`26788`: Fix axh{line,span} on polar axes. +* :ghpull:`26935`: add tomli to rstcheck extras +* :ghpull:`26275`: Use pybind11 in image module +* :ghpull:`26887`: DOC: improve removal for julian dates [ci doc] +* :ghpull:`26929`: DOC: Fix removal doc for Animation attributes +* :ghpull:`26918`: 26865 Removed deprecations from quiver.py +* :ghpull:`26902`: Fixed deprecated APIs in lines.py +* :ghpull:`26903`: Simplify CheckButtons and RadioButtons click handler. +* :ghpull:`26899`: MNT: only account for Artists once in fig.get_tightbbox +* :ghpull:`26861`: QT/NavigationToolbar2: configure subplots dialog should be modal +* :ghpull:`26885`: Removed deprecated code from gridspec.py +* :ghpull:`26880`: Updated offsetbox.py +* :ghpull:`26910`: Removed the deprecated code from offsetbox.py +* :ghpull:`26905`: Add users/explain to default skip subdirs +* :ghpull:`26853`: Widgets: Remove deprecations and make arguments keyword only +* :ghpull:`26877`: Fixes deprecation in lines.py +* :ghpull:`26871`: Removed the deprecated code from ``axis.py`` +* :ghpull:`26872`: Deprecated code removed in animation.py +* :ghpull:`26859`: Add datetime testing skeleton +* :ghpull:`26848`: ci: Don't install recommended packages on Circle +* :ghpull:`26852`: Remove Julian date support +* :ghpull:`26801`: [MNT]: Cleanup ticklabel_format (style=) +* :ghpull:`26840`: Reduce redundant information in _process_plot_var_args. +* :ghpull:`26731`: Explicitly set foreground color to black in svg icons +* :ghpull:`26826`: [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in +* :ghpull:`26742`: [TYP] Add typing for some private methods and modules +* :ghpull:`26819`: Reorder safe_first_element() and _safe_first_finite() code +* :ghpull:`26813`: Bump docker/setup-qemu-action from 2 to 3 +* :ghpull:`26797`: Remove deprecated draw_gouraud_triangle +* :ghpull:`26815`: Remove plt.Axes from tests +* :ghpull:`26818`: Fix doc build (alternative) +* :ghpull:`26785`: merge up v3.8.0 +* :ghpull:`25272`: Do not add padding to 3D axis limits when limits are manually set +* :ghpull:`26798`: Remove deprecated methods and attributed in Axes3D +* :ghpull:`26744`: Use cbook methods for string checking +* :ghpull:`26802`: specify input range in logs when image data must be clipped +* :ghpull:`26787`: Remove unused Axis private init helpers. +* :ghpull:`26629`: DOC: organize figure API +* :ghpull:`26690`: Make generated pgf code more robust against later changes of tex engine. +* :ghpull:`26577`: Bugfix: data sanitizing for barh +* :ghpull:`26684`: Update PR template doc links +* :ghpull:`26686`: PR template: shorten comment and pull up top +* :ghpull:`26670`: Added sanitize_sequence to kwargs in _preprocess_data +* :ghpull:`26634`: [MNT] Move SubplotParams from figure to gridspec +* :ghpull:`26609`: Cleanup AutoMinorLocator implementation. +* :ghpull:`26293`: Added get_xmargin(), get_ymargin() and get_zmargin() and tests. +* :ghpull:`26516`: Replace reference to %pylab by %matplotlib. +* :ghpull:`26483`: Improve legend(loc='best') warning and test +* :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions +* :ghpull:`23787`: Use pybind11 for C/C++ extensions + +Issues (97): + +* :ghissue:`28202`: [Bug]: Qt test_ipython fails on older ipython +* :ghissue:`28145`: [TST] Upcoming dependency test failures +* :ghissue:`28034`: [TST] Upcoming dependency test failures +* :ghissue:`28168`: [TST] Upcoming dependency test failures +* :ghissue:`28040`: [Bug]: vertical_axis not respected when rotating plots interactively +* :ghissue:`28146`: [Bug]: Useless recursive group in SVG output when using path_effects +* :ghissue:`28067`: [Bug]: ``LinearSegmentedColormap.from_list`` does not have all type hints for argument ``colors`` +* :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy +* :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower +* :ghissue:`7720`: WXAgg backend not rendering nicely on retina +* :ghissue:`28069`: [Bug]: Can't save with custom toolbar +* :ghissue:`28005`: [Doc]: Improve contribute instructions +* :ghissue:`22376`: [ENH]: align_titles +* :ghissue:`5506`: Confusing status bar values in presence of multiple axes +* :ghissue:`4284`: Twin axis message coordinates +* :ghissue:`18940`: WxAgg backend draws the wrong size when wxpython app is high DPI aware on Windows +* :ghissue:`27792`: [ENH]: Legend entries for boxplot +* :ghissue:`27828`: [Bug]: ".C10" does not work as plot shorthand format spec +* :ghissue:`27911`: redirect not working for updated contribute page +* :ghissue:`21876`: [Doc]: redirect-from directive appears broken? +* :ghissue:`27941`: [Bug]: ShrinkA and ShrinkB are ignored in ax.annotate(arrowprops=...) +* :ghissue:`26477`: [ENH]: Add interpolation_stage selector for images in qt figureoptions +* :ghissue:`363`: Enable hatches for Cairo backend +* :ghissue:`27852`: [Bug]: matplotlib.pyplot.matshow "(first dimension of the array) are displayed horizontally" but are displayed vertically +* :ghissue:`27400`: [Bug]: tk backend confused by presence of file named "move" in current working directory +* :ghissue:`25882`: [Bug]: plt.hist takes significantly more time with torch and jax arrays +* :ghissue:`25204`: [Bug]: Pyparsing warnings emitted in mathtext +* :ghissue:`17707`: getpwuid(): uid not found: 99 +* :ghissue:`27896`: [Doc]: Empty "User guide tutorials page" in docs +* :ghissue:`27824`: [Bug]: polygon from axvspan not correct in polar plot after set_xy +* :ghissue:`27378`: [ENH]: Suggest 'CN' if color is an integer +* :ghissue:`27843`: [Bug]: close_group is not called when using patheffects +* :ghissue:`27839`: [Bug]: PathCollection using alpha ignores 'none' facecolors +* :ghissue:`25119`: [ENH]: secondary_x/yaxis accept transform argument +* :ghissue:`27876`: [Doc]: Fix version switcher in devdocs +* :ghissue:`27301`: [Bug]: ``imshow`` allows RGB(A) images with ``np.nan`` values to pass +* :ghissue:`23839`: [MNT]: Add tests to codify ``ax.clear`` +* :ghissue:`27652`: [Doc]: Low contrast on clicked links in dark mode +* :ghissue:`27865`: [Bug]: Zoom und pan not working after writing pdf pages. +* :ghissue:`25871`: [Bug]: Colorbar cannot be added to another figure +* :ghissue:`8072`: plot_date() ignores timezone in matplotlib version 2.0.0 +* :ghissue:`27812`: [ENH]: Add split feature for violin plots +* :ghissue:`27659`: [MNT]: Improve return type of ``ioff`` and ``ion`` to improve Pyright analysis of bound variables +* :ghissue:`27805`: [Bug]: Saving a figure with indicate_inset_zoom to pdf and then pickling it causes TypeError +* :ghissue:`27701`: [Bug]: axis set_xscale('log') interferes with set_xticks +* :ghissue:`19807`: radius modification in contains_point function when linewidth is specified +* :ghissue:`27762`: [Bug]: Inconsistent treatment of list of labels in ``plot`` when the input is a dataframe +* :ghissue:`27745`: [MNT]: ``_ImageBase.draw`` and ``Axis.draw`` args and kwargs +* :ghissue:`27782`: [Doc]: Link to citation page in read me broken +* :ghissue:`8789`: legend handle size does not automatically scale with linewidth +* :ghissue:`27746`: [Doc]: Citation link in the readme.md points to 404 +* :ghissue:`20853`: Add deprecation for colormaps +* :ghissue:`26865`: [MNT]: Remove 3.7-deprecated API +* :ghissue:`24168`: [Bug]: ``subprocess-exited-with-error`` when trying to build on M1 mac +* :ghissue:`27727`: [Doc]: Text in the colormap normalization gallery doesn't match the code +* :ghissue:`27635`: [Bug]: test_figure_leak_20490 repeatedly failing on CI +* :ghissue:`14217`: [Feature request] Add a way to update the position of the Arrow patch. +* :ghissue:`20512`: Bad boxplot legend entries +* :ghissue:`22011`: [Bug]: subfigures messes up with fig.legend zorder +* :ghissue:`27414`: [Bug]: Legend overlaps shaded area in fill_between with legend location "best" +* :ghissue:`23323`: Legend with "loc=best" does not try to avoid text +* :ghissue:`27648`: [Doc]: ``Axes.inset_axes`` is still experimental +* :ghissue:`27277`: [Doc]: Two license pages in docs +* :ghissue:`24648`: [Doc]: make html fail early if latex not present +* :ghissue:`27554`: [Bug]: Large image draw performance deterioration in recent releases +* :ghissue:`25239`: [Bug]: colors.PowerNorm results in incorrect colorbar +* :ghissue:`13533`: Boxplotting Masked Arrays +* :ghissue:`25967`: [Doc]: dollar_ticks example refers to unused formatter classes +* :ghissue:`24859`: [Doc]: Document color in a consistent way, including link +* :ghissue:`27159`: [Bug]: Meson build fails due to qhull link issue. +* :ghissue:`25691`: [Bug]: Secondary axis does not support Transform as functions +* :ghissue:`25860`: [Bug]: canvas pick events not working when Axes belongs to a subfigure +* :ghissue:`27361`: [Bug]: (Tight) layout engine breaks for 3D patches +* :ghissue:`27145`: [ENH]: Make "No artists with labels found to put in legend" a warning +* :ghissue:`27399`: [Bug]: None in y or yerr arrays leads to TypeError when using errorbar +* :ghissue:`13887`: Accessing default ``norm`` of a Collection removes its colors. +* :ghissue:`26593`: [ENH]: Support SubFigure.remove() +* :ghissue:`27329`: [Bug]: Removing a colorbar for an axes positioned in a subgridspec restores the axes' position to the wrong place. +* :ghissue:`27214`: [Bug]: ``NavigationToolbar2QT`` should use ``@Slot`` annotation +* :ghissue:`27146`: [ENH]: Multi hatching in ``ax.stackplot()`` +* :ghissue:`27168`: [Doc]: Instructions for editable installation on Windows potentially missing a step +* :ghissue:`27174`: [MNT]: Build nightly wheels with NumPy nightly wheels +* :ghissue:`25043`: [ENH]: Plotting masked arrays correctly in 3D line plot +* :ghissue:`26990`: [Doc]: Histogram path example renders poorly in HTML +* :ghissue:`25738`: [MNT]: Improve readability of _mathtext_data.stix_virtual_fonts table +* :ghissue:`11129`: Highlight past winners for JDH plotting contest in docs +* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles +* :ghissue:`10922`: ENH: clear() method for widgets.RadioButtons +* :ghissue:`18295`: How to modify ticklabels in axisartist? +* :ghissue:`24996`: [Bug]: for non-rectilinear axes, axvline/axhline should behave as "draw a gridline at that x/y" +* :ghissue:`26841`: [Bug]: Global legend weird behaviors +* :ghissue:`25974`: [MNT]: Cleanup ticklabel_format(..., style=) +* :ghissue:`26786`: Please upload new dev wheel so we pick up 3.9.dev after 3.8 release +* :ghissue:`18052`: the limits of axes are inexact with mplot3d +* :ghissue:`25596`: [MNT]: Consistency on Interface +* :ghissue:`26557`: [ENH]: Nightly Python 3.12 builds +* :ghissue:`26281`: [ENH]: Add get_xmargin, get_ymargin, get_zmargin axes methods diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 3befbeee5b77..1204450f6c05 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -19,8 +19,10 @@ Version 3.9 :maxdepth: 1 prev_whats_new/whats_new_3.9.0.rst + ../api/prev_api_changes/api_changes_3.9.1.rst ../api/prev_api_changes/api_changes_3.9.0.rst github_stats.rst + prev_whats_new/github_stats_3.9.0.rst Version 3.8 ^^^^^^^^^^^ From 44be14cc3a866e28a9ad22f36abfe8c0623cc6ac Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 01:45:22 -0400 Subject: [PATCH 1040/1120] REL: 3.9.1 This is the first bugfix release of the 3.9.x series. This release contains several bug-fixes and adjustments: - Add GitHub artifact attestations for sdist and wheels - Re-add `matplotlib.cm.get_cmap`; note this function will still be removed at a later date - Allow duplicate backend entry points - Fix `Axes` autoscaling of thin bars at large locations - Fix `Axes` autoscaling with `axhspan` / `axvspan` - Fix `Axes3D` autoscaling of `Line3DCollection` / `Poly3DCollection` - Fix `Axes3D` mouse interactivity with non-default roll angle - Fix box aspect ratios in `Axes3D` with alternate vertical axis - Fix case handling of backends specified as `module://...` - Fix crash with TkAgg on Windows with `tk.window_focus: True` - Fix interactive update of SubFigures - Fix interactivity when using the IPython console - Fix pickling of AxesWidgets and SubFigures - Fix scaling on GTK3Cairo / GTK4Cairo backends - Fix text wrapping within SubFigures - Promote `mpltype` Sphinx role to a public extension; note this is only intended for development reasons From 60bfa22f28a4bd20c8911451614d47ac175b58d4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 02:26:59 -0400 Subject: [PATCH 1041/1120] BLD: bump branch away from tag So e tarballs from GitHub are stable. From 9c9792a66900de7740fa6428f7fe59edb5083c60 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 20:09:23 -0400 Subject: [PATCH 1042/1120] DOC: Add Zenodo DOI for 3.9.1 --- doc/_static/zenodo_cache/12652732.svg | 35 +++++++++++++++++++++++++++ doc/project/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/12652732.svg diff --git a/doc/_static/zenodo_cache/12652732.svg b/doc/_static/zenodo_cache/12652732.svg new file mode 100644 index 000000000000..cde5c5f37839 --- /dev/null +++ b/doc/_static/zenodo_cache/12652732.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.12652732 + + + 10.5281/zenodo.12652732 + + + \ No newline at end of file diff --git a/doc/project/citing.rst b/doc/project/citing.rst index fd013231b6c5..e0b99995ad11 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -32,6 +32,9 @@ By version .. START OF AUTOGENERATED +v3.9.1 + .. image:: ../_static/zenodo_cache/12652732.svg + :target: https://doi.org/10.5281/zenodo.12652732 v3.9.0 .. image:: ../_static/zenodo_cache/11201097.svg :target: https://doi.org/10.5281/zenodo.11201097 diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index 600e87efc498..1dc2fbba020b 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,7 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.9.1": "12652732", "v3.9.0": "11201097", "v3.8.4": "10916799", "v3.8.3": "10661079", From 5ea9a7f6b23ffa7a0f9a37e8422feddefa75d0db Mon Sep 17 00:00:00 2001 From: hannah Date: Sun, 7 Jul 2024 06:10:23 -0400 Subject: [PATCH 1043/1120] Backport PR #28517: DOC: better cross referencing for animations --- doc/api/animation_api.rst | 3 +++ galleries/users_explain/animations/animations.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index df39b5942199..1233b482165d 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -18,6 +18,9 @@ Animation The easiest way to make a live animation in Matplotlib is to use one of the `Animation` classes. +.. seealso:: + - :ref:`animations` + .. inheritance-diagram:: matplotlib.animation.FuncAnimation matplotlib.animation.ArtistAnimation :parts: 1 diff --git a/galleries/users_explain/animations/animations.py b/galleries/users_explain/animations/animations.py index 0391d9bc030a..2711663196f2 100644 --- a/galleries/users_explain/animations/animations.py +++ b/galleries/users_explain/animations/animations.py @@ -11,7 +11,7 @@ generate animations using the `~matplotlib.animation` module. An animation is a sequence of frames where each frame corresponds to a plot on a `~matplotlib.figure.Figure`. This tutorial covers a general guideline on -how to create such animations and the different options available. +how to create such animations and the different options available. More information is available in the API description: `~matplotlib.animation` """ import matplotlib.pyplot as plt From af6cc664bcdff66b1f1c3d4afdc93832681253ef Mon Sep 17 00:00:00 2001 From: hannah Date: Sun, 7 Jul 2024 06:10:23 -0400 Subject: [PATCH 1044/1120] Backport PR #28517: DOC: better cross referencing for animations --- doc/api/animation_api.rst | 3 +++ galleries/users_explain/animations/animations.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index df39b5942199..1233b482165d 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -18,6 +18,9 @@ Animation The easiest way to make a live animation in Matplotlib is to use one of the `Animation` classes. +.. seealso:: + - :ref:`animations` + .. inheritance-diagram:: matplotlib.animation.FuncAnimation matplotlib.animation.ArtistAnimation :parts: 1 diff --git a/galleries/users_explain/animations/animations.py b/galleries/users_explain/animations/animations.py index 0391d9bc030a..2711663196f2 100644 --- a/galleries/users_explain/animations/animations.py +++ b/galleries/users_explain/animations/animations.py @@ -11,7 +11,7 @@ generate animations using the `~matplotlib.animation` module. An animation is a sequence of frames where each frame corresponds to a plot on a `~matplotlib.figure.Figure`. This tutorial covers a general guideline on -how to create such animations and the different options available. +how to create such animations and the different options available. More information is available in the API description: `~matplotlib.animation` """ import matplotlib.pyplot as plt From b7afb01dab684ddb8d6631ca2a0698f7f54bbfe0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 8 Jul 2024 08:44:59 -0500 Subject: [PATCH 1045/1120] Backport PR #28523: Fix value error when set widget size to zero while using FiCureCanvasQT --- lib/matplotlib/backends/backend_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index a93b37799971..6603883075d4 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -492,7 +492,7 @@ def _draw_idle(self): if not self._draw_pending: return self._draw_pending = False - if self.height() < 0 or self.width() < 0: + if self.height() <= 0 or self.width() <= 0: return try: self.draw() From 3d1b29b738f5ffb2b83f87ca03f30acd308e30c1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 12 Jul 2024 14:14:47 -0400 Subject: [PATCH 1046/1120] Backport PR #28541: MNT: be more careful about disk I/O failures when writing font cache --- lib/matplotlib/font_manager.py | 8 ++++---- lib/matplotlib/tests/test_font_manager.py | 24 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 813bee6eb623..d9560ec0cc0f 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -965,11 +965,11 @@ def json_dump(data, filename): This function temporarily locks the output file to prevent multiple processes from overwriting one another's output. """ - with cbook._lock_path(filename), open(filename, 'w') as fh: - try: + try: + with cbook._lock_path(filename), open(filename, 'w') as fh: json.dump(data, fh, cls=_JSONEncoder, indent=2) - except OSError as e: - _log.warning('Could not save font_manager cache %s', e) + except OSError as e: + _log.warning('Could not save font_manager cache %s', e) def json_load(filename): diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 9563e4bf0869..776af16eeaaf 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -16,7 +16,7 @@ json_dump, json_load, get_font, is_opentype_cff_font, MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure -from matplotlib.testing import subprocess_run_helper +from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing has_fclist = shutil.which('fc-list') is not None @@ -280,6 +280,28 @@ def test_fontcache_thread_safe(): subprocess_run_helper(_test_threading, timeout=10) +def test_lockfilefailure(tmp_path): + # The logic here: + # 1. get a temp directory from pytest + # 2. import matplotlib which makes sure it exists + # 3. get the cache dir (where we check it is writable) + # 4. make it not writable + # 5. try to write into it via font manager + proc = subprocess_run_for_testing( + [ + sys.executable, + "-c", + "import matplotlib;" + "import os;" + "p = matplotlib.get_cachedir();" + "os.chmod(p, 0o555);" + "import matplotlib.font_manager;" + ], + env={**os.environ, 'MPLCONFIGDIR': str(tmp_path)}, + check=True + ) + + def test_fontentry_dataclass(): fontent = FontEntry(name='font-name') From 8ede65d3a5a0b9f8047f175b632201cb113a151a Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Sat, 13 Jul 2024 12:06:20 -0500 Subject: [PATCH 1047/1120] Backport PR #28526: Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index a4c0c0781813..050ff16cfbbd 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 + uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 + uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 + uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 + uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 + uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From fadfe844470759dfb77f3589c9b2e9d57c0587b8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 13 Jul 2024 11:20:24 -0700 Subject: [PATCH 1048/1120] Backport PR #28534: [BLD] Fix WSL build warning --- src/_image_resample.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_image_resample.h b/src/_image_resample.h index 745fe9f10cd7..a6404092ea2d 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -500,7 +500,7 @@ typedef enum { // T is rgba if and only if it has an T::r field. template struct is_grayscale : std::true_type {}; -template struct is_grayscale : std::false_type {}; +template struct is_grayscale> : std::false_type {}; template From 73df1322432503d6c4e9f6a6d70ec9d936f91a46 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 14 Jul 2024 08:52:46 +0200 Subject: [PATCH 1049/1120] Backport PR #28571: DOC: Add version directive to hatch parameter in stackplot --- lib/matplotlib/stackplot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index dd579bcd5877..43da57c25da5 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -64,6 +64,9 @@ def stackplot(axes, x, *args, of provided *y*, in which case the styles will repeat from the beginning. + .. versionadded:: 3.9 + Support for list input + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER From 26ca2f6013b706cc0d54f02c2d580255c1880c6d Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 17 Jul 2024 07:15:46 -0700 Subject: [PATCH 1050/1120] Backport PR #28582: FIX: make sticky edge tolerance relative to data range --- lib/matplotlib/axes/_base.py | 10 ++-------- .../test_axes/sticky_tolerance_cf.png | Bin 0 -> 6222 bytes lib/matplotlib/tests/test_axes.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f83999436cbb..17feef5b2105 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2962,22 +2962,16 @@ def handle_single_axis( # Prevent margin addition from crossing a sticky value. A small # tolerance must be added due to floating point issues with - # streamplot; it is defined relative to x0, x1, x1-x0 but has + # streamplot; it is defined relative to x1-x0 but has # no absolute term (e.g. "+1e-8") to avoid issues when working with # datasets where all values are tiny (less than 1e-8). - tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0)) + tol = 1e-5 * abs(x1 - x0) # Index of largest element < x0 + tol, if any. i0 = stickies.searchsorted(x0 + tol) - 1 x0bound = stickies[i0] if i0 != -1 else None - # Ensure the boundary acts only if the sticky is the extreme value - if x0bound is not None and x0bound > x0: - x0bound = None # Index of smallest element > x1 - tol, if any. i1 = stickies.searchsorted(x1 - tol) x1bound = stickies[i1] if i1 != len(stickies) else None - # Ensure the boundary acts only if the sticky is the extreme value - if x1bound is not None and x1bound < x1: - x1bound = None # Add the margin in figure space and then transform back, to handle # non-linear scales. diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png new file mode 100644 index 0000000000000000000000000000000000000000..a2e185c2769d6de5ae19ee2154fc6097a9173a32 GIT binary patch literal 6222 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3c0407ee4098..f18e05dc2f1e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -701,6 +701,16 @@ def test_sticky_tolerance(): axs.flat[3].barh(y=1, width=width, left=-20000.1) +@image_comparison(['sticky_tolerance_cf.png'], remove_text=True, style="mpl20") +def test_sticky_tolerance_contourf(): + fig, ax = plt.subplots() + + x = y = [14496.71, 14496.75] + data = [[0, 1], [2, 3]] + + ax.contourf(x, y, data) + + def test_nargs_stem(): with pytest.raises(TypeError, match='0 were given'): # stem() takes 1-3 arguments. From 2f12c03ab9d32b24c65ea186396d02e71d37b802 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 18 Jul 2024 14:04:45 -0500 Subject: [PATCH 1051/1120] Backport PR #28580: Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group --- .github/workflows/cibuildwheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 050ff16cfbbd..ef819ea5a438 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -203,7 +203,7 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 + uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 with: subject-path: dist/matplotlib-* From c37449cf46d2da62c42cf3657c8913d176e6d076 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:50:31 +0200 Subject: [PATCH 1052/1120] Backport PR #28518: [TYP] Fix overload of `pyplot.subplots` --- lib/matplotlib/figure.pyi | 2 +- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index b079312695c1..c31f90b4b2a8 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -132,7 +132,7 @@ class FigureBase(Artist): height_ratios: Sequence[float] | None = ..., subplot_kw: dict[str, Any] | None = ..., gridspec_kw: dict[str, Any] | None = ..., - ) -> Axes | np.ndarray: ... + ) -> Any: ... def delaxes(self, ax: Axes) -> None: ... def clear(self, keep_observers: bool = ...) -> None: ... def clf(self, keep_observers: bool = ...) -> None: ... diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8b4769342c7d..442013f7d21a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1597,7 +1597,7 @@ def subplots( subplot_kw: dict[str, Any] | None = ..., gridspec_kw: dict[str, Any] | None = ..., **fig_kw -) -> tuple[Figure, Axes | np.ndarray]: +) -> tuple[Figure, Any]: ... From 99eaf725ec585ebe3d950492ce141c87b1d3d5c5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 19 Jul 2024 19:18:51 -0400 Subject: [PATCH 1053/1120] Pin PyQt6 back on Ubuntu 20.04 The 6.7.1 wheels on PyPI do not conform to manylinux 2.28 due to requiring glibc 2.35 symbols, and cannot be loaded on Ubuntu 20.04, which has glibc 2.31. So we need to pin that back to avoid test failures. --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index daa07e62b2e5..634c83fa57fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: CFLAGS: "-fno-lto" # Ensure that disabling LTO works. # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.5.1,!=6.6.0' + pyqt6-ver: '!=6.5.1,!=6.6.0,!=6.7.1' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: ubuntu-20.04 @@ -72,7 +72,7 @@ jobs: extra-requirements: '-r requirements/testing/extra.txt' # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.5.1,!=6.6.0' + pyqt6-ver: '!=6.5.1,!=6.6.0,!=6.7.1' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: ubuntu-22.04 From b8be220c6937b10a36700d5aa6b02dae8cc8c458 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 23 Jul 2024 07:17:28 +0200 Subject: [PATCH 1054/1120] Backport PR #28604: cycler signature update. --- lib/matplotlib/axes/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 17feef5b2105..6b3f2750575c 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1548,7 +1548,7 @@ def set_prop_cycle(self, *args, **kwargs): Axes. If multiple properties are given, their value lists must have the same length. This is just a shortcut for explicitly creating a cycler and passing it to the function, i.e. it's short for - ``set_prop_cycle(cycler(label=values label2=values2, ...))``. + ``set_prop_cycle(cycler(label=values, label2=values2, ...))``. Form 3 creates a `~cycler.Cycler` for a single property and set it as the property cycle of the Axes. This form exists for compatibility From 4bedccf11b683b7b9ada06261818ab01ae56c659 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:04:18 +0200 Subject: [PATCH 1055/1120] Backport PR #28621: TYP: Fix a typo in animation.pyi --- lib/matplotlib/animation.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/animation.pyi b/lib/matplotlib/animation.pyi index 56c27a465b7f..345e3c6dbe61 100644 --- a/lib/matplotlib/animation.pyi +++ b/lib/matplotlib/animation.pyi @@ -207,7 +207,7 @@ class FuncAnimation(TimedAnimation): self, fig: Figure, func: Callable[..., Iterable[Artist]], - frames: Iterable[Artist] | int | Callable[[], Generator] | None = ..., + frames: Iterable | int | Callable[[], Generator] | None = ..., init_func: Callable[[], Iterable[Artist]] | None = ..., fargs: tuple[Any, ...] | None = ..., save_count: int | None = ..., From 7457ba43b8362bb43de954fc46897e9da7738e02 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 1 Aug 2024 13:57:56 -0500 Subject: [PATCH 1056/1120] Backport PR #28625: added typing_extensions.Self to _AxesBase.twinx --- lib/matplotlib/axes/_base.pyi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 751dcd248a5c..8cd88a92cc09 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -4,6 +4,7 @@ import datetime from collections.abc import Callable, Iterable, Iterator, Sequence from matplotlib import cbook from matplotlib.artist import Artist +from matplotlib.axes import Axes from matplotlib.axis import XAxis, YAxis, Tick from matplotlib.backend_bases import RendererBase, MouseButton, MouseEvent from matplotlib.cbook import CallbackRegistry @@ -384,8 +385,8 @@ class _AxesBase(martist.Artist): bbox_extra_artists: Sequence[Artist] | None = ..., for_layout_only: bool = ... ) -> Bbox | None: ... - def twinx(self) -> _AxesBase: ... - def twiny(self) -> _AxesBase: ... + def twinx(self) -> Axes: ... + def twiny(self) -> Axes: ... def get_shared_x_axes(self) -> cbook.GrouperView: ... def get_shared_y_axes(self) -> cbook.GrouperView: ... def label_outer(self, remove_inner_ticks: bool = ...) -> None: ... From 6ed77f6b6965408b6a07524e8fbcf77675593de2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 1 Aug 2024 17:31:20 -0400 Subject: [PATCH 1057/1120] Backport PR #28634: Closed open div tag in color.ColorMap._repr_html_ --- lib/matplotlib/colors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 177557b371a6..5f40e7b0fb9a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -960,6 +960,7 @@ def color_block(color): '' '
    ' f'over {color_block(self.get_over())}' + '
    ' '') def copy(self): From 105533f20837e152625269a32e9e11cfe9d93ef9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Aug 2024 01:28:29 -0400 Subject: [PATCH 1058/1120] Backport PR #28644: DOC: Fix matching for version switcher --- doc/conf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index f43806a8b4c0..56e09a24b53a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -496,10 +496,9 @@ def js_tag_with_cache_busting(js): f"https://matplotlib.org/devdocs/_static/switcher.json?{SHA}" ), "version_match": ( - # The start version to show. This must be in switcher.json. - # We either go to 'stable' or to 'devdocs' - 'stable' if matplotlib.__version_info__.releaselevel == 'final' - else 'devdocs') + matplotlib.__version__ + if matplotlib.__version_info__.releaselevel == 'final' + else 'dev') }, "navbar_end": ["theme-switcher", "version-switcher", "mpl_icon_links"], "navbar_persistent": ["search-button"], From ab258b73605635a0c6bc28d3e5e391232abd3292 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Aug 2024 01:28:29 -0400 Subject: [PATCH 1059/1120] Backport PR #28644: DOC: Fix matching for version switcher --- doc/conf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index f43806a8b4c0..56e09a24b53a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -496,10 +496,9 @@ def js_tag_with_cache_busting(js): f"https://matplotlib.org/devdocs/_static/switcher.json?{SHA}" ), "version_match": ( - # The start version to show. This must be in switcher.json. - # We either go to 'stable' or to 'devdocs' - 'stable' if matplotlib.__version_info__.releaselevel == 'final' - else 'devdocs') + matplotlib.__version__ + if matplotlib.__version_info__.releaselevel == 'final' + else 'dev') }, "navbar_end": ["theme-switcher", "version-switcher", "mpl_icon_links"], "navbar_persistent": ["search-button"], From e7aba708ec6c8d834bc250bafa4e405d2736af38 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 3 Aug 2024 05:42:17 -0400 Subject: [PATCH 1060/1120] Backport PR #28649: FIX: improve formatting of image values in cases of singular norms --- lib/matplotlib/artist.py | 4 +++- lib/matplotlib/cbook.py | 4 ++++ lib/matplotlib/tests/test_image.py | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index d5b8631e95df..735c2eb59cf5 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -1346,7 +1346,9 @@ def format_cursor_data(self, data): delta = np.diff( self.norm.boundaries[neigh_idx:cur_idx + 2] ).max() - + elif self.norm.vmin == self.norm.vmax: + # singular norms, use delta of 10% of only value + delta = np.abs(self.norm.vmin * .1) else: # Midpoints of neighboring color intervals. neighbors = self.norm.inverse( diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index a156ac200abf..f5a4199cf9ad 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2252,6 +2252,10 @@ def _g_sig_digits(value, delta): it is known with an error of *delta*. """ if delta == 0: + if value == 0: + # if both value and delta are 0, np.spacing below returns 5e-324 + # which results in rather silly results + return 3 # delta = 0 may occur when trying to format values over a tiny range; # in that case, replace it by the distance to the closest float. delta = abs(np.spacing(value)) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index a043d3aec983..0c032fa5367a 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -411,7 +411,8 @@ def test_cursor_data_nonuniform(xy, data): ([[.123, .987]], "[0.123]"), ([[np.nan, 1, 2]], "[]"), ([[1, 1+1e-15]], "[1.0000000000000000]"), - ([[-1, -1]], "[-1.0000000000000000]"), + ([[-1, -1]], "[-1.0]"), + ([[0, 0]], "[0.00]"), ]) def test_format_cursor_data(data, text): from matplotlib.backend_bases import MouseEvent From e68bfdbea55b60c566de04d41a2b1d95544ea833 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 31 Jul 2024 06:34:27 +0200 Subject: [PATCH 1061/1120] Backport PR #28546: DOC: Clarify/simplify example of multiple images with one colorbar --- .../images_contours_and_fields/multi_image.py | 88 +++++++++++-------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/multi_image.py b/galleries/examples/images_contours_and_fields/multi_image.py index 5634a32abeb9..8be048055dec 100644 --- a/galleries/examples/images_contours_and_fields/multi_image.py +++ b/galleries/examples/images_contours_and_fields/multi_image.py @@ -1,9 +1,19 @@ """ -=============== -Multiple images -=============== +================================= +Multiple images with one colorbar +================================= -Make a set of images with a single colormap, norm, and colorbar. +Use a single colorbar for multiple images. + +Currently, a colorbar can only be connected to one image. The connection +guarantees that the data coloring is consistent with the colormap scale +(i.e. the color of value *x* in the colormap is used for coloring a data +value *x* in the image). + +If we want one colorbar to be representative for multiple images, we have +to explicitly ensure consistent data coloring by using the same data +normalization for all the images. We ensure this by explicitly creating a +``norm`` object that we pass to all the image plotting methods. """ import matplotlib.pyplot as plt @@ -12,47 +22,53 @@ from matplotlib import colors np.random.seed(19680801) -Nr = 3 -Nc = 2 -fig, axs = plt.subplots(Nr, Nc) +datasets = [ + (i+1)/10 * np.random.rand(10, 20) + for i in range(4) +] + +fig, axs = plt.subplots(2, 2) fig.suptitle('Multiple images') -images = [] -for i in range(Nr): - for j in range(Nc): - # Generate data with a range that varies from one plot to the next. - data = ((1 + i + j) / 10) * np.random.rand(10, 20) - images.append(axs[i, j].imshow(data)) - axs[i, j].label_outer() +# create a single norm to be shared across all images +norm = colors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets)) -# Find the min and max of all colors for use in setting the color scale. -vmin = min(image.get_array().min() for image in images) -vmax = max(image.get_array().max() for image in images) -norm = colors.Normalize(vmin=vmin, vmax=vmax) -for im in images: - im.set_norm(norm) +images = [] +for ax, data in zip(axs.flat, datasets): + images.append(ax.imshow(data, norm=norm)) fig.colorbar(images[0], ax=axs, orientation='horizontal', fraction=.1) - -# Make images respond to changes in the norm of other images (e.g. via the -# "edit axis, curves and images parameters" GUI on Qt), but be careful not to -# recurse infinitely! -def update(changed_image): - for im in images: - if (changed_image.get_cmap() != im.get_cmap() - or changed_image.get_clim() != im.get_clim()): - im.set_cmap(changed_image.get_cmap()) - im.set_clim(changed_image.get_clim()) - - -for im in images: - im.callbacks.connect('changed', update) - plt.show() # %% +# The colors are now kept consistent across all images when changing the +# scaling, e.g. through zooming in the colorbar or via the "edit axis, +# curves and images parameters" GUI of the Qt backend. This is sufficient +# for most practical use cases. +# +# Advanced: Additionally sync the colormap +# ---------------------------------------- +# +# Sharing a common norm object guarantees synchronized scaling because scale +# changes modify the norm object in-place and thus propagate to all images +# that use this norm. This approach does not help with synchronizing colormaps +# because changing the colormap of an image (e.g. through the "edit axis, +# curves and images parameters" GUI of the Qt backend) results in the image +# referencing the new colormap object. Thus, the other images are not updated. +# +# To update the other images, sync the +# colormaps using the following code:: +# +# def sync_cmaps(changed_image): +# for im in images: +# if changed_image.get_cmap() != im.get_cmap(): +# im.set_cmap(changed_image.get_cmap()) +# +# for im in images: +# im.callbacks.connect('changed', sync_cmaps) +# # # .. admonition:: References # @@ -63,6 +79,4 @@ def update(changed_image): # - `matplotlib.figure.Figure.colorbar` / `matplotlib.pyplot.colorbar` # - `matplotlib.colors.Normalize` # - `matplotlib.cm.ScalarMappable.set_cmap` -# - `matplotlib.cm.ScalarMappable.set_norm` -# - `matplotlib.cm.ScalarMappable.set_clim` # - `matplotlib.cbook.CallbackRegistry.connect` From 6c7dbc06ebc50d4833189605b4fdc6a30ea8f283 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 31 Jul 2024 06:34:27 +0200 Subject: [PATCH 1062/1120] Backport PR #28546: DOC: Clarify/simplify example of multiple images with one colorbar --- .../images_contours_and_fields/multi_image.py | 88 +++++++++++-------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/multi_image.py b/galleries/examples/images_contours_and_fields/multi_image.py index 5634a32abeb9..8be048055dec 100644 --- a/galleries/examples/images_contours_and_fields/multi_image.py +++ b/galleries/examples/images_contours_and_fields/multi_image.py @@ -1,9 +1,19 @@ """ -=============== -Multiple images -=============== +================================= +Multiple images with one colorbar +================================= -Make a set of images with a single colormap, norm, and colorbar. +Use a single colorbar for multiple images. + +Currently, a colorbar can only be connected to one image. The connection +guarantees that the data coloring is consistent with the colormap scale +(i.e. the color of value *x* in the colormap is used for coloring a data +value *x* in the image). + +If we want one colorbar to be representative for multiple images, we have +to explicitly ensure consistent data coloring by using the same data +normalization for all the images. We ensure this by explicitly creating a +``norm`` object that we pass to all the image plotting methods. """ import matplotlib.pyplot as plt @@ -12,47 +22,53 @@ from matplotlib import colors np.random.seed(19680801) -Nr = 3 -Nc = 2 -fig, axs = plt.subplots(Nr, Nc) +datasets = [ + (i+1)/10 * np.random.rand(10, 20) + for i in range(4) +] + +fig, axs = plt.subplots(2, 2) fig.suptitle('Multiple images') -images = [] -for i in range(Nr): - for j in range(Nc): - # Generate data with a range that varies from one plot to the next. - data = ((1 + i + j) / 10) * np.random.rand(10, 20) - images.append(axs[i, j].imshow(data)) - axs[i, j].label_outer() +# create a single norm to be shared across all images +norm = colors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets)) -# Find the min and max of all colors for use in setting the color scale. -vmin = min(image.get_array().min() for image in images) -vmax = max(image.get_array().max() for image in images) -norm = colors.Normalize(vmin=vmin, vmax=vmax) -for im in images: - im.set_norm(norm) +images = [] +for ax, data in zip(axs.flat, datasets): + images.append(ax.imshow(data, norm=norm)) fig.colorbar(images[0], ax=axs, orientation='horizontal', fraction=.1) - -# Make images respond to changes in the norm of other images (e.g. via the -# "edit axis, curves and images parameters" GUI on Qt), but be careful not to -# recurse infinitely! -def update(changed_image): - for im in images: - if (changed_image.get_cmap() != im.get_cmap() - or changed_image.get_clim() != im.get_clim()): - im.set_cmap(changed_image.get_cmap()) - im.set_clim(changed_image.get_clim()) - - -for im in images: - im.callbacks.connect('changed', update) - plt.show() # %% +# The colors are now kept consistent across all images when changing the +# scaling, e.g. through zooming in the colorbar or via the "edit axis, +# curves and images parameters" GUI of the Qt backend. This is sufficient +# for most practical use cases. +# +# Advanced: Additionally sync the colormap +# ---------------------------------------- +# +# Sharing a common norm object guarantees synchronized scaling because scale +# changes modify the norm object in-place and thus propagate to all images +# that use this norm. This approach does not help with synchronizing colormaps +# because changing the colormap of an image (e.g. through the "edit axis, +# curves and images parameters" GUI of the Qt backend) results in the image +# referencing the new colormap object. Thus, the other images are not updated. +# +# To update the other images, sync the +# colormaps using the following code:: +# +# def sync_cmaps(changed_image): +# for im in images: +# if changed_image.get_cmap() != im.get_cmap(): +# im.set_cmap(changed_image.get_cmap()) +# +# for im in images: +# im.callbacks.connect('changed', sync_cmaps) +# # # .. admonition:: References # @@ -63,6 +79,4 @@ def update(changed_image): # - `matplotlib.figure.Figure.colorbar` / `matplotlib.pyplot.colorbar` # - `matplotlib.colors.Normalize` # - `matplotlib.cm.ScalarMappable.set_cmap` -# - `matplotlib.cm.ScalarMappable.set_norm` -# - `matplotlib.cm.ScalarMappable.set_clim` # - `matplotlib.cbook.CallbackRegistry.connect` From 2a8d1fc7a4a0303c534b6452c752c79122ecf926 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:05:43 +0200 Subject: [PATCH 1063/1120] Backport PR #28650: remove out of date todos on animation.py --- lib/matplotlib/animation.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 1efb72cb52e6..5a4764f1a79f 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -1,23 +1,3 @@ -# TODO: -# * Documentation -- this will need a new section of the User's Guide. -# Both for Animations and just timers. -# - Also need to update -# https://scipy-cookbook.readthedocs.io/items/Matplotlib_Animations.html -# * Blit -# * Currently broken with Qt4 for widgets that don't start on screen -# * Still a few edge cases that aren't working correctly -# * Can this integrate better with existing matplotlib animation artist flag? -# - If animated removes from default draw(), perhaps we could use this to -# simplify initial draw. -# * Example -# * Frameless animation - pure procedural with no loop -# * Need example that uses something like inotify or subprocess -# * Complex syncing examples -# * Movies -# * Can blit be enabled for movies? -# * Need to consider event sources to allow clicking through multiple figures - - import abc import base64 import contextlib From 1bb9c02173f86efee085c9e771a61a449be6e02d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 7 Aug 2024 15:48:15 -0500 Subject: [PATCH 1064/1120] Backport PR #28577: Copy all internals from initial Tick to lazy ones --- lib/matplotlib/axis.py | 33 ++++++++++++++++++++----------- lib/matplotlib/tests/test_axes.py | 22 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 3afc98fac60b..98f7db89b09f 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -33,12 +33,6 @@ _gridline_param_names = ['grid_' + name for name in _line_param_names + _line_param_aliases] -_MARKER_DICT = { - 'out': (mlines.TICKDOWN, mlines.TICKUP), - 'in': (mlines.TICKUP, mlines.TICKDOWN), - 'inout': ('|', '|'), -} - class Tick(martist.Artist): """ @@ -204,18 +198,21 @@ def _set_labelrotation(self, labelrotation): _api.check_in_list(['auto', 'default'], labelrotation=mode) self._labelrotation = (mode, angle) + @property + def _pad(self): + return self._base_pad + self.get_tick_padding() + def _apply_tickdir(self, tickdir): """Set tick direction. Valid values are 'out', 'in', 'inout'.""" - # This method is responsible for updating `_pad`, and, in subclasses, - # for setting the tick{1,2}line markers as well. From the user - # perspective this should always be called through _apply_params, which - # further updates ticklabel positions using the new pads. + # This method is responsible for verifying input and, in subclasses, for setting + # the tick{1,2}line markers. From the user perspective this should always be + # called through _apply_params, which further updates ticklabel positions using + # the new pads. if tickdir is None: tickdir = mpl.rcParams[f'{self.__name__}.direction'] else: _api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir) self._tickdir = tickdir - self._pad = self._base_pad + self.get_tick_padding() def get_tickdir(self): return self._tickdir @@ -425,7 +422,11 @@ def _get_text2_transform(self): def _apply_tickdir(self, tickdir): # docstring inherited super()._apply_tickdir(tickdir) - mark1, mark2 = _MARKER_DICT[self._tickdir] + mark1, mark2 = { + 'out': (mlines.TICKDOWN, mlines.TICKUP), + 'in': (mlines.TICKUP, mlines.TICKDOWN), + 'inout': ('|', '|'), + }[self._tickdir] self.tick1line.set_marker(mark1) self.tick2line.set_marker(mark2) @@ -1617,6 +1618,14 @@ def _copy_tick_props(self, src, dest): dest.tick1line.update_from(src.tick1line) dest.tick2line.update_from(src.tick2line) dest.gridline.update_from(src.gridline) + dest.update_from(src) + dest._loc = src._loc + dest._size = src._size + dest._width = src._width + dest._base_pad = src._base_pad + dest._labelrotation = src._labelrotation + dest._zorder = src._zorder + dest._tickdir = src._tickdir def get_label_text(self): """Get the text of the label.""" diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f18e05dc2f1e..3ec9923c0840 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5631,6 +5631,28 @@ def test_reset_ticks(fig_test, fig_ref): ax.yaxis.reset_ticks() +@mpl.style.context('mpl20') +def test_context_ticks(): + with plt.rc_context({ + 'xtick.direction': 'in', 'xtick.major.size': 30, 'xtick.major.width': 5, + 'xtick.color': 'C0', 'xtick.major.pad': 12, + 'xtick.bottom': True, 'xtick.top': True, + 'xtick.labelsize': 14, 'xtick.labelcolor': 'C1'}): + fig, ax = plt.subplots() + # Draw outside the context so that all-but-first tick are generated with the normal + # mpl20 style in place. + fig.draw_without_rendering() + + first_tick = ax.xaxis.majorTicks[0] + for tick in ax.xaxis.majorTicks[1:]: + assert tick._size == first_tick._size + assert tick._width == first_tick._width + assert tick._base_pad == first_tick._base_pad + assert tick._labelrotation == first_tick._labelrotation + assert tick._zorder == first_tick._zorder + assert tick._tickdir == first_tick._tickdir + + def test_vline_limit(): fig = plt.figure() ax = fig.gca() From c57960c89497163dd80f13244c00a429a5469c62 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 8 Aug 2024 11:31:47 -0500 Subject: [PATCH 1065/1120] Backport PR #28682: Fix warnings from mingw compilers --- src/_c_internal_utils.cpp | 13 ++++++++++--- src/_tkagg.cpp | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index e118183ecc8b..74bb97904f89 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -7,7 +7,14 @@ #define WIN32_LEAN_AND_MEAN // Windows 10, for latest HiDPI API support. #define WINVER 0x0A00 -#define _WIN32_WINNT 0x0A00 +#if defined(_WIN32_WINNT) +#if _WIN32_WINNT < WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT WINVER +#endif +#else +#define _WIN32_WINNT WINVER +#endif #endif #include #ifdef __linux__ @@ -125,7 +132,7 @@ static void mpl_SetForegroundWindow(py::capsule UNUSED_ON_NON_WINDOWS(handle_p)) { #ifdef _WIN32 - if (handle_p.name() != "HWND") { + if (strcmp(handle_p.name(), "HWND") != 0) { throw std::runtime_error("Handle must be a value returned from Win32_GetForegroundWindow"); } HWND handle = static_cast(handle_p.get_pointer()); @@ -158,7 +165,7 @@ mpl_SetProcessDpiAwareness_max(void) DPI_AWARENESS_CONTEXT_SYSTEM_AWARE}; // Win10 if (IsValidDpiAwarenessContextPtr != NULL && SetProcessDpiAwarenessContextPtr != NULL) { - for (int i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { + for (size_t i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { if (IsValidDpiAwarenessContextPtr(ctxs[i])) { SetProcessDpiAwarenessContextPtr(ctxs[i]); break; diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index e35502fe23ff..bfc2253188fd 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -19,7 +19,14 @@ #define WIN32_LEAN_AND_MEAN // Windows 8.1 #define WINVER 0x0603 -#define _WIN32_WINNT 0x0603 +#if defined(_WIN32_WINNT) +#if _WIN32_WINNT < WINVER +#undef _WIN32_WINNT +#define _WIN32_WINNT WINVER +#endif +#else +#define _WIN32_WINNT WINVER +#endif #endif #include From e40125ac7f17de880ebc8177e82a16c194779f2a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jul 2024 15:39:40 -0400 Subject: [PATCH 1066/1120] Backport PR #28293 and #28668: Enable 3.13 wheels and bump cibuildwheel This is the commit message #1: > Merge pull request #28293 from QuLogic/py313 > > BLD: Enable building Python 3.13 wheels for nightlies (cherry picked from commit 725ee995000985b2ee24d1b21cd777e0811272c8) This is the commit message #2: > Merge pull request #28668 from matplotlib/dependabot/github_actions/actions-167bd8b160 > > Bump the actions group with 2 updates (cherry picked from commit fd42e7d63577ef88694913268fe5a5ffd8539431) --- .github/workflows/cibuildwheel.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index ef819ea5a438..4e8ea0ab5bf6 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -134,8 +134,28 @@ jobs: name: cibw-sdist path: dist/ + - name: Build wheels for CPython 3.13 + uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} + env: + CIBW_BUILD: "cp313-* cp313t-*" + # No free-threading wheels for NumPy; musllinux skipped for main builds also. + CIBW_SKIP: "cp313t-win_amd64 *-musllinux_aarch64" + CIBW_BUILD_FRONTEND: + "pip; args: --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" + CIBW_FREE_THREADED_SUPPORT: true + # No free-threading wheels available for aarch64 on Pillow. + CIBW_TEST_SKIP: "cp313t-manylinux_aarch64" + # We need pre-releases to get the nightly wheels. + CIBW_BEFORE_TEST: >- + pip install --pre + --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + contourpy numpy pillow + CIBW_ARCHS: ${{ matrix.cibw_archs }} + - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 + uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +163,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 + uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +171,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 + uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +187,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 + uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -203,7 +223,7 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 with: subject-path: dist/matplotlib-* From 465401ed3000baff801e8f14592754cf80ce25d9 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:15:32 +0200 Subject: [PATCH 1067/1120] Backport PR #28632: DOC: Tell sphinx-gallery to link mpl_toolkits from our build --- doc/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 56e09a24b53a..843766a804ea 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -263,7 +263,8 @@ def _check_dependencies(): 'matplotlib_animations': True, 'min_reported_time': 1, 'plot_gallery': 'True', # sphinx-gallery/913 - 'reference_url': {'matplotlib': None}, + 'reference_url': {'matplotlib': None, 'mpl_toolkits': None}, + 'prefer_full_module': {r'mpl_toolkits\.'}, 'remove_config_comments': True, 'reset_modules': ('matplotlib', clear_basic_units), 'subsection_order': gallery_order_sectionorder, From d88a582fb14accd95e80a25a567b1d1bb08561d0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Fri, 9 Aug 2024 12:17:28 -0500 Subject: [PATCH 1068/1120] Backport PR #27797: DOC: Use video files for saving animations --- doc/conf.py | 7 ++++++- requirements/doc/doc-requirements.txt | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 843766a804ea..8036edec9989 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -194,6 +194,11 @@ def _check_dependencies(): subsectionorder as gallery_order_subsectionorder) from sphinxext.util import clear_basic_units, matplotlib_reduced_latex_scraper +if parse_version(sphinx_gallery.__version__) >= parse_version('0.17.0'): + sg_matplotlib_animations = (True, 'mp4') +else: + sg_matplotlib_animations = True + # The following import is only necessary to monkey patch the signature later on from sphinx_gallery import gen_rst @@ -260,7 +265,7 @@ def _check_dependencies(): 'image_scrapers': (matplotlib_reduced_latex_scraper, ), 'image_srcset': ["2x"], 'junit': '../test-results/sphinx-gallery/junit.xml' if CIRCLECI else '', - 'matplotlib_animations': True, + 'matplotlib_animations': sg_matplotlib_animations, 'min_reported_time': 1, 'plot_gallery': 'True', # sphinx-gallery/913 'reference_url': {'matplotlib': None, 'mpl_toolkits': None}, diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 87bc483b15c0..ee74d02f7146 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -18,6 +18,7 @@ pydata-sphinx-theme~=0.15.0 mpl-sphinx-theme~=3.9.0 pyyaml sphinxcontrib-svg2pdfconverter>=1.1.0 +sphinxcontrib-video>=0.2.1 sphinx-copybutton sphinx-design sphinx-gallery>=0.12.0 From 8a62afa5dd0fd8e9fa4d30f8960f38d882728212 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 8 Aug 2024 15:34:39 -0400 Subject: [PATCH 1069/1120] BLD: Include MSVCP140 runtime statically This should prevent conflicts with other wheels that use the runtime at a different version. --- .github/workflows/cibuildwheel.yml | 10 +++++++++- doc/api/next_api_changes/development/28687-ES.rst | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 doc/api/next_api_changes/development/28687-ES.rst diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 4e8ea0ab5bf6..9de63b14c4fd 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -100,7 +100,15 @@ jobs: CIBW_AFTER_BUILD: >- twine check {wheel} && python {package}/ci/check_wheel_licenses.py {wheel} - CIBW_CONFIG_SETTINGS: setup-args="--vsenv" + # On Windows, we explicitly request MSVC compilers (as GitHub Action runners have + # MinGW on PATH that would be picked otherwise), switch to a static build for + # runtimes, but use dynamic linking for `VCRUNTIME140.dll`, `VCRUNTIME140_1.dll`, + # and the UCRT. This avoids requiring specific versions of `MSVCP140.dll`, while + # keeping shared state with the rest of the Python process/extensions. + CIBW_CONFIG_SETTINGS_WINDOWS: >- + setup-args="--vsenv" + setup-args="-Db_vscrt=mt" + setup-args="-Dcpp_link_args=['ucrt.lib','vcruntime.lib','/nodefaultlib:libucrt.lib','/nodefaultlib:libvcruntime.lib']" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- diff --git a/doc/api/next_api_changes/development/28687-ES.rst b/doc/api/next_api_changes/development/28687-ES.rst new file mode 100644 index 000000000000..339dafdd05d0 --- /dev/null +++ b/doc/api/next_api_changes/development/28687-ES.rst @@ -0,0 +1,10 @@ +Windows wheel runtime bundling made static +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In 3.7.0, the MSVC runtime DLL was bundled in wheels to enable importing Matplotlib on +systems that do not have it installed. However, this could cause inconsistencies with +other wheels that did the same, and trigger random crashes depending on import order. See +`this issue `_ for further +details. + +Since 3.9.2, wheels now bundle the MSVC runtime DLL statically to avoid such issues. From 056f307c1eaceb7615594e02b03336ec047ef02d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 12 Aug 2024 19:25:14 -0400 Subject: [PATCH 1070/1120] DOC: Create release notes for 3.9.2 --- .../api_changes_3.9.2.rst} | 6 + doc/users/github_stats.rst | 236 ++++++------------ .../prev_whats_new/github_stats_3.9.1.rst | 192 ++++++++++++++ doc/users/release_notes.rst | 2 + 4 files changed, 270 insertions(+), 166 deletions(-) rename doc/api/{next_api_changes/development/28687-ES.rst => prev_api_changes/api_changes_3.9.2.rst} (88%) create mode 100644 doc/users/prev_whats_new/github_stats_3.9.1.rst diff --git a/doc/api/next_api_changes/development/28687-ES.rst b/doc/api/prev_api_changes/api_changes_3.9.2.rst similarity index 88% rename from doc/api/next_api_changes/development/28687-ES.rst rename to doc/api/prev_api_changes/api_changes_3.9.2.rst index 339dafdd05d0..4c2a69634502 100644 --- a/doc/api/next_api_changes/development/28687-ES.rst +++ b/doc/api/prev_api_changes/api_changes_3.9.2.rst @@ -1,3 +1,9 @@ +API Changes for 3.9.2 +===================== + +Development +----------- + Windows wheel runtime bundling made static ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 0c8f29687afb..00c3e5d656a1 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,195 +1,99 @@ .. _github-stats: -GitHub statistics for 3.9.1 (Jul 04, 2024) +GitHub statistics for 3.9.2 (Aug 12, 2024) ========================================== -GitHub statistics for 2024/05/15 (tag: v3.9.0) - 2024/07/04 +GitHub statistics for 2024/07/04 (tag: v3.9.1) - 2024/08/12 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 30 issues and merged 111 pull requests. -The full list can be seen `on GitHub `__ +We closed 9 issues and merged 45 pull requests. +The full list can be seen `on GitHub `__ -The following 29 authors contributed 184 commits. +The following 20 authors contributed 67 commits. -* Antony Lee -* Brigitta Sipőcz -* Christian Mattsson -* dale +* Adam J. Stewart +* Anthony Lee +* Caitlin Hathaway +* ClarkeAC * dependabot[bot] * Elliott Sales de Andrade -* Eytan Adler +* Filippo Balzaretti * Greg Lucas -* haaris * hannah * Ian Thomas -* Illviljan -* K900 +* Jody Klymak * Kyle Sunden -* Lumberbot (aka Jack) -* malhar2460 -* Matthew Feickert -* Melissa Weber Mendonça -* MischaMegens2 * Oscar Gustafsson +* Randolf Scholz +* Refael Ackermann * Ruth Comer * Scott Shambaugh -* simond07 -* SjoerdB93 -* Takumasa N -* Takumasa N. -* Takumasa Nakamura +* Sean Smith * Thomas A Caswell * Tim Hoffmann GitHub issues and pull requests: -Pull Requests (111): +Pull Requests (45): -* :ghpull:`28507`: Backport PR #28430 on branch v3.9.x (Fix pickling of AxesWidgets.) -* :ghpull:`28506`: Backport PR #28451 on branch v3.9.x (Fix GTK cairo backends) -* :ghpull:`28430`: Fix pickling of AxesWidgets. -* :ghpull:`25861`: Fix Hidpi scaling for GTK4Cairo -* :ghpull:`28451`: Fix GTK cairo backends -* :ghpull:`28499`: Backport PR #28498 on branch v3.9.x (Don't fail if we can't query system fonts on macOS) -* :ghpull:`28498`: Don't fail if we can't query system fonts on macOS -* :ghpull:`28491`: Backport PR #28487 on branch v3.9.x (Fix autoscaling with axhspan) -* :ghpull:`28490`: Backport PR #28486 on branch v3.9.x (Fix CompositeGenericTransform.contains_branch_seperately) -* :ghpull:`28487`: Fix autoscaling with axhspan -* :ghpull:`28486`: Fix CompositeGenericTransform.contains_branch_seperately -* :ghpull:`28483`: Backport PR #28393 on branch v3.9.x (Make sticky edges only apply if the sticky edge is the most extreme limit point) -* :ghpull:`28482`: Backport PR #28473 on branch v3.9.x (Do not lowercase module:// backends) -* :ghpull:`28393`: Make sticky edges only apply if the sticky edge is the most extreme limit point -* :ghpull:`28473`: Do not lowercase module:// backends -* :ghpull:`28480`: Backport PR #28474 on branch v3.9.x (Fix typing and docs for containers) -* :ghpull:`28479`: Backport PR #28397 (FIX: stale root Figure when adding/updating subfigures) -* :ghpull:`28474`: Fix typing and docs for containers -* :ghpull:`28472`: Backport PR #28289 on branch v3.9.x (Promote mpltype Sphinx role to a public extension) -* :ghpull:`28471`: Backport PR #28342 on branch v3.9.x (DOC: Document the parameter *position* of apply_aspect() as internal) -* :ghpull:`28470`: Backport PR #28398 on branch v3.9.x (Add GIL Release to flush_events in macosx backend) -* :ghpull:`28469`: Backport PR #28355 on branch v3.9.x (MNT: Re-add matplotlib.cm.get_cmap) -* :ghpull:`28397`: FIX: stale root Figure when adding/updating subfigures -* :ghpull:`28289`: Promote mpltype Sphinx role to a public extension -* :ghpull:`28342`: DOC: Document the parameter *position* of apply_aspect() as internal -* :ghpull:`28398`: Add GIL Release to flush_events in macosx backend -* :ghpull:`28355`: MNT: Re-add matplotlib.cm.get_cmap -* :ghpull:`28468`: Backport PR #28465 on branch v3.9.x (Fix pickling of SubFigures) -* :ghpull:`28465`: Fix pickling of SubFigures -* :ghpull:`28462`: Backport PR #28440 on branch v3.9.x (DOC: Add note about simplification of to_polygons) -* :ghpull:`28460`: Backport PR #28459 on branch v3.9.x (DOC: Document kwargs scope for tick setter functions) -* :ghpull:`28461`: Backport PR #28458 on branch v3.9.x (Correct numpy dtype comparisons in image_resample) -* :ghpull:`28440`: DOC: Add note about simplification of to_polygons -* :ghpull:`28458`: Correct numpy dtype comparisons in image_resample -* :ghpull:`28459`: DOC: Document kwargs scope for tick setter functions -* :ghpull:`28450`: Backport of 28371 and 28411 -* :ghpull:`28446`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection -* :ghpull:`28445`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection) -* :ghpull:`28438`: Backport PR #28436 on branch v3.9.x (Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)``) -* :ghpull:`28403`: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection -* :ghpull:`28443`: Backport PR #28441 on branch v3.9.x (MNT: Update basic units example to work with numpy 2.0) -* :ghpull:`28441`: MNT: Update basic units example to work with numpy 2.0 -* :ghpull:`28436`: Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)`` -* :ghpull:`28426`: Backport PR #28425 on branch v3.9.x (Fix Circle yaml line length) -* :ghpull:`28427`: Fix circleci yaml -* :ghpull:`28425`: Fix Circle yaml line length -* :ghpull:`28422`: Backport PR #28401 on branch v3.9.x (FIX: Fix text wrapping) -* :ghpull:`28424`: Backport PR #28423 on branch v3.9.x (Update return type for Axes.axhspan and Axes.axvspan) -* :ghpull:`28423`: Update return type for Axes.axhspan and Axes.axvspan -* :ghpull:`28401`: FIX: Fix text wrapping -* :ghpull:`28419`: Backport PR #28414 on branch v3.9.x (Clean up obsolete widget code) -* :ghpull:`28411`: Bump the actions group with 3 updates -* :ghpull:`28414`: Clean up obsolete widget code -* :ghpull:`28415`: Backport PR #28413 on branch v3.9.x (CI: update action that got moved org) -* :ghpull:`28413`: CI: update action that got moved org -* :ghpull:`28392`: Backport PR #28388 on branch v3.9.x (Allow duplicate (name, value) entry points for backends) -* :ghpull:`28362`: Backport PR #28337 on branch v3.9.x (Bump the actions group across 1 directory with 3 updates) -* :ghpull:`28388`: Allow duplicate (name, value) entry points for backends -* :ghpull:`28389`: Backport PR #28380 on branch v3.9.x (Remove outdated docstring section in RendererBase.draw_text.) -* :ghpull:`28380`: Remove outdated docstring section in RendererBase.draw_text. -* :ghpull:`28385`: Backport PR #28377 on branch v3.9.x (DOC: Clarify scope of wrap.) -* :ghpull:`28377`: DOC: Clarify scope of wrap. -* :ghpull:`28368`: Backport PR #28359 on branch v3.9.x (Document that axes unsharing is impossible.) -* :ghpull:`28359`: Document that axes unsharing is impossible. -* :ghpull:`28337`: Bump the actions group across 1 directory with 3 updates -* :ghpull:`28351`: Backport PR #28307 on branch v3.9.x (DOC: New color line by value example) -* :ghpull:`28307`: DOC: New color line by value example -* :ghpull:`28339`: Backport PR #28336 on branch v3.9.x (DOC: Add version warning banner for docs versions different from stable) -* :ghpull:`28336`: DOC: Add version warning banner for docs versions different from stable -* :ghpull:`28334`: Backport PR #28332 on branch v3.9.x (Call IPython.enable_gui when install repl displayhook) -* :ghpull:`28332`: Call IPython.enable_gui when install repl displayhook -* :ghpull:`28331`: Backport PR #28329 on branch v3.9.x (DOC: Add example for 3D intersecting planes) -* :ghpull:`28329`: DOC: Add example for 3D intersecting planes -* :ghpull:`28327`: Backport PR #28292 on branch v3.9.x (Resolve MaxNLocator IndexError when no large steps) -* :ghpull:`28292`: Resolve MaxNLocator IndexError when no large steps -* :ghpull:`28326`: Backport PR #28041 on branch v3.9.x ([BUG]: Shift box_aspect according to vertical_axis) -* :ghpull:`28041`: [BUG]: Shift box_aspect according to vertical_axis -* :ghpull:`28320`: Backport PR #27001 on branch v3.9.x ([TYP] Add overload of ``pyplot.subplots``) -* :ghpull:`27001`: [TYP] Add overload of ``pyplot.subplots`` -* :ghpull:`28318`: Backport PR #28273 on branch v3.9.x (CI: Add GitHub artifact attestations to package distribution) -* :ghpull:`28273`: CI: Add GitHub artifact attestations to package distribution -* :ghpull:`28305`: Backport PR #28303 on branch v3.9.x (Removed drawedges repeated definition from function doc string) -* :ghpull:`28303`: Removed drawedges repeated definition from function doc string -* :ghpull:`28299`: Backport PR #28297 on branch v3.9.x (Solved #28296 Added missing comma) -* :ghpull:`28297`: Solved #28296 Added missing comma -* :ghpull:`28294`: Backport PR #28261 on branch v3.9.x (Correct roll angle units, issue #28256) -* :ghpull:`28261`: Correct roll angle units, issue #28256 -* :ghpull:`28283`: Backport PR #28280 on branch v3.9.x (DOC: Add an example for 2D images in 3D plots) -* :ghpull:`28280`: DOC: Add an example for 2D images in 3D plots -* :ghpull:`28278`: Backport PR #28272 on branch v3.9.x (BLD: Move macos builders from 11 to 12) -* :ghpull:`28277`: Backport PR #28274 on branch v3.9.x (ci: Remove deprecated codeql option) -* :ghpull:`28272`: BLD: Move macos builders from 11 to 12 -* :ghpull:`28274`: ci: Remove deprecated codeql option -* :ghpull:`28270`: Backport PR #28269 on branch v3.9.x (Handle GetForegroundWindow() returning NULL.) -* :ghpull:`28269`: Handle GetForegroundWindow() returning NULL. -* :ghpull:`28266`: Backport PR #28257 on branch v3.9.x (Clean up some Meson-related leftovers) -* :ghpull:`28257`: Clean up some Meson-related leftovers -* :ghpull:`28255`: Backport PR #28254 on branch v3.9.x ([DOC] plot type heading consistency) -* :ghpull:`28254`: [DOC] plot type heading consistency -* :ghpull:`28253`: Backport PR #28252 on branch v3.9.x (DOC: Flip the imshow plot types example to match the other examples) -* :ghpull:`28252`: DOC: Flip the imshow plot types example to match the other examples -* :ghpull:`28247`: Backport PR #28230 on branch v3.9.x (Add extra imports to improve typing) -* :ghpull:`28230`: Add extra imports to improve typing -* :ghpull:`28246`: Backport PR #28243 on branch v3.9.x (DOC: Add more 3D plot types) -* :ghpull:`28243`: DOC: Add more 3D plot types -* :ghpull:`28241`: Backport PR #28219 on branch v3.9.x (Bump the actions group with 2 updates) -* :ghpull:`28219`: Bump the actions group with 2 updates -* :ghpull:`28237`: Backport PR #28233 on branch v3.9.x (CI: Fix font install on macOS/Homebrew) -* :ghpull:`28236`: Backport PR #28231 on branch v3.9.x (DOC: we do not need the blit call in on_draw) -* :ghpull:`28233`: CI: Fix font install on macOS/Homebrew -* :ghpull:`28231`: DOC: we do not need the blit call in on_draw +* :ghpull:`28687`: BLD: Include MSVCP140 runtime statically +* :ghpull:`28679`: Run delvewheel with path to required msvcp140.dll +* :ghpull:`28695`: Backport PR #27797 on branch v3.9.x (DOC: Use video files for saving animations) +* :ghpull:`28688`: Backport PR #28293 and #28668: Enable 3.13 wheels and bump cibuildwheel +* :ghpull:`27797`: DOC: Use video files for saving animations +* :ghpull:`28692`: Backport PR #28632 on branch v3.9.x (DOC: Tell sphinx-gallery to link mpl_toolkits from our build) +* :ghpull:`28632`: DOC: Tell sphinx-gallery to link mpl_toolkits from our build +* :ghpull:`28668`: Bump the actions group with 2 updates +* :ghpull:`28686`: Backport PR #28682 on branch v3.9.x (Fix warnings from mingw compilers) +* :ghpull:`28682`: Fix warnings from mingw compilers +* :ghpull:`28676`: Backport PR #28577 on branch v3.9.x (Copy all internals from initial Tick to lazy ones) +* :ghpull:`28577`: Copy all internals from initial Tick to lazy ones +* :ghpull:`28674`: Backport PR #28650 on branch v3.9.x (remove out of date todos on animation.py) +* :ghpull:`28650`: remove out of date todos on animation.py +* :ghpull:`28656`: Backport PR #28649 on branch v3.9.x (FIX: improve formatting of image values in cases of singular norms) +* :ghpull:`28665`: Backport PR #28546 on branch v3.9.x (DOC: Clarify/simplify example of multiple images with one colorbar) +* :ghpull:`28649`: FIX: improve formatting of image values in cases of singular norms +* :ghpull:`28635`: BLD: windows wheels +* :ghpull:`28645`: Backport PR #28644 on branch v3.9.x (DOC: Fix matching for version switcher) +* :ghpull:`28640`: Backport PR #28634 on branch v3.9.x (Closed open div tag in color.ColorMap._repr_html_) +* :ghpull:`28634`: Closed open div tag in color.ColorMap._repr_html_ +* :ghpull:`28636`: Backport PR #28625 on branch v3.9.x (added typing_extensions.Self to _AxesBase.twinx) +* :ghpull:`28625`: added typing_extensions.Self to _AxesBase.twinx +* :ghpull:`28622`: Backport PR #28621 on branch v3.9.x (TYP: Fix a typo in animation.pyi) +* :ghpull:`28621`: TYP: Fix a typo in animation.pyi +* :ghpull:`28605`: Backport PR #28604 on branch v3.9.x (cycler signature update.) +* :ghpull:`28604`: cycler signature update. +* :ghpull:`28598`: Pin PyQt6 back on Ubuntu 20.04 +* :ghpull:`28596`: Backport PR #28518 on branch v3.9.x ([TYP] Fix overload of ``pyplot.subplots``) +* :ghpull:`28518`: [TYP] Fix overload of ``pyplot.subplots`` +* :ghpull:`28591`: Backport PR #28580 on branch v3.9.x (Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group) +* :ghpull:`28580`: Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group +* :ghpull:`28586`: Backport PR #28582 on branch v3.9.x (FIX: make sticky edge tolerance relative to data range) +* :ghpull:`28582`: FIX: make sticky edge tolerance relative to data range +* :ghpull:`28572`: Backport PR #28571 on branch v3.9.x (DOC: Add version directive to hatch parameter in stackplot) +* :ghpull:`28571`: DOC: Add version directive to hatch parameter in stackplot +* :ghpull:`28564`: Backport PR #28534 on branch v3.9.x ([BLD] Fix WSL build warning) +* :ghpull:`28563`: Backport PR #28526 on branch v3.9.x (Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group) +* :ghpull:`28534`: [BLD] Fix WSL build warning +* :ghpull:`28526`: Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group +* :ghpull:`28552`: Backport PR #28541 on branch v3.9.x (MNT: be more careful about disk I/O failures when writing font cache) +* :ghpull:`28541`: MNT: be more careful about disk I/O failures when writing font cache +* :ghpull:`28524`: Backport PR #28523 on branch v3.9.x (Fix value error when set widget size to zero while using FigureCanvasQT ) +* :ghpull:`28523`: Fix value error when set widget size to zero while using FigureCanvasQT +* :ghpull:`28519`: Backport PR #28517 on branch v3.9.x (DOC: better cross referencing for animations) -Issues (30): +Issues (9): -* :ghissue:`22482`: [ENH]: pickle (or save) matplotlib figure with insteractive slider -* :ghissue:`25847`: [Bug]: Graph gets cut off with scaled resolution using gtk4cairo backend -* :ghissue:`28341`: [Bug]: Incorrect X-axis scaling with date values -* :ghissue:`28383`: [Bug]: axvspan no longer participating in limit calculations -* :ghissue:`28223`: [Bug]: Inconsistent Visualization of Intervals in ax.barh for Different Duration Widths -* :ghissue:`28432`: [Bug]: Backend name specified as module gets lowercased since 3.9 -* :ghissue:`28467`: [Bug]: Incorrect type stub for ``ErrorbarContainer``'s ``lines`` attribute. -* :ghissue:`28384`: [Bug]: subfigure artists not drawn interactively -* :ghissue:`28234`: [Bug]: mpltype custom role breaks sphinx build for third-party projects that have intersphinx links to matplotlib -* :ghissue:`28464`: [Bug]: figure with subfigures cannot be pickled -* :ghissue:`28448`: [Bug]: Making an RGB image from pickled data throws error -* :ghissue:`23317`: [Bug]: ``add_collection3d`` does not update view limits -* :ghissue:`17130`: autoscale_view is not working with Line3DCollection -* :ghissue:`28434`: [Bug]: Setting exactly 2 colors with tuple in ``plot`` method gives confusing error -* :ghissue:`28417`: [Doc]: axhspan and axvspan now return Rectangles, not Polygons. -* :ghissue:`28378`: [ENH]: Switch text wrapping boundary to subfigure -* :ghissue:`28404`: [Doc]: matplotlib.widgets.CheckButtons no longer has .rectangles attribute, needs removed. -* :ghissue:`28367`: [Bug]: Backend entry points can be erroneously duplicated -* :ghissue:`28358`: [Bug]: Labels don't get wrapped when set_yticks() is used in subplots -* :ghissue:`28374`: [Bug]: rcParam ``tk.window_focus: True`` is causes crash on Linux in version 3.9.0. -* :ghissue:`28324`: [Bug]: show(block=False) freezes -* :ghissue:`28239`: [Doc]: Gallery example showing 3D slice planes -* :ghissue:`27603`: [Bug]: _raw_ticker() istep -* :ghissue:`24328`: [Bug]: class Axes3D.set_box_aspect() sets wrong aspect ratios when Axes3D.view_init( vertical_axis='y') is enabled. -* :ghissue:`28221`: [Doc]: drawedges attribute described twice in matplotlib.colorbar documentation -* :ghissue:`28296`: [Doc]: Missing comma -* :ghissue:`28256`: [Bug]: axes3d.py's _on_move() converts the roll angle to radians, but then passes it to view_init() as if it were still in degrees -* :ghissue:`28267`: [Bug]: for Python 3.11.9 gor error ValueError: PyCapsule_New called with null pointer -* :ghissue:`28022`: [Bug]: Type of Axes is unknown pyright -* :ghissue:`28002`: Segfault from path editor example with QtAgg +* :ghissue:`28551`: [Bug]: Possible issue with Matplotlib 3.9.1 wheel on Windows only +* :ghissue:`28250`: [Doc]: Sphinx gallery links mispointed for Axes3D methods +* :ghissue:`28574`: [Bug]: Nondeterministic behavior with subplot spacing and constrained layout +* :ghissue:`28626`: [Doc]: Remove old TODO's from animation.py +* :ghissue:`28648`: [Bug]: format_image_data on an image of only zeros produses a large number of zeros +* :ghissue:`28624`: [Bug]: Bad type hint in ``_AxesBase.twinx()`` +* :ghissue:`28567`: [Bug]: sticky edge related changes for datetime plots +* :ghissue:`28533`: [Doc]: Stackplot hatch functionality has version dependencies +* :ghissue:`28538`: [Bug]: Permission denied when importing matplotlib.pyplot Previous GitHub statistics diff --git a/doc/users/prev_whats_new/github_stats_3.9.1.rst b/doc/users/prev_whats_new/github_stats_3.9.1.rst new file mode 100644 index 000000000000..1bd7860546cb --- /dev/null +++ b/doc/users/prev_whats_new/github_stats_3.9.1.rst @@ -0,0 +1,192 @@ +.. _github-stats-3-9-1: + +GitHub statistics for 3.9.1 (Jul 04, 2024) +========================================== + +GitHub statistics for 2024/05/15 (tag: v3.9.0) - 2024/07/04 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 30 issues and merged 111 pull requests. +The full list can be seen `on GitHub `__ + +The following 29 authors contributed 184 commits. + +* Antony Lee +* Brigitta Sipőcz +* Christian Mattsson +* dale +* dependabot[bot] +* Elliott Sales de Andrade +* Eytan Adler +* Greg Lucas +* haaris +* hannah +* Ian Thomas +* Illviljan +* K900 +* Kyle Sunden +* Lumberbot (aka Jack) +* malhar2460 +* Matthew Feickert +* Melissa Weber Mendonça +* MischaMegens2 +* Oscar Gustafsson +* Ruth Comer +* Scott Shambaugh +* simond07 +* SjoerdB93 +* Takumasa N +* Takumasa N. +* Takumasa Nakamura +* Thomas A Caswell +* Tim Hoffmann + +GitHub issues and pull requests: + +Pull Requests (111): + +* :ghpull:`28507`: Backport PR #28430 on branch v3.9.x (Fix pickling of AxesWidgets.) +* :ghpull:`28506`: Backport PR #28451 on branch v3.9.x (Fix GTK cairo backends) +* :ghpull:`28430`: Fix pickling of AxesWidgets. +* :ghpull:`25861`: Fix Hidpi scaling for GTK4Cairo +* :ghpull:`28451`: Fix GTK cairo backends +* :ghpull:`28499`: Backport PR #28498 on branch v3.9.x (Don't fail if we can't query system fonts on macOS) +* :ghpull:`28498`: Don't fail if we can't query system fonts on macOS +* :ghpull:`28491`: Backport PR #28487 on branch v3.9.x (Fix autoscaling with axhspan) +* :ghpull:`28490`: Backport PR #28486 on branch v3.9.x (Fix CompositeGenericTransform.contains_branch_seperately) +* :ghpull:`28487`: Fix autoscaling with axhspan +* :ghpull:`28486`: Fix CompositeGenericTransform.contains_branch_seperately +* :ghpull:`28483`: Backport PR #28393 on branch v3.9.x (Make sticky edges only apply if the sticky edge is the most extreme limit point) +* :ghpull:`28482`: Backport PR #28473 on branch v3.9.x (Do not lowercase module:// backends) +* :ghpull:`28393`: Make sticky edges only apply if the sticky edge is the most extreme limit point +* :ghpull:`28473`: Do not lowercase module:// backends +* :ghpull:`28480`: Backport PR #28474 on branch v3.9.x (Fix typing and docs for containers) +* :ghpull:`28479`: Backport PR #28397 (FIX: stale root Figure when adding/updating subfigures) +* :ghpull:`28474`: Fix typing and docs for containers +* :ghpull:`28472`: Backport PR #28289 on branch v3.9.x (Promote mpltype Sphinx role to a public extension) +* :ghpull:`28471`: Backport PR #28342 on branch v3.9.x (DOC: Document the parameter *position* of apply_aspect() as internal) +* :ghpull:`28470`: Backport PR #28398 on branch v3.9.x (Add GIL Release to flush_events in macosx backend) +* :ghpull:`28469`: Backport PR #28355 on branch v3.9.x (MNT: Re-add matplotlib.cm.get_cmap) +* :ghpull:`28397`: FIX: stale root Figure when adding/updating subfigures +* :ghpull:`28289`: Promote mpltype Sphinx role to a public extension +* :ghpull:`28342`: DOC: Document the parameter *position* of apply_aspect() as internal +* :ghpull:`28398`: Add GIL Release to flush_events in macosx backend +* :ghpull:`28355`: MNT: Re-add matplotlib.cm.get_cmap +* :ghpull:`28468`: Backport PR #28465 on branch v3.9.x (Fix pickling of SubFigures) +* :ghpull:`28465`: Fix pickling of SubFigures +* :ghpull:`28462`: Backport PR #28440 on branch v3.9.x (DOC: Add note about simplification of to_polygons) +* :ghpull:`28460`: Backport PR #28459 on branch v3.9.x (DOC: Document kwargs scope for tick setter functions) +* :ghpull:`28461`: Backport PR #28458 on branch v3.9.x (Correct numpy dtype comparisons in image_resample) +* :ghpull:`28440`: DOC: Add note about simplification of to_polygons +* :ghpull:`28458`: Correct numpy dtype comparisons in image_resample +* :ghpull:`28459`: DOC: Document kwargs scope for tick setter functions +* :ghpull:`28450`: Backport of 28371 and 28411 +* :ghpull:`28446`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28445`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection) +* :ghpull:`28438`: Backport PR #28436 on branch v3.9.x (Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)``) +* :ghpull:`28403`: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28443`: Backport PR #28441 on branch v3.9.x (MNT: Update basic units example to work with numpy 2.0) +* :ghpull:`28441`: MNT: Update basic units example to work with numpy 2.0 +* :ghpull:`28436`: Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)`` +* :ghpull:`28426`: Backport PR #28425 on branch v3.9.x (Fix Circle yaml line length) +* :ghpull:`28427`: Fix circleci yaml +* :ghpull:`28425`: Fix Circle yaml line length +* :ghpull:`28422`: Backport PR #28401 on branch v3.9.x (FIX: Fix text wrapping) +* :ghpull:`28424`: Backport PR #28423 on branch v3.9.x (Update return type for Axes.axhspan and Axes.axvspan) +* :ghpull:`28423`: Update return type for Axes.axhspan and Axes.axvspan +* :ghpull:`28401`: FIX: Fix text wrapping +* :ghpull:`28419`: Backport PR #28414 on branch v3.9.x (Clean up obsolete widget code) +* :ghpull:`28411`: Bump the actions group with 3 updates +* :ghpull:`28414`: Clean up obsolete widget code +* :ghpull:`28415`: Backport PR #28413 on branch v3.9.x (CI: update action that got moved org) +* :ghpull:`28413`: CI: update action that got moved org +* :ghpull:`28392`: Backport PR #28388 on branch v3.9.x (Allow duplicate (name, value) entry points for backends) +* :ghpull:`28362`: Backport PR #28337 on branch v3.9.x (Bump the actions group across 1 directory with 3 updates) +* :ghpull:`28388`: Allow duplicate (name, value) entry points for backends +* :ghpull:`28389`: Backport PR #28380 on branch v3.9.x (Remove outdated docstring section in RendererBase.draw_text.) +* :ghpull:`28380`: Remove outdated docstring section in RendererBase.draw_text. +* :ghpull:`28385`: Backport PR #28377 on branch v3.9.x (DOC: Clarify scope of wrap.) +* :ghpull:`28377`: DOC: Clarify scope of wrap. +* :ghpull:`28368`: Backport PR #28359 on branch v3.9.x (Document that axes unsharing is impossible.) +* :ghpull:`28359`: Document that axes unsharing is impossible. +* :ghpull:`28337`: Bump the actions group across 1 directory with 3 updates +* :ghpull:`28351`: Backport PR #28307 on branch v3.9.x (DOC: New color line by value example) +* :ghpull:`28307`: DOC: New color line by value example +* :ghpull:`28339`: Backport PR #28336 on branch v3.9.x (DOC: Add version warning banner for docs versions different from stable) +* :ghpull:`28336`: DOC: Add version warning banner for docs versions different from stable +* :ghpull:`28334`: Backport PR #28332 on branch v3.9.x (Call IPython.enable_gui when install repl displayhook) +* :ghpull:`28332`: Call IPython.enable_gui when install repl displayhook +* :ghpull:`28331`: Backport PR #28329 on branch v3.9.x (DOC: Add example for 3D intersecting planes) +* :ghpull:`28329`: DOC: Add example for 3D intersecting planes +* :ghpull:`28327`: Backport PR #28292 on branch v3.9.x (Resolve MaxNLocator IndexError when no large steps) +* :ghpull:`28292`: Resolve MaxNLocator IndexError when no large steps +* :ghpull:`28326`: Backport PR #28041 on branch v3.9.x ([BUG]: Shift box_aspect according to vertical_axis) +* :ghpull:`28041`: [BUG]: Shift box_aspect according to vertical_axis +* :ghpull:`28320`: Backport PR #27001 on branch v3.9.x ([TYP] Add overload of ``pyplot.subplots``) +* :ghpull:`27001`: [TYP] Add overload of ``pyplot.subplots`` +* :ghpull:`28318`: Backport PR #28273 on branch v3.9.x (CI: Add GitHub artifact attestations to package distribution) +* :ghpull:`28273`: CI: Add GitHub artifact attestations to package distribution +* :ghpull:`28305`: Backport PR #28303 on branch v3.9.x (Removed drawedges repeated definition from function doc string) +* :ghpull:`28303`: Removed drawedges repeated definition from function doc string +* :ghpull:`28299`: Backport PR #28297 on branch v3.9.x (Solved #28296 Added missing comma) +* :ghpull:`28297`: Solved #28296 Added missing comma +* :ghpull:`28294`: Backport PR #28261 on branch v3.9.x (Correct roll angle units, issue #28256) +* :ghpull:`28261`: Correct roll angle units, issue #28256 +* :ghpull:`28283`: Backport PR #28280 on branch v3.9.x (DOC: Add an example for 2D images in 3D plots) +* :ghpull:`28280`: DOC: Add an example for 2D images in 3D plots +* :ghpull:`28278`: Backport PR #28272 on branch v3.9.x (BLD: Move macos builders from 11 to 12) +* :ghpull:`28277`: Backport PR #28274 on branch v3.9.x (ci: Remove deprecated codeql option) +* :ghpull:`28272`: BLD: Move macos builders from 11 to 12 +* :ghpull:`28274`: ci: Remove deprecated codeql option +* :ghpull:`28270`: Backport PR #28269 on branch v3.9.x (Handle GetForegroundWindow() returning NULL.) +* :ghpull:`28269`: Handle GetForegroundWindow() returning NULL. +* :ghpull:`28266`: Backport PR #28257 on branch v3.9.x (Clean up some Meson-related leftovers) +* :ghpull:`28257`: Clean up some Meson-related leftovers +* :ghpull:`28255`: Backport PR #28254 on branch v3.9.x ([DOC] plot type heading consistency) +* :ghpull:`28254`: [DOC] plot type heading consistency +* :ghpull:`28253`: Backport PR #28252 on branch v3.9.x (DOC: Flip the imshow plot types example to match the other examples) +* :ghpull:`28252`: DOC: Flip the imshow plot types example to match the other examples +* :ghpull:`28247`: Backport PR #28230 on branch v3.9.x (Add extra imports to improve typing) +* :ghpull:`28230`: Add extra imports to improve typing +* :ghpull:`28246`: Backport PR #28243 on branch v3.9.x (DOC: Add more 3D plot types) +* :ghpull:`28243`: DOC: Add more 3D plot types +* :ghpull:`28241`: Backport PR #28219 on branch v3.9.x (Bump the actions group with 2 updates) +* :ghpull:`28219`: Bump the actions group with 2 updates +* :ghpull:`28237`: Backport PR #28233 on branch v3.9.x (CI: Fix font install on macOS/Homebrew) +* :ghpull:`28236`: Backport PR #28231 on branch v3.9.x (DOC: we do not need the blit call in on_draw) +* :ghpull:`28233`: CI: Fix font install on macOS/Homebrew +* :ghpull:`28231`: DOC: we do not need the blit call in on_draw + +Issues (30): + +* :ghissue:`22482`: [ENH]: pickle (or save) matplotlib figure with insteractive slider +* :ghissue:`25847`: [Bug]: Graph gets cut off with scaled resolution using gtk4cairo backend +* :ghissue:`28341`: [Bug]: Incorrect X-axis scaling with date values +* :ghissue:`28383`: [Bug]: axvspan no longer participating in limit calculations +* :ghissue:`28223`: [Bug]: Inconsistent Visualization of Intervals in ax.barh for Different Duration Widths +* :ghissue:`28432`: [Bug]: Backend name specified as module gets lowercased since 3.9 +* :ghissue:`28467`: [Bug]: Incorrect type stub for ``ErrorbarContainer``'s ``lines`` attribute. +* :ghissue:`28384`: [Bug]: subfigure artists not drawn interactively +* :ghissue:`28234`: [Bug]: mpltype custom role breaks sphinx build for third-party projects that have intersphinx links to matplotlib +* :ghissue:`28464`: [Bug]: figure with subfigures cannot be pickled +* :ghissue:`28448`: [Bug]: Making an RGB image from pickled data throws error +* :ghissue:`23317`: [Bug]: ``add_collection3d`` does not update view limits +* :ghissue:`17130`: autoscale_view is not working with Line3DCollection +* :ghissue:`28434`: [Bug]: Setting exactly 2 colors with tuple in ``plot`` method gives confusing error +* :ghissue:`28417`: [Doc]: axhspan and axvspan now return Rectangles, not Polygons. +* :ghissue:`28378`: [ENH]: Switch text wrapping boundary to subfigure +* :ghissue:`28404`: [Doc]: matplotlib.widgets.CheckButtons no longer has .rectangles attribute, needs removed. +* :ghissue:`28367`: [Bug]: Backend entry points can be erroneously duplicated +* :ghissue:`28358`: [Bug]: Labels don't get wrapped when set_yticks() is used in subplots +* :ghissue:`28374`: [Bug]: rcParam ``tk.window_focus: True`` is causes crash on Linux in version 3.9.0. +* :ghissue:`28324`: [Bug]: show(block=False) freezes +* :ghissue:`28239`: [Doc]: Gallery example showing 3D slice planes +* :ghissue:`27603`: [Bug]: _raw_ticker() istep +* :ghissue:`24328`: [Bug]: class Axes3D.set_box_aspect() sets wrong aspect ratios when Axes3D.view_init( vertical_axis='y') is enabled. +* :ghissue:`28221`: [Doc]: drawedges attribute described twice in matplotlib.colorbar documentation +* :ghissue:`28296`: [Doc]: Missing comma +* :ghissue:`28256`: [Bug]: axes3d.py's _on_move() converts the roll angle to radians, but then passes it to view_init() as if it were still in degrees +* :ghissue:`28267`: [Bug]: for Python 3.11.9 gor ValueError: PyCapsule_New called with null pointer +* :ghissue:`28022`: [Bug]: Type of Axes is unknown pyright +* :ghissue:`28002`: Segfault from path editor example with QtAgg diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 1204450f6c05..74bc0f13bf1f 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -19,9 +19,11 @@ Version 3.9 :maxdepth: 1 prev_whats_new/whats_new_3.9.0.rst + ../api/prev_api_changes/api_changes_3.9.2.rst ../api/prev_api_changes/api_changes_3.9.1.rst ../api/prev_api_changes/api_changes_3.9.0.rst github_stats.rst + prev_whats_new/github_stats_3.9.1.rst prev_whats_new/github_stats_3.9.0.rst Version 3.8 From a254b687df97cda8c6affa37a1dfcf213f8e6c3a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 12 Aug 2024 19:35:24 -0400 Subject: [PATCH 1071/1120] REL: 3.9.2 This is the second bugfix release of the 3.9.x series. This release contains several bug-fixes and adjustments: - Be more resilient to I/O failures when writing font cache - Fix nondeterministic behavior with subplot spacing and constrained layout - Fix sticky edge tolerance relative to data range - Improve formatting of image values in cases of singular norms Windows wheels now bundle the MSVC runtime DLL statically to avoid inconsistencies with other wheels and random crashes depending on import order. From 4b30b1d938b1ccad1e96b35ec11292e9fb8f05fd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 12 Aug 2024 19:52:20 -0400 Subject: [PATCH 1072/1120] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 3aea791026cabe9b9bdaba6d9a23c122bbf04115 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 12 Aug 2024 20:13:22 -0400 Subject: [PATCH 1073/1120] DOC: Add Zenodo DOI for 3.9.2 --- doc/_static/zenodo_cache/13308876.svg | 35 +++++++++++++++++++++++++++ doc/project/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/13308876.svg diff --git a/doc/_static/zenodo_cache/13308876.svg b/doc/_static/zenodo_cache/13308876.svg new file mode 100644 index 000000000000..749bc3c19026 --- /dev/null +++ b/doc/_static/zenodo_cache/13308876.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.13308876 + + + 10.5281/zenodo.13308876 + + + \ No newline at end of file diff --git a/doc/project/citing.rst b/doc/project/citing.rst index e0b99995ad11..38c989fca195 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -32,6 +32,9 @@ By version .. START OF AUTOGENERATED +v3.9.2 + .. image:: ../_static/zenodo_cache/13308876.svg + :target: https://doi.org/10.5281/zenodo.13308876 v3.9.1 .. image:: ../_static/zenodo_cache/12652732.svg :target: https://doi.org/10.5281/zenodo.12652732 diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index 1dc2fbba020b..40814d21573c 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,7 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.9.2": "13308876", "v3.9.1": "12652732", "v3.9.0": "11201097", "v3.8.4": "10916799", From d04b2f64fe2fdc3f83707229d64d1516bb56405d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 12 Aug 2024 21:55:17 -0400 Subject: [PATCH 1074/1120] DOC: Fix a typo in GitHub stats --- doc/users/github_stats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 00c3e5d656a1..d357a6759d30 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -89,7 +89,7 @@ Issues (9): * :ghissue:`28250`: [Doc]: Sphinx gallery links mispointed for Axes3D methods * :ghissue:`28574`: [Bug]: Nondeterministic behavior with subplot spacing and constrained layout * :ghissue:`28626`: [Doc]: Remove old TODO's from animation.py -* :ghissue:`28648`: [Bug]: format_image_data on an image of only zeros produses a large number of zeros +* :ghissue:`28648`: [Bug]: format_image_data on an image of only zeros produces a large number of zeros * :ghissue:`28624`: [Bug]: Bad type hint in ``_AxesBase.twinx()`` * :ghissue:`28567`: [Bug]: sticky edge related changes for datetime plots * :ghissue:`28533`: [Doc]: Stackplot hatch functionality has version dependencies From 8e2d7914a2c8943187405661be3e876537c32e54 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 14 Aug 2024 16:44:38 -0400 Subject: [PATCH 1075/1120] TST: Guard against PyGObject existing, but not gobject-introspection --- lib/matplotlib/tests/test_backends_interactive.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index d624b5db0ac2..2c6b61a48438 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -81,10 +81,18 @@ def _get_available_interactive_backends(): elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): reason = "macosx backend fails on Azure" elif env["MPLBACKEND"].startswith('gtk'): - import gi # type: ignore + try: + import gi # type: ignore + except ImportError: + # Though we check that `gi` exists above, it is possible that its + # C-level dependencies are not available, and then it still raises an + # `ImportError`, so guard against that. + available_gtk_versions = [] + else: + gi_repo = gi.Repository.get_default() + available_gtk_versions = gi_repo.enumerate_versions('Gtk') version = env["MPLBACKEND"][3] - repo = gi.Repository.get_default() - if f'{version}.0' not in repo.enumerate_versions('Gtk'): + if f'{version}.0' not in available_gtk_versions: reason = "no usable GTK bindings" marks = [] if reason: From f6b32660a348aa027b25709cc3d8218b017e8db5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 14 Aug 2024 17:02:23 -0400 Subject: [PATCH 1076/1120] BLD: Avoid pybind11 2.13.3 due to Windows quoting bug See https://github.com/pybind/pybind11/issues/5300#issuecomment-2287698500 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 52bbe308c0f9..891ef87e4342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ requires-python = ">=3.9" dev = [ "meson-python>=0.13.1", "numpy>=1.25", - "pybind11>=2.6", + "pybind11>=2.6,!=2.13.3", "setuptools_scm>=7", # Not required by us but setuptools_scm without a version, cso _if_ # installed, then setuptools_scm 8 requires at least this version. @@ -73,7 +73,7 @@ build-backend = "mesonpy" # Also keep in sync with optional dependencies above. requires = [ "meson-python>=0.13.1", - "pybind11>=2.6", + "pybind11>=2.6,!=2.13.3", "setuptools_scm>=7", # Comments on numpy build requirement range: From cb124887a10c2e69e70974847a47f320353e791c Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 15 Aug 2024 18:21:14 -0400 Subject: [PATCH 1077/1120] Backport PR #28732: Renames the minumumSizeHint method to minimumSizeHint --- lib/matplotlib/backends/backend_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 6603883075d4..242c6fdbf9f9 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -393,7 +393,7 @@ def sizeHint(self): w, h = self.get_width_height() return QtCore.QSize(w, h) - def minumumSizeHint(self): + def minimumSizeHint(self): return QtCore.QSize(10, 10) @staticmethod From 3900fa24aa082c1727ca274b98747933f1578671 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:22:23 +0100 Subject: [PATCH 1078/1120] Backport PR #28737: TST: Fix image comparison directory for test_striped_lines --- lib/matplotlib/tests/test_collections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 23e951b17a2f..c4f98d4eeb45 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -1316,7 +1316,6 @@ def test_check_offsets_dtype(): @pytest.mark.parametrize('gapcolor', ['orange', ['r', 'k']]) @check_figures_equal(extensions=['png']) -@mpl.rc_context({'lines.linewidth': 20}) def test_striped_lines(fig_test, fig_ref, gapcolor): ax_test = fig_test.add_subplot(111) ax_ref = fig_ref.add_subplot(111) @@ -1328,11 +1327,12 @@ def test_striped_lines(fig_test, fig_ref, gapcolor): x = range(1, 6) linestyles = [':', '-', '--'] - ax_test.vlines(x, 0, 1, linestyle=linestyles, gapcolor=gapcolor, alpha=0.5) + ax_test.vlines(x, 0, 1, linewidth=20, linestyle=linestyles, gapcolor=gapcolor, + alpha=0.5) if isinstance(gapcolor, str): gapcolor = [gapcolor] for x, gcol, ls in zip(x, itertools.cycle(gapcolor), itertools.cycle(linestyles)): - ax_ref.axvline(x, 0, 1, linestyle=ls, gapcolor=gcol, alpha=0.5) + ax_ref.axvline(x, 0, 1, linewidth=20, linestyle=ls, gapcolor=gcol, alpha=0.5) From fc5bc4a50b6c16cde5e21e122fb370862b56148c Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:17:02 +0100 Subject: [PATCH 1079/1120] Backport PR #28739: Tweak interactivity docs wording (and fix capitalization). --- galleries/users_explain/figure/event_handling.rst | 15 +++++++++------ galleries/users_explain/figure/interactive.rst | 2 +- .../users_explain/figure/interactive_guide.rst | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/galleries/users_explain/figure/event_handling.rst b/galleries/users_explain/figure/event_handling.rst index 49d73afeb366..32da038634ae 100644 --- a/galleries/users_explain/figure/event_handling.rst +++ b/galleries/users_explain/figure/event_handling.rst @@ -251,7 +251,8 @@ is created every time a mouse is pressed:: def __call__(self, event): print('click', event) - if event.inaxes!=self.line.axes: return + if event.inaxes != self.line.axes: + return self.xs.append(event.xdata) self.ys.append(event.ydata) self.line.set_data(self.xs, self.ys) @@ -277,17 +278,19 @@ event.ydata)``. In addition to the ``LocationEvent`` attributes, it also has: Draggable rectangle exercise ---------------------------- -Write draggable rectangle class that is initialized with a +Write a draggable rectangle class that is initialized with a `.Rectangle` instance but will move its ``xy`` -location when dragged. Hint: you will need to store the original -``xy`` location of the rectangle which is stored as rect.xy and +location when dragged. + +Hint: You will need to store the original +``xy`` location of the rectangle which is stored as ``rect.xy`` and connect to the press, motion and release mouse events. When the mouse is pressed, check to see if the click occurs over your rectangle (see `.Rectangle.contains`) and if it does, store -the rectangle xy and the location of the mouse click in data coords. +the rectangle xy and the location of the mouse click in data coordinates. In the motion event callback, compute the deltax and deltay of the mouse movement, and add those deltas to the origin of the rectangle -you stored. The redraw the figure. On the button release event, just +you stored, then redraw the figure. On the button release event, just reset all the button press data you stored as None. Here is the solution:: diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index 6fd908fcac7a..b06283152e50 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -10,7 +10,7 @@ Interactive figures =================== -When working with data, interactivity can be invaluable. The pan/zoom and +Interactivity can be invaluable when exploring plots. The pan/zoom and mouse-location tools built into the Matplotlib GUI windows are often sufficient, but you can also use the event system to build customized data exploration tools. diff --git a/galleries/users_explain/figure/interactive_guide.rst b/galleries/users_explain/figure/interactive_guide.rst index 3b6f527f6d42..b08231e84f7e 100644 --- a/galleries/users_explain/figure/interactive_guide.rst +++ b/galleries/users_explain/figure/interactive_guide.rst @@ -236,7 +236,7 @@ which would poll for new data and update the figure at 1Hz. .. _spin_event_loop: -Explicitly spinning the event Loop +Explicitly spinning the event loop ---------------------------------- .. autosummary:: From cbc42296dce9d199ef6f33ee6a893ebb11fbfac5 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:17:02 +0100 Subject: [PATCH 1080/1120] Backport PR #28739: Tweak interactivity docs wording (and fix capitalization). --- galleries/users_explain/figure/event_handling.rst | 15 +++++++++------ galleries/users_explain/figure/interactive.rst | 2 +- .../users_explain/figure/interactive_guide.rst | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/galleries/users_explain/figure/event_handling.rst b/galleries/users_explain/figure/event_handling.rst index 49d73afeb366..32da038634ae 100644 --- a/galleries/users_explain/figure/event_handling.rst +++ b/galleries/users_explain/figure/event_handling.rst @@ -251,7 +251,8 @@ is created every time a mouse is pressed:: def __call__(self, event): print('click', event) - if event.inaxes!=self.line.axes: return + if event.inaxes != self.line.axes: + return self.xs.append(event.xdata) self.ys.append(event.ydata) self.line.set_data(self.xs, self.ys) @@ -277,17 +278,19 @@ event.ydata)``. In addition to the ``LocationEvent`` attributes, it also has: Draggable rectangle exercise ---------------------------- -Write draggable rectangle class that is initialized with a +Write a draggable rectangle class that is initialized with a `.Rectangle` instance but will move its ``xy`` -location when dragged. Hint: you will need to store the original -``xy`` location of the rectangle which is stored as rect.xy and +location when dragged. + +Hint: You will need to store the original +``xy`` location of the rectangle which is stored as ``rect.xy`` and connect to the press, motion and release mouse events. When the mouse is pressed, check to see if the click occurs over your rectangle (see `.Rectangle.contains`) and if it does, store -the rectangle xy and the location of the mouse click in data coords. +the rectangle xy and the location of the mouse click in data coordinates. In the motion event callback, compute the deltax and deltay of the mouse movement, and add those deltas to the origin of the rectangle -you stored. The redraw the figure. On the button release event, just +you stored, then redraw the figure. On the button release event, just reset all the button press data you stored as None. Here is the solution:: diff --git a/galleries/users_explain/figure/interactive.rst b/galleries/users_explain/figure/interactive.rst index 6fd908fcac7a..b06283152e50 100644 --- a/galleries/users_explain/figure/interactive.rst +++ b/galleries/users_explain/figure/interactive.rst @@ -10,7 +10,7 @@ Interactive figures =================== -When working with data, interactivity can be invaluable. The pan/zoom and +Interactivity can be invaluable when exploring plots. The pan/zoom and mouse-location tools built into the Matplotlib GUI windows are often sufficient, but you can also use the event system to build customized data exploration tools. diff --git a/galleries/users_explain/figure/interactive_guide.rst b/galleries/users_explain/figure/interactive_guide.rst index 3b6f527f6d42..b08231e84f7e 100644 --- a/galleries/users_explain/figure/interactive_guide.rst +++ b/galleries/users_explain/figure/interactive_guide.rst @@ -236,7 +236,7 @@ which would poll for new data and update the figure at 1Hz. .. _spin_event_loop: -Explicitly spinning the event Loop +Explicitly spinning the event loop ---------------------------------- .. autosummary:: From d9d1a4d3f6612e9349f1607f46f359bdce8934f6 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:20:39 +0200 Subject: [PATCH 1081/1120] Backport PR #28743: Minor fixes in ticker docs --- lib/matplotlib/ticker.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 2b00937f9e29..d824bbe3b6e2 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -435,10 +435,10 @@ class ScalarFormatter(Formatter): lim = (1_000_000, 1_000_010) fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={'hspace': 2}) - ax1.set(title='offset_notation', xlim=lim) + ax1.set(title='offset notation', xlim=lim) ax2.set(title='scientific notation', xlim=lim) ax2.xaxis.get_major_formatter().set_useOffset(False) - ax3.set(title='floating point notation', xlim=lim) + ax3.set(title='floating-point notation', xlim=lim) ax3.xaxis.get_major_formatter().set_useOffset(False) ax3.xaxis.get_major_formatter().set_scientific(False) @@ -1146,10 +1146,10 @@ def __init__( Parameters ---------- use_overline : bool, default: False - If x > 1/2, with x = 1-v, indicate if x should be displayed as - $\overline{v}$. The default is to display $1-v$. + If x > 1/2, with x = 1 - v, indicate if x should be displayed as + $\overline{v}$. The default is to display $1 - v$. - one_half : str, default: r"\frac{1}{2}" + one_half : str, default: r"\\frac{1}{2}" The string used to represent 1/2. minor : bool, default: False @@ -1179,9 +1179,9 @@ def use_overline(self, use_overline): Parameters ---------- - use_overline : bool, default: False - If x > 1/2, with x = 1-v, indicate if x should be displayed as - $\overline{v}$. The default is to display $1-v$. + use_overline : bool + If x > 1/2, with x = 1 - v, indicate if x should be displayed as + $\overline{v}$. The default is to display $1 - v$. """ self._use_overline = use_overline @@ -1189,7 +1189,7 @@ def set_one_half(self, one_half): r""" Set the way one half is displayed. - one_half : str, default: r"\frac{1}{2}" + one_half : str The string used to represent 1/2. """ self._one_half = one_half @@ -1707,14 +1707,14 @@ def tick_values(self, vmin, vmax): class FixedLocator(Locator): - """ + r""" Place ticks at a set of fixed values. If *nbins* is None ticks are placed at all values. Otherwise, the *locs* array of - possible positions will be subsampled to keep the number of ticks <= - :math:`nbins* +1`. The subsampling will be done to include the smallest absolute - value; for example, if zero is included in the array of possibilities, then it of - the chosen ticks. + possible positions will be subsampled to keep the number of ticks + :math:`\leq nbins + 1`. The subsampling will be done to include the smallest + absolute value; for example, if zero is included in the array of possibilities, then + it will be included in the chosen ticks. """ def __init__(self, locs, nbins=None): @@ -1861,9 +1861,9 @@ def __init__(self, base=1.0, offset=0.0): """ Parameters ---------- - base : float > 0 + base : float > 0, default: 1.0 Interval between ticks. - offset : float + offset : float, default: 0.0 Value added to each multiple of *base*. .. versionadded:: 3.8 @@ -1877,9 +1877,9 @@ def set_params(self, base=None, offset=None): Parameters ---------- - base : float > 0 + base : float > 0, optional Interval between ticks. - offset : float + offset : float, optional Value added to each multiple of *base*. .. versionadded:: 3.8 From 83251ac2a1d780dc4af2bacab07b43552c5b7a70 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 22 Aug 2024 14:50:03 -0400 Subject: [PATCH 1082/1120] Backport PR #28271: Fix draggable legend disappearing when picking while use_blit=True --- lib/matplotlib/offsetbox.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 32c5bafcde1d..194b950a8a30 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -1486,11 +1486,13 @@ def on_motion(self, evt): self.canvas.draw() def on_pick(self, evt): - if self._check_still_parented() and evt.artist == self.ref_artist: - self.mouse_x = evt.mouseevent.x - self.mouse_y = evt.mouseevent.y - self.got_artist = True - if self._use_blit: + if self._check_still_parented(): + if evt.artist == self.ref_artist: + self.mouse_x = evt.mouseevent.x + self.mouse_y = evt.mouseevent.y + self.save_offset() + self.got_artist = True + if self.got_artist and self._use_blit: self.ref_artist.set_animated(True) self.canvas.draw() self.background = \ @@ -1498,13 +1500,15 @@ def on_pick(self, evt): self.ref_artist.draw( self.ref_artist.figure._get_renderer()) self.canvas.blit() - self.save_offset() def on_release(self, event): if self._check_still_parented() and self.got_artist: self.finalize_offset() self.got_artist = False if self._use_blit: + self.canvas.restore_region(self.background) + self.ref_artist.draw(self.ref_artist.figure._get_renderer()) + self.canvas.blit() self.ref_artist.set_animated(False) def _check_still_parented(self): From 4439116246b1f74e6dda7a11d96602c3912c01b9 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:33:56 +0100 Subject: [PATCH 1083/1120] Backport PR #28706: Add Returns info to to_jshtml docstring --- lib/matplotlib/animation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 5a4764f1a79f..b402c5fdb4da 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -1311,6 +1311,12 @@ def to_jshtml(self, fps=None, embed_frames=True, default_mode=None): What to do when the animation ends. Must be one of ``{'loop', 'once', 'reflect'}``. Defaults to ``'loop'`` if the *repeat* parameter is True, otherwise ``'once'``. + + Returns + ------- + str + An HTML representation of the animation embedded as a js object as + produced with the `.HTMLWriter`. """ if fps is None and hasattr(self, '_interval'): # Convert interval in ms to frames per second From 7c370eac988c51dea9d6fca285636ebfcdeba55c Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 8 Sep 2024 08:44:57 +0100 Subject: [PATCH 1084/1120] Backport PR #28790: DOC: Fix duplicate Figure.set_dpi entry --- doc/api/figure_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 2371e5a9a863..5dd3adbfec9f 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -91,7 +91,7 @@ Figure geometry Figure.get_figwidth Figure.dpi Figure.set_dpi - Figure.set_dpi + Figure.get_dpi Subplot layout -------------- From cf7e7ae718ced0a8b2bbf7aa2d30b141fa5eeb8c Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 8 Sep 2024 08:44:57 +0100 Subject: [PATCH 1085/1120] Backport PR #28790: DOC: Fix duplicate Figure.set_dpi entry --- doc/api/figure_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 2371e5a9a863..5dd3adbfec9f 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -91,7 +91,7 @@ Figure geometry Figure.get_figwidth Figure.dpi Figure.set_dpi - Figure.set_dpi + Figure.get_dpi Subplot layout -------------- From 88309f528843195ec1e25d75ea5cf148a40bcd8d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 11 Sep 2024 16:54:17 -0400 Subject: [PATCH 1086/1120] Backport PR #28798: DOC: Correctly list modules that have been internalized --- doc/api/_afm_api.rst | 8 ++++++++ doc/api/_docstring_api.rst | 8 ++++++++ doc/api/_tight_bbox_api.rst | 8 ++++++++ doc/api/_tight_layout_api.rst | 8 ++++++++ doc/api/_type1font.rst | 8 ++++++++ doc/api/afm_api.rst | 13 ------------- doc/api/docstring_api.rst | 13 ------------- doc/api/index.rst | 10 +++++----- doc/api/tight_bbox_api.rst | 13 ------------- doc/api/tight_layout_api.rst | 13 ------------- doc/api/type1font.rst | 13 ------------- 11 files changed, 45 insertions(+), 70 deletions(-) create mode 100644 doc/api/_afm_api.rst create mode 100644 doc/api/_docstring_api.rst create mode 100644 doc/api/_tight_bbox_api.rst create mode 100644 doc/api/_tight_layout_api.rst create mode 100644 doc/api/_type1font.rst delete mode 100644 doc/api/afm_api.rst delete mode 100644 doc/api/docstring_api.rst delete mode 100644 doc/api/tight_bbox_api.rst delete mode 100644 doc/api/tight_layout_api.rst delete mode 100644 doc/api/type1font.rst diff --git a/doc/api/_afm_api.rst b/doc/api/_afm_api.rst new file mode 100644 index 000000000000..4e2ac4997272 --- /dev/null +++ b/doc/api/_afm_api.rst @@ -0,0 +1,8 @@ +******************* +``matplotlib._afm`` +******************* + +.. automodule:: matplotlib._afm + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_docstring_api.rst b/doc/api/_docstring_api.rst new file mode 100644 index 000000000000..040a3653a87b --- /dev/null +++ b/doc/api/_docstring_api.rst @@ -0,0 +1,8 @@ +************************* +``matplotlib._docstring`` +************************* + +.. automodule:: matplotlib._docstring + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_tight_bbox_api.rst b/doc/api/_tight_bbox_api.rst new file mode 100644 index 000000000000..826e051fcf6d --- /dev/null +++ b/doc/api/_tight_bbox_api.rst @@ -0,0 +1,8 @@ +************************** +``matplotlib._tight_bbox`` +************************** + +.. automodule:: matplotlib._tight_bbox + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_tight_layout_api.rst b/doc/api/_tight_layout_api.rst new file mode 100644 index 000000000000..ac4f66f280e1 --- /dev/null +++ b/doc/api/_tight_layout_api.rst @@ -0,0 +1,8 @@ +**************************** +``matplotlib._tight_layout`` +**************************** + +.. automodule:: matplotlib._tight_layout + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_type1font.rst b/doc/api/_type1font.rst new file mode 100644 index 000000000000..1a9ff2292887 --- /dev/null +++ b/doc/api/_type1font.rst @@ -0,0 +1,8 @@ +************************* +``matplotlib._type1font`` +************************* + +.. automodule:: matplotlib._type1font + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/afm_api.rst b/doc/api/afm_api.rst deleted file mode 100644 index bcae04150909..000000000000 --- a/doc/api/afm_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -****************** -``matplotlib.afm`` -****************** - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._afm - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/docstring_api.rst b/doc/api/docstring_api.rst deleted file mode 100644 index 38a73a2e83d1..000000000000 --- a/doc/api/docstring_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************ -``matplotlib.docstring`` -************************ - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._docstring - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/index.rst b/doc/api/index.rst index 70c3b5343e7a..53f397a6817a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -79,7 +79,6 @@ Alphabetical list of modules: :maxdepth: 1 matplotlib_configuration_api.rst - afm_api.rst animation_api.rst artist_api.rst axes_api.rst @@ -98,7 +97,6 @@ Alphabetical list of modules: container_api.rst contour_api.rst dates_api.rst - docstring_api.rst dviread.rst figure_api.rst font_manager_api.rst @@ -134,16 +132,18 @@ Alphabetical list of modules: text_api.rst texmanager_api.rst ticker_api.rst - tight_bbox_api.rst - tight_layout_api.rst transformations.rst tri_api.rst - type1font.rst typing_api.rst units_api.rst widgets_api.rst + _afm_api.rst _api_api.rst + _docstring_api.rst _enums_api.rst + _type1font.rst + _tight_bbox_api.rst + _tight_layout_api.rst toolkits/mplot3d.rst toolkits/axes_grid1.rst toolkits/axisartist.rst diff --git a/doc/api/tight_bbox_api.rst b/doc/api/tight_bbox_api.rst deleted file mode 100644 index 9e8dd2fa66f9..000000000000 --- a/doc/api/tight_bbox_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************* -``matplotlib.tight_bbox`` -************************* - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._tight_bbox - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/tight_layout_api.rst b/doc/api/tight_layout_api.rst deleted file mode 100644 index 35f92e3ddced..000000000000 --- a/doc/api/tight_layout_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -*************************** -``matplotlib.tight_layout`` -*************************** - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._tight_layout - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/type1font.rst b/doc/api/type1font.rst deleted file mode 100644 index 00ef38f4d447..000000000000 --- a/doc/api/type1font.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************ -``matplotlib.type1font`` -************************ - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._type1font - :members: - :undoc-members: - :show-inheritance: From 259b3ee84784341b50696206d559f67c74ba9c89 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 11 Sep 2024 16:54:17 -0400 Subject: [PATCH 1087/1120] Backport PR #28798: DOC: Correctly list modules that have been internalized --- doc/api/_afm_api.rst | 8 ++++++++ doc/api/_docstring_api.rst | 8 ++++++++ doc/api/_tight_bbox_api.rst | 8 ++++++++ doc/api/_tight_layout_api.rst | 8 ++++++++ doc/api/_type1font.rst | 8 ++++++++ doc/api/afm_api.rst | 13 ------------- doc/api/docstring_api.rst | 13 ------------- doc/api/index.rst | 10 +++++----- doc/api/tight_bbox_api.rst | 13 ------------- doc/api/tight_layout_api.rst | 13 ------------- doc/api/type1font.rst | 13 ------------- 11 files changed, 45 insertions(+), 70 deletions(-) create mode 100644 doc/api/_afm_api.rst create mode 100644 doc/api/_docstring_api.rst create mode 100644 doc/api/_tight_bbox_api.rst create mode 100644 doc/api/_tight_layout_api.rst create mode 100644 doc/api/_type1font.rst delete mode 100644 doc/api/afm_api.rst delete mode 100644 doc/api/docstring_api.rst delete mode 100644 doc/api/tight_bbox_api.rst delete mode 100644 doc/api/tight_layout_api.rst delete mode 100644 doc/api/type1font.rst diff --git a/doc/api/_afm_api.rst b/doc/api/_afm_api.rst new file mode 100644 index 000000000000..4e2ac4997272 --- /dev/null +++ b/doc/api/_afm_api.rst @@ -0,0 +1,8 @@ +******************* +``matplotlib._afm`` +******************* + +.. automodule:: matplotlib._afm + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_docstring_api.rst b/doc/api/_docstring_api.rst new file mode 100644 index 000000000000..040a3653a87b --- /dev/null +++ b/doc/api/_docstring_api.rst @@ -0,0 +1,8 @@ +************************* +``matplotlib._docstring`` +************************* + +.. automodule:: matplotlib._docstring + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_tight_bbox_api.rst b/doc/api/_tight_bbox_api.rst new file mode 100644 index 000000000000..826e051fcf6d --- /dev/null +++ b/doc/api/_tight_bbox_api.rst @@ -0,0 +1,8 @@ +************************** +``matplotlib._tight_bbox`` +************************** + +.. automodule:: matplotlib._tight_bbox + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_tight_layout_api.rst b/doc/api/_tight_layout_api.rst new file mode 100644 index 000000000000..ac4f66f280e1 --- /dev/null +++ b/doc/api/_tight_layout_api.rst @@ -0,0 +1,8 @@ +**************************** +``matplotlib._tight_layout`` +**************************** + +.. automodule:: matplotlib._tight_layout + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/_type1font.rst b/doc/api/_type1font.rst new file mode 100644 index 000000000000..1a9ff2292887 --- /dev/null +++ b/doc/api/_type1font.rst @@ -0,0 +1,8 @@ +************************* +``matplotlib._type1font`` +************************* + +.. automodule:: matplotlib._type1font + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/afm_api.rst b/doc/api/afm_api.rst deleted file mode 100644 index bcae04150909..000000000000 --- a/doc/api/afm_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -****************** -``matplotlib.afm`` -****************** - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._afm - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/docstring_api.rst b/doc/api/docstring_api.rst deleted file mode 100644 index 38a73a2e83d1..000000000000 --- a/doc/api/docstring_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************ -``matplotlib.docstring`` -************************ - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._docstring - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/index.rst b/doc/api/index.rst index 70c3b5343e7a..53f397a6817a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -79,7 +79,6 @@ Alphabetical list of modules: :maxdepth: 1 matplotlib_configuration_api.rst - afm_api.rst animation_api.rst artist_api.rst axes_api.rst @@ -98,7 +97,6 @@ Alphabetical list of modules: container_api.rst contour_api.rst dates_api.rst - docstring_api.rst dviread.rst figure_api.rst font_manager_api.rst @@ -134,16 +132,18 @@ Alphabetical list of modules: text_api.rst texmanager_api.rst ticker_api.rst - tight_bbox_api.rst - tight_layout_api.rst transformations.rst tri_api.rst - type1font.rst typing_api.rst units_api.rst widgets_api.rst + _afm_api.rst _api_api.rst + _docstring_api.rst _enums_api.rst + _type1font.rst + _tight_bbox_api.rst + _tight_layout_api.rst toolkits/mplot3d.rst toolkits/axes_grid1.rst toolkits/axisartist.rst diff --git a/doc/api/tight_bbox_api.rst b/doc/api/tight_bbox_api.rst deleted file mode 100644 index 9e8dd2fa66f9..000000000000 --- a/doc/api/tight_bbox_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************* -``matplotlib.tight_bbox`` -************************* - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._tight_bbox - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/tight_layout_api.rst b/doc/api/tight_layout_api.rst deleted file mode 100644 index 35f92e3ddced..000000000000 --- a/doc/api/tight_layout_api.rst +++ /dev/null @@ -1,13 +0,0 @@ -*************************** -``matplotlib.tight_layout`` -*************************** - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._tight_layout - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/type1font.rst b/doc/api/type1font.rst deleted file mode 100644 index 00ef38f4d447..000000000000 --- a/doc/api/type1font.rst +++ /dev/null @@ -1,13 +0,0 @@ -************************ -``matplotlib.type1font`` -************************ - -.. attention:: - This module is considered internal. - - Its use is deprecated and it will be removed in a future version. - -.. automodule:: matplotlib._type1font - :members: - :undoc-members: - :show-inheritance: From 7b941c8a924c07215984810649ddee2a4ed51a7a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Sep 2024 05:28:44 -0400 Subject: [PATCH 1088/1120] Backport PR #28805: add brackets to satisfy the new sequence requirement --- galleries/examples/animation/frame_grabbing_sgskip.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/galleries/examples/animation/frame_grabbing_sgskip.py b/galleries/examples/animation/frame_grabbing_sgskip.py index 08155d2c61a7..dcc2ca01afd9 100644 --- a/galleries/examples/animation/frame_grabbing_sgskip.py +++ b/galleries/examples/animation/frame_grabbing_sgskip.py @@ -6,8 +6,6 @@ Use a MovieWriter directly to grab individual frames and write them to a file. This avoids any event loop integration, and thus works even with the Agg backend. This is not recommended for use in an interactive setting. - -Output generated via `matplotlib.animation.Animation.to_jshtml`. """ import numpy as np @@ -39,5 +37,5 @@ for i in range(100): x0 += 0.1 * np.random.randn() y0 += 0.1 * np.random.randn() - l.set_data(x0, y0) + l.set_data([x0], [y0]) writer.grab_frame() From 96654d64dfea78901c9d912aca62c47fd4fb287c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Sep 2024 05:28:44 -0400 Subject: [PATCH 1089/1120] Backport PR #28805: add brackets to satisfy the new sequence requirement --- galleries/examples/animation/frame_grabbing_sgskip.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/galleries/examples/animation/frame_grabbing_sgskip.py b/galleries/examples/animation/frame_grabbing_sgskip.py index 08155d2c61a7..dcc2ca01afd9 100644 --- a/galleries/examples/animation/frame_grabbing_sgskip.py +++ b/galleries/examples/animation/frame_grabbing_sgskip.py @@ -6,8 +6,6 @@ Use a MovieWriter directly to grab individual frames and write them to a file. This avoids any event loop integration, and thus works even with the Agg backend. This is not recommended for use in an interactive setting. - -Output generated via `matplotlib.animation.Animation.to_jshtml`. """ import numpy as np @@ -39,5 +37,5 @@ for i in range(100): x0 += 0.1 * np.random.randn() y0 += 0.1 * np.random.randn() - l.set_data(x0, y0) + l.set_data([x0], [y0]) writer.grab_frame() From 5fa2a000e4700294a059212dc3e0847e0db5ea01 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Sep 2024 19:05:23 -0400 Subject: [PATCH 1090/1120] Backport PR #28810: Document how to obtain sans-serif usetex math. --- galleries/users_explain/text/usetex.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/galleries/users_explain/text/usetex.py b/galleries/users_explain/text/usetex.py index 0194a0030d48..f0c266819897 100644 --- a/galleries/users_explain/text/usetex.py +++ b/galleries/users_explain/text/usetex.py @@ -102,6 +102,12 @@ :rc:`text.usetex`. As noted above, underscores (``_``) do not require escaping outside of math mode. +.. note:: + LaTeX always defaults to using a serif font for math (even when + ``rcParams["font.family"] = "sans-serif"``). If desired, adding + ``\usepackage{sfmath}`` to ``rcParams["text.latex.preamble"]`` lets LaTeX + output sans-serif math. + PostScript options ================== @@ -129,19 +135,13 @@ :ref:`setting-windows-environment-variables` for details. * Using MiKTeX with Computer Modern fonts, if you get odd \*Agg and PNG - results, go to MiKTeX/Options and update your format files + results, go to MiKTeX/Options and update your format files. * On Ubuntu and Gentoo, the base texlive install does not ship with the type1cm package. You may need to install some of the extra packages to get all the goodies that come bundled with other LaTeX distributions. -* Some progress has been made so Matplotlib uses the dvi files - directly for text layout. This allows LaTeX to be used for text - layout with the pdf and svg backends, as well as the \*Agg and PS - backends. In the future, a LaTeX installation may be the only - external dependency. - .. _usetex-troubleshooting: Troubleshooting @@ -150,7 +150,7 @@ * Try deleting your :file:`.matplotlib/tex.cache` directory. If you don't know where to find :file:`.matplotlib`, see :ref:`locating-matplotlib-config-dir`. -* Make sure LaTeX, dvipng and ghostscript are each working and on your +* Make sure LaTeX, dvipng and Ghostscript are each working and on your :envvar:`PATH`. * Make sure what you are trying to do is possible in a LaTeX document, @@ -159,8 +159,7 @@ * :rc:`text.latex.preamble` is not officially supported. This option provides lots of flexibility, and lots of ways to cause - problems. Please disable this option before reporting problems to - the mailing list. + problems. Please disable this option before reporting problems. * If you still need help, please see :ref:`reporting-problems`. From a058fae22f55154bd4080b41a5d9b096e8e96d1d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Sep 2024 19:05:23 -0400 Subject: [PATCH 1091/1120] Backport PR #28810: Document how to obtain sans-serif usetex math. --- galleries/users_explain/text/usetex.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/galleries/users_explain/text/usetex.py b/galleries/users_explain/text/usetex.py index 0194a0030d48..f0c266819897 100644 --- a/galleries/users_explain/text/usetex.py +++ b/galleries/users_explain/text/usetex.py @@ -102,6 +102,12 @@ :rc:`text.usetex`. As noted above, underscores (``_``) do not require escaping outside of math mode. +.. note:: + LaTeX always defaults to using a serif font for math (even when + ``rcParams["font.family"] = "sans-serif"``). If desired, adding + ``\usepackage{sfmath}`` to ``rcParams["text.latex.preamble"]`` lets LaTeX + output sans-serif math. + PostScript options ================== @@ -129,19 +135,13 @@ :ref:`setting-windows-environment-variables` for details. * Using MiKTeX with Computer Modern fonts, if you get odd \*Agg and PNG - results, go to MiKTeX/Options and update your format files + results, go to MiKTeX/Options and update your format files. * On Ubuntu and Gentoo, the base texlive install does not ship with the type1cm package. You may need to install some of the extra packages to get all the goodies that come bundled with other LaTeX distributions. -* Some progress has been made so Matplotlib uses the dvi files - directly for text layout. This allows LaTeX to be used for text - layout with the pdf and svg backends, as well as the \*Agg and PS - backends. In the future, a LaTeX installation may be the only - external dependency. - .. _usetex-troubleshooting: Troubleshooting @@ -150,7 +150,7 @@ * Try deleting your :file:`.matplotlib/tex.cache` directory. If you don't know where to find :file:`.matplotlib`, see :ref:`locating-matplotlib-config-dir`. -* Make sure LaTeX, dvipng and ghostscript are each working and on your +* Make sure LaTeX, dvipng and Ghostscript are each working and on your :envvar:`PATH`. * Make sure what you are trying to do is possible in a LaTeX document, @@ -159,8 +159,7 @@ * :rc:`text.latex.preamble` is not officially supported. This option provides lots of flexibility, and lots of ways to cause - problems. Please disable this option before reporting problems to - the mailing list. + problems. Please disable this option before reporting problems. * If you still need help, please see :ref:`reporting-problems`. From 791d6c02c6a43b91ea2953910e6d20b9adffa612 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 16 Sep 2024 14:39:29 -0400 Subject: [PATCH 1092/1120] Backport PR #28818: Resolve configdir so that it's not a symlink when is_dir() is called --- lib/matplotlib/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 9e9325a27d73..ad4676b11ae0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -518,35 +518,38 @@ def _get_xdg_cache_dir(): def _get_config_or_cache_dir(xdg_base_getter): configdir = os.environ.get('MPLCONFIGDIR') if configdir: - configdir = Path(configdir).resolve() + configdir = Path(configdir) elif sys.platform.startswith(('linux', 'freebsd')): # Only call _xdg_base_getter here so that MPLCONFIGDIR is tried first, # as _xdg_base_getter can throw. configdir = Path(xdg_base_getter(), "matplotlib") else: configdir = Path.home() / ".matplotlib" + # Resolve the path to handle potential issues with inaccessible symlinks. + configdir = configdir.resolve() try: configdir.mkdir(parents=True, exist_ok=True) - except OSError: - pass + except OSError as exc: + _log.warning("mkdir -p failed for path %s: %s", configdir, exc) else: if os.access(str(configdir), os.W_OK) and configdir.is_dir(): return str(configdir) + _log.warning("%s is not a writable directory", configdir) # If the config or cache directory cannot be created or is not a writable # directory, create a temporary one. try: tmpdir = tempfile.mkdtemp(prefix="matplotlib-") except OSError as exc: raise OSError( - f"Matplotlib requires access to a writable cache directory, but the " - f"default path ({configdir}) is not a writable directory, and a temporary " + f"Matplotlib requires access to a writable cache directory, but there " + f"was an issue with the default path ({configdir}), and a temporary " f"directory could not be created; set the MPLCONFIGDIR environment " f"variable to a writable directory") from exc os.environ["MPLCONFIGDIR"] = tmpdir atexit.register(shutil.rmtree, tmpdir) _log.warning( - "Matplotlib created a temporary cache directory at %s because the default path " - "(%s) is not a writable directory; it is highly recommended to set the " + "Matplotlib created a temporary cache directory at %s because there was " + "an issue with the default path (%s); it is highly recommended to set the " "MPLCONFIGDIR environment variable to a writable directory, in particular to " "speed up the import of Matplotlib and to better support multiprocessing.", tmpdir, configdir) From ae85b62e623baffa6679f9f3a58384ba0e1e5947 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 18 Sep 2024 22:36:21 -0400 Subject: [PATCH 1093/1120] Backport PR #28836: MNT: Use __init__ parameters of font properties --- .../text_labels_and_annotations/fonts_demo.py | 17 +++++------------ galleries/users_explain/text/text_intro.py | 5 +---- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/galleries/examples/text_labels_and_annotations/fonts_demo.py b/galleries/examples/text_labels_and_annotations/fonts_demo.py index bc36c10bce8b..821ee278c4ba 100644 --- a/galleries/examples/text_labels_and_annotations/fonts_demo.py +++ b/galleries/examples/text_labels_and_annotations/fonts_demo.py @@ -21,34 +21,28 @@ fig.text(0.1, 0.9, 'family', fontproperties=heading_font, **alignment) families = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] for k, family in enumerate(families): - font = FontProperties() - font.set_family(family) + font = FontProperties(family=[family]) fig.text(0.1, yp[k], family, fontproperties=font, **alignment) # Show style options styles = ['normal', 'italic', 'oblique'] fig.text(0.3, 0.9, 'style', fontproperties=heading_font, **alignment) for k, style in enumerate(styles): - font = FontProperties() - font.set_family('sans-serif') - font.set_style(style) + font = FontProperties(family='sans-serif', style=style) fig.text(0.3, yp[k], style, fontproperties=font, **alignment) # Show variant options variants = ['normal', 'small-caps'] fig.text(0.5, 0.9, 'variant', fontproperties=heading_font, **alignment) for k, variant in enumerate(variants): - font = FontProperties() - font.set_family('serif') - font.set_variant(variant) + font = FontProperties(family='serif', variant=variant) fig.text(0.5, yp[k], variant, fontproperties=font, **alignment) # Show weight options weights = ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] fig.text(0.7, 0.9, 'weight', fontproperties=heading_font, **alignment) for k, weight in enumerate(weights): - font = FontProperties() - font.set_weight(weight) + font = FontProperties(weight=weight) fig.text(0.7, yp[k], weight, fontproperties=font, **alignment) # Show size options @@ -56,8 +50,7 @@ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'] fig.text(0.9, 0.9, 'size', fontproperties=heading_font, **alignment) for k, size in enumerate(sizes): - font = FontProperties() - font.set_size(size) + font = FontProperties(size=size) fig.text(0.9, yp[k], size, fontproperties=font, **alignment) # Show bold italic diff --git a/galleries/users_explain/text/text_intro.py b/galleries/users_explain/text/text_intro.py index 948545667fa9..3b8a66f1c98e 100644 --- a/galleries/users_explain/text/text_intro.py +++ b/galleries/users_explain/text/text_intro.py @@ -177,10 +177,7 @@ from matplotlib.font_manager import FontProperties -font = FontProperties() -font.set_family('serif') -font.set_name('Times New Roman') -font.set_style('italic') +font = FontProperties(family='Times New Roman', style='italic') fig, ax = plt.subplots(figsize=(5, 3)) fig.subplots_adjust(bottom=0.15, left=0.2) From 172624ebcbdccb08b27fb137910a2253871801e1 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 21 Sep 2024 08:31:41 +0100 Subject: [PATCH 1094/1120] Backport PR #28858: Fix flaky labelcolor tests --- lib/matplotlib/tests/test_legend.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 0353f1408b73..3c2af275649f 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -868,8 +868,8 @@ def test_legend_pathcollection_labelcolor_linecolor_iterable(): # test the labelcolor for labelcolor='linecolor' on PathCollection # with iterable colors fig, ax = plt.subplots() - colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) - ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c=colors) + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', c=colors) leg = ax.legend(labelcolor='linecolor') text, = leg.get_texts() @@ -915,8 +915,8 @@ def test_legend_pathcollection_labelcolor_markeredgecolor_iterable(): # test the labelcolor for labelcolor='markeredgecolor' on PathCollection # with iterable colors fig, ax = plt.subplots() - colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) - ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor=colors) + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', edgecolor=colors) leg = ax.legend(labelcolor='markeredgecolor') for text, color in zip(leg.get_texts(), ['k']): @@ -970,8 +970,8 @@ def test_legend_pathcollection_labelcolor_markerfacecolor_iterable(): # test the labelcolor for labelcolor='markerfacecolor' on PathCollection # with iterable colors fig, ax = plt.subplots() - colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) - ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor=colors) + colors = np.array(['r', 'g', 'b', 'c', 'm'] * 2) + ax.scatter(np.arange(10), np.arange(10), label='#1', facecolor=colors) leg = ax.legend(labelcolor='markerfacecolor') for text, color in zip(leg.get_texts(), ['k']): From ff3e448f0d7183d04c4e353fcb19948e88e5d31f Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:44:15 +0200 Subject: [PATCH 1095/1120] Backport PR #28883: Only check X11 when running Tkinter tests --- lib/matplotlib/_c_internal_utils.pyi | 1 + lib/matplotlib/cbook.py | 2 +- lib/matplotlib/tests/test_backend_tk.py | 4 +-- .../tests/test_backends_interactive.py | 10 +++++--- lib/matplotlib/tests/test_rcparams.py | 2 +- src/_c_internal_utils.cpp | 25 ++++++++++++++++++- 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/_c_internal_utils.pyi b/lib/matplotlib/_c_internal_utils.pyi index 3efc81bc8332..ccc172cde27a 100644 --- a/lib/matplotlib/_c_internal_utils.pyi +++ b/lib/matplotlib/_c_internal_utils.pyi @@ -1,4 +1,5 @@ def display_is_valid() -> bool: ... +def xdisplay_is_valid() -> bool: ... def Win32_GetForegroundWindow() -> int | None: ... def Win32_SetForegroundWindow(hwnd: int) -> None: ... diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index f5a4199cf9ad..c5b851ff6c9b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -72,7 +72,7 @@ def _get_running_interactive_framework(): if frame.f_code in codes: return "tk" frame = frame.f_back - # premetively break reference cycle between locals and the frame + # Preemptively break reference cycle between locals and the frame. del frame macosx = sys.modules.get("matplotlib.backends._macosx") if macosx and macosx.event_loop_is_running(): diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index ee20a94042f7..89782e8a66f3 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -35,8 +35,8 @@ def _isolated_tk_test(success_count, func=None): reason="missing tkinter" ) @pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), - reason="$DISPLAY and $WAYLAND_DISPLAY are unset" + sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), + reason="$DISPLAY is unset" ) @pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649 ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 2c6b61a48438..ca702bc1d99c 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -57,6 +57,8 @@ def wait_for(self, terminator): def _get_available_interactive_backends(): _is_linux_and_display_invalid = (sys.platform == "linux" and not _c_internal_utils.display_is_valid()) + _is_linux_and_xdisplay_invalid = (sys.platform == "linux" and + not _c_internal_utils.xdisplay_is_valid()) envs = [] for deps, env in [ *[([qt_api], @@ -74,10 +76,12 @@ def _get_available_interactive_backends(): ]: reason = None missing = [dep for dep in deps if not importlib.util.find_spec(dep)] - if _is_linux_and_display_invalid: - reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" - elif missing: + if missing: reason = "{} cannot be imported".format(", ".join(missing)) + elif env["MPLBACKEND"] == "tkagg" and _is_linux_and_xdisplay_invalid: + reason = "$DISPLAY is unset" + elif _is_linux_and_display_invalid: + reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): reason = "macosx backend fails on Azure" elif env["MPLBACKEND"].startswith('gtk'): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 4823df0ce250..25ae258ffcbb 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -536,7 +536,7 @@ def test_backend_fallback_headless(tmp_path): @pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), + sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless") def test_backend_fallback_headful(tmp_path): pytest.importorskip("tkinter") diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 74bb97904f89..561cb303639c 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -33,7 +33,7 @@ namespace py = pybind11; using namespace pybind11::literals; static bool -mpl_display_is_valid(void) +mpl_xdisplay_is_valid(void) { #ifdef __linux__ void* libX11; @@ -57,6 +57,19 @@ mpl_display_is_valid(void) return true; } } + return false; +#else + return true; +#endif +} + +static bool +mpl_display_is_valid(void) +{ +#ifdef __linux__ + if (mpl_xdisplay_is_valid()) { + return true; + } void* libwayland_client; if (getenv("WAYLAND_DISPLAY") && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { @@ -194,6 +207,16 @@ PYBIND11_MODULE(_c_internal_utils, m) succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) succeeds. + On other platforms, always returns True.)"""); + m.def( + "xdisplay_is_valid", &mpl_xdisplay_is_valid, + R"""( -- + Check whether the current X11 display is valid. + + On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) + succeeds. Use this function if you need to specifically check for X11 + only (e.g., for Tkinter). + On other platforms, always returns True.)"""); m.def( "Win32_GetCurrentProcessExplicitAppUserModelID", From 00f63b745f453f5e736b62d978df95ac9ee68f6b Mon Sep 17 00:00:00 2001 From: Kyra Cho Date: Fri, 27 Sep 2024 16:55:12 -0700 Subject: [PATCH 1096/1120] Backport PR #28881: Fix `axline` for slopes <= 1E-8. Closes #28386 --- lib/matplotlib/lines.py | 2 +- lib/matplotlib/tests/test_lines.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 72e74f4eb9c5..569669f76e8d 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1520,7 +1520,7 @@ def get_transform(self): (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) # General case: find intersections with view limits in either # direction, and draw between the middle two points. - if np.isclose(slope, 0): + if slope == 0: start = vxlo, y1 stop = vxhi, y1 elif np.isinf(slope): diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 531237b2ba28..902b7aa2c02d 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -436,3 +436,14 @@ def test_axline_setters(): with pytest.raises(ValueError, match="Cannot set a 'slope' value while 'xy2' is set"): line2.set_slope(3) + + +def test_axline_small_slope(): + """Test that small slopes are not coerced to zero in the transform.""" + line = plt.axline((0, 0), slope=1e-14) + p1 = line.get_transform().transform_point((0, 0)) + p2 = line.get_transform().transform_point((1, 1)) + # y-values must be slightly different + dy = p2[1] - p1[1] + assert dy > 0 + assert dy < 4e-12 From ab09fcc97c9ad791ba41dcfdd4276f634585263a Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:36:36 +0200 Subject: [PATCH 1097/1120] Backport PR #28689: ci: Enable testing on Python 3.13 --- .github/workflows/tests.yml | 54 +++++++++++++++++-- lib/matplotlib/tests/test_axes.py | 4 +- lib/matplotlib/tests/test_contour.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 2 +- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 634c83fa57fd..cd4c08e29fb9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,14 +88,31 @@ jobs: pyqt6-ver: '!=6.6.0' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - - os: macos-12 # This runnre is on Intel chips. - python-version: 3.9 + - os: ubuntu-22.04 + python-version: '3.13' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.6.0' + # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 + pyside6-ver: '!=6.5.1' + - name-suffix: "Free-threaded" + os: ubuntu-22.04 + python-version: '3.13t' + # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html + pyqt6-ver: '!=6.6.0' + # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 + pyside6-ver: '!=6.5.1' + - os: macos-12 # This runner is on Intel chips. + python-version: '3.9' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - os: macos-14 # This runner is on M1 (arm64) chips. python-version: '3.12' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' + - os: macos-14 # This runner is on M1 (arm64) chips. + python-version: '3.13' + # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 + pyside6-ver: '!=6.5.1' steps: - uses: actions/checkout@v4 @@ -104,8 +121,17 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 + if: matrix.python-version != '3.13t' with: python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Set up Python ${{ matrix.python-version }} + uses: deadsnakes/action@6c8b9b82fe0b4344f4b98f2775fcc395df45e494 # v3.1.0 + if: matrix.python-version == '3.13t' + with: + python-version: '3.13' + nogil: true - name: Install OS dependencies run: | @@ -152,6 +178,11 @@ jobs: texlive-luatex \ texlive-pictures \ texlive-xetex + if [[ "${{ matrix.python-version }}" = '3.13t' ]]; then + # TODO: Remove this once setup-python supports nogil distributions. + sudo apt-get install -yy --no-install-recommends \ + python3.13-tk-nogil + fi if [[ "${{ matrix.os }}" = ubuntu-20.04 ]]; then sudo apt-get install -yy --no-install-recommends libopengl0 else # ubuntu-22.04 @@ -202,6 +233,15 @@ jobs: 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl- + - name: Install the nightly dependencies + if: matrix.python-version == '3.13t' + run: | + python -m pip install pytz tzdata python-dateutil # Must be installed for Pandas. + python -m pip install \ + --pre \ + --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ + --upgrade --only-binary=:all: numpy pandas pillow contourpy + - name: Install Python dependencies run: | # Upgrade pip and setuptools and wheel to get as clean an install as @@ -227,6 +267,7 @@ jobs: # Sphinx is needed to run sphinxext tests python -m pip install --upgrade sphinx!=6.1.2 + if [[ "${{ matrix.python-version }}" != '3.13t' ]]; then # GUI toolkits are pip-installable only for some versions of Python # so don't fail if we can't install them. Make it easier to check # whether the install was successful by trying to import the toolkit @@ -246,11 +287,11 @@ jobs: python -c 'import PyQt5.QtCore' && echo 'PyQt5 is available' || echo 'PyQt5 is not available' - # Even though PySide2 wheels can be installed on Python 3.12, they are broken and since PySide2 is + # Even though PySide2 wheels can be installed on Python 3.12+, they are broken and since PySide2 is # deprecated, they are unlikely to be fixed. For the same deprecation reason, there are no wheels # on M1 macOS, so don't bother there either. if [[ "${{ matrix.os }}" != 'macos-14' - && "${{ matrix.python-version }}" != '3.12' ]]; then + && "${{ matrix.python-version }}" != '3.12' && "${{ matrix.python-version }}" != '3.13' ]]; then python -mpip install --upgrade pyside2${{ matrix.pyside2-ver }} && python -c 'import PySide2.QtCore' && echo 'PySide2 is available' || @@ -272,6 +313,8 @@ jobs: echo 'wxPython is available' || echo 'wxPython is not available' + fi # Skip backends on Python 3.13t. + - name: Install the nightly dependencies # Only install the nightly dependencies during the scheduled event if: github.event_name == 'schedule' && matrix.name-suffix != '(Minimum Versions)' @@ -310,6 +353,9 @@ jobs: - name: Run pytest run: | + if [[ "${{ matrix.python-version }}" == '3.13t' ]]; then + export PYTHON_GIL=0 + fi pytest -rfEsXR -n auto \ --maxfail=50 --timeout=300 --durations=25 \ --cov-report=xml --cov=lib --log-level=DEBUG --color=yes diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3ec9923c0840..e99ef129eb9a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1186,7 +1186,7 @@ def test_imshow(): @image_comparison( ['imshow_clip'], style='mpl20', - tol=1.24 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) + tol=1.24 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) def test_imshow_clip(): # As originally reported by Gellule Xg # use former defaults to match existing baseline image @@ -2570,7 +2570,7 @@ def test_contour_hatching(): @image_comparison( ['contour_colorbar'], style='mpl20', - tol=0.54 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) + tol=0.54 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) def test_contour_colorbar(): x, y, z = contour_dat() diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index d4600a14fe1c..0622c099a20c 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -442,7 +442,7 @@ def test_contourf_log_extension(split_collections): @pytest.mark.parametrize("split_collections", [False, True]) @image_comparison( ['contour_addlines.png'], remove_text=True, style='mpl20', - tol=0.15 if platform.machine() in ('aarch64', 'ppc64le', 's390x') + tol=0.15 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0.03) # tolerance is because image changed minutely when tick finding on # colorbars was cleaned up... diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ecb51b724c27..ff5ab230ef06 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -221,7 +221,7 @@ def test_bar3d_lightsource(): @mpl3d_image_comparison( ['contour3d.png'], style='mpl20', - tol=0.002 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0) + tol=0.002 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) def test_contour3d(): plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() From fad3579287324c16daeb8719af68ccb58fe1b94b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Oct 2024 05:02:26 -0400 Subject: [PATCH 1098/1120] Backport PR #28900: DOC: Improve fancybox demo --- .../shapes_and_collections/fancybox_demo.py | 84 ++++++++++++++----- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/galleries/examples/shapes_and_collections/fancybox_demo.py b/galleries/examples/shapes_and_collections/fancybox_demo.py index 91cc1d1749ea..8d36a5a14d9d 100644 --- a/galleries/examples/shapes_and_collections/fancybox_demo.py +++ b/galleries/examples/shapes_and_collections/fancybox_demo.py @@ -3,7 +3,8 @@ Drawing fancy boxes =================== -The following examples show how to plot boxes with different visual properties. +The following examples show how to plot boxes (`.FancyBboxPatch`) with different +visual properties. """ import inspect @@ -15,7 +16,12 @@ import matplotlib.transforms as mtransforms # %% -# First we'll show some sample boxes with fancybox. +# Box styles +# ---------- +# `.FancyBboxPatch` supports different `.BoxStyle`\s. Note that `~.Axes.text` +# allows to draw a box around the text by adding the ``bbox`` parameter. Therefore, +# you don't see explicit `.FancyBboxPatch` and `.BoxStyle` calls in the following +# example. styles = mpatch.BoxStyle.get_styles() ncol = 2 @@ -41,13 +47,21 @@ # %% -# Next we'll show off multiple fancy boxes at once. - +# Parameters for modifying the box +# -------------------------------- +# `.BoxStyle`\s have additional parameters to configure their appearance. +# For example, "round" boxes can have ``pad`` and ``rounding``. +# +# Additionally, the `.FancyBboxPatch` parameters ``mutation_scale`` and +# ``mutation_aspect`` scale the box appearance. def add_fancy_patch_around(ax, bb, **kwargs): - fancy = FancyBboxPatch(bb.p0, bb.width, bb.height, - fc=(1, 0.8, 1, 0.5), ec=(1, 0.5, 1, 0.5), - **kwargs) + kwargs = { + 'facecolor': (1, 0.8, 1, 0.5), + 'edgecolor': (1, 0.5, 1, 0.5), + **kwargs + } + fancy = FancyBboxPatch(bb.p0, bb.width, bb.height, **kwargs) ax.add_patch(fancy) return fancy @@ -65,7 +79,7 @@ def draw_control_points_for_patches(ax): ax = axs[0, 0] # a fancy box with round corners. pad=0.1 -fancy = add_fancy_patch_around(ax, bb, boxstyle="round,pad=0.1") +add_fancy_patch_around(ax, bb, boxstyle="round,pad=0.1") ax.set(xlim=(0, 1), ylim=(0, 1), aspect=1, title='boxstyle="round,pad=0.1"') @@ -84,33 +98,61 @@ def draw_control_points_for_patches(ax): ax = axs[1, 0] # mutation_scale determines the overall scale of the mutation, i.e. both pad # and rounding_size is scaled according to this value. -fancy = add_fancy_patch_around( - ax, bb, boxstyle="round,pad=0.1", mutation_scale=2) +add_fancy_patch_around(ax, bb, boxstyle="round,pad=0.1", mutation_scale=2) ax.set(xlim=(0, 1), ylim=(0, 1), aspect=1, title='boxstyle="round,pad=0.1"\n mutation_scale=2') ax = axs[1, 1] -# When the aspect ratio of the Axes is not 1, the fancy box may not be what you -# expected (green). -fancy = add_fancy_patch_around(ax, bb, boxstyle="round,pad=0.2") -fancy.set(facecolor="none", edgecolor="green") -# You can compensate this by setting the mutation_aspect (pink). -fancy = add_fancy_patch_around( - ax, bb, boxstyle="round,pad=0.3", mutation_aspect=0.5) -ax.set(xlim=(-.5, 1.5), ylim=(0, 1), aspect=2, - title='boxstyle="round,pad=0.3"\nmutation_aspect=.5') +# mutation_aspect scales the vertical influence of the parameters (technically, +# it scales the height of the box down by mutation_aspect, applies the box parameters +# and scales the result back up). In effect, the vertical pad is scaled to +# pad * mutation_aspect, e.g. mutation_aspect=0.5 halves the vertical pad. +add_fancy_patch_around(ax, bb, boxstyle="round,pad=0.1", mutation_aspect=0.5) +ax.set(xlim=(0, 1), ylim=(0, 1), + title='boxstyle="round,pad=0.1"\nmutation_aspect=0.5') for ax in axs.flat: draw_control_points_for_patches(ax) # Draw the original bbox (using boxstyle=square with pad=0). - fancy = add_fancy_patch_around(ax, bb, boxstyle="square,pad=0") - fancy.set(edgecolor="black", facecolor="none", zorder=10) + add_fancy_patch_around(ax, bb, boxstyle="square,pad=0", + edgecolor="black", facecolor="none", zorder=10) fig.tight_layout() plt.show() +# %% +# Creating visually constant padding on non-equal aspect Axes +# ----------------------------------------------------------- +# Since padding is in box coordinates, i.e. usually data coordinates, +# a given padding is rendered to different visual sizes if the +# Axes aspect is not 1. +# To get visually equal vertical and horizontal padding, set the +# mutation_aspect to the inverse of the Axes aspect. This scales +# the vertical padding appropriately. + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6.5, 5)) + +# original boxes +bb = mtransforms.Bbox([[-0.5, -0.5], [0.5, 0.5]]) +add_fancy_patch_around(ax1, bb, boxstyle="square,pad=0", + edgecolor="black", facecolor="none", zorder=10) +add_fancy_patch_around(ax2, bb, boxstyle="square,pad=0", + edgecolor="black", facecolor="none", zorder=10) +ax1.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), aspect=2) +ax2.set(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), aspect=2) + + +fancy = add_fancy_patch_around( + ax1, bb, boxstyle="round,pad=0.5") +ax1.set_title("aspect=2\nmutation_aspect=1") + +fancy = add_fancy_patch_around( + ax2, bb, boxstyle="round,pad=0.5", mutation_aspect=0.5) +ax2.set_title("aspect=2\nmutation_aspect=0.5") + + # %% # # .. admonition:: References From 97ea6ef94d0e67271fda0853b55aaf8c077ae4df Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:07:04 -0700 Subject: [PATCH 1099/1120] Backport PR #28952: BLD: update trove metadata to support py3.13 (#28954) Co-authored-by: Greg Lucas --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 891ef87e4342..c0237c7df5c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers=[ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Visualization", ] From 7fd3b7ba4aae22d6defd14d1cbb69e562d7f4522 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:04:14 +0200 Subject: [PATCH 1100/1120] Backport PR #28987: Fix: Do not use numeric tolerances for axline special cases --- lib/matplotlib/lines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 569669f76e8d..d24e528e4c0a 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1505,8 +1505,8 @@ def get_transform(self): points_transform.transform([self._xy1, self._xy2]) dx = x2 - x1 dy = y2 - y1 - if np.allclose(x1, x2): - if np.allclose(y1, y2): + if dx == 0: + if dy == 0: raise ValueError( f"Cannot draw a line through two identical points " f"(x={(x1, x2)}, y={(y1, y2)})") From c2116dea25226d756b55e55489a53f73b89cdf97 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 17 Oct 2024 15:07:00 -0400 Subject: [PATCH 1101/1120] Backport PR #28972: Switch macOS 12 runner images to macOS 13 --- .github/workflows/cibuildwheel.yml | 2 +- .github/workflows/tests.yml | 13 ++++++++++++- azure-pipelines.yml | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9de63b14c4fd..a145b5bbcc08 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -124,7 +124,7 @@ jobs: cibw_archs: "aarch64" - os: windows-latest cibw_archs: "auto64" - - os: macos-12 + - os: macos-13 cibw_archs: "x86_64" - os: macos-14 cibw_archs: "arm64" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cd4c08e29fb9..4100fda43a36 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -101,7 +101,7 @@ jobs: pyqt6-ver: '!=6.6.0' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' - - os: macos-12 # This runner is on Intel chips. + - os: macos-13 # This runner is on Intel chips. python-version: '3.9' # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 pyside6-ver: '!=6.5.1' @@ -192,6 +192,17 @@ jobs: ;; macOS) brew update + # Periodically, Homebrew updates Python and fails to overwrite the + # existing not-managed-by-Homebrew copy without explicitly being told + # to do so. GitHub/Azure continues to avoid fixing their runner images: + # https://github.com/actions/runner-images/issues/9966 + # so force an overwrite even if there are no Python updates. + # We don't even care about Homebrew's Python because we use the one + # from actions/setup-python. + for python_package in $(brew list | grep python@); do + brew unlink ${python_package} + brew link --overwrite ${python_package} + done brew install ccache ghostscript gobject-introspection gtk4 ninja brew install --cask font-noto-sans-cjk inkscape ;; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4c50c543846a..e2829ce81e9f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -120,6 +120,17 @@ stages: ;; Darwin) brew update + # Periodically, Homebrew updates Python and fails to overwrite the + # existing not-managed-by-Homebrew copy without explicitly being told + # to do so. GitHub/Azure continues to avoid fixing their runner images: + # https://github.com/actions/runner-images/issues/9966 + # so force an overwrite even if there are no Python updates. + # We don't even care about Homebrew's Python because we use the one + # from UsePythonVersion. + for python_package in $(brew list | grep python@); do + brew unlink ${python_package} + brew link --overwrite ${python_package} + done brew install --cask xquartz brew install ccache ffmpeg imagemagick mplayer ninja pkg-config brew install --cask font-noto-sans-cjk-sc From 1f1901baa5ce5788eba5feb63ae12df2eac84975 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 7 Oct 2024 16:08:48 +0200 Subject: [PATCH 1102/1120] Backport PR #28925: TST: handle change in pytest.importorskip behavior --- lib/matplotlib/tests/test_rcparams.py | 8 +++++++- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 25ae258ffcbb..0aa3ec0ba603 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -5,6 +5,7 @@ from unittest import mock from cycler import cycler, Cycler +from packaging.version import parse as parse_version import pytest import matplotlib as mpl @@ -539,7 +540,12 @@ def test_backend_fallback_headless(tmp_path): sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless") def test_backend_fallback_headful(tmp_path): - pytest.importorskip("tkinter") + if parse_version(pytest.__version__) >= parse_version('8.2.0'): + pytest_kwargs = dict(exc_type=ImportError) + else: + pytest_kwargs = {} + + pytest.importorskip("tkinter", **pytest_kwargs) env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} backend = subprocess_run_for_testing( [sys.executable, "-c", diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ff5ab230ef06..20b8dcd432db 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1,6 +1,7 @@ import functools import itertools import platform +import sys import pytest @@ -114,7 +115,7 @@ def test_axes3d_repr(): @mpl3d_image_comparison(['axes3d_primary_views.png'], style='mpl20', - tol=0.05 if platform.machine() == "arm64" else 0) + tol=0.05 if sys.platform == "darwin" else 0) def test_axes3d_primary_views(): # (elev, azim, roll) views = [(90, -90, 0), # XY From 7b547eacebb0d854152e0e5d1afb3edc5da5ee4d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:09:44 +0100 Subject: [PATCH 1103/1120] Backport PR #28993: FIX: contourf hatches use multiple edgecolors --- lib/matplotlib/contour.py | 4 ++++ .../test_contour/contourf_hatch_colors.png | Bin 0 -> 19765 bytes lib/matplotlib/tests/test_contour.py | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 0e6068c64b62..3b2bf3843f4d 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1415,12 +1415,16 @@ def draw(self, renderer): super().draw(renderer) return # In presence of hatching, draw contours one at a time. + edgecolors = self.get_edgecolors() + if edgecolors.size == 0: + edgecolors = ("none",) for idx in range(n_paths): with cbook._setattr_cm(self, _paths=[paths[idx]]), self._cm_set( hatch=self.hatches[idx % len(self.hatches)], array=[self.get_array()[idx]], linewidths=[self.get_linewidths()[idx % len(self.get_linewidths())]], linestyles=[self.get_linestyles()[idx % len(self.get_linestyles())]], + edgecolors=edgecolors[idx % len(edgecolors)], ): super().draw(renderer) diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png b/lib/matplotlib/tests/baseline_images/test_contour/contourf_hatch_colors.png new file mode 100644 index 0000000000000000000000000000000000000000..18d949773ded79b5e931e035032c7f0ee590cd6b GIT binary patch literal 19765 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 0622c099a20c..a3e00c30ce97 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -198,6 +198,14 @@ def test_given_colors_levels_and_extends(split_collections): _maybe_split_collections(split_collections) +@image_comparison(['contourf_hatch_colors'], + remove_text=True, style='mpl20', extensions=['png']) +def test_hatch_colors(): + fig, ax = plt.subplots() + cf = ax.contourf([[0, 1], [1, 2]], hatches=['-', '/', '\\', '//'], cmap='gray') + cf.set_edgecolors(["blue", "grey", "yellow", "red"]) + + @pytest.mark.parametrize("split_collections", [False, True]) @image_comparison(['contour_log_locator.svg'], style='mpl20', remove_text=False) def test_log_locator_levels(split_collections): From 3cf5e95e1c98e5ab3ba955606b2bfd1aa324b911 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:24:26 +0200 Subject: [PATCH 1104/1120] Backport PR #29005: DOC: Update meson-python intersphinx link --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 8036edec9989..04f48204eb77 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -241,7 +241,7 @@ def _check_dependencies(): 'scipy': ('https://docs.scipy.org/doc/scipy/', None), 'tornado': ('https://www.tornadoweb.org/en/stable/', None), 'xarray': ('https://docs.xarray.dev/en/stable/', None), - 'meson-python': ('https://meson-python.readthedocs.io/en/stable/', None) + 'meson-python': ('https://mesonbuild.com/meson-python/', None), } From f3df3a16d1dce3770555d94a386b3f8587f8feba Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 24 Oct 2024 07:02:26 +0200 Subject: [PATCH 1105/1120] Backport PR #29014: FIX: fake out setuptools scm in tox on ci --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index cb2fcc979076..a4fbbd1793ff 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,8 @@ setenv = MPLCONFIGDIR={envtmpdir}/.matplotlib PIP_USER = 0 PIP_ISOLATED = 1 + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MATPLOTLIB = 0.0.0 + usedevelop = True commands = pytest --pyargs matplotlib {posargs} From 5882c882d67ef134a6ad758f3f54cb43c25cb05b Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 28 Oct 2024 18:49:26 -0400 Subject: [PATCH 1106/1120] Backport PR #27569: DOC: initial tags for statistics section of gallery --- galleries/examples/statistics/boxplot.py | 2 ++ galleries/examples/statistics/boxplot_color.py | 2 ++ galleries/examples/statistics/boxplot_demo.py | 2 ++ galleries/examples/statistics/boxplot_vs_violin.py | 2 ++ galleries/examples/statistics/bxp.py | 2 ++ galleries/examples/statistics/confidence_ellipse.py | 8 ++++++++ galleries/examples/statistics/customized_violin.py | 2 ++ galleries/examples/statistics/errorbar.py | 3 +++ galleries/examples/statistics/errorbar_features.py | 2 ++ galleries/examples/statistics/errorbar_limits.py | 2 ++ galleries/examples/statistics/errorbars_and_boxes.py | 8 ++++++++ galleries/examples/statistics/hexbin_demo.py | 2 ++ galleries/examples/statistics/hist.py | 9 +++++++++ galleries/examples/statistics/histogram_bihistogram.py | 4 ++++ galleries/examples/statistics/histogram_cumulative.py | 2 ++ galleries/examples/statistics/histogram_histtypes.py | 2 ++ galleries/examples/statistics/histogram_multihist.py | 2 ++ galleries/examples/statistics/histogram_normalization.py | 2 ++ .../statistics/multiple_histograms_side_by_side.py | 7 +++++++ galleries/examples/statistics/time_series_histogram.py | 8 ++++++++ galleries/examples/statistics/violinplot.py | 2 ++ 21 files changed, 75 insertions(+) diff --git a/galleries/examples/statistics/boxplot.py b/galleries/examples/statistics/boxplot.py index ccdaab97d24a..6d30cbd4b5f0 100644 --- a/galleries/examples/statistics/boxplot.py +++ b/galleries/examples/statistics/boxplot.py @@ -97,6 +97,8 @@ # %% # +# .. tags:: plot-type: boxplot, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_color.py b/galleries/examples/statistics/boxplot_color.py index 3491253aaf3e..acdb37d7d520 100644 --- a/galleries/examples/statistics/boxplot_color.py +++ b/galleries/examples/statistics/boxplot_color.py @@ -36,6 +36,8 @@ # %% # +# .. tags:: styling: color, domain: statistics, plot-type: boxplot +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_demo.py b/galleries/examples/statistics/boxplot_demo.py index eca0e152078e..ec23408c0bfc 100644 --- a/galleries/examples/statistics/boxplot_demo.py +++ b/galleries/examples/statistics/boxplot_demo.py @@ -247,6 +247,8 @@ def fake_bootstrapper(n): # %% # +# .. tags:: domain: statistics, plot-type: boxplot +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_vs_violin.py b/galleries/examples/statistics/boxplot_vs_violin.py index dce8912013b0..f277e737e65c 100644 --- a/galleries/examples/statistics/boxplot_vs_violin.py +++ b/galleries/examples/statistics/boxplot_vs_violin.py @@ -54,6 +54,8 @@ # %% # +# .. tags:: plot-type: violin, plot-type: boxplot, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/bxp.py b/galleries/examples/statistics/bxp.py index b12bfebd16cc..3ddd164df742 100644 --- a/galleries/examples/statistics/bxp.py +++ b/galleries/examples/statistics/bxp.py @@ -64,6 +64,8 @@ # %% # +# .. tags:: plot-type: speciality, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/confidence_ellipse.py b/galleries/examples/statistics/confidence_ellipse.py index 27b06146a44e..4ac0917f4bda 100644 --- a/galleries/examples/statistics/confidence_ellipse.py +++ b/galleries/examples/statistics/confidence_ellipse.py @@ -217,6 +217,14 @@ def get_correlated_dataset(n, dependency, mu, scale): # %% # +# .. tags:: +# +# plot-type: speciality +# plot-type: scatter +# component: ellipse +# component: patch +# domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index c1d2e432ca2e..29ddcda92fbe 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -73,6 +73,8 @@ def set_axis_style(ax, labels): # %% # +# .. tags:: plot-type: violin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar.py b/galleries/examples/statistics/errorbar.py index a25cadf88211..019590dc3f32 100644 --- a/galleries/examples/statistics/errorbar.py +++ b/galleries/examples/statistics/errorbar.py @@ -21,6 +21,9 @@ # %% # +# +# .. tags:: plot-type: errorbar, domain: statistics, +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar_features.py b/galleries/examples/statistics/errorbar_features.py index 349cc89ff2e0..8abaffcda537 100644 --- a/galleries/examples/statistics/errorbar_features.py +++ b/galleries/examples/statistics/errorbar_features.py @@ -48,6 +48,8 @@ # %% # +# .. tags:: plot-type: errorbar, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar_limits.py b/galleries/examples/statistics/errorbar_limits.py index d5b0e476d894..f1d26460d947 100644 --- a/galleries/examples/statistics/errorbar_limits.py +++ b/galleries/examples/statistics/errorbar_limits.py @@ -77,6 +77,8 @@ # %% # +# .. tags:: plot-type: errorbar, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbars_and_boxes.py b/galleries/examples/statistics/errorbars_and_boxes.py index 54c8786096c7..b45eee751137 100644 --- a/galleries/examples/statistics/errorbars_and_boxes.py +++ b/galleries/examples/statistics/errorbars_and_boxes.py @@ -71,6 +71,14 @@ def make_error_boxes(ax, xdata, ydata, xerror, yerror, facecolor='r', # %% # +# +# .. tags:: +# +# plot-type: errorbar +# component: rectangle +# component: patchcollection +# domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/hexbin_demo.py b/galleries/examples/statistics/hexbin_demo.py index b9a6206a934f..bd1522772aae 100644 --- a/galleries/examples/statistics/hexbin_demo.py +++ b/galleries/examples/statistics/hexbin_demo.py @@ -35,6 +35,8 @@ # %% # +# .. tags:: plot-type: histogram, plot-type: hexbin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/hist.py b/galleries/examples/statistics/hist.py index 8b06093913df..e31aca0228c2 100644 --- a/galleries/examples/statistics/hist.py +++ b/galleries/examples/statistics/hist.py @@ -103,6 +103,15 @@ # %% # +# .. tags:: +# +# plot-type: histogram, +# plot-type: histogram2d +# domain: statistics +# styling: color, +# component: normalization +# component: patch +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index 7bfae14e9285..73f549493438 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -43,3 +43,7 @@ ax.legend() plt.show() + +# %% +# +# .. tags:: plot-type: histogram, domain: statistics, purpose: showcase diff --git a/galleries/examples/statistics/histogram_cumulative.py b/galleries/examples/statistics/histogram_cumulative.py index 9ce16568d126..d87305629f8d 100644 --- a/galleries/examples/statistics/histogram_cumulative.py +++ b/galleries/examples/statistics/histogram_cumulative.py @@ -67,6 +67,8 @@ # %% # +# .. tags:: plot-type: ecdf, plot-type: histogram, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_histtypes.py b/galleries/examples/statistics/histogram_histtypes.py index 0188d7bf5d0f..53d6425cf4dc 100644 --- a/galleries/examples/statistics/histogram_histtypes.py +++ b/galleries/examples/statistics/histogram_histtypes.py @@ -51,6 +51,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics, purpose: reference +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index f1957dc38939..78ff03719057 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -47,6 +47,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics, purpose: reference +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_normalization.py b/galleries/examples/statistics/histogram_normalization.py index 9418b7af002b..2c423edad208 100644 --- a/galleries/examples/statistics/histogram_normalization.py +++ b/galleries/examples/statistics/histogram_normalization.py @@ -243,6 +243,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/multiple_histograms_side_by_side.py b/galleries/examples/statistics/multiple_histograms_side_by_side.py index 3c5766f8e546..733aed51f253 100644 --- a/galleries/examples/statistics/multiple_histograms_side_by_side.py +++ b/galleries/examples/statistics/multiple_histograms_side_by_side.py @@ -63,6 +63,13 @@ # %% # +# .. tags:: +# +# domain: statistics +# plot-type: barh +# plot-type: histogram +# styling: position +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/time_series_histogram.py b/galleries/examples/statistics/time_series_histogram.py index 371d182915b8..6f9a543fb4e5 100644 --- a/galleries/examples/statistics/time_series_histogram.py +++ b/galleries/examples/statistics/time_series_histogram.py @@ -94,6 +94,14 @@ # %% # +# .. tags:: +# +# plot-type: histogram2d +# plot-type: pcolormesh +# purpose: storytelling +# styling: color +# component: colormap +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/violinplot.py b/galleries/examples/statistics/violinplot.py index afcc1c977034..3d0d44538032 100644 --- a/galleries/examples/statistics/violinplot.py +++ b/galleries/examples/statistics/violinplot.py @@ -106,6 +106,8 @@ # %% # +# .. tags:: plot-type: violin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown From af21823d513c6c01d9b8037fa6c2e68ab90fedfe Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 28 Oct 2024 18:49:26 -0400 Subject: [PATCH 1107/1120] Backport PR #27569: DOC: initial tags for statistics section of gallery --- galleries/examples/statistics/boxplot.py | 2 ++ galleries/examples/statistics/boxplot_color.py | 2 ++ galleries/examples/statistics/boxplot_demo.py | 2 ++ galleries/examples/statistics/boxplot_vs_violin.py | 2 ++ galleries/examples/statistics/bxp.py | 2 ++ galleries/examples/statistics/confidence_ellipse.py | 8 ++++++++ galleries/examples/statistics/customized_violin.py | 2 ++ galleries/examples/statistics/errorbar.py | 3 +++ galleries/examples/statistics/errorbar_features.py | 2 ++ galleries/examples/statistics/errorbar_limits.py | 2 ++ galleries/examples/statistics/errorbars_and_boxes.py | 8 ++++++++ galleries/examples/statistics/hexbin_demo.py | 2 ++ galleries/examples/statistics/hist.py | 9 +++++++++ galleries/examples/statistics/histogram_bihistogram.py | 4 ++++ galleries/examples/statistics/histogram_cumulative.py | 2 ++ galleries/examples/statistics/histogram_histtypes.py | 2 ++ galleries/examples/statistics/histogram_multihist.py | 2 ++ galleries/examples/statistics/histogram_normalization.py | 2 ++ .../statistics/multiple_histograms_side_by_side.py | 7 +++++++ galleries/examples/statistics/time_series_histogram.py | 8 ++++++++ galleries/examples/statistics/violinplot.py | 2 ++ 21 files changed, 75 insertions(+) diff --git a/galleries/examples/statistics/boxplot.py b/galleries/examples/statistics/boxplot.py index ccdaab97d24a..6d30cbd4b5f0 100644 --- a/galleries/examples/statistics/boxplot.py +++ b/galleries/examples/statistics/boxplot.py @@ -97,6 +97,8 @@ # %% # +# .. tags:: plot-type: boxplot, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_color.py b/galleries/examples/statistics/boxplot_color.py index 3491253aaf3e..acdb37d7d520 100644 --- a/galleries/examples/statistics/boxplot_color.py +++ b/galleries/examples/statistics/boxplot_color.py @@ -36,6 +36,8 @@ # %% # +# .. tags:: styling: color, domain: statistics, plot-type: boxplot +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_demo.py b/galleries/examples/statistics/boxplot_demo.py index eca0e152078e..ec23408c0bfc 100644 --- a/galleries/examples/statistics/boxplot_demo.py +++ b/galleries/examples/statistics/boxplot_demo.py @@ -247,6 +247,8 @@ def fake_bootstrapper(n): # %% # +# .. tags:: domain: statistics, plot-type: boxplot +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/boxplot_vs_violin.py b/galleries/examples/statistics/boxplot_vs_violin.py index dce8912013b0..f277e737e65c 100644 --- a/galleries/examples/statistics/boxplot_vs_violin.py +++ b/galleries/examples/statistics/boxplot_vs_violin.py @@ -54,6 +54,8 @@ # %% # +# .. tags:: plot-type: violin, plot-type: boxplot, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/bxp.py b/galleries/examples/statistics/bxp.py index b12bfebd16cc..3ddd164df742 100644 --- a/galleries/examples/statistics/bxp.py +++ b/galleries/examples/statistics/bxp.py @@ -64,6 +64,8 @@ # %% # +# .. tags:: plot-type: speciality, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/confidence_ellipse.py b/galleries/examples/statistics/confidence_ellipse.py index 27b06146a44e..4ac0917f4bda 100644 --- a/galleries/examples/statistics/confidence_ellipse.py +++ b/galleries/examples/statistics/confidence_ellipse.py @@ -217,6 +217,14 @@ def get_correlated_dataset(n, dependency, mu, scale): # %% # +# .. tags:: +# +# plot-type: speciality +# plot-type: scatter +# component: ellipse +# component: patch +# domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index c1d2e432ca2e..29ddcda92fbe 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -73,6 +73,8 @@ def set_axis_style(ax, labels): # %% # +# .. tags:: plot-type: violin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar.py b/galleries/examples/statistics/errorbar.py index a25cadf88211..019590dc3f32 100644 --- a/galleries/examples/statistics/errorbar.py +++ b/galleries/examples/statistics/errorbar.py @@ -21,6 +21,9 @@ # %% # +# +# .. tags:: plot-type: errorbar, domain: statistics, +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar_features.py b/galleries/examples/statistics/errorbar_features.py index 349cc89ff2e0..8abaffcda537 100644 --- a/galleries/examples/statistics/errorbar_features.py +++ b/galleries/examples/statistics/errorbar_features.py @@ -48,6 +48,8 @@ # %% # +# .. tags:: plot-type: errorbar, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbar_limits.py b/galleries/examples/statistics/errorbar_limits.py index d5b0e476d894..f1d26460d947 100644 --- a/galleries/examples/statistics/errorbar_limits.py +++ b/galleries/examples/statistics/errorbar_limits.py @@ -77,6 +77,8 @@ # %% # +# .. tags:: plot-type: errorbar, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/errorbars_and_boxes.py b/galleries/examples/statistics/errorbars_and_boxes.py index 54c8786096c7..b45eee751137 100644 --- a/galleries/examples/statistics/errorbars_and_boxes.py +++ b/galleries/examples/statistics/errorbars_and_boxes.py @@ -71,6 +71,14 @@ def make_error_boxes(ax, xdata, ydata, xerror, yerror, facecolor='r', # %% # +# +# .. tags:: +# +# plot-type: errorbar +# component: rectangle +# component: patchcollection +# domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/hexbin_demo.py b/galleries/examples/statistics/hexbin_demo.py index b9a6206a934f..bd1522772aae 100644 --- a/galleries/examples/statistics/hexbin_demo.py +++ b/galleries/examples/statistics/hexbin_demo.py @@ -35,6 +35,8 @@ # %% # +# .. tags:: plot-type: histogram, plot-type: hexbin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/hist.py b/galleries/examples/statistics/hist.py index 8b06093913df..e31aca0228c2 100644 --- a/galleries/examples/statistics/hist.py +++ b/galleries/examples/statistics/hist.py @@ -103,6 +103,15 @@ # %% # +# .. tags:: +# +# plot-type: histogram, +# plot-type: histogram2d +# domain: statistics +# styling: color, +# component: normalization +# component: patch +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_bihistogram.py b/galleries/examples/statistics/histogram_bihistogram.py index 7bfae14e9285..73f549493438 100644 --- a/galleries/examples/statistics/histogram_bihistogram.py +++ b/galleries/examples/statistics/histogram_bihistogram.py @@ -43,3 +43,7 @@ ax.legend() plt.show() + +# %% +# +# .. tags:: plot-type: histogram, domain: statistics, purpose: showcase diff --git a/galleries/examples/statistics/histogram_cumulative.py b/galleries/examples/statistics/histogram_cumulative.py index 9ce16568d126..d87305629f8d 100644 --- a/galleries/examples/statistics/histogram_cumulative.py +++ b/galleries/examples/statistics/histogram_cumulative.py @@ -67,6 +67,8 @@ # %% # +# .. tags:: plot-type: ecdf, plot-type: histogram, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_histtypes.py b/galleries/examples/statistics/histogram_histtypes.py index 0188d7bf5d0f..53d6425cf4dc 100644 --- a/galleries/examples/statistics/histogram_histtypes.py +++ b/galleries/examples/statistics/histogram_histtypes.py @@ -51,6 +51,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics, purpose: reference +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index f1957dc38939..78ff03719057 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -47,6 +47,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics, purpose: reference +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/histogram_normalization.py b/galleries/examples/statistics/histogram_normalization.py index 9418b7af002b..2c423edad208 100644 --- a/galleries/examples/statistics/histogram_normalization.py +++ b/galleries/examples/statistics/histogram_normalization.py @@ -243,6 +243,8 @@ # %% # +# .. tags:: plot-type: histogram, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/multiple_histograms_side_by_side.py b/galleries/examples/statistics/multiple_histograms_side_by_side.py index 3c5766f8e546..733aed51f253 100644 --- a/galleries/examples/statistics/multiple_histograms_side_by_side.py +++ b/galleries/examples/statistics/multiple_histograms_side_by_side.py @@ -63,6 +63,13 @@ # %% # +# .. tags:: +# +# domain: statistics +# plot-type: barh +# plot-type: histogram +# styling: position +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/time_series_histogram.py b/galleries/examples/statistics/time_series_histogram.py index 371d182915b8..6f9a543fb4e5 100644 --- a/galleries/examples/statistics/time_series_histogram.py +++ b/galleries/examples/statistics/time_series_histogram.py @@ -94,6 +94,14 @@ # %% # +# .. tags:: +# +# plot-type: histogram2d +# plot-type: pcolormesh +# purpose: storytelling +# styling: color +# component: colormap +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/galleries/examples/statistics/violinplot.py b/galleries/examples/statistics/violinplot.py index afcc1c977034..3d0d44538032 100644 --- a/galleries/examples/statistics/violinplot.py +++ b/galleries/examples/statistics/violinplot.py @@ -106,6 +106,8 @@ # %% # +# .. tags:: plot-type: violin, domain: statistics +# # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown From d6270b1ec462118f8181f98442a985d5e0a99a24 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 28 Oct 2024 23:05:33 -0400 Subject: [PATCH 1108/1120] Backport PR #29031: DOC: Fix copy-paste typo in ColorSequenceRegistry --- lib/matplotlib/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 5f40e7b0fb9a..2c0d1b5d9a4f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -105,7 +105,7 @@ class ColorSequenceRegistry(Mapping): Read access uses a dict-like interface mapping names to lists of colors:: import matplotlib as mpl - cmap = mpl.color_sequences['tab10'] + colors = mpl.color_sequences['tab10'] The returned lists are copies, so that their modification does not change the global definition of the color sequence. From f9e680177baf36ffa237a49af5fa993b677d7b38 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:56:09 +0100 Subject: [PATCH 1109/1120] Backport PR #29036: Don't pass redundant inline=True to example clabel() calls. --- .../images_contours_and_fields/contour_demo.py | 12 ++++++------ .../images_contours_and_fields/contour_label_demo.py | 4 ++-- galleries/examples/misc/demo_agg_filter.py | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/contour_demo.py b/galleries/examples/images_contours_and_fields/contour_demo.py index 1d64986850f5..885a8a3c2005 100644 --- a/galleries/examples/images_contours_and_fields/contour_demo.py +++ b/galleries/examples/images_contours_and_fields/contour_demo.py @@ -30,7 +30,7 @@ fig, ax = plt.subplots() CS = ax.contour(X, Y, Z) -ax.clabel(CS, inline=True, fontsize=10) +ax.clabel(CS, fontsize=10) ax.set_title('Simplest default with labels') # %% @@ -42,7 +42,7 @@ CS = ax.contour(X, Y, Z) manual_locations = [ (-1, -1.4), (-0.62, -0.7), (-2, 0.5), (1.7, 1.2), (2.0, 1.4), (2.4, 1.7)] -ax.clabel(CS, inline=True, fontsize=10, manual=manual_locations) +ax.clabel(CS, fontsize=10, manual=manual_locations) ax.set_title('labels at selected locations') # %% @@ -50,7 +50,7 @@ fig, ax = plt.subplots() CS = ax.contour(X, Y, Z, 6, colors='k') # Negative contours default to dashed. -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Single color - negative contours dashed') # %% @@ -59,7 +59,7 @@ plt.rcParams['contour.negative_linestyle'] = 'solid' fig, ax = plt.subplots() CS = ax.contour(X, Y, Z, 6, colors='k') # Negative contours default to dashed. -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Single color - negative contours solid') # %% @@ -70,7 +70,7 @@ linewidths=np.arange(.5, 4, .5), colors=('r', 'green', 'blue', (1, 1, 0), '#afeeee', '0.5'), ) -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Crazy lines') # %% @@ -90,7 +90,7 @@ CS.set_linewidth(lws) ax.clabel(CS, levels[1::2], # label every second level - inline=True, fmt='%1.1f', fontsize=14) + fmt='%1.1f', fontsize=14) # make a colorbar for the contour lines CB = fig.colorbar(CS, shrink=0.8) diff --git a/galleries/examples/images_contours_and_fields/contour_label_demo.py b/galleries/examples/images_contours_and_fields/contour_label_demo.py index 57f29c827757..0b24b57f6afd 100644 --- a/galleries/examples/images_contours_and_fields/contour_label_demo.py +++ b/galleries/examples/images_contours_and_fields/contour_label_demo.py @@ -43,7 +43,7 @@ def fmt(x): fig, ax = plt.subplots() CS = ax.contour(X, Y, Z) -ax.clabel(CS, CS.levels, inline=True, fmt=fmt, fontsize=10) +ax.clabel(CS, CS.levels, fmt=fmt, fontsize=10) # %% # Label contours with arbitrary strings using a dictionary @@ -59,7 +59,7 @@ def fmt(x): fmt[l] = s # Label every other level using strings -ax1.clabel(CS1, CS1.levels[::2], inline=True, fmt=fmt, fontsize=10) +ax1.clabel(CS1, CS1.levels[::2], fmt=fmt, fontsize=10) # %% # Use a Formatter diff --git a/galleries/examples/misc/demo_agg_filter.py b/galleries/examples/misc/demo_agg_filter.py index 5c2ad71a6673..302250ced9f0 100644 --- a/galleries/examples/misc/demo_agg_filter.py +++ b/galleries/examples/misc/demo_agg_filter.py @@ -188,7 +188,6 @@ def filtered_text(ax): # contour label cl = ax.clabel(CS, levels[1::2], # label every second level - inline=True, fmt='%1.1f', fontsize=11) From 9b1b45bf8bb30609f7155305000b4aebbc61ec39 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:56:09 +0100 Subject: [PATCH 1110/1120] Backport PR #29036: Don't pass redundant inline=True to example clabel() calls. --- .../images_contours_and_fields/contour_demo.py | 12 ++++++------ .../images_contours_and_fields/contour_label_demo.py | 4 ++-- galleries/examples/misc/demo_agg_filter.py | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/galleries/examples/images_contours_and_fields/contour_demo.py b/galleries/examples/images_contours_and_fields/contour_demo.py index 1d64986850f5..885a8a3c2005 100644 --- a/galleries/examples/images_contours_and_fields/contour_demo.py +++ b/galleries/examples/images_contours_and_fields/contour_demo.py @@ -30,7 +30,7 @@ fig, ax = plt.subplots() CS = ax.contour(X, Y, Z) -ax.clabel(CS, inline=True, fontsize=10) +ax.clabel(CS, fontsize=10) ax.set_title('Simplest default with labels') # %% @@ -42,7 +42,7 @@ CS = ax.contour(X, Y, Z) manual_locations = [ (-1, -1.4), (-0.62, -0.7), (-2, 0.5), (1.7, 1.2), (2.0, 1.4), (2.4, 1.7)] -ax.clabel(CS, inline=True, fontsize=10, manual=manual_locations) +ax.clabel(CS, fontsize=10, manual=manual_locations) ax.set_title('labels at selected locations') # %% @@ -50,7 +50,7 @@ fig, ax = plt.subplots() CS = ax.contour(X, Y, Z, 6, colors='k') # Negative contours default to dashed. -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Single color - negative contours dashed') # %% @@ -59,7 +59,7 @@ plt.rcParams['contour.negative_linestyle'] = 'solid' fig, ax = plt.subplots() CS = ax.contour(X, Y, Z, 6, colors='k') # Negative contours default to dashed. -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Single color - negative contours solid') # %% @@ -70,7 +70,7 @@ linewidths=np.arange(.5, 4, .5), colors=('r', 'green', 'blue', (1, 1, 0), '#afeeee', '0.5'), ) -ax.clabel(CS, fontsize=9, inline=True) +ax.clabel(CS, fontsize=9) ax.set_title('Crazy lines') # %% @@ -90,7 +90,7 @@ CS.set_linewidth(lws) ax.clabel(CS, levels[1::2], # label every second level - inline=True, fmt='%1.1f', fontsize=14) + fmt='%1.1f', fontsize=14) # make a colorbar for the contour lines CB = fig.colorbar(CS, shrink=0.8) diff --git a/galleries/examples/images_contours_and_fields/contour_label_demo.py b/galleries/examples/images_contours_and_fields/contour_label_demo.py index 57f29c827757..0b24b57f6afd 100644 --- a/galleries/examples/images_contours_and_fields/contour_label_demo.py +++ b/galleries/examples/images_contours_and_fields/contour_label_demo.py @@ -43,7 +43,7 @@ def fmt(x): fig, ax = plt.subplots() CS = ax.contour(X, Y, Z) -ax.clabel(CS, CS.levels, inline=True, fmt=fmt, fontsize=10) +ax.clabel(CS, CS.levels, fmt=fmt, fontsize=10) # %% # Label contours with arbitrary strings using a dictionary @@ -59,7 +59,7 @@ def fmt(x): fmt[l] = s # Label every other level using strings -ax1.clabel(CS1, CS1.levels[::2], inline=True, fmt=fmt, fontsize=10) +ax1.clabel(CS1, CS1.levels[::2], fmt=fmt, fontsize=10) # %% # Use a Formatter diff --git a/galleries/examples/misc/demo_agg_filter.py b/galleries/examples/misc/demo_agg_filter.py index 5c2ad71a6673..302250ced9f0 100644 --- a/galleries/examples/misc/demo_agg_filter.py +++ b/galleries/examples/misc/demo_agg_filter.py @@ -188,7 +188,6 @@ def filtered_text(ax): # contour label cl = ax.clabel(CS, levels[1::2], # label every second level - inline=True, fmt='%1.1f', fontsize=11) From befcf72e1610f28e634c9b2d6479294992d0d918 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 29 Oct 2024 14:56:23 -0600 Subject: [PATCH 1111/1120] Backport PR #29035: FIX: Don't set_wmclass on GTK3 --- lib/matplotlib/backends/_backend_gtk.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_gtk.py b/lib/matplotlib/backends/_backend_gtk.py index 565d92932023..ac443730e28a 100644 --- a/lib/matplotlib/backends/_backend_gtk.py +++ b/lib/matplotlib/backends/_backend_gtk.py @@ -143,7 +143,6 @@ def __init__(self, canvas, num): super().__init__(canvas, num) if gtk_ver == 3: - self.window.set_wmclass("matplotlib", "Matplotlib") icon_ext = "png" if sys.platform == "win32" else "svg" self.window.set_icon_from_file( str(cbook._get_data_path(f"images/matplotlib.{icon_ext}"))) From 985e8f4f92c35518fd452e95c127425411e36fd8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 30 Oct 2024 16:05:32 -0400 Subject: [PATCH 1112/1120] Backport PR #28981: FIX: macos: Use standard NSApp run loop in our input hook --- src/_macosx.m | 110 +++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index fda928536ab5..1f291b52f6ba 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -40,60 +40,84 @@ static bool keyChangeCapsLock = false; /* Keep track of the current mouse up/down state for open/closed cursor hand */ static bool leftMouseGrabbing = false; -/* Keep track of whether stdin has been received */ -static bool stdin_received = false; -static bool stdin_sigint = false; // Global variable to store the original SIGINT handler static PyOS_sighandler_t originalSigintAction = NULL; -// Signal handler for SIGINT, only sets a flag to exit the run loop +// Stop the current app's run loop, sending an event to ensure it actually stops +static void stopWithEvent() { + [NSApp stop: nil]; + // Post an event to trigger the actual stopping. + [NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: 0 + data1: 0 + data2: 0] + atStart: YES]; +} + +// Signal handler for SIGINT, only argument matching for stopWithEvent static void handleSigint(int signal) { - stdin_sigint = true; + stopWithEvent(); +} + +// Helper function to flush all events. +// This is needed in some instances to ensure e.g. that windows are properly closed. +// It is used in the input hook as well as wrapped in a version callable from Python. +static void flushEvents() { + while (true) { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) { + break; + } + [NSApp sendEvent:event]; + } } static int wait_for_stdin() { - @autoreleasepool { - stdin_received = false; - stdin_sigint = false; + // Short circuit if no windows are active + // Rely on Python's input handling to manage CPU usage + // This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still + // need to be processed to properly close the windows. + if (![[NSApp windows] count]) { + flushEvents(); + return 1; + } + @autoreleasepool { // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too originalSigintAction = PyOS_setsig(SIGINT, handleSigint); // Create an NSFileHandle for standard input NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; + // Register for data available notifications on standard input - [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification - object: stdinHandle - queue: [NSOperationQueue mainQueue] // Use the main queue - usingBlock: ^(NSNotification *notification) { - // Mark that input has been received - stdin_received = true; - } + id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification + object: stdinHandle + queue: [NSOperationQueue mainQueue] // Use the main queue + usingBlock: ^(NSNotification *notification) {stopWithEvent();} ]; // Wait in the background for anything that happens to stdin [stdinHandle waitForDataInBackgroundAndNotify]; - // continuously run an event loop until the stdin_received flag is set to exit - while (!stdin_received && !stdin_sigint) { - // This loop is similar to the main event loop and flush_events which have - // Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop. - // This should not be necessary here because PyOS_InputHook releases the GIL for us. - while (true) { - NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) { break; } - [NSApp sendEvent: event]; - } - } + // Run the application's event loop, which will be interrupted on stdin or SIGINT + [NSApp run]; + // Remove the input handler as an observer - [[NSNotificationCenter defaultCenter] removeObserver: stdinHandle]; + [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; + // Restore the original SIGINT handler upon exiting the function PyOS_setsig(SIGINT, originalSigintAction); + return 1; } } @@ -236,18 +260,7 @@ static void lazy_init(void) { static PyObject* stop(PyObject* self) { - [NSApp stop: nil]; - // Post an event to trigger the actual stopping. - [NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined - location: NSZeroPoint - modifierFlags: 0 - timestamp: 0 - windowNumber: 0 - context: nil - subtype: 0 - data1: 0 - data2: 0] - atStart: YES]; + stopWithEvent(); Py_RETURN_NONE; } @@ -382,20 +395,9 @@ static CGFloat _get_device_scale(CGContextRef cr) // We run the app, matching any events that are waiting in the queue // to process, breaking out of the loop when no events remain and // displaying the canvas if needed. - NSEvent *event; - Py_BEGIN_ALLOW_THREADS - while (true) { - event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) { - break; - } - [NSApp sendEvent:event]; - } + flushEvents(); Py_END_ALLOW_THREADS From c87f8078114310ac93c548686764ef5e5aaa8861 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:02:56 +0000 Subject: [PATCH 1113/1120] Backport PR #29120: DOC: Switch nested pie example from cmaps to color_sequences --- galleries/examples/pie_and_polar_charts/nested_pie.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/galleries/examples/pie_and_polar_charts/nested_pie.py b/galleries/examples/pie_and_polar_charts/nested_pie.py index c83b4f6f84ee..61cb5e6ee429 100644 --- a/galleries/examples/pie_and_polar_charts/nested_pie.py +++ b/galleries/examples/pie_and_polar_charts/nested_pie.py @@ -31,9 +31,9 @@ size = 0.3 vals = np.array([[60., 32.], [37., 40.], [29., 10.]]) -cmap = plt.colormaps["tab20c"] -outer_colors = cmap(np.arange(3)*4) -inner_colors = cmap([1, 2, 5, 6, 9, 10]) +tab20c = plt.color_sequences["tab20c"] +outer_colors = [tab20c[i] for i in [0, 4, 8]] +inner_colors = [tab20c[i] for i in [1, 2, 5, 6, 9, 10]] ax.pie(vals.sum(axis=1), radius=1, colors=outer_colors, wedgeprops=dict(width=size, edgecolor='w')) From 8d8d1d6a7ad9ff03d86ec2d30c65002eb5a93ba4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 Nov 2024 14:46:04 -0500 Subject: [PATCH 1114/1120] Backport CI config updates to v3.9.x (#29149) This includes updates to `actions/attest-build-provenance`, `pypa/cibuildwheel`, `pypa/gh-action-pypi-publish`, `deadsnakes/action`, and cleanup of `workflow_dispatch` and outdated skips. --- .github/workflows/cibuildwheel.yml | 18 ++++++++---------- .github/workflows/cygwin.yml | 1 - .github/workflows/tests.yml | 3 +-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index a145b5bbcc08..aa0366ad16ff 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -143,13 +143,11 @@ jobs: path: dist/ - name: Build wheels for CPython 3.13 - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: CIBW_BUILD: "cp313-* cp313t-*" - # No free-threading wheels for NumPy; musllinux skipped for main builds also. - CIBW_SKIP: "cp313t-win_amd64 *-musllinux_aarch64" CIBW_BUILD_FRONTEND: "pip; args: --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" CIBW_FREE_THREADED_SUPPORT: true @@ -163,7 +161,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -171,7 +169,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -179,7 +177,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -187,7 +185,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -195,7 +193,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 + uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -231,9 +229,9 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: dist/matplotlib-* - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 58c132315b6f..a3f93ba195a8 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -29,7 +29,6 @@ on: # 5:47 UTC on Saturdays - cron: "47 5 * * 6" workflow_dispatch: - workflow: "*" permissions: contents: read diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4100fda43a36..853311366ee3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,6 @@ on: # 5:47 UTC on Saturdays - cron: "47 5 * * 6" workflow_dispatch: - workflow: "*" env: NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test. @@ -127,7 +126,7 @@ jobs: allow-prereleases: true - name: Set up Python ${{ matrix.python-version }} - uses: deadsnakes/action@6c8b9b82fe0b4344f4b98f2775fcc395df45e494 # v3.1.0 + uses: deadsnakes/action@e640ac8743173a67cca4d7d77cd837e514bf98e8 # v3.2.0 if: matrix.python-version == '3.13t' with: python-version: '3.13' From d71ff494bbf808f029cd6a3f3c0de061ea480010 Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:54:15 -0800 Subject: [PATCH 1115/1120] Backport PR #29153: Bump codecov/codecov-action from 4 to 5 in the actions group (#29165) Co-authored-by: Greg Lucas --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 853311366ee3..6d2fe41546f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -388,7 +388,7 @@ jobs: fi - name: Upload code coverage if: ${{ !cancelled() && github.event_name != 'schedule' }} - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}" token: ${{ secrets.CODECOV_TOKEN }} From c4bfd54a058f9124bfb75beccafdfd03432b3778 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 22 Nov 2024 21:50:55 -0500 Subject: [PATCH 1116/1120] Backport PR #29148: Don't fail on equal-but-differently-named cmaps in qt figureoptions. --- lib/matplotlib/backends/qt_editor/figureoptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index c36bbeb62641..3c2eaf1bacd3 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -149,7 +149,7 @@ def prepare_data(d, init): cmaps = [(cmap, name) for name, cmap in sorted(cm._colormaps.items())] for label, mappable in labeled_mappables: cmap = mappable.get_cmap() - if cmap not in cm._colormaps.values(): + if cmap.name not in cm._colormaps: cmaps = [(cmap, cmap.name), *cmaps] low, high = mappable.get_clim() mappabledata = [ From dd5777232d288970697f4effafd38946d541784c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 Nov 2024 23:48:23 -0500 Subject: [PATCH 1117/1120] Backport PR #29163: ci: Remove outdated pkg-config package on macOS --- .github/workflows/tests.yml | 2 ++ azure-pipelines.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6d2fe41546f1..6f604db8cd0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -202,6 +202,8 @@ jobs: brew unlink ${python_package} brew link --overwrite ${python_package} done + # Workaround for https://github.com/actions/runner-images/issues/10984 + brew uninstall --ignore-dependencies --force pkg-config@0.29.2 brew install ccache ghostscript gobject-introspection gtk4 ninja brew install --cask font-noto-sans-cjk inkscape ;; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e2829ce81e9f..00edc8e9a412 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -131,6 +131,8 @@ stages: brew unlink ${python_package} brew link --overwrite ${python_package} done + # Workaround for https://github.com/actions/runner-images/issues/10984 + brew uninstall --ignore-dependencies --force pkg-config@0.29.2 brew install --cask xquartz brew install ccache ffmpeg imagemagick mplayer ninja pkg-config brew install --cask font-noto-sans-cjk-sc From 562d45862d411925f98b489270ea7a206a9a3bb1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 26 Nov 2024 10:14:08 -0500 Subject: [PATCH 1118/1120] Backport PR #29191: ci: Simplify 3.13t test setup --- .github/workflows/tests.yml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f604db8cd0b..dc216c6959f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -126,11 +126,11 @@ jobs: allow-prereleases: true - name: Set up Python ${{ matrix.python-version }} - uses: deadsnakes/action@e640ac8743173a67cca4d7d77cd837e514bf98e8 # v3.2.0 + uses: Quansight-Labs/setup-python@b9ab292c751a42bcd2bb465b7fa202ea2c3f5796 # v5.3.1 if: matrix.python-version == '3.13t' with: - python-version: '3.13' - nogil: true + python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Install OS dependencies run: | @@ -177,11 +177,6 @@ jobs: texlive-luatex \ texlive-pictures \ texlive-xetex - if [[ "${{ matrix.python-version }}" = '3.13t' ]]; then - # TODO: Remove this once setup-python supports nogil distributions. - sudo apt-get install -yy --no-install-recommends \ - python3.13-tk-nogil - fi if [[ "${{ matrix.os }}" = ubuntu-20.04 ]]; then sudo apt-get install -yy --no-install-recommends libopengl0 else # ubuntu-22.04 @@ -245,15 +240,6 @@ jobs: 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl- - - name: Install the nightly dependencies - if: matrix.python-version == '3.13t' - run: | - python -m pip install pytz tzdata python-dateutil # Must be installed for Pandas. - python -m pip install \ - --pre \ - --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ - --upgrade --only-binary=:all: numpy pandas pillow contourpy - - name: Install Python dependencies run: | # Upgrade pip and setuptools and wheel to get as clean an install as From 4ca8d68b6b161b41492fb27c2431c3adc4d26051 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 30 Nov 2024 03:28:23 -0500 Subject: [PATCH 1119/1120] DOC: Create release notes for 3.9.3 --- doc/users/github_stats.rst | 156 ++++++++++-------- .../prev_whats_new/github_stats_3.9.2.rst | 96 +++++++++++ doc/users/release_notes.rst | 1 + 3 files changed, 181 insertions(+), 72 deletions(-) create mode 100644 doc/users/prev_whats_new/github_stats_3.9.2.rst diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index d357a6759d30..bec073081a68 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,99 +1,111 @@ .. _github-stats: -GitHub statistics for 3.9.2 (Aug 12, 2024) +GitHub statistics for 3.9.3 (Nov 30, 2024) ========================================== -GitHub statistics for 2024/07/04 (tag: v3.9.1) - 2024/08/12 +GitHub statistics for 2024/08/12 (tag: v3.9.2) - 2024/11/30 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 9 issues and merged 45 pull requests. -The full list can be seen `on GitHub `__ +We closed 6 issues and merged 62 pull requests. +The full list can be seen `on GitHub `__ -The following 20 authors contributed 67 commits. +The following 18 authors contributed 90 commits. -* Adam J. Stewart -* Anthony Lee -* Caitlin Hathaway -* ClarkeAC +* Andresporcruz +* Antony Lee +* Charlie LeWarne * dependabot[bot] * Elliott Sales de Andrade -* Filippo Balzaretti +* Gavin S * Greg Lucas * hannah -* Ian Thomas -* Jody Klymak * Kyle Sunden +* Kyra Cho +* kyracho +* Lumberbot (aka Jack) +* Michael Hinton * Oscar Gustafsson -* Randolf Scholz -* Refael Ackermann * Ruth Comer -* Scott Shambaugh -* Sean Smith * Thomas A Caswell * Tim Hoffmann +* vittoboa GitHub issues and pull requests: -Pull Requests (45): +Pull Requests (62): -* :ghpull:`28687`: BLD: Include MSVCP140 runtime statically -* :ghpull:`28679`: Run delvewheel with path to required msvcp140.dll -* :ghpull:`28695`: Backport PR #27797 on branch v3.9.x (DOC: Use video files for saving animations) -* :ghpull:`28688`: Backport PR #28293 and #28668: Enable 3.13 wheels and bump cibuildwheel -* :ghpull:`27797`: DOC: Use video files for saving animations -* :ghpull:`28692`: Backport PR #28632 on branch v3.9.x (DOC: Tell sphinx-gallery to link mpl_toolkits from our build) -* :ghpull:`28632`: DOC: Tell sphinx-gallery to link mpl_toolkits from our build -* :ghpull:`28668`: Bump the actions group with 2 updates -* :ghpull:`28686`: Backport PR #28682 on branch v3.9.x (Fix warnings from mingw compilers) -* :ghpull:`28682`: Fix warnings from mingw compilers -* :ghpull:`28676`: Backport PR #28577 on branch v3.9.x (Copy all internals from initial Tick to lazy ones) -* :ghpull:`28577`: Copy all internals from initial Tick to lazy ones -* :ghpull:`28674`: Backport PR #28650 on branch v3.9.x (remove out of date todos on animation.py) -* :ghpull:`28650`: remove out of date todos on animation.py -* :ghpull:`28656`: Backport PR #28649 on branch v3.9.x (FIX: improve formatting of image values in cases of singular norms) -* :ghpull:`28665`: Backport PR #28546 on branch v3.9.x (DOC: Clarify/simplify example of multiple images with one colorbar) -* :ghpull:`28649`: FIX: improve formatting of image values in cases of singular norms -* :ghpull:`28635`: BLD: windows wheels -* :ghpull:`28645`: Backport PR #28644 on branch v3.9.x (DOC: Fix matching for version switcher) -* :ghpull:`28640`: Backport PR #28634 on branch v3.9.x (Closed open div tag in color.ColorMap._repr_html_) -* :ghpull:`28634`: Closed open div tag in color.ColorMap._repr_html_ -* :ghpull:`28636`: Backport PR #28625 on branch v3.9.x (added typing_extensions.Self to _AxesBase.twinx) -* :ghpull:`28625`: added typing_extensions.Self to _AxesBase.twinx -* :ghpull:`28622`: Backport PR #28621 on branch v3.9.x (TYP: Fix a typo in animation.pyi) -* :ghpull:`28621`: TYP: Fix a typo in animation.pyi -* :ghpull:`28605`: Backport PR #28604 on branch v3.9.x (cycler signature update.) -* :ghpull:`28604`: cycler signature update. -* :ghpull:`28598`: Pin PyQt6 back on Ubuntu 20.04 -* :ghpull:`28596`: Backport PR #28518 on branch v3.9.x ([TYP] Fix overload of ``pyplot.subplots``) -* :ghpull:`28518`: [TYP] Fix overload of ``pyplot.subplots`` -* :ghpull:`28591`: Backport PR #28580 on branch v3.9.x (Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group) -* :ghpull:`28580`: Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group -* :ghpull:`28586`: Backport PR #28582 on branch v3.9.x (FIX: make sticky edge tolerance relative to data range) -* :ghpull:`28582`: FIX: make sticky edge tolerance relative to data range -* :ghpull:`28572`: Backport PR #28571 on branch v3.9.x (DOC: Add version directive to hatch parameter in stackplot) -* :ghpull:`28571`: DOC: Add version directive to hatch parameter in stackplot -* :ghpull:`28564`: Backport PR #28534 on branch v3.9.x ([BLD] Fix WSL build warning) -* :ghpull:`28563`: Backport PR #28526 on branch v3.9.x (Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group) -* :ghpull:`28534`: [BLD] Fix WSL build warning -* :ghpull:`28526`: Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group -* :ghpull:`28552`: Backport PR #28541 on branch v3.9.x (MNT: be more careful about disk I/O failures when writing font cache) -* :ghpull:`28541`: MNT: be more careful about disk I/O failures when writing font cache -* :ghpull:`28524`: Backport PR #28523 on branch v3.9.x (Fix value error when set widget size to zero while using FigureCanvasQT ) -* :ghpull:`28523`: Fix value error when set widget size to zero while using FigureCanvasQT -* :ghpull:`28519`: Backport PR #28517 on branch v3.9.x (DOC: better cross referencing for animations) +* :ghpull:`29195`: Backport PR #29191 on branch v3.9.x (ci: Simplify 3.13t test setup) +* :ghpull:`29191`: ci: Simplify 3.13t test setup +* :ghpull:`29176`: Backport PR #29148 on branch v3.9.x (Don't fail on equal-but-differently-named cmaps in qt figureoptions.) +* :ghpull:`29148`: Don't fail on equal-but-differently-named cmaps in qt figureoptions. +* :ghpull:`29165`: Backport PR #29153 on branch v3.9.x (Bump codecov/codecov-action from 4 to 5 in the actions group) +* :ghpull:`29153`: Bump codecov/codecov-action from 4 to 5 in the actions group +* :ghpull:`29149`: Backport CI config updates to v3.9.x +* :ghpull:`29121`: Backport PR #29120 on branch v3.9.x (DOC: Switch nested pie example from cmaps to color_sequences) +* :ghpull:`29071`: Bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0 in the actions group +* :ghpull:`29046`: Backport PR #28981 on branch v3.9.x (FIX: macos: Use standard NSApp run loop in our input hook) +* :ghpull:`28981`: FIX: macos: Use standard NSApp run loop in our input hook +* :ghpull:`29041`: Backport PR #29035 on branch v3.9.x (FIX: Don't set_wmclass on GTK3) +* :ghpull:`29035`: FIX: Don't set_wmclass on GTK3 +* :ghpull:`29037`: Backport PR #29036 on branch v3.9.x (Don't pass redundant inline=True to example clabel() calls.) +* :ghpull:`29032`: Backport PR #27569 on branch v3.9.x (DOC: initial tags for statistics section of gallery) +* :ghpull:`29034`: Backport PR #29031 on branch v3.9.x (DOC: Fix copy-paste typo in ColorSequenceRegistry) +* :ghpull:`29031`: DOC: Fix copy-paste typo in ColorSequenceRegistry +* :ghpull:`29015`: Backport PR #29014 on branch v3.9.x (FIX: fake out setuptools scm in tox on ci) +* :ghpull:`29014`: FIX: fake out setuptools scm in tox on ci +* :ghpull:`29010`: Backport PR #29005 on branch v3.9.x (DOC: Update meson-python intersphinx link) +* :ghpull:`29006`: Backport PR #28993 on branch v3.9.x (FIX: contourf hatches use multiple edgecolors) +* :ghpull:`28993`: FIX: contourf hatches use multiple edgecolors +* :ghpull:`28988`: Backport PR #28987 on branch v3.9.x (Fix: Do not use numeric tolerances for axline special cases) +* :ghpull:`28947`: Backport PR #28925 on branch v3.9.x (TST: handle change in pytest.importorskip behavior) +* :ghpull:`28989`: Backport PR #28972 on branch v3.9.x (Switch macOS 12 runner images to macOS 13) +* :ghpull:`28972`: Switch macOS 12 runner images to macOS 13 +* :ghpull:`28987`: Fix: Do not use numeric tolerances for axline special cases +* :ghpull:`28954`: Backport PR #28952 on branch v3.9.x (BLD: update trove metadata to support py3.13) +* :ghpull:`28952`: BLD: update trove metadata to support py3.13 +* :ghpull:`28887`: Backport PR #28883 on branch v3.9.x (Only check X11 when running Tkinter tests) +* :ghpull:`28926`: Backport PR #28689 on branch v3.9.x (ci: Enable testing on Python 3.13) +* :ghpull:`28925`: TST: handle change in pytest.importorskip behavior +* :ghpull:`28945`: Backport PR #28943 on branch v3.9.x (DOC: Clarify the returned line of axhline()/axvline()) +* :ghpull:`28939`: Backport PR #28900 on branch v3.9.x (DOC: Improve fancybox demo) +* :ghpull:`28900`: DOC: Improve fancybox demo +* :ghpull:`28902`: Backport PR #28881 on branch v3.9.x (Fix ``axline`` for slopes <= 1E-8. Closes #28386) +* :ghpull:`28431`: Fix ``axline`` for slopes < 1E-8 +* :ghpull:`28881`: Fix ``axline`` for slopes <= 1E-8. Closes #28386 +* :ghpull:`28883`: Only check X11 when running Tkinter tests +* :ghpull:`28859`: Backport PR #28858 on branch v3.9.x (Fix flaky labelcolor tests) +* :ghpull:`28858`: Fix flaky labelcolor tests +* :ghpull:`28839`: Backport PR #28836 on branch v3.9.x (MNT: Use __init__ parameters of font properties) +* :ghpull:`28836`: MNT: Use __init__ parameters of font properties +* :ghpull:`28828`: Backport PR #28818 on branch v3.9.x (Resolve configdir so that it's not a symlink when is_dir() is called) +* :ghpull:`28818`: Resolve configdir so that it's not a symlink when is_dir() is called +* :ghpull:`28811`: Backport PR #28810 on branch v3.9.x (Document how to obtain sans-serif usetex math.) +* :ghpull:`28806`: Backport PR #28805 on branch v3.9.x (add brackets to satisfy the new sequence requirement) +* :ghpull:`28802`: Backport PR #28798 on branch v3.9.x (DOC: Correctly list modules that have been internalized) +* :ghpull:`28791`: Backport PR #28790 on branch v3.9.x (DOC: Fix duplicate Figure.set_dpi entry) +* :ghpull:`28787`: Backport PR #28706 on branch v3.9.x (Add Returns info to to_jshtml docstring) +* :ghpull:`28706`: Add Returns info to to_jshtml docstring +* :ghpull:`28751`: Backport PR #28271 on branch v3.9.x (Fix draggable legend disappearing when picking while use_blit=True) +* :ghpull:`28271`: Fix draggable legend disappearing when picking while use_blit=True +* :ghpull:`28747`: Backport PR #28743 on branch v3.9.x (Minor fixes in ticker docs) +* :ghpull:`28743`: Minor fixes in ticker docs +* :ghpull:`28738`: Backport PR #28737 on branch v3.9.x (TST: Fix image comparison directory for test_striped_lines) +* :ghpull:`28740`: Backport PR #28739 on branch v3.9.x (Tweak interactivity docs wording (and fix capitalization).) +* :ghpull:`28737`: TST: Fix image comparison directory for test_striped_lines +* :ghpull:`28733`: Backport PR #28732 on branch v3.9.x (Renames the minumumSizeHint method to minimumSizeHint) +* :ghpull:`28732`: Renames the minumumSizeHint method to minimumSizeHint +* :ghpull:`28689`: ci: Enable testing on Python 3.13 +* :ghpull:`28724`: Backport fixes from #28711 -Issues (9): +Issues (6): -* :ghissue:`28551`: [Bug]: Possible issue with Matplotlib 3.9.1 wheel on Windows only -* :ghissue:`28250`: [Doc]: Sphinx gallery links mispointed for Axes3D methods -* :ghissue:`28574`: [Bug]: Nondeterministic behavior with subplot spacing and constrained layout -* :ghissue:`28626`: [Doc]: Remove old TODO's from animation.py -* :ghissue:`28648`: [Bug]: format_image_data on an image of only zeros produces a large number of zeros -* :ghissue:`28624`: [Bug]: Bad type hint in ``_AxesBase.twinx()`` -* :ghissue:`28567`: [Bug]: sticky edge related changes for datetime plots -* :ghissue:`28533`: [Doc]: Stackplot hatch functionality has version dependencies -* :ghissue:`28538`: [Bug]: Permission denied when importing matplotlib.pyplot +* :ghissue:`28960`: [Bug]: High CPU utilization of the macosx backend +* :ghissue:`28990`: [Bug]: no longer able to set multiple hatch colors +* :ghissue:`28870`: [Bug]: axline doesn't work with some axes scales +* :ghissue:`28386`: [Bug]: Minor issue - Drawing an axline sets slopes less than 1E-8 to 0 +* :ghissue:`28817`: [Bug]: ``~/.config/matplotlib`` is never used because ``~/.config`` is a symlink +* :ghissue:`28716`: Size hint method in Qt backend should be named ``minimumSizeHint``, not ``minumumSizeHint`` Previous GitHub statistics diff --git a/doc/users/prev_whats_new/github_stats_3.9.2.rst b/doc/users/prev_whats_new/github_stats_3.9.2.rst new file mode 100644 index 000000000000..542e0d81ce32 --- /dev/null +++ b/doc/users/prev_whats_new/github_stats_3.9.2.rst @@ -0,0 +1,96 @@ +.. _github-stats-3-9-2: + +GitHub statistics for 3.9.2 (Aug 12, 2024) +========================================== + +GitHub statistics for 2024/07/04 (tag: v3.9.1) - 2024/08/12 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 9 issues and merged 45 pull requests. +The full list can be seen `on GitHub `__ + +The following 20 authors contributed 67 commits. + +* Adam J. Stewart +* Anthony Lee +* Caitlin Hathaway +* ClarkeAC +* dependabot[bot] +* Elliott Sales de Andrade +* Filippo Balzaretti +* Greg Lucas +* hannah +* Ian Thomas +* Jody Klymak +* Kyle Sunden +* Oscar Gustafsson +* Randolf Scholz +* Refael Ackermann +* Ruth Comer +* Scott Shambaugh +* Sean Smith +* Thomas A Caswell +* Tim Hoffmann + +GitHub issues and pull requests: + +Pull Requests (45): + +* :ghpull:`28687`: BLD: Include MSVCP140 runtime statically +* :ghpull:`28679`: Run delvewheel with path to required msvcp140.dll +* :ghpull:`28695`: Backport PR #27797 on branch v3.9.x (DOC: Use video files for saving animations) +* :ghpull:`28688`: Backport PR #28293 and #28668: Enable 3.13 wheels and bump cibuildwheel +* :ghpull:`27797`: DOC: Use video files for saving animations +* :ghpull:`28692`: Backport PR #28632 on branch v3.9.x (DOC: Tell sphinx-gallery to link mpl_toolkits from our build) +* :ghpull:`28632`: DOC: Tell sphinx-gallery to link mpl_toolkits from our build +* :ghpull:`28668`: Bump the actions group with 2 updates +* :ghpull:`28686`: Backport PR #28682 on branch v3.9.x (Fix warnings from mingw compilers) +* :ghpull:`28682`: Fix warnings from mingw compilers +* :ghpull:`28676`: Backport PR #28577 on branch v3.9.x (Copy all internals from initial Tick to lazy ones) +* :ghpull:`28577`: Copy all internals from initial Tick to lazy ones +* :ghpull:`28674`: Backport PR #28650 on branch v3.9.x (remove out of date todos on animation.py) +* :ghpull:`28650`: remove out of date todos on animation.py +* :ghpull:`28656`: Backport PR #28649 on branch v3.9.x (FIX: improve formatting of image values in cases of singular norms) +* :ghpull:`28665`: Backport PR #28546 on branch v3.9.x (DOC: Clarify/simplify example of multiple images with one colorbar) +* :ghpull:`28649`: FIX: improve formatting of image values in cases of singular norms +* :ghpull:`28635`: BLD: windows wheels +* :ghpull:`28645`: Backport PR #28644 on branch v3.9.x (DOC: Fix matching for version switcher) +* :ghpull:`28640`: Backport PR #28634 on branch v3.9.x (Closed open div tag in color.ColorMap._repr_html_) +* :ghpull:`28634`: Closed open div tag in color.ColorMap._repr_html_ +* :ghpull:`28636`: Backport PR #28625 on branch v3.9.x (added typing_extensions.Self to _AxesBase.twinx) +* :ghpull:`28625`: added typing_extensions.Self to _AxesBase.twinx +* :ghpull:`28622`: Backport PR #28621 on branch v3.9.x (TYP: Fix a typo in animation.pyi) +* :ghpull:`28621`: TYP: Fix a typo in animation.pyi +* :ghpull:`28605`: Backport PR #28604 on branch v3.9.x (cycler signature update.) +* :ghpull:`28604`: cycler signature update. +* :ghpull:`28598`: Pin PyQt6 back on Ubuntu 20.04 +* :ghpull:`28596`: Backport PR #28518 on branch v3.9.x ([TYP] Fix overload of ``pyplot.subplots``) +* :ghpull:`28518`: [TYP] Fix overload of ``pyplot.subplots`` +* :ghpull:`28591`: Backport PR #28580 on branch v3.9.x (Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group) +* :ghpull:`28580`: Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 in the actions group +* :ghpull:`28586`: Backport PR #28582 on branch v3.9.x (FIX: make sticky edge tolerance relative to data range) +* :ghpull:`28582`: FIX: make sticky edge tolerance relative to data range +* :ghpull:`28572`: Backport PR #28571 on branch v3.9.x (DOC: Add version directive to hatch parameter in stackplot) +* :ghpull:`28571`: DOC: Add version directive to hatch parameter in stackplot +* :ghpull:`28564`: Backport PR #28534 on branch v3.9.x ([BLD] Fix WSL build warning) +* :ghpull:`28563`: Backport PR #28526 on branch v3.9.x (Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group) +* :ghpull:`28534`: [BLD] Fix WSL build warning +* :ghpull:`28526`: Bump pypa/cibuildwheel from 2.19.1 to 2.19.2 in the actions group +* :ghpull:`28552`: Backport PR #28541 on branch v3.9.x (MNT: be more careful about disk I/O failures when writing font cache) +* :ghpull:`28541`: MNT: be more careful about disk I/O failures when writing font cache +* :ghpull:`28524`: Backport PR #28523 on branch v3.9.x (Fix value error when set widget size to zero while using FigureCanvasQT ) +* :ghpull:`28523`: Fix value error when set widget size to zero while using FigureCanvasQT +* :ghpull:`28519`: Backport PR #28517 on branch v3.9.x (DOC: better cross referencing for animations) + +Issues (9): + +* :ghissue:`28551`: [Bug]: Possible issue with Matplotlib 3.9.1 wheel on Windows only +* :ghissue:`28250`: [Doc]: Sphinx gallery links mispointed for Axes3D methods +* :ghissue:`28574`: [Bug]: Nondeterministic behavior with subplot spacing and constrained layout +* :ghissue:`28626`: [Doc]: Remove old TODO's from animation.py +* :ghissue:`28648`: [Bug]: format_image_data on an image of only zeros produces a large number of zeros +* :ghissue:`28624`: [Bug]: Bad type hint in ``_AxesBase.twinx()`` +* :ghissue:`28567`: [Bug]: sticky edge related changes for datetime plots +* :ghissue:`28533`: [Doc]: Stackplot hatch functionality has version dependencies +* :ghissue:`28538`: [Bug]: Permission denied when importing matplotlib.pyplot diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 74bc0f13bf1f..010f9b7534bc 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -23,6 +23,7 @@ Version 3.9 ../api/prev_api_changes/api_changes_3.9.1.rst ../api/prev_api_changes/api_changes_3.9.0.rst github_stats.rst + prev_whats_new/github_stats_3.9.2.rst prev_whats_new/github_stats_3.9.1.rst prev_whats_new/github_stats_3.9.0.rst From 3ac0aea8be540028cb2510dabb43b290d15b503d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 30 Nov 2024 03:33:58 -0500 Subject: [PATCH 1120/1120] REL: 3.9.3 This is the third bugfix release of the 3.9.x series. This release contains several bug-fixes and adjustments: - Fix `axline` with extremely small slopes - Fix `axline` with non-linear axis scales - Fix `minimumSizeHint` with Qt backend - Fix config directory usage when it's behind a symlink - Fix draggable legend when blitting is enabled - Fix high CPU utilization in the `macosx` backend - Fix multiple hatch `edgecolors` passed to `contourf` - Improve compatibility with `pytest` 8.2.0