From a104a01411269ef914e7f079c5d441715212e537 Mon Sep 17 00:00:00 2001 From: Vishnu V K Date: Wed, 21 Jul 2021 22:23:26 +0530 Subject: [PATCH 001/130] Indicate a possible 'lmodern' requirement --- lib/matplotlib/backends/backend_pgf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 3f1cb7b172eb..7190aba86de2 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -777,6 +777,10 @@ def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None): %% Make sure the required packages are loaded in your preamble %% \\usepackage{pgf} %% +%% Also ensure that all the required font packages are loaded; for instance, +%% the lmodern package is often necessary when using math font. +%% \\usepackage{lmodern} +%% %% Figures using additional raster images can only be included by \\input if %% they are in the same directory as the main LaTeX file. For loading figures %% from other directories you can use the `import` package From a8041fa29fe17257181c733bd551289cac14757f Mon Sep 17 00:00:00 2001 From: Vishnu V K Date: Wed, 21 Jul 2021 22:29:17 +0530 Subject: [PATCH 002/130] Describe the font issue --- tutorials/text/pgf.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tutorials/text/pgf.py b/tutorials/text/pgf.py index 6f958efe03fc..30410d35fe52 100644 --- a/tutorials/text/pgf.py +++ b/tutorials/text/pgf.py @@ -187,6 +187,11 @@ using either the ``rasterized=True`` keyword, or ``.set_rasterized(True)`` as per :doc:`this example `. +* Various math font are compiled and rendered only if corresponding font + packages are loaded. The ``lmodern`` package is often required when using + math symbols. See `discussion ` + for more details. + * If you still need help, please see :ref:`reporting-problems` .. _LaTeX: http://www.tug.org From 77fb127776c6b40e121b7adb3153557d4d8c920b Mon Sep 17 00:00:00 2001 From: Vishnu V K Date: Thu, 22 Jul 2021 15:41:24 +0530 Subject: [PATCH 003/130] Specify that the issue is with bold-face Greek letters --- tutorials/text/pgf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorials/text/pgf.py b/tutorials/text/pgf.py index 30410d35fe52..cc7584ffba61 100644 --- a/tutorials/text/pgf.py +++ b/tutorials/text/pgf.py @@ -188,9 +188,9 @@ per :doc:`this example `. * Various math font are compiled and rendered only if corresponding font - packages are loaded. The ``lmodern`` package is often required when using - math symbols. See `discussion ` - for more details. + packages are loaded. Specifically, when using ``\mathbf{}`` on Greek letters, + the default computer modern font may not contain them, in which case such font + is not rendered. Is such scenarios, the ``lmodern`` package may be loaded. * If you still need help, please see :ref:`reporting-problems` From 3d443e56182f2e84f613a6d4914837a280260829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Tue, 13 Jul 2021 20:22:58 +0300 Subject: [PATCH 004/130] Improve the Type-1 font parsing Move Type1Font._tokens into a top-level function _tokenize that is a coroutine. The parsing stage consuming the tokens can instruct the tokenizer to return a binary token - this is necessary when decrypting the CharStrings and Subrs arrays, since the preceding context determines which parts of the data need to be decrypted. The function now also parses the encrypted portion of the font file. To support usage as a coroutine, move the whitespace filtering into the function, since passing the information about binary tokens would not easily work through a filter. The function now returns tokens as subclasses of a new _Token class, which carry the position and value of the token and can have token-specific helper methods. The position data will be needed when modifying the file, as the font is transformed or subsetted. A new helper function _expression can be used to consume tokens that form a balanced subexpression delimited by [] or {}. This helps fix a bug in UniqueID removal: if the font includes PostScript code that checks if the UniqueID is set in the current dictionary, the previous code broke that code instead of removing the UniqueID definition. Fonts can include UniqueID in the encrypted portion as well as the cleartext one, and removal is now done in both portions. Fix a bug related to font weight: the key is title-cased and not lower-cased, so font.prop['weight'] should not exist. --- LICENSE/LICENSE_COURIERTEN | 18 + .../next_api_changes/behavior/20715-JKS.rst | 8 + .../tests/Courier10PitchBT-Bold.pfb | Bin 0 -> 38080 bytes lib/matplotlib/tests/test_type1font.py | 99 ++- lib/matplotlib/type1font.py | 800 ++++++++++++++---- 5 files changed, 744 insertions(+), 181 deletions(-) create mode 100644 LICENSE/LICENSE_COURIERTEN create mode 100644 doc/api/next_api_changes/behavior/20715-JKS.rst create mode 100644 lib/matplotlib/tests/Courier10PitchBT-Bold.pfb diff --git a/LICENSE/LICENSE_COURIERTEN b/LICENSE/LICENSE_COURIERTEN new file mode 100644 index 000000000000..c6d3fd7410a2 --- /dev/null +++ b/LICENSE/LICENSE_COURIERTEN @@ -0,0 +1,18 @@ +The Courier10PitchBT-Bold.pfb file is a Type-1 version of +Courier 10 Pitch BT Bold by Bitstream, obtained from +. It is included +here as test data only, but the following license applies. + + +(c) Copyright 1989-1992, Bitstream Inc., Cambridge, MA. + +You are hereby granted permission under all Bitstream propriety rights +to use, copy, modify, sublicense, sell, and redistribute the 4 Bitstream +Charter (r) Type 1 outline fonts and the 4 Courier Type 1 outline fonts +for any purpose and without restriction; provided, that this notice is +left intact on all copies of such fonts and that Bitstream's trademark +is acknowledged as shown below on all unmodified copies of the 4 Charter +Type 1 fonts. + +BITSTREAM CHARTER is a registered trademark of Bitstream Inc. + diff --git a/doc/api/next_api_changes/behavior/20715-JKS.rst b/doc/api/next_api_changes/behavior/20715-JKS.rst new file mode 100644 index 000000000000..f0ca1d707d3d --- /dev/null +++ b/doc/api/next_api_changes/behavior/20715-JKS.rst @@ -0,0 +1,8 @@ +``Type1Font`` objects include more properties +--------------------------------------------- + +The `.type1font.Type1Font.prop` dictionary now includes more keys, such +as ``CharStrings`` and ``Subrs``. The value of the ``Encoding`` key is +now a dictionary mapping codes to glyph names. The +`.type1font.Type1Font.transform` method now correctly removes +``UniqueID`` properties from the font. diff --git a/lib/matplotlib/tests/Courier10PitchBT-Bold.pfb b/lib/matplotlib/tests/Courier10PitchBT-Bold.pfb new file mode 100644 index 0000000000000000000000000000000000000000..88d9af2af701fb5304b37d87fdb6ca7efe5e8277 GIT binary patch literal 38080 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 RwS|Y5tFwiNBTg{-e*j{lGr<4= literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_type1font.py b/lib/matplotlib/tests/test_type1font.py index 99cc3e500b0e..39279c229cca 100644 --- a/lib/matplotlib/tests/test_type1font.py +++ b/lib/matplotlib/tests/test_type1font.py @@ -1,6 +1,7 @@ import matplotlib.type1font as t1f import os.path import difflib +import pytest def test_Type1Font(): @@ -13,10 +14,34 @@ def test_Type1Font(): assert font.parts[0] == rawdata[0x0006:0x10c5] assert font.parts[1] == rawdata[0x10cb:0x897f] assert font.parts[2] == rawdata[0x8985:0x8ba6] - assert font.parts[1:] == slanted.parts[1:] - assert font.parts[1:] == condensed.parts[1:] assert font.decrypted.startswith(b'dup\n/Private 18 dict dup begin') assert font.decrypted.endswith(b'mark currentfile closefile\n') + assert slanted.decrypted.startswith(b'dup\n/Private 18 dict dup begin') + assert slanted.decrypted.endswith(b'mark currentfile closefile\n') + assert b'UniqueID 5000793' in font.parts[0] + assert b'UniqueID 5000793' in font.decrypted + assert font._pos['UniqueID'] == [(797, 818), (4483, 4504)] + + len0 = len(font.parts[0]) + for key in font._pos.keys(): + for pos0, pos1 in font._pos[key]: + if pos0 < len0: + data = font.parts[0][pos0:pos1] + else: + data = font.decrypted[pos0-len0:pos1-len0] + assert data.startswith(f'/{key}'.encode('ascii')) + assert {'FontType', 'FontMatrix', 'PaintType', 'ItalicAngle', 'RD' + } < set(font._pos.keys()) + + assert b'UniqueID 5000793' not in slanted.parts[0] + assert b'UniqueID 5000793' not in slanted.decrypted + assert 'UniqueID' not in slanted._pos + assert font.prop['Weight'] == 'Medium' + assert not font.prop['isFixedPitch'] + assert font.prop['ItalicAngle'] == 0 + assert slanted.prop['ItalicAngle'] == -45 + assert font.prop['Encoding'][5] == 'Pi' + assert isinstance(font.prop['CharStrings']['Pi'], bytes) differ = difflib.Differ() diff = list(differ.compare( @@ -24,14 +49,13 @@ def test_Type1Font(): slanted.parts[0].decode('latin-1').splitlines())) for line in ( # Removes UniqueID - '- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup', - '+ FontDirectory/CMR10 known{/CMR10 findfont dup', + '- /UniqueID 5000793 def', # Changes the font name '- /FontName /CMR10 def', - '+ /FontName /CMR10_Slant_1000 def', + '+ /FontName/CMR10_Slant_1000 def', # Alters FontMatrix '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def', - '+ /FontMatrix [0.001 0 0.001 0.001 0 0]readonly def', + '+ /FontMatrix [0.001 0 0.001 0.001 0 0] readonly def', # Alters ItalicAngle '- /ItalicAngle 0 def', '+ /ItalicAngle -45.0 def'): @@ -42,17 +66,72 @@ def test_Type1Font(): condensed.parts[0].decode('latin-1').splitlines())) for line in ( # Removes UniqueID - '- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup', - '+ FontDirectory/CMR10 known{/CMR10 findfont dup', + '- /UniqueID 5000793 def', # Changes the font name '- /FontName /CMR10 def', - '+ /FontName /CMR10_Extend_500 def', + '+ /FontName/CMR10_Extend_500 def', # Alters FontMatrix '- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def', - '+ /FontMatrix [0.0005 0 0 0.001 0 0]readonly def'): + '+ /FontMatrix [0.0005 0 0 0.001 0 0] readonly def'): assert line in diff, 'diff to condensed font must contain %s' % line +def test_Type1Font_2(): + filename = os.path.join(os.path.dirname(__file__), + 'Courier10PitchBT-Bold.pfb') + font = t1f.Type1Font(filename) + assert font.prop['Weight'] == 'Bold' + assert font.prop['isFixedPitch'] + assert font.prop['Encoding'][65] == 'A' # the font uses StandardEncoding + (pos0, pos1), = font._pos['Encoding'] + assert font.parts[0][pos0:pos1] == b'/Encoding StandardEncoding' + + +def test_tokenize(): + data = (b'1234/abc false -9.81 Foo <<[0 1 2]<0 1ef a\t>>>\n' + b'(string with(nested\t\\) par)ens\\\\)') + # 1 2 x 2 xx1 + # 1 and 2 are matching parens, x means escaped character + n, w, num, kw, d = 'name', 'whitespace', 'number', 'keyword', 'delimiter' + b, s = 'boolean', 'string' + correct = [ + (num, 1234), (n, 'abc'), (w, ' '), (b, False), (w, ' '), (num, -9.81), + (w, ' '), (kw, 'Foo'), (w, ' '), (d, '<<'), (d, '['), (num, 0), + (w, ' '), (num, 1), (w, ' '), (num, 2), (d, ']'), (s, b'\x01\xef\xa0'), + (d, '>>'), (w, '\n'), (s, 'string with(nested\t) par)ens\\') + ] + correct_no_ws = [x for x in correct if x[0] != w] + + def convert(tokens): + return [(t.kind, t.value()) for t in tokens] + + assert convert(t1f._tokenize(data, False)) == correct + assert convert(t1f._tokenize(data, True)) == correct_no_ws + + def bin_after(n): + tokens = t1f._tokenize(data, True) + result = [] + for _ in range(n): + result.append(next(tokens)) + result.append(tokens.send(10)) + return convert(result) + + for n in range(1, len(correct_no_ws)): + result = bin_after(n) + assert result[:-1] == correct_no_ws[:n] + assert result[-1][0] == 'binary' + assert isinstance(result[-1][1], bytes) + + +def test_tokenize_errors(): + with pytest.raises(ValueError): + list(t1f._tokenize(b'1234 (this (string) is unterminated\\)', True)) + with pytest.raises(ValueError): + list(t1f._tokenize(b'/Foo<01234', True)) + with pytest.raises(ValueError): + list(t1f._tokenize(b'/Foo<01234abcg>/Bar', True)) + + def test_overprecision(): # We used to output too many digits in FontMatrix entries and # ItalicAngle, which could make Type-1 parsers unhappy. diff --git a/lib/matplotlib/type1font.py b/lib/matplotlib/type1font.py index f417c0fc97a4..871f13642ee3 100644 --- a/lib/matplotlib/type1font.py +++ b/lib/matplotlib/type1font.py @@ -22,10 +22,10 @@ """ import binascii -import enum -import itertools +import functools import logging import re +import string import struct import numpy as np @@ -35,9 +35,292 @@ _log = logging.getLogger(__name__) -# token types -_TokenType = enum.Enum('_TokenType', - 'whitespace name string delimiter number') + +class _Token: + """ + A token in a PostScript stream + + Attributes + ---------- + pos : int + position, i.e. offset from the beginning of the data + + raw : str + the raw text of the token + + kind : str + description of the token (for debugging or testing) + """ + __slots__ = ('pos', 'raw') + kind = '?' + + def __init__(self, pos, raw): + _log.debug('type1font._Token %s at %d: %r', self.kind, pos, raw) + self.pos = pos + self.raw = raw + + def __str__(self): + return f"<{self.kind} {self.raw} @{self.pos}>" + + def endpos(self): + """Position one past the end of the token""" + return self.pos + len(self.raw) + + def is_keyword(self, *names): + """Is this a name token with one of the names?""" + return False + + def is_slash_name(self): + """Is this a name token that starts with a slash?""" + return False + + def is_delim(self): + """Is this a delimiter token?""" + return False + + def is_number(self): + """Is this a number token?""" + return False + + def value(self): + return self.raw + + +class _NameToken(_Token): + kind = 'name' + + def is_slash_name(self): + return self.raw.startswith('/') + + def value(self): + return self.raw[1:] + + +class _BooleanToken(_Token): + kind = 'boolean' + + def value(self): + return self.raw == 'true' + + +class _KeywordToken(_Token): + kind = 'keyword' + + def is_keyword(self, *names): + return self.raw in names + + +class _DelimiterToken(_Token): + kind = 'delimiter' + + def is_delim(self): + return True + + def opposite(self): + return {'[': ']', ']': '[', + '{': '}', '}': '{', + '<<': '>>', '>>': '<<' + }[self.raw] + + +class _WhitespaceToken(_Token): + kind = 'whitespace' + + +class _StringToken(_Token): + kind = 'string' + _escapes_re = re.compile(r'\\([\\()nrtbf]|[0-7]{1,3})') + _replacements = {'\\': '\\', '(': '(', ')': ')', 'n': '\n', + 'r': '\r', 't': '\t', 'b': '\b', 'f': '\f'} + _ws_re = re.compile('[\0\t\r\f\n ]') + + @classmethod + def _escape(cls, match): + group = match.group(1) + try: + return cls._replacements[group] + except KeyError: + return chr(int(group, 8)) + + @functools.lru_cache() + def value(self): + if self.raw[0] == '(': + return self._escapes_re.sub(self._escape, self.raw[1:-1]) + else: + data = self._ws_re.sub('', self.raw[1:-1]) + if len(data) % 2 == 1: + data += '0' + return binascii.unhexlify(data) + + +class _BinaryToken(_Token): + kind = 'binary' + + def value(self): + return self.raw[1:] + + +class _NumberToken(_Token): + kind = 'number' + + def is_number(self): + return True + + def value(self): + if '.' not in self.raw: + return int(self.raw) + else: + return float(self.raw) + + +def _tokenize(data: bytes, skip_ws: bool): + """ + A generator that produces _Token instances from Type-1 font code. + + The consumer of the generator may send an integer to the tokenizer + to indicate that the next token should be _BinaryToken of the given + length. + + Parameters + ---------- + data : bytes + The data of the font to tokenize. + + skip_ws : bool + If true, the generator will drop any _WhitespaceTokens from the output. + """ + + text = data.decode('ascii', 'replace') + whitespace_or_comment_re = re.compile(r'[\0\t\r\f\n ]+|%[^\r\n]*') + token_re = re.compile(r'/{0,2}[^]\0\t\r\f\n ()<>{}/%[]+') + instring_re = re.compile(r'[()\\]') + hex_re = re.compile(r'^<[0-9a-fA-F\0\t\r\f\n ]*>$') + oct_re = re.compile(r'[0-7]{1,3}') + pos = 0 + next_binary = None + + while pos < len(text): + if next_binary is not None: + n = next_binary + next_binary = (yield _BinaryToken(pos, data[pos:pos+n])) + pos += n + continue + match = whitespace_or_comment_re.match(text, pos) + if match: + if not skip_ws: + next_binary = (yield _WhitespaceToken(pos, match.group())) + pos = match.end() + elif text[pos] == '(': + # PostScript string rules: + # - parentheses must be balanced + # - backslashes escape backslashes and parens + # - also codes \n\r\t\b\f and octal escapes are recognized + # - other backslashes do not escape anything + start = pos + pos += 1 + depth = 1 + while depth: + match = instring_re.search(text, pos) + if match is None: + raise ValueError( + f'Unterminated string starting at {start}') + pos = match.end() + if match.group() == '(': + depth += 1 + elif match.group() == ')': + depth -= 1 + else: # a backslash + char = text[pos] + if char in r'\()nrtbf': + pos += 1 + else: + octal = oct_re.match(text, pos) + if octal: + pos = octal.end() + else: + pass # non-escaping backslash + next_binary = (yield _StringToken(start, text[start:pos])) + elif text[pos:pos + 2] in ('<<', '>>'): + next_binary = (yield _DelimiterToken(pos, text[pos:pos + 2])) + pos += 2 + elif text[pos] == '<': + start = pos + try: + pos = text.index('>', pos) + 1 + except ValueError as e: + raise ValueError(f'Unterminated hex string starting at {start}' + ) from e + if not hex_re.match(text[start:pos]): + raise ValueError(f'Malformed hex string starting at {start}') + next_binary = (yield _StringToken(pos, text[start:pos])) + else: + match = token_re.match(text, pos) + if match: + raw = match.group() + if raw.startswith('/'): + next_binary = (yield _NameToken(pos, raw)) + elif match.group() in ('true', 'false'): + next_binary = (yield _BooleanToken(pos, raw)) + else: + try: + float(raw) + next_binary = (yield _NumberToken(pos, raw)) + except ValueError: + next_binary = (yield _KeywordToken(pos, raw)) + pos = match.end() + else: + next_binary = (yield _DelimiterToken(pos, text[pos])) + pos += 1 + + +class _BalancedExpression(_Token): + pass + + +def _expression(initial, tokens, data): + """ + Consume some number of tokens and return a balanced PostScript expression + + Parameters + ---------- + initial : _Token + the token that triggered parsing a balanced expression + + tokens : iterator of _Token + following tokens + + data : bytes + underlying data that the token positions point to + + Returns + ------- + _BalancedExpression + """ + delim_stack = [] + token = initial + while True: + if token.is_delim(): + if token.raw in ('[', '{'): + delim_stack.append(token) + elif token.raw in (']', '}'): + if not delim_stack: + raise RuntimeError(f"unmatched closing token {token}") + match = delim_stack.pop() + if match.raw != token.opposite(): + raise RuntimeError( + f"opening token {match} closed by {token}" + ) + if not delim_stack: + break + else: + raise RuntimeError(f'unknown delimiter {token}') + elif not delim_stack: + break + token = next(tokens) + return _BalancedExpression( + initial.pos, + data[initial.pos:token.endpos()].decode('ascii', 'replace') + ) class Type1Font: @@ -52,9 +335,20 @@ class Type1Font: decrypted : bytes The decrypted form of parts[1]. prop : dict[str, Any] - A dictionary of font properties. + A dictionary of font properties. Noteworthy keys include: + FontName - PostScript name of the font + Encoding - dict from numeric codes to glyph names + FontMatrix - bytes object encoding a matrix + UniqueID - optional font identifier, dropped when modifying the font + CharStrings - dict from glyph names to byte code + Subrs - array of byte code subroutines + OtherSubrs - bytes object encoding some PostScript code """ - __slots__ = ('parts', 'decrypted', 'prop') + __slots__ = ('parts', 'decrypted', 'prop', '_pos') + # the _pos dict contains (begin, end) indices to parts[0] + decrypted + # so that they can be replaced when transforming the font; + # but since sometimes a definition appears in both parts[0] and decrypted, + # _pos[name] is an array of such pairs def __init__(self, input): """ @@ -144,10 +438,6 @@ def _split(self, data): return data[:len1], binary, data[idx+1:] - _whitespace_or_comment_re = re.compile(br'[\0\t\r\014\n ]+|%[^\r\n\v]*') - _token_re = re.compile(br'/{0,2}[^]\0\t\r\v\n ()<>{}/%[]+') - _instring_re = re.compile(br'[()\\]') - @staticmethod def _decrypt(ciphertext, key, ndiscard=4): """ @@ -196,101 +486,75 @@ def _encrypt(plaintext, key, ndiscard=4): return bytes(ciphertext) - @classmethod - def _tokens(cls, text): - """ - A PostScript tokenizer. Yield (token, value) pairs such as - (_TokenType.whitespace, ' ') or (_TokenType.name, '/Foobar'). - """ - # Preload enum members for speed. - tok_whitespace = _TokenType.whitespace - tok_name = _TokenType.name - tok_string = _TokenType.string - tok_delimiter = _TokenType.delimiter - tok_number = _TokenType.number - pos = 0 - while pos < len(text): - match = cls._whitespace_or_comment_re.match(text, pos) - if match: - yield (tok_whitespace, match.group()) - pos = match.end() - elif text[pos:pos+1] == b'(': - start = pos - pos += 1 - depth = 1 - while depth: - match = cls._instring_re.search(text, pos) - if match is None: - return - pos = match.end() - if match.group() == b'(': - depth += 1 - elif match.group() == b')': - depth -= 1 - else: # a backslash - skip the next character - pos += 1 - yield (tok_string, text[start:pos]) - elif text[pos:pos + 2] in (b'<<', b'>>'): - yield (tok_delimiter, text[pos:pos + 2]) - pos += 2 - elif text[pos:pos+1] == b'<': - start = pos - pos = text.index(b'>', pos) - yield (tok_string, text[start:pos]) - else: - match = cls._token_re.match(text, pos) - if match: - try: - float(match.group()) - yield (tok_number, match.group()) - except ValueError: - yield (tok_name, match.group()) - pos = match.end() - else: - yield (tok_delimiter, text[pos:pos + 1]) - pos += 1 - def _parse(self): """ Find the values of various font properties. This limited kind of parsing is described in Chapter 10 "Adobe Type Manager Compatibility" of the Type-1 spec. """ - # Preload enum members for speed. - tok_whitespace = _TokenType.whitespace - tok_name = _TokenType.name - tok_string = _TokenType.string - tok_number = _TokenType.number # Start with reasonable defaults - prop = {'weight': 'Regular', 'ItalicAngle': 0.0, 'isFixedPitch': False, + prop = {'Weight': 'Regular', 'ItalicAngle': 0.0, 'isFixedPitch': False, 'UnderlinePosition': -100, 'UnderlineThickness': 50} - filtered = ((token, value) - for token, value in self._tokens(self.parts[0]) - if token is not tok_whitespace) - # The spec calls this an ASCII format; in Python 2.x we could - # just treat the strings and names as opaque bytes but let's - # turn them into proper Unicode, and be lenient in case of high bytes. - def convert(x): return x.decode('ascii', 'replace') - for token, value in filtered: - if token is tok_name and value.startswith(b'/'): - key = convert(value[1:]) - token, value = next(filtered) - if token is tok_name: - if value in (b'true', b'false'): - value = value == b'true' - else: - value = convert(value.lstrip(b'/')) - elif token is tok_string: - value = convert(value.lstrip(b'(').rstrip(b')')) - elif token is tok_number: - if b'.' in value: - value = float(value) - else: - value = int(value) - else: # more complicated value such as an array - value = None - if key != 'FontInfo' and value is not None: - prop[key] = value + pos = {} + data = self.parts[0] + self.decrypted + + source = _tokenize(data, True) + while True: + # See if there is a key to be assigned a value + # e.g. /FontName in /FontName /Helvetica def + try: + token = next(source) + except StopIteration: + break + if token.is_delim(): + # skip over this - we want top-level keys only + _expression(token, source, data) + if token.is_slash_name(): + key = token.value() + keypos = token.pos + else: + continue + + # Some values need special parsing + if key in ('Subrs', 'CharStrings', 'Encoding', 'OtherSubrs'): + prop[key], endpos = { + 'Subrs': self._parse_subrs, + 'CharStrings': self._parse_charstrings, + 'Encoding': self._parse_encoding, + 'OtherSubrs': self._parse_othersubrs + }[key](source, data) + pos.setdefault(key, []).append((keypos, endpos)) + continue + + try: + token = next(source) + except StopIteration: + break + + if isinstance(token, _KeywordToken): + # constructs like + # FontDirectory /Helvetica known {...} {...} ifelse + # mean the key was not really a key + continue + + if token.is_delim(): + value = _expression(token, source, data).raw + else: + value = token.value() + + # look for a 'def' possibly preceded by access modifiers + try: + kw = next( + kw for kw in source + if not kw.is_keyword('readonly', 'noaccess', 'executeonly') + ) + except StopIteration: + break + + # sometimes noaccess def and readonly def are abbreviated + if kw.is_name(b'def', b'ND', b'RD', b'|-'): + prop[key] = value + pos.setdefault(key, []).append((keypos, kw.endpos())) # Fill in the various *Name properties if 'FontName' not in prop: @@ -303,79 +567,114 @@ def convert(x): return x.decode('ascii', 'replace') extras = ('(?i)([ -](regular|plain|italic|oblique|(semi)?bold|' '(ultra)?light|extra|condensed))+$') prop['FamilyName'] = re.sub(extras, '', prop['FullName']) + # Decrypt the encrypted parts + ndiscard = prop.get('lenIV', 4) + cs = prop['CharStrings'] + for key, value in cs.items(): + cs[key] = self._decrypt(value, 'charstring', ndiscard) + if 'Subrs' in prop: + prop['Subrs'] = [ + self._decrypt(value, 'charstring', ndiscard) + for value in prop['Subrs'] + ] self.prop = prop + self._pos = pos - @classmethod - def _transformer(cls, tokens, slant, extend): - tok_whitespace = _TokenType.whitespace - tok_name = _TokenType.name - - def fontname(name): - result = name - if slant: - result += b'_Slant_%d' % int(1000 * slant) - if extend != 1.0: - result += b'_Extend_%d' % int(1000 * extend) - return result - - def italicangle(angle): - return b'%a' % round( - float(angle) - np.arctan(slant) / np.pi * 180, - 5 + def _parse_subrs(self, tokens, _data): + count_token = next(tokens) + if not count_token.is_number(): + raise RuntimeError( + f"Token following /Subrs must be a number, was {count_token}" ) + count = count_token.value() + array = [None] * count + next(t for t in tokens if t.is_keyword('array')) + for _ in range(count): + next(t for t in tokens if t.is_keyword('dup')) + index_token = next(tokens) + if not index_token.is_number(): + raise RuntimeError( + "Token following dup in Subrs definition must be a " + f"number, was {index_token}" + ) + nbytes_token = next(tokens) + if not nbytes_token.is_number(): + raise RuntimeError( + "Second token following dup in Subrs definition must " + f"be a number, was {nbytes_token}" + ) + token = next(tokens) # usually RD or |- but the font can define this to be anything + binary_token = tokens.send(1+nbytes_token.numeric_value()) + array[index_token.numeric_value()] = binary_token.value[1:] + + return array, next(tokens).endpos() - def fontmatrix(array): - array = array.lstrip(b'[').rstrip(b']').split() - array = [float(x) for x in array] - oldmatrix = np.eye(3, 3) - oldmatrix[0:3, 0] = array[::2] - oldmatrix[0:3, 1] = array[1::2] - modifier = np.array([[extend, 0, 0], - [slant, 1, 0], - [0, 0, 1]]) - newmatrix = np.dot(modifier, oldmatrix) - array[::2] = newmatrix[0:3, 0] - array[1::2] = newmatrix[0:3, 1] - return ( - '[%s]' % ' '.join(_format_approx(x, 6) for x in array) - ).encode('ascii') - - def replace(fun): - def replacer(tokens): - token, value = next(tokens) # name, e.g., /FontMatrix - yield value - token, value = next(tokens) # possible whitespace - while token is tok_whitespace: - yield value - token, value = next(tokens) - if value != b'[': # name/number/etc. - yield fun(value) - else: # array, e.g., [1 2 3] - result = b'' - while value != b']': - result += value - token, value = next(tokens) - result += value - yield fun(result) - return replacer - - def suppress(tokens): - for _ in itertools.takewhile(lambda x: x[1] != b'def', tokens): - pass - yield b'' - - table = {b'/FontName': replace(fontname), - b'/ItalicAngle': replace(italicangle), - b'/FontMatrix': replace(fontmatrix), - b'/UniqueID': suppress} - - for token, value in tokens: - if token is tok_name and value in table: - yield from table[value]( - itertools.chain([(token, value)], tokens)) - else: - yield value + @staticmethod + def _parse_charstrings(tokens, _data): + count_token = next(tokens) + if not count_token.is_number(): + raise RuntimeError( + "Token following /CharStrings must be a number, " + f"was {count_token}" + ) + count = count_token.value() + charstrings = {} + next(t for t in tokens if t.is_keyword('begin')) + while True: + token = next(t for t in tokens + if t.is_keyword('end') or t.is_slash_name()) + if token.raw == 'end': + return charstrings, token.endpos() + glyphname = token.value() + nbytes_token = next(tokens) + if not nbytes_token.is_number(): + raise RuntimeError( + f"Token following /{glyphname} in CharStrings definition " + f"must be a number, was {nbytes_token}" + ) + next(tokens) # usually RD or |- + binary_token = tokens.send(1+nbytes_token.value()) + charstrings[glyphname] = binary_token.value() + + @staticmethod + def _parse_encoding(tokens, _data): + # this only works for encodings that follow the Adobe manual + # but some old fonts include non-compliant data - we log a warning + # and return a possibly incomplete encoding + encoding = {} + while True: + token = next(t for t in tokens + if t.is_keyword('StandardEncoding', 'dup', 'def')) + if token.is_keyword('StandardEncoding'): + return _StandardEncoding, token.endpos() + if token.is_keyword('def'): + return encoding, token.endpos() + index_token = next(tokens) + if not index_token.is_number(): + _log.warning( + f"Parsing encoding: expected number, got {index_token}" + ) + continue + name_token = next(tokens) + if not name_token.is_slash_name(): + _log.warning( + f"Parsing encoding: expected slash-name, got {name_token}" + ) + continue + encoding[index_token.value()] = name_token.value() + + @staticmethod + def _parse_othersubrs(tokens, data): + init_pos = None + while True: + token = next(tokens) + if init_pos is None: + init_pos = token.pos + if token.is_delim(): + _expression(token, tokens, data) + elif token.is_keyword('def', 'ND', '|-'): + return data[init_pos:token.endpos()], token.endpos() def transform(self, effects): """ @@ -397,8 +696,167 @@ def transform(self, effects): ------- `Type1Font` """ - tokenizer = self._tokens(self.parts[0]) - transformed = self._transformer(tokenizer, - slant=effects.get('slant', 0.0), - extend=effects.get('extend', 1.0)) - return Type1Font((b"".join(transformed), self.parts[1], self.parts[2])) + fontname = self.prop['FontName'] + italicangle = self.prop['ItalicAngle'] + + array = [ + float(x) for x in (self.prop['FontMatrix'] + .lstrip('[').rstrip(']').split()) + ] + oldmatrix = np.eye(3, 3) + oldmatrix[0:3, 0] = array[::2] + oldmatrix[0:3, 1] = array[1::2] + modifier = np.eye(3, 3) + + if 'slant' in effects: + slant = effects['slant'] + fontname += '_Slant_%d' % int(1000 * slant) + italicangle = round( + float(italicangle) - np.arctan(slant) / np.pi * 180, + 5 + ) + modifier[1, 0] = slant + + if 'extend' in effects: + extend = effects['extend'] + fontname += '_Extend_%d' % int(1000 * extend) + modifier[0, 0] = extend + + newmatrix = np.dot(modifier, oldmatrix) + array[::2] = newmatrix[0:3, 0] + array[1::2] = newmatrix[0:3, 1] + fontmatrix = ( + '[%s]' % ' '.join(_format_approx(x, 6) for x in array) + ) + replacements = ( + [(x, '/FontName/%s def' % fontname) + for x in self._pos['FontName']] + + [(x, '/ItalicAngle %a def' % italicangle) + for x in self._pos['ItalicAngle']] + + [(x, '/FontMatrix %s readonly def' % fontmatrix) + for x in self._pos['FontMatrix']] + + [(x, '') for x in self._pos.get('UniqueID', [])] + ) + + data = bytearray(self.parts[0]) + data.extend(self.decrypted) + len0 = len(self.parts[0]) + for (pos0, pos1), value in sorted(replacements, reverse=True): + data[pos0:pos1] = value.encode('ascii', 'replace') + if pos0 < len(self.parts[0]): + if pos1 >= len(self.parts[0]): + raise RuntimeError( + f"text to be replaced with {value} spans " + "the eexec boundary" + ) + len0 += len(value) - pos1 + pos0 + + data = bytes(data) + return Type1Font(( + data[:len0], + self._encrypt(data[len0:], 'eexec'), + self.parts[2] + )) + + +_StandardEncoding = { + **{ord(letter): letter for letter in string.ascii_letters}, + 0: '.notdef', + 32: 'space', + 33: 'exclam', + 34: 'quotedbl', + 35: 'numbersign', + 36: 'dollar', + 37: 'percent', + 38: 'ampersand', + 39: 'quoteright', + 40: 'parenleft', + 41: 'parenright', + 42: 'asterisk', + 43: 'plus', + 44: 'comma', + 45: 'hyphen', + 46: 'period', + 47: 'slash', + 48: 'zero', + 49: 'one', + 50: 'two', + 51: 'three', + 52: 'four', + 53: 'five', + 54: 'six', + 55: 'seven', + 56: 'eight', + 57: 'nine', + 58: 'colon', + 59: 'semicolon', + 60: 'less', + 61: 'equal', + 62: 'greater', + 63: 'question', + 64: 'at', + 91: 'bracketleft', + 92: 'backslash', + 93: 'bracketright', + 94: 'asciicircum', + 95: 'underscore', + 96: 'quoteleft', + 123: 'braceleft', + 124: 'bar', + 125: 'braceright', + 126: 'asciitilde', + 161: 'exclamdown', + 162: 'cent', + 163: 'sterling', + 164: 'fraction', + 165: 'yen', + 166: 'florin', + 167: 'section', + 168: 'currency', + 169: 'quotesingle', + 170: 'quotedblleft', + 171: 'guillemotleft', + 172: 'guilsinglleft', + 173: 'guilsinglright', + 174: 'fi', + 175: 'fl', + 177: 'endash', + 178: 'dagger', + 179: 'daggerdbl', + 180: 'periodcentered', + 182: 'paragraph', + 183: 'bullet', + 184: 'quotesinglbase', + 185: 'quotedblbase', + 186: 'quotedblright', + 187: 'guillemotright', + 188: 'ellipsis', + 189: 'perthousand', + 191: 'questiondown', + 193: 'grave', + 194: 'acute', + 195: 'circumflex', + 196: 'tilde', + 197: 'macron', + 198: 'breve', + 199: 'dotaccent', + 200: 'dieresis', + 202: 'ring', + 203: 'cedilla', + 205: 'hungarumlaut', + 206: 'ogonek', + 207: 'caron', + 208: 'emdash', + 225: 'AE', + 227: 'ordfeminine', + 232: 'Lslash', + 233: 'Oslash', + 234: 'OE', + 235: 'ordmasculine', + 241: 'ae', + 245: 'dotlessi', + 248: 'lslash', + 249: 'oslash', + 250: 'oe', + 251: 'germandbls', +} From e98bb83384729684ce4c991448d969f7b016d392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 22 Jul 2021 13:36:51 +0300 Subject: [PATCH 005/130] Recognize abbreviations of PostScript code Type-1 fonts are required to have subroutines with specific contents but their names may vary. They are usually ND, NP and RD but names like | and |- appear too. --- lib/matplotlib/tests/test_type1font.py | 2 ++ lib/matplotlib/type1font.py | 27 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_type1font.py b/lib/matplotlib/tests/test_type1font.py index 39279c229cca..6a16da10def1 100644 --- a/lib/matplotlib/tests/test_type1font.py +++ b/lib/matplotlib/tests/test_type1font.py @@ -42,6 +42,7 @@ def test_Type1Font(): assert slanted.prop['ItalicAngle'] == -45 assert font.prop['Encoding'][5] == 'Pi' assert isinstance(font.prop['CharStrings']['Pi'], bytes) + assert font._abbr['ND'] == 'ND' differ = difflib.Differ() diff = list(differ.compare( @@ -85,6 +86,7 @@ def test_Type1Font_2(): assert font.prop['Encoding'][65] == 'A' # the font uses StandardEncoding (pos0, pos1), = font._pos['Encoding'] assert font.parts[0][pos0:pos1] == b'/Encoding StandardEncoding' + assert font._abbr['ND'] == '|-' def test_tokenize(): diff --git a/lib/matplotlib/type1font.py b/lib/matplotlib/type1font.py index 871f13642ee3..4c39ea8750b9 100644 --- a/lib/matplotlib/type1font.py +++ b/lib/matplotlib/type1font.py @@ -344,11 +344,14 @@ class Type1Font: Subrs - array of byte code subroutines OtherSubrs - bytes object encoding some PostScript code """ - __slots__ = ('parts', 'decrypted', 'prop', '_pos') + __slots__ = ('parts', 'decrypted', 'prop', '_pos', '_abbr') # the _pos dict contains (begin, end) indices to parts[0] + decrypted # so that they can be replaced when transforming the font; # but since sometimes a definition appears in both parts[0] and decrypted, # _pos[name] is an array of such pairs + # + # _abbr maps three standard abbreviations to their particular names in + # this font (e.g. 'RD' is named '-|' in some fonts) def __init__(self, input): """ @@ -368,6 +371,7 @@ def __init__(self, input): self.parts = self._split(data) self.decrypted = self._decrypt(self.parts[1], 'eexec') + self._abbr = {'RD': 'RD', 'ND': 'ND', 'NP': 'NP'} self._parse() def _read(self, file): @@ -552,10 +556,18 @@ def _parse(self): break # sometimes noaccess def and readonly def are abbreviated - if kw.is_name(b'def', b'ND', b'RD', b'|-'): + if kw.is_keyword('def', self._abbr['ND'], self._abbr['NP']): prop[key] = value pos.setdefault(key, []).append((keypos, kw.endpos())) + # detect the standard abbreviations + if value == '{noaccess def}': + self._abbr['ND'] = key + elif value == '{noaccess put}': + self._abbr['NP'] = key + elif value == '{string currentfile exch readstring pop}': + self._abbr['RD'] = key + # Fill in the various *Name properties if 'FontName' not in prop: prop['FontName'] = (prop.get('FullName') or @@ -604,9 +616,14 @@ def _parse_subrs(self, tokens, _data): "Second token following dup in Subrs definition must " f"be a number, was {nbytes_token}" ) - token = next(tokens) # usually RD or |- but the font can define this to be anything - binary_token = tokens.send(1+nbytes_token.numeric_value()) - array[index_token.numeric_value()] = binary_token.value[1:] + token = next(tokens) + if not token.is_keyword(self._abbr['RD']): + raise RuntimeError( + f"Token preceding subr must be {self._abbr['RD']}, " + f"was {token}" + ) + binary_token = tokens.send(1+nbytes_token.value()) + array[index_token.value()] = binary_token.value() return array, next(tokens).endpos() From e487b1c0d91f01e51c3203666b1a5e19d7c43920 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Fri, 23 Jul 2021 08:57:10 +0200 Subject: [PATCH 006/130] Add user supplied transforms and join/cap styles Improvement was done in instantiating new instance utilizing deep copy should preserve immutability of MarkerStyle members (e.g. Path, or Transform). --- lib/matplotlib/markers.py | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 8fbbb71818a4..644ab2a1aba7 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -127,6 +127,7 @@ .. |m36| image:: /_static/markers/m36.png .. |m37| image:: /_static/markers/m37.png """ +import copy from collections.abc import Sized import inspect @@ -221,7 +222,8 @@ class MarkerStyle: _unset = object() # For deprecation of MarkerStyle(). - def __init__(self, marker=_unset, fillstyle=None): + def __init__(self, marker=_unset, fillstyle=None, + transform=None, capstyle=None, joinstyle=None): """ Parameters ---------- @@ -236,6 +238,9 @@ def __init__(self, marker=_unset, fillstyle=None): One of 'full', 'left', 'right', 'bottom', 'top', 'none'. """ self._marker_function = None + self._user_transform = transform + self._user_capstyle = capstyle + self._user_joinstyle = joinstyle self._set_fillstyle(fillstyle) # Remove _unset and signature rewriting after deprecation elapses. if marker is self._unset: @@ -265,7 +270,7 @@ def _recache(self): self._alt_transform = None self._snap_threshold = None self._joinstyle = JoinStyle.round - self._capstyle = CapStyle.butt + self._capstyle = self._user_capstyle or CapStyle.butt # Initial guess: Assume the marker is filled unless the fillstyle is # set to 'none'. The marker function will override this for unfilled # markers. @@ -342,7 +347,8 @@ def _set_marker(self, marker): self._marker_function = getattr( self, '_set_' + self.markers[marker]) elif isinstance(marker, MarkerStyle): - self.__dict__.update(marker.__dict__) + self.__dict__ = copy.deepcopy(marker.__dict__) + else: try: Path(marker) @@ -369,7 +375,10 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - return self._transform.frozen() + if self._user_transform is not None: + return (self._transform + self._user_transform).frozen() + else: + return self._transform.frozen() def get_alt_path(self): """ @@ -385,7 +394,10 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - return self._alt_transform.frozen() + if self._user_transform is not None: + return (self._alt_transform + self._user_transform).frozen() + else: + return self._alt_transform.frozen() def get_snap_threshold(self): return self._snap_threshold @@ -413,14 +425,14 @@ def _set_tuple_marker(self): symstyle = marker[1] if symstyle == 0: self._path = Path.unit_regular_polygon(numsides) - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter elif symstyle == 1: self._path = Path.unit_regular_star(numsides) - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel else: raise ValueError(f"Unexpected tuple marker: {marker}") self._transform = Affine2D().scale(0.5).rotate_deg(rotation) @@ -521,7 +533,7 @@ def _set_triangle(self, rot, skip): self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_triangle_up(self): return self._set_triangle(0.0, 0) @@ -551,7 +563,7 @@ def _set_square(self): self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_diamond(self): self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) @@ -565,7 +577,7 @@ def _set_diamond(self): rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_thin_diamond(self): self._set_diamond() @@ -592,7 +604,7 @@ def _set_pentagon(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_star(self): self._transform = Affine2D().scale(0.5) @@ -614,7 +626,7 @@ def _set_star(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel def _set_hexagon1(self): self._transform = Affine2D().scale(0.5) @@ -638,7 +650,7 @@ def _set_hexagon1(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_hexagon2(self): self._transform = Affine2D().scale(0.5).rotate_deg(30) @@ -664,7 +676,7 @@ def _set_hexagon2(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_octagon(self): self._transform = Affine2D().scale(0.5) @@ -685,7 +697,7 @@ def _set_octagon(self): {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.0) - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]]) @@ -759,7 +771,7 @@ def _set_caretdown(self): self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_caretup(self): self._set_caretdown() @@ -825,7 +837,7 @@ def _set_x(self): def _set_plus_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._plus_filled_path else: @@ -849,7 +861,7 @@ def _set_plus_filled(self): def _set_x_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._x_filled_path else: From 37859ea33869d86c4e57aaa3512a705092e774be Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Thu, 26 Aug 2021 20:40:53 +0200 Subject: [PATCH 007/130] Avoid reinstanting MarkerStyle in lines.py --- lib/matplotlib/lines.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index ea3b98b63208..f1efd125a4ed 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -370,7 +370,10 @@ def __init__(self, xdata, ydata, self.set_color(color) if marker is None: marker = 'none' # Default. - self._marker = MarkerStyle(marker, fillstyle) + if not isinstance(marker, MarkerStyle): + self._marker = MarkerStyle(marker, fillstyle) + else: + self._marker = marker self._markevery = None self._markersize = None From 81536320d4a702db9376782ee7ac6b5a6f43971c Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 5 Sep 2021 23:30:26 +0200 Subject: [PATCH 008/130] Update dosctring. --- lib/matplotlib/markers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 644ab2a1aba7..b4413bbe3ba0 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -236,6 +236,18 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. + + transform : Affine2D, default: None + User supplied transformation that will be combined with the + native transformation of selected marker. + + capstyle : CapStyle, default: None + User supplied cap style that will override the default cap + style of selected marker. + + joinstyle : JoinStyle, default: None + User supplied join style that will override the default cap + style of selected marker. """ self._marker_function = None self._user_transform = transform From 4469006fb4b8720876b938023561233dd9588993 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Mon, 6 Sep 2021 22:56:14 +0200 Subject: [PATCH 009/130] Add marker.transformed + test --- lib/matplotlib/markers.py | 51 +++++++++++++++++++++++++++-- lib/matplotlib/tests/test_marker.py | 11 +++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index b4413bbe3ba0..5652382c945a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -236,7 +236,7 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. - + transform : Affine2D, default: None User supplied transformation that will be combined with the native transformation of selected marker. @@ -319,10 +319,10 @@ def _set_fillstyle(self, fillstyle): self._recache() def get_joinstyle(self): - return self._joinstyle + return self._user_joinstyle or self._joinstyle def get_capstyle(self): - return self._capstyle + return self._user_capstyle or self._capstyle def get_marker(self): return self._marker @@ -414,6 +414,51 @@ def get_alt_transform(self): def get_snap_threshold(self): return self._snap_threshold + def get_user_transform(self): + """Return user supplied part of marker transform.""" + if self._user_transform is not None: + return self._user_transform.frozen() + + def transformed(self, transform:Affine2D): + """ + Return new marker with combined transformation. + + Parameters + ---------- + transform : Affine2D, default: None + - transform will be combined with current user supplied transform. + """ + new_marker = MarkerStyle(self) + if new_marker._user_transform is not None: + new_marker._user_transform += transform + else: + new_marker._user_transform = transform + return new_marker + + def rotated(self, deg=None, rad=None): + """ + Return new marker rotated by specified angle. + + Parameters + ---------- + deg : float, default: None + + rad : float, default: None + """ + if not ((deg is None) ^ (rad is None)): + raise Exception("Only one of deg or rad shall be used.") + + _transform = self._user_transform or Affine2D() + + if deg is not None: + _transform = _transform.rotate_deg(deg) + + if rad is not None: + _transform = _transform.rotate(rad) + + new_marker = MarkerStyle(self) + new_marker._user_transform = _transform + def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 75681b0e1a9b..11c9564a9218 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -4,6 +4,7 @@ from matplotlib._api.deprecation import MatplotlibDeprecationWarning from matplotlib.path import Path from matplotlib.testing.decorators import check_figures_equal +from matplotlib.transforms import Affine2D import pytest @@ -204,3 +205,13 @@ def test_marker_clipping(fig_ref, fig_test): ax_test.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow)) ax_ref.axis('off') ax_test.axis('off') + +@pytest.mark.parametrize("marker,transform,expected", [ + (markers.MarkerStyle("o"), Affine2D().translate(1,1), Affine2D().translate(1,1)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1,1)), Affine2D().translate(1,1), Affine2D().translate(2,2)), +]) +def test_marker_transformed(marker, transform, expected): + new_marker = marker.transformed(transform) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker.get_user_transform() is not new_marker.get_user_transform() \ No newline at end of file From 4ea2b913da2932e08a3d8eea4ad0ff63290d4306 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Fri, 10 Sep 2021 23:10:54 +0200 Subject: [PATCH 010/130] Add affine transform primitives to markers Added, translated, scaled, and rotated methods with test. --- lib/matplotlib/markers.py | 61 ++++++++++++++++++++++++----- lib/matplotlib/tests/test_marker.py | 41 +++++++++++++++++-- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 5652382c945a..90532104f8a9 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -419,7 +419,7 @@ def get_user_transform(self): if self._user_transform is not None: return self._user_transform.frozen() - def transformed(self, transform:Affine2D): + def transformed(self, transform: Affine2D): """ Return new marker with combined transformation. @@ -442,22 +442,65 @@ def rotated(self, deg=None, rad=None): Parameters ---------- deg : float, default: None + - use this parameter to specify rotation angle in degrees. rad : float, default: None + - use this parameter to specify rotation angle in radians. + + Note: you must specify exactly one of deg or rad. """ if not ((deg is None) ^ (rad is None)): - raise Exception("Only one of deg or rad shall be used.") - - _transform = self._user_transform or Affine2D() + raise ValueError("Exactly one of deg or rad shall be used.") + + new_marker = MarkerStyle(self) + if new_marker._user_transform is None: + new_marker._user_transform = Affine2D() - if deg is not None: - _transform = _transform.rotate_deg(deg) - + if deg is not None: + new_marker._user_transform.rotate_deg(deg) if rad is not None: - _transform = _transform.rotate(rad) + new_marker._user_transform.rotate(rad) + + return new_marker + + def scaled(self, sx, sy=None): + """ + Return new marker scaled by specified scale factors. + + If *sy* is None, the same scale is applied in both the *x*- and + *y*-directions. + + Parameters + ---------- + sx : float + - *x*-direction scaling factor. + + sy : float, default: None + - *y*-direction scaling factor. + """ + if sy is None: + sy = sx + + new_marker = MarkerStyle(self) + _transform = new_marker._user_transform or Affine2D() + new_marker._user_transform = _transform.scale(sx, sy) + return new_marker + + def translated(self, tx, ty): + """ + Return new marker translated by tx and ty. + Parameters + ---------- + tx : float + + ty : float + + """ new_marker = MarkerStyle(self) - new_marker._user_transform = _transform + _transform = new_marker._user_transform or Affine2D() + new_marker._user_transform = _transform.translate(tx, ty) + return new_marker def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 11c9564a9218..db2aaa768e8f 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -206,12 +206,47 @@ def test_marker_clipping(fig_ref, fig_test): ax_ref.axis('off') ax_test.axis('off') + @pytest.mark.parametrize("marker,transform,expected", [ - (markers.MarkerStyle("o"), Affine2D().translate(1,1), Affine2D().translate(1,1)), - (markers.MarkerStyle("o", transform=Affine2D().translate(1,1)), Affine2D().translate(1,1), Affine2D().translate(2,2)), + (markers.MarkerStyle("o"), Affine2D().translate(1, 1), + Affine2D().translate(1, 1)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + # Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), ]) def test_marker_transformed(marker, transform, expected): new_marker = marker.transformed(transform) assert new_marker is not marker assert new_marker.get_user_transform() == expected - assert marker.get_user_transform() is not new_marker.get_user_transform() \ No newline at end of file + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_rotated_invalid(): + marker = markers.MarkerStyle("o") + with pytest.raises(ValueError): + new_marker = marker.rotated() + new_marker = marker.rotated(deg=10, rad=10) + + +@pytest.mark.parametrize("marker,deg,rad,expected", [ + (markers.MarkerStyle("o"), 10, None, Affine2D().rotate_deg(10)), + (markers.MarkerStyle("o"), None, 0.01, Affine2D().rotate(0.01)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + None, 0.01, Affine2D().translate(1, 1).rotate(0.01)), + # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + # 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), +]) +def test_marker_rotated_deg(marker, deg, rad, expected): + new_marker = marker.rotated(deg=deg, rad=rad) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform From 7f6d1bece34f6dbbb8ab917e828acc2edda34425 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sat, 11 Sep 2021 20:20:54 +0200 Subject: [PATCH 011/130] Tweaking docstrings in MarkerStyle. --- lib/matplotlib/markers.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 90532104f8a9..fc639fd6fbd8 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -426,7 +426,7 @@ def transformed(self, transform: Affine2D): Parameters ---------- transform : Affine2D, default: None - - transform will be combined with current user supplied transform. + Transform will be combined with current user supplied transform. """ new_marker = MarkerStyle(self) if new_marker._user_transform is not None: @@ -442,12 +442,12 @@ def rotated(self, deg=None, rad=None): Parameters ---------- deg : float, default: None - - use this parameter to specify rotation angle in degrees. + Use this parameter to specify rotation angle in degrees. rad : float, default: None - - use this parameter to specify rotation angle in radians. + Use this parameter to specify rotation angle in radians. - Note: you must specify exactly one of deg or rad. + .. note:: You must specify exactly one of deg or rad. """ if not ((deg is None) ^ (rad is None)): raise ValueError("Exactly one of deg or rad shall be used.") @@ -473,10 +473,9 @@ def scaled(self, sx, sy=None): Parameters ---------- sx : float - - *x*-direction scaling factor. - + *X*-direction scaling factor. sy : float, default: None - - *y*-direction scaling factor. + *Y*-direction scaling factor. """ if sy is None: sy = sx @@ -493,9 +492,9 @@ def translated(self, tx, ty): Parameters ---------- tx : float - + Coordinate for translation in *x*-direction. ty : float - + Coordinate for translation in *y*-direction. """ new_marker = MarkerStyle(self) _transform = new_marker._user_transform or Affine2D() From 2729fe43217ea810c33131eb640aac8c5c45abd3 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:05:03 +0200 Subject: [PATCH 012/130] Update MarkerStyle module documentation --- lib/matplotlib/markers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index fc639fd6fbd8..b380e1203484 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,6 +82,13 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) +Markers have some reasonable default settings for join and cap styles. +However, those can be overriden when creating a new instance of MarkerStyle. +Furthermore, the marker shape can be modified by supplying +`~matplotlib .transforms.Transform` during creation. +Some markers are created as internally rotated shapes (e.g. triangles). +For such cases, both internal and user supplied transforms are combined. + Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` From 435799533a9a2a741d0b04aad23b64730263ebd7 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:05:38 +0200 Subject: [PATCH 013/130] Update marker Reference documentation --- .../marker_reference.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index 98af2519124c..d1d675c5c268 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -19,8 +19,10 @@ .. redirect-from:: /gallery/shapes_and_collections/marker_path """ +from matplotlib.markers import MarkerStyle import matplotlib.pyplot as plt from matplotlib.lines import Line2D +from matplotlib.transforms import Affine2D text_style = dict(horizontalalignment='right', verticalalignment='center', @@ -159,3 +161,98 @@ def split_list(a_list): format_axes(ax) plt.show() + +############################################################################### +# Advanced marker modifications with transform +# ============================================ +# +# All markers can be modified by a user transform in MarkerStyle constructor. +# Supplied transform is combined with the default transforms needed for +# selected marker shape (e.g. caret up, caret down). Following example shows +# how user supplied rotation applies to several marker shapes. + +common_style = {k: v for k, v in filled_marker_style.items() if v != 'marker'} +angles = [0, 10, 20, 30, 45, 60, 90] + +fig, ax = plt.subplots() +fig.suptitle('Rotated markers', fontsize=14) + +ax.text(-0.5, 0, 'Filled marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + ax.plot(x, 0, marker=MarkerStyle('o', 'left', t), **common_style) + +ax.text(-0.5, 1, 'Un-filled marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + ax.plot(x, 1, marker=MarkerStyle('1', 'left', t), **common_style) + +ax.text(-0.5, 2, 'Equation marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + eq = r'$\frac{1}{x}$' + ax.plot(x, 2, marker=MarkerStyle(eq, 'left', t), **common_style) + +for x, theta in enumerate(angles): + ax.text(x, 2.5, f"{theta}°", horizontalalignment="center") +format_axes(ax) + +plt.show() + +############################################################################### +# Setting marker cap style and join style +# ======================================= +# +# All markers have predefined cap style and join style, but this can be +# overriden during creation of MarkerStyle. Follwing example show how to +# change the cap style and how different styles look. + +from matplotlib.markers import JoinStyle, CapStyle + +marker_inner = dict(markersize=35, + markerfacecolor='tab:blue', + markerfacecoloralt='lightsteelblue', + markeredgecolor='brown', + markeredgewidth=8, + ) + +marker_outer = dict(markersize=35, + markerfacecolor='tab:blue', + markerfacecoloralt='lightsteelblue', + markeredgecolor='white', + markeredgewidth=1, + ) + +fig, ax = plt.subplots() +fig.suptitle('Marker CapStyle', fontsize=14) +fig.subplots_adjust(left=0.1) + +for y, cap_style in enumerate([None, *CapStyle]): + ax.text(-0.5, y, cap_style.name, **text_style) + for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + m = MarkerStyle('1', transform=t, capstyle=cap_style) + ax.plot(x, y, marker=m, **marker_inner) + ax.plot(x, y, marker=m, **marker_outer) + ax.text(x, len(CapStyle) - .5, f'{theta}°', ha='center') +format_axes(ax) +plt.show() + +############################################################################### +# Follwing example show how to change the join style and how different styles +# look. + +fig, ax = plt.subplots() +fig.suptitle('Marker JoinStyle', fontsize=14) +fig.subplots_adjust(left=0.05) + +for y, join_style in enumerate(JoinStyle): + ax.text(-0.5, y, join_style.name, **text_style) + for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + m = MarkerStyle('*', transform=t, joinstyle=join_style) + ax.plot(x, y, marker=m, **marker_inner) + ax.text(x, len(JoinStyle) - .5, f'{theta}°', ha='center') +format_axes(ax) + +plt.show() From b1bbdff12758e8e0a30841ba075cdd6c9c2485c6 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:06:03 +0200 Subject: [PATCH 014/130] Add whats new for MarkerStyle features --- .../next_whats_new/extending_MarkerStyle.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 doc/users/next_whats_new/extending_MarkerStyle.rst diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst new file mode 100644 index 000000000000..06479a7badf8 --- /dev/null +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -0,0 +1,19 @@ +New customization of MarkerStyle +-------------------------------- + +New MarkerStyle parameters allow control of join style and cap style. +The appearance of individual markers can be further controlled by +transform supplied during creation. + +.. plot:: + :include-source: + import matplotlib.pyplot as plt + from matplotlib.markers import MarkerStyle + from matplotlib.transforms import Affine2D + fig, ax = plt.subplots(figsize=(6, 1)) + fig.suptitle('New markers', fontsize=14) + for col, (size, rot) in enumerate(zip([2, 5, 10], [0, 45, 90])): + t = Affine2D().rotate_deg(rot).scale(size) + ax.plot(col, 0, marker=MarkerStyle("*", transform=t)) + ax.axis("off") + ax.set_xlim(-0.1, 2.4) From 9ac3ad2f6e5a14ab12a1dabf975ba71fa466f42f Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 19:27:36 +0200 Subject: [PATCH 015/130] Add new example with advanced MarkerStyle mapping --- .../multivariate_marker_plot.py | 45 +++++++++++++++++++ lib/matplotlib/markers.py | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 examples/lines_bars_and_markers/multivariate_marker_plot.py diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py new file mode 100644 index 000000000000..274a1113fbc1 --- /dev/null +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -0,0 +1,45 @@ +""" +============================================== +Mapping marker properties to multivariate data +============================================== + +This example shows how to use different properties of markers to plot +multivariate datasets. Following example shows an illustrative case of +plotting success of baseball throw as an smiley face with size mapped to +the skill of thrower, rotation mapped to the take-off angle, and thrust +to the color of the marker. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.markers import MarkerStyle +from matplotlib.transforms import Affine2D +from matplotlib.textpath import TextPath +from matplotlib.colors import Normalize + +SUCCESS_SYMBOLS = [ + TextPath((0,0), "☹"), + TextPath((0,0), "😒"), + TextPath((0,0), "☺"), +] + +N = 25 +np.random.seed(42) +skills = np.random.uniform(5, 80, size=N) * 0.1 + 5 +takeoff_angles = np.random.normal(0, 90, N) +thrusts = np.random.uniform(size=N) +successfull = np.random.randint(0, 3, size=N) +positions = np.random.normal(size=(N, 2)) * 5 +data = zip(skills, takeoff_angles, thrusts, successfull, positions) + +cmap = plt.cm.get_cmap("plasma") +fig, ax = plt.subplots() +fig.suptitle("Throwing success", size=14) +for skill, takeoff, thrust, mood, pos in data: + t = Affine2D().scale(skill).rotate_deg(takeoff) + m = MarkerStyle(SUCCESS_SYMBOLS[mood], transform=t) + ax.plot(pos[0], pos[1], marker=m, color=cmap(thrust)) +fig.colorbar(plt.cm.ScalarMappable(norm=Normalize(0, 1), cmap=cmap), + ax=ax, label="Normalized Thrust [a.u.]") +ax.set_xlabel("X position [m]") +ax.set_ylabel("Y position [m]") diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index b380e1203484..86b3492d8776 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -93,7 +93,7 @@ * :doc:`/gallery/lines_bars_and_markers/marker_reference` * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly` - +* :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot` .. |m00| image:: /_static/markers/m00.png .. |m01| image:: /_static/markers/m01.png From 41808430ace77c5dfdbb815c1d2e26ad3c1e8181 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 23:40:33 +0200 Subject: [PATCH 016/130] Fix errors discovered by CI --- doc/users/next_whats_new/extending_MarkerStyle.rst | 3 ++- examples/lines_bars_and_markers/marker_reference.py | 6 +++--- .../lines_bars_and_markers/multivariate_marker_plot.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst index 06479a7badf8..fbe0fadd7f0c 100644 --- a/doc/users/next_whats_new/extending_MarkerStyle.rst +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -6,7 +6,8 @@ The appearance of individual markers can be further controlled by transform supplied during creation. .. plot:: - :include-source: + :include-source: true + import matplotlib.pyplot as plt from matplotlib.markers import MarkerStyle from matplotlib.transforms import Affine2D diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index d1d675c5c268..f82ec36035e6 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -171,7 +171,7 @@ def split_list(a_list): # selected marker shape (e.g. caret up, caret down). Following example shows # how user supplied rotation applies to several marker shapes. -common_style = {k: v for k, v in filled_marker_style.items() if v != 'marker'} +common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] fig, ax = plt.subplots() @@ -227,7 +227,7 @@ def split_list(a_list): fig.suptitle('Marker CapStyle', fontsize=14) fig.subplots_adjust(left=0.1) -for y, cap_style in enumerate([None, *CapStyle]): +for y, cap_style in enumerate(CapStyle): ax.text(-0.5, y, cap_style.name, **text_style) for x, theta in enumerate(angles): t = Affine2D().rotate_deg(theta) @@ -240,7 +240,7 @@ def split_list(a_list): ############################################################################### # Follwing example show how to change the join style and how different styles -# look. +# looks like. fig, ax = plt.subplots() fig.suptitle('Marker JoinStyle', fontsize=14) diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 274a1113fbc1..51d7d4bca30e 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -18,9 +18,9 @@ from matplotlib.colors import Normalize SUCCESS_SYMBOLS = [ - TextPath((0,0), "☹"), - TextPath((0,0), "😒"), - TextPath((0,0), "☺"), + TextPath((0, 0), "☹"), + TextPath((0, 0), "😒"), + TextPath((0, 0), "☺"), ] N = 25 @@ -40,6 +40,6 @@ m = MarkerStyle(SUCCESS_SYMBOLS[mood], transform=t) ax.plot(pos[0], pos[1], marker=m, color=cmap(thrust)) fig.colorbar(plt.cm.ScalarMappable(norm=Normalize(0, 1), cmap=cmap), - ax=ax, label="Normalized Thrust [a.u.]") + ax=ax, label="Normalized Thrust [a.u.]") ax.set_xlabel("X position [m]") ax.set_ylabel("Y position [m]") From 905eba488f9d2f0db420bd710b4fdc465397a2d8 Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Mon, 13 Sep 2021 08:07:33 +0200 Subject: [PATCH 017/130] Fix typo in markers api documentation. Apply suggestion from @timhoffm, update lib/matplotlib/markers.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/markers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 86b3492d8776..ad06879532f6 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -85,7 +85,7 @@ Markers have some reasonable default settings for join and cap styles. However, those can be overriden when creating a new instance of MarkerStyle. Furthermore, the marker shape can be modified by supplying -`~matplotlib .transforms.Transform` during creation. +`~matplotlib.transforms.Transform` during creation. Some markers are created as internally rotated shapes (e.g. triangles). For such cases, both internal and user supplied transforms are combined. From 3fc6140998d3a8cf130d53e161c0b2b4ff852a18 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 00:32:03 +0200 Subject: [PATCH 018/130] Improve test for invalid rotated inputs --- lib/matplotlib/markers.py | 2 +- lib/matplotlib/tests/test_marker.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index ad06879532f6..2a51d8fa5e9a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -456,7 +456,7 @@ def rotated(self, deg=None, rad=None): .. note:: You must specify exactly one of deg or rad. """ - if not ((deg is None) ^ (rad is None)): + if not (deg is None) ^ (rad is None): raise ValueError("Exactly one of deg or rad shall be used.") new_marker = MarkerStyle(self) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index db2aaa768e8f..e2b18354f34a 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -229,6 +229,7 @@ def test_marker_rotated_invalid(): marker = markers.MarkerStyle("o") with pytest.raises(ValueError): new_marker = marker.rotated() + with pytest.raises(ValueError): new_marker = marker.rotated(deg=10, rad=10) From 04a3e7046b0ae8c54f3fbd70b35d975005731a03 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 00:39:35 +0200 Subject: [PATCH 019/130] Add tests for MarkerStyle scaled and translated --- lib/matplotlib/tests/test_marker.py | 38 +++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index e2b18354f34a..7925f22f54f7 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -229,7 +229,7 @@ def test_marker_rotated_invalid(): marker = markers.MarkerStyle("o") with pytest.raises(ValueError): new_marker = marker.rotated() - with pytest.raises(ValueError): + with pytest.raises(ValueError): new_marker = marker.rotated(deg=10, rad=10) @@ -246,8 +246,42 @@ def test_marker_rotated_invalid(): markers.TICKLEFT, transform=Affine2D().translate(1, 1)), 10, None, Affine2D().translate(1, 1).rotate_deg(10)), ]) -def test_marker_rotated_deg(marker, deg, rad, expected): +def test_marker_rotated(marker, deg, rad, expected): new_marker = marker.rotated(deg=deg, rad=rad) assert new_marker is not marker assert new_marker.get_user_transform() == expected assert marker._user_transform is not new_marker._user_transform + + +def test_marker_translated(): + marker = markers.MarkerStyle("1") + new_marker = marker.translated(1, 1) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().translate(1, 1) + assert marker._user_transform is not new_marker._user_transform + + marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) + new_marker = marker.translated(1, 1) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().translate(2, 2) + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_scaled(): + marker = markers.MarkerStyle("1") + new_marker = marker.scaled(2) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2) + assert marker._user_transform is not new_marker._user_transform + + new_marker = marker.scaled(2, 3) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2, 3) + assert marker._user_transform is not new_marker._user_transform + + marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) + new_marker = marker.scaled(2) + assert new_marker is not marker + expected = Affine2D().translate(1, 1).scale(2) + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform From af6b89ad58025d24fe73d8292a8c5e08f4d330bd Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:09:27 +0200 Subject: [PATCH 020/130] Add function for optimized None+transform --- lib/matplotlib/markers.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 2a51d8fa5e9a..e8fafd909e19 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -154,6 +154,14 @@ _empty_path = Path(np.empty((0, 2))) +def _fast_transform_combine(t1, t2): + """Combine two transformations where the second one can be None.""" + if t2 is None: + return t1.frozen() + else: + return (t1 + t2).frozen() + + class MarkerStyle: """ A class representing marker types. @@ -394,10 +402,7 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - if self._user_transform is not None: - return (self._transform + self._user_transform).frozen() - else: - return self._transform.frozen() + return _fast_transform_combine(self._transform, self._user_transform) def get_alt_path(self): """ @@ -413,10 +418,8 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - if self._user_transform is not None: - return (self._alt_transform + self._user_transform).frozen() - else: - return self._alt_transform.frozen() + return _fast_transform_combine(self._alt_transform, + self._user_transform) def get_snap_threshold(self): return self._snap_threshold From c01e8f1b5e1786b7be1159e92833496712ac7e89 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:11:05 +0200 Subject: [PATCH 021/130] Remove ambiguous translated primitive for MarkerStyle --- lib/matplotlib/markers.py | 16 ---------------- lib/matplotlib/tests/test_marker.py | 14 -------------- 2 files changed, 30 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index e8fafd909e19..6a839d040495 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -495,22 +495,6 @@ def scaled(self, sx, sy=None): new_marker._user_transform = _transform.scale(sx, sy) return new_marker - def translated(self, tx, ty): - """ - Return new marker translated by tx and ty. - - Parameters - ---------- - tx : float - Coordinate for translation in *x*-direction. - ty : float - Coordinate for translation in *y*-direction. - """ - new_marker = MarkerStyle(self) - _transform = new_marker._user_transform or Affine2D() - new_marker._user_transform = _transform.translate(tx, ty) - return new_marker - def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 7925f22f54f7..f7f5d5614cac 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -253,20 +253,6 @@ def test_marker_rotated(marker, deg, rad, expected): assert marker._user_transform is not new_marker._user_transform -def test_marker_translated(): - marker = markers.MarkerStyle("1") - new_marker = marker.translated(1, 1) - assert new_marker is not marker - assert new_marker.get_user_transform() == Affine2D().translate(1, 1) - assert marker._user_transform is not new_marker._user_transform - - marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) - new_marker = marker.translated(1, 1) - assert new_marker is not marker - assert new_marker.get_user_transform() == Affine2D().translate(2, 2) - assert marker._user_transform is not new_marker._user_transform - - def test_marker_scaled(): marker = markers.MarkerStyle("1") new_marker = marker.scaled(2) From 95d93d27a3e82a311d86e33007668c36e11cb745 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:25:18 +0200 Subject: [PATCH 022/130] Add missing test for MarkerStyle.get_alt_transform --- lib/matplotlib/tests/test_marker.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index f7f5d5614cac..d3665d9ce09d 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -271,3 +271,9 @@ def test_marker_scaled(): expected = Affine2D().translate(1, 1).scale(2) assert new_marker.get_user_transform() == expected assert marker._user_transform is not new_marker._user_transform + + +def test_alt_transform(): + m1 = markers.MarkerStyle("o", "left") + m2 = markers.MarkerStyle("o", "left", Affine2D().rotate_deg(90)) + assert m1.get_alt_transform().rotate_deg(90) == m2.get_alt_transform() From 75a92d81edfb69c0da319ced1f9f1e8b590e4b1f Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Thu, 16 Sep 2021 23:02:53 +0200 Subject: [PATCH 023/130] Improve documentation outputs. --- examples/lines_bars_and_markers/marker_reference.py | 1 + examples/lines_bars_and_markers/multivariate_marker_plot.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index f82ec36035e6..f751bfb975b8 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -197,6 +197,7 @@ def split_list(a_list): ax.text(x, 2.5, f"{theta}°", horizontalalignment="center") format_axes(ax) +fig.tight_layout() plt.show() ############################################################################### diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 51d7d4bca30e..0a79026b2772 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -43,3 +43,5 @@ ax=ax, label="Normalized Thrust [a.u.]") ax.set_xlabel("X position [m]") ax.set_ylabel("Y position [m]") + +plt.show() From 886157f157dc95690b459708311f8c609b0572fc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 23 Aug 2021 22:42:21 -0400 Subject: [PATCH 024/130] TST: add pyqt on appveyor --- .appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 0e8e0a553fd4..c637dae4d869 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -60,6 +60,8 @@ install: # pull pywin32 from conda because on py38 there is something wrong with finding # the dlls when insalled from pip - conda install -c conda-forge pywin32 + # install pyqt from conda-forge + - conda install -c conda-forge pyqt - echo %PYTHON_VERSION% %TARGET_ARCH% # Install dependencies from PyPI. - python -m pip install --upgrade -r requirements/testing/all.txt %EXTRAREQS% %PINNEDVERS% From 2974f3ca9c86fcae57cc477f2e725e8147d85d0e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 25 Aug 2021 20:41:07 -0400 Subject: [PATCH 025/130] Move sigint tests into subprocesses. This prevents them accidentally breaking the test runner itself, depending on platform. --- lib/matplotlib/tests/test_backend_qt.py | 162 +++++++++++++++++------- 1 file changed, 117 insertions(+), 45 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index b22772c35f78..fa941d072c89 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -24,6 +24,9 @@ pytestmark = pytest.mark.skip('No usable Qt bindings') +_test_timeout = 60 # A reasonably safe value for slower architectures. + + @pytest.fixture def qt_core(request): backend, = request.node.get_closest_marker('backend').args @@ -33,19 +36,6 @@ def qt_core(request): return QtCore -@pytest.fixture -def platform_simulate_ctrl_c(request): - import signal - from functools import partial - - if hasattr(signal, "CTRL_C_EVENT"): - win32api = pytest.importorskip('win32api') - return partial(win32api.GenerateConsoleCtrlEvent, 0, 0) - else: - # we're not on windows - return partial(os.kill, os.getpid(), signal.SIGINT) - - @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_fig_close(): @@ -64,50 +54,134 @@ def test_fig_close(): assert init_figs == Gcf.figs -@pytest.mark.backend('QtAgg', skip_on_importerror=True) -@pytest.mark.parametrize("target, kwargs", [ - (plt.show, {"block": True}), - (plt.pause, {"interval": 10}) -]) -def test_sigint(qt_core, platform_simulate_ctrl_c, target, - kwargs): - plt.figure() - def fire_signal(): - platform_simulate_ctrl_c() +class InterruptiblePopen(subprocess.Popen): + """ + A Popen that passes flags that allow triggering KeyboardInterrupt. + """ + + def __init__(self, *args, **kwargs): + if sys.platform == 'win32': + kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP + super().__init__( + *args, **kwargs, + # Force Agg so that each test can switch to its desired Qt backend. + env={**os.environ, "MPLBACKEND": "Agg", "SOURCE_DATE_EPOCH": "0"}, + stdout=subprocess.PIPE, universal_newlines=True) + + def wait_for(self, terminator): + """Read until the terminator is reached.""" + buf = '' + while True: + c = self.stdout.read(1) + if not c: + raise RuntimeError( + f'Subprocess died before emitting expected {terminator!r}') + buf += c + if buf.endswith(terminator): + return + + def interrupt(self): + """Interrupt process in a platform-specific way.""" + if sys.platform == 'win32': + self.send_signal(signal.CTRL_C_EVENT) + else: + self.send_signal(signal.SIGINT) + + +def _test_sigint_impl(backend, target_name, kwargs): + import sys + import matplotlib.pyplot as plt + plt.switch_backend(backend) + from matplotlib.backends.qt_compat import QtCore - qt_core.QTimer.singleShot(100, fire_signal) - with pytest.raises(KeyboardInterrupt): + target = getattr(plt, target_name) + + fig = plt.figure() + fig.canvas.mpl_connect('draw_event', + lambda *args: print('DRAW', flush=True)) + try: target(**kwargs) + except KeyboardInterrupt: + print('SUCCESS', flush=True) @pytest.mark.backend('QtAgg', skip_on_importerror=True) @pytest.mark.parametrize("target, kwargs", [ - (plt.show, {"block": True}), - (plt.pause, {"interval": 10}) + ('show', {'block': True}), + ('pause', {'interval': 10}) ]) -def test_other_signal_before_sigint(qt_core, platform_simulate_ctrl_c, - target, kwargs): - plt.figure() +def test_sigint(target, kwargs): + backend = plt.get_backend() + proc = InterruptiblePopen( + [sys.executable, "-c", + inspect.getsource(_test_sigint_impl) + + f"\n_test_sigint_impl({backend!r}, {target!r}, {kwargs!r})"]) + try: + proc.wait_for('DRAW') + proc.interrupt() + stdout, _ = proc.communicate(timeout=_test_timeout) + except: + proc.kill() + stdout, _ = proc.communicate() + raise + print(stdout) + assert 'SUCCESS' in stdout + + +def _test_other_signal_before_sigint_impl(backend, target_name, kwargs): + import signal + import sys + import matplotlib.pyplot as plt + plt.switch_backend(backend) + from matplotlib.backends.qt_compat import QtCore - sigcld_caught = False - def custom_sigpipe_handler(signum, frame): - nonlocal sigcld_caught - sigcld_caught = True - signal.signal(signal.SIGCHLD, custom_sigpipe_handler) + target = getattr(plt, target_name) - def fire_other_signal(): - os.kill(os.getpid(), signal.SIGCHLD) + fig = plt.figure() + fig.canvas.mpl_connect('draw_event', + lambda *args: print('DRAW', flush=True)) - def fire_sigint(): - platform_simulate_ctrl_c() + timer = fig.canvas.new_timer(interval=1) + timer.single_shot = True + timer.add_callback(print, 'SIGUSR1', flush=True) - qt_core.QTimer.singleShot(50, fire_other_signal) - qt_core.QTimer.singleShot(100, fire_sigint) + def custom_signal_handler(signum, frame): + timer.start() + signal.signal(signal.SIGUSR1, custom_signal_handler) - with pytest.raises(KeyboardInterrupt): + try: target(**kwargs) + except KeyboardInterrupt: + print('SUCCESS', flush=True) - assert sigcld_caught + +@pytest.mark.skipif(sys.platform == 'win32', + reason='No other signal available to send on Windows') +@pytest.mark.backend('QtAgg', skip_on_importerror=True) +@pytest.mark.parametrize("target, kwargs", [ + ('show', {'block': True}), + ('pause', {'interval': 10}) +]) +def test_other_signal_before_sigint(target, kwargs): + backend = plt.get_backend() + proc = InterruptiblePopen( + [sys.executable, "-c", + inspect.getsource(_test_other_signal_before_sigint_impl) + + "\n_test_other_signal_before_sigint_impl(" + f"{backend!r}, {target!r}, {kwargs!r})"]) + try: + proc.wait_for('DRAW') + os.kill(proc.pid, signal.SIGUSR1) + proc.wait_for('SIGUSR1') + proc.interrupt() + stdout, _ = proc.communicate(timeout=_test_timeout) + except: + proc.kill() + stdout, _ = proc.communicate() + raise + print(stdout) + assert 'SUCCESS' in stdout + plt.figure() @pytest.mark.backend('Qt5Agg') @@ -548,8 +622,6 @@ def _get_testable_qt_backends(): envs.append(pytest.param(env, marks=marks, id=str(env))) return envs -_test_timeout = 60 # A reasonably safe value for slower architectures. - @pytest.mark.parametrize("env", _get_testable_qt_backends()) def test_enums_available(env): From df090437c1dc8cce9e46305d0ea55ad31b1f83e9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 25 Aug 2021 23:30:37 -0400 Subject: [PATCH 026/130] Ensure test_fig_sigint_override cleans up global state. --- lib/matplotlib/tests/test_backend_qt.py | 36 +++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index fa941d072c89..b0a052c5c1fe 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -214,29 +214,31 @@ def custom_handler(signum, frame): signal.signal(signal.SIGINT, custom_handler) - # mainloop() sets SIGINT, starts Qt event loop (which triggers timer and - # exits) and then mainloop() resets SIGINT - matplotlib.backends.backend_qt._BackendQT.mainloop() + try: + # mainloop() sets SIGINT, starts Qt event loop (which triggers timer + # and exits) and then mainloop() resets SIGINT + matplotlib.backends.backend_qt._BackendQT.mainloop() - # Assert: signal handler during loop execution is changed - # (can't test equality with func) - assert event_loop_handler != custom_handler + # Assert: signal handler during loop execution is changed + # (can't test equality with func) + assert event_loop_handler != custom_handler - # Assert: current signal handler is the same as the one we set before - assert signal.getsignal(signal.SIGINT) == custom_handler + # Assert: current signal handler is the same as the one we set before + assert signal.getsignal(signal.SIGINT) == custom_handler - # Repeat again to test that SIG_DFL and SIG_IGN will not be overridden - for custom_handler in (signal.SIG_DFL, signal.SIG_IGN): - qt_core.QTimer.singleShot(0, fire_signal_and_quit) - signal.signal(signal.SIGINT, custom_handler) + # Repeat again to test that SIG_DFL and SIG_IGN will not be overridden + for custom_handler in (signal.SIG_DFL, signal.SIG_IGN): + qt_core.QTimer.singleShot(0, fire_signal_and_quit) + signal.signal(signal.SIGINT, custom_handler) - _BackendQT5.mainloop() + _BackendQT5.mainloop() - assert event_loop_handler == custom_handler - assert signal.getsignal(signal.SIGINT) == custom_handler + assert event_loop_handler == custom_handler + assert signal.getsignal(signal.SIGINT) == custom_handler - # Reset SIGINT handler to what it was before the test - signal.signal(signal.SIGINT, original_handler) + finally: + # Reset SIGINT handler to what it was before the test + signal.signal(signal.SIGINT, original_handler) @pytest.mark.parametrize( From 9e84366c0debd78906f888dc2cce2248c4a41e4d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 15 Sep 2021 16:00:13 -0400 Subject: [PATCH 027/130] FIX: try @vdrhtc to fix ctrl-c on windows --- lib/matplotlib/backends/qt_compat.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 6fbe103ce03b..f0568d48b62d 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -228,8 +228,14 @@ def _maybe_allow_interrupt(qapp): rsock.fileno(), _enum('QtCore.QSocketNotifier.Type').Read ) + rsock.setblocking(False) # Clear the socket to re-arm the notifier. - sn.activated.connect(lambda *args: rsock.recv(1)) + @sn.activated.connect + def _may_clear_sock(*args): + try: + rsock.recv(1) + except BlockingIOError: + pass def handle(*args): nonlocal handler_args From 9d13e7d14f864dbb3b5ea35f74d02f649596ac0d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 16 Sep 2021 18:58:16 -0400 Subject: [PATCH 028/130] TST: fix ctrl-c tests on windows --- lib/matplotlib/tests/test_backend_qt.py | 41 +++++++++++++++---------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index b0a052c5c1fe..7bdc01fe0223 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -54,14 +54,14 @@ def test_fig_close(): assert init_figs == Gcf.figs -class InterruptiblePopen(subprocess.Popen): +class WaitForStringPopen(subprocess.Popen): """ A Popen that passes flags that allow triggering KeyboardInterrupt. """ def __init__(self, *args, **kwargs): if sys.platform == 'win32': - kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP + kwargs['creationflags'] = subprocess.CREATE_NEW_CONSOLE super().__init__( *args, **kwargs, # Force Agg so that each test can switch to its desired Qt backend. @@ -80,25 +80,35 @@ def wait_for(self, terminator): if buf.endswith(terminator): return - def interrupt(self): - """Interrupt process in a platform-specific way.""" - if sys.platform == 'win32': - self.send_signal(signal.CTRL_C_EVENT) - else: - self.send_signal(signal.SIGINT) - def _test_sigint_impl(backend, target_name, kwargs): import sys import matplotlib.pyplot as plt + import os + import threading + plt.switch_backend(backend) from matplotlib.backends.qt_compat import QtCore - target = getattr(plt, target_name) + def interupter(): + if sys.platform == 'win32': + import win32api + win32api.GenerateConsoleCtrlEvent(0, 0) + else: + import signal + os.kill(os.getpid(), signal.SIGINT) + target = getattr(plt, target_name) + timer = threading.Timer(1, interupter) fig = plt.figure() - fig.canvas.mpl_connect('draw_event', - lambda *args: print('DRAW', flush=True)) + fig.canvas.mpl_connect( + 'draw_event', + lambda *args: print('DRAW', flush=True) + ) + fig.canvas.mpl_connect( + 'draw_event', + lambda *args: timer.start() + ) try: target(**kwargs) except KeyboardInterrupt: @@ -112,13 +122,12 @@ def _test_sigint_impl(backend, target_name, kwargs): ]) def test_sigint(target, kwargs): backend = plt.get_backend() - proc = InterruptiblePopen( + proc = WaitForStringPopen( [sys.executable, "-c", inspect.getsource(_test_sigint_impl) + f"\n_test_sigint_impl({backend!r}, {target!r}, {kwargs!r})"]) try: proc.wait_for('DRAW') - proc.interrupt() stdout, _ = proc.communicate(timeout=_test_timeout) except: proc.kill() @@ -164,7 +173,7 @@ def custom_signal_handler(signum, frame): ]) def test_other_signal_before_sigint(target, kwargs): backend = plt.get_backend() - proc = InterruptiblePopen( + proc = WaitForStringPopen( [sys.executable, "-c", inspect.getsource(_test_other_signal_before_sigint_impl) + "\n_test_other_signal_before_sigint_impl(" @@ -173,7 +182,7 @@ def test_other_signal_before_sigint(target, kwargs): proc.wait_for('DRAW') os.kill(proc.pid, signal.SIGUSR1) proc.wait_for('SIGUSR1') - proc.interrupt() + os.kill(proc.pid, signal.SIGINT) stdout, _ = proc.communicate(timeout=_test_timeout) except: proc.kill() From a90f67636707c4c0393acf150f447ca3fed8d5ef Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 24 Jul 2021 01:59:12 -0400 Subject: [PATCH 029/130] Add a release mode for docs This toggles defaults for the image compression, HiDPI images, theme navigation, and analytics. --- doc/conf.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 8c010bd71cba..9e750efad671 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,6 +22,9 @@ from datetime import datetime import time +# Release mode enables optimizations and other related options. +_doc_release_mode = tags.has('release') # noqa + # are we running circle CI? CIRCLECI = 'CIRCLECI' in os.environ @@ -175,10 +178,10 @@ def _check_dependencies(): 'remove_config_comments': True, 'min_reported_time': 1, 'thumbnail_size': (320, 224), - 'compress_images': () if CIRCLECI else ('thumbnails', 'images'), + 'compress_images': ('thumbnails', 'images') if _doc_release_mode else (), 'matplotlib_animations': True, - # 3.7 CI doc build should not use hidpi images during the testing phase - 'image_srcset': [] if sys.version_info[:2] == (3, 7) else ["2x"], + # Doc build should not use hidpi images during the testing phase. + 'image_srcset': ["2x"] if _doc_release_mode else [], 'junit': '../test-results/sphinx-gallery/junit.xml' if CIRCLECI else '', } @@ -293,7 +296,7 @@ def _check_dependencies(): html_logo = "_static/logo2.svg" html_theme_options = { "logo_link": "index", - "collapse_navigation": True if CIRCLECI else False, + "collapse_navigation": not _doc_release_mode, "icon_links": [ { "name": "gitter", @@ -319,7 +322,7 @@ def _check_dependencies(): "show_prev_next": False, "navbar_center": ["mpl_nav_bar.html"], } -include_analytics = False +include_analytics = _doc_release_mode if include_analytics: html_theme_options["google_analytics_id"] = "UA-55954603-1" From db08fccc7c6524ca12e98a76e3fed021fdc4cbf9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 24 Jul 2021 04:13:53 -0400 Subject: [PATCH 030/130] Check that the new sphinx theme is available early --- doc/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/conf.py b/doc/conf.py index 9e750efad671..6636cc796308 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -91,6 +91,7 @@ def _check_dependencies(): "matplotlib": 'matplotlib', "numpydoc": 'numpydoc', "PIL.Image": 'pillow', + "pydata_sphinx_theme": 'pydata_sphinx_theme', "sphinx_copybutton": 'sphinx_copybutton', "sphinx_gallery": 'sphinx_gallery', "sphinxcontrib.inkscapeconverter": 'sphinxcontrib-svg2pdfconverter', From 76d4a3ef38ad968cd8f1c9dacc7d7df2a2bead6b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 24 Jul 2021 04:56:14 -0400 Subject: [PATCH 031/130] DOC: Disable extra plot formats if not release mode --- doc/conf.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 6636cc796308..ed65d780ce95 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -542,6 +542,18 @@ def _check_dependencies(): # graphviz_output_format = 'svg' +def reduce_plot_formats(app): + if app.builder.name == 'html': + keep = 'png' + elif app.builder.name == 'latex': + keep = 'pdf' + else: + return + app.config.plot_formats = [entry + for entry in app.config.plot_formats + if entry[0] == keep] + + def setup(app): if any(st in version for st in ('post', 'alpha', 'beta')): bld_type = 'dev' @@ -549,6 +561,9 @@ def setup(app): bld_type = 'rel' app.add_config_value('releaselevel', bld_type, 'env') + if not _doc_release_mode: + app.connect('builder-inited', reduce_plot_formats) + # ----------------------------------------------------------------------------- # Source code links # ----------------------------------------------------------------------------- From f09213da7cc7763cfcce74adc0bbaba0f09bb9f1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 24 Jul 2021 05:01:29 -0400 Subject: [PATCH 032/130] Update release guide with new release tag --- doc/devel/release_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst index 612ee423590e..423b2ba51b2c 100644 --- a/doc/devel/release_guide.rst +++ b/doc/devel/release_guide.rst @@ -345,7 +345,7 @@ build the docs from the ``ver-doc`` branch. An easy way to arrange this is:: pip install -r requirements/doc/doc-requirements.txt git checkout v2.0.0-doc git clean -xfd - make -Cdoc O="-Ainclude_analytics=True -j$(nproc)" html latexpdf LATEXMKOPTS="-silent -f" + make -Cdoc O="-t release -j$(nproc)" html latexpdf LATEXMKOPTS="-silent -f" which will build both the html and pdf version of the documentation. From 61727eb289895e5523d778042ad602f2e4394682 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 24 Jul 2021 05:08:50 -0400 Subject: [PATCH 033/130] DOC: Set release mode when deploying devdocs only But not for PRs. --- .circleci/config.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4a0f262a4eb7..93335b41bbac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,15 +124,13 @@ commands: command: | # Set epoch to date of latest tag. export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" - # Include analytics only when deploying to devdocs. - if [ "$CIRCLE_PROJECT_USERNAME" != "matplotlib" ] || \ - [ "$CIRCLE_BRANCH" != "master" ] || \ - [[ "$CIRCLE_PULL_REQUEST" == https://github.com/matplotlib/matplotlib/pull/* ]]; then - export ANALYTICS=False - else - export ANALYTICS=True + # Set release mode only when deploying to devdocs. + if [ "$CIRCLE_PROJECT_USERNAME" = "matplotlib" ] && \ + [ "$CIRCLE_BRANCH" = "master" ] && \ + [ "$CIRCLE_PR_NUMBER" = "" ]; then + export RELEASE_TAG='-t release' fi - make html O="-T -Ainclude_analytics=$ANALYTICS" + make html O="-T $RELEASE_TAG" rm -r build/html/_sources working_directory: doc - save_cache: From 3e29c6941a9948760ceae80675af34e09897b9d8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 28 Sep 2021 00:02:57 -0400 Subject: [PATCH 034/130] DOC: Always create HiDPI images --- doc/conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index ed65d780ce95..08a944c39dd2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -181,8 +181,7 @@ def _check_dependencies(): 'thumbnail_size': (320, 224), 'compress_images': ('thumbnails', 'images') if _doc_release_mode else (), 'matplotlib_animations': True, - # Doc build should not use hidpi images during the testing phase. - 'image_srcset': ["2x"] if _doc_release_mode else [], + 'image_srcset': ["2x"], 'junit': '../test-results/sphinx-gallery/junit.xml' if CIRCLECI else '', } From e4f845643b0989ea6604e64181c1802ddc151aae Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:26:39 +0200 Subject: [PATCH 035/130] Update docstrings according to jklymak Apply suggestions from @jklymak code review Co-authored-by: Jody Klymak --- .../next_whats_new/extending_MarkerStyle.rst | 5 ++-- .../marker_reference.py | 14 ++++------ .../multivariate_marker_plot.py | 7 +++-- lib/matplotlib/markers.py | 27 +++++++------------ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst index fbe0fadd7f0c..6e970d0738fe 100644 --- a/doc/users/next_whats_new/extending_MarkerStyle.rst +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -1,9 +1,8 @@ New customization of MarkerStyle -------------------------------- -New MarkerStyle parameters allow control of join style and cap style. -The appearance of individual markers can be further controlled by -transform supplied during creation. +New MarkerStyle parameters allow control of join style and cap style, and for +the user to supply a transformation to be applied to the marker (e.g. a rotation). .. plot:: :include-source: true diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index f751bfb975b8..ca9d1a77470a 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -166,10 +166,8 @@ def split_list(a_list): # Advanced marker modifications with transform # ============================================ # -# All markers can be modified by a user transform in MarkerStyle constructor. -# Supplied transform is combined with the default transforms needed for -# selected marker shape (e.g. caret up, caret down). Following example shows -# how user supplied rotation applies to several marker shapes. +# Markers can be modified by passing a transform to the MarkerStyle constructor. +# Following example shows how a supplied rotation is applied to several marker shapes. common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] @@ -204,9 +202,8 @@ def split_list(a_list): # Setting marker cap style and join style # ======================================= # -# All markers have predefined cap style and join style, but this can be -# overriden during creation of MarkerStyle. Follwing example show how to -# change the cap style and how different styles look. +# Markers have default cap and join styles, but these can be +# customized when creating a MarkerStyle. from matplotlib.markers import JoinStyle, CapStyle @@ -240,8 +237,7 @@ def split_list(a_list): plt.show() ############################################################################### -# Follwing example show how to change the join style and how different styles -# looks like. +# Modifying the join style: fig, ax = plt.subplots() fig.suptitle('Marker JoinStyle', fontsize=14) diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 0a79026b2772..284b21571d59 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -4,10 +4,9 @@ ============================================== This example shows how to use different properties of markers to plot -multivariate datasets. Following example shows an illustrative case of -plotting success of baseball throw as an smiley face with size mapped to -the skill of thrower, rotation mapped to the take-off angle, and thrust -to the color of the marker. +multivariate datasets. Here we represent a successful baseball throw as a smiley +face with marker size mapped to the skill of thrower, marker rotation to the take-off angle, +and thrust to the marker color. """ import numpy as np diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 6a839d040495..1436a8434d60 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,13 +82,9 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) -Markers have some reasonable default settings for join and cap styles. -However, those can be overriden when creating a new instance of MarkerStyle. -Furthermore, the marker shape can be modified by supplying -`~matplotlib.transforms.Transform` during creation. -Some markers are created as internally rotated shapes (e.g. triangles). -For such cases, both internal and user supplied transforms are combined. - +Markers join and cap styles can be customized by creating a new instance of MarkerStyle. +A MarkerStyle can also have a custom +`~matplotlib.transforms.Transform` allowing it to be arbitrarily rotated or offset. Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` @@ -253,16 +249,13 @@ def __init__(self, marker=_unset, fillstyle=None, One of 'full', 'left', 'right', 'bottom', 'top', 'none'. transform : Affine2D, default: None - User supplied transformation that will be combined with the - native transformation of selected marker. + Transform that will be combined with the native transform of the marker. capstyle : CapStyle, default: None - User supplied cap style that will override the default cap - style of selected marker. + Cap style that will override the default cap style of the marker. joinstyle : JoinStyle, default: None - User supplied join style that will override the default cap - style of selected marker. + Join style that will override the default join style of the marker. """ self._marker_function = None self._user_transform = transform @@ -431,7 +424,7 @@ def get_user_transform(self): def transformed(self, transform: Affine2D): """ - Return new marker with combined transformation. + Return a new version of this marker with the transform applied. Parameters ---------- @@ -447,15 +440,15 @@ def transformed(self, transform: Affine2D): def rotated(self, deg=None, rad=None): """ - Return new marker rotated by specified angle. + Return a new version of this marker rotated by specified angle. Parameters ---------- deg : float, default: None - Use this parameter to specify rotation angle in degrees. + Rotation angle in degrees. rad : float, default: None - Use this parameter to specify rotation angle in radians. + Rotation angle in radians. .. note:: You must specify exactly one of deg or rad. """ From 5aa5bbd071acd6285f43c387a6a8538ffa57520a Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:31:55 +0200 Subject: [PATCH 036/130] Make MarkerStyle.rotated more concise Apply suggestions from code review Co-authored-by: Jody Klymak --- lib/matplotlib/markers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 1436a8434d60..a2189ea23d6a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -438,7 +438,7 @@ def transformed(self, transform: Affine2D): new_marker._user_transform = transform return new_marker - def rotated(self, deg=None, rad=None): + def rotated(self, *, deg=None, rad=None): """ Return a new version of this marker rotated by specified angle. @@ -452,9 +452,10 @@ def rotated(self, deg=None, rad=None): .. note:: You must specify exactly one of deg or rad. """ - if not (deg is None) ^ (rad is None): - raise ValueError("Exactly one of deg or rad shall be used.") - + if deg is None and rad is None: + raise ValueError('One of deg or rad is required') + if deg is not None and rad is not None: + raise ValueError('Only one of deg and rad can be supplied') new_marker = MarkerStyle(self) if new_marker._user_transform is None: new_marker._user_transform = Affine2D() From 214ea3f8fc9d6313c857ad6ffe8b13457a1a58d0 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Tue, 28 Sep 2021 10:37:44 +0200 Subject: [PATCH 037/130] Refactor code, linting. --- .../marker_reference.py | 5 +-- .../multivariate_marker_plot.py | 6 ++-- lib/matplotlib/markers.py | 34 +++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index ca9d1a77470a..c4564d2b027d 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -166,8 +166,9 @@ def split_list(a_list): # Advanced marker modifications with transform # ============================================ # -# Markers can be modified by passing a transform to the MarkerStyle constructor. -# Following example shows how a supplied rotation is applied to several marker shapes. +# Markers can be modified by passing a transform to the MarkerStyle +# constructor. Following example shows how a supplied rotation is applied to +# several marker shapes. common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 284b21571d59..487bb2b85fd3 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -4,9 +4,9 @@ ============================================== This example shows how to use different properties of markers to plot -multivariate datasets. Here we represent a successful baseball throw as a smiley -face with marker size mapped to the skill of thrower, marker rotation to the take-off angle, -and thrust to the marker color. +multivariate datasets. Here we represent a successful baseball throw as a +smiley face with marker size mapped to the skill of thrower, marker rotation to +the take-off angle, and thrust to the marker color. """ import numpy as np diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index a2189ea23d6a..85be6d7651bc 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,9 +82,11 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) -Markers join and cap styles can be customized by creating a new instance of MarkerStyle. -A MarkerStyle can also have a custom -`~matplotlib.transforms.Transform` allowing it to be arbitrarily rotated or offset. +Markers join and cap styles can be customized by creating a new instance of +MarkerStyle. +A MarkerStyle can also have a custom `~matplotlib.transforms.Transform` +allowing it to be arbitrarily rotated or offset. + Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` @@ -150,14 +152,6 @@ _empty_path = Path(np.empty((0, 2))) -def _fast_transform_combine(t1, t2): - """Combine two transformations where the second one can be None.""" - if t2 is None: - return t1.frozen() - else: - return (t1 + t2).frozen() - - class MarkerStyle: """ A class representing marker types. @@ -248,8 +242,9 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. - transform : Affine2D, default: None - Transform that will be combined with the native transform of the marker. + transform : transforms.Transform, default: None + Transform that will be combined with the native transform of the + marker. capstyle : CapStyle, default: None Cap style that will override the default cap style of the marker. @@ -395,7 +390,10 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - return _fast_transform_combine(self._transform, self._user_transform) + if self._user_transform is None: + return self._transform.frozen() + else: + return (self._transform + self._user_transform).frozen() def get_alt_path(self): """ @@ -411,8 +409,10 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - return _fast_transform_combine(self._alt_transform, - self._user_transform) + if self._user_transform is None: + return self._alt_transform.frozen() + else: + return (self._alt_transform + self._user_transform).frozen() def get_snap_threshold(self): return self._snap_threshold @@ -424,7 +424,7 @@ def get_user_transform(self): def transformed(self, transform: Affine2D): """ - Return a new version of this marker with the transform applied. + Return a new version of this marker with the transform applied. Parameters ---------- From 7101e3c1216c48e65711a97926ccb56027d06c94 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Tue, 28 Sep 2021 15:30:05 +0200 Subject: [PATCH 038/130] Rework get_cap(join)style, add tests Check for user style in get_cap(join)style is removed. This is also supported with two new tests. Additional test checks that get_transform returns combination of supplied and internal transformation. --- lib/matplotlib/markers.py | 4 ++-- lib/matplotlib/tests/test_marker.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 85be6d7651bc..b1d6fa2be6a4 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -322,10 +322,10 @@ def _set_fillstyle(self, fillstyle): self._recache() def get_joinstyle(self): - return self._user_joinstyle or self._joinstyle + return self._joinstyle def get_capstyle(self): - return self._user_capstyle or self._capstyle + return self._capstyle def get_marker(self): return self._marker diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index d3665d9ce09d..894dead24e84 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -207,6 +207,30 @@ def test_marker_clipping(fig_ref, fig_test): ax_test.axis('off') +def test_marker_init_transforms(): + """Test that initializing marker with transform is a simple addition.""" + marker = markers.MarkerStyle("o") + t = Affine2D().translate(1, 1) + t_marker = markers.MarkerStyle("o", transform=t) + assert marker.get_transform() + t == t_marker.get_transform() + + +def test_marker_init_joinstyle(): + marker = markers.MarkerStyle("*") + jstl = markers.JoinStyle.round + styled_marker = markers.MarkerStyle("*", joinstyle=jstl) + assert styled_marker.get_joinstyle() == jstl + assert marker.get_joinstyle() != jstl + + +def test_marker_init_captyle(): + marker = markers.MarkerStyle("*") + capstl = markers.CapStyle.round + styled_marker = markers.MarkerStyle("*", capstyle=capstl) + assert styled_marker.get_capstyle() == capstl + assert marker.get_capstyle() != capstl + + @pytest.mark.parametrize("marker,transform,expected", [ (markers.MarkerStyle("o"), Affine2D().translate(1, 1), Affine2D().translate(1, 1)), From 629b01a12537fb882e70c0c4d87bcf63eeb4a8e0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 18 Aug 2021 01:17:56 -0400 Subject: [PATCH 039/130] Consolidate What's new for 3.5 --- ...changing_the_vertical_axis_in_3d_plots.rst | 5 - .../next_whats_new/animatable_FancyArrow.rst | 5 - doc/users/next_whats_new/annulus.rst | 17 - doc/users/next_whats_new/arrow_param.rst | 6 - .../next_whats_new/axes3d_computed_zorder.rst | 6 - .../next_whats_new/callback_blocking.rst | 25 - .../next_whats_new/callbacks_on_norms.rst | 8 - .../next_whats_new/collection_offsets.rst | 5 - doc/users/next_whats_new/colormaps.rst | 22 - .../fig_draw_without_rendering.rst | 12 - doc/users/next_whats_new/figure_kwargs.rst | 11 - .../image_interpolation_rgbastage.rst | 21 - .../legend_title_fontproperties_kwarg.rst | 11 - doc/users/next_whats_new/mathmpl_hidpi.rst | 13 - doc/users/next_whats_new/new_arrow_styles.rst | 5 - .../polygons_selector_remove_points.rst | 4 - .../positioning_of_text_inside_TextBox.rst | 13 - doc/users/next_whats_new/rcparams_legend.rst | 25 - doc/users/next_whats_new/set_ticks_labels.rst | 16 - .../setting_artists_properties_selector.rst | 5 - .../simplify_font_setting_usetex.rst | 13 - doc/users/next_whats_new/slider_styling.rst | 42 -- doc/users/next_whats_new/subsetting.rst | 22 - doc/users/next_whats_new/version_info.rst | 15 - doc/users/next_whats_new/widget_clear.rst | 7 - doc/users/next_whats_new/widget_dragging.rst | 12 - .../next_whats_new/widget_ignore_events.rst | 7 - doc/users/prev_whats_new/whats_new_3.5.0.rst | 429 ++++++++++++++++++ doc/users/release_notes.rst | 7 + 29 files changed, 436 insertions(+), 353 deletions(-) delete mode 100644 doc/users/next_whats_new/allow_changing_the_vertical_axis_in_3d_plots.rst delete mode 100644 doc/users/next_whats_new/animatable_FancyArrow.rst delete mode 100644 doc/users/next_whats_new/annulus.rst delete mode 100644 doc/users/next_whats_new/arrow_param.rst delete mode 100644 doc/users/next_whats_new/axes3d_computed_zorder.rst delete mode 100644 doc/users/next_whats_new/callback_blocking.rst delete mode 100644 doc/users/next_whats_new/callbacks_on_norms.rst delete mode 100644 doc/users/next_whats_new/collection_offsets.rst delete mode 100644 doc/users/next_whats_new/colormaps.rst delete mode 100644 doc/users/next_whats_new/fig_draw_without_rendering.rst delete mode 100644 doc/users/next_whats_new/figure_kwargs.rst delete mode 100644 doc/users/next_whats_new/image_interpolation_rgbastage.rst delete mode 100644 doc/users/next_whats_new/legend_title_fontproperties_kwarg.rst delete mode 100644 doc/users/next_whats_new/mathmpl_hidpi.rst delete mode 100644 doc/users/next_whats_new/new_arrow_styles.rst delete mode 100644 doc/users/next_whats_new/polygons_selector_remove_points.rst delete mode 100644 doc/users/next_whats_new/positioning_of_text_inside_TextBox.rst delete mode 100644 doc/users/next_whats_new/rcparams_legend.rst delete mode 100644 doc/users/next_whats_new/set_ticks_labels.rst delete mode 100644 doc/users/next_whats_new/setting_artists_properties_selector.rst delete mode 100644 doc/users/next_whats_new/simplify_font_setting_usetex.rst delete mode 100644 doc/users/next_whats_new/slider_styling.rst delete mode 100644 doc/users/next_whats_new/subsetting.rst delete mode 100644 doc/users/next_whats_new/version_info.rst delete mode 100644 doc/users/next_whats_new/widget_clear.rst delete mode 100644 doc/users/next_whats_new/widget_dragging.rst delete mode 100644 doc/users/next_whats_new/widget_ignore_events.rst create mode 100644 doc/users/prev_whats_new/whats_new_3.5.0.rst diff --git a/doc/users/next_whats_new/allow_changing_the_vertical_axis_in_3d_plots.rst b/doc/users/next_whats_new/allow_changing_the_vertical_axis_in_3d_plots.rst deleted file mode 100644 index fead9f359f56..000000000000 --- a/doc/users/next_whats_new/allow_changing_the_vertical_axis_in_3d_plots.rst +++ /dev/null @@ -1,5 +0,0 @@ -Allow changing the vertical axis in 3d plots ----------------------------------------------- - -`~mpl_toolkits.mplot3d.axes3d.Axes3D.view_init` now has the parameter -*vertical_axis* which allows switching which axis is aligned vertically. diff --git a/doc/users/next_whats_new/animatable_FancyArrow.rst b/doc/users/next_whats_new/animatable_FancyArrow.rst deleted file mode 100644 index 55513fba6db0..000000000000 --- a/doc/users/next_whats_new/animatable_FancyArrow.rst +++ /dev/null @@ -1,5 +0,0 @@ -``set_data`` method for ``FancyArrow`` patch --------------------------------------------- - -`.FancyArrow`, the patch returned by ``ax.arrow``, now has a ``set_data`` -method that allows for animating the arrow. diff --git a/doc/users/next_whats_new/annulus.rst b/doc/users/next_whats_new/annulus.rst deleted file mode 100644 index cdfa875bba2b..000000000000 --- a/doc/users/next_whats_new/annulus.rst +++ /dev/null @@ -1,17 +0,0 @@ -Add ``Annulus`` patch ---------------------- - -`.Annulus` is a new class for drawing elliptical annuli. - -.. plot:: - - import matplotlib.pyplot as plt - from matplotlib.patches import Annulus - - fig, ax = plt.subplots() - cir = Annulus((0.5, 0.5), 0.2, 0.05, fc='g') # circular annulus - ell = Annulus((0.5, 0.5), (0.5, 0.3), 0.1, 45, # elliptical - fc='m', ec='b', alpha=0.5, hatch='xxx') - ax.add_patch(cir) - ax.add_patch(ell) - ax.set_aspect('equal') diff --git a/doc/users/next_whats_new/arrow_param.rst b/doc/users/next_whats_new/arrow_param.rst deleted file mode 100644 index 7e544feb0380..000000000000 --- a/doc/users/next_whats_new/arrow_param.rst +++ /dev/null @@ -1,6 +0,0 @@ -New param ``arrow`` for the ``ArrowStyle`` arrows -------------------------------------------------- - -The new ``arrow`` substitutes the use of ``beginarrow``, ``beginarrow`` -params in the creation of arrows. It receives arrows strings like '<-', -']-[' and ']->'. \ No newline at end of file diff --git a/doc/users/next_whats_new/axes3d_computed_zorder.rst b/doc/users/next_whats_new/axes3d_computed_zorder.rst deleted file mode 100644 index d40d9334988b..000000000000 --- a/doc/users/next_whats_new/axes3d_computed_zorder.rst +++ /dev/null @@ -1,6 +0,0 @@ -Axes3D now allows manual control of draw order ----------------------------------------------- - -The :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D` class now has -``computed_zorder`` parameter. When set to False, Artists are drawn using their -``zorder`` attribute. diff --git a/doc/users/next_whats_new/callback_blocking.rst b/doc/users/next_whats_new/callback_blocking.rst deleted file mode 100644 index 090e06c61bbf..000000000000 --- a/doc/users/next_whats_new/callback_blocking.rst +++ /dev/null @@ -1,25 +0,0 @@ -``CallbackRegistry`` objects gain a method to temporarily block signals ------------------------------------------------------------------------ - -The context manager `~matplotlib.cbook.CallbackRegistry.blocked` can be used -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:: - - import matplotlib.pyplot as plt - - fig, ax = plt.subplots() - ax.imshow([[0, 1], [2, 3]]) - - # Block all interactivity through the canvas callbacks - with fig.canvas.callbacks.blocked(): - plt.show() - - fig, ax = plt.subplots() - ax.imshow([[0, 1], [2, 3]]) - - # Only block key press events - with fig.canvas.callbacks.blocked(signal="key_press_event"): - plt.show() diff --git a/doc/users/next_whats_new/callbacks_on_norms.rst b/doc/users/next_whats_new/callbacks_on_norms.rst deleted file mode 100644 index 1904a92d2fba..000000000000 --- a/doc/users/next_whats_new/callbacks_on_norms.rst +++ /dev/null @@ -1,8 +0,0 @@ -A callback registry has been added to Normalize objects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`.colors.Normalize` objects now have a callback registry, ``callbacks``, -that can be connected to by other objects to be notified when the norm is -updated. The callback emits the key ``changed`` when the norm is modified. -`.cm.ScalarMappable` is now a listener and will register a change -when the norm's vmin, vmax or other attributes are changed. diff --git a/doc/users/next_whats_new/collection_offsets.rst b/doc/users/next_whats_new/collection_offsets.rst deleted file mode 100644 index d53d349a38fb..000000000000 --- a/doc/users/next_whats_new/collection_offsets.rst +++ /dev/null @@ -1,5 +0,0 @@ -Setting collection offset transform after initialization --------------------------------------------------------- -`.collections.Collection.set_offset_transform()` was added. - -Previously the offset transform could not be set after initialization. This can be helpful when creating a `.collections.Collection` outside an axes object and later adding it with `.Axes.add_collection()` and settings the offset transform to `.Axes.transData`. diff --git a/doc/users/next_whats_new/colormaps.rst b/doc/users/next_whats_new/colormaps.rst deleted file mode 100644 index b4e92877b1f0..000000000000 --- a/doc/users/next_whats_new/colormaps.rst +++ /dev/null @@ -1,22 +0,0 @@ -Colormap registry (experimental) --------------------------------- - -Colormaps are now managed via `matplotlib.colormaps` (or `.pyplot.colormaps`), -which is a `.ColormapRegistry`. While we are confident that the API is final, -we formally mark it as experimental for 3.5 because we want to keep the option -to still adapt the API for 3.6 should the need arise. - -Colormaps can be obtained using item access:: - - import matplotlib.pyplot as plt - cmap = plt.colormaps['viridis'] - -To register new colormaps use:: - - plt.colormaps.register(my_colormap) - -We recommend to use the new API instead of the `~.cm.get_cmap` and -`~.cm.register_cmap` functions for new code. `matplotlib.cm.get_cmap` and -`matplotlib.cm.register_cmap` will eventually be deprecated and removed. -Within `.pyplot` ``plt.get_cmap()`` and ``plt.register_cmap()`` will continue -to be supported for backward compatibility. \ No newline at end of file diff --git a/doc/users/next_whats_new/fig_draw_without_rendering.rst b/doc/users/next_whats_new/fig_draw_without_rendering.rst deleted file mode 100644 index 8b9e3147bf07..000000000000 --- a/doc/users/next_whats_new/fig_draw_without_rendering.rst +++ /dev/null @@ -1,12 +0,0 @@ -Figure now has ``draw_without_rendering`` method ------------------------------------------------- - -Some aspects of a figure are only determined at draw-time, such as the exact -position of text artists or deferred computation like automatic data limits. -If you need these values, you can use ``figure.canvas.draw()`` to force a full -draw. However, this has side effects, sometimes requires an open file, and is -doing more work than is needed. - -The new `.Figure.draw_without_rendering` method runs all the updates that -``draw()`` does, but skips rendering the figure. It's thus more efficient if you -need the updated values to configure further aspects of the figure. diff --git a/doc/users/next_whats_new/figure_kwargs.rst b/doc/users/next_whats_new/figure_kwargs.rst deleted file mode 100644 index 738cb65f972d..000000000000 --- a/doc/users/next_whats_new/figure_kwargs.rst +++ /dev/null @@ -1,11 +0,0 @@ - -Figure init passes keyword arguments through to set ---------------------------------------------------- - -Similar to many other sub-classes of `~.Artist`, `~.FigureBase`, `~.SubFigure`, -and `~.Figure` will now pass any additional keyword arguments to `~.Artist.set` -to allow properties of the newly created object to be set at init time. For -example :: - - from matplotlib.figure import Figure - fig = Figure(label='my figure') diff --git a/doc/users/next_whats_new/image_interpolation_rgbastage.rst b/doc/users/next_whats_new/image_interpolation_rgbastage.rst deleted file mode 100644 index 5b95f368e382..000000000000 --- a/doc/users/next_whats_new/image_interpolation_rgbastage.rst +++ /dev/null @@ -1,21 +0,0 @@ -Image interpolation now possible at RGBA stage ----------------------------------------------- - -Images in Matplotlib created via `~.axes.Axes.imshow` are resampled to match -the resolution of the current canvas. It is useful to apply an anto-aliasing -filter when downsampling to reduce Moire effects. By default, interpolation -is done on the data, a norm applied, and then the colormapping performed. - -However, it is often desireable for the anti-aliasing interpolation to happen -in RGBA space, where the colors are interpolated rather than the data. This -usually leads to colors outside the colormap, but visually blends adjacent -colors, and is what browsers and other image processing software does. - -A new keyword argument *interpolation_stage* is provided for -`~.axes.Axes.imshow` to set the stage at which the anti-aliasing interpolation -happens. The default is the current behaviour of "data", with the alternative -being "rgba" for the newly-available behavior. - -For more details see the discussion of the new keyword argument in -:doc:`/gallery/images_contours_and_fields/image_antialiasing`. - diff --git a/doc/users/next_whats_new/legend_title_fontproperties_kwarg.rst b/doc/users/next_whats_new/legend_title_fontproperties_kwarg.rst deleted file mode 100644 index fca6c8b4da8f..000000000000 --- a/doc/users/next_whats_new/legend_title_fontproperties_kwarg.rst +++ /dev/null @@ -1,11 +0,0 @@ -Font properties of legend title are configurable ------------------------------------------------- - -Title's font properties can be set via the *title_fontproperties* keyword -argument, for example: - -.. plot:: - - fig, ax = plt.subplots(figsize=(4, 3)) - ax.plot(range(10),label='point') - ax.legend(title='Points', title_fontproperties={'family': 'serif', 'size': 20}) diff --git a/doc/users/next_whats_new/mathmpl_hidpi.rst b/doc/users/next_whats_new/mathmpl_hidpi.rst deleted file mode 100644 index e90708d5e35d..000000000000 --- a/doc/users/next_whats_new/mathmpl_hidpi.rst +++ /dev/null @@ -1,13 +0,0 @@ -More configuration of ``mathmpl:`` sphinx extension ---------------------------------------------------- - -The `matplotlib.sphinxext.mathmpl` sphinx extension supports two new -configuration options that may be specified in your ``conf.py``: - -- ``mathmpl_fontsize`` (float), which sets the font size of the math text in - points; -- ``mathmpl_srcset`` (list of str), which provides a list of sizes to support - `responsive resolution images - `__ - The list should contain additional x-descriptors (``'1.5x'``, ``'2x'``, etc.) - to generate (1x is the default and always included.) diff --git a/doc/users/next_whats_new/new_arrow_styles.rst b/doc/users/next_whats_new/new_arrow_styles.rst deleted file mode 100644 index d2c73cfb584c..000000000000 --- a/doc/users/next_whats_new/new_arrow_styles.rst +++ /dev/null @@ -1,5 +0,0 @@ -New arrow styles for ``ConnectionPatch`` ----------------------------------------- - -The new ``]->`` and ``<-[`` can be passed as the ``arrowstyle`` -params to the ``ConnectionPatch``. \ No newline at end of file diff --git a/doc/users/next_whats_new/polygons_selector_remove_points.rst b/doc/users/next_whats_new/polygons_selector_remove_points.rst deleted file mode 100644 index 267031d62e96..000000000000 --- a/doc/users/next_whats_new/polygons_selector_remove_points.rst +++ /dev/null @@ -1,4 +0,0 @@ -Removing points on a PolygonSelector ------------------------------------- -After completing a `~matplotlib.widgets.PolygonSelector`, individual -points can now be removed by right-clicking on them. diff --git a/doc/users/next_whats_new/positioning_of_text_inside_TextBox.rst b/doc/users/next_whats_new/positioning_of_text_inside_TextBox.rst deleted file mode 100644 index c52c57efee19..000000000000 --- a/doc/users/next_whats_new/positioning_of_text_inside_TextBox.rst +++ /dev/null @@ -1,13 +0,0 @@ -Text can be positioned inside TextBox widget --------------------------------------------- - -A new parameter called *textalignment* can be used to control for the position of the text inside the Axes of the TextBox widget. - -.. plot:: - - from matplotlib import pyplot as plt - from matplotlib.widgets import TextBox - - box_input = plt.axes([0.2, 0.2, 0.1, 0.075]) - text_box = TextBox(ax=box_input, initial="text", label="", textalignment="center") - diff --git a/doc/users/next_whats_new/rcparams_legend.rst b/doc/users/next_whats_new/rcparams_legend.rst deleted file mode 100644 index bd9c6b3db8c5..000000000000 --- a/doc/users/next_whats_new/rcparams_legend.rst +++ /dev/null @@ -1,25 +0,0 @@ -New rcParams for legend: set legend labelcolor globally -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A new :rc:`legend.labelcolor` sets the default *labelcolor* argument for -`.Figure.legend`. The special values 'linecolor', 'markerfacecolor' -(or 'mfc'), or 'markeredgecolor' (or 'mec') will cause the legend text to match -the corresponding color of marker. - - -.. plot:: - - plt.rcParams['legend.labelcolor'] = 'linecolor' - - # Make some fake data. - a = np.arange(0, 3, .02) - c = np.exp(a) - d = c[::-1] - - fig, ax = plt.subplots() - ax.plot(a, c, 'g--', label='Model length') - ax.plot(a, d, 'r:', label='Data length') - - ax.legend() - - plt.show() \ No newline at end of file diff --git a/doc/users/next_whats_new/set_ticks_labels.rst b/doc/users/next_whats_new/set_ticks_labels.rst deleted file mode 100644 index 7cb6692d5260..000000000000 --- a/doc/users/next_whats_new/set_ticks_labels.rst +++ /dev/null @@ -1,16 +0,0 @@ -Settings tick positions and labels simultaneously in ``set_ticks`` ------------------------------------------------------------------- -`.Axis.set_ticks` (and the corresponding `.Axes.set_xticks` / -`.Axes.set_yticks`) got a new parameter *labels* allowing to set tick positions -and labels simultaneously. - -Previously, setting tick labels was done using `.Axis.set_ticklabels` (or -the corresponding `.Axes.set_xticklabels` / `.Axes.set_yticklabels`). This -usually only makes sense if you previously fix the position with -`~.Axis.set_ticks`. Both functionality is now available in `~.Axis.set_ticks`. -The use of `.Axis.set_ticklabels` is discouraged, but it will stay available -for backward compatibility. - -Note: This addition makes the API of `~.Axis.set_ticks` also more similar to -`.pyplot.xticks` / `.pyplot.yticks`, which already had the additional *labels* -parameter. diff --git a/doc/users/next_whats_new/setting_artists_properties_selector.rst b/doc/users/next_whats_new/setting_artists_properties_selector.rst deleted file mode 100644 index dbf1a4333f0e..000000000000 --- a/doc/users/next_whats_new/setting_artists_properties_selector.rst +++ /dev/null @@ -1,5 +0,0 @@ -Setting artist properties of selectors --------------------------------------- - -The artist properties of selectors can be changed using the ``set_props`` and -``set_handle_props`` methods. diff --git a/doc/users/next_whats_new/simplify_font_setting_usetex.rst b/doc/users/next_whats_new/simplify_font_setting_usetex.rst deleted file mode 100644 index 85618de24416..000000000000 --- a/doc/users/next_whats_new/simplify_font_setting_usetex.rst +++ /dev/null @@ -1,13 +0,0 @@ - -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:: - - plt.rcParams.update({ - "text.usetex": True, - "font.family": "Helvetica" - }) \ No newline at end of file diff --git a/doc/users/next_whats_new/slider_styling.rst b/doc/users/next_whats_new/slider_styling.rst deleted file mode 100644 index f007954b4806..000000000000 --- a/doc/users/next_whats_new/slider_styling.rst +++ /dev/null @@ -1,42 +0,0 @@ -Updated the appearance of Slider widgets ----------------------------------------- - -The appearance of `~.Slider` and `~.RangeSlider` widgets -were updated and given new styling parameters for the -added handles. - -.. plot:: - - import matplotlib.pyplot as plt - from matplotlib.widgets import Slider - - plt.figure(figsize=(4, 2)) - ax_old = plt.axes([0.2, 0.65, 0.65, 0.1]) - ax_new = plt.axes([0.2, 0.25, 0.65, 0.1]) - Slider(ax_new, "New", 0, 1) - - ax = ax_old - valmin = 0 - valinit = 0.5 - ax.set_xlim([0, 1]) - ax_old.axvspan(valmin, valinit, 0, 1) - ax.axvline(valinit, 0, 1, color="r", lw=1) - ax.set_xticks([]) - ax.set_yticks([]) - ax.text( - -0.02, - 0.5, - "Old", - transform=ax.transAxes, - verticalalignment="center", - horizontalalignment="right", - ) - - ax.text( - 1.02, - 0.5, - "0.5", - transform=ax.transAxes, - verticalalignment="center", - horizontalalignment="left", - ) diff --git a/doc/users/next_whats_new/subsetting.rst b/doc/users/next_whats_new/subsetting.rst deleted file mode 100644 index 89c33f58d371..000000000000 --- a/doc/users/next_whats_new/subsetting.rst +++ /dev/null @@ -1,22 +0,0 @@ -Type 42 Subsetting is now enabled for PDF/PS backends ------------------------------------------------------ - -`~matplotlib.backends.backend_pdf` and `~matplotlib.backends.backend_ps` now use -a unified Type 42 font subsetting interface, with the help of `fontTools `_ - -Set `~matplotlib.RcParams`'s *fonttype* value as ``42`` to trigger this workflow: - -.. code-block:: - - # for PDF backend - plt.rcParams['pdf.fonttype'] = 42 - - # for PS backend - plt.rcParams['ps.fonttype'] = 42 - - - fig, ax = plt.subplots() - ax.text(0.4, 0.5, 'subsetted document is smaller in size!') - - fig.savefig("document.pdf") - fig.savefig("document.ps") diff --git a/doc/users/next_whats_new/version_info.rst b/doc/users/next_whats_new/version_info.rst deleted file mode 100644 index 5c51f9fe7332..000000000000 --- a/doc/users/next_whats_new/version_info.rst +++ /dev/null @@ -1,15 +0,0 @@ -Version information -------------------- -We switched to the `release-branch-semver`_ version scheme. This only affects, -the version information for development builds. Their version number now -describes the targeted release, i.e. 3.5.0.dev820+g6768ef8c4c.d20210520 -is 820 commits after the previous release and is scheduled to be officially -released as 3.5.0 later. - -In addition to the string ``__version__``, there is now a namedtuple -``__version_info__`` as well, which is modelled after `sys.version_info`_. -Its primary use is safely comparing version information, e.g. -``if __version_info__ >= (3, 4, 2)``. - -.. _release-branch-semver: https://github.com/pypa/setuptools_scm#version-number-construction -.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info \ No newline at end of file diff --git a/doc/users/next_whats_new/widget_clear.rst b/doc/users/next_whats_new/widget_clear.rst deleted file mode 100644 index 3c72dd60cc52..000000000000 --- a/doc/users/next_whats_new/widget_clear.rst +++ /dev/null @@ -1,7 +0,0 @@ -Clear selector --------------- -The selectors (`~matplotlib.widgets.SpanSelector`, `~matplotlib.widgets.RectangleSelector`, -`~matplotlib.widgets.EllipseSelector`, `~matplotlib.widgets.PolygonSelector` and -`~matplotlib.widgets.LassoSelector` have a new method *clear*, which will clear -the current selection and get the selector ready to make a new selection. This -is equivalent to press the *escape* key. diff --git a/doc/users/next_whats_new/widget_dragging.rst b/doc/users/next_whats_new/widget_dragging.rst deleted file mode 100644 index 174a9ced77d4..000000000000 --- a/doc/users/next_whats_new/widget_dragging.rst +++ /dev/null @@ -1,12 +0,0 @@ -Dragging selectors ------------------- - -The `~matplotlib.widgets.SpanSelector`, `~matplotlib.widgets.RectangleSelector` -and `~matplotlib.widgets.EllipseSelector` have a new keyword argument, -*drag_from_anywhere*, which when set to `True` allows you to click and drag -from anywhere inside the selector to move it. Previously it was only possible -to move it by either activating the move modifier button, or clicking on the -central handle. - -The size of the `~matplotlib.widgets.SpanSelector` can now be changed using -the edge handles. diff --git a/doc/users/next_whats_new/widget_ignore_events.rst b/doc/users/next_whats_new/widget_ignore_events.rst deleted file mode 100644 index 2d90e65ad5a7..000000000000 --- a/doc/users/next_whats_new/widget_ignore_events.rst +++ /dev/null @@ -1,7 +0,0 @@ -Ignore events outside selection -------------------------------- -The `~matplotlib.widgets.SpanSelector`, `~matplotlib.widgets.RectangleSelector` -and `~matplotlib.widgets.EllipseSelector` have a new keyword argument, -*ignore_event_outside*, which when set to `True` will ignore events outside of -the current selection. The handles or the new dragging functionality can instead -be used to change the selection. 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 new file mode 100644 index 000000000000..f48aa1104041 --- /dev/null +++ b/doc/users/prev_whats_new/whats_new_3.5.0.rst @@ -0,0 +1,429 @@ +============================== +What's new in Matplotlib 3.5.0 +============================== + +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 + +Figure and Axes creation / management +===================================== + +Figure now has ``draw_without_rendering`` method +------------------------------------------------ + +Some aspects of a figure are only determined at draw-time, such as the exact +position of text artists or deferred computation like automatic data limits. +If you need these values, you can use ``figure.canvas.draw()`` to force a full +draw. However, this has side effects, sometimes requires an open file, and is +doing more work than is needed. + +The new `.Figure.draw_without_rendering` method runs all the updates that +``draw()`` does, but skips rendering the figure. It's thus more efficient if +you need the updated values to configure further aspects of the figure. + +Figure ``__init__`` passes keyword arguments through to set +----------------------------------------------------------- + +Similar to many other sub-classes of `~.Artist`, the `~.FigureBase`, +`~.SubFigure`, and `~.Figure` classes will now pass any additional keyword +arguments to `~.Artist.set` to allow properties of the newly created object to +be set at initialization time. For example:: + + from matplotlib.figure import Figure + fig = Figure(label='my figure') + +Plotting methods +================ + +Add ``Annulus`` patch +--------------------- + +`.Annulus` is a new class for drawing elliptical annuli. + +.. plot:: + + import matplotlib.pyplot as plt + from matplotlib.patches import Annulus + + fig, ax = plt.subplots() + cir = Annulus((0.5, 0.5), 0.2, 0.05, fc='g') # circular annulus + ell = Annulus((0.5, 0.5), (0.5, 0.3), 0.1, 45, # elliptical + fc='m', ec='b', alpha=0.5, hatch='xxx') + ax.add_patch(cir) + ax.add_patch(ell) + ax.set_aspect('equal') + +``set_data`` method for ``FancyArrow`` patch +-------------------------------------------- + +`.FancyArrow`, the patch returned by ``ax.arrow``, now has a ``set_data`` +method that allows modifying the arrow after creation, e.g., for animation. + +New arrow styles in ``ArrowStyle`` and ``ConnectionPatch`` +---------------------------------------------------------- + +The new *arrow* parameter to `.ArrowStyle` substitutes the use of the +*beginarrow* and *endarrow* parameters in the creation of arrows. It receives +arrows strings like ``'<-'``, ``']-[``' and ``']->``' instead of individual +booleans. + +Two new styles ``']->'`` and ``'<-['`` are also added via this mechanism. +`.ConnectionPatch`, which accepts arrow styles though its *arrowstyle* +parameter, also accepts these new styles. + +Setting collection offset transform after initialization +-------------------------------------------------------- + +The added `.collections.Collection.set_offset_transform` may be used to set the +offset transform after initialization. This can be helpful when creating a +`.collections.Collection` outside an Axes object and later adding it with +`.Axes.add_collection()` and settings the offset transform to +`.Axes.transData`. + +Colors and colormaps +==================== + +Colormap registry (experimental) +-------------------------------- + +Colormaps are now managed via `matplotlib.colormaps` (or `.pyplot.colormaps`), +which is a `.ColormapRegistry`. While we are confident that the API is final, +we formally mark it as experimental for 3.5 because we want to keep the option +to still adapt the API for 3.6 should the need arise. + +Colormaps can be obtained using item access:: + + import matplotlib.pyplot as plt + cmap = plt.colormaps['viridis'] + +To register new colormaps use:: + + plt.colormaps.register(my_colormap) + +We recommend to use the new API instead of the `~.cm.get_cmap` and +`~.cm.register_cmap` functions for new code. `matplotlib.cm.get_cmap` and +`matplotlib.cm.register_cmap` will eventually be deprecated and removed. +Within `.pyplot`, ``plt.get_cmap()`` and ``plt.register_cmap()`` will continue +to be supported for backward compatibility. + +Image interpolation now possible at RGBA stage +---------------------------------------------- + +Images in Matplotlib created via `~.axes.Axes.imshow` are resampled to match +the resolution of the current canvas. It is useful to apply an anto-aliasing +filter when downsampling to reduce Moiré effects. By default, interpolation is +done on the data, a norm applied, and then the colormapping performed. + +However, it is often desireable for the anti-aliasing interpolation to happen +in RGBA space, where the colors are interpolated rather than the data. This +usually leads to colors outside the colormap, but visually blends adjacent +colors, and is what browsers and other image processing software do. + +A new keyword argument *interpolation_stage* is provided for +`~.axes.Axes.imshow` to set the stage at which the anti-aliasing interpolation +happens. The default is the current behaviour of "data", with the alternative +being "rgba" for the newly-available behavior. + +For more details see the discussion of the new keyword argument in +:doc:`/gallery/images_contours_and_fields/image_antialiasing`. + +A callback registry has been added to Normalize objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`.colors.Normalize` objects now have a callback registry, ``callbacks``, that +can be connected to by other objects to be notified when the norm is updated. +The callback emits the key ``changed`` when the norm is modified. +`.cm.ScalarMappable` is now a listener and will register a change when the +norm's vmin, vmax or other attributes are changed. + +Titles, ticks, and labels +========================= + +Settings tick positions and labels simultaneously in ``set_ticks`` +------------------------------------------------------------------ + +`.Axis.set_ticks` (and the corresponding `.Axes.set_xticks` / +`.Axes.set_yticks`) has a new parameter *labels* allowing to set tick positions +and labels simultaneously. + +Previously, setting tick labels was done using `.Axis.set_ticklabels` (or +the corresponding `.Axes.set_xticklabels` / `.Axes.set_yticklabels`). This +usually only makes sense if tick positions were previously fixed with +`~.Axis.set_ticks`. The combined functionality is now available in +`~.Axis.set_ticks`. The use of `.Axis.set_ticklabels` is discouraged, but it +will stay available for backward compatibility. + +Note: This addition makes the API of `~.Axis.set_ticks` also more similar to +`.pyplot.xticks` / `.pyplot.yticks`, which already had the additional *labels* +parameter. + +Fonts and Text +============== + +Font properties of legend title are configurable +------------------------------------------------ + +Title's font properties can be set via the *title_fontproperties* keyword +argument, for example: + +.. plot:: + + fig, ax = plt.subplots(figsize=(4, 3)) + ax.plot(range(10), label='point') + ax.legend(title='Points', + title_fontproperties={'family': 'serif', 'size': 20}) + +Text can be positioned inside TextBox widget +-------------------------------------------- + +A new parameter called *textalignment* can be used to control for the position +of the text inside the Axes of the `.TextBox` widget. + +.. plot:: + + from matplotlib import pyplot as plt + from matplotlib.widgets import TextBox + + for i, alignment in enumerate(['left', 'center', 'right']): + box_input = plt.axes([0.2, 0.7 - i*0.2, 0.6, 0.1]) + text_box = TextBox(ax=box_input, initial=f'{alignment} alignment', + label='', textalignment=alignment) + +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:: + + plt.rcParams.update({ + "text.usetex": True, + "font.family": "Helvetica" + }) + +Type 42 Subsetting is now enabled for PDF/PS backends +----------------------------------------------------- + +`~matplotlib.backends.backend_pdf` and `~matplotlib.backends.backend_ps` now +use a unified Type 42 font subsetting interface, with the help of `fontTools +`_ + +Set :rc:`pdf.fonttype` or :rc:`ps.fonttype` to ``42`` to trigger this +workflow:: + + # for PDF backend + plt.rcParams['pdf.fonttype'] = 42 + + # for PS backend + plt.rcParams['ps.fonttype'] = 42 + + fig, ax = plt.subplots() + ax.text(0.4, 0.5, 'subsetted document is smaller in size!') + + fig.savefig("document.pdf") + fig.savefig("document.ps") + +rcParams improvements +===================== + +Allow setting default legend labelcolor globally +------------------------------------------------ + +A new :rc:`legend.labelcolor` sets the default *labelcolor* argument for +`.Figure.legend`. The special values 'linecolor', 'markerfacecolor' (or +'mfc'), or 'markeredgecolor' (or 'mec') will cause the legend text to match the +corresponding color of marker. + + +.. plot:: + + plt.rcParams['legend.labelcolor'] = 'linecolor' + + # Make some fake data. + a = np.arange(0, 3, .02) + c = np.exp(a) + d = c[::-1] + + fig, ax = plt.subplots() + ax.plot(a, c, 'g--', label='Model length') + ax.plot(a, d, 'r:', label='Data length') + + ax.legend() + + plt.show() + +3D Axes improvements +==================== + +Axes3D now allows manual control of draw order +---------------------------------------------- + +The `~mpl_toolkits.mplot3d.axes3d.Axes3D` class now has *computed_zorder* +parameter. When set to False, Artists are drawn using their ``zorder`` +attribute. + +Allow changing the vertical axis in 3d plots +---------------------------------------------- + +`~mpl_toolkits.mplot3d.axes3d.Axes3D.view_init` now has the parameter +*vertical_axis* which allows switching which axis is aligned vertically. + +Interactive tool improvements +============================= + +Updated the appearance of Slider widgets +---------------------------------------- + +The appearance of `~.Slider` and `~.RangeSlider` widgets were updated and given +new styling parameters for the added handles. + +.. plot:: + + import matplotlib.pyplot as plt + from matplotlib.widgets import Slider + + plt.figure(figsize=(4, 2)) + ax_old = plt.axes([0.2, 0.65, 0.65, 0.1]) + ax_new = plt.axes([0.2, 0.25, 0.65, 0.1]) + Slider(ax_new, "New", 0, 1) + + ax = ax_old + valmin = 0 + valinit = 0.5 + ax.set_xlim([0, 1]) + ax_old.axvspan(valmin, valinit, 0, 1) + ax.axvline(valinit, 0, 1, color="r", lw=1) + ax.set_xticks([]) + ax.set_yticks([]) + ax.text( + -0.02, + 0.5, + "Old", + transform=ax.transAxes, + verticalalignment="center", + horizontalalignment="right", + ) + + ax.text( + 1.02, + 0.5, + "0.5", + transform=ax.transAxes, + verticalalignment="center", + horizontalalignment="left", + ) + +Removing points on a PolygonSelector +------------------------------------ + +After completing a `~matplotlib.widgets.PolygonSelector`, individual points can +now be removed by right-clicking on them. + +Dragging selectors +------------------ + +The `~matplotlib.widgets.SpanSelector`, `~matplotlib.widgets.RectangleSelector` +and `~matplotlib.widgets.EllipseSelector` have a new keyword argument, +*drag_from_anywhere*, which when set to `True` allows you to click and drag +from anywhere inside the selector to move it. Previously it was only possible +to move it by either activating the move modifier button, or clicking on the +central handle. + +The size of the `~matplotlib.widgets.SpanSelector` can now be changed using the +edge handles. + +Clearing selectors +------------------ + +The selectors (`~.widgets.EllipseSelector`, `~.widgets.LassoSelector`, +`~.widgets.PolygonSelector`, `~.widgets.RectangleSelector`, and +`~.widgets.SpanSelector`) have a new method *clear*, which will clear the +current selection and get the selector ready to make a new selection. This is +equivalent to pressing the *escape* key. + +Setting artist properties of selectors +-------------------------------------- + +The artist properties of the `~.widgets.EllipseSelector`, +`~.widgets.LassoSelector`, `~.widgets.PolygonSelector`, +`~.widgets.RectangleSelector` and `~.widgets.SpanSelector` selectors can be +changed using the ``set_props`` and ``set_handle_props`` methods. + +Ignore events outside selection +------------------------------- + +The `~.widgets.EllipseSelector`, `~.widgets.RectangleSelector` and +`~.widgets.SpanSelector` selectors have a new keyword argument, +*ignore_event_outside*, which when set to `True` will ignore events outside of +the current selection. The handles or the new dragging functionality can instead +be used to change the selection. + +``CallbackRegistry`` objects gain a method to temporarily block signals +----------------------------------------------------------------------- + +The context manager `~matplotlib.cbook.CallbackRegistry.blocked` can be used +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:: + + import matplotlib.pyplot as plt + + fig, ax = plt.subplots() + ax.imshow([[0, 1], [2, 3]]) + + # Block all interactivity through the canvas callbacks + with fig.canvas.callbacks.blocked(): + plt.show() + + fig, ax = plt.subplots() + ax.imshow([[0, 1], [2, 3]]) + + # Only block key press events + with fig.canvas.callbacks.blocked(signal="key_press_event"): + plt.show() + +Sphinx extensions +================= + +More configuration of ``mathmpl`` sphinx extension +-------------------------------------------------- + +The `matplotlib.sphinxext.mathmpl` sphinx extension supports two new +configuration options that may be specified in your ``conf.py``: + +- ``mathmpl_fontsize`` (float), which sets the font size of the math text in + points; +- ``mathmpl_srcset`` (list of str), which provides a list of sizes to support + `responsive resolution images + `__ + The list should contain additional x-descriptors (``'1.5x'``, ``'2x'``, etc.) + to generate (1x is the default and always included.) + +Backend-specific improvements +============================= + +Version information +=================== + +We switched to the `release-branch-semver`_ version scheme. This only affects, +the version information for development builds. Their version number now +describes the targeted release, i.e. 3.5.0.dev820+g6768ef8c4c.d20210520 is 820 +commits after the previous release and is scheduled to be officially released +as 3.5.0 later. + +In addition to the string ``__version__``, there is now a namedtuple +``__version_info__`` as well, which is modelled after `sys.version_info`_. Its +primary use is safely comparing version information, e.g. ``if +__version_info__ >= (3, 4, 2)``. + +.. _release-branch-semver: https://github.com/pypa/setuptools_scm#version-number-construction +.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index d8458e5ba1c2..bf4c23ac3486 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -9,6 +9,13 @@ Release notes .. include from another document so that it's easy to exclude this for releases .. include:: release_notes_next.rst +Version 3.5 +=========== +.. toctree:: + :maxdepth: 1 + + prev_whats_new/whats_new_3.5.0.rst + Version 3.4 =========== .. toctree:: From e89695dc4e3354c9e7a31b0a23d7f8a2533fdc12 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 24 Sep 2021 01:14:19 -0400 Subject: [PATCH 040/130] Consolidate development API changes for 3.5 --- .../next_api_changes/development/20003-ES.rst | 15 ------ .../next_api_changes/development/20391-AG.rst | 8 ---- .../next_api_changes/development/20706-AP.rst | 4 -- .../development/20840-RJS.rst | 13 ----- .../next_api_changes/development/XXXXX-AL.rst | 8 ---- .../prev_api_changes/api_changes_3.5.0.rst | 8 ++++ .../api_changes_3.5.0/development.rst | 48 +++++++++++++++++++ doc/users/release_notes.rst | 1 + 8 files changed, 57 insertions(+), 48 deletions(-) delete mode 100644 doc/api/next_api_changes/development/20003-ES.rst delete mode 100644 doc/api/next_api_changes/development/20391-AG.rst delete mode 100644 doc/api/next_api_changes/development/20706-AP.rst delete mode 100644 doc/api/next_api_changes/development/20840-RJS.rst delete mode 100644 doc/api/next_api_changes/development/XXXXX-AL.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.5.0.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.5.0/development.rst diff --git a/doc/api/next_api_changes/development/20003-ES.rst b/doc/api/next_api_changes/development/20003-ES.rst deleted file mode 100644 index 48cd21bf4878..000000000000 --- a/doc/api/next_api_changes/development/20003-ES.rst +++ /dev/null @@ -1,15 +0,0 @@ -Increase to minimum supported versions of dependencies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For Maptlotlib 3.5, the :ref:`minimum supported versions ` are -being bumped: - -+------------+-----------------+---------------+ -| Dependency | min in mpl3.4 | min in mpl3.5 | -+============+=================+===============+ -| NumPy | 1.16 | 1.17 | -+------------+-----------------+---------------+ - -This is consistent with our :ref:`min_deps_policy` and `NEP29 -`__ - diff --git a/doc/api/next_api_changes/development/20391-AG.rst b/doc/api/next_api_changes/development/20391-AG.rst deleted file mode 100644 index 37cc539c5ad2..000000000000 --- a/doc/api/next_api_changes/development/20391-AG.rst +++ /dev/null @@ -1,8 +0,0 @@ -fontTools for type 42 subsetting -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A new dependency known as `fontTools `_ -is integrated in with Maptlotlib 3.5 - -It is designed to be used with PS/EPS and PDF documents; and handles -Type 42 font subsetting. diff --git a/doc/api/next_api_changes/development/20706-AP.rst b/doc/api/next_api_changes/development/20706-AP.rst deleted file mode 100644 index 4c46c020f2cb..000000000000 --- a/doc/api/next_api_changes/development/20706-AP.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improve underscore support in LaTeX -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `underscore `_ package is now a requirement to improve support for underscores in LaTeX. diff --git a/doc/api/next_api_changes/development/20840-RJS.rst b/doc/api/next_api_changes/development/20840-RJS.rst deleted file mode 100644 index 443331d7c887..000000000000 --- a/doc/api/next_api_changes/development/20840-RJS.rst +++ /dev/null @@ -1,13 +0,0 @@ -Increase to minimum supported optional dependencies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For Matplotlib 3.5, the :ref:`minimum supported versions of optional dependencies -` are being bumped: - -+------------+-----------------+---------------+ -| Dependency | min in mpl3.4 | min in mpl3.5 | -+============+=================+===============+ -| Tk | 8.3 | 8.4 | -+------------+-----------------+---------------+ - -This is consistent with our :ref:`min_deps_policy` diff --git a/doc/api/next_api_changes/development/XXXXX-AL.rst b/doc/api/next_api_changes/development/XXXXX-AL.rst deleted file mode 100644 index 330dfc9e7300..000000000000 --- a/doc/api/next_api_changes/development/XXXXX-AL.rst +++ /dev/null @@ -1,8 +0,0 @@ -Matplotlib-specific build options have moved from ``setup.cfg`` to ``mplsetup.cfg`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... in order to avoid conflicting with the use of :file:`setup.cfg` by -``setuptools``. - -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. diff --git a/doc/api/prev_api_changes/api_changes_3.5.0.rst b/doc/api/prev_api_changes/api_changes_3.5.0.rst new file mode 100644 index 000000000000..64957ad03965 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.5.0.rst @@ -0,0 +1,8 @@ +API Changes for 3.5.0 +===================== + +.. contents:: + :local: + :depth: 1 + +.. include:: /api/prev_api_changes/api_changes_3.5.0/development.rst 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 new file mode 100644 index 000000000000..d86bc5e344a9 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.5.0/development.rst @@ -0,0 +1,48 @@ +Development changes +------------------- + +Increase to minimum supported versions of dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Matplotlib 3.5, the :ref:`minimum supported versions ` and +some :ref:`optional dependencies ` are being bumped: + ++---------------+---------------+---------------+ +| Dependency | min in mpl3.4 | min in mpl3.5 | ++===============+===============+===============+ +| NumPy | 1.16 | 1.17 | ++---------------+---------------+---------------+ +| Tk (optional) | 8.3 | 8.4 | ++---------------+---------------+---------------+ + +This is consistent with our :ref:`min_deps_policy` and `NEP29 +`__ + +New runtime dependencies +~~~~~~~~~~~~~~~~~~~~~~~~ + +fontTools for type 42 subsetting +................................ + +A new dependency `fontTools `_ is integrated +into Matplotlib 3.5. It is designed to be used with PS/EPS and PDF documents; +and handles Type 42 font subsetting. + +Underscore support in LaTeX +........................... + +The `underscore `_ package is now a +requirement to improve support for underscores in LaTeX. + +This is consistent with our :ref:`min_deps_policy`. + +Matplotlib-specific build options moved from ``setup.cfg`` to ``mplsetup.cfg`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to avoid conflicting with the use of :file:`setup.cfg` by +``setuptools``, the Matplotlib-specific build options have moved from +``setup.cfg`` to ``mplsetup.cfg`` + +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. diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index bf4c23ac3486..4608dbc93977 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -15,6 +15,7 @@ Version 3.5 :maxdepth: 1 prev_whats_new/whats_new_3.5.0.rst + ../api/prev_api_changes/api_changes_3.5.0.rst Version 3.4 =========== From 2c8e125db1cc610a9fb16e03bafcb2c19950ffa9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 24 Sep 2021 03:27:21 -0400 Subject: [PATCH 041/130] Consolidate API behaviour changes for 3.5 --- .../next_api_changes/behavior/14913-AL.rst | 9 - .../next_api_changes/behavior/18216-ES.rst | 30 --- .../next_api_changes/behavior/19375-AL.rst | 2 - .../next_api_changes/behavior/19515-GL.rst | 10 - .../next_api_changes/behavior/20012-AL.rst | 5 - .../next_api_changes/behavior/20027-AL.rst | 3 - .../next_api_changes/behavior/20046-BB.rst | 25 -- .../next_api_changes/behavior/20054-JMK.rst | 8 - .../next_api_changes/behavior/20064-AL.rst | 5 - .../next_api_changes/behavior/20077-TH.rst | 7 - .../next_api_changes/behavior/20150-TAC.rst | 10 - .../next_api_changes/behavior/20199-AL.rst | 5 - .../next_api_changes/behavior/20268-JMK.rst | 11 - .../next_api_changes/behavior/20367-AG.rst | 12 - .../next_api_changes/behavior/20405-JMK.rst | 8 - .../next_api_changes/behavior/20634-JKS.rst | 8 - .../next_api_changes/behavior/20692-AL.rst | 3 - .../next_api_changes/behavior/20699-AFV.rst | 31 --- .../next_api_changes/behavior/21031-AL.rst | 3 - .../next_api_changes/behavior/21038-DS.rst | 4 - .../deprecations/18216-ES.rst | 2 +- .../prev_api_changes/api_changes_3.5.0.rst | 2 + .../api_changes_3.5.0/behaviour.rst | 236 ++++++++++++++++++ 23 files changed, 239 insertions(+), 200 deletions(-) delete mode 100644 doc/api/next_api_changes/behavior/14913-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/18216-ES.rst delete mode 100644 doc/api/next_api_changes/behavior/19375-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/19515-GL.rst delete mode 100644 doc/api/next_api_changes/behavior/20012-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/20027-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/20046-BB.rst delete mode 100644 doc/api/next_api_changes/behavior/20064-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/20077-TH.rst delete mode 100644 doc/api/next_api_changes/behavior/20150-TAC.rst delete mode 100644 doc/api/next_api_changes/behavior/20199-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/20268-JMK.rst delete mode 100644 doc/api/next_api_changes/behavior/20367-AG.rst delete mode 100644 doc/api/next_api_changes/behavior/20405-JMK.rst delete mode 100644 doc/api/next_api_changes/behavior/20634-JKS.rst delete mode 100644 doc/api/next_api_changes/behavior/20692-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/20699-AFV.rst delete mode 100644 doc/api/next_api_changes/behavior/21031-AL.rst delete mode 100644 doc/api/next_api_changes/behavior/21038-DS.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst diff --git a/doc/api/next_api_changes/behavior/14913-AL.rst b/doc/api/next_api_changes/behavior/14913-AL.rst deleted file mode 100644 index 9e98b7e6c8ec..000000000000 --- a/doc/api/next_api_changes/behavior/14913-AL.rst +++ /dev/null @@ -1,9 +0,0 @@ -The output of ``NonUniformImage`` and ``PcolorImage`` has changed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pixel-level differences may be observed in images generated using -`.NonUniformImage` or `.PcolorImage`, typically for pixels exactly at the -boundary between two data cells (no user-facing axes method currently generates -`.NonUniformImage`\s, and only `.pcolorfast` can generate `.PcolorImage`\s). -These artists are also now slower, normally by ~1.5x but sometimes more (in -particular for ``NonUniformImage(interpolation="bilinear")``. This slowdown -arises from fixing occasional floating point inaccuracies. diff --git a/doc/api/next_api_changes/behavior/18216-ES.rst b/doc/api/next_api_changes/behavior/18216-ES.rst deleted file mode 100644 index d6e6cae4b55d..000000000000 --- a/doc/api/next_api_changes/behavior/18216-ES.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. _Behavioral API Changes 3.5 - Axes children combined: - -``Axes`` children are no longer separated by type -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Formerly, `.axes.Axes` children were separated by `.Artist` type, into sublists -such as ``Axes.lines``. For methods that produced multiple elements (such as -`.Axes.errorbar`), though individual parts would have similar *zorder*, this -separation might cause them to be drawn at different times, causing -inconsistent results when overlapping other Artists. - -Now, the children are no longer separated by type, and the sublist properties -are generated dynamically when accessed. Consequently, Artists will now always -appear in the correct sublist; e.g., if `.axes.Axes.add_line` is called on a -`.Patch`, it will be appear in the ``Axes.patches`` sublist, _not_ -``Axes.lines``. The ``Axes.add_*`` methods will now warn if passed an -unexpected type. - -Modification of the following sublists is still accepted, but deprecated: - -* ``Axes.artists`` -* ``Axes.collections`` -* ``Axes.images`` -* ``Axes.lines`` -* ``Axes.patches`` -* ``Axes.tables`` -* ``Axes.texts`` - -To remove an Artist, use its `.Artist.remove` method. To add an Artist, use the -corresponding ``Axes.add_*`` method. diff --git a/doc/api/next_api_changes/behavior/19375-AL.rst b/doc/api/next_api_changes/behavior/19375-AL.rst deleted file mode 100644 index c543f57d3818..000000000000 --- a/doc/api/next_api_changes/behavior/19375-AL.rst +++ /dev/null @@ -1,2 +0,0 @@ -``Figure.suppressComposite`` now also controls compositing of Axes images -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/api/next_api_changes/behavior/19515-GL.rst b/doc/api/next_api_changes/behavior/19515-GL.rst deleted file mode 100644 index cb6d925b797c..000000000000 --- a/doc/api/next_api_changes/behavior/19515-GL.rst +++ /dev/null @@ -1,10 +0,0 @@ -Colorbars now have pan and zoom functionality -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Interactive plots with colorbars can now be zoomed and panned on -the colorbar axis. This adjusts the *vmin* and *vmax* of the -``ScalarMappable`` associated with the colorbar. This is currently -only enabled for continuous norms. Norms used with contourf and -categoricals, such as ``BoundaryNorm`` and ``NoNorm``, have the -interactive capability disabled by default. ``cb.ax.set_navigate()`` -can be used to set whether a colorbar axes is interactive or not. diff --git a/doc/api/next_api_changes/behavior/20012-AL.rst b/doc/api/next_api_changes/behavior/20012-AL.rst deleted file mode 100644 index 78615239e17c..000000000000 --- a/doc/api/next_api_changes/behavior/20012-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -Default theta tick locations for non-full-circle polar plots have changed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For polar plots that don't cover a full circle, the default theta tick -locations are now at multiples of 10°, 15°, 30°, 45°, 90°, rather than using -values that mostly make sense for linear plots (20°, 25°, etc.). diff --git a/doc/api/next_api_changes/behavior/20027-AL.rst b/doc/api/next_api_changes/behavior/20027-AL.rst deleted file mode 100644 index 59e5063a6c1b..000000000000 --- a/doc/api/next_api_changes/behavior/20027-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``axvspan`` now plots full wedges in polar plots -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... rather than triangles. diff --git a/doc/api/next_api_changes/behavior/20046-BB.rst b/doc/api/next_api_changes/behavior/20046-BB.rst deleted file mode 100644 index 717a42b84603..000000000000 --- a/doc/api/next_api_changes/behavior/20046-BB.rst +++ /dev/null @@ -1,25 +0,0 @@ -``MatplotlibDeprecationWarning`` now subclasses ``DeprecationWarning`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Historically, it has not been possible to filter -``MatplotlibDeprecationWarning``\s by checking for ``DeprecationWarning``, -since we subclass ``UserWarning`` directly. - -The decision to not subclass DeprecationWarning has to do with a decision from -core Python in the 2.x days to not show DeprecationWarnings to users. However, -there is now a more sophisticated filter in place (see -https://www.python.org/dev/peps/pep-0565/). - -Users will now see ``MatplotlibDeprecationWarning`` only during interactive -sessions, and these can be silenced by the standard mechanism: - -.. code:: python - - warnings.filterwarnings("ignore", category=DeprecationWarning) - -Library authors must now enable ``DeprecationWarning``\s explicitly in order -for (non-interactive) CI/CD pipelines to report back these warnings, as is -standard for the rest of the Python ecosystem: - -.. code:: python - - warnings.filterwarnings("always", DeprecationWarning) diff --git a/doc/api/next_api_changes/behavior/20054-JMK.rst b/doc/api/next_api_changes/behavior/20054-JMK.rst index 0b9fb193df64..875dd7570475 100644 --- a/doc/api/next_api_changes/behavior/20054-JMK.rst +++ b/doc/api/next_api_changes/behavior/20054-JMK.rst @@ -1,11 +1,3 @@ -Colorbar lines no longer clipped -================================ - -If a colorbar has lines added to it (e.g. for contour lines), these will -no longer be clipped. This is an improvement for lines on the edge of -the colorbar, but could lead to lines off the colorbar if the limits of -the colorbar are changed. - ``Colorbar.patch`` is deprecated ================================ diff --git a/doc/api/next_api_changes/behavior/20064-AL.rst b/doc/api/next_api_changes/behavior/20064-AL.rst deleted file mode 100644 index 858d311a86c6..000000000000 --- a/doc/api/next_api_changes/behavior/20064-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -``AxesDivider`` now defaults to rcParams-specified pads -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`.AxesDivider.append_axes`, `.AxesDivider.new_horizontal`, and -`.AxesDivider.new_vertical` now default to paddings specified by -:rc:`figure.subplot.wspace` and :rc:`figure.subplot.hspace` rather than zero. diff --git a/doc/api/next_api_changes/behavior/20077-TH.rst b/doc/api/next_api_changes/behavior/20077-TH.rst deleted file mode 100644 index 63dac85d795b..000000000000 --- a/doc/api/next_api_changes/behavior/20077-TH.rst +++ /dev/null @@ -1,7 +0,0 @@ -``Artist.set`` applies artist properties in the order in which they are given -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The change only affects the interaction between the *color*, *edgecolor*, -*facecolor*, and, for `.Collection`\s, *alpha* properties: the *color* property -now needs to be passed first in order not to override the other properties. -This is consistent with e.g. `.Artist.update`, which did not reorder the -properties passed to it. diff --git a/doc/api/next_api_changes/behavior/20150-TAC.rst b/doc/api/next_api_changes/behavior/20150-TAC.rst deleted file mode 100644 index e5109d9afa43..000000000000 --- a/doc/api/next_api_changes/behavior/20150-TAC.rst +++ /dev/null @@ -1,10 +0,0 @@ -Rename fist arg to subplot_mosaic -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Both `.FigureBase.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* -keyword argument which would collide. - -As this API is provisional, we are changing this with no deprecation period. diff --git a/doc/api/next_api_changes/behavior/20199-AL.rst b/doc/api/next_api_changes/behavior/20199-AL.rst deleted file mode 100644 index deb1cb2893be..000000000000 --- a/doc/api/next_api_changes/behavior/20199-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -``Line2D.set_markeredgecolor(None)`` and ``Line2D.set_markerfacecolor(None)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... now set the line property using the corresponding rcParam -(:rc:`lines.markeredgecolor` and :rc:`lines.markerfacecolor`). This is -consistent with other `.Line2D` property setters. diff --git a/doc/api/next_api_changes/behavior/20268-JMK.rst b/doc/api/next_api_changes/behavior/20268-JMK.rst deleted file mode 100644 index e0ff20bfcc82..000000000000 --- a/doc/api/next_api_changes/behavior/20268-JMK.rst +++ /dev/null @@ -1,11 +0,0 @@ -pcolor(mesh) shading defaults to auto -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The *shading* kwarg for `.Axes.pcolormesh` and `.Axes.pcolor` default -has been changed to 'auto'. - -Passing ``Z(M, N)``, ``x(N)``, ``y(M)`` to ``pcolormesh`` with -``shading='flat'`` will now raise a ``TypeError``. Use -``shading='auto'`` or ``shading='nearest'`` for ``x`` and ``y`` -to be treated as cell centers, or drop the last column and row -of ``Z`` to get the old behavior with ``shading='flat'``. \ No newline at end of file diff --git a/doc/api/next_api_changes/behavior/20367-AG.rst b/doc/api/next_api_changes/behavior/20367-AG.rst deleted file mode 100644 index 38bc9ac05ad7..000000000000 --- a/doc/api/next_api_changes/behavior/20367-AG.rst +++ /dev/null @@ -1,12 +0,0 @@ -``Text`` allows a boolean *parse_math* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`.Text` objects now allow a *parse_math* keyword-only argument -which defaults to True. When it is passed as False, that text object -will consider the string as a literal and will not try to parse it -as a mathtext object. - -``TextBox`` defaults it to False --------------------------------- -`.TextBox` now defaults its text object's *parse_math** argument -as False. diff --git a/doc/api/next_api_changes/behavior/20405-JMK.rst b/doc/api/next_api_changes/behavior/20405-JMK.rst deleted file mode 100644 index 30af063e61ad..000000000000 --- a/doc/api/next_api_changes/behavior/20405-JMK.rst +++ /dev/null @@ -1,8 +0,0 @@ -Make norm from scale now public -=============================== - -Downstream libraries can take advantage of `.colors.make_norm_from_scale` -to create a `~.colors.Normalize` subclass directly from an existing scale. -Usually norms have a scale, and the advantage of having a `~.scale.ScaleBase` -attached to a norm is to provide a scale, and associated tick locators and -formatters, for the colorbar. \ No newline at end of file diff --git a/doc/api/next_api_changes/behavior/20634-JKS.rst b/doc/api/next_api_changes/behavior/20634-JKS.rst deleted file mode 100644 index ff4046445e42..000000000000 --- a/doc/api/next_api_changes/behavior/20634-JKS.rst +++ /dev/null @@ -1,8 +0,0 @@ -``Type1Font`` objects now decrypt the encrypted part ----------------------------------------------------- - -Type 1 fonts have a large part of their code encrypted as an obsolete -copy-protection measure. This part is now available decrypted as the -``decrypted`` attribute of :class:`~matplotlib.type1font.Type1Font`. -This decrypted data is not yet parsed, but this is a prerequisite for -implementing subsetting. diff --git a/doc/api/next_api_changes/behavior/20692-AL.rst b/doc/api/next_api_changes/behavior/20692-AL.rst deleted file mode 100644 index 1070fb55c41e..000000000000 --- a/doc/api/next_api_changes/behavior/20692-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``hatch.SmallFilledCircles`` now inherits from ``hatch.Circles`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... rather than from ``hatch.SmallCircles``. diff --git a/doc/api/next_api_changes/behavior/20699-AFV.rst b/doc/api/next_api_changes/behavior/20699-AFV.rst deleted file mode 100644 index 9852290baa96..000000000000 --- a/doc/api/next_api_changes/behavior/20699-AFV.rst +++ /dev/null @@ -1,31 +0,0 @@ -Change of the (default) legend handler for Line2D instances ------------------------------------------------------------ - -The default legend handler for Line2D instances (`.HandlerLine2D`) now -consistently exposes all the attributes and methods related to the line -marker (:ghissue:`11358`). This makes easy to change the marker features -after instantiating a legend. - -.. code:: - - import matplotlib.pyplot as plt - - fig, ax = plt.subplots() - - ax.plot([1, 3, 2], marker="s", label="Line", color="pink", mec="red", ms=8) - leg = ax.legend() - - leg.legendHandles[0].set_color("lightgray") - leg.legendHandles[0].set_mec("black") # marker edge color - -The former legend handler for Line2D objects has been renamed -`.HandlerLine2DCompound`. To revert to the previous behavior, one can use - -.. code:: - - import matplotlib.legend as mlegend - from matplotlib.legend_handler import HandlerLine2DCompound - from matplotlib.lines import Line2D - - mlegend.Legend.update_default_handler_map({Line2D: HandlerLine2DCompound()}) - diff --git a/doc/api/next_api_changes/behavior/21031-AL.rst b/doc/api/next_api_changes/behavior/21031-AL.rst deleted file mode 100644 index ec1a80f9478a..000000000000 --- a/doc/api/next_api_changes/behavior/21031-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -Setting invalid ``rcParams["date.converter"]`` now raises ValueError -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Previously, invalid values would be ignored with a UserWarning. diff --git a/doc/api/next_api_changes/behavior/21038-DS.rst b/doc/api/next_api_changes/behavior/21038-DS.rst deleted file mode 100644 index 55ca3fb7597d..000000000000 --- a/doc/api/next_api_changes/behavior/21038-DS.rst +++ /dev/null @@ -1,4 +0,0 @@ -hexbin with a log norm ----------------------- -`~.axes.Axes.hexbin` no longer (incorrectly) adds 1 to every bin value if a -log norm is being used. diff --git a/doc/api/next_api_changes/deprecations/18216-ES.rst b/doc/api/next_api_changes/deprecations/18216-ES.rst index 56fa58c65c39..88a2614f9dde 100644 --- a/doc/api/next_api_changes/deprecations/18216-ES.rst +++ b/doc/api/next_api_changes/deprecations/18216-ES.rst @@ -1,7 +1,7 @@ Modification of ``Axes`` children sublists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -See :ref:`Behavioral API Changes 3.5 - Axes children combined` for more +See :ref:`Behavioural API Changes 3.5 - Axes children combined` for more information; modification of the following sublists is deprecated: * ``Axes.artists`` diff --git a/doc/api/prev_api_changes/api_changes_3.5.0.rst b/doc/api/prev_api_changes/api_changes_3.5.0.rst index 64957ad03965..a17bffa8a79b 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0.rst @@ -5,4 +5,6 @@ API Changes for 3.5.0 :local: :depth: 1 +.. include:: /api/prev_api_changes/api_changes_3.5.0/behaviour.rst + .. include:: /api/prev_api_changes/api_changes_3.5.0/development.rst 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 new file mode 100644 index 000000000000..7e1fdefc4936 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.5.0/behaviour.rst @@ -0,0 +1,236 @@ +Behaviour changes +----------------- + +First argument to ``subplot_mosaic`` renamed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Both `.FigureBase.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 +argument, the original ``subplot_mosaic`` argument name would collide. + +As this API is provisional, we are changing this argument name with no +deprecation period. + +.. _Behavioural API Changes 3.5 - Axes children combined: + +``Axes`` children are no longer separated by type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Formerly, `.axes.Axes` children were separated by `.Artist` type, into sublists +such as ``Axes.lines``. For methods that produced multiple elements (such as +`.Axes.errorbar`), though individual parts would have similar *zorder*, this +separation might cause them to be drawn at different times, causing +inconsistent results when overlapping other Artists. + +Now, the children are no longer separated by type, and the sublist properties +are generated dynamically when accessed. Consequently, Artists will now always +appear in the correct sublist; e.g., if `.axes.Axes.add_line` is called on a +`.Patch`, it will appear in the ``Axes.patches`` sublist, *not* ``Axes.lines``. +The ``Axes.add_*`` methods will now warn if passed an unexpected type. + +Modification of the following sublists is still accepted, but deprecated: + +* ``Axes.artists`` +* ``Axes.collections`` +* ``Axes.images`` +* ``Axes.lines`` +* ``Axes.patches`` +* ``Axes.tables`` +* ``Axes.texts`` + +To remove an Artist, use its `.Artist.remove` method. To add an Artist, use the +corresponding ``Axes.add_*`` method. + +``MatplotlibDeprecationWarning`` now subclasses ``DeprecationWarning`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Historically, it has not been possible to filter +`.MatplotlibDeprecationWarning`\s by checking for `DeprecationWarning`, since we +subclass `UserWarning` directly. + +The decision to not subclass `DeprecationWarning` has to do with a decision +from core Python in the 2.x days to not show `DeprecationWarning`\s to users. +However, there is now a more sophisticated filter in place (see +https://www.python.org/dev/peps/pep-0565/). + +Users will now see `.MatplotlibDeprecationWarning` only during interactive +sessions, and these can be silenced by the standard mechanism: + +.. code:: python + + warnings.filterwarnings("ignore", category=DeprecationWarning) + +Library authors must now enable `DeprecationWarning`\s explicitly in order for +(non-interactive) CI/CD pipelines to report back these warnings, as is standard +for the rest of the Python ecosystem: + +.. code:: python + + warnings.filterwarnings("always", DeprecationWarning) + +``Artist.set`` applies artist properties in the order in which they are given +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The change only affects the interaction between the *color*, *edgecolor*, +*facecolor*, and (for `.Collection`\s) *alpha* properties: the *color* property +now needs to be passed first in order not to override the other properties. +This is consistent with e.g. `.Artist.update`, which did not reorder the +properties passed to it. + +``pcolor(mesh)`` shading defaults to auto +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The *shading* keyword argument for `.Axes.pcolormesh` and `.Axes.pcolor` +default has been changed to 'auto'. + +Passing ``Z(M, N)``, ``x(N)``, ``y(M)`` to ``pcolormesh`` with +``shading='flat'`` will now raise a `TypeError`. Use ``shading='auto'`` or +``shading='nearest'`` for ``x`` and ``y`` to be treated as cell centers, or +drop the last column and row of ``Z`` to get the old behaviour with +``shading='flat'``. + +Colorbars now have pan and zoom functionality +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Interactive plots with colorbars can now be zoomed and panned on the colorbar +axis. This adjusts the *vmin* and *vmax* of the `.ScalarMappable` associated +with the colorbar. This is currently only enabled for continuous norms. Norms +used with ``contourf`` and categoricals, such as `.BoundaryNorm` and `.NoNorm`, +have the interactive capability disabled by default. `cb.ax.set_navigate() +<.Axes.set_navigate>` can be used to set whether a colorbar axes is interactive +or not. + +Colorbar lines no longer clipped +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a colorbar has lines added to it (e.g. for contour lines), these will no +longer be clipped. This is an improvement for lines on the edge of the +colorbar, but could lead to lines off the colorbar if the limits of the +colorbar are changed. + +``Figure.suppressComposite`` now also controls compositing of Axes images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The output of ``NonUniformImage`` and ``PcolorImage`` has changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pixel-level differences may be observed in images generated using +`.NonUniformImage` or `.PcolorImage`, typically for pixels exactly at the +boundary between two data cells (no user-facing axes method currently generates +`.NonUniformImage`\s, and only `.pcolorfast` can generate `.PcolorImage`\s). +These artists are also now slower, normally by ~1.5x but sometimes more (in +particular for ``NonUniformImage(interpolation="bilinear")``. This slowdown +arises from fixing occasional floating point inaccuracies. + +Change of the (default) legend handler for ``Line2D`` instances +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default legend handler for Line2D instances (`.HandlerLine2D`) now +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:: + + import matplotlib.pyplot as plt + + fig, ax = plt.subplots() + + ax.plot([1, 3, 2], marker="s", label="Line", color="pink", mec="red", ms=8) + leg = ax.legend() + + leg.legendHandles[0].set_color("lightgray") + leg.legendHandles[0].set_mec("black") # marker edge color + +The former legend handler for Line2D objects has been renamed +`.HandlerLine2DCompound`. To revert to the previous behaviour, one can use + +.. code:: + + import matplotlib.legend as mlegend + from matplotlib.legend_handler import HandlerLine2DCompound + from matplotlib.lines import Line2D + + mlegend.Legend.update_default_handler_map({Line2D: HandlerLine2DCompound()}) + +Setting ``Line2D`` marker edge/face color to *None* use rcParams +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Line2D.set_markeredgecolor(None)`` and ``Line2D.set_markerfacecolor(None)`` +now set the line property using the corresponding rcParam +(:rc:`lines.markeredgecolor` and :rc:`lines.markerfacecolor`). This is +consistent with other `.Line2D` property setters. + +Default theta tick locations for wedge polar plots have changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For polar plots that don't cover a full circle, the default theta tick +locations are now at multiples of 10°, 15°, 30°, 45°, 90°, rather than using +values that mostly make sense for linear plots (20°, 25°, etc.). + +``axvspan`` now plots full wedges in polar plots +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... rather than triangles. + +Convenience converter from ``Scale`` to ``Normalize`` now public +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Downstream libraries can take advantage of `.colors.make_norm_from_scale` to +create a `~.colors.Normalize` subclass directly from an existing scale. +Usually norms have a scale, and the advantage of having a `~.scale.ScaleBase` +attached to a norm is to provide a scale, and associated tick locators and +formatters, for the colorbar. + +``hatch.SmallFilledCircles`` inherits from ``hatch.Circles`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``hatch.SmallFilledCircles`` class now inherits from ``hatch.Circles`` +rather than from ``hatch.SmallCircles``. + +hexbin with a log norm +~~~~~~~~~~~~~~~~~~~~~~ + +`~.axes.Axes.hexbin` no longer (incorrectly) adds 1 to every bin value if a log +norm is being used. + +Setting invalid ``rcParams["date.converter"]`` now raises ValueError +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, invalid values passed to :rc:`date.converter` would be ignored with +a `UserWarning`, but now raise `ValueError`. + +``Text`` and ``TextBox`` added *parse_math* option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`.Text` and `.TextBox` objects now allow a *parse_math* keyword-only argument +which controls whether math should be parsed from the displayed string. If +*True*, the string will be parsed as a math text object. If *False*, the string +will be considered a literal and no parsing will occur. + +For `.Text`, this argument defaults to *True*. For `.TextBox` this argument +defaults to *False*. + +``Type1Font`` objects now decrypt the encrypted part +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Type 1 fonts have a large part of their code encrypted as an obsolete +copy-protection measure. This part is now available decrypted as the +``decrypted`` attribute of `~.type1font.Type1Font`. This decrypted data is not +yet parsed, but this is a prerequisite for implementing subsetting. + +3D contourf polygons placed between levels +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The polygons used in a 3D `~mpl_toolkits.mplot3d.Axes3D.contourf` plot are now +placed halfway between the contour levels, as each polygon represents the +location of values that lie between two levels. + +``AxesDivider`` now defaults to rcParams-specified pads +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`.AxesDivider.append_axes`, `.AxesDivider.new_horizontal`, and +`.AxesDivider.new_vertical` now default to paddings specified by +:rc:`figure.subplot.wspace` and :rc:`figure.subplot.hspace` rather than zero. From 18b99313c2a1c586cc90096ae161a3b0ce24241d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 25 Sep 2021 00:16:22 -0400 Subject: [PATCH 042/130] Consolidate API removals for 3.5 --- .../next_api_changes/removals/19033-AL.rst | 6 - .../next_api_changes/removals/19348-OE.rst | 6 - .../next_api_changes/removals/19552-GL.rst | 5 - .../next_api_changes/removals/19795-AL.rst | 11 - .../next_api_changes/removals/19796-AL.rst | 3 - .../next_api_changes/removals/19801-AL.rst | 3 - .../next_api_changes/removals/19810-AL.rst | 10 - .../next_api_changes/removals/19894-ES.rst | 15 - .../next_api_changes/removals/19898-ES.rst | 70 ---- .../next_api_changes/removals/19900-ES.rst | 27 -- .../next_api_changes/removals/19901-ES.rst | 22 -- .../next_api_changes/removals/19922-ES.rst | 7 - .../next_api_changes/removals/20051-AL.rst | 8 - .../next_api_changes/removals/20052-AL.rst | 20 - .../next_api_changes/removals/20095-ES.rst | 20 - .../next_api_changes/removals/20188-ES.rst | 10 - .../next_api_changes/removals/20245-GL.rst | 7 - .../next_api_changes/removals/20314-GL.rst | 11 - .../next_api_changes/removals/20331-ES.rst | 63 --- .../next_api_changes/removals/20447-ES.rst | 58 --- .../next_api_changes/removals/20465-ES.rst | 93 ----- .../next_api_changes/removals/20563-TH.rst | 4 - .../prev_api_changes/api_changes_3.5.0.rst | 2 + .../api_changes_3.5.0/removals.rst | 365 ++++++++++++++++++ 24 files changed, 367 insertions(+), 479 deletions(-) delete mode 100644 doc/api/next_api_changes/removals/19033-AL.rst delete mode 100644 doc/api/next_api_changes/removals/19348-OE.rst delete mode 100644 doc/api/next_api_changes/removals/19552-GL.rst delete mode 100644 doc/api/next_api_changes/removals/19795-AL.rst delete mode 100644 doc/api/next_api_changes/removals/19796-AL.rst delete mode 100644 doc/api/next_api_changes/removals/19801-AL.rst delete mode 100644 doc/api/next_api_changes/removals/19810-AL.rst delete mode 100644 doc/api/next_api_changes/removals/19894-ES.rst delete mode 100644 doc/api/next_api_changes/removals/19898-ES.rst delete mode 100644 doc/api/next_api_changes/removals/19900-ES.rst delete mode 100644 doc/api/next_api_changes/removals/19901-ES.rst delete mode 100644 doc/api/next_api_changes/removals/19922-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20051-AL.rst delete mode 100644 doc/api/next_api_changes/removals/20052-AL.rst delete mode 100644 doc/api/next_api_changes/removals/20095-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20188-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20245-GL.rst delete mode 100644 doc/api/next_api_changes/removals/20314-GL.rst delete mode 100644 doc/api/next_api_changes/removals/20331-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20447-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20465-ES.rst delete mode 100644 doc/api/next_api_changes/removals/20563-TH.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.5.0/removals.rst diff --git a/doc/api/next_api_changes/removals/19033-AL.rst b/doc/api/next_api_changes/removals/19033-AL.rst deleted file mode 100644 index 258950c1b86a..000000000000 --- a/doc/api/next_api_changes/removals/19033-AL.rst +++ /dev/null @@ -1,6 +0,0 @@ -The private ``matplotlib.axes._subplots._subplot_classes`` dict has been removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Support for passing ``None`` to ``subplot_class_factory`` has been removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Explicitly pass in the base `~matplotlib.axes.Axes` class instead. diff --git a/doc/api/next_api_changes/removals/19348-OE.rst b/doc/api/next_api_changes/removals/19348-OE.rst deleted file mode 100644 index 4045b7e3d02b..000000000000 --- a/doc/api/next_api_changes/removals/19348-OE.rst +++ /dev/null @@ -1,6 +0,0 @@ -Removed ``dates.YearLocator.replaced`` attribute -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`.YearLocator` is now a subclass of `.RRuleLocator`, and the attribute -``YearLocator.replaced`` has been removed. For tick locations that -required modifying this, a custom rrule and `.RRuleLocator` can be used instead. \ No newline at end of file diff --git a/doc/api/next_api_changes/removals/19552-GL.rst b/doc/api/next_api_changes/removals/19552-GL.rst deleted file mode 100644 index be0bebd3d89e..000000000000 --- a/doc/api/next_api_changes/removals/19552-GL.rst +++ /dev/null @@ -1,5 +0,0 @@ -ScalarMappable update checkers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``ScalarMappable.update_dict``, ``ScalarMappable.add_checker()``, and -``ScalarMappable.check_update()`` have been removed. A callback can -be registered in ``ScalarMappable.callbacks`` to be notified of updates. \ No newline at end of file diff --git a/doc/api/next_api_changes/removals/19795-AL.rst b/doc/api/next_api_changes/removals/19795-AL.rst deleted file mode 100644 index dba29fabdcfc..000000000000 --- a/doc/api/next_api_changes/removals/19795-AL.rst +++ /dev/null @@ -1,11 +0,0 @@ -usetex-related removals -~~~~~~~~~~~~~~~~~~~~~~~ - -The ``text.latex.preview`` rcParam and associated methods -(``TexManager.make_tex_preview``, ``TexManager.make_dvi_preview``) have been -removed. - -The ``cachedir``, ``rgba_arrayd``, ``serif``, ``sans_serif``, ``cursive``, and -``monospace`` attributes of ``TexManager`` have been removed. - -``dviread.Encoding`` has been removed. diff --git a/doc/api/next_api_changes/removals/19796-AL.rst b/doc/api/next_api_changes/removals/19796-AL.rst deleted file mode 100644 index d2e51427093a..000000000000 --- a/doc/api/next_api_changes/removals/19796-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -Keymaps toggling ``Axes.get_navigate`` have been removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This includes numeric key events and the ``keymap.all_axes`` rcParam. diff --git a/doc/api/next_api_changes/removals/19801-AL.rst b/doc/api/next_api_changes/removals/19801-AL.rst deleted file mode 100644 index 6451e335309a..000000000000 --- a/doc/api/next_api_changes/removals/19801-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -The ``mathtext.fallback_to_cm`` rcParams -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... has been removed. Use :rc:`mathtext.fallback` instead. diff --git a/doc/api/next_api_changes/removals/19810-AL.rst b/doc/api/next_api_changes/removals/19810-AL.rst deleted file mode 100644 index 2b641a2e622f..000000000000 --- a/doc/api/next_api_changes/removals/19810-AL.rst +++ /dev/null @@ -1,10 +0,0 @@ -jpeg-related keywords and rcParams -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Support has been removed for the *quality*, *optimize*, and *progressive* -parameters of `.Figure.savefig` (which only affected jpeg output), as well as -:rc:`savefig.jpeg_quality`. This support has also been removed from the -corresponding ``print_jpg`` methods. - -JPEG output options can be set by directly passing the relevant parameters in -*pil_kwargs*. diff --git a/doc/api/next_api_changes/removals/19894-ES.rst b/doc/api/next_api_changes/removals/19894-ES.rst deleted file mode 100644 index a18fb1cb8206..000000000000 --- a/doc/api/next_api_changes/removals/19894-ES.rst +++ /dev/null @@ -1,15 +0,0 @@ -Qt4-based backends -~~~~~~~~~~~~~~~~~~ -The qt4agg and qt4cairo backends have been removed. Qt4 has reached its -end-of-life in 2015 and there are no releases of either PyQt4 or PySide for -recent versions of Python. Please use one of the Qt5 or Qt6 backends. - -``qt_compat.is_pyqt5`` -~~~~~~~~~~~~~~~~~~~~~~ -This function has been removed due to the release of PyQt6. The Qt version can -be checked using ``QtCore.qVersion()``. - -``matplotlib.backends.qt_editor.formsubplottool`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This module has been removed. Use -``matplotlib.backends.backend_qt5.SubplotToolQt`` instead. diff --git a/doc/api/next_api_changes/removals/19898-ES.rst b/doc/api/next_api_changes/removals/19898-ES.rst deleted file mode 100644 index 9ae0a64c1cca..000000000000 --- a/doc/api/next_api_changes/removals/19898-ES.rst +++ /dev/null @@ -1,70 +0,0 @@ -NavigationToolbar2._init_toolbar -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Overriding this method to initialize third-party toolbars is no longer -supported. Instead, the toolbar should be initialized in the ``__init__`` -method of the subclass (which should call the base-class' ``__init__`` as -appropriate). - -NavigationToolbar2.press and .release -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These methods were called when pressing or releasing a mouse button, but *only* -when an interactive pan or zoom was occurring (contrary to what the docs -stated). They are no longer called; if you write a backend which needs to -customize such events, please directly override -``press_pan``/``press_zoom``/``release_pan``/``release_zoom`` instead. - -NavigationToolbar2QT.parent and .basedir -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These attributes have been removed. In order to access the parent window, use -``toolbar.canvas.parent()`` or ``toolbar.parent()``. The base directory to the -icons is ``os.path.join(mpl.get_data_path(), "images")``. - -NavigationToolbar2QT.ctx -~~~~~~~~~~~~~~~~~~~~~~~~ -This attribute has been removed. - -NavigationToolbar2Wx attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``prevZoomRect``, ``retinaFix``, ``savedRetinaImage``, ``wxoverlay``, -``zoomAxes``, ``zoomStartX``, and ``zoomStartY`` attributes have been removed. - -Statusbar classes and attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``statusbar`` attribute of `.FigureManagerBase`, as well as -``backend_bases.StatusbarBase`` and all its subclasses, and ``StatusBarWx``, -have been removed, as messages are now displayed in the toolbar instead. - -Backend-specific internals -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following module-level classes/variables have been removed: - -* ``MODIFIER_KEYS``, ``SUPER``, ``ALT``, ``CTRL``, and ``SHIFT`` of - :mod:`matplotlib.backends.backend_qt5agg` and - :mod:`matplotlib.backends.backend_qt5cairo` -* ``backend_pgf.GraphicsContextPgf`` -* ``backend_wx.DEBUG_MSG`` - -The following parameters have been removed: - -* The *dummy* parameter of `.RendererPgf` - -The following class attributes have been removed: - -* ``RendererCairo.fontweights``, ``RendererCairo.fontangles`` -* ``backend_pgf.LatexManager.latex_stdin_utf8`` -* ``backend_pgf.PdfPages.metadata`` -* ``used_characters`` of `.RendererPdf`, `.PdfFile`, and `.RendererPS` - -The following class methods have been removed: - -* ``FigureCanvasGTK3._renderer_init`` -* ``track_characters`` and ``merge_used_characters`` of `.RendererPdf`, - `.PdfFile`, and `.RendererPS` -* ``RendererWx.get_gc`` - -Stricter PDF metadata keys in PGF -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Saving metadata in PDF with the PGF backend no longer changes keys to -lowercase. Only the canonically cased keys listed in the PDF specification (and -the `~.backend_pgf.PdfPages` documentation) are accepted. diff --git a/doc/api/next_api_changes/removals/19900-ES.rst b/doc/api/next_api_changes/removals/19900-ES.rst deleted file mode 100644 index 7cb4bc13c5a6..000000000000 --- a/doc/api/next_api_changes/removals/19900-ES.rst +++ /dev/null @@ -1,27 +0,0 @@ -The ``TTFPATH`` and ``AFMPATH`` environment variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Support for the (undocumented) ``TTFPATH`` and ``AFMPATH`` environment -variables has been removed. Register additional fonts using -``matplotlib.font_manager.fontManager.addfont()``. - -mathtext ``Glue`` classes -~~~~~~~~~~~~~~~~~~~~~~~~~ -The following have been removed from `matplotlib.mathtext`: - -* *copy* parameter of ``mathtext.Glue`` -* ``mathtext.Glue.glue_subtype`` -* ``mathtext.GlueSpec`` -* ``Fil``, ``Fill``, ``Filll``, ``NegFil``, ``NegFill``, ``NegFilll``, and - ``SsGlue``; directly construct glue instances with ``Glue("fil")``, etc. - -*ismath* parameter of ``draw_tex`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *ismath* parameter of the ``draw_tex`` method of all renderer classes has -been removed (as a call to ``draw_tex`` -- not to be confused with -``draw_text``! -- means that the entire string should be passed to the -``usetex`` machinery anyways). Likewise, the text machinery will no longer pass -the *ismath* parameter when calling ``draw_tex`` (this should only matter for -backend implementers). - -Passing ``ismath="TeX!"`` to `.RendererAgg.get_text_width_height_descent` is no -longer supported; pass ``ismath="TeX"`` instead, diff --git a/doc/api/next_api_changes/removals/19901-ES.rst b/doc/api/next_api_changes/removals/19901-ES.rst deleted file mode 100644 index 523f8fa0e304..000000000000 --- a/doc/api/next_api_changes/removals/19901-ES.rst +++ /dev/null @@ -1,22 +0,0 @@ -Deprecated rcParams validators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following validators, defined in `.rcsetup`, have been removed: -``validate_alignment``, ``validate_axes_titlelocation``, -``validate_axis_locator``, ``validate_bool_maybe_none``, ``validate_fontset``, -``validate_grid_axis``, ``validate_hinting``, ``validate_legend_loc``, -``validate_mathtext_default``, ``validate_movie_frame_fmt``, -``validate_movie_html_fmt``, ``validate_movie_writer``, -``validate_nseq_float``, ``validate_nseq_int``, ``validate_orientation``, -``validate_pgf_texsystem``, ``validate_ps_papersize``, -``validate_svg_fontset``, ``validate_toolbar``, ``validate_webagg_address``. - -Stricter rcParam validation -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:rc:`axes.axisbelow` no longer accepts strings starting with "line" -(case-insensitive) as "line"; use "line" (case-sensitive) instead. - -The :rc:`text.latex.preamble` and :rc:`pdf.preamble` no longer accept -non-string values. - -All ``*.linestyle`` rcParams no longer accept ``offset = None``; set the offset -to 0 instead. diff --git a/doc/api/next_api_changes/removals/19922-ES.rst b/doc/api/next_api_changes/removals/19922-ES.rst deleted file mode 100644 index 9b73570c2cbd..000000000000 --- a/doc/api/next_api_changes/removals/19922-ES.rst +++ /dev/null @@ -1,7 +0,0 @@ -Removed modules -~~~~~~~~~~~~~~~ - -The following deprecated modules have been removed: - -* ``matplotlib.compat`` -* ``matplotlib.ttconv`` diff --git a/doc/api/next_api_changes/removals/20051-AL.rst b/doc/api/next_api_changes/removals/20051-AL.rst deleted file mode 100644 index c3679a3aaf2d..000000000000 --- a/doc/api/next_api_changes/removals/20051-AL.rst +++ /dev/null @@ -1,8 +0,0 @@ -Removal of deprecated ``offsetbox`` APIs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following APIs were removed: - -- the *s* kwarg to `.AnnotationBbox.get_fontsize` (which had no effect), -- the ``DraggableBase.artist_picker`` method (set the artist's picker instead), -- the ``DraggableBase.on_motion_blit`` method (use `.DraggableBase.on_motion` - instead). diff --git a/doc/api/next_api_changes/removals/20052-AL.rst b/doc/api/next_api_changes/removals/20052-AL.rst deleted file mode 100644 index a250cb2bdc9a..000000000000 --- a/doc/api/next_api_changes/removals/20052-AL.rst +++ /dev/null @@ -1,20 +0,0 @@ -Removal of ``axes_grid``/``axisartist`` APIs deprecated in Matplotlib 3.3 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following deprecated APIs have been removed: - -- ``CbarAxesBase.cbid`` and ``CbarAxesBase.locator`` (use - ``mappable.colorbar_cid`` and ``colorbar.locator`` instead), -- the ``add_all`` parameter to ``axes_grid.Grid``, ``axes_grid.ImageGrid``, - ``axes_rgb.make_rgb_axes``, ``axes_rgb.RGBAxes`` (the APIs always behave as - if ``add_all=True``), -- ``axes_rgb.imshow_rgb`` (use ``imshow(np.dstack([r, g, b]))`` instead), -- ``RGBAxes.add_RGB_to_figure`` (no replacement), -- ``RGBAxesBase`` (use ``RGBAxes`` instead), -- passing ``None``, or no argument, to ``parasite_axes_class_factory``, - ``parasite_axes_auxtrans_class_factory``, ``host_axes_class_factory`` - (pass an explicit base class instead), -- the ``den`` parameter and attribute of ``angle_helper.LocatorBase`` (it has - been renamed to ``nbins``), -- ``AxisArtist.dpi_transform`` (no replacement), -- ``grid_finder.MaxNLocator.set_factor`` and ``grid_finder.FixedLocator.set_factor`` - (the factor is always 1 now). diff --git a/doc/api/next_api_changes/removals/20095-ES.rst b/doc/api/next_api_changes/removals/20095-ES.rst deleted file mode 100644 index 4338ded19bdb..000000000000 --- a/doc/api/next_api_changes/removals/20095-ES.rst +++ /dev/null @@ -1,20 +0,0 @@ -Axis and Locator ``pan`` and ``zoom`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The unused ``pan`` and ``zoom`` methods of `~.axis.Axis` and `~.ticker.Locator` -have been removed. Panning and zooming are now implemented using the -``start_pan``, ``drag_pan``, and ``end_pan`` methods of `~.axes.Axes`. - -Ticker Locators and Formatters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following deprecated `.Locator`\s and `.Formatter`\s have been removed: - -* ``OldScalarFormatter``, ``IndexFormatter`` and ``IndexDateFormatter``; use - `.FuncFormatter` instead. -* ``OldAutoLocator`` - -The following deprecated properties and methods have been removed: - -* ``DateFormatter.illegal_s`` -* ``Locator.refresh()`` and the associated helper methods - ``NavigationToolbar2.draw()`` and ``ToolViewsPositions.refresh_locators()`` diff --git a/doc/api/next_api_changes/removals/20188-ES.rst b/doc/api/next_api_changes/removals/20188-ES.rst deleted file mode 100644 index 577e5dcb56ce..000000000000 --- a/doc/api/next_api_changes/removals/20188-ES.rst +++ /dev/null @@ -1,10 +0,0 @@ -Removal of ``cbook`` deprecations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* ``local_over_kwdict`` has been removed; use `.cbook.normalize_kwargs` - instead. -* *required*, *forbidden* and *allowed* parameters of `.cbook.normalize_kwargs` - have been removed. -* Flags containing "U" passed to `.cbook.to_filehandle` and - `.cbook.open_file_cm` are no longer accepted. This is consistent with their - removal from `open` in Python 3.9. diff --git a/doc/api/next_api_changes/removals/20245-GL.rst b/doc/api/next_api_changes/removals/20245-GL.rst deleted file mode 100644 index 6b4fa862aca6..000000000000 --- a/doc/api/next_api_changes/removals/20245-GL.rst +++ /dev/null @@ -1,7 +0,0 @@ -Collection's offset_position has been removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The deprecated keyword argument *offset_position* has been removed from -the Collection class, along with the setter and getter -``Collection.set_offset_position()`` and ``Collection.get_offset_position()``. -The ``offset_position`` of the Collection class is now "screen". diff --git a/doc/api/next_api_changes/removals/20314-GL.rst b/doc/api/next_api_changes/removals/20314-GL.rst deleted file mode 100644 index 042708d6adf0..000000000000 --- a/doc/api/next_api_changes/removals/20314-GL.rst +++ /dev/null @@ -1,11 +0,0 @@ -Colorbar related removals -~~~~~~~~~~~~~~~~~~~~~~~~~ - -``Colorbar.on_mappable_changed()`` and ``Colorbar.update_bruteforce()`` have -been removed, you can use ``Colorbar.update_normal()`` instead. - -``ColorbarBase`` only takes a single positional argument now, the ``Axes`` to -create it in, with all other options required to be keyword arguments. - -The warning for keyword arguments that were overridden by the mappable -is now removed. diff --git a/doc/api/next_api_changes/removals/20331-ES.rst b/doc/api/next_api_changes/removals/20331-ES.rst deleted file mode 100644 index d68a567c342e..000000000000 --- a/doc/api/next_api_changes/removals/20331-ES.rst +++ /dev/null @@ -1,63 +0,0 @@ -``Axes.pie`` *radius*, *startangle*, and *normalize* parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passing ``None`` as either the *radius* or *startangle* arguments of an -`.Axes.pie` is no longer accepted; use the explicit defaults of 1 and 0, -respectively, instead. - -Passing ``None`` as the *normalize* argument of `.Axes.pie` (the former -default) is no longer accepted, and the pie will always be normalized by -default. If you wish to plot an incomplete pie, explicitly pass -``normalize=False``. - -Signatures of `.Artist.draw` and `.Axes.draw` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *inframe* parameter to `.Axes.draw` has been removed. Use -`.Axes.redraw_in_frame` instead. - -Omitting the *renderer* parameter to `.Axes.draw` is no longer supported. -Use ``axes.draw_artist(axes)`` instead. - -These changes make the signature of the ``draw`` (``artist.draw(renderer)``) -method consistent across all artists; thus, additional parameters to -`.Artist.draw` have also been removed. - -``Axes.update_datalim_bounds`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This method has been removed. Use -``ax.dataLim.set(Bbox.union([ax.dataLim, bounds]))`` instead. - -``{,Symmetrical}LogScale.{,Inverted}LogTransform`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``LogScale.LogTransform``, ``LogScale.InvertedLogTransform``, -``SymmetricalScale.SymmetricalTransform`` and -``SymmetricalScale.InvertedSymmetricalTransform`` have been removed. Directly -access the transform classes from the `matplotlib.scale` module. - -log/symlog scale base, ticks, and nonpos specification -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`~.Axes.semilogx`, `~.Axes.semilogy`, `~.Axes.loglog`, `.LogScale`, and -`.SymmetricalLogScale` used to take keyword arguments that depends on the axis -orientation ("basex" vs "basey", "subsx" vs "subsy", "nonposx" vs "nonposy"); -these parameter names have been removed in favor of "base", "subs", -"nonpositive". This removal also affects e.g. ``ax.set_yscale("log", -basey=...)`` which must now be spelled ``ax.set_yscale("log", base=...)``. - -The change from "nonpos" to "nonpositive" also affects `~.scale.LogTransform`, -`~.scale.InvertedLogTransform`, `~.scale.SymmetricalLogTransform`, etc. - -To use *different* bases for the x-axis and y-axis of a `~.Axes.loglog` plot, -use e.g. ``ax.set_xscale("log", base=10); ax.set_yscale("log", base=2)``. - -Implicit initialization of ``Tick`` attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `.Tick` constructor no longer initializes the attributes ``tick1line``, -``tick2line``, ``gridline``, ``label1``, and ``label2`` via ``_get_tick1line``, -``_get_tick2line``, ``_get_gridline``, ``_get_text1``, and ``_get_text2``. -Please directly set the attribute in the subclass' ``__init__`` instead. - -Unused parameters -~~~~~~~~~~~~~~~~~ -The following parameters do not have any effect and have been removed: - -- parameter *label* of `.Tick` diff --git a/doc/api/next_api_changes/removals/20447-ES.rst b/doc/api/next_api_changes/removals/20447-ES.rst deleted file mode 100644 index 20e4aae8fd0d..000000000000 --- a/doc/api/next_api_changes/removals/20447-ES.rst +++ /dev/null @@ -1,58 +0,0 @@ -``figure.add_axes()`` without arguments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Calling ``fig.add_axes()`` with no arguments will raise an error. Adding a -free-floating axes needs a position rectangle. If you want a figure-filling -single axes, use ``add_subplot()`` instead. - -All parameters of `.Figure.subplots` except *nrows* and *ncols* are now keyword-only -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This avoids typing e.g. ``subplots(1, 1, 1)`` when meaning ``subplot(1, 1, 1)``, -but actually getting ``subplots(1, 1, sharex=1)``. - -``add_subplot()`` validates its inputs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In particular, for ``add_subplot(rows, cols, index)``, all parameters must -be integral. Previously strings and floats were accepted and converted to -int. - -``SubplotSpec.get_rows_columns`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use the ``GridSpec.nrows``, ``GridSpec.ncols``, ``SubplotSpec.rowspan``, and -``SubplotSpec.colspan`` properties instead. - -``autofmt_xdate(which=None)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use its more explicit synonym, ``which="major"``, instead. - -`.pyplot.tight_layout` parameters are now keyword-only -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All parameters of `.pyplot.tight_layout` are now keyword-only, to be consistent -with `.Figure.tight_layout`. - -Parameters *norm* and *vmin*/*vmax* may not be used simultaneously -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passing parameters *norm* and *vmin*/*vmax* simultaneously to functions using -colormapping such as ``scatter()`` and ``imshow()`` is no longer supported. -Instead of ``norm=LogNorm(), vmin=min_val, vmax=max_val`` pass -``norm=LogNorm(min_val, max_val)``. *vmin* and *vmax* should only be used -without setting *norm*. - -`.Axes.annotate` and `.pyplot.annotate` parameter name changed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The parameter *s* to `.Axes.annotate` and `.pyplot.annotate` has previously -been renamed to *text*, matching `.Annotation`. The old parameter name is no -longer supported. - -*orientation* of ``eventplot()`` and `.EventCollection` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Setting the *orientation* of an ``eventplot()`` or `.EventCollection` to "none" -or None is no longer supported; set it to "horizontal" instead. Moreover, the two -orientations ("horizontal" and "vertical") are now case-sensitive. - -``ContourSet.ax``, ``Quiver.ax`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These attributes have been removed in favor of ``ContourSet.axes`` and -``Quiver.axes``, for consistency with other artists. - -*fontdict* and *minor* parameters of `.Axes.set_xticklabels` / `.Axes.set_yticklabels` are now keyword-only -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/api/next_api_changes/removals/20465-ES.rst b/doc/api/next_api_changes/removals/20465-ES.rst deleted file mode 100644 index abe773d5b2a2..000000000000 --- a/doc/api/next_api_changes/removals/20465-ES.rst +++ /dev/null @@ -1,93 +0,0 @@ -Case-insensitive properties -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Upper or mixed-case property names are no longer normalized to lowercase in -`.Artist.set` and `.Artist.update`. This allows one to pass names such as -*patchA* or *UVC*. - -Case-insensitive capstyles and joinstyles -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Please pass capstyles ("miter", "round", "bevel") and joinstyles ("butt", -"round", "projecting") as lowercase. - -AVConv animation writers removed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``AVConvBase``, ``AVConvWriter`` and ``AVConvFileWriter`` classes, and the -associated ``animation.avconv_path`` and ``animation.avconv_args`` rcParams -have been removed. - -Debian 8 (2015, EOL 06/2020) and Ubuntu 14.04 (EOL 04/2019) were the -last versions of Debian and Ubuntu to ship avconv. It remains possible -to force the use of avconv by using the FFmpeg-based writers with -:rc:`animation.ffmpeg_path` set to "avconv". - -``MovieWriter`` attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~ -* ``animation.html_args`` rcParam -* ``HTMLWriter.args_key`` attribute -* ``MovieWriter.args_key`` and ``MovieWriter.exec_key`` attributes -* *clear_temp* parameter and attribute of `.FileMovieWriter`; files placed in a - temporary directory (using ``frame_prefix=None``, the default) will be - cleared; files placed elsewhere will not. - -Artist-specific removals -~~~~~~~~~~~~~~~~~~~~~~~~ -* Setting a custom method overriding `.Artist.contains` using - ``Artist.set_contains`` has been removed, as has ``Artist.get_contains``. - There is no replacement, but you may still customize pick events using - `.Artist.set_picker`. -* Passing the dash offset as ``None`` is no longer accepted, as this was never - universally implemented, e.g. for vector output. Set the offset to 0 instead. -* The parameter *props* of `.Shadow` has been removed. Use keyword arguments - instead. -* Arbitrary keyword arguments to ``StreamplotSet`` have no effect and have been - removed. -* ``NonUniformImage.is_grayscale`` and ``PcolorImage.is_grayscale`` attributes - have been removed, for consistency with ``AxesImage.is_grayscale``. (Note - that previously, these attributes were only available *after rendering the - image*). - -Path helpers -~~~~~~~~~~~~ -* ``bezier.make_path_regular``; use ``Path.cleaned()`` (or - ``Path.cleaned(curves=True)``, etc.) instead, but note that these methods add - a ``STOP`` code at the end of the path. -* ``bezier.concatenate_paths``; use ``Path.make_compound_path()`` instead. -* *quantize* parameter of `.Path.cleaned()` - -``BboxBase.inverse_transformed`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``.BboxBase.inverse_transformed`` has been removed (call `.BboxBase.transformed` -on the `~.Transform.inverted()` transform instead). - -``matplotlib.test(recursionlimit=...)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *recursionlimit* parameter of `matplotlib.test` has been removed. - -``testing.compare.make_external_conversion_command`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... has been removed. - -``docstring.Substitution.from_params`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This method has been removed. If needed, directly assign to the ``params`` -attribute of the Substitution object. - -``widgets.TextBox.params_to_disable`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This attribute has been removed. - -`.widgets.SubplotTool` callbacks and axes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``funcleft``, ``funcright``, ``funcbottom``, ``functop``, ``funcwspace``, -and ``funchspace`` methods of `.widgets.SubplotTool` have been removed. - -The ``axleft``, ``axright``, ``axbottom``, ``axtop``, ``axwspace``, and -``axhspace`` attributes of `.widgets.SubplotTool` have been removed. Access -the ``ax`` attribute of the corresponding slider, if needed. - -Variants on ``ToolManager.update_keymap`` calls -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passing multiple keys as a single comma-separated string or multiple arguments -to `.ToolManager.update_keymap` is no longer supported; pass keys as a list of -strings instead. diff --git a/doc/api/next_api_changes/removals/20563-TH.rst b/doc/api/next_api_changes/removals/20563-TH.rst deleted file mode 100644 index 2908b9ddab5e..000000000000 --- a/doc/api/next_api_changes/removals/20563-TH.rst +++ /dev/null @@ -1,4 +0,0 @@ -MaxNLocator passing *nbins* in two ways -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`~.ticker.MaxNLocator` does not accept a positional parameter and the keyword -argument *nbins* simultaneously because they specify the same quantity. \ No newline at end of file diff --git a/doc/api/prev_api_changes/api_changes_3.5.0.rst b/doc/api/prev_api_changes/api_changes_3.5.0.rst index a17bffa8a79b..96c4f390eb1a 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0.rst @@ -7,4 +7,6 @@ API Changes for 3.5.0 .. include:: /api/prev_api_changes/api_changes_3.5.0/behaviour.rst +.. include:: /api/prev_api_changes/api_changes_3.5.0/removals.rst + .. include:: /api/prev_api_changes/api_changes_3.5.0/development.rst diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/removals.rst b/doc/api/prev_api_changes/api_changes_3.5.0/removals.rst new file mode 100644 index 000000000000..cc81bd702918 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.5.0/removals.rst @@ -0,0 +1,365 @@ +Removals +-------- + +The following deprecated APIs have been removed: + +Removed behaviour +~~~~~~~~~~~~~~~~~ + +Stricter validation of function parameters +.......................................... + +- Calling `.Figure.add_axes` with no arguments will raise an error. Adding a + free-floating axes needs a position rectangle. If you want a figure-filling + single axes, use `.Figure.add_subplot` instead. +- `.Figure.add_subplot` validates its inputs; in particular, for + ``add_subplot(rows, cols, index)``, all parameters must be integral. + Previously strings and floats were accepted and converted to int. +- Passing *None* as the *which* argument to ``autofmt_xdate`` is no longer + supported; use its more explicit synonym, ``which="major"``, instead. +- Setting the *orientation* of an ``eventplot()`` or `.EventCollection` to + "none" or *None* is no longer supported; set it to "horizontal" instead. + Moreover, the two orientations ("horizontal" and "vertical") are now + case-sensitive. +- Passing parameters *norm* and *vmin*/*vmax* simultaneously to functions using + colormapping such as ``scatter()`` and ``imshow()`` is no longer supported. + Instead of ``norm=LogNorm(), vmin=min_val, vmax=max_val`` pass + ``norm=LogNorm(min_val, max_val)``. *vmin* and *vmax* should only be used + without setting *norm*. +- Passing *None* as either the *radius* or *startangle* arguments of an + `.Axes.pie` is no longer accepted; use the explicit defaults of 1 and 0, + respectively, instead. +- Passing *None* as the *normalize* argument of `.Axes.pie` (the former + default) is no longer accepted, and the pie will always be normalized by + default. If you wish to plot an incomplete pie, explicitly pass + ``normalize=False``. +- Support for passing *None* to ``subplot_class_factory`` has been removed. + Explicitly pass in the base `~matplotlib.axes.Axes` class instead. +- Passing multiple keys as a single comma-separated string or multiple + arguments to `.ToolManager.update_keymap` is no longer supported; pass keys + as a list of strings instead. +- Passing the dash offset as *None* is no longer accepted, as this was never + universally implemented, e.g. for vector output. Set the offset to 0 instead. +- Setting a custom method overriding `.Artist.contains` using + ``Artist.set_contains`` has been removed, as has ``Artist.get_contains``. + There is no replacement, but you may still customize pick events using + `.Artist.set_picker`. +- `~.Axes.semilogx`, `~.Axes.semilogy`, `~.Axes.loglog`, `.LogScale`, and + `.SymmetricalLogScale` used to take keyword arguments that depends on the + axis orientation ("basex" vs "basey", "subsx" vs "subsy", "nonposx" vs + "nonposy"); these parameter names have been removed in favor of "base", + "subs", "nonpositive". This removal also affects e.g. ``ax.set_yscale("log", + basey=...)`` which must now be spelled ``ax.set_yscale("log", base=...)``. + + The change from "nonpos" to "nonpositive" also affects + `~.scale.LogTransform`, `~.scale.InvertedLogTransform`, + `~.scale.SymmetricalLogTransform`, etc. + + To use *different* bases for the x-axis and y-axis of a `~.Axes.loglog` plot, + use e.g. ``ax.set_xscale("log", base=10); ax.set_yscale("log", base=2)``. +- Passing *None*, or no argument, to ``parasite_axes_class_factory``, + ``parasite_axes_auxtrans_class_factory``, ``host_axes_class_factory`` is no + longer accepted; pass an explicit base class instead. + +Case-sensitivity is now enforced more +...................................... + +- Upper or mixed-case property names are no longer normalized to lowercase in + `.Artist.set` and `.Artist.update`. This allows one to pass names such as + *patchA* or *UVC*. +- Case-insensitive capstyles and joinstyles are no longer lower-cased; please + pass capstyles ("miter", "round", "bevel") and joinstyles ("butt", "round", + "projecting") as lowercase. +- Saving metadata in PDF with the PGF backend no longer changes keys to + lowercase. Only the canonically cased keys listed in the PDF specification + (and the `~.backend_pgf.PdfPages` documentation) are accepted. + +No implicit initialization of ``Tick`` attributes +................................................. + +The `.Tick` constructor no longer initializes the attributes ``tick1line``, +``tick2line``, ``gridline``, ``label1``, and ``label2`` via ``_get_tick1line``, +``_get_tick2line``, ``_get_gridline``, ``_get_text1``, and ``_get_text2``. +Please directly set the attribute in the subclass' ``__init__`` instead. + +``NavigationToolbar2`` subclass changes +....................................... + +Overriding the ``_init_toolbar`` method of `.NavigationToolbar2` to initialize +third-party toolbars is no longer supported. Instead, the toolbar should be +initialized in the ``__init__`` method of the subclass (which should call the +base-class' ``__init__`` as appropriate). + +The ``press`` and ``release`` methods of `.NavigationToolbar2` were called when +pressing or releasing a mouse button, but *only* when an interactive pan or +zoom was occurring (contrary to what the docs stated). They are no longer +called; if you write a backend which needs to customize such events, please +directly override ``press_pan``/``press_zoom``/``release_pan``/``release_zoom`` +instead. + +Removal of old file mode flag +............................. + +Flags containing "U" passed to `.cbook.to_filehandle` and `.cbook.open_file_cm` +are no longer accepted. This is consistent with their removal from `open` in +Python 3.9. + +Keymaps toggling ``Axes.get_navigate`` have been removed +........................................................ + +This includes numeric key events and rcParams. + +The ``TTFPATH`` and ``AFMPATH`` environment variables +..................................................... + +Support for the (undocumented) ``TTFPATH`` and ``AFMPATH`` environment +variables has been removed. Register additional fonts using +``matplotlib.font_manager.fontManager.addfont()``. + +Modules +~~~~~~~ + +- ``matplotlib.backends.qt_editor.formsubplottool``; use + ``matplotlib.backends.backend_qt.SubplotToolQt`` instead. +- ``matplotlib.compat`` +- ``matplotlib.ttconv`` +- The Qt4-based backends, ``qt4agg`` and ``qt4cairo``, have been removed. Qt4 + has reached its end-of-life in 2015 and there are no releases of either PyQt4 + or PySide for recent versions of Python. Please use one of the Qt5 or Qt6 + backends. + +Classes, methods and attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following module-level classes/variables have been removed: + +- ``backend_bases.StatusbarBase`` and all its subclasses, and ``StatusBarWx``; + messages are displayed in the toolbar +- ``backend_pgf.GraphicsContextPgf`` +- ``MODIFIER_KEYS``, ``SUPER``, ``ALT``, ``CTRL``, and ``SHIFT`` of + `matplotlib.backends.backend_qt5agg` and + `matplotlib.backends.backend_qt5cairo` +- ``backend_wx.DEBUG_MSG`` +- ``dviread.Encoding`` +- ``Fil``, ``Fill``, ``Filll``, ``NegFil``, ``NegFill``, ``NegFilll``, and + ``SsGlue`` from `.mathtext`; directly construct glue instances with + ``Glue("fil")``, etc. +- ``mathtext.GlueSpec`` +- ``OldScalarFormatter``, ``IndexFormatter`` and ``IndexDateFormatter``; use + `.FuncFormatter` instead +- ``OldAutoLocator`` +- ``AVConvBase``, ``AVConvWriter`` and ``AVConvFileWriter``. Debian 8 (2015, + EOL 06/2020) and Ubuntu 14.04 (EOL 04/2019) were the last versions of Debian + and Ubuntu to ship avconv. It remains possible to force the use of avconv by + using the FFmpeg-based writers with :rc:`animation.ffmpeg_path` set to + "avconv". +- ``matplotlib.axes._subplots._subplot_classes`` +- ``axes_grid1.axes_rgb.RGBAxesBase``; use ``RGBAxes`` instead + +The following class attributes have been removed: + +- ``backend_pgf.LatexManager.latex_stdin_utf8`` +- ``backend_pgf.PdfPages.metadata`` +- ``ContourSet.ax`` and ``Quiver.ax``; use ``ContourSet.axes`` or + ``Quiver.axes`` as with other artists +- ``DateFormatter.illegal_s`` +- ``dates.YearLocator.replaced``; `.YearLocator` is now a subclass of + `.RRuleLocator`, and the attribute ``YearLocator.replaced`` has been removed. + For tick locations that required modifying this, a custom rrule and + `.RRuleLocator` can be used instead. +- ``FigureManagerBase.statusbar``; messages are displayed in the toolbar +- ``FileMovieWriter.clear_temp`` +- ``mathtext.Glue.glue_subtype`` +- ``MovieWriter.args_key``, ``MovieWriter.exec_key``, and + ``HTMLWriter.args_key`` +- ``NavigationToolbar2QT.basedir``; the base directory to the icons is + ``os.path.join(mpl.get_data_path(), "images")`` +- ``NavigationToolbar2QT.ctx`` +- ``NavigationToolbar2QT.parent``; to access the parent window, use + ``toolbar.canvas.parent()`` or ``toolbar.parent()`` +- ``prevZoomRect``, ``retinaFix``, ``savedRetinaImage``, ``wxoverlay``, + ``zoomAxes``, ``zoomStartX``, and ``zoomStartY`` attributes of + ``NavigationToolbar2Wx`` +- ``NonUniformImage.is_grayscale``, ``PcolorImage.is_grayscale``, for + consistency with ``AxesImage.is_grayscale``. (Note that previously, these + attributes were only available *after rendering the image*). +- ``RendererCairo.fontweights``, ``RendererCairo.fontangles`` +- ``used_characters`` of `.RendererPdf`, `.PdfFile`, and `.RendererPS` +- ``LogScale.LogTransform``, ``LogScale.InvertedLogTransform``, + ``SymmetricalScale.SymmetricalTransform``, and + ``SymmetricalScale.InvertedSymmetricalTransform``; directly access the + transform classes from `matplotlib.scale` +- ``cachedir``, ``rgba_arrayd``, ``serif``, ``sans_serif``, ``cursive``, and + ``monospace`` attributes of `.TexManager` +- ``axleft``, ``axright``, ``axbottom``, ``axtop``, ``axwspace``, and + ``axhspace`` attributes of `.widgets.SubplotTool`; access the ``ax`` + attribute of the corresponding slider +- ``widgets.TextBox.params_to_disable`` +- ``angle_helper.LocatorBase.den``; it has been renamed to *nbins* +- ``axes_grid.CbarAxesBase.cbid`` and ``axes_grid.CbarAxesBase.locator``; use + ``mappable.colorbar_cid`` or ``colorbar.locator`` instead + +The following class methods have been removed: + +- ``Axes.update_datalim_bounds``; use ``ax.dataLim.set(Bbox.union([ax.dataLim, + bounds]))`` +- ``pan`` and ``zoom`` methods of `~.axis.Axis` and `~.ticker.Locator` have + been removed; panning and zooming are now implemented using the + ``start_pan``, ``drag_pan``, and ``end_pan`` methods of `~.axes.Axes` +- ``.BboxBase.inverse_transformed``; call `.BboxBase.transformed` on the + `~.Transform.inverted()` transform +- ``Collection.set_offset_position`` and ``Collection.get_offset_position`` + have been removed; the ``offset_position`` of the `.Collection` class is now + "screen" +- ``Colorbar.on_mappable_changed`` and ``Colorbar.update_bruteforce``; use + ``Colorbar.update_normal()`` instead +- ``docstring.Substitution.from_params`` has been removed; directly assign to + ``params`` of `.Substitution` instead +- ``DraggableBase.artist_picker``; set the artist's picker instead +- ``DraggableBase.on_motion_blit``; use `.DraggableBase.on_motion` instead +- ``FigureCanvasGTK3._renderer_init`` +- ``Locator.refresh()`` and the associated helper methods + ``NavigationToolbar2.draw()`` and ``ToolViewsPositions.refresh_locators()`` +- ``track_characters`` and ``merge_used_characters`` of `.RendererPdf`, + `.PdfFile`, and `.RendererPS` +- ``RendererWx.get_gc`` +- ``SubplotSpec.get_rows_columns``; use the ``GridSpec.nrows``, + ``GridSpec.ncols``, ``SubplotSpec.rowspan``, and ``SubplotSpec.colspan`` + properties instead. +- ``ScalarMappable.update_dict``, ``ScalarMappable.add_checker()``, and + ``ScalarMappable.check_update()``; register a callback in + ``ScalarMappable.callbacks`` to be notified of updates +- ``TexManager.make_tex_preview`` and ``TexManager.make_dvi_preview`` +- ``funcleft``, ``funcright``, ``funcbottom``, ``functop``, ``funcwspace``, and + ``funchspace`` methods of `.widgets.SubplotTool` + +- ``axes_grid1.axes_rgb.RGBAxes.add_RGB_to_figure`` +- ``axisartist.axis_artist.AxisArtist.dpi_transform`` +- ``axisartist.grid_finder.MaxNLocator.set_factor`` and + ``axisartist.grid_finder.FixedLocator.set_factor``; the factor is always 1 + now + +Functions +~~~~~~~~~ + +- ``bezier.make_path_regular`` has been removed; use ``Path.cleaned()`` (or + ``Path.cleaned(curves=True)``, etc.) instead, but note that these methods add + a ``STOP`` code at the end of the path. +- ``bezier.concatenate_paths`` has been removed; use + ``Path.make_compound_path()`` instead. +- ``cbook.local_over_kwdict`` has been removed; use `.cbook.normalize_kwargs` + instead. +- ``qt_compat.is_pyqt5`` has been removed due to the release of PyQt6. The Qt + version can be checked using ``QtCore.qVersion()``. +- ``testing.compare.make_external_conversion_command`` has been removed. +- ``axes_grid1.axes_rgb.imshow_rgb`` has been removed; use + ``imshow(np.dstack([r, g, b]))`` instead. + +Arguments +~~~~~~~~~ + +- The *s* parameter to `.Axes.annotate` and `.pyplot.annotate` is no longer + supported; use the new name *text*. +- The *inframe* parameter to `.Axes.draw` has been removed; use + `.Axes.redraw_in_frame` instead. +- The *required*, *forbidden* and *allowed* parameters of + `.cbook.normalize_kwargs` have been removed. +- The *ismath* parameter of the ``draw_tex`` method of all renderer classes has + been removed (as a call to ``draw_tex`` — not to be confused with + ``draw_text``! — means that the entire string should be passed to the + ``usetex`` machinery anyways). Likewise, the text machinery will no longer + pass the *ismath* parameter when calling ``draw_tex`` (this should only + matter for backend implementers). +- The *quality*, *optimize*, and *progressive* parameters of `.Figure.savefig` + (which only affected JPEG output) have been removed, as well as from the + corresponding ``print_jpg`` methods. JPEG output options can be set by + directly passing the relevant parameters in *pil_kwargs*. +- The *clear_temp* parameter of `.FileMovieWriter` has been removed; files + placed in a temporary directory (using ``frame_prefix=None``, the default) + will be cleared; files placed elsewhere will not. +- The *copy* parameter of ``mathtext.Glue`` has been removed. +- The *quantize* parameter of `.Path.cleaned()` has been removed. +- The *dummy* parameter of `.RendererPgf` has been removed. +- The *props* parameter of `.Shadow` has been removed; use keyword arguments + instead. +- The *recursionlimit* parameter of `matplotlib.test` has been removed. +- The *label* parameter of `.Tick` has no effect and has been removed. +- `~.ticker.MaxNLocator` no longer accepts a positional parameter and the + keyword argument *nbins* simultaneously because they specify the same + quantity. +- The *add_all* parameter to ``axes_grid.Grid``, ``axes_grid.ImageGrid``, + ``axes_rgb.make_rgb_axes``, and ``axes_rgb.RGBAxes`` have been removed; the + APIs always behave as if ``add_all=True``. +- The *den* parameter of ``axisartist.angle_helper.LocatorBase`` has been + removed; use *nbins* instead. + +- The *s* keyword argument to `.AnnotationBbox.get_fontsize` has no effect and + has been removed. +- The *offset_position* keyword argument of the `.Collection` class has been + removed; the ``offset_position`` now "screen". +- Arbitrary keyword arguments to ``StreamplotSet`` have no effect and have been + removed. + +- The *fontdict* and *minor* parameters of `.Axes.set_xticklabels` / + `.Axes.set_yticklabels` are now keyword-only. +- All parameters of `.Figure.subplots` except *nrows* and *ncols* are now + keyword-only; this avoids typing e.g. ``subplots(1, 1, 1)`` when meaning + ``subplot(1, 1, 1)``, but actually getting ``subplots(1, 1, sharex=1)``. +- All parameters of `.pyplot.tight_layout` are now keyword-only, to be + consistent with `.Figure.tight_layout`. +- ``ColorbarBase`` only takes a single positional argument now, the ``Axes`` to + create it in, with all other options required to be keyword arguments. The + warning for keyword arguments that were overridden by the mappable is now + removed. + +- Omitting the *renderer* parameter to `.Axes.draw` is no longer supported; use + ``axes.draw_artist(axes)`` instead. +- Passing ``ismath="TeX!"`` to `.RendererAgg.get_text_width_height_descent` is + no longer supported; pass ``ismath="TeX"`` instead, +- Changes to the signature of the `.Axes.draw` method make it consistent with + all other artists; thus additional parameters to `.Artist.draw` have also + been removed. + +rcParams +~~~~~~~~ + +- The ``animation.avconv_path`` and ``animation.avconv_args`` rcParams have + been removed. +- The ``animation.html_args`` rcParam has been removed. +- The ``keymap.all_axes`` rcParam has been removed. +- The ``mathtext.fallback_to_cm`` rcParam has been removed. Use + :rc:`mathtext.fallback` instead. +- The ``savefig.jpeg_quality`` rcParam has been removed. +- The ``text.latex.preview`` rcParam has been removed. +- The following deprecated rcParams validators, defined in `.rcsetup`, have + been removed: + + - ``validate_alignment`` + - ``validate_axes_titlelocation`` + - ``validate_axis_locator`` + - ``validate_bool_maybe_none`` + - ``validate_fontset`` + - ``validate_grid_axis`` + - ``validate_hinting`` + - ``validate_legend_loc`` + - ``validate_mathtext_default`` + - ``validate_movie_frame_fmt`` + - ``validate_movie_html_fmt`` + - ``validate_movie_writer`` + - ``validate_nseq_float`` + - ``validate_nseq_int`` + - ``validate_orientation`` + - ``validate_pgf_texsystem`` + - ``validate_ps_papersize`` + - ``validate_svg_fontset`` + - ``validate_toolbar`` + - ``validate_webagg_address`` + +- Some rcParam validation has become stricter: + + - :rc:`axes.axisbelow` no longer accepts strings starting with "line" + (case-insensitive) as "line"; use "line" (case-sensitive) instead. + - :rc:`text.latex.preamble` and :rc:`pdf.preamble` no longer accept + non-string values. + - All ``*.linestyle`` rcParams no longer accept ``offset = None``; set the + offset to 0 instead. From 50cedf22c98496cd3d86005d35c0a758c0b75a3e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 27 Sep 2021 19:36:25 -0400 Subject: [PATCH 043/130] Consolidate API deprecations for 3.5 --- .../next_api_changes/behavior/20054-JMK.rst | 4 - .../deprecations/13860-AL.rst | 6 - .../deprecations/15604-AL.rst | 5 - .../deprecations/18216-ES.rst | 22 - .../deprecations/18346-TH.rst | 9 - .../deprecations/19401-AL.rst | 3 - .../deprecations/19441-AL.rst | 5 - .../deprecations/19483-JMK.rst | 7 - .../deprecations/19487-AL.rst | 6 - .../deprecations/19517-AL.rst | 4 - .../deprecations/19558-AL.rst | 3 - .../deprecations/19575-AL.rst | 4 - .../deprecations/19585-AL.rst | 4 - .../deprecations/19655-AL.rst | 5 - .../deprecations/19795-AL.rst | 3 - .../deprecations/19858-AL.rst | 4 - .../deprecations/19892-TH.rst | 12 - .../deprecations/19934-DS.rst | 17 - .../deprecations/20063-AL.rst | 4 - .../deprecations/20065-AL.rst | 4 - .../deprecations/20079-AL.rst | 10 - .../deprecations/20091-AL.rst | 3 - .../deprecations/20108-AL.rst | 3 - .../deprecations/20109-AL.rst | 5 - .../deprecations/20113-EP.rst | 21 - .../deprecations/20126-AL.rst | 4 - .../deprecations/20170-AL.rst | 4 - .../deprecations/20173-AL.rst | 4 - .../deprecations/20193-AL.rst | 3 - .../deprecations/20206-AL.rst | 3 - .../deprecations/20208-AL.rst | 4 - .../deprecations/20209-AL.rst | 3 - .../deprecations/20237-TH.rst | 17 - .../deprecations/20254-AL.rst | 3 - .../deprecations/20278-AL.rst | 4 - .../deprecations/20302-AL.rst | 4 - .../deprecations/20311-AL.rst | 5 - .../deprecations/20334-AL.rst | 11 - .../deprecations/20428-AL.rst | 4 - .../deprecations/20466-AL.rst | 3 - .../deprecations/20474-AL.rst | 2 - .../deprecations/20540-AL.rst | 3 - .../deprecations/20543-AL.rst | 3 - .../deprecations/20585-EP.rst | 32 -- .../deprecations/20586-TH.rst | 12 - .../deprecations/20601-ES.rst | 9 - .../deprecations/20603-AL.rst | 3 - .../deprecations/20606-AL.rst | 4 - .../deprecations/20620-ES.rst | 18 - .../deprecations/20638-AL.rst | 3 - .../deprecations/20686-AL.rst | 2 - .../deprecations/20693-EP.rst | 4 - .../deprecations/20753-AL.rst | 4 - .../deprecations/20795-TAC.rst | 5 - .../deprecations/20799-GL.rst | 3 - .../deprecations/20806-TH.rst | 4 - .../deprecations/20881-AL.rst | 3 - .../deprecations/21126-TH.rst | 2 - .../deprecations/XXXXX-AL.rst | 3 - .../next_api_changes/removals/20444-CE.rst | 4 - .../prev_api_changes/api_changes_3.5.0.rst | 2 + .../api_changes_3.5.0/deprecations.rst | 375 ++++++++++++++++++ 62 files changed, 377 insertions(+), 372 deletions(-) delete mode 100644 doc/api/next_api_changes/behavior/20054-JMK.rst delete mode 100644 doc/api/next_api_changes/deprecations/13860-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/15604-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/18216-ES.rst delete mode 100644 doc/api/next_api_changes/deprecations/18346-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/19401-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19441-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19483-JMK.rst delete mode 100644 doc/api/next_api_changes/deprecations/19487-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19517-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19558-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19575-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19585-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19655-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19795-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19858-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/19892-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/19934-DS.rst delete mode 100644 doc/api/next_api_changes/deprecations/20063-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20065-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20079-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20091-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20108-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20109-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20113-EP.rst delete mode 100644 doc/api/next_api_changes/deprecations/20126-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20170-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20173-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20193-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20206-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20208-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20209-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20237-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/20254-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20278-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20302-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20311-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20334-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20428-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20466-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20474-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20540-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20543-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20585-EP.rst delete mode 100644 doc/api/next_api_changes/deprecations/20586-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/20601-ES.rst delete mode 100644 doc/api/next_api_changes/deprecations/20603-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20606-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20620-ES.rst delete mode 100644 doc/api/next_api_changes/deprecations/20638-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20686-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20693-EP.rst delete mode 100644 doc/api/next_api_changes/deprecations/20753-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20795-TAC.rst delete mode 100644 doc/api/next_api_changes/deprecations/20799-GL.rst delete mode 100644 doc/api/next_api_changes/deprecations/20806-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/20881-AL.rst delete mode 100644 doc/api/next_api_changes/deprecations/21126-TH.rst delete mode 100644 doc/api/next_api_changes/deprecations/XXXXX-AL.rst delete mode 100644 doc/api/next_api_changes/removals/20444-CE.rst create mode 100644 doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst diff --git a/doc/api/next_api_changes/behavior/20054-JMK.rst b/doc/api/next_api_changes/behavior/20054-JMK.rst deleted file mode 100644 index 875dd7570475..000000000000 --- a/doc/api/next_api_changes/behavior/20054-JMK.rst +++ /dev/null @@ -1,4 +0,0 @@ -``Colorbar.patch`` is deprecated -================================ - -This attribute is not correctly updated anymore. diff --git a/doc/api/next_api_changes/deprecations/13860-AL.rst b/doc/api/next_api_changes/deprecations/13860-AL.rst deleted file mode 100644 index 98ca88e1a840..000000000000 --- a/doc/api/next_api_changes/deprecations/13860-AL.rst +++ /dev/null @@ -1,6 +0,0 @@ -Locator and Formatter wrapper methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``set_view_interval``, ``set_data_interval`` and ``set_bounds`` methods of -`.Locator`\s and `.Formatter`\s (and their common base class, TickHelper) are -deprecated. Directly manipulate the view and data intervals on the underlying -axis instead. diff --git a/doc/api/next_api_changes/deprecations/15604-AL.rst b/doc/api/next_api_changes/deprecations/15604-AL.rst deleted file mode 100644 index d87b4205fc93..000000000000 --- a/doc/api/next_api_changes/deprecations/15604-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -Auto-removal of grids by `~.Axes.pcolor` and `~.Axes.pcolormesh` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -`~.Axes.pcolor` and `~.Axes.pcolormesh` currently remove any visible axes major -grid. This behavior is deprecated; please explicitly call ``ax.grid(False)`` -to remove the grid. diff --git a/doc/api/next_api_changes/deprecations/18216-ES.rst b/doc/api/next_api_changes/deprecations/18216-ES.rst deleted file mode 100644 index 88a2614f9dde..000000000000 --- a/doc/api/next_api_changes/deprecations/18216-ES.rst +++ /dev/null @@ -1,22 +0,0 @@ -Modification of ``Axes`` children sublists -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -See :ref:`Behavioural API Changes 3.5 - Axes children combined` for more -information; modification of the following sublists is deprecated: - -* ``Axes.artists`` -* ``Axes.collections`` -* ``Axes.images`` -* ``Axes.lines`` -* ``Axes.patches`` -* ``Axes.tables`` -* ``Axes.texts`` - -To remove an Artist, use its `.Artist.remove` method. To add an Artist, use the -corresponding ``Axes.add_*`` method. - -Passing incorrect types to ``Axes.add_*`` methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``Axes.add_*`` methods will now warn if passed an unexpected type. See -their documentation for the types they expect. diff --git a/doc/api/next_api_changes/deprecations/18346-TH.rst b/doc/api/next_api_changes/deprecations/18346-TH.rst deleted file mode 100644 index c6348157e2b2..000000000000 --- a/doc/api/next_api_changes/deprecations/18346-TH.rst +++ /dev/null @@ -1,9 +0,0 @@ -``plot_date`` -~~~~~~~~~~~~~ -The use of `~.Axes.plot_date` is discouraged. This method exists for historic -reasons and may be deprecated in the future. - -- ``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/19401-AL.rst b/doc/api/next_api_changes/deprecations/19401-AL.rst deleted file mode 100644 index 81d69076d510..000000000000 --- a/doc/api/next_api_changes/deprecations/19401-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``mpl_toolkits.axisartist.clip_path`` is deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... with no replacement. diff --git a/doc/api/next_api_changes/deprecations/19441-AL.rst b/doc/api/next_api_changes/deprecations/19441-AL.rst deleted file mode 100644 index 3f1f8822cd51..000000000000 --- a/doc/api/next_api_changes/deprecations/19441-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -``cursord`` in GTK, Qt, and wx backends -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``backend_gtk3.cursord``, ``backend_qt.cursord``, and -``backend_wx.cursord`` dictionaries are deprecated. This makes the GTK module -importable on headless environments. diff --git a/doc/api/next_api_changes/deprecations/19483-JMK.rst b/doc/api/next_api_changes/deprecations/19483-JMK.rst deleted file mode 100644 index ddd3c252ea0a..000000000000 --- a/doc/api/next_api_changes/deprecations/19483-JMK.rst +++ /dev/null @@ -1,7 +0,0 @@ -``epoch2num`` and ``num2epoch`` are deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These methods convert from unix timestamps to matplotlib floats, but are not -used internally to matplotlib, and should not be needed by endusers. -To convert a unix timestamp to datetime, simply use -`datetime.datetime.utcfromtimestamp`, or to use numpy datetime64 -``dt = np.datetim64(e*1e6, 'us')``. diff --git a/doc/api/next_api_changes/deprecations/19487-AL.rst b/doc/api/next_api_changes/deprecations/19487-AL.rst deleted file mode 100644 index 6466e0516994..000000000000 --- a/doc/api/next_api_changes/deprecations/19487-AL.rst +++ /dev/null @@ -1,6 +0,0 @@ -Unused positional parameters to ``print_`` methods are deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -None of the ``print_`` methods implemented by canvas subclasses used -positional arguments other that the first (the output filename or file-like), -so these extra parameters are deprecated. diff --git a/doc/api/next_api_changes/deprecations/19517-AL.rst b/doc/api/next_api_changes/deprecations/19517-AL.rst deleted file mode 100644 index 8b937e82ba41..000000000000 --- a/doc/api/next_api_changes/deprecations/19517-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``is_url`` and ``URL_REGEX`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated. (They were previously defined in the toplevel -:mod:`matplotlib` module.) diff --git a/doc/api/next_api_changes/deprecations/19558-AL.rst b/doc/api/next_api_changes/deprecations/19558-AL.rst deleted file mode 100644 index 394ce962bdd5..000000000000 --- a/doc/api/next_api_changes/deprecations/19558-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -The *format* parameter of ``dviread.find_tex_file`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated (with no replacement). diff --git a/doc/api/next_api_changes/deprecations/19575-AL.rst b/doc/api/next_api_changes/deprecations/19575-AL.rst deleted file mode 100644 index 0b7d638fc9c8..000000000000 --- a/doc/api/next_api_changes/deprecations/19575-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``Text.get_prop_tup`` -~~~~~~~~~~~~~~~~~~~~~ -... is deprecated with no replacements (because the `.Text` class cannot know -whether a backend needs to update cache e.g. when the text's color changes). diff --git a/doc/api/next_api_changes/deprecations/19585-AL.rst b/doc/api/next_api_changes/deprecations/19585-AL.rst deleted file mode 100644 index af220b7f4e71..000000000000 --- a/doc/api/next_api_changes/deprecations/19585-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -matplotlib.style.core deprecations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``STYLE_FILE_PATTERN``, ``load_base_library``, and ``iter_user_libraries`` are -deprecated. diff --git a/doc/api/next_api_changes/deprecations/19655-AL.rst b/doc/api/next_api_changes/deprecations/19655-AL.rst deleted file mode 100644 index aaa8969d1dc3..000000000000 --- a/doc/api/next_api_changes/deprecations/19655-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -``Tick.apply_tickdir`` is deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``apply_tickdir`` didn't actually update the tick markers on the existing -Line2D objects used to draw the ticks; use `.Axis.set_tick_params` instead. diff --git a/doc/api/next_api_changes/deprecations/19795-AL.rst b/doc/api/next_api_changes/deprecations/19795-AL.rst deleted file mode 100644 index 70f165ef8705..000000000000 --- a/doc/api/next_api_changes/deprecations/19795-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``Dvi.baseline`` -~~~~~~~~~~~~~~~~ -... is deprecated (with no replacement). diff --git a/doc/api/next_api_changes/deprecations/19858-AL.rst b/doc/api/next_api_changes/deprecations/19858-AL.rst deleted file mode 100644 index cbd81544e96b..000000000000 --- a/doc/api/next_api_changes/deprecations/19858-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -"units finalize" signal -~~~~~~~~~~~~~~~~~~~~~~~ -This signal (previously emitted by Axis instances) is deprecated. Connect to -"units" instead. diff --git a/doc/api/next_api_changes/deprecations/19892-TH.rst b/doc/api/next_api_changes/deprecations/19892-TH.rst deleted file mode 100644 index 7db621376d35..000000000000 --- a/doc/api/next_api_changes/deprecations/19892-TH.rst +++ /dev/null @@ -1,12 +0,0 @@ -Discouraged: ``Figure`` parameters *tight_layout* and *constrained_layout* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``Figure`` parameters *tight_layout* and *constrained_layout* are -triggering competing layout mechanisms and thus should not be used together. - -To make the API clearer, we've merged them under the new parameter *layout* -with values 'constrained' (equal to ``constrained_layout=True``), 'tight' -(equal to ``tight_layout=True``). If given *layout* takes precedence. - -The use of *tight_layout* and *constrained_layout* is discouraged in favor -of *layout*. However, these parameters will stay available for backward -compatibility. diff --git a/doc/api/next_api_changes/deprecations/19934-DS.rst b/doc/api/next_api_changes/deprecations/19934-DS.rst deleted file mode 100644 index 867eee378294..000000000000 --- a/doc/api/next_api_changes/deprecations/19934-DS.rst +++ /dev/null @@ -1,17 +0,0 @@ -*drawtype* argument to RectangleSelector -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The *drawtype* keyword argument to -`~matplotlib.widgets.RectangleSelector` is deprecated. In the future the -behaviour will be the default behaviour of ``drawtype='box'``. - -Support for ``drawtype=line`` will be removed altogether as it is not clear -which points are within and outside a selector that is just a line. -As a result, the ``lineprops`` keyword argument to -`~matplotlib.widgets.RectangleSelector` is also deprecated. - -To retain the behaviour of ``drawtype='none'``, use -``rectprops={'visible': False}`` to make the drawn -`~matplotlib.patches.Rectangle` invisible. - -These changes also apply to `~matplotlib.widgets.EllipseSelector`, which -is a sub-class of `~matplotlib.widgets.RectangleSelector`. diff --git a/doc/api/next_api_changes/deprecations/20063-AL.rst b/doc/api/next_api_changes/deprecations/20063-AL.rst deleted file mode 100644 index 1811896119ba..000000000000 --- a/doc/api/next_api_changes/deprecations/20063-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``Divider.get_vsize_hsize`` and ``Grid.get_vsize_hsize`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These ``axes_grid1`` methods are deprecated. Copy their implementations if -needed. diff --git a/doc/api/next_api_changes/deprecations/20065-AL.rst b/doc/api/next_api_changes/deprecations/20065-AL.rst deleted file mode 100644 index 3b894b7a5505..000000000000 --- a/doc/api/next_api_changes/deprecations/20065-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``AxesDivider.append_axes(..., add_to_figure=False)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. Use ``ax.remove()`` to remove the Axes from the figure if -needed. diff --git a/doc/api/next_api_changes/deprecations/20079-AL.rst b/doc/api/next_api_changes/deprecations/20079-AL.rst deleted file mode 100644 index 1d76e309b512..000000000000 --- a/doc/api/next_api_changes/deprecations/20079-AL.rst +++ /dev/null @@ -1,10 +0,0 @@ -Support for ``nx1 = None`` or ``ny1 = None`` in ``AxesLocator`` and ``Divider.locate`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In :mod:`.axes_grid1.axes_divider`, various internal APIs will stop supporting -passing ``nx1 = None`` or ``ny1 = None`` to mean ``nx + 1`` or ``ny + 1``, in -preparation for a possible future API which allows indexing and slicing of -dividers (possibly ``divider[a:b] == divider.new_locator(a, b)``, but also -``divider[a:] == divider.new_locator(a, )``). The user-facing -`.Divider.new_locator` API is unaffected -- it correctly normalizes ``nx1 = None`` -and ``ny1 = None`` as needed. diff --git a/doc/api/next_api_changes/deprecations/20091-AL.rst b/doc/api/next_api_changes/deprecations/20091-AL.rst deleted file mode 100644 index 7368691e30b0..000000000000 --- a/doc/api/next_api_changes/deprecations/20091-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``tight_layout.auto_adjust_subplotpars`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. diff --git a/doc/api/next_api_changes/deprecations/20108-AL.rst b/doc/api/next_api_changes/deprecations/20108-AL.rst deleted file mode 100644 index 372e8c7bd20e..000000000000 --- a/doc/api/next_api_changes/deprecations/20108-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``ContourLabeler.get_label_width`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. diff --git a/doc/api/next_api_changes/deprecations/20109-AL.rst b/doc/api/next_api_changes/deprecations/20109-AL.rst deleted file mode 100644 index 8874fd147039..000000000000 --- a/doc/api/next_api_changes/deprecations/20109-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -``plot_directive`` internals deprecations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following helpers in `matplotlib.sphinxext.plot_directive` are deprecated: -``unescape_doctest`` (use `doctest.script_from_examples` instead), -``split_code_at_show``, ``run_code``. diff --git a/doc/api/next_api_changes/deprecations/20113-EP.rst b/doc/api/next_api_changes/deprecations/20113-EP.rst deleted file mode 100644 index fe39447632c6..000000000000 --- a/doc/api/next_api_changes/deprecations/20113-EP.rst +++ /dev/null @@ -1,21 +0,0 @@ -SpanSelector -~~~~~~~~~~~~ -``span_stays`` is deprecated, use ``interactive`` argument instead -Several `~matplotlib.widgets.SpanSelector` class internals have been privatized -and deprecated: -- ``pressv`` -- ``prev`` -- ``rect`` -- ``rectprops`` -- ``active_handle`` -- ``span_stays`` - - -Several `~matplotlib.widgets.RectangleSelector` and -`~matplotlib.widgets.EllipseSelector` class internals have been privatized and -deprecated: -- ``to_draw`` -- ``drawtype`` -- ``rectprops`` -- ``active_handle`` -- ``interactive`` diff --git a/doc/api/next_api_changes/deprecations/20126-AL.rst b/doc/api/next_api_changes/deprecations/20126-AL.rst deleted file mode 100644 index 474089f3b81a..000000000000 --- a/doc/api/next_api_changes/deprecations/20126-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``font_manager`` helpers -~~~~~~~~~~~~~~~~~~~~~~~~ -The following functions in `matplotlib.font_manager` have been deprecated: -``win32InstalledFonts``, ``get_fontconfig_fonts``. diff --git a/doc/api/next_api_changes/deprecations/20170-AL.rst b/doc/api/next_api_changes/deprecations/20170-AL.rst deleted file mode 100644 index a4fc29e18623..000000000000 --- a/doc/api/next_api_changes/deprecations/20170-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``SubplotParams.validate`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. Use `.SubplotParams.update` to change `.SubplotParams` -while always keeping it in a valid state. diff --git a/doc/api/next_api_changes/deprecations/20173-AL.rst b/doc/api/next_api_changes/deprecations/20173-AL.rst deleted file mode 100644 index d9bce337af9f..000000000000 --- a/doc/api/next_api_changes/deprecations/20173-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -The first parameter of ``Axes.grid`` and ``Axis.grid`` has been renamed to *visible* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The parameter was previously named *b*. This deprecation only matters if -that parameter was passed using a keyword argument, e.g. ``grid(b=False)``. diff --git a/doc/api/next_api_changes/deprecations/20193-AL.rst b/doc/api/next_api_changes/deprecations/20193-AL.rst deleted file mode 100644 index 837265c4ed92..000000000000 --- a/doc/api/next_api_changes/deprecations/20193-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``ParasiteAxesBase.get_images_artists`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... has been deprecated. diff --git a/doc/api/next_api_changes/deprecations/20206-AL.rst b/doc/api/next_api_changes/deprecations/20206-AL.rst deleted file mode 100644 index e313a30ceeed..000000000000 --- a/doc/api/next_api_changes/deprecations/20206-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -The ``grid_info`` attribute of ``axisartist`` classes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... has been deprecated. diff --git a/doc/api/next_api_changes/deprecations/20208-AL.rst b/doc/api/next_api_changes/deprecations/20208-AL.rst deleted file mode 100644 index e3da7db95e59..000000000000 --- a/doc/api/next_api_changes/deprecations/20208-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``matplotlib.blocking_input`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This module has been deprecated. Instead, use ``canvas.start_event_loop()`` -and ``canvas.stop_event_loop()`` while connecting event callbacks as needed. diff --git a/doc/api/next_api_changes/deprecations/20209-AL.rst b/doc/api/next_api_changes/deprecations/20209-AL.rst deleted file mode 100644 index 79c462cd048f..000000000000 --- a/doc/api/next_api_changes/deprecations/20209-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``cbook.report_memory`` -~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. Use ``psutil.virtual_memory`` instead. diff --git a/doc/api/next_api_changes/deprecations/20237-TH.rst b/doc/api/next_api_changes/deprecations/20237-TH.rst deleted file mode 100644 index 435986f94f0f..000000000000 --- a/doc/api/next_api_changes/deprecations/20237-TH.rst +++ /dev/null @@ -1,17 +0,0 @@ -QuadMesh signature -~~~~~~~~~~~~~~~~~~ -The ``QuadMesh`` signature :: - - def __init__(meshWidth, meshHeight, coordinates, - antialiased=True, shading='flat', **kwargs) - -is deprecated and replaced by the new signature :: - - def __init__(coordinates, *, antialiased=True, shading='flat', **kwargs) - -In particular: - -- *coordinates* must now be a (M, N, 2) array-like. Previously, the grid - shape was separately specified as (*meshHeight* + 1, *meshWidth* + 1) and - *coordinates* could be an array-like of any shape with M * N * 2 elements. -- all parameters except *coordinates* are keyword-only now. diff --git a/doc/api/next_api_changes/deprecations/20254-AL.rst b/doc/api/next_api_changes/deprecations/20254-AL.rst deleted file mode 100644 index 9fce2b7061a1..000000000000 --- a/doc/api/next_api_changes/deprecations/20254-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``floating_axes.GridHelperCurveLinear.get_boundary`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated, with no replacement. diff --git a/doc/api/next_api_changes/deprecations/20278-AL.rst b/doc/api/next_api_changes/deprecations/20278-AL.rst deleted file mode 100644 index 396442042ad4..000000000000 --- a/doc/api/next_api_changes/deprecations/20278-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``TexManager`` attributes -~~~~~~~~~~~~~~~~~~~~~~~~~ -The following attributes of `.TexManager` are deprecated: ``grey_arrayd``, -``font_family``, ``font_families``, ``font_info``. diff --git a/doc/api/next_api_changes/deprecations/20302-AL.rst b/doc/api/next_api_changes/deprecations/20302-AL.rst deleted file mode 100644 index 7530a063eb5c..000000000000 --- a/doc/api/next_api_changes/deprecations/20302-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``QuadMesh.convert_mesh_to_paths`` and ``QuadMesh.convert_mesh_to_triangles`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated. ``QuadMesh.get_paths()`` can be used as an alternative for -the former; there is no replacement for the latter. diff --git a/doc/api/next_api_changes/deprecations/20311-AL.rst b/doc/api/next_api_changes/deprecations/20311-AL.rst deleted file mode 100644 index a56941e5253a..000000000000 --- a/doc/api/next_api_changes/deprecations/20311-AL.rst +++ /dev/null @@ -1,5 +0,0 @@ -rcParams will no longer cast inputs to str -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -After a deprecation period, rcParams that expect a (non-pathlike) str will no -longer cast non-str inputs using `str`. This will avoid confusing errors in -subsequent code if e.g. a list input gets implicitly cast to a str. diff --git a/doc/api/next_api_changes/deprecations/20334-AL.rst b/doc/api/next_api_changes/deprecations/20334-AL.rst deleted file mode 100644 index 425170e38f97..000000000000 --- a/doc/api/next_api_changes/deprecations/20334-AL.rst +++ /dev/null @@ -1,11 +0,0 @@ -``ConversionInterface.convert`` no longer needs to accept unitless values -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Previously, custom subclasses of `.units.ConversionInterface` needed to -implement a ``convert`` method that not only accepted instances of the -unit, but also unitless values (which are passed through as is). This is -no longer the case (``convert`` is never called with a unitless value), -and such support in `.StrCategoryConverter` is deprecated. Likewise, the -`.ConversionInterface.is_numlike` helper is deprecated. - -Consider calling `.Axis.convert_units` instead, which still supports unitless -values. diff --git a/doc/api/next_api_changes/deprecations/20428-AL.rst b/doc/api/next_api_changes/deprecations/20428-AL.rst deleted file mode 100644 index 3de56e880afc..000000000000 --- a/doc/api/next_api_changes/deprecations/20428-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``FancyArrowPatch.get_path_in_displaycoord`` and ``ConnectionPath.get_path_in_displaycoord`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated. The path in display coordinates can still be obtained, as -for other patches, using ``patch.get_transform().transform_path(patch.get_path())``. diff --git a/doc/api/next_api_changes/deprecations/20466-AL.rst b/doc/api/next_api_changes/deprecations/20466-AL.rst deleted file mode 100644 index d986ab87273f..000000000000 --- a/doc/api/next_api_changes/deprecations/20466-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``backend_pgf.LatexManager.str_cache`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. diff --git a/doc/api/next_api_changes/deprecations/20474-AL.rst b/doc/api/next_api_changes/deprecations/20474-AL.rst deleted file mode 100644 index 89c627023ba3..000000000000 --- a/doc/api/next_api_changes/deprecations/20474-AL.rst +++ /dev/null @@ -1,2 +0,0 @@ -``dviread.PsfontsMap`` now raises LookupError instead of KeyError for missing fonts -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/api/next_api_changes/deprecations/20540-AL.rst b/doc/api/next_api_changes/deprecations/20540-AL.rst deleted file mode 100644 index 49caf2d3df8f..000000000000 --- a/doc/api/next_api_changes/deprecations/20540-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``:encoding:`` option to ``.. plot`` directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This option has had no effect since Matplotlib 1.3.1, and is now deprecated. diff --git a/doc/api/next_api_changes/deprecations/20543-AL.rst b/doc/api/next_api_changes/deprecations/20543-AL.rst deleted file mode 100644 index 49f696172902..000000000000 --- a/doc/api/next_api_changes/deprecations/20543-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``@pytest.mark.style`` is deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use ``@mpl.style.context``, which has the same effect. diff --git a/doc/api/next_api_changes/deprecations/20585-EP.rst b/doc/api/next_api_changes/deprecations/20585-EP.rst deleted file mode 100644 index 7da4d42246ed..000000000000 --- a/doc/api/next_api_changes/deprecations/20585-EP.rst +++ /dev/null @@ -1,32 +0,0 @@ -Unification of Selector API -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The API for Selector widgets has been unified to use - -- *props* for the properties of the Artist representing the selection. -- *handle_props* for the Artists representing handles for modifying the selection. -- *grab_range* for the maximal tolerance to grab a handle with the mouse. - -This affects the following parameters and attributes: - - -RectangleSelector and EllipseSelector -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The *maxdist* argument is deprecated, use *grab_range* instead. -The *rectprops* argument is deprecated, use *props* instead. -The *marker_props* argument is deprecated, use *handle_props* instead. - -PolygonSelector -^^^^^^^^^^^^^^^ -The *vertex_select_radius* argument and attribute is deprecated, use *grab_range* instead. -The *lineprops* argument is deprecated, use *props* instead. -The *markerprops* argument is deprecated, use *handle_props* instead. -The *maxdist* argument and attribute is deprecated, use *grab_range* instead. - -SpanSelector -^^^^^^^^^^^^ -The *rectprops* argument is deprecated, use *props* instead. -The *maxdist* argument and attribute is deprecated, use *grab_range* instead. - -LassoSelector -^^^^^^^^^^^^^ -The *lineprops* argument is deprecated, use *props* instead. \ No newline at end of file diff --git a/doc/api/next_api_changes/deprecations/20586-TH.rst b/doc/api/next_api_changes/deprecations/20586-TH.rst deleted file mode 100644 index 99cd1d0cd3bd..000000000000 --- a/doc/api/next_api_changes/deprecations/20586-TH.rst +++ /dev/null @@ -1,12 +0,0 @@ -``matplotlib.test()`` is deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Run tests using ``pytest`` from the commandline instead. The variable -``matplotlib.default_test_modules`` is only used for ``matplotlib.test()`` and -is thus deprecated as well. - -To test an installed copy, be sure to specify both ``matplotlib`` and -``mpl_toolkits`` with ``--pyargs``:: - - python -m pytest --pyargs matplotlib.tests mpl_toolkits.tests - -See :ref:`testing` for more details. diff --git a/doc/api/next_api_changes/deprecations/20601-ES.rst b/doc/api/next_api_changes/deprecations/20601-ES.rst deleted file mode 100644 index 4d30bdd10a1c..000000000000 --- a/doc/api/next_api_changes/deprecations/20601-ES.rst +++ /dev/null @@ -1,9 +0,0 @@ -Selector widget state internals -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Several `~matplotlib.widgets.EllipseSelector`, -`~matplotlib.widgets.RectangleSelector` and `~matplotlib.widgets.SpanSelector` -class internals have been privatized and deprecated: - -- ``eventpress`` -- ``eventrelease`` -- ``state`` diff --git a/doc/api/next_api_changes/deprecations/20603-AL.rst b/doc/api/next_api_changes/deprecations/20603-AL.rst deleted file mode 100644 index d64b4f6c9001..000000000000 --- a/doc/api/next_api_changes/deprecations/20603-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``LassoSelector.onpress`` and ``LassoSelector.onrelease`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated (they are straight aliases for ``press`` and ``release``). diff --git a/doc/api/next_api_changes/deprecations/20606-AL.rst b/doc/api/next_api_changes/deprecations/20606-AL.rst deleted file mode 100644 index 63a728510e4d..000000000000 --- a/doc/api/next_api_changes/deprecations/20606-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -``axes_grid1.axes_grid.CbarAxes`` and ``axes_grid1.axisartist.CbarAxes`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These classes are deprecated (they are now dynamically generated based on the -owning axes class). diff --git a/doc/api/next_api_changes/deprecations/20620-ES.rst b/doc/api/next_api_changes/deprecations/20620-ES.rst deleted file mode 100644 index 02ac7e574603..000000000000 --- a/doc/api/next_api_changes/deprecations/20620-ES.rst +++ /dev/null @@ -1,18 +0,0 @@ -``NavigationToolbar2.set_cursor`` and ``backend_tools.SetCursorBase.set_cursor`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Instead, use the `.FigureCanvasBase.set_cursor` method on the canvas (available -as the ``canvas`` attribute on the toolbar or the Figure.) - -``backend_tools.SetCursorBase`` and subclasses -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``backend_tools.SetCursorBase`` was subclassed to provide backend-specific -implementations of ``set_cursor``. As that is now deprecated, the subclassing -is no longer necessary. Consequently, the following subclasses are also -deprecated: - -- ``matplotlib.backends.backend_gtk3.SetCursorGTK3`` -- ``matplotlib.backends.backend_qt5.SetCursorQt`` -- ``matplotlib.backends._backend_tk.SetCursorTk`` -- ``matplotlib.backends.backend_wx.SetCursorWx`` - -Instead, use the `.backend_tools.ToolSetCursor` class. diff --git a/doc/api/next_api_changes/deprecations/20638-AL.rst b/doc/api/next_api_changes/deprecations/20638-AL.rst deleted file mode 100644 index cd9f5f738eb2..000000000000 --- a/doc/api/next_api_changes/deprecations/20638-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``FixedAxisArtistHelper.change_tick_coord`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated with no replacement. diff --git a/doc/api/next_api_changes/deprecations/20686-AL.rst b/doc/api/next_api_changes/deprecations/20686-AL.rst deleted file mode 100644 index 929d3eaeaa6f..000000000000 --- a/doc/api/next_api_changes/deprecations/20686-AL.rst +++ /dev/null @@ -1,2 +0,0 @@ -All parameters of ``imshow`` starting from *aspect* will become keyword-only -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/api/next_api_changes/deprecations/20693-EP.rst b/doc/api/next_api_changes/deprecations/20693-EP.rst deleted file mode 100644 index 7d63e27fdd4f..000000000000 --- a/doc/api/next_api_changes/deprecations/20693-EP.rst +++ /dev/null @@ -1,4 +0,0 @@ -PolygonSelector -^^^^^^^^^^^^^^^ -The *line* attribute is deprecated. If you want to change the selector -artist properties, use the ``set_props`` or ``set_handle_props`` methods. diff --git a/doc/api/next_api_changes/deprecations/20753-AL.rst b/doc/api/next_api_changes/deprecations/20753-AL.rst deleted file mode 100644 index 75b989f5c658..000000000000 --- a/doc/api/next_api_changes/deprecations/20753-AL.rst +++ /dev/null @@ -1,4 +0,0 @@ -Case-insensitive scales -~~~~~~~~~~~~~~~~~~~~~~~ -Previously, scales could be set case-insensitively (e.g. ``set_xscale("LoG")``). -This is deprecated; all builtin scales use lowercase names. diff --git a/doc/api/next_api_changes/deprecations/20795-TAC.rst b/doc/api/next_api_changes/deprecations/20795-TAC.rst deleted file mode 100644 index ada6992f3483..000000000000 --- a/doc/api/next_api_changes/deprecations/20795-TAC.rst +++ /dev/null @@ -1,5 +0,0 @@ -Remove unused pytest fixture -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The fixture ``matplotlib.testing.conftest.mpl_image_comparison_parameters`` -is not used internally by Matplotlib. If you use this please copy it -into your code base. diff --git a/doc/api/next_api_changes/deprecations/20799-GL.rst b/doc/api/next_api_changes/deprecations/20799-GL.rst deleted file mode 100644 index 5c713cfa2ed3..000000000000 --- a/doc/api/next_api_changes/deprecations/20799-GL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``ScalarMappable.callbacksSM`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -is deprecated. Use ``ScalarMappable.callbacks`` instead. diff --git a/doc/api/next_api_changes/deprecations/20806-TH.rst b/doc/api/next_api_changes/deprecations/20806-TH.rst deleted file mode 100644 index 2a9008e75265..000000000000 --- a/doc/api/next_api_changes/deprecations/20806-TH.rst +++ /dev/null @@ -1,4 +0,0 @@ -``matplotlib.cm.LUTSIZE`` -~~~~~~~~~~~~~~~~~~~~~~~~~ -is deprecated. Use :rc:`image.lut` instead. This value only affects colormap -quantization levels for default colormaps generated at module import time. diff --git a/doc/api/next_api_changes/deprecations/20881-AL.rst b/doc/api/next_api_changes/deprecations/20881-AL.rst deleted file mode 100644 index 71c1223a5d55..000000000000 --- a/doc/api/next_api_changes/deprecations/20881-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``matplotlib.backends.qt_compat.ETS`` and ``matplotlib.backends.qt_compat.QT_RC_MAJOR_VERSION`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... are deprecated, with no replacement. diff --git a/doc/api/next_api_changes/deprecations/21126-TH.rst b/doc/api/next_api_changes/deprecations/21126-TH.rst deleted file mode 100644 index 43df16287319..000000000000 --- a/doc/api/next_api_changes/deprecations/21126-TH.rst +++ /dev/null @@ -1,2 +0,0 @@ -Passing formatting parameters positionally to ``stem()`` is deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/api/next_api_changes/deprecations/XXXXX-AL.rst b/doc/api/next_api_changes/deprecations/XXXXX-AL.rst deleted file mode 100644 index bd15c20af312..000000000000 --- a/doc/api/next_api_changes/deprecations/XXXXX-AL.rst +++ /dev/null @@ -1,3 +0,0 @@ -``matplotlib.streamplot.get_integrator`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -... is deprecated. diff --git a/doc/api/next_api_changes/removals/20444-CE.rst b/doc/api/next_api_changes/removals/20444-CE.rst deleted file mode 100644 index 7ae2e4242a39..000000000000 --- a/doc/api/next_api_changes/removals/20444-CE.rst +++ /dev/null @@ -1,4 +0,0 @@ -Change the drawing parameter in ArrowStyle -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For drawing arrows heads or brackets at the arrow, use the ``arrow`` -parameter with the drawing of the desired arrow. diff --git a/doc/api/prev_api_changes/api_changes_3.5.0.rst b/doc/api/prev_api_changes/api_changes_3.5.0.rst index 96c4f390eb1a..890484bcd19a 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0.rst @@ -7,6 +7,8 @@ API Changes for 3.5.0 .. include:: /api/prev_api_changes/api_changes_3.5.0/behaviour.rst +.. include:: /api/prev_api_changes/api_changes_3.5.0/deprecations.rst + .. include:: /api/prev_api_changes/api_changes_3.5.0/removals.rst .. include:: /api/prev_api_changes/api_changes_3.5.0/development.rst diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst new file mode 100644 index 000000000000..3118c2bca0e6 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst @@ -0,0 +1,375 @@ +Deprecations +------------ + +Discouraged: ``Figure`` parameters *tight_layout* and *constrained_layout* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Figure`` parameters *tight_layout* and *constrained_layout* are +triggering competing layout mechanisms and thus should not be used together. + +To make the API clearer, we've merged them under the new parameter *layout* +with values 'constrained' (equal to ``constrained_layout=True``), 'tight' +(equal to ``tight_layout=True``). If given, *layout* takes precedence. + +The use of *tight_layout* and *constrained_layout* is discouraged in favor of +*layout*. However, these parameters will stay available for backward +compatibility. + +Modification of ``Axes`` children sublists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See :ref:`Behavioural API Changes 3.5 - Axes children combined` for more +information; modification of the following sublists is deprecated: + +* ``Axes.artists`` +* ``Axes.collections`` +* ``Axes.images`` +* ``Axes.lines`` +* ``Axes.patches`` +* ``Axes.tables`` +* ``Axes.texts`` + +To remove an Artist, use its `.Artist.remove` method. To add an Artist, use the +corresponding ``Axes.add_*`` method. + +Passing incorrect types to ``Axes.add_*`` methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following ``Axes.add_*`` methods will now warn if passed an unexpected +type. See their documentation for the types they expect. + +- `.Axes.add_collection` +- `.Axes.add_image` +- `.Axes.add_line` +- `.Axes.add_patch` +- `.Axes.add_table` + +Discouraged: ``plot_date`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The use of `~.Axes.plot_date` is discouraged. This method exists for historic +reasons and may be deprecated in the future. + +- ``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`. + +``epoch2num`` and ``num2epoch`` are deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These methods convert from unix timestamps to matplotlib floats, but are not +used internally to matplotlib, and should not be needed by end users. To +convert a unix timestamp to datetime, simply use +`datetime.datetime.utcfromtimestamp`, or to use NumPy `~numpy.datetime64` +``dt = np.datetim64(e*1e6, 'us')``. + +Auto-removal of grids by `~.Axes.pcolor` and `~.Axes.pcolormesh` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`~.Axes.pcolor` and `~.Axes.pcolormesh` currently remove any visible axes major +grid. This behavior is deprecated; please explicitly call ``ax.grid(False)`` to +remove the grid. + +The first parameter of ``Axes.grid`` and ``Axis.grid`` has been renamed to *visible* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The parameter was previously named *b*. This deprecation only matters if that +parameter was passed using a keyword argument, e.g. ``grid(b=False)``. + +Unification and cleanup of Selector widget API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The API for Selector widgets has been unified to use: + +- *props* for the properties of the Artist representing the selection. +- *handle_props* for the Artists representing handles for modifying the + selection. +- *grab_range* for the maximal tolerance to grab a handle with the mouse. + +Additionally, several internal parameters and attribute have been deprecated +with the intention of keeping them private. + +RectangleSelector and EllipseSelector +..................................... + +The *drawtype* keyword argument to `~matplotlib.widgets.RectangleSelector` is +deprecated. In the future the only behaviour will be the default behaviour of +``drawtype='box'``. + +Support for ``drawtype=line`` will be removed altogether as it is not clear +which points are within and outside a selector that is just a line. As a +result, the *lineprops* keyword argument to +`~matplotlib.widgets.RectangleSelector` is also deprecated. + +To retain the behaviour of ``drawtype='none'``, use ``rectprops={'visible': +False}`` to make the drawn `~matplotlib.patches.Rectangle` invisible. + +Cleaned up attributes and arguments are: + +- The ``active_handle`` attribute has been privatized and deprecated. +- The ``drawtype`` attribute has been privatized and deprecated. +- The ``eventpress`` attribute has been privatized and deprecated. +- The ``eventrelease`` attribute has been privatized and deprecated. +- The ``interactive`` attribute has been privatized and deprecated. +- The *marker_props* argument is deprecated, use *handle_props* instead. +- The *maxdist* argument is deprecated, use *grab_range* instead. +- The *rectprops* argument is deprecated, use *props* instead. +- The ``rectprops`` attribute has been privatized and deprecated. +- The ``state`` attribute has been privatized and deprecated. +- The ``to_draw`` attribute has been privatized and deprecated. + +PolygonSelector +............... + +- The *line* attribute is deprecated. If you want to change the selector artist + properties, use the ``set_props`` or ``set_handle_props`` methods. +- The *lineprops* argument is deprecated, use *props* instead. +- The *markerprops* argument is deprecated, use *handle_props* instead. +- The *maxdist* argument and attribute is deprecated, use *grab_range* instead. +- The *vertex_select_radius* argument and attribute is deprecated, use + *grab_range* instead. + +SpanSelector +............ + +- The ``active_handle`` attribute has been privatized and deprecated. +- The ``eventpress`` attribute has been privatized and deprecated. +- The ``eventrelease`` attribute has been privatized and deprecated. +- The *maxdist* argument and attribute is deprecated, use *grab_range* instead. +- The ``pressv`` attribute has been privatized and deprecated. +- The ``prev`` attribute has been privatized and deprecated. +- The ``rect`` attribute has been privatized and deprecated. +- The *rectprops* argument is deprecated, use *props* instead. +- The ``rectprops`` attribute has been privatized and deprecated. +- The *span_stays* argument is deprecated, use the *interactive* argument + instead. +- The ``span_stays`` attribute has been privatized and deprecated. +- The ``state`` attribute has been privatized and deprecated. + +LassoSelector +............. + +- The *lineprops* argument is deprecated, use *props* instead. +- The ``onpress`` and ``onrelease`` methods are deprecated. They are straight + aliases for ``press`` and ``release``. + +``ConversionInterface.convert`` no longer needs to accept unitless values +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, custom subclasses of `.units.ConversionInterface` needed to +implement a ``convert`` method that not only accepted instances of the unit, +but also unitless values (which are passed through as is). This is no longer +the case (``convert`` is never called with a unitless value), and such support +in `.StrCategoryConverter` is deprecated. Likewise, the +`.ConversionInterface.is_numlike` helper is deprecated. + +Consider calling `.Axis.convert_units` instead, which still supports unitless +values. + +Locator and Formatter wrapper methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``set_view_interval``, ``set_data_interval`` and ``set_bounds`` methods of +`.Locator`\s and `.Formatter`\s (and their common base class, TickHelper) are +deprecated. Directly manipulate the view and data intervals on the underlying +axis instead. + +Unused positional parameters to ``print_`` methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +None of the ``print_`` methods implemented by canvas subclasses used +positional arguments other that the first (the output filename or file-like), +so these extra parameters are deprecated. + +``QuadMesh`` signature +~~~~~~~~~~~~~~~~~~~~~~ + +The `.QuadMesh` signature :: + + def __init__(meshWidth, meshHeight, coordinates, + antialiased=True, shading='flat', **kwargs) + +is deprecated and replaced by the new signature :: + + def __init__(coordinates, *, antialiased=True, shading='flat', **kwargs) + +In particular: + +- The *coordinates* argument must now be a (M, N, 2) array-like. Previously, + the grid shape was separately specified as (*meshHeight* + 1, *meshWidth* + + 1) and *coordinates* could be an array-like of any shape with M * N * 2 + elements. +- All parameters except *coordinates* are keyword-only now. + +rcParams will no longer cast inputs to str +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +After a deprecation period, rcParams that expect a (non-pathlike) str will no +longer cast non-str inputs using `str`. This will avoid confusing errors in +subsequent code if e.g. a list input gets implicitly cast to a str. + +Case-insensitive scales +~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, scales could be set case-insensitively (e.g., +``set_xscale("LoG")``). This is deprecated; all builtin scales use lowercase +names. + +Interactive cursor details +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Setting a mouse cursor on a window has been moved from the toolbar to the +canvas. Consequently, several implementation details on toolbars and within +backends have been deprecated. + +``NavigationToolbar2.set_cursor`` and ``backend_tools.SetCursorBase.set_cursor`` +................................................................................ + +Instead, use the `.FigureCanvasBase.set_cursor` method on the canvas (available +as the ``canvas`` attribute on the toolbar or the Figure.) + +``backend_tools.SetCursorBase`` and subclasses +.............................................. + +``backend_tools.SetCursorBase`` was subclassed to provide backend-specific +implementations of ``set_cursor``. As that is now deprecated, the subclassing +is no longer necessary. Consequently, the following subclasses are also +deprecated: + +- ``matplotlib.backends.backend_gtk3.SetCursorGTK3`` +- ``matplotlib.backends.backend_qt5.SetCursorQt`` +- ``matplotlib.backends._backend_tk.SetCursorTk`` +- ``matplotlib.backends.backend_wx.SetCursorWx`` + +Instead, use the `.backend_tools.ToolSetCursor` class. + +``cursord`` in GTK, Qt, and wx backends +....................................... + +The ``backend_gtk3.cursord``, ``backend_qt.cursord``, and +``backend_wx.cursord`` dictionaries are deprecated. This makes the GTK module +importable on headless environments. + +Miscellaneous deprecations +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``is_url`` and ``URL_REGEX`` are deprecated. (They were previously defined in + the toplevel :mod:`matplotlib` module.) +- The ``ArrowStyle.beginarrow`` and ``ArrowStyle.endarrow`` attributes are + deprecated; use the ``arrow`` attribute to define the desired heads and tails + of the arrow. +- ``backend_pgf.LatexManager.str_cache`` is deprecated. +- ``backends.qt_compat.ETS`` and ``backends.qt_compat.QT_RC_MAJOR_VERSION`` are + deprecated, with no replacement. +- The ``blocking_input`` module has been deprecated. Instead, use + ``canvas.start_event_loop()`` and ``canvas.stop_event_loop()`` while + connecting event callbacks as needed. +- ``cbook.report_memory`` is deprecated; use ``psutil.virtual_memory`` instead. +- ``cm.LUTSIZE`` is deprecated. Use :rc:`image.lut` instead. This value only + affects colormap quantization levels for default colormaps generated at + module import time. +- ``Colorbar.patch`` is deprecated; this attribute is not correctly updated + anymore. +- ``ContourLabeler.get_label_width`` is deprecated. +- ``dviread.PsfontsMap`` now raises LookupError instead of KeyError for missing + fonts. +- ``Dvi.baseline`` is deprecated (with no replacement). +- The *format* parameter of ``dviread.find_tex_file`` is deprecated (with no + replacement). +- ``FancyArrowPatch.get_path_in_displaycoord`` and + ``ConnectionPath.get_path_in_displaycoord`` are deprecated. The path in + display coordinates can still be obtained, as for other patches, using + ``patch.get_transform().transform_path(patch.get_path())``. +- The ``font_manager.win32InstalledFonts`` and + ``font_manager.get_fontconfig_fonts`` helper functions have been deprecated. +- All parameters of ``imshow`` starting from *aspect* will become keyword-only. +- ``QuadMesh.convert_mesh_to_paths`` and ``QuadMesh.convert_mesh_to_triangles`` + are deprecated. ``QuadMesh.get_paths()`` can be used as an alternative for + the former; there is no replacement for the latter. +- ``ScalarMappable.callbacksSM`` is deprecated. Use + ``ScalarMappable.callbacks`` instead. +- ``streamplot.get_integrator`` is deprecated. +- ``style.core.STYLE_FILE_PATTERN``, ``style.core.load_base_library``, and + ``style.core.iter_user_libraries`` are deprecated. +- ``SubplotParams.validate`` is deprecated. Use `.SubplotParams.update` to + change `.SubplotParams` while always keeping it in a valid state. +- The ``grey_arrayd``, ``font_family``, ``font_families``, and ``font_info`` + attributes of `.TexManager` are deprecated. +- ``Text.get_prop_tup`` is deprecated with no replacements (because the `.Text` + class cannot know whether a backend needs to update cache e.g. when the + text's color changes). +- ``Tick.apply_tickdir`` didn't actually update the tick markers on the + existing Line2D objects used to draw the ticks and is deprecated; use + `.Axis.set_tick_params` instead. +- ``tight_layout.auto_adjust_subplotpars`` is deprecated. + +- The ``grid_info`` attribute of ``axisartist`` classes has been deprecated. +- ``axisartist.clip_path`` is deprecated with no replacement. +- ``axes_grid1.axes_grid.CbarAxes`` and ``axes_grid1.axisartist.CbarAxes`` are + deprecated (they are now dynamically generated based on the owning axes + class). +- The ``axes_grid1.Divider.get_vsize_hsize`` and + ``axes_grid1.Grid.get_vsize_hsize`` methods are deprecated. Copy their + implementations if needed. +- ``AxesDivider.append_axes(..., add_to_figure=False)`` is deprecated. Use + ``ax.remove()`` to remove the Axes from the figure if needed. +- ``FixedAxisArtistHelper.change_tick_coord`` is deprecated with no + replacement. +- ``floating_axes.GridHelperCurveLinear.get_boundary`` is deprecated, with no + replacement. +- ``ParasiteAxesBase.get_images_artists`` has been deprecated. + +- The "units finalize" signal (previously emitted by Axis instances) is + deprecated. Connect to "units" instead. +- Passing formatting parameters positionally to ``stem()`` is deprecated + +``plot_directive`` deprecations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``:encoding:`` option to ``.. plot`` directive has had no effect since +Matplotlib 1.3.1, and is now deprecated. + +The following helpers in `matplotlib.sphinxext.plot_directive` are deprecated: + +- ``unescape_doctest`` (use `doctest.script_from_examples` instead), +- ``split_code_at_show``, +- ``run_code``. + +Testing support +~~~~~~~~~~~~~~~ + +``matplotlib.test()`` is deprecated +................................... + +Run tests using ``pytest`` from the commandline instead. The variable +``matplotlib.default_test_modules`` is only used for ``matplotlib.test()`` and +is thus deprecated as well. + +To test an installed copy, be sure to specify both ``matplotlib`` and +``mpl_toolkits`` with ``--pyargs``:: + + python -m pytest --pyargs matplotlib.tests mpl_toolkits.tests + +See :ref:`testing` for more details. + +Unused pytest fixtures and markers +.................................. + +The fixture ``matplotlib.testing.conftest.mpl_image_comparison_parameters`` is +not used internally by Matplotlib. If you use this please copy it into your +code base. + +The ``@pytest.mark.style`` marker is deprecated; use ``@mpl.style.context``, +which has the same effect. + +Support for ``nx1 = None`` or ``ny1 = None`` in ``AxesLocator`` and ``Divider.locate`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In `.axes_grid1.axes_divider`, various internal APIs will stop supporting +passing ``nx1 = None`` or ``ny1 = None`` to mean ``nx + 1`` or ``ny + 1``, in +preparation for a possible future API which allows indexing and slicing of +dividers (possibly ``divider[a:b] == divider.new_locator(a, b)``, but also +``divider[a:] == divider.new_locator(a, )``). The user-facing +`.Divider.new_locator` API is unaffected -- it correctly normalizes ``nx1 = +None`` and ``ny1 = None`` as needed. From 1ed35422d4bc139369ceffe843c2164fd4d3aaaa Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 27 Sep 2021 21:42:53 -0400 Subject: [PATCH 044/130] Document API change in ContourSet Also, update documentation for Attributes. Followup to #19623; fixes #20906. --- .../prev_api_changes/api_changes_3.5.0/behaviour.rst | 11 +++++++++++ lib/matplotlib/contour.py | 5 ++--- 2 files changed, 13 insertions(+), 3 deletions(-) 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 7e1fdefc4936..62cdb0a32854 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 @@ -184,6 +184,17 @@ Usually norms have a scale, and the advantage of having a `~.scale.ScaleBase` attached to a norm is to provide a scale, and associated tick locators and formatters, for the colorbar. +``ContourSet`` always use ``PathCollection`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to correct rendering issues with closed loops, the `.ContourSet` now +creates a `.PathCollection` instead of a `.LineCollection` for line contours. +This type matches the artist used for filled contours. + +This affects `.ContourSet` itself and its subclasses, `.QuadContourSet` +(returned by `.Axes.contour`), and `.TriContourSet` (returned by +`.Axes.tricontour`). + ``hatch.SmallFilledCircles`` inherits from ``hatch.Circles`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 26ce26d43105..9f1307c44791 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -662,10 +662,9 @@ def _find_closest_point_on_path(xys, p): ax : `~matplotlib.axes.Axes` The Axes object in which the contours are drawn. -collections : `.silent_list` of `.LineCollection`\s or `.PathCollection`\s +collections : `.silent_list` of `.PathCollection`\s The `.Artist`\s representing the contour. This is a list of - `.LineCollection`\s for line contours and a list of `.PathCollection`\s - for filled contours. + `.PathCollection`\s for both line and filled contours. levels : array The values of the contour levels. From 27f7f405d8044df3362917805221b2ff1aa1e077 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:17:14 +0200 Subject: [PATCH 045/130] Move plot_types stats/barbs to arrays/barbs and basic/pie to stats/pie --- doc/sphinxext/gallery_order.py | 7 ++++--- plot_types/{stats => arrays}/barbs.py | 0 plot_types/{basic => stats}/pie.py | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename plot_types/{stats => arrays}/barbs.py (100%) rename plot_types/{basic => stats}/pie.py (100%) diff --git a/doc/sphinxext/gallery_order.py b/doc/sphinxext/gallery_order.py index 8705d473c81e..589fed453f06 100644 --- a/doc/sphinxext/gallery_order.py +++ b/doc/sphinxext/gallery_order.py @@ -68,12 +68,13 @@ def __call__(self, item): # **Plot Types # Basic - "plot", "scatter_plot", "bar", "stem", "step", "pie", "fill_between", + "plot", "scatter_plot", "bar", "stem", "step", "fill_between", # Arrays - "imshow", "pcolormesh", "contour", "contourf", "quiver", "streamplot", + "imshow", "pcolormesh", "contour", "contourf", + "barbs", "quiver", "streamplot", # Stats "hist_plot", "boxplot_plot", "errorbar_plot", "violin", - "barbs", "eventplot", "hist2d", "hexbin", + "eventplot", "hist2d", "hexbin", "pie", # Unstructured "tricontour", "tricontourf", "tripcolor", "triplot", ] diff --git a/plot_types/stats/barbs.py b/plot_types/arrays/barbs.py similarity index 100% rename from plot_types/stats/barbs.py rename to plot_types/arrays/barbs.py diff --git a/plot_types/basic/pie.py b/plot_types/stats/pie.py similarity index 100% rename from plot_types/basic/pie.py rename to plot_types/stats/pie.py From 9b58d8e5f3ff92144d1caacf74b06552b958d786 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:19:23 +0200 Subject: [PATCH 046/130] Update plot_types barbs to show a more realistic use case Barbs are a visualization of vector fields and usually plotted on a regular grid. --- plot_types/arrays/barbs.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/plot_types/arrays/barbs.py b/plot_types/arrays/barbs.py index 7b632139662d..a54424a58b46 100644 --- a/plot_types/arrays/barbs.py +++ b/plot_types/arrays/barbs.py @@ -1,28 +1,33 @@ """ -=========== -barbs(U, V) -=========== +================ +barbs(X, Y U, V) +================ See `~matplotlib.axes.Axes.barbs`. """ import matplotlib.pyplot as plt import numpy as np -plt.style.use('_mpl-gallery') +plt.style.use('_mpl-gallery-nogrid') # make data: -np.random.seed(1) -X = [[2, 4, 6]] -Y = [[1.5, 3, 2]] -U = np.zeros_like(X) -V = -np.ones_like(X) * np.linspace(50, 100, 3) +X, Y = np.meshgrid([1, 2, 3, 4], [1, 2, 3, 4]) +angle = np.pi / 180 * np.array([[15., 30, 35, 45], + [25., 40, 55, 60], + [35., 50, 65, 75], + [45., 60, 75, 90]]) +amplitude = np.array([[5, 10, 25, 50], + [10, 15, 30, 60], + [15, 26, 50, 70], + [20, 45, 80, 100]]) +U = amplitude * np.sin(angle) +V = amplitude * np.cos(angle) # plot: fig, ax = plt.subplots() -ax.barbs(X, Y, U, V, barbcolor="C0", flagcolor="C0", length=10, linewidth=1.5) +ax.barbs(X, Y, U, V, barbcolor="C0", flagcolor="C0", length=7, linewidth=1.5) -ax.set(xlim=(0, 8), xticks=np.arange(1, 8), - ylim=(0, 8), yticks=np.arange(1, 8)) +ax.set(xlim=(0, 4.5), ylim=(0, 4.5)) plt.show() From ce8342f68e5b1e7a02b1443ee8d514a95a3adbb8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:57:15 +0200 Subject: [PATCH 047/130] Simplify data generation for imshow and pcolormesh plot_types --- plot_types/arrays/imshow.py | 3 +-- plot_types/arrays/pcolormesh.py | 13 +++---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/plot_types/arrays/imshow.py b/plot_types/arrays/imshow.py index beca827cdc80..be647d1f2924 100644 --- a/plot_types/arrays/imshow.py +++ b/plot_types/arrays/imshow.py @@ -12,9 +12,8 @@ plt.style.use('_mpl-gallery-nogrid') # make data -X, Y = np.meshgrid(np.linspace(-3, 3, 256), np.linspace(-3, 3, 256)) +X, Y = np.meshgrid(np.linspace(-3, 3, 16), np.linspace(-3, 3, 16)) Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2) -Z = Z[::16, ::16] # plot fig, ax = plt.subplots() diff --git a/plot_types/arrays/pcolormesh.py b/plot_types/arrays/pcolormesh.py index dad94dd2ae5d..b490dcb99d3f 100644 --- a/plot_types/arrays/pcolormesh.py +++ b/plot_types/arrays/pcolormesh.py @@ -12,18 +12,11 @@ plt.style.use('_mpl-gallery-nogrid') -# make full-res data -X, Y = np.meshgrid(np.linspace(-3, 3, 256), np.linspace(-3, 3, 256)) +# make data with uneven sampling in x +x = [-3, -2, -1.6, -1.2, -.8, -.5, -.2, .1, .3, .5, .8, 1.1, 1.5, 1.9, 2.3, 3] +X, Y = np.meshgrid(x, np.linspace(-3, 3, 128)) Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2) -# sample unevenly in x: -dx = np.sqrt((np.arange(16) - 8)**2) + 6 -dx = np.floor(dx / sum(dx) * 255) -xint = np.cumsum(dx).astype('int') -X = X[0, xint] -Y = Y[::8, 0] -Z = Z[::8, :][:, xint] - # plot fig, ax = plt.subplots() From 3dd8eeebc25cc889e23b44a3c1d89500e9198971 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 30 Sep 2021 02:44:27 +0200 Subject: [PATCH 048/130] DOC: Adapt some colors in examples - Fill between alpha: Use default color palette more - Hatch filled historgrams: Soften fill colors using alpha - Bar chart with gradients: Replace the strong orange background gradient with softened red-green horizontal gradient. This looks nicer and the red-green background could even be used as a semantic good-bad indicator. - Stackplot: Soften fill colors using alpha --- .../fill_between_alpha.py | 45 ++++++++++--------- .../lines_bars_and_markers/filled_step.py | 1 + .../lines_bars_and_markers/gradient_bar.py | 4 +- .../lines_bars_and_markers/stackplot_demo.py | 2 +- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/examples/lines_bars_and_markers/fill_between_alpha.py b/examples/lines_bars_and_markers/fill_between_alpha.py index acd6cebbbd7f..30e7f6be3dcd 100644 --- a/examples/lines_bars_and_markers/fill_between_alpha.py +++ b/examples/lines_bars_and_markers/fill_between_alpha.py @@ -17,9 +17,6 @@ import matplotlib.cbook as cbook -# Fixing random state for reproducibility -np.random.seed(19680801) - # load up some sample financial data r = (cbook.get_sample_data('goog.npz', np_load=True)['price_data'] .view(np.recarray)) @@ -29,7 +26,7 @@ pricemin = r.close.min() ax1.plot(r.date, r.close, lw=2) -ax2.fill_between(r.date, pricemin, r.close, facecolor='blue', alpha=0.5) +ax2.fill_between(r.date, pricemin, r.close, alpha=0.7) for ax in ax1, ax2: ax.grid(True) @@ -52,16 +49,19 @@ # # Our next example computes two populations of random walkers with a # different mean and standard deviation of the normal distributions from -# which the steps are drawn. We use shared regions to plot +/- one +# which the steps are drawn. We use filled regions to plot +/- one # standard deviation of the mean position of the population. Here the # alpha channel is useful, not just aesthetic. +# Fixing random state for reproducibility +np.random.seed(19680801) + Nsteps, Nwalkers = 100, 250 t = np.arange(Nsteps) # an (Nsteps x Nwalkers) array of random walk steps -S1 = 0.002 + 0.01*np.random.randn(Nsteps, Nwalkers) -S2 = 0.004 + 0.02*np.random.randn(Nsteps, Nwalkers) +S1 = 0.004 + 0.02*np.random.randn(Nsteps, Nwalkers) +S2 = 0.002 + 0.01*np.random.randn(Nsteps, Nwalkers) # an (Nsteps x Nwalkers) array of random walker positions X1 = S1.cumsum(axis=0) @@ -77,10 +77,10 @@ # plot it! fig, ax = plt.subplots(1) -ax.plot(t, mu1, lw=2, label='mean population 1', color='blue') -ax.plot(t, mu2, lw=2, label='mean population 2', color='yellow') -ax.fill_between(t, mu1+sigma1, mu1-sigma1, facecolor='blue', alpha=0.5) -ax.fill_between(t, mu2+sigma2, mu2-sigma2, facecolor='yellow', alpha=0.5) +ax.plot(t, mu1, lw=2, label='mean population 1') +ax.plot(t, mu2, lw=2, label='mean population 2') +ax.fill_between(t, mu1+sigma1, mu1-sigma1, facecolor='C0', alpha=0.4) +ax.fill_between(t, mu2+sigma2, mu2-sigma2, facecolor='C1', alpha=0.4) ax.set_title(r'random walkers empirical $\mu$ and $\pm \sigma$ interval') ax.legend(loc='upper left') ax.set_xlabel('num steps') @@ -93,11 +93,14 @@ # as the x, ymin and ymax arguments, and only fills in the region where # the boolean mask is True. In the example below, we simulate a single # random walker and compute the analytic mean and standard deviation of -# the population positions. The population mean is shown as the black -# dashed line, and the plus/minus one sigma deviation from the mean is -# shown as the yellow filled region. We use the where mask -# ``X > upper_bound`` to find the region where the walker is above the one -# sigma boundary, and shade that region blue. +# the population positions. The population mean is shown as the dashed +# line, and the plus/minus one sigma deviation from the mean is shown +# as the filled region. We use the where mask ``X > upper_bound`` to +# find the region where the walker is outside the one sigma boundary, +# and shade that region red. + +# Fixing random state for reproducibility +np.random.seed(1) Nsteps = 500 t = np.arange(Nsteps) @@ -114,16 +117,16 @@ upper_bound = mu*t + sigma*np.sqrt(t) fig, ax = plt.subplots(1) -ax.plot(t, X, lw=2, label='walker position', color='blue') -ax.plot(t, mu*t, lw=1, label='population mean', color='black', ls='--') -ax.fill_between(t, lower_bound, upper_bound, facecolor='yellow', alpha=0.5, +ax.plot(t, X, lw=2, label='walker position') +ax.plot(t, mu*t, lw=1, label='population mean', color='C0', ls='--') +ax.fill_between(t, lower_bound, upper_bound, facecolor='C0', alpha=0.4, label='1 sigma range') ax.legend(loc='upper left') # here we use the where argument to only fill the region where the # walker is above the population 1 sigma boundary -ax.fill_between(t, upper_bound, X, where=X > upper_bound, facecolor='blue', - alpha=0.5) +ax.fill_between(t, upper_bound, X, where=X > upper_bound, fc='red', alpha=0.4) +ax.fill_between(t, lower_bound, X, where=X < lower_bound, fc='red', alpha=0.4) ax.set_xlabel('num steps') ax.set_ylabel('position') ax.grid() diff --git a/examples/lines_bars_and_markers/filled_step.py b/examples/lines_bars_and_markers/filled_step.py index acd4823b4f46..a4185366b7a2 100644 --- a/examples/lines_bars_and_markers/filled_step.py +++ b/examples/lines_bars_and_markers/filled_step.py @@ -53,6 +53,7 @@ def filled_hist(ax, edges, values, bottoms=None, orientation='v', "not {o}".format(o=orientation)) kwargs.setdefault('step', 'post') + kwargs.setdefault('alpha', 0.7) edges = np.asarray(edges) values = np.asarray(values) if len(edges) - 1 != len(values): diff --git a/examples/lines_bars_and_markers/gradient_bar.py b/examples/lines_bars_and_markers/gradient_bar.py index 70998fe138a7..2441f4d00744 100644 --- a/examples/lines_bars_and_markers/gradient_bar.py +++ b/examples/lines_bars_and_markers/gradient_bar.py @@ -70,8 +70,8 @@ def gradient_bar(ax, x, y, width=0.5, bottom=0): ax.set(xlim=xlim, ylim=ylim, autoscale_on=False) # background image -gradient_image(ax, direction=0, extent=(0, 1, 0, 1), transform=ax.transAxes, - cmap=plt.cm.Oranges, cmap_range=(0.1, 0.6)) +gradient_image(ax, direction=1, extent=(0, 1, 0, 1), transform=ax.transAxes, + cmap=plt.cm.RdYlGn, cmap_range=(0.2, 0.8), alpha=0.5) N = 10 x = np.arange(N) + 0.15 diff --git a/examples/lines_bars_and_markers/stackplot_demo.py b/examples/lines_bars_and_markers/stackplot_demo.py index 6c9acfcacc34..142b3d2a0ce0 100644 --- a/examples/lines_bars_and_markers/stackplot_demo.py +++ b/examples/lines_bars_and_markers/stackplot_demo.py @@ -29,7 +29,7 @@ fig, ax = plt.subplots() ax.stackplot(year, population_by_continent.values(), - labels=population_by_continent.keys()) + labels=population_by_continent.keys(), alpha=0.8) ax.legend(loc='upper left') ax.set_title('World population') ax.set_xlabel('Year') From 5d9b312457f8e6faaaf3f267dba9b1ae7981839a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 29 Sep 2021 22:05:30 -0400 Subject: [PATCH 049/130] Apply changes from review --- doc/conf.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 08a944c39dd2..7774b0f726eb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -23,7 +23,7 @@ import time # Release mode enables optimizations and other related options. -_doc_release_mode = tags.has('release') # noqa +is_release_build = tags.has('release') # noqa # are we running circle CI? CIRCLECI = 'CIRCLECI' in os.environ @@ -179,7 +179,8 @@ def _check_dependencies(): 'remove_config_comments': True, 'min_reported_time': 1, 'thumbnail_size': (320, 224), - 'compress_images': ('thumbnails', 'images') if _doc_release_mode else (), + # Compression is a significant effort that we skip for local and CI builds. + 'compress_images': ('thumbnails', 'images') if is_release_build else (), 'matplotlib_animations': True, 'image_srcset': ["2x"], 'junit': '../test-results/sphinx-gallery/junit.xml' if CIRCLECI else '', @@ -296,7 +297,9 @@ def _check_dependencies(): html_logo = "_static/logo2.svg" html_theme_options = { "logo_link": "index", - "collapse_navigation": not _doc_release_mode, + # collapse_navigation in pydata-sphinx-theme is slow, so skipped for local + # and CI builds https://github.com/pydata/pydata-sphinx-theme/pull/386 + "collapse_navigation": not is_release_build, "icon_links": [ { "name": "gitter", @@ -322,7 +325,7 @@ def _check_dependencies(): "show_prev_next": False, "navbar_center": ["mpl_nav_bar.html"], } -include_analytics = _doc_release_mode +include_analytics = is_release_build if include_analytics: html_theme_options["google_analytics_id"] = "UA-55954603-1" @@ -542,6 +545,8 @@ def _check_dependencies(): def reduce_plot_formats(app): + # Fox CI and local builds, we don't need all the default plot formats, so + # only generate the directly useful one for the current builder. if app.builder.name == 'html': keep = 'png' elif app.builder.name == 'latex': @@ -560,7 +565,7 @@ def setup(app): bld_type = 'rel' app.add_config_value('releaselevel', bld_type, 'env') - if not _doc_release_mode: + if not is_release_build: app.connect('builder-inited', reduce_plot_formats) # ----------------------------------------------------------------------------- From 9d2e67b0d3728f42c52eb0d4cae25de45a51aba9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 30 Sep 2021 10:14:01 +0200 Subject: [PATCH 050/130] Small doc nits. Typo/capitalization/punctuation/styling. I think the only debatable point is whether to emphasize "most" in the Reference part, but I didn't see any good reason to do it (and it looks a bit too much to me). --- doc/faq/environment_variables_faq.rst | 2 +- doc/faq/index.rst | 2 +- doc/faq/troubleshooting_faq.rst | 8 ++++---- doc/index.rst | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/faq/environment_variables_faq.rst b/doc/faq/environment_variables_faq.rst index 9af0420c835a..dc26fa9a53f7 100644 --- a/doc/faq/environment_variables_faq.rst +++ b/doc/faq/environment_variables_faq.rst @@ -80,7 +80,7 @@ searched first or last? To append to an existing environment variable:: setenv PATH ${PATH}:~/bin # csh/tcsh To make your changes available in the future, add the commands to your -:file:`~/.bashrc`/:file:`.cshrc` file. +:file:`~/.bashrc` or :file:`~/.cshrc` file. .. _setting-windows-environment-variables: diff --git a/doc/faq/index.rst b/doc/faq/index.rst index bb477de21f0e..24e9112465d3 100644 --- a/doc/faq/index.rst +++ b/doc/faq/index.rst @@ -9,7 +9,7 @@ How-to :Release: |version| :Date: |today| - Frequently asked questions about matplotlib + Frequently asked questions about Matplotlib: .. toctree:: :maxdepth: 2 diff --git a/doc/faq/troubleshooting_faq.rst b/doc/faq/troubleshooting_faq.rst index ba5a7735729c..cf9e3b5cf8b9 100644 --- a/doc/faq/troubleshooting_faq.rst +++ b/doc/faq/troubleshooting_faq.rst @@ -46,10 +46,10 @@ locate your :file:`matplotlib/` configuration directory, use >>> mpl.get_configdir() '/home/darren/.config/matplotlib' -On unix-like systems, this directory is generally located in your +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 +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`:: @@ -57,7 +57,7 @@ separate from the configuration directory by default. To locate your >>> mpl.get_cachedir() '/home/darren/.cache/matplotlib' -On windows, both the config directory and the cache directory are +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:: @@ -93,7 +93,7 @@ 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``). +* Your operating system (Linux/Unix users: post the output of ``uname -a``). * Matplotlib version:: diff --git a/doc/index.rst b/doc/index.rst index 42bb4b2a8f5d..7b88cd7374d6 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -78,7 +78,7 @@ Learning resources ^^^^^^^^^ - :doc:`API Reference ` - - :doc:`Axes API ` for *most* plotting methods + - :doc:`Axes API ` for most plotting methods - :doc:`Figure API ` for figure-level methods - Top-level interfaces to create: @@ -98,7 +98,7 @@ Matplotlib. Contributing ------------ -Matplotlib is a community project maitained for and by its users. There are many ways +Matplotlib is a community project maintained for and by its users. There are many ways you can help! - Help other users `on discourse `__ From 785f0d65daac96c087c8ee94534b51746347356b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 30 Sep 2021 10:37:18 +0200 Subject: [PATCH 051/130] Shorten PdfPages FAQ entry. The original wording was inaccurate as both the pdf and pgf backends provide this functionality. I tried fixing that, but duplicating all links to both backends seemed unwieldy; additionally the "narrative" version of the FAQ reads worse (we should just encourage `pp.savefig()` instead of explaining that `plt.savefig()` needs workarounds, and also prefer `with PdfPages(...) as pp:`). Given that the docstring of both classes have examples, I just got rid of most of the FAQ entry instead (see also the next FAQ entries, which also have no code). --- doc/faq/howto_faq.rst | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/doc/faq/howto_faq.rst b/doc/faq/howto_faq.rst index 13d5412c985b..533e7d2b3141 100644 --- a/doc/faq/howto_faq.rst +++ b/doc/faq/howto_faq.rst @@ -110,33 +110,10 @@ on individual elements, e.g.:: Save multiple plots to one pdf file ----------------------------------- -Many image file formats can only have one image per file, but some -formats support multi-page files. Currently only the pdf backend has -support for this. To make a multi-page pdf file, first initialize the -file:: - - from matplotlib.backends.backend_pdf import PdfPages - pp = PdfPages('multipage.pdf') - -You can give the :class:`~matplotlib.backends.backend_pdf.PdfPages` -object to :func:`~matplotlib.pyplot.savefig`, but you have to specify -the format:: - - plt.savefig(pp, format='pdf') - -An easier way is to call -:meth:`PdfPages.savefig `:: - - pp.savefig() - -Finally, the multipage pdf object has to be closed:: - - pp.close() - -The same can be done using the pgf backend:: - - from matplotlib.backends.backend_pgf import PdfPages - +Many image file formats can only have one image per file, but some formats +support multi-page files. Currently, Matplotlib only provides multi-page +output to pdf files, using either the pdf or pgf backends, via the +`.backend_pdf.PdfPages` and `.backend_pgf.PdfPages` classes. .. _howto-auto-adjust: From e13caa4805dc320a774e945122256a071f3be0b5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 30 Sep 2021 15:39:45 -0400 Subject: [PATCH 052/130] DOC: add note about why reading the socket is complicated --- lib/matplotlib/backends/qt_compat.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index f0568d48b62d..9e320e341c4c 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -228,6 +228,12 @@ def _maybe_allow_interrupt(qapp): rsock.fileno(), _enum('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. rsock.setblocking(False) # Clear the socket to re-arm the notifier. @sn.activated.connect From b43ace8897f6f43d8a7383f14d0bcbc62fa0d6fc Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 30 Sep 2021 23:02:41 +0200 Subject: [PATCH 053/130] DOC: Add fill_between to plot_types --- plot_types/basic/fill_between.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 plot_types/basic/fill_between.py diff --git a/plot_types/basic/fill_between.py b/plot_types/basic/fill_between.py new file mode 100644 index 000000000000..afe59824aaea --- /dev/null +++ b/plot_types/basic/fill_between.py @@ -0,0 +1,29 @@ +""" +======================= +fill_between(x, y1, y2) +======================= + +See `~matplotlib.axes.Axes.fill_between`. +""" + +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# make data +np.random.seed(1) +x = np.linspace(0, 8, 16) +y1 = 3 + 4*x/8 + np.random.uniform(0.0, 0.5, len(x)) +y2 = 1 + 2*x/8 + np.random.uniform(0.0, 0.5, len(x)) + +# plot +fig, ax = plt.subplots() + +plt.fill_between(x, y1, y2, alpha=.5, linewidth=0) +plt.plot(x, (y1 + y2)/2, linewidth=2) + +ax.set(xlim=(0, 8), xticks=np.arange(1, 8), + ylim=(0, 8), yticks=np.arange(1, 8)) + +plt.show() From fff325cc3e17e7c2fc97d140546ae9651e22ac93 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 1 Oct 2021 00:22:19 +0200 Subject: [PATCH 054/130] Update plot_types/basic/fill_between.py Co-authored-by: hannah --- plot_types/basic/fill_between.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plot_types/basic/fill_between.py b/plot_types/basic/fill_between.py index afe59824aaea..a454c3c30772 100644 --- a/plot_types/basic/fill_between.py +++ b/plot_types/basic/fill_between.py @@ -20,8 +20,8 @@ # plot fig, ax = plt.subplots() -plt.fill_between(x, y1, y2, alpha=.5, linewidth=0) -plt.plot(x, (y1 + y2)/2, linewidth=2) +ax.fill_between(x, y1, y2, alpha=.5, linewidth=0) +ax.plot(x, (y1 + y2)/2, linewidth=2) ax.set(xlim=(0, 8), xticks=np.arange(1, 8), ylim=(0, 8), yticks=np.arange(1, 8)) From d3b46f2939b54e67aeaa9658ba991afde9bc6c8d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 27 Sep 2021 22:34:37 -0400 Subject: [PATCH 055/130] Clean up doc configuration slightly --- .flake8 | 2 +- doc/conf.py | 41 ++++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.flake8 b/.flake8 index 7094b6c49b5f..9de6a96bfc59 100644 --- a/.flake8 +++ b/.flake8 @@ -85,7 +85,7 @@ per-file-ignores = lib/mpl_toolkits/tests/conftest.py: F401 lib/pylab.py: F401, F403 - doc/conf.py: E402, E501 + doc/conf.py: E402 tutorials/advanced/path_tutorial.py: E402 tutorials/advanced/patheffects_guide.py: E402 tutorials/advanced/transforms_tutorial.py: E402, E501 diff --git a/doc/conf.py b/doc/conf.py index 7774b0f726eb..a48039524b6b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,10 +1,12 @@ # Matplotlib documentation build configuration file, created by # sphinx-quickstart on Fri May 2 12:33:25 2008. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its containing +# dir. # # The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). +# that aren't pickleable (module imports are okay, they're removed +# automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. @@ -30,7 +32,8 @@ # Parse year using SOURCE_DATE_EPOCH, falling back to current time. # https://reproducible-builds.org/specs/source-date-epoch/ -sourceyear = datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))).year +sourceyear = datetime.utcfromtimestamp( + int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))).year # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it @@ -226,9 +229,11 @@ def _check_dependencies(): } project = 'Matplotlib' -copyright = ('2002 - 2012 John Hunter, Darren Dale, Eric Firing, ' - 'Michael Droettboom and the Matplotlib development ' - f'team; 2012 - {sourceyear} The Matplotlib development team') +copyright = ( + '2002 - 2012 John Hunter, Darren Dale, Eric Firing, Michael Droettboom ' + 'and the Matplotlib development team; ' + f'2012 - {sourceyear} The Matplotlib development team' +) # The default replacements for |version| and |release|, also used in various @@ -389,8 +394,10 @@ def _check_dependencies(): # The paper size ('letter' or 'a4'). latex_paper_size = 'letter' -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). +# Grouping the document tree into LaTeX files. +# List of tuples: +# (source start file, target name, title, author, +# document class [howto/manual]) latex_documents = [ ('contents', 'Matplotlib.tex', 'Matplotlib', @@ -618,10 +625,8 @@ def linkcode_resolve(domain, info): except (OSError, TypeError): lineno = None - if lineno: - linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) - else: - linespec = "" + linespec = (f"#L{lineno:d}-L{lineno + len(source) - 1:d}" + if lineno else "") startdir = Path(matplotlib.__file__).parent.parent fn = os.path.relpath(fn, start=startdir).replace(os.path.sep, '/') @@ -629,12 +634,10 @@ def linkcode_resolve(domain, info): if not fn.startswith(('matplotlib/', 'mpl_toolkits/')): return None - m = re.match(r'^.*post[0-9]+\+\w([a-z0-9]+).\w+$', matplotlib.__version__) - if m: - return "https://github.com/matplotlib/matplotlib/blob/%s/lib/%s%s" % ( - m.group(1), fn, linespec) - else: - return "https://github.com/matplotlib/matplotlib/blob/v%s/lib/%s%s" % ( - matplotlib.__version__, fn, linespec) + m = re.match(r'^.*post[0-9]+\+\w([a-z0-9]+).\w+$', + matplotlib.__version__) + version = m.group(1) if m else f'v{matplotlib.__version__}' + return ("https://github.com/matplotlib/matplotlib/blob" + f"/{version}/lib/{fn}{linespec}") else: extensions.append('sphinx.ext.viewcode') From d1a4a11583fa1e26a2572cd5a0264c5964823fde Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 27 Sep 2021 22:39:40 -0400 Subject: [PATCH 056/130] Re-wrap toplevel readme --- README.rst | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index f6695c964068..078898049a55 100644 --- a/README.rst +++ b/README.rst @@ -42,23 +42,24 @@ .. image:: https://matplotlib.org/_static/logo2.svg -Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. +Matplotlib is a comprehensive library for creating static, animated, and +interactive visualizations in Python. Check out our `home page `_ for more information. .. image:: https://matplotlib.org/_static/readme_preview.png -Matplotlib produces publication-quality figures in a variety of hardcopy formats -and interactive environments across platforms. Matplotlib can be used in Python scripts, -the Python and IPython shell, web application servers, and various -graphical user interface toolkits. +Matplotlib produces publication-quality figures in a variety of hardcopy +formats and interactive environments across platforms. Matplotlib can be used +in Python scripts, the Python and IPython shell, web application servers, and +various graphical user interface toolkits. Install ======= For installation instructions and requirements, see `INSTALL.rst `_ or the -`install `_ documentation. +`install `_ documentation. Test ==== @@ -67,28 +68,36 @@ After installation, launch the test suite:: python -m pytest -Read the `testing guide `_ for more information and alternatives. +Read the `testing guide `_ +for more information and alternatives. Contribute ========== + You've discovered a bug or something else you want to change - excellent! You've worked out a way to fix it – even better! You want to tell us about it – best of all! -Start at the `contributing guide `_! +Start at the `contributing guide +`_! Contact ======= -`Discourse `_ is the discussion forum for general questions and discussions and our recommended starting point. +`Discourse `_ is the discussion forum for +general questions and discussions and our recommended starting point. Our active mailing lists (which are mirrored on Discourse) are: -* `Users `_ mailing list: matplotlib-users@python.org -* `Announcement `_ mailing list: matplotlib-announce@python.org -* `Development `_ mailing list: matplotlib-devel@python.org +* `Users `_ mailing + list: matplotlib-users@python.org +* `Announcement + `_ mailing + list: matplotlib-announce@python.org +* `Development `_ + mailing list: matplotlib-devel@python.org Gitter_ is for coordinating development and asking questions directly related to contributing to matplotlib. @@ -99,21 +108,21 @@ Citing Matplotlib If Matplotlib contributes to a project that leads to publication, please acknowledge this by citing Matplotlib. -`A ready-made citation entry `_ is available. +`A ready-made citation entry `_ is +available. Research notice ~~~~~~~~~~~~~~~ Please note that this repository is participating in a study into sustainability of open source projects. Data will be gathered about this -repository for approximately the next 12 months, starting from June -2021. +repository for approximately the next 12 months, starting from June 2021. -Data collected will include number of contributors, number of PRs, time -taken to close/merge these PRs, and issues closed. +Data collected will include number of contributors, number of PRs, time taken +to close/merge these PRs, and issues closed. -For more information, please visit `the informational -page `__ or -download the `participant information -sheet `__. +For more information, please visit `the informational page +`__ or download the +`participant information sheet +`__. From 96fa1950e7a70387d53712490fe95a8215accc1c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 30 Sep 2021 19:11:12 -0400 Subject: [PATCH 057/130] Fix links to doc sources Instead of figuring out a regex ourselves, just use `packaging` to determine the version. And point to `master` branch if not on an actual tag. Fixes #21230 --- doc/conf.py | 9 ++++----- environment.yml | 1 + requirements/doc/doc-requirements.txt | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index a48039524b6b..e703fc724e06 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -582,8 +582,8 @@ def setup(app): # You can add build old with link_github = False if link_github: - import re import inspect + from packaging.version import parse extensions.append('sphinx.ext.linkcode') @@ -634,10 +634,9 @@ def linkcode_resolve(domain, info): if not fn.startswith(('matplotlib/', 'mpl_toolkits/')): return None - m = re.match(r'^.*post[0-9]+\+\w([a-z0-9]+).\w+$', - matplotlib.__version__) - version = m.group(1) if m else f'v{matplotlib.__version__}' + version = parse(matplotlib.__version__) + tag = 'master' if version.is_devrelease else f'v{version.base_version}' return ("https://github.com/matplotlib/matplotlib/blob" - f"/{version}/lib/{fn}{linespec}") + f"/{tag}/lib/{fn}{linespec}") else: extensions.append('sphinx.ext.viewcode') diff --git a/environment.yml b/environment.yml index eb284c6ab09d..1f9ad4471d2c 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,7 @@ dependencies: - ipython - ipywidgets - numpydoc>=0.8 + - packaging - pydata-sphinx-theme - scipy - sphinx>=1.8.1,!=2.0.0 diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 8be10d4d107c..8e7172966a4e 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -12,6 +12,7 @@ colorspacious ipython ipywidgets numpydoc>=0.8 +packaging>=20 pydata-sphinx-theme>=0.6.0 sphinxcontrib-svg2pdfconverter>=1.1.0 # sphinx-gallery>=0.7 From f521899d9b43371967e262bad931559a03004886 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 1 Oct 2021 12:10:17 +0200 Subject: [PATCH 058/130] Fix release notes typos. --- doc/users/prev_whats_new/whats_new_3.5.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 f48aa1104041..0fc7d33f69a2 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 @@ -116,11 +116,11 @@ Image interpolation now possible at RGBA stage ---------------------------------------------- Images in Matplotlib created via `~.axes.Axes.imshow` are resampled to match -the resolution of the current canvas. It is useful to apply an anto-aliasing +the resolution of the current canvas. It is useful to apply an auto-aliasing filter when downsampling to reduce Moiré effects. By default, interpolation is done on the data, a norm applied, and then the colormapping performed. -However, it is often desireable for the anti-aliasing interpolation to happen +However, it is often desirable for the anti-aliasing interpolation to happen in RGBA space, where the colors are interpolated rather than the data. This usually leads to colors outside the colormap, but visually blends adjacent colors, and is what browsers and other image processing software do. @@ -208,7 +208,7 @@ user-friendly setup. "font.family": "Helvetica" }) -Type 42 Subsetting is now enabled for PDF/PS backends +Type 42 subsetting is now enabled for PDF/PS backends ----------------------------------------------------- `~matplotlib.backends.backend_pdf` and `~matplotlib.backends.backend_ps` now From 49b782750e260240e4e7fb8069e7d04adcb09021 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 1 Oct 2021 15:35:29 +0200 Subject: [PATCH 059/130] DOC: Remove examples/README This is not used anywhere in the build docs and the contents is not particularly useful either. --- examples/README | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 examples/README diff --git a/examples/README b/examples/README deleted file mode 100644 index 84a95a7d66f4..000000000000 --- a/examples/README +++ /dev/null @@ -1,42 +0,0 @@ -Matplotlib examples -=================== - -There are a variety of ways to use Matplotlib, and most of them are -illustrated in the examples in this directory. - -Probably the most common way people use Matplotlib is with the -procedural interface, which follows the MATLAB/IDL/Mathematica approach -of using simple procedures like "plot" or "title" to modify the current -figure. These examples are included in the "pylab_examples" directory. -If you want to write more robust scripts, e.g., for production use or in -a web application server, you will probably want to use the Matplotlib -API for full control. These examples are found in the "api" directory. -Below is a brief description of the different directories found here: - - * animation - Dynamic plots, see the documentation at - https://matplotlib.org/api/animation_api.html - - * axes_grid1 - Examples related to the axes_grid1 toolkit. - - * axisartist - Examples related to the axisartist toolkit. - - * event_handling - How to interact with your figure, mouse presses, - key presses, object picking, etc. - - * misc - Miscellaneous examples. Some demos for loading and working - with record arrays. - - * mplot3d - 3D examples. - - * pylab_examples - The interface to Matplotlib similar to MATLAB. - - * tests - Tests used by Matplotlib developers to check functionality. - (These tests are still sometimes useful, but mostly developers should - use the pytest tests which perform automatic image comparison.) - - * units - Working with unit data and custom types in Matplotlib. - - * user_interfaces - Using Matplotlib in a GUI application, e.g., - Tkinter, wxPython, PyGObject, PyQt widgets. - - * widgets - Examples using interactive widgets. From 66cbf006a55202b8cc4fb7e410177631f721db96 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 1 Oct 2021 15:30:08 +0200 Subject: [PATCH 060/130] Fix some syntax highlights in coding and contributing guide. --- doc/devel/coding_guide.rst | 32 +++++++++++++++++++------------- doc/devel/contributing.rst | 22 ++++++++++++---------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst index 8ac786e7c031..68d8f9cf7d1f 100644 --- a/doc/devel/coding_guide.rst +++ b/doc/devel/coding_guide.rst @@ -45,10 +45,12 @@ When making a PR, pay attention to: 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 using:: + You can achieve this using - git commit --amend --no-edit - git push [your-remote-repo] [your-branch] --force-with-lease + .. code-block:: bash + + git commit --amend --no-edit + git push [your-remote-repo] [your-branch] --force-with-lease See also :ref:`contributing` for how to make a PR. @@ -352,22 +354,26 @@ the merge notification) or through the git CLI tools. Assuming that you already have a local branch ``v2.2.x`` (if not, then ``git checkout -b v2.2.x``), and that your remote pointing to -``https://github.com/matplotlib/matplotlib`` is called ``upstream``:: +``https://github.com/matplotlib/matplotlib`` is called ``upstream``: + +.. code-block:: bash - git fetch upstream - git checkout v2.2.x # or include -b if you don't already have this. - git reset --hard upstream/v2.2.x - git cherry-pick -m 1 TARGET_SHA - # resolve conflicts and commit if required + git fetch upstream + git checkout v2.2.x # or include -b if you don't already have this. + git reset --hard upstream/v2.2.x + git cherry-pick -m 1 TARGET_SHA + # resolve conflicts and commit if required Files with conflicts can be listed by ``git status``, and will have to be fixed by hand (search on ``>>>>>``). Once the conflict is resolved, you will have to re-add the file(s) to the branch -and then continue the cherry pick:: +and then continue the cherry pick: + +.. code-block:: bash - git add lib/matplotlib/conflicted_file.py - git add lib/matplotlib/conflicted_file2.py - git cherry-pick --continue + git add lib/matplotlib/conflicted_file.py + git add lib/matplotlib/conflicted_file2.py + git cherry-pick --continue Use your discretion to push directly to upstream or to open a PR; be sure to push or PR against the ``v2.2.x`` upstream branch, not ``master``! diff --git a/doc/devel/contributing.rst b/doc/devel/contributing.rst index 943110385f03..c91a185566b8 100644 --- a/doc/devel/contributing.rst +++ b/doc/devel/contributing.rst @@ -440,11 +440,13 @@ or manually with :: logging.basicConfig(level=logging.DEBUG) import matplotlib.pyplot as plt -Then they will receive messages like:: +Then they will receive messages like - DEBUG:matplotlib.backends:backend MacOSX version unknown - DEBUG:matplotlib.yourmodulename:Here is some information - DEBUG:matplotlib.yourmodulename:Here is some more detailed information +.. code-block:: none + + DEBUG:matplotlib.backends:backend MacOSX version unknown + DEBUG:matplotlib.yourmodulename:Here is some information + DEBUG:matplotlib.yourmodulename:Here is some more detailed information Which logging level to use? ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -489,12 +491,10 @@ Matplotlib. For example, for the module:: if bottom == top: warnings.warn('Attempting to set identical bottom==top') - running the script:: from matplotlib import my_matplotlib_module - my_matplotlib_module.set_range(0, 0) #set range - + my_matplotlib_module.set_range(0, 0) # set range will display:: @@ -509,10 +509,12 @@ Modifying the module to use `._api.warn_external`:: if bottom == top: _api.warn_external('Attempting to set identical bottom==top') -and running the same script will display:: +and running the same script will display + +.. code-block:: none - UserWarning: Attempting to set identical bottom==top - my_matplotlib_module.set_range(0, 0) #set range + UserWarning: Attempting to set identical bottom==top + my_matplotlib_module.set_range(0, 0) # set range .. _logging tutorial: https://docs.python.org/3/howto/logging.html#logging-basic-tutorial From 30ec0e889aea44c6b101249a3d7e6df8ef22bba3 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 Oct 2021 16:47:05 -0400 Subject: [PATCH 061/130] DOC: Fix source links to prereleases --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index e703fc724e06..c1b5cc164ca8 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -635,7 +635,7 @@ def linkcode_resolve(domain, info): return None version = parse(matplotlib.__version__) - tag = 'master' if version.is_devrelease else f'v{version.base_version}' + tag = 'master' if version.is_devrelease else f'v{version.public}' return ("https://github.com/matplotlib/matplotlib/blob" f"/{tag}/lib/{fn}{linespec}") else: From 2e7a052c3ec2160c94867ea1902c7acd8b15d7e3 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Thu, 30 Sep 2021 13:38:58 -0600 Subject: [PATCH 062/130] DOC: Update interactive colormap example The colormaps have interactivity with zoom/pan now, so demonstrate what those modes do with the new example. This is instead of using callbacks and mouse events for updates. --- .../colormap_interactive_adjustment.py | 95 ++++--------------- 1 file changed, 18 insertions(+), 77 deletions(-) diff --git a/examples/images_contours_and_fields/colormap_interactive_adjustment.py b/examples/images_contours_and_fields/colormap_interactive_adjustment.py index c1a933878af0..3ab9074fd1b6 100644 --- a/examples/images_contours_and_fields/colormap_interactive_adjustment.py +++ b/examples/images_contours_and_fields/colormap_interactive_adjustment.py @@ -3,90 +3,31 @@ Interactive Adjustment of Colormap Range ======================================== -Demonstration of using colorbar, picker, and event functionality to make an -interactively adjustable colorbar widget. - -Left clicks and drags inside the colorbar axes adjust the high range of the -color scheme. Likewise, right clicks and drags adjust the low range. The -connected AxesImage immediately updates to reflect the change. +Demonstration of how a colorbar can be used to interactively adjust the +range of colormapping on an image. To use the interactive feature, you must +be in either zoom mode (magnifying glass toolbar button) or +pan mode (4-way arrow toolbar button) and click inside the colorbar. + +When zooming, the bounding box of the zoom region defines the new vmin and +vmax of the norm. Zooming using the right mouse button will expand the +vmin and vmax proportionally to the selected region, in the same manner that +one can zoom out on an axis. When panning, the vmin and vmax of the norm are +both shifted according to the direction of movement. The +Home/Back/Forward buttons can also be used to get back to a previous state. .. redirect-from:: /gallery/userdemo/colormap_interactive_adjustment """ - -import numpy as np import matplotlib.pyplot as plt -from matplotlib.backend_bases import MouseButton - -############################################################################### -# Callback definitions - - -def on_pick(event): - adjust_colorbar(event.mouseevent) - - -def on_move(mouseevent): - if mouseevent.inaxes is colorbar.ax: - adjust_colorbar(mouseevent) - - -def adjust_colorbar(mouseevent): - if mouseevent.button == MouseButton.LEFT: - colorbar.norm.vmax = max(mouseevent.ydata, colorbar.norm.vmin) - elif mouseevent.button == MouseButton.RIGHT: - colorbar.norm.vmin = min(mouseevent.ydata, colorbar.norm.vmax) - else: - # discard all others - return - - canvas.draw_idle() - +import numpy as np -############################################################################### -# Generate figure with Axesimage and Colorbar +t = np.linspace(0, 2 * np.pi, 1024) +data2d = np.sin(t)[:, np.newaxis] * np.cos(t)[np.newaxis, :] fig, ax = plt.subplots() -canvas = fig.canvas - -delta = 0.1 -x = np.arange(-3.0, 4.001, delta) -y = np.arange(-4.0, 3.001, delta) -X, Y = np.meshgrid(x, y) -Z1 = np.exp(-X**2 - Y**2) -Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) -Z = (0.9*Z1 - 0.5*Z2) * 2 - -cmap = plt.colormaps['viridis'].with_extremes( - over='xkcd:orange', under='xkcd:dark red') -axesimage = plt.imshow(Z, cmap=cmap) -colorbar = plt.colorbar(axesimage, ax=ax, use_gridspec=True) - -############################################################################### -# Note that axesimage and colorbar share a Normalize object -# so they will stay in sync - -assert colorbar.norm is axesimage.norm -colorbar.norm.vmax = 1.5 -axesimage.norm.vmin = -0.75 - -############################################################################### -# Hook Colorbar up to canvas events - -# `set_navigate` helps you see what value you are about to set the range -# to, and enables zoom and pan in the colorbar which can be helpful for -# narrow or wide data ranges -colorbar.ax.set_navigate(True) - -# React to all motion with left or right mouse buttons held -canvas.mpl_connect("motion_notify_event", on_move) - -# React only to left and right clicks -colorbar.ax.set_picker(True) -canvas.mpl_connect("pick_event", on_pick) +im = ax.imshow(data2d) +ax.set_title('Pan on the colorbar to shift the color mapping\n' + 'Zoom on the colorbar to scale the color mapping') -############################################################################### -# Display -# -# The colormap will now respond to left and right clicks in the Colorbar axes +fig.colorbar(im, ax=ax, label='Interactive colorbar') plt.show() From 203c5de8a68bb8c22fa06c6c516b7617a1a056f2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 Oct 2021 19:01:54 -0400 Subject: [PATCH 063/130] Ensure internal FreeType uses same compiler as Python Otherwise, mixed compilers might break things when compiled together into the extension. Fixes #21202 --- setupext.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/setupext.py b/setupext.py index 0d85f479d106..c6f9d82804ba 100644 --- a/setupext.py +++ b/setupext.py @@ -605,9 +605,16 @@ def do_custom_build(self, env): if (src_path / 'objs' / '.libs' / libfreetype).is_file(): return # Bail out because we have already built FreeType. + cc = get_ccompiler() + print(f"Building freetype in {src_path}") if sys.platform != 'win32': # compilation on non-windows - env = {**env, "CFLAGS": "{} -fPIC".format(env.get("CFLAGS", ""))} + env = { + **env, + "CC": (shlex.join(cc.compiler) if sys.version_info >= (3, 8) + else " ".join(shlex.quote(x) for x in cc.compiler)), + "CFLAGS": "{} -fPIC".format(env.get("CFLAGS", "")), + } subprocess.check_call( ["./configure", "--with-zlib=no", "--with-bzip2=no", "--with-png=no", "--with-harfbuzz=no", "--enable-static", @@ -660,7 +667,6 @@ def do_custom_build(self, env): f.truncate() f.write(vcxproj) - cc = get_ccompiler() cc.initialize() # Get msbuild in the %PATH% of cc.spawn. cc.spawn(["msbuild", str(sln_path), "/t:Clean;Build", From 76f1516736b0de6f3dfbd644a19b0b01e0298856 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 Oct 2021 19:09:12 -0400 Subject: [PATCH 064/130] Use same host triplet for internal FreeType as Python This should fix 32-bit compiles on a 64-bit system, as in #18113, but I could never get a working 32-bit Python to test. --- setupext.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setupext.py b/setupext.py index c6f9d82804ba..80cce3ade3fa 100644 --- a/setupext.py +++ b/setupext.py @@ -618,7 +618,8 @@ def do_custom_build(self, env): subprocess.check_call( ["./configure", "--with-zlib=no", "--with-bzip2=no", "--with-png=no", "--with-harfbuzz=no", "--enable-static", - "--disable-shared"], + "--disable-shared", + "--host=" + sysconfig.get_config_var('BUILD_GNU_TYPE')], env=env, cwd=src_path) if 'GNUMAKE' in env: make = env['GNUMAKE'] From f22833a4a6642d0ba4c4945b2fe371a388234cd9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 2 Oct 2021 01:10:26 -0400 Subject: [PATCH 065/130] DOC: Fix footnote that breaks PDF builds --- examples/subplots_axes_and_figures/figure_size_units.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/subplots_axes_and_figures/figure_size_units.py b/examples/subplots_axes_and_figures/figure_size_units.py index 82063ad4577d..94de72c1554c 100644 --- a/examples/subplots_axes_and_figures/figure_size_units.py +++ b/examples/subplots_axes_and_figures/figure_size_units.py @@ -56,15 +56,15 @@ # tedious for quick iterations. # # Because of the default ``rcParams['figure.dpi'] = 100``, one can mentally -# divide the needed pixel value by 100 [*]_: +# divide the needed pixel value by 100 [#]_: # plt.subplots(figsize=(6, 2)) plt.text(0.5, 0.5, '600px x 200px', **text_kwargs) plt.show() ############################################################################# -# .. [*] Unfortunately, this does not work well for the ``matplotlib inline`` -# backend in jupyter because that backend uses a different default of +# .. [#] Unfortunately, this does not work well for the ``matplotlib inline`` +# backend in Jupyter because that backend uses a different default of # ``rcParams['figure.dpi'] = 72``. Additionally, it saves the figure # with ``bbox_inches='tight'``, which crops the figure and makes the # actual size unpredictable. From de1e5ba8002f6bbb75dbe203cfabcd23c9e3d79d Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 08:41:34 +0200 Subject: [PATCH 066/130] add validator to errorbar method --- lib/matplotlib/axes/_axes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4eca2e6b0bdb..b041373c3049 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3283,6 +3283,11 @@ def errorbar(self, x, y, yerr=None, xerr=None, x, y = np.atleast_1d(x, y) # Make sure all the args are iterable. if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") + + if xerr is not None and np.any(xerr < 0): + raise ValueError("'xerr' must have positive numbers") + if yerr is not None and np.any(yerr < 0): + raise ValueError("'yerr' must have positive numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) From ec1da3b961345e869922b0e387347bc954ff3901 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 09:26:23 +0200 Subject: [PATCH 067/130] add check when two inputs have negative numbers --- lib/matplotlib/axes/_axes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b041373c3049..5f82df500d53 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3283,9 +3283,13 @@ def errorbar(self, x, y, yerr=None, xerr=None, x, y = np.atleast_1d(x, y) # Make sure all the args are iterable. if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - + if xerr is not None and np.any(xerr < 0): - raise ValueError("'xerr' must have positive numbers") + if yerr is not None and np.any(yerr < 0): + raise ValueError( + "'xerr' and 'yerr' must have positive numbers") + else: + raise ValueError("'xerr' must have positive numbers") if yerr is not None and np.any(yerr < 0): raise ValueError("'yerr' must have positive numbers") From 3496ba2e581d18921e6e1b422c9497e3a64ef14b Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 09:26:50 +0200 Subject: [PATCH 068/130] add test_xer_yerr_positive --- lib/matplotlib/tests/test_axes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3168fe86ad66..2a8be5680d4a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3513,6 +3513,18 @@ def test_errorbar_every_invalid(): ax.errorbar(x, y, yerr, errorevery='foobar') +def test_xerr_yerr_positive(): + ax = plt.figure().subplots() + + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match="'xerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match="'yerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) + + @check_figures_equal() def test_errorbar_every(fig_test, fig_ref): x = np.linspace(0, 1, 15) From e2381b53c36833e65ccaa7943a971f5909ae9627 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 13:14:51 +0200 Subject: [PATCH 069/130] don't fail on test_units.py --- lib/matplotlib/axes/_axes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 5f82df500d53..40953794b1f1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3284,13 +3284,20 @@ def errorbar(self, x, y, yerr=None, xerr=None, if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - if xerr is not None and np.any(xerr < 0): - if yerr is not None and np.any(yerr < 0): + def check_if_negative(array): + try: + if np.any(array < 0): + return True + except: + pass + + if xerr is not None and check_if_negative(xerr): + if yerr is not None and check_if_negative(yerr): raise ValueError( "'xerr' and 'yerr' must have positive numbers") else: raise ValueError("'xerr' must have positive numbers") - if yerr is not None and np.any(yerr < 0): + if check_if_negative(yerr): raise ValueError("'yerr' must have positive numbers") if isinstance(errorevery, Integral): From 11b1c4b8ab6ffbb77052fe4e5a785952b038f91f Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 13:18:51 +0200 Subject: [PATCH 070/130] add a comment to TypeError --- 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 40953794b1f1..cb92b0254c0c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3288,7 +3288,7 @@ def check_if_negative(array): try: if np.any(array < 0): return True - except: + except TypeError: # Don't fail on 'datetime.*' types pass if xerr is not None and check_if_negative(xerr): From 6d0c9a877e9d9c0765db7876b34b725f587c1178 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 2 Oct 2021 21:35:27 +0200 Subject: [PATCH 071/130] Don't use pixelDelta() on X11. pixelDelta() is explicitly documented as "unreliable" and not-to-be-used on X11 (https://doc.qt.io/qt-5/qwheelevent.html#pixelDelta); locally, it reports the same value as angleDelta, but because it isn't scaled, the value is 120x too big. So add a check that forces the use of angleDelta() on X11 (see also https://doc.qt.io/qt-6/qguiapplication.html#platformName-prop). --- lib/matplotlib/backends/backend_qt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index ba8eb02becf8..6bb1f82c0f24 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -312,8 +312,10 @@ def mouseReleaseEvent(self, event): def wheelEvent(self, event): x, y = self.mouseEventCoords(self._get_position(event)) - # from QWheelEvent::delta doc - if event.pixelDelta().x() == 0 and event.pixelDelta().y() == 0: + # from QWheelEvent::pixelDelta doc: pixelDelta is sometimes not + # provided (`isNull()`) and is unreliable on X11 ("xcb"). + if (event.pixelDelta().isNull() + or QtWidgets.QApplication.instance().platformName() == "xcb"): steps = event.angleDelta().y() / 120 else: steps = event.pixelDelta().y() From 9b5bbe7808bfd2f98919f38f4aaaca0342ea1b55 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 09:56:40 +0200 Subject: [PATCH 072/130] check None in check_if_negative --- lib/matplotlib/axes/_axes.py | 12 ++++-------- lib/matplotlib/tests/test_axes.py | 6 ++++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cb92b0254c0c..cff3535b1c5f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3285,20 +3285,16 @@ def errorbar(self, x, y, yerr=None, xerr=None, raise ValueError("'x' and 'y' must have the same size") def check_if_negative(array): + if array is None: + return False try: if np.any(array < 0): return True except TypeError: # Don't fail on 'datetime.*' types pass - if xerr is not None and check_if_negative(xerr): - if yerr is not None and check_if_negative(yerr): - raise ValueError( - "'xerr' and 'yerr' must have positive numbers") - else: - raise ValueError("'xerr' must have positive numbers") - if check_if_negative(yerr): - raise ValueError("'yerr' must have positive numbers") + if check_if_negative(xerr) or check_if_negative(yerr): + raise ValueError("'xerr' and 'yerr' must have positive numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2a8be5680d4a..2319c1aa5654 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3519,9 +3519,11 @@ def test_xerr_yerr_positive(): with pytest.raises(ValueError, match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match="'xerr' must have positive numbers"): + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match="'yerr' must have positive numbers"): + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) From 6333ea4d94c6a9bebce60ac55f7dab7dcd634701 Mon Sep 17 00:00:00 2001 From: Kinshuk Dua Date: Mon, 4 Oct 2021 13:24:29 +0530 Subject: [PATCH 073/130] Add support to save images in WebP format --- lib/matplotlib/backend_bases.py | 2 ++ lib/matplotlib/backends/backend_agg.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 9bb78cf0cdbc..38d408ba02c6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -69,6 +69,7 @@ 'svgz': 'Scalable Vector Graphics', 'tif': 'Tagged Image File Format', 'tiff': 'Tagged Image File Format', + 'webp': 'WebP Image Format', } _default_backends = { 'eps': 'matplotlib.backends.backend_ps', @@ -84,6 +85,7 @@ 'svgz': 'matplotlib.backends.backend_svg', 'tif': 'matplotlib.backends.backend_agg', 'tiff': 'matplotlib.backends.backend_agg', + 'webp': 'matplotlib.backends.backend_agg', } diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 4a62f5a921cd..35cc25579c67 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -594,6 +594,29 @@ def print_tif(self, filename_or_obj, *, pil_kwargs=None): print_tiff = print_tif + @_check_savefig_extra_args + def print_webp(self, filename_or_obj, *, pil_kwargs=None): + """ + Write the figure to a WebP file. + + Parameters + ---------- + filename_or_obj : str or path-like or file-like + The file to write to. + + Other Parameters + ---------------- + pil_kwargs : dict, optional + Additional keyword arguments that are passed to + `PIL.Image.Image.save` when saving the figure. + """ + FigureCanvasAgg.draw(self) + if pil_kwargs is None: + pil_kwargs = {} + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + return (Image.fromarray(np.asarray(self.buffer_rgba())) + .save(filename_or_obj, format='webp', **pil_kwargs)) + @_Backend.export class _BackendAgg(_Backend): From 5fbc3b356e34111bc3526988f4cee67533b606e8 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 4 Oct 2021 12:05:09 +0200 Subject: [PATCH 074/130] Fix format_cursor_data for values close to float resolution. Previously, moving the cursor over `plt.imshow(1 + np.random.randn(10, 10) * 1e-15)` would result in `ValueError: math domain error` (when trying to compute `math.log10(delta)` with `delta = 0`). This is because the full range of normalization implied by the image above (~1e-15) is representable with float precision, but the spacing between individual colors (256x less) is too small to be represented, and thus rounded to zero. In general, I consider that such floating point errors should be fixed on the user side, but this specific error seems worth fixing as the 1) the image is still rendered correctly, 2) errors in the mouse event handler (rendering the cursor text) are somewhat obscure for end users, and 3) it used to work before Matplotlib 3.5. --- lib/matplotlib/cbook/__init__.py | 4 ++++ lib/matplotlib/tests/test_image.py | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 9d0fcadc431c..b068fbb1d84a 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2212,6 +2212,10 @@ def _g_sig_digits(value, delta): Return the number of significant digits to %g-format *value*, assuming that it is known with an error of *delta*. """ + if delta == 0: + # 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 = np.spacing(value) # If e.g. value = 45.67 and delta = 0.02, then we want to round to 2 digits # after the decimal point (floor(log10(0.02)) = -2); 45.67 contributes 2 # digits before the decimal point (floor(log10(45.67)) + 1 = 2): the total diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 2c4a2763e4c6..719b19057875 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -341,6 +341,7 @@ def test_cursor_data(): ([[10001, 10000]], "[10001.000]"), ([[.123, .987]], "[0.123]"), ([[np.nan, 1, 2]], "[]"), + ([[1, 1+1e-15]], "[1.0000000000000000]"), ]) def test_format_cursor_data(data, text): from matplotlib.backend_bases import MouseEvent From b025271b8b901e5470fbaf18dd01719d1820b3b5 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 14:58:07 +0200 Subject: [PATCH 075/130] fix wording & simplify function --- lib/matplotlib/axes/_axes.py | 12 ++++++++---- lib/matplotlib/tests/test_axes.py | 11 +++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cff3535b1c5f..d9842f74d5f2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3180,7 +3180,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, errors. - *None*: No errorbar. - Note that all error arrays should have *positive* values. + Note that all error arrays should have *non-negative* values. See :doc:`/gallery/statistics/errorbar_features` for an example on the usage of ``xerr`` and ``yerr``. @@ -3284,17 +3284,21 @@ def errorbar(self, x, y, yerr=None, xerr=None, if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - def check_if_negative(array): + def has_negative_values(array): if array is None: return False try: + return np.any(array < 0) + except TypeError: + pass # Don't fail on 'datetime.*' types if np.any(array < 0): return True except TypeError: # Don't fail on 'datetime.*' types pass - if check_if_negative(xerr) or check_if_negative(yerr): - raise ValueError("'xerr' and 'yerr' must have positive numbers") + if has_negative_values(xerr) or has_negative_values(yerr): + raise ValueError( + "'xerr' and 'yerr' must have non-negative numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2319c1aa5654..2d726cea9fda 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3516,14 +3516,13 @@ def test_errorbar_every_invalid(): def test_xerr_yerr_positive(): ax = plt.figure().subplots() - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + error_message = "'xerr' and 'yerr' must have non-negative numbers" + + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) From ced1eb25e4a66cf5d47e72d547f132f0d844a246 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:03:34 +0200 Subject: [PATCH 076/130] Stage all changes in has_negative_values --- lib/matplotlib/axes/_axes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d9842f74d5f2..2cebd2c06789 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3291,10 +3291,6 @@ def has_negative_values(array): return np.any(array < 0) except TypeError: pass # Don't fail on 'datetime.*' types - if np.any(array < 0): - return True - except TypeError: # Don't fail on 'datetime.*' types - pass if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( From 8f63438fa3ae3e0238031248146d913dbb31c1c8 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:10:23 +0200 Subject: [PATCH 077/130] allign wording in description and error --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/tests/test_axes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2cebd2c06789..3897c53c4cc8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3294,7 +3294,7 @@ def has_negative_values(array): if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( - "'xerr' and 'yerr' must have non-negative numbers") + "'xerr' and 'yerr' must have non-negative values") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2d726cea9fda..50a1bb078ec9 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3516,7 +3516,7 @@ def test_errorbar_every_invalid(): def test_xerr_yerr_positive(): ax = plt.figure().subplots() - error_message = "'xerr' and 'yerr' must have non-negative numbers" + error_message = "'xerr' and 'yerr' must have non-negative values" with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) From 9d182aebd7c4c268a0dbe3bf75dd8e89e4a0b0a6 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:44:52 +0200 Subject: [PATCH 078/130] add validator for timedelta --- lib/matplotlib/axes/_axes.py | 5 +++-- lib/matplotlib/tests/test_axes.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3897c53c4cc8..25cafef60d79 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3,6 +3,7 @@ import logging import math from numbers import Integral, Number +from datetime import timedelta import numpy as np from numpy import ma @@ -3289,8 +3290,8 @@ def has_negative_values(array): return False try: return np.any(array < 0) - except TypeError: - pass # Don't fail on 'datetime.*' types + except TypeError: # if array contains 'datetime.timedelta' types + return np.any(array < timedelta(0)) if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 50a1bb078ec9..d270136f9667 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3524,6 +3524,12 @@ def test_xerr_yerr_positive(): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match=error_message): + x = np.arange(5) + y = [datetime.datetime(2021, 9, i * 2 + 1) for i in x] + ax.errorbar(x=x, + y=y, + yerr=datetime.timedelta(days=-10)) @check_figures_equal() From 36a6b3c2d036c37347d5a81f9e07c98fdc90cb62 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 4 Oct 2021 18:20:24 -0400 Subject: [PATCH 079/130] Ensure *full* compiler config is passed to FreeType Using just `CC` from the `distutils` compiler can break universal2 wheels on macOS, but passing all compiler and linker settings appears to work. Plus grabbing this information from `sysconfig` is better than hacking the `distutils` compiler into working. --- setupext.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/setupext.py b/setupext.py index 80cce3ade3fa..35a612243922 100644 --- a/setupext.py +++ b/setupext.py @@ -605,16 +605,18 @@ def do_custom_build(self, env): if (src_path / 'objs' / '.libs' / libfreetype).is_file(): return # Bail out because we have already built FreeType. - cc = get_ccompiler() - print(f"Building freetype in {src_path}") if sys.platform != 'win32': # compilation on non-windows env = { **env, - "CC": (shlex.join(cc.compiler) if sys.version_info >= (3, 8) - else " ".join(shlex.quote(x) for x in cc.compiler)), - "CFLAGS": "{} -fPIC".format(env.get("CFLAGS", "")), + **{ + var: value + for var, value in sysconfig.get_config_vars().items() + if var in {"CC", "CFLAGS", "CXX", "CXXFLAGS", "LD", + "LDFLAGS"} + }, } + env["CFLAGS"] = env.get("CFLAGS", "") + " -fPIC" subprocess.check_call( ["./configure", "--with-zlib=no", "--with-bzip2=no", "--with-png=no", "--with-harfbuzz=no", "--enable-static", @@ -668,6 +670,7 @@ def do_custom_build(self, env): f.truncate() f.write(vcxproj) + cc = get_ccompiler() cc.initialize() # Get msbuild in the %PATH% of cc.spawn. cc.spawn(["msbuild", str(sln_path), "/t:Clean;Build", From 81730f7c69b85ce95d54d53e5e8a059c982ae023 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 4 Oct 2021 22:13:43 -0400 Subject: [PATCH 080/130] Fix FreeType build PyPy PyPy doesn't have a host type, so don't pass `--host` in that case, and hope it is correct (which has been how it was since forever anyway.) --- setupext.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/setupext.py b/setupext.py index 35a612243922..5320ee3a518b 100644 --- a/setupext.py +++ b/setupext.py @@ -617,12 +617,15 @@ def do_custom_build(self, env): }, } env["CFLAGS"] = env.get("CFLAGS", "") + " -fPIC" - subprocess.check_call( - ["./configure", "--with-zlib=no", "--with-bzip2=no", - "--with-png=no", "--with-harfbuzz=no", "--enable-static", - "--disable-shared", - "--host=" + sysconfig.get_config_var('BUILD_GNU_TYPE')], - env=env, cwd=src_path) + configure = [ + "./configure", "--with-zlib=no", "--with-bzip2=no", + "--with-png=no", "--with-harfbuzz=no", "--enable-static", + "--disable-shared" + ] + host = sysconfig.get_config_var('BUILD_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: From d297e413a8ab94bd9888f5a76936e11f50f022b8 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 5 Oct 2021 02:44:52 -0400 Subject: [PATCH 081/130] Add language about not assigning issues (#21276) * Add language about not assigning issues This is a combination of @anntzer and @jklymak's language on issues not being assigned, though I put it under "new contributor issues" * https://gitter.im/matplotlib/matplotlib?at=615aca48fb3dcd4e8808f439 * https://gitter.im/matplotlib/matplotlib?at=615acbf9f2cedf67f96662fd * linewrap * linewrap --- doc/devel/contributing.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/devel/contributing.rst b/doc/devel/contributing.rst index c91a185566b8..8e67e4393dba 100644 --- a/doc/devel/contributing.rst +++ b/doc/devel/contributing.rst @@ -40,6 +40,16 @@ difficulty. ``Difficulty: Easy`` is suited for people with little Python experie ``Difficulty: Medium`` and ``Difficulty: Hard`` are not trivial to solve and require more thought and programming experience. +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. + .. _submitting-a-bug-report: Submitting a bug report @@ -73,7 +83,7 @@ If you are reporting a bug, please do your best to include the following: >>> platform.python_version() '3.9.2' -We have preloaded the issue creation page with a Markdown template that you can +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. From 26f1e3fdb9b713e951633ec7c896ada56d4ac35f Mon Sep 17 00:00:00 2001 From: Kinshuk Dua Date: Tue, 5 Oct 2021 15:44:12 +0530 Subject: [PATCH 082/130] Add tests for WebP format --- lib/matplotlib/tests/test_agg.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 0e4abf86fe02..2192f939a284 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -248,6 +248,24 @@ def test_pil_kwargs_tiff(): tags = {TiffTags.TAGS_V2[k].name: v for k, v in im.tag_v2.items()} assert tags["ImageDescription"] == "test image" +def test_pil_kwargs_webp(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf_small = io.BytesIO() + pil_kwargs_low = {"quality": 1} + plt.savefig(buf_small, format="webp", pil_kwargs=pil_kwargs_low) + buf_large = io.BytesIO() + pil_kwargs_high = {"quality": 100} + plt.savefig(buf_large, format="webp", pil_kwargs=pil_kwargs_high) + assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes + +def test_webp_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="webp", transparent=True) + im = Image.open(buf) + assert im.mode == "RGBA" + + def test_draw_path_collection_error_handling(): fig, ax = plt.subplots() From 96a8e32c01c7125c59481423c8838bf634305720 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 16:17:45 +0200 Subject: [PATCH 083/130] Fix incorrect markup in example. --- examples/axes_grid1/scatter_hist_locatable_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/axes_grid1/scatter_hist_locatable_axes.py b/examples/axes_grid1/scatter_hist_locatable_axes.py index 30111e876d1c..8d2ee72b9c6b 100644 --- a/examples/axes_grid1/scatter_hist_locatable_axes.py +++ b/examples/axes_grid1/scatter_hist_locatable_axes.py @@ -65,7 +65,7 @@ ############################################################################# # -## .. admonition:: References +# .. admonition:: References # # The use of the following functions, methods, classes and modules is shown # in this example: From 811c9a6b1fcd03a09a2256469fc48afe5598dac9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 10:19:36 +0200 Subject: [PATCH 084/130] Make `Path.__deepcopy__` interact better with subclasses, e.g. TextPath. --- lib/matplotlib/path.py | 11 ++++------- lib/matplotlib/tests/test_textpath.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 lib/matplotlib/tests/test_textpath.py diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 4280d55eeacd..43d3f2fd95da 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -272,13 +272,10 @@ def __deepcopy__(self, memo=None): Return a deepcopy of the `Path`. The `Path` will not be readonly, even if the source `Path` is. """ - try: - codes = self.codes.copy() - except AttributeError: - codes = None - return self.__class__( - self.vertices.copy(), codes, - _interpolation_steps=self._interpolation_steps) + # Deepcopying arrays (vertices, codes) strips the writeable=False flag. + p = copy.deepcopy(super(), memo) + p._readonly = False + return p deepcopy = __deepcopy__ diff --git a/lib/matplotlib/tests/test_textpath.py b/lib/matplotlib/tests/test_textpath.py new file mode 100644 index 000000000000..e421d2623cad --- /dev/null +++ b/lib/matplotlib/tests/test_textpath.py @@ -0,0 +1,10 @@ +import copy + +from matplotlib.textpath import TextPath + + +def test_copy(): + tp = TextPath((0, 0), ".") + assert copy.deepcopy(tp).vertices is not tp.vertices + assert (copy.deepcopy(tp).vertices == tp.vertices).all() + assert copy.copy(tp).vertices is tp.vertices From 3574e73cf04faeb8743d9b0f75970f83dc102c28 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 21:44:10 +0200 Subject: [PATCH 085/130] Clarify FigureBase.tight_bbox as different from all other artists. It explicitly scales the union of child tightbboxes by 1/dpi, i.e. converts from pixels to inches. Perhaps that's not the most elegant design, but let's make that behavior clear. --- lib/matplotlib/figure.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index dee0de8c5691..8b6420535e61 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1596,7 +1596,10 @@ def get_default_bbox_extra_artists(self): def get_tightbbox(self, renderer, bbox_extra_artists=None): """ - Return a (tight) bounding box of the figure in inches. + Return a (tight) bounding box of the figure *in inches*. + + Note that `.FigureBase` differs from all other artists, which return + their `.Bbox` in pixels. Artists that have ``artist.set_in_layout(False)`` are not included in the bbox. From a928840cb5715e960406709a315f16057ff05691 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 22:55:33 +0200 Subject: [PATCH 086/130] Simplify _init_legend_box. Reword some comments. Inline `label_prop` to its only use site (previously quite far away from the definition). Rename `lab` to `label`, and some unCamelCasing. Replace the manual splitting-into-columns computation by a single call to np.array_split followed by filtering away empty columns. --- lib/matplotlib/legend.py | 63 +++++++++++++++------------------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index b255bba67240..cb789cd3ec91 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -712,27 +712,19 @@ def _init_legend_box(self, handles, labels, markerfirst=True): fontsize = self._fontsize - # legend_box is a HPacker, horizontally packed with - # columns. Each column is a VPacker, vertically packed with - # legend items. Each legend item is HPacker packed with - # legend handleBox and labelBox. handleBox is an instance of - # offsetbox.DrawingArea which contains legend handle. labelBox - # is an instance of offsetbox.TextArea which contains legend - # text. + # legend_box is a HPacker, horizontally packed with columns. + # Each column is a VPacker, vertically packed with legend items. + # Each legend item is a HPacker packed with: + # - handlebox: a DrawingArea which contains the legend handle. + # - labelbox: a TextArea which contains the legend text. text_list = [] # the list of text instances handle_list = [] # the list of handle instances handles_and_labels = [] - label_prop = dict(verticalalignment='baseline', - horizontalalignment='left', - fontproperties=self.prop, - ) - # The approximate height and descent of text. These values are # only used for plotting the legend handle. - descent = 0.35 * fontsize * (self.handleheight - 0.7) - # 0.35 and 0.7 are just heuristic numbers and may need to be improved. + descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic. height = fontsize * self.handleheight - descent # each handle needs to be drawn inside a box of (x, y, w, h) = # (0, -descent, width, height). And their coordinates should @@ -744,7 +736,7 @@ def _init_legend_box(self, handles, labels, markerfirst=True): # manually set their transform to the self.get_transform(). legend_handler_map = self.get_legend_handler_map() - for orig_handle, lab in zip(handles, labels): + for orig_handle, label in zip(handles, labels): handler = self.get_legend_handler(legend_handler_map, orig_handle) if handler is None: _api.warn_external( @@ -753,12 +745,14 @@ def _init_legend_box(self, handles, labels, markerfirst=True): "https://matplotlib.org/users/legend_guide.html" "#creating-artists-specifically-for-adding-to-the-legend-" "aka-proxy-artists".format(orig_handle)) - # We don't have a handle for this artist, so we just defer - # to None. + # No handle for this artist, so we just defer to None. handle_list.append(None) else: - textbox = TextArea(lab, textprops=label_prop, - multilinebaseline=True) + textbox = TextArea(label, multilinebaseline=True, + textprops=dict( + verticalalignment='baseline', + horizontalalignment='left', + fontproperties=self.prop)) handlebox = DrawingArea(width=self.handlelength * fontsize, height=height, xdescent=0., ydescent=descent) @@ -770,34 +764,25 @@ def _init_legend_box(self, handles, labels, markerfirst=True): fontsize, handlebox)) handles_and_labels.append((handlebox, textbox)) - if handles_and_labels: - # We calculate number of rows in each column. The first - # (num_largecol) columns will have (nrows+1) rows, and remaining - # (num_smallcol) columns will have (nrows) rows. - ncol = min(self._ncol, len(handles_and_labels)) - nrows, num_largecol = divmod(len(handles_and_labels), ncol) - num_smallcol = ncol - num_largecol - # starting index of each column and number of rows in it. - rows_per_col = [nrows + 1] * num_largecol + [nrows] * num_smallcol - start_idxs = np.concatenate([[0], np.cumsum(rows_per_col)[:-1]]) - cols = zip(start_idxs, rows_per_col) - else: - cols = [] - columnbox = [] - for i0, di in cols: - # pack handleBox and labelBox into itemBox - itemBoxes = [HPacker(pad=0, + # array_split splits n handles_and_labels into ncol columns, with the + # first n%ncol columns having an extra entry. filter(len, ...) handles + # the case where n < ncol: the last ncol-n columns are empty and get + # filtered out. + for handles_and_labels_column \ + in filter(len, np.array_split(handles_and_labels, self._ncol)): + # pack handlebox and labelbox into itembox + itemboxes = [HPacker(pad=0, sep=self.handletextpad * fontsize, children=[h, t] if markerfirst else [t, h], align="baseline") - for h, t in handles_and_labels[i0:i0 + di]] - # pack columnBox + for h, t in handles_and_labels_column] + # pack columnbox alignment = "baseline" if markerfirst else "right" columnbox.append(VPacker(pad=0, sep=self.labelspacing * fontsize, align=alignment, - children=itemBoxes)) + children=itemboxes)) mode = "expand" if self._mode == "expand" else "fixed" sep = self.columnspacing * fontsize From 917a616846c83ba615caeff89eb68d11697ce998 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 20:05:06 -0400 Subject: [PATCH 087/130] DOC: Fix some lists in animation examples --- examples/animation/animate_decay.py | 1 + examples/animation/pause_resume.py | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/animation/animate_decay.py b/examples/animation/animate_decay.py index 88f25e2d6aaa..c4e8eded4e6e 100644 --- a/examples/animation/animate_decay.py +++ b/examples/animation/animate_decay.py @@ -4,6 +4,7 @@ ===== This example showcases: + - using a generator to drive an animation, - changing axes limits during an animation. """ diff --git a/examples/animation/pause_resume.py b/examples/animation/pause_resume.py index ed20197f6167..0a8c2f7c549a 100644 --- a/examples/animation/pause_resume.py +++ b/examples/animation/pause_resume.py @@ -4,6 +4,7 @@ ================================= This example showcases: + - using the Animation.pause() method to pause an animation. - using the Animation.resume() method to resume an animation. """ From 57d7f45c082184950f219b220f0519557a303eb9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 16:56:27 -0400 Subject: [PATCH 088/130] Allow macosx thread safety test on macOS11 It appears to no longer timeout on that system, and is breaking CI, which just switched to it. Note, macOS plays games with the version number it reports depending on how Python was compiled: https://eclecticlight.co/2020/08/13/macos-version-numbering-isnt-so-simple/ --- lib/matplotlib/tests/test_backends_interactive.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index bb17e5fdaf82..5d51cf2ab9c3 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -222,8 +222,14 @@ def _test_thread_impl(): param.marks.append( pytest.mark.xfail(raises=subprocess.CalledProcessError)) elif backend == "macosx": - param.marks.append( - pytest.mark.xfail(raises=subprocess.TimeoutExpired, strict=True)) + from packaging.version import parse + mac_ver = platform.mac_ver()[0] + # Note, macOS Big Sur is both 11 and 10.16, depending on SDK that + # Python was compiled against. + if mac_ver and parse(mac_ver) < parse('10.16'): + param.marks.append( + pytest.mark.xfail(raises=subprocess.TimeoutExpired, + strict=True)) elif param.values[0].get("QT_API") == "PySide2": param.marks.append( pytest.mark.xfail(raises=subprocess.CalledProcessError)) From 0fca630af0893cae1cf2ea4a7c4e2336c741ca3b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 19:33:07 -0400 Subject: [PATCH 089/130] DOC: Bump to the sphinx-gallery release Now that it's out, we don't need to depend on a random commit. --- environment.yml | 7 ++----- requirements/doc/doc-requirements.txt | 6 +----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/environment.yml b/environment.yml index 1f9ad4471d2c..a0d869a356b9 100644 --- a/environment.yml +++ b/environment.yml @@ -32,13 +32,10 @@ dependencies: - scipy - sphinx>=1.8.1,!=2.0.0 - sphinx-copybutton + - sphinx-gallery>=0.10 - pip - pip: - - sphinxcontrib-svg2pdfconverter - # b41e328 is PR 808 which adds the image_srcset directive. When this is - # released with sphinx gallery, we can change to the last release w/o this feature: - # sphinx-gallery>0.9 - - git+git://github.com/sphinx-gallery/sphinx-gallery@b41e328#egg=sphinx-gallery + - sphinxcontrib-svg2pdfconverter # testing - coverage - flake8>=3.8 diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 8e7172966a4e..30b7c0c88669 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -15,11 +15,7 @@ numpydoc>=0.8 packaging>=20 pydata-sphinx-theme>=0.6.0 sphinxcontrib-svg2pdfconverter>=1.1.0 -# sphinx-gallery>=0.7 -# b41e328 is PR 808 which adds the image_srcset directive. When this is -# released with sphinx gallery, we can change to the last release w/o this feature: -# sphinx-gallery>0.90 -git+git://github.com/sphinx-gallery/sphinx-gallery@b41e328#egg=sphinx-gallery +sphinx-gallery>=0.10 sphinx-copybutton sphinx-panels scipy From 1ae17603b3c66c0a000f20b882b2cbf1f4e5c135 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 22:14:49 -0400 Subject: [PATCH 090/130] Fix snap argument to pcolormesh If it's passed as a keyword argument, then it needs to be removed from `kwargs`, or not passed again explicitly. Other handling of `snap` uses `setdefault`, so do that here too. --- lib/matplotlib/axes/_axes.py | 5 +++-- lib/matplotlib/tests/test_axes.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 25cafef60d79..806e2acd0226 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6029,9 +6029,10 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, # convert to one dimensional array C = C.ravel() - snap = kwargs.get('snap', rcParams['pcolormesh.snap']) + kwargs.setdefault('snap', rcParams['pcolormesh.snap']) + collection = mcoll.QuadMesh( - coords, antialiased=antialiased, shading=shading, snap=snap, + coords, antialiased=antialiased, shading=shading, array=C, cmap=cmap, norm=norm, alpha=alpha, **kwargs) collection._scale_norm(norm, vmin, vmax) self._pcolor_grid_deprecation_helper() diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index d270136f9667..cb3d79358695 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1249,22 +1249,23 @@ def test_pcolorflaterror(): ax.pcolormesh(x, y, Z, shading='flat') +@pytest.mark.parametrize('snap', [False, True]) @check_figures_equal(extensions=["png"]) -def test_pcolorauto(fig_test, fig_ref): +def test_pcolorauto(fig_test, fig_ref, snap): ax = fig_test.subplots() x = np.arange(0, 10) y = np.arange(0, 4) np.random.seed(19680801) Z = np.random.randn(3, 9) # this is the same as flat; note that auto is default - ax.pcolormesh(x, y, Z) + ax.pcolormesh(x, y, Z, snap=snap) ax = fig_ref.subplots() # specify the centers x2 = x[:-1] + np.diff(x) / 2 y2 = y[:-1] + np.diff(y) / 2 # this is same as nearest: - ax.pcolormesh(x2, y2, Z) + ax.pcolormesh(x2, y2, Z, snap=snap) @image_comparison(['canonical']) From c727cfa79a04e45c37c611000a62e44a96bc9cbf Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 28 Sep 2021 14:26:09 +0200 Subject: [PATCH 091/130] Simplify `Colormap.__call__` a bit. - lut is never modified anymore, so we don't need to copy it. - Preallocating rgba doesn't help. - We can rely on np.clip to cast alpha to an array. --- lib/matplotlib/colors.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index f5081825c2ab..b5f66bc33224 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -633,24 +633,20 @@ def __call__(self, X, alpha=None, bytes=False): xa[xa < 0] = self._i_under xa[mask_bad] = self._i_bad + lut = self._lut if bytes: - lut = (self._lut * 255).astype(np.uint8) - else: - lut = self._lut.copy() # Don't let alpha modify original _lut. + lut = (lut * 255).astype(np.uint8) - rgba = np.empty(shape=xa.shape + (4,), dtype=lut.dtype) - lut.take(xa, axis=0, mode='clip', out=rgba) + rgba = lut.take(xa, axis=0, mode='clip') if alpha is not None: - if np.iterable(alpha): - alpha = np.asarray(alpha) - if alpha.shape != xa.shape: - raise ValueError("alpha is array-like but its shape" - " %s doesn't match that of X %s" % - (alpha.shape, xa.shape)) alpha = np.clip(alpha, 0, 1) if bytes: - alpha = (alpha * 255).astype(np.uint8) + alpha *= 255 # Will be cast to uint8 upon assignment. + if alpha.shape not in [(), xa.shape]: + raise ValueError( + f"alpha is array-like but its shape {alpha.shape} does " + f"not match that of X {xa.shape}") rgba[..., -1] = alpha # If the "bad" color is all zeros, then ignore alpha input. From c46d616948f01df0221586b5cda43f17d326d3bf Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 23:28:14 -0400 Subject: [PATCH 092/130] Disable blitting on GTK4 backends It is not possible to queue a draw for a subregion of a widget, so this crashes. Since 3.5 is feature frozen, disable blitting for now to make animations work. --- lib/matplotlib/backends/backend_gtk4.py | 1 + lib/matplotlib/backends/backend_gtk4agg.py | 56 +++++----------------- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 30719b1bba0f..6ddcaaa18bbb 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -51,6 +51,7 @@ def _mpl_to_gtk_cursor(mpl_cursor): class FigureCanvasGTK4(Gtk.DrawingArea, FigureCanvasBase): required_interactive_framework = "gtk4" + supports_blit = False _timer_cls = TimerGTK4 _context_is_scaled = False diff --git a/lib/matplotlib/backends/backend_gtk4agg.py b/lib/matplotlib/backends/backend_gtk4agg.py index d47dd07fee3b..58f085e85d8f 100644 --- a/lib/matplotlib/backends/backend_gtk4agg.py +++ b/lib/matplotlib/backends/backend_gtk4agg.py @@ -8,67 +8,35 @@ from . import backend_agg, backend_gtk4 from .backend_cairo import cairo from .backend_gtk4 import Gtk, _BackendGTK4 -from matplotlib import transforms class FigureCanvasGTK4Agg(backend_gtk4.FigureCanvasGTK4, backend_agg.FigureCanvasAgg): def __init__(self, figure): backend_gtk4.FigureCanvasGTK4.__init__(self, figure) - self._bbox_queue = [] def on_draw_event(self, widget, ctx): scale = self.device_pixel_ratio allocation = self.get_allocation() - w = allocation.width * scale - h = allocation.height * scale - if not len(self._bbox_queue): - Gtk.render_background( - self.get_style_context(), ctx, - allocation.x, allocation.y, - allocation.width, allocation.height) - bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] - else: - bbox_queue = self._bbox_queue + Gtk.render_background( + self.get_style_context(), ctx, + allocation.x, allocation.y, + allocation.width, allocation.height) ctx = backend_cairo._to_context(ctx) - for bbox in bbox_queue: - x = int(bbox.x0) - y = h - int(bbox.y1) - width = int(bbox.x1) - int(bbox.x0) - height = int(bbox.y1) - int(bbox.y0) - - buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32( - np.asarray(self.copy_from_bbox(bbox))) - image = cairo.ImageSurface.create_for_data( - buf.ravel().data, cairo.FORMAT_ARGB32, width, height) - image.set_device_scale(scale, scale) - ctx.set_source_surface(image, x / scale, y / scale) - ctx.paint() - - if len(self._bbox_queue): - self._bbox_queue = [] + buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32( + np.asarray(self.renderer.buffer_rgba())) + height, width, _ = buf.shape + image = cairo.ImageSurface.create_for_data( + buf.ravel().data, cairo.FORMAT_ARGB32, width, height) + image.set_device_scale(scale, scale) + ctx.set_source_surface(image, 0, 0) + ctx.paint() return False - def blit(self, bbox=None): - # If bbox is None, blit the entire canvas to gtk. Otherwise - # blit only the area defined by the bbox. - if bbox is None: - bbox = self.figure.bbox - - scale = self.device_pixel_ratio - allocation = self.get_allocation() - x = int(bbox.x0 / scale) - y = allocation.height - int(bbox.y1 / scale) - width = (int(bbox.x1) - int(bbox.x0)) // scale - height = (int(bbox.y1) - int(bbox.y0)) // scale - - self._bbox_queue.append(bbox) - self.queue_draw_area(x, y, width, height) - def draw(self): backend_agg.FigureCanvasAgg.draw(self) super().draw() From 28249e07affb7accdee60b4103a3bdb4063926c3 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Oct 2021 16:41:41 -0400 Subject: [PATCH 093/130] Pin macOS to 10.15 for wheels cibuildwheel does not support macOS 11 for PyPy. --- .github/workflows/cibuildwheel.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 6bd35f22567d..e73bc83c210d 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -18,7 +18,7 @@ jobs: CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" strategy: matrix: - os: [ubuntu-18.04, windows-latest, macos-latest] + os: [ubuntu-18.04, windows-latest, macos-10.15] cibw_archs: ["auto"] include: - os: ubuntu-18.04 @@ -64,7 +64,7 @@ jobs: - name: Build wheels for CPython 3.10 run: | python -m cibuildwheel --output-dir dist - if: matrix.os != 'macos-latest' + if: matrix.os != 'macos-10.15' env: CIBW_BUILD: "cp310-*" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 From 8050ee633f0719a8945a6a36ad046f6fac041dfa Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Oct 2021 05:16:08 -0400 Subject: [PATCH 094/130] Use in-tree builds for PyPy wheels Doing out-of-tree builds makes a copy that breaks symlinks in the git tree, causing PyPy wheels to get a version that signifies a dirty tree when they shouldn't. This will change in pip soon, but changing this will fix the version for our release now. --- .github/workflows/cibuildwheel.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index e73bc83c210d..6d64422adb66 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -102,6 +102,7 @@ jobs: CIBW_BUILD: "pp37-*" CIBW_BEFORE_BUILD: pip install certifi numpy==${{ env.min-numpy-version }} CIBW_ARCHS: ${{ matrix.cibw_archs }} + PIP_USE_FEATURE: in-tree-build if: matrix.cibw_archs != 'aarch64' - name: Validate that LICENSE files are included in wheels From 071b53b25d652a7f0326929639e9c64ac7e9ce9b Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 7 Oct 2021 23:27:02 +0200 Subject: [PATCH 095/130] DOC: Minimal getting started page --- doc/_static/mpl.css | 14 +++++++++ doc/users/getting_started.rst | 54 +++++++++++++++++++++++++++++++++++ doc/users/index.rst | 1 + 3 files changed, 69 insertions(+) create mode 100644 doc/users/getting_started.rst diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 5f62cd3c95c7..370e2e5ac201 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -231,3 +231,17 @@ table.property-table td { /* fix width to width of cheatsheet */ width: 210px; } + +/* Two columns for install code blocks */ +div.twocol { + padding-left: 0; + padding-right: 0; + display: flex; + gap: 20px; +} + +div.twocol > div { + flex-grow: 1; + padding: 0; + margin: 0; +} diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst new file mode 100644 index 000000000000..915d27fa8b8a --- /dev/null +++ b/doc/users/getting_started.rst @@ -0,0 +1,54 @@ +Getting started +=============== + +Installation +------------ + +.. container:: twocol + + .. container:: + + Install using pip: + + .. code-block:: bash + + pip install matplotlib + + .. container:: + + Install using conda: + + .. code-block:: bash + + conda install matplotlib + +Further details are available in the :doc:`Installation Guide `. + + +Draw a first plot +----------------- + +Here is a minimal example plot you can try out: + +.. plot:: + :include-source: + :align: center + + import matplotlib.pyplot as plt + import numpy as np + + x = np.linspace(0, 2 * np.pi, 200) + y = np.sin(x) + + fig, ax = plt.subplots() + ax.plot(x, y) + plt.show() + + +Where to go next +---------------- + +- Check out :doc:`Plot types ` to get an overview of the + types of plots you can to with Matplotlib. +- Learn Matplotlib from the ground up in the + :doc:`Quick-start guide `. diff --git a/doc/users/index.rst b/doc/users/index.rst index fd3bec7dfcdd..f3ca47bf82ac 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -10,6 +10,7 @@ Usage guide ../plot_types/index.rst ../tutorials/index.rst ../gallery/index.rst + getting_started.rst explain.rst ../faq/index.rst ../api/index.rst From fcc8445745ee6fa62f614234c3603f7445788a1e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 7 Oct 2021 16:50:39 -0400 Subject: [PATCH 096/130] Update link to Agg website Fixes #21311 --- doc/users/history.rst | 2 +- examples/misc/demo_agg_filter.py | 2 +- lib/matplotlib/backends/backend_agg.py | 4 +++- src/ft2font.cpp | 2 +- tutorials/introductory/usage.py | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/users/history.rst b/doc/users/history.rst index 78c5d7cd0af9..19ba9c551ec2 100644 --- a/doc/users/history.rst +++ b/doc/users/history.rst @@ -75,7 +75,7 @@ backends: PS creates `PostScript® `_ hardcopy, SVG creates `Scalable Vector Graphics `_ hardcopy, Agg creates PNG output using the high quality `Anti-Grain -Geometry `_ +Geometry `_ library that ships with Matplotlib, GTK embeds Matplotlib in a `Gtk+ `_ application, GTKAgg uses the Anti-Grain renderer to create a figure diff --git a/examples/misc/demo_agg_filter.py b/examples/misc/demo_agg_filter.py index 45cd8bcd9620..f3eb95e520fd 100644 --- a/examples/misc/demo_agg_filter.py +++ b/examples/misc/demo_agg_filter.py @@ -7,7 +7,7 @@ rendering. You can modify the rendering of Artists by applying a filter via `.Artist.set_agg_filter`. -.. _Anti-Grain Geometry (AGG): http://antigrain.com +.. _Anti-Grain Geometry (AGG): http://agg.sourceforge.net/antigrain.com """ import matplotlib.cm as cm diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 4a62f5a921cd..59156e7bd81a 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -1,5 +1,5 @@ """ -An `Anti-Grain Geometry `_ (AGG) backend. +An `Anti-Grain Geometry`_ (AGG) backend. Features that are implemented: @@ -17,6 +17,8 @@ Still TODO: * integrate screen dpi w/ ppi and text + +.. _Anti-Grain Geometry: http://agg.sourceforge.net/antigrain.com """ try: diff --git a/src/ft2font.cpp b/src/ft2font.cpp index e1287c68592e..bb7fb1fa2980 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -20,7 +20,7 @@ To improve the hinting of the fonts, this code uses a hack presented here: - http://antigrain.com/research/font_rasterization/index.html + http://agg.sourceforge.net/antigrain.com/research/font_rasterization/index.html The idea is to limit the effect of hinting in the x-direction, while preserving hinting in the y-direction. Since freetype does not diff --git a/tutorials/introductory/usage.py b/tutorials/introductory/usage.py index 17e623399b65..233445877767 100644 --- a/tutorials/introductory/usage.py +++ b/tutorials/introductory/usage.py @@ -444,7 +444,7 @@ def my_plotter(ax, data1, data2, param_dict): # The names of builtin backends case-insensitive; e.g., 'QtAgg' and # 'qtagg' are equivalent. # -# .. _`Anti-Grain Geometry`: http://antigrain.com/ +# .. _`Anti-Grain Geometry`: http://agg.sourceforge.net/antigrain.com/ # .. _`Portable Document Format`: https://en.wikipedia.org/wiki/Portable_Document_Format # .. _Postscript: https://en.wikipedia.org/wiki/PostScript # .. _`Scalable Vector Graphics`: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics From 4b539a55ea24c6431a1e9c2adce1edf56552896f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 21:41:06 +0200 Subject: [PATCH 097/130] Inherit more docstrings. --- lib/matplotlib/artist.py | 2 +- lib/matplotlib/figure.py | 4 +--- lib/matplotlib/legend.py | 15 +-------------- lib/matplotlib/offsetbox.py | 20 ++++++++------------ lib/matplotlib/table.py | 2 +- lib/matplotlib/text.py | 13 +------------ lib/mpl_toolkits/mplot3d/axis3d.py | 2 +- 7 files changed, 14 insertions(+), 44 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 179badd3598e..89a3c3732126 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -302,7 +302,7 @@ def stale(self, val): def get_window_extent(self, renderer): """ - Get the axes bounding box in display space. + Get the artist's bounding box in display space. The bounding box' width and height are nonnegative. diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index dee0de8c5691..3793974b3096 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -317,9 +317,7 @@ def contains(self, mouseevent): return inside, {} def get_window_extent(self, *args, **kwargs): - """ - Return the figure bounding box in display space. Arguments are ignored. - """ + # docstring inherited return self.bbox def _suplabels(self, t, info, **kwargs): diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index b255bba67240..1814850c82fd 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -900,20 +900,7 @@ def get_window_extent(self, renderer=None): return self._legend_box.get_window_extent(renderer=renderer) def get_tightbbox(self, renderer): - """ - Like `.Legend.get_window_extent`, but uses the box for the legend. - - Parameters - ---------- - renderer : `.RendererBase` subclass - renderer that will be used to draw the figures (i.e. - ``fig.canvas.get_renderer()``) - - Returns - ------- - `.BboxBase` - The bounding box in figure pixel coordinates. - """ + # docstring inherited return self._legend_box.get_window_extent(renderer) def get_frame_on(self): diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index aabc1db652e1..27145abdf96c 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -346,7 +346,7 @@ def get_extent(self, renderer): return w, h, xd, yd def get_window_extent(self, renderer): - """Return the bounding box (`.Bbox`) in display space.""" + # docstring inherited w, h, xd, yd, offsets = self.get_extent_offsets(renderer) px, py = self.get_offset(w, h, xd, yd, renderer) return mtransforms.Bbox.from_bounds(px - xd, py - yd, w, h) @@ -640,7 +640,7 @@ def get_offset(self): return self._offset def get_window_extent(self, renderer): - """Return the bounding box in display space.""" + # docstring inherited w, h, xd, yd = self.get_extent(renderer) ox, oy = self.get_offset() # w, h, xd, yd) @@ -800,7 +800,7 @@ def get_offset(self): return self._offset def get_window_extent(self, renderer): - """Return the bounding box in display space.""" + # docstring inherited w, h, xd, yd = self.get_extent(renderer) ox, oy = self.get_offset() return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) @@ -901,7 +901,7 @@ def get_offset(self): return self._offset def get_window_extent(self, renderer): - """Return the bounding box in display space.""" + # docstring inherited w, h, xd, yd = self.get_extent(renderer) ox, oy = self.get_offset() # w, h, xd, yd) return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) @@ -1085,7 +1085,7 @@ def set_bbox_to_anchor(self, bbox, transform=None): self.stale = True def get_window_extent(self, renderer): - """Return the bounding box in display space.""" + # docstring inherited self._update_offset_func(renderer) w, h, xd, yd = self.get_extent(renderer) ox, oy = self.get_offset(w, h, xd, yd, renderer) @@ -1249,7 +1249,7 @@ def get_children(self): return [self.image] def get_window_extent(self, renderer): - """Return the bounding box in display space.""" + # docstring inherited w, h, xd, yd = self.get_extent(renderer) ox, oy = self.get_offset() return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) @@ -1440,18 +1440,14 @@ def get_fontsize(self): return self.prop.get_size_in_points() def get_window_extent(self, renderer): - """ - get the bounding box in display space. - """ + # docstring inherited bboxes = [child.get_window_extent(renderer) for child in self.get_children()] return Bbox.union(bboxes) def get_tightbbox(self, renderer): - """ - get tight bounding box in display space. - """ + # docstring inherited bboxes = [child.get_tightbbox(renderer) for child in self.get_children()] diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index e46cec6193f8..15ac6e4ae75a 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -447,7 +447,7 @@ def get_children(self): return list(self._cells.values()) def get_window_extent(self, renderer): - """Return the bounding box of the table in window coords.""" + # docstring inherited self._update_positions(renderer) boxes = [cell.get_window_extent(renderer) for cell in self._cells.values()] diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 3c0bc9965ed1..c9f7c75851d5 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1987,18 +1987,7 @@ def draw(self, renderer): Text.draw(self, renderer) def get_window_extent(self, renderer=None): - """ - Return the `.Bbox` bounding the text and arrow, in display units. - - Parameters - ---------- - renderer : Renderer, optional - A renderer is needed to compute the bounding box. If the artist - has already been drawn, the renderer is cached; thus, it is only - necessary to pass this argument when calling `get_window_extent` - before the first `draw`. In practice, it is usually easier to - trigger a draw first (e.g. by saving the figure). - """ + # docstring inherited # This block is the same as in Text.get_window_extent, but we need to # set the renderer before calling update_positions(). if not self.get_visible() or not self._check_xy(renderer): diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index adced6f4c21d..7d6110c25a47 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -451,7 +451,7 @@ def draw(self, renderer): # TODO: Get this to work (more) properly when mplot3d supports the # transforms framework. def get_tightbbox(self, renderer, *, for_layout_only=False): - # inherited docstring + # docstring inherited if not self.get_visible(): return # We have to directly access the internal data structures From 0dce1f475ab5a0a7c92ed72bdeb9f82d9c7dc685 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:51:15 +0200 Subject: [PATCH 098/130] Update doc/users/getting_started.rst Co-authored-by: hannah --- doc/users/getting_started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst index 915d27fa8b8a..241f103f09be 100644 --- a/doc/users/getting_started.rst +++ b/doc/users/getting_started.rst @@ -49,6 +49,6 @@ Where to go next ---------------- - Check out :doc:`Plot types ` to get an overview of the - types of plots you can to with Matplotlib. + types of plots you can create with Matplotlib. - Learn Matplotlib from the ground up in the :doc:`Quick-start guide `. From 865d9646f3dfb7761df30eb511df3252e0624001 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Fri, 8 Oct 2021 21:46:09 +0200 Subject: [PATCH 099/130] Add description for XYZ in contour method --- lib/mpl_toolkits/mplot3d/axes3d.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index b087229ce432..a18e52ea16d9 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2103,8 +2103,18 @@ def contour(self, X, Y, Z, *args, Parameters ---------- - X, Y, Z : array-like - Input data. + X, Y : array-like, + The coordinates of the values in *Z*. + + *X* and *Y* must both be 2D with the same shape as *Z* (e.g. + created via `numpy.meshgrid`), or they must both be 1-D such + that ``len(X) == N`` is the number of columns in *Z* and + ``len(Y) == M`` is the number of rows in *Z*. + + If not given, they are assumed to be integer indices, i.e. + ``X = range(N)``, ``Y = range(M)``. + Z : (M, N) array-like + The height values over which the contour is drawn. extend3d : bool, default: False Whether to extend contour in 3D. stride : int From 0a7013ae0f25a32b1e0de2e1032e8f94da20ad6e Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Fri, 8 Oct 2021 22:36:13 +0200 Subject: [PATCH 100/130] tested descreption --- lib/mpl_toolkits/mplot3d/axes3d.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a18e52ea16d9..4742540fe524 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2104,17 +2104,15 @@ def contour(self, X, Y, Z, *args, Parameters ---------- X, Y : array-like, - The coordinates of the values in *Z*. - - *X* and *Y* must both be 2D with the same shape as *Z* (e.g. - created via `numpy.meshgrid`), or they must both be 1-D such - that ``len(X) == N`` is the number of columns in *Z* and - ``len(Y) == M`` is the number of rows in *Z*. - - If not given, they are assumed to be integer indices, i.e. - ``X = range(N)``, ``Y = range(M)``. - Z : (M, N) array-like - The height values over which the contour is drawn. + *X* and *Y* are x-y coordinates, specified as 2D arrays of the same + size as *Z*. `numpy.meshgrid` function could be used to create *X* + and *Y* coordinate matrices from coordinate vectors. + If ``size(Z) == [N, N]`` the *X* and *Y* matrices could be + specified as 1D arrays, where ``len(X) == len(Y) == N`` + Z : array-like, + *Z* is a 2D array that specifies the height values at each x-y + coordinate over which the contour is drawn. Z must have at least + two rows and two columns. extend3d : bool, default: False Whether to extend contour in 3D. stride : int From 8d1edba7991542986bdc15b4ec79696cb6d46a98 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 9 Oct 2021 21:34:49 +0200 Subject: [PATCH 101/130] add references to axes methods --- lib/mpl_toolkits/mplot3d/axes3d.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 4742540fe524..25d9e9fde457 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2103,16 +2103,9 @@ def contour(self, X, Y, Z, *args, Parameters ---------- - X, Y : array-like, - *X* and *Y* are x-y coordinates, specified as 2D arrays of the same - size as *Z*. `numpy.meshgrid` function could be used to create *X* - and *Y* coordinate matrices from coordinate vectors. - If ``size(Z) == [N, N]`` the *X* and *Y* matrices could be - specified as 1D arrays, where ``len(X) == len(Y) == N`` - Z : array-like, - *Z* is a 2D array that specifies the height values at each x-y - coordinate over which the contour is drawn. Z must have at least - two rows and two columns. + X, Y, Z : array-like, + Input data. See `~matplotlib.axes.Axes.contour` for acceptable + data shapes. extend3d : bool, default: False Whether to extend contour in 3D. stride : int @@ -2156,7 +2149,8 @@ def tricontour(self, *args, Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `~matplotlib.axes.Axes.tricontour` for acceptable + data shapes. extend3d : bool, default: False Whether to extend contour in 3D. stride : int @@ -2214,7 +2208,8 @@ def contourf(self, X, Y, Z, *args, zdir='z', offset=None, **kwargs): Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `~matplotlib.axes.Axes.contourf` for acceptable + data shapes. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional @@ -2252,7 +2247,8 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs): Parameters ---------- X, Y, Z : array-like - Input data. + Input data. See `~matplotlib.axes.Axes.tricontourf` for acceptable + data shapes. zdir : {'x', 'y', 'z'}, default: 'z' The direction to use. offset : float, optional @@ -2333,7 +2329,7 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, Parameters ---------- xs, ys : array-like - The data positions. + The data positions. zs : float or array-like, default: 0 The z-positions. Either an array of the same length as *xs* and *ys* or a single value to place all points in the same plane. From d6548bf6381c2f2b68f6dc0fb43b6c61c828a317 Mon Sep 17 00:00:00 2001 From: Fernando Date: Sun, 10 Oct 2021 16:16:45 +0100 Subject: [PATCH 102/130] Fix medical image caption in tutorial --- tutorials/introductory/sample_plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/introductory/sample_plots.py b/tutorials/introductory/sample_plots.py index dd5f0cc078be..84dd92d3289b 100644 --- a/tutorials/introductory/sample_plots.py +++ b/tutorials/introductory/sample_plots.py @@ -42,7 +42,7 @@ :target: ../../gallery/images_contours_and_fields/image_demo.html :align: center - Example of using :func:`~matplotlib.pyplot.imshow` to display a CT scan + Example of using :func:`~matplotlib.pyplot.imshow` to display an MRI .. _screenshots_pcolormesh_demo: From 755043370e19d35c371cb7d9b44adb8545445c12 Mon Sep 17 00:00:00 2001 From: "Navid C. Constantinou" Date: Mon, 11 Oct 2021 07:35:52 +1100 Subject: [PATCH 103/130] fix default value for `shading` in 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 806e2acd0226..dde8bfd023bd 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5643,7 +5643,7 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None, rectangular grid. shading : {'flat', 'nearest', 'auto'}, optional - The fill style for the quadrilateral; defaults to 'flat' or + The fill style for the quadrilateral; defaults to 'auto' or :rc:`pcolor.shading`. Possible values: - 'flat': A solid color is used for each quad. The color of the From 19b735378ffbce204c34655f1d096e213d9ecbb7 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 11 Oct 2021 15:12:16 +0200 Subject: [PATCH 104/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] --- doc/users/backends.rst | 250 +++++++++++++++ doc/users/getting_started.rst | 6 + doc/users/index.rst | 4 + doc/users/performance.rst | 151 +++++++++ tutorials/introductory/usage.py | 541 +------------------------------- 5 files changed, 412 insertions(+), 540 deletions(-) create mode 100644 doc/users/backends.rst create mode 100644 doc/users/performance.rst diff --git a/doc/users/backends.rst b/doc/users/backends.rst new file mode 100644 index 000000000000..24fe007173f1 --- /dev/null +++ b/doc/users/backends.rst @@ -0,0 +1,250 @@ +.. _backends: + +======== +Backends +======== + +.. _what-is-a-backend: + +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. + +To support all of these use cases, Matplotlib can target different +outputs, and each of these capabilities is called a backend; the +"frontend" is the user facing code, i.e., the plotting code, whereas the +"backend" does all the hard work behind-the-scenes to make the figure. +There are two types of backends: user interface backends (for use in +PyQt/PySide, PyGObject, Tkinter, wxPython, or macOS/Cocoa); also referred to +as "interactive backends") and hardcopy backends to make image files +(PNG, SVG, PDF, PS; also referred to as "non-interactive backends"). + +Selecting a backend +------------------- + +There are three ways to configure your backend: + +- The :rc:`backend` parameter in your :file:`matplotlibrc` file +- The :envvar:`MPLBACKEND` environment variable +- The function :func:`matplotlib.use` + +Below is a more detailed description. + +If there is more than one configuration present, the last one from the +list takes precedence; e.g. calling :func:`matplotlib.use()` will override +the setting in your :file:`matplotlibrc`. + +Without a backend explicitly set, Matplotlib automatically detects a usable +backend based on what is available on your system and on whether a GUI event +loop is already running. The first usable backend in the following list is +selected: MacOSX, QtAgg, GTK4Agg, Gtk3Agg, TkAgg, WxAgg, Agg. The last, Agg, +is a non-interactive backend that can only write to files. It is used on +Linux, if Matplotlib cannot connect to either an X display or a Wayland +display. + +Here is a detailed description of the configuration methods: + +#. Setting :rc:`backend` in your :file:`matplotlibrc` file:: + + backend : qtagg # use pyqt with antigrain (agg) rendering + + See also :doc:`/tutorials/introductory/customizing`. + +#. Setting the :envvar:`MPLBACKEND` environment variable: + + You can set the environment variable either for your current shell or for + a single script. + + On Unix:: + + > export MPLBACKEND=qtagg + > python simple_plot.py + + > MPLBACKEND=qtagg python simple_plot.py + + On Windows, only the former is possible:: + + > set MPLBACKEND=qtagg + > python simple_plot.py + + Setting this environment variable will override the ``backend`` parameter + in *any* :file:`matplotlibrc`, even if there is a :file:`matplotlibrc` in + your current working directory. Therefore, setting :envvar:`MPLBACKEND` + globally, e.g. in your :file:`.bashrc` or :file:`.profile`, is discouraged + as it might lead to counter-intuitive behavior. + +#. If your script depends on a specific backend you can use the function + :func:`matplotlib.use`:: + + import matplotlib + matplotlib.use('qtagg') + + This should be done before any figure is created, otherwise Matplotlib may + fail to switch the backend and raise an ImportError. + + Using `~matplotlib.use` will require changes in your code if users want to + use a different backend. Therefore, you should avoid explicitly calling + `~matplotlib.use` unless absolutely necessary. + +.. _the-builtin-backends: + +The builtin backends +-------------------- + +By default, Matplotlib should automatically select a default backend which +allows both interactive work and plotting from scripts, with output to the +screen and/or to a file, so at least initially, you will not need to worry +about the backend. The most common exception is if your Python distribution +comes without :mod:`tkinter` and you have no other GUI toolkit installed. +This happens on certain Linux distributions, where you need to install a +Linux package named ``python-tk`` (or similar). + +If, however, you want to write graphical user interfaces, or a web +application server +(:doc:`/gallery/user_interfaces/web_application_server_sgskip`), or need a +better understanding of what is going on, read on. To make things easily +more customizable for graphical user interfaces, Matplotlib separates +the concept of the renderer (the thing that actually does the drawing) +from the canvas (the place where the drawing goes). The canonical +renderer for user interfaces is ``Agg`` which uses the `Anti-Grain +Geometry`_ C++ library to make a raster (pixel) image of the figure; it +is used by the ``QtAgg``, ``GTK4Agg``, ``GTK3Agg``, ``wxAgg``, ``TkAgg``, and +``macosx`` backends. An alternative renderer is based on the Cairo library, +used by ``QtCairo``, etc. + +For the rendering engines, users can also distinguish between `vector +`_ or `raster +`_ renderers. Vector +graphics languages issue drawing commands like "draw a line from this +point to this point" and hence are scale free. Raster backends +generate a pixel representation of the line whose accuracy depends on a +DPI setting. + +Here is a summary of the Matplotlib renderers (there is an eponymous +backend for each; these are *non-interactive backends*, capable of +writing to a file): + +======== ========= ======================================================= +Renderer Filetypes Description +======== ========= ======================================================= +AGG png raster_ graphics -- high quality images using the + `Anti-Grain Geometry`_ engine +PDF pdf vector_ graphics -- `Portable Document Format`_ +PS ps, eps vector_ graphics -- Postscript_ output +SVG svg vector_ graphics -- `Scalable Vector Graphics`_ +PGF pgf, pdf vector_ graphics -- using the pgf_ package +Cairo png, ps, raster_ or vector_ graphics -- using the Cairo_ library + pdf, svg +======== ========= ======================================================= + +To save plots using the non-interactive backends, use the +``matplotlib.pyplot.savefig('filename')`` method. + +These are the user interfaces and renderer combinations supported; +these are *interactive backends*, capable of displaying to the screen +and using appropriate renderers from the table above to write to +a file: + +========= ================================================================ +Backend Description +========= ================================================================ +QtAgg Agg rendering in a Qt_ canvas (requires PyQt_ or `Qt for Python`_, + a.k.a. PySide). This backend can be activated in IPython with + ``%matplotlib qt``. +ipympl Agg rendering embedded in a Jupyter widget. (requires ipympl). + This backend can be enabled in a Jupyter notebook with + ``%matplotlib ipympl``. +GTK3Agg Agg rendering to a GTK_ 3.x canvas (requires PyGObject_, + and pycairo_ or cairocffi_). This backend can be activated in + IPython with ``%matplotlib gtk3``. +GTK4Agg Agg rendering to a GTK_ 4.x canvas (requires PyGObject_, + and pycairo_ or cairocffi_). This backend can be activated in + IPython with ``%matplotlib gtk4``. +macosx Agg rendering into a Cocoa canvas in OSX. This backend can be + activated in IPython with ``%matplotlib osx``. +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``. +WebAgg On ``show()`` will start a tornado server with an interactive + figure. +GTK3Cairo Cairo rendering to a GTK_ 3.x canvas (requires PyGObject_, + and pycairo_ or cairocffi_). +GTK4Cairo Cairo rendering to a GTK_ 4.x canvas (requires PyGObject_, + and pycairo_ or cairocffi_). +wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4). + This backend can be activated in IPython with ``%matplotlib wx``. +========= ================================================================ + +.. note:: + The names of builtin backends case-insensitive; e.g., 'QtAgg' and + 'qtagg' are equivalent. + +.. _`Anti-Grain Geometry`: http://agg.sourceforge.net/antigrain.com/ +.. _`Portable Document Format`: https://en.wikipedia.org/wiki/Portable_Document_Format +.. _Postscript: https://en.wikipedia.org/wiki/PostScript +.. _`Scalable Vector Graphics`: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics +.. _pgf: https://ctan.org/pkg/pgf +.. _Cairo: https://www.cairographics.org +.. _PyGObject: https://wiki.gnome.org/action/show/Projects/PyGObject +.. _pycairo: https://www.cairographics.org/pycairo/ +.. _cairocffi: https://pythonhosted.org/cairocffi/ +.. _wxPython: https://www.wxpython.org/ +.. _TkInter: https://docs.python.org/3/library/tk.html +.. _PyQt: https://riverbankcomputing.com/software/pyqt/intro +.. _`Qt for Python`: https://doc.qt.io/qtforpython/ +.. _Qt: https://qt.io/ +.. _GTK: https://www.gtk.org/ +.. _Tk: https://www.tcl.tk/ +.. _wxWidgets: https://www.wxwidgets.org/ + +ipympl +^^^^^^ + +The Jupyter widget ecosystem is moving too fast to support directly in +Matplotlib. To install ipympl: + +.. code-block:: bash + + pip install ipympl + jupyter nbextension enable --py --sys-prefix ipympl + +or + +.. code-block:: bash + + conda install ipympl -c conda-forge + +See `jupyter-matplotlib `__ +for more details. + +.. _QT_API-usage: + +How do I select PyQt5 or PySide2? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The :envvar:`QT_API` environment variable can be set to either ``pyqt5`` or +``pyside2`` to use ``PyQt5`` or ``PySide2``, respectively. + +Since the default value for the bindings to be used is ``PyQt5``, Matplotlib +first tries to import it. If the import fails, it tries to import +``PySide2``. + +Using non-builtin backends +-------------------------- +More generally, any importable backend can be selected by using any of the +methods above. If ``name.of.the.backend`` is the module containing the +backend, use ``module://name.of.the.backend`` as the backend name, e.g. +``matplotlib.use('module://name.of.the.backend')``. \ No newline at end of file diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst index 241f103f09be..d48b6b454ba7 100644 --- a/doc/users/getting_started.rst +++ b/doc/users/getting_started.rst @@ -24,6 +24,12 @@ Installation Further details are available in the :doc:`Installation Guide `. +Choosing a backend +------------------ + +Matplotlib needs a "backend" to render your figure, either in its own GUI window, +as part of a notebook, or saved as an image to disk. If you need more information on +choosing a backend, see :ref:`backends`. Draw a first plot ----------------- diff --git a/doc/users/index.rst b/doc/users/index.rst index f3ca47bf82ac..09ea86ef5eb8 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -11,7 +11,11 @@ Usage guide ../tutorials/index.rst ../gallery/index.rst getting_started.rst + backends.rst + performance.rst + ../gallery/usage.rst explain.rst ../faq/index.rst ../api/index.rst ../resources/index.rst + \ No newline at end of file diff --git a/doc/users/performance.rst b/doc/users/performance.rst new file mode 100644 index 000000000000..aa291f407c1e --- /dev/null +++ b/doc/users/performance.rst @@ -0,0 +1,151 @@ +.. _performance: + +Performance +=========== + +Whether exploring data in interactive mode or programmatically +saving lots of plots, rendering performance can be a challenging +bottleneck in your pipeline. Matplotlib provides multiple +ways to greatly reduce rendering time at the cost of a slight +change (to a settable tolerance) in your plot's appearance. +The methods available to reduce rendering time depend on the +type of plot that is being created. + +Line segment simplification +--------------------------- + +For plots that have line segments (e.g. typical line plots, outlines +of polygons, etc.), rendering performance can be controlled by +:rc:`path.simplify` and :rc:`path.simplify_threshold`, which +can be defined e.g. in the :file:`matplotlibrc` file (see +:doc:`/tutorials/introductory/customizing` for more information about +the :file:`matplotlibrc` file). :rc:`path.simplify` is a Boolean +indicating whether or not line segments are simplified at all. +:rc:`path.simplify_threshold` controls how much line segments are simplified; +higher thresholds result in quicker rendering. + +The following script will first display the data without any +simplification, and then display the same data with simplification. +Try interacting with both of them:: + + import numpy as np + import matplotlib.pyplot as plt + import matplotlib as mpl + + # Setup, and create the data to plot + y = np.random.rand(100000) + y[50000:] *= 2 + y[np.geomspace(10, 50000, 400).astype(int)] = -1 + mpl.rcParams['path.simplify'] = True + + mpl.rcParams['path.simplify_threshold'] = 0.0 + plt.plot(y) + plt.show() + + mpl.rcParams['path.simplify_threshold'] = 1.0 + plt.plot(y) + plt.show() + +Matplotlib currently defaults to a conservative simplification +threshold of ``1/9``. To change default settings to use a different +value, change the :file:`matplotlibrc` file. Alternatively, users +can create a new style for interactive plotting (with maximal +simplification) and another style for publication quality plotting +(with minimal simplification) and activate them as necessary. See +:doc:`/tutorials/introductory/customizing` for instructions on +how to perform these actions. + + +The simplification works by iteratively merging line segments +into a single vector until the next line segment's perpendicular +distance to the vector (measured in display-coordinate space) +is greater than the ``path.simplify_threshold`` parameter. + +.. note:: + Changes related to how line segments are simplified were made + in version 2.1. Rendering time will still be improved by these + parameters prior to 2.1, but rendering time for some kinds of + data will be vastly improved in versions 2.1 and greater. + +Marker simplification +--------------------- + +Markers can also be simplified, albeit less robustly than +line segments. Marker simplification is only available +to :class:`~matplotlib.lines.Line2D` objects (through the +``markevery`` property). Wherever +:class:`~matplotlib.lines.Line2D` construction parameters +are passed through, such as +:func:`matplotlib.pyplot.plot` and +:meth:`matplotlib.axes.Axes.plot`, the ``markevery`` +parameter can be used:: + + plt.plot(x, y, markevery=10) + +The ``markevery`` argument allows for naive subsampling, or an +attempt at evenly spaced (along the *x* axis) sampling. See the +:doc:`/gallery/lines_bars_and_markers/markevery_demo` +for more information. + +Splitting lines into smaller chunks +----------------------------------- + +If you are using the Agg backend (see :ref:`what-is-a-backend`), +then you can make use of :rc:`agg.path.chunksize` +This allows users to specify a chunk size, and any lines with +greater than that many vertices will be split into multiple +lines, each of which has no more than ``agg.path.chunksize`` +many vertices. (Unless ``agg.path.chunksize`` is zero, in +which case there is no chunking.) For some kind of data, +chunking the line up into reasonable sizes can greatly +decrease rendering time. + +The following script will first display the data without any +chunk size restriction, and then display the same data with +a chunk size of 10,000. The difference can best be seen when +the figures are large, try maximizing the GUI and then +interacting with them:: + + import numpy as np + import matplotlib.pyplot as plt + import matplotlib as mpl + mpl.rcParams['path.simplify_threshold'] = 1.0 + + # Setup, and create the data to plot + y = np.random.rand(100000) + y[50000:] *= 2 + y[np.geomspace(10, 50000, 400).astype(int)] = -1 + mpl.rcParams['path.simplify'] = True + + mpl.rcParams['agg.path.chunksize'] = 0 + plt.plot(y) + plt.show() + + mpl.rcParams['agg.path.chunksize'] = 10000 + plt.plot(y) + plt.show() + +Legends +------- + +The default legend behavior for axes attempts to find the location +that covers the fewest data points (``loc='best'``). This can be a +very expensive computation if there are lots of data points. In +this case, you may want to provide a specific location. + +Using the *fast* style +---------------------- + +The *fast* style can be used to automatically set +simplification and chunking parameters to reasonable +settings to speed up plotting large amounts of data. +The following code runs it:: + + import matplotlib.style as mplstyle + mplstyle.use('fast') + +It is very lightweight, so it works well with other +styles. Be sure the fast style is applied last +so that other styles do not overwrite the settings:: + + mplstyle.use(['dark_background', 'ggplot', 'fast']) diff --git a/tutorials/introductory/usage.py b/tutorials/introductory/usage.py index 233445877767..d40dcb3fd325 100644 --- a/tutorials/introductory/usage.py +++ b/tutorials/introductory/usage.py @@ -249,543 +249,4 @@ def my_plotter(ax, data1, data2, param_dict): ############################################################################### # These examples provide convenience for more complex graphs. -# -# -# .. _backends: -# -# Backends -# ======== -# -# .. _what-is-a-backend: -# -# 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. -# -# To support all of these use cases, Matplotlib can target different -# outputs, and each of these capabilities is called a backend; the -# "frontend" is the user facing code, i.e., the plotting code, whereas the -# "backend" does all the hard work behind-the-scenes to make the figure. -# There are two types of backends: user interface backends (for use in -# PyQt/PySide, PyGObject, Tkinter, wxPython, or macOS/Cocoa); also referred to -# as "interactive backends") and hardcopy backends to make image files -# (PNG, SVG, PDF, PS; also referred to as "non-interactive backends"). -# -# Selecting a backend -# ------------------- -# -# There are three ways to configure your backend: -# -# - The :rc:`backend` parameter in your :file:`matplotlibrc` file -# - The :envvar:`MPLBACKEND` environment variable -# - The function :func:`matplotlib.use` -# -# Below is a more detailed description. -# -# If there is more than one configuration present, the last one from the -# list takes precedence; e.g. calling :func:`matplotlib.use()` will override -# the setting in your :file:`matplotlibrc`. -# -# Without a backend explicitly set, Matplotlib automatically detects a usable -# backend based on what is available on your system and on whether a GUI event -# loop is already running. The first usable backend in the following list is -# selected: MacOSX, QtAgg, GTK4Agg, Gtk3Agg, TkAgg, WxAgg, Agg. The last, Agg, -# is a non-interactive backend that can only write to files. It is used on -# Linux, if Matplotlib cannot connect to either an X display or a Wayland -# display. -# -# Here is a detailed description of the configuration methods: -# -# #. Setting :rc:`backend` in your :file:`matplotlibrc` file:: -# -# backend : qtagg # use pyqt with antigrain (agg) rendering -# -# See also :doc:`/tutorials/introductory/customizing`. -# -# #. Setting the :envvar:`MPLBACKEND` environment variable: -# -# You can set the environment variable either for your current shell or for -# a single script. -# -# On Unix:: -# -# > export MPLBACKEND=qtagg -# > python simple_plot.py -# -# > MPLBACKEND=qtagg python simple_plot.py -# -# On Windows, only the former is possible:: -# -# > set MPLBACKEND=qtagg -# > python simple_plot.py -# -# Setting this environment variable will override the ``backend`` parameter -# in *any* :file:`matplotlibrc`, even if there is a :file:`matplotlibrc` in -# your current working directory. Therefore, setting :envvar:`MPLBACKEND` -# globally, e.g. in your :file:`.bashrc` or :file:`.profile`, is discouraged -# as it might lead to counter-intuitive behavior. -# -# #. If your script depends on a specific backend you can use the function -# :func:`matplotlib.use`:: -# -# import matplotlib -# matplotlib.use('qtagg') -# -# This should be done before any figure is created, otherwise Matplotlib may -# fail to switch the backend and raise an ImportError. -# -# Using `~matplotlib.use` will require changes in your code if users want to -# use a different backend. Therefore, you should avoid explicitly calling -# `~matplotlib.use` unless absolutely necessary. -# -# .. _the-builtin-backends: -# -# The builtin backends -# -------------------- -# -# By default, Matplotlib should automatically select a default backend which -# allows both interactive work and plotting from scripts, with output to the -# screen and/or to a file, so at least initially, you will not need to worry -# about the backend. The most common exception is if your Python distribution -# comes without :mod:`tkinter` and you have no other GUI toolkit installed. -# This happens on certain Linux distributions, where you need to install a -# Linux package named ``python-tk`` (or similar). -# -# If, however, you want to write graphical user interfaces, or a web -# application server -# (:doc:`/gallery/user_interfaces/web_application_server_sgskip`), or need a -# better understanding of what is going on, read on. To make things easily -# more customizable for graphical user interfaces, Matplotlib separates -# the concept of the renderer (the thing that actually does the drawing) -# from the canvas (the place where the drawing goes). The canonical -# renderer for user interfaces is ``Agg`` which uses the `Anti-Grain -# Geometry`_ C++ library to make a raster (pixel) image of the figure; it -# is used by the ``QtAgg``, ``GTK4Agg``, ``GTK3Agg``, ``wxAgg``, ``TkAgg``, and -# ``macosx`` backends. An alternative renderer is based on the Cairo library, -# used by ``QtCairo``, etc. -# -# For the rendering engines, users can also distinguish between `vector -# `_ or `raster -# `_ renderers. Vector -# graphics languages issue drawing commands like "draw a line from this -# point to this point" and hence are scale free. Raster backends -# generate a pixel representation of the line whose accuracy depends on a -# DPI setting. -# -# Here is a summary of the Matplotlib renderers (there is an eponymous -# backend for each; these are *non-interactive backends*, capable of -# writing to a file): -# -# ======== ========= ======================================================= -# Renderer Filetypes Description -# ======== ========= ======================================================= -# AGG png raster_ graphics -- high quality images using the -# `Anti-Grain Geometry`_ engine -# PDF pdf vector_ graphics -- `Portable Document Format`_ -# PS ps, eps vector_ graphics -- Postscript_ output -# SVG svg vector_ graphics -- `Scalable Vector Graphics`_ -# PGF pgf, pdf vector_ graphics -- using the pgf_ package -# Cairo png, ps, raster_ or vector_ graphics -- using the Cairo_ library -# pdf, svg -# ======== ========= ======================================================= -# -# To save plots using the non-interactive backends, use the -# ``matplotlib.pyplot.savefig('filename')`` method. -# -# These are the user interfaces and renderer combinations supported; -# these are *interactive backends*, capable of displaying to the screen -# and using appropriate renderers from the table above to write to -# a file: -# -# ========= ================================================================ -# Backend Description -# ========= ================================================================ -# QtAgg Agg rendering in a Qt_ canvas (requires PyQt_ or `Qt for Python`_, -# a.k.a. PySide). This backend can be activated in IPython with -# ``%matplotlib qt``. -# ipympl Agg rendering embedded in a Jupyter widget. (requires ipympl). -# This backend can be enabled in a Jupyter notebook with -# ``%matplotlib ipympl``. -# GTK3Agg Agg rendering to a GTK_ 3.x canvas (requires PyGObject_, -# and pycairo_ or cairocffi_). This backend can be activated in -# IPython with ``%matplotlib gtk3``. -# GTK4Agg Agg rendering to a GTK_ 4.x canvas (requires PyGObject_, -# and pycairo_ or cairocffi_). This backend can be activated in -# IPython with ``%matplotlib gtk4``. -# macosx Agg rendering into a Cocoa canvas in OSX. This backend can be -# activated in IPython with ``%matplotlib osx``. -# 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``. -# WebAgg On ``show()`` will start a tornado server with an interactive -# figure. -# GTK3Cairo Cairo rendering to a GTK_ 3.x canvas (requires PyGObject_, -# and pycairo_ or cairocffi_). -# GTK4Cairo Cairo rendering to a GTK_ 4.x canvas (requires PyGObject_, -# and pycairo_ or cairocffi_). -# wxAgg Agg rendering to a wxWidgets_ canvas (requires wxPython_ 4). -# This backend can be activated in IPython with ``%matplotlib wx``. -# ========= ================================================================ -# -# .. note:: -# The names of builtin backends case-insensitive; e.g., 'QtAgg' and -# 'qtagg' are equivalent. -# -# .. _`Anti-Grain Geometry`: http://agg.sourceforge.net/antigrain.com/ -# .. _`Portable Document Format`: https://en.wikipedia.org/wiki/Portable_Document_Format -# .. _Postscript: https://en.wikipedia.org/wiki/PostScript -# .. _`Scalable Vector Graphics`: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics -# .. _pgf: https://ctan.org/pkg/pgf -# .. _Cairo: https://www.cairographics.org -# .. _PyGObject: https://wiki.gnome.org/action/show/Projects/PyGObject -# .. _pycairo: https://www.cairographics.org/pycairo/ -# .. _cairocffi: https://pythonhosted.org/cairocffi/ -# .. _wxPython: https://www.wxpython.org/ -# .. _TkInter: https://docs.python.org/3/library/tk.html -# .. _PyQt: https://riverbankcomputing.com/software/pyqt/intro -# .. _`Qt for Python`: https://doc.qt.io/qtforpython/ -# .. _Qt: https://qt.io/ -# .. _GTK: https://www.gtk.org/ -# .. _Tk: https://www.tcl.tk/ -# .. _wxWidgets: https://www.wxwidgets.org/ -# -# ipympl -# ^^^^^^ -# -# The Jupyter widget ecosystem is moving too fast to support directly in -# Matplotlib. To install ipympl: -# -# .. code-block:: bash -# -# pip install ipympl -# jupyter nbextension enable --py --sys-prefix ipympl -# -# or -# -# .. code-block:: bash -# -# conda install ipympl -c conda-forge -# -# See `jupyter-matplotlib `__ -# for more details. -# -# .. _QT_API-usage: -# -# How do I select PyQt5 or PySide2? -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# The :envvar:`QT_API` environment variable can be set to either ``pyqt5`` or -# ``pyside2`` to use ``PyQt5`` or ``PySide2``, respectively. -# -# Since the default value for the bindings to be used is ``PyQt5``, Matplotlib -# first tries to import it. If the import fails, it tries to import -# ``PySide2``. -# -# Using non-builtin backends -# -------------------------- -# More generally, any importable backend can be selected by using any of the -# methods above. If ``name.of.the.backend`` is the module containing the -# backend, use ``module://name.of.the.backend`` as the backend name, e.g. -# ``matplotlib.use('module://name.of.the.backend')``. -# -# -# .. _interactive-mode: -# -# What is interactive mode? -# ========================= -# -# Use of an interactive backend (see :ref:`what-is-a-backend`) -# permits--but does not by itself require or ensure--plotting -# to the screen. Whether and when plotting to the screen occurs, -# and whether a script or shell session continues after a plot -# is drawn on the screen, depends on the functions and methods -# that are called, and on a state variable that determines whether -# Matplotlib is in "interactive mode." The default Boolean value is set -# by the :file:`matplotlibrc` file, and may be customized like any other -# configuration parameter (see :doc:`/tutorials/introductory/customizing`). It -# may also be set via :func:`matplotlib.interactive`, and its -# value may be queried via :func:`matplotlib.is_interactive`. Turning -# interactive mode on and off in the middle of a stream of plotting -# commands, whether in a script or in a shell, is rarely needed -# and potentially confusing. In the following, we will assume all -# plotting is done with interactive mode either on or off. -# -# .. note:: -# Major changes related to interactivity, and in particular the -# role and behavior of :func:`~matplotlib.pyplot.show`, were made in the -# transition to Matplotlib version 1.0, and bugs were fixed in -# 1.0.1. Here we describe the version 1.0.1 behavior for the -# primary interactive backends, with the partial exception of -# *macosx*. -# -# Interactive mode may also be turned on via :func:`matplotlib.pyplot.ion`, -# and turned off via :func:`matplotlib.pyplot.ioff`. -# -# .. note:: -# Interactive mode works with suitable backends in ipython and in -# the ordinary Python shell, but it does *not* work in the IDLE IDE. -# If the default backend does not support interactivity, an interactive -# backend can be explicitly activated using any of the methods discussed -# in `What is a backend?`_. -# -# -# Interactive example -# -------------------- -# -# From an ordinary Python prompt, or after invoking ipython with no options, -# try this:: -# -# import matplotlib.pyplot as plt -# plt.ion() -# plt.plot([1.6, 2.7]) -# -# This will pop up a plot window. Your terminal prompt will remain active, so -# that you can type additional commands such as:: -# -# plt.title("interactive test") -# plt.xlabel("index") -# -# On most interactive backends, the figure window will also be updated if you -# change it via the object-oriented interface. That is, get a reference to the -# `~matplotlib.axes.Axes` instance, and call a method of that instance:: -# -# ax = plt.gca() -# ax.plot([3.1, 2.2]) -# -# If you are using certain backends (like ``macosx``), or an older version -# of Matplotlib, you may not see the new line added to the plot immediately. -# In this case, you need to explicitly call :func:`~matplotlib.pyplot.draw` -# in order to update the plot:: -# -# plt.draw() -# -# -# Non-interactive example -# ----------------------- -# -# Start a new session as per the previous example, but now -# turn interactive mode off:: -# -# import matplotlib.pyplot as plt -# plt.ioff() -# plt.plot([1.6, 2.7]) -# -# Nothing happened--or at least nothing has shown up on the -# screen (unless you are using *macosx* backend, which is -# anomalous). To make the plot appear, you need to do this:: -# -# plt.show() -# -# Now you see the plot, but your terminal command line is -# unresponsive; `.pyplot.show()` *blocks* the input -# of additional commands until you manually close the plot -# window. -# -# Using a blocking function has benefits to users. Suppose a user -# needs a script that plots the contents of a file to the screen. -# The user may want to look at that plot, and then end the script. -# Without a blocking command such as ``show()``, the script would -# flash up the plot and then end immediately, leaving nothing on -# the screen. -# -# In addition, non-interactive mode delays all drawing until -# ``show()`` is called. This is more efficient than redrawing -# the plot each time a line in the script adds a new feature. -# -# Prior to version 1.0, ``show()`` generally could not be called -# more than once in a single script (although sometimes one -# could get away with it). For version 1.0.1 and above, this -# restriction is lifted, so one can write a script like this:: -# -# import numpy as np -# import matplotlib.pyplot as plt -# -# plt.ioff() -# for i in range(3): -# plt.plot(np.random.rand(10)) -# plt.show() -# -# This makes three plots, one at a time. That is, the second plot will show up -# once the first plot is closed. -# -# Summary -# ------- -# -# In interactive mode, pyplot functions automatically draw -# to the screen. -# -# When plotting interactively, if using -# object method calls in addition to pyplot functions, then -# call :func:`~matplotlib.pyplot.draw` whenever you want to -# refresh the plot. -# -# Use non-interactive mode in scripts in which you want to -# generate one or more figures and display them before ending -# or generating a new set of figures. In that case, use -# :func:`~matplotlib.pyplot.show` to display the figure(s) and -# to block execution until you have manually destroyed them. -# -# .. _performance: -# -# Performance -# =========== -# -# Whether exploring data in interactive mode or programmatically -# saving lots of plots, rendering performance can be a challenging -# bottleneck in your pipeline. Matplotlib provides multiple -# ways to greatly reduce rendering time at the cost of a slight -# change (to a settable tolerance) in your plot's appearance. -# The methods available to reduce rendering time depend on the -# type of plot that is being created. -# -# Line segment simplification -# --------------------------- -# -# For plots that have line segments (e.g. typical line plots, outlines -# of polygons, etc.), rendering performance can be controlled by -# :rc:`path.simplify` and :rc:`path.simplify_threshold`, which -# can be defined e.g. in the :file:`matplotlibrc` file (see -# :doc:`/tutorials/introductory/customizing` for more information about -# the :file:`matplotlibrc` file). :rc:`path.simplify` is a Boolean -# indicating whether or not line segments are simplified at all. -# :rc:`path.simplify_threshold` controls how much line segments are simplified; -# higher thresholds result in quicker rendering. -# -# The following script will first display the data without any -# simplification, and then display the same data with simplification. -# Try interacting with both of them:: -# -# import numpy as np -# import matplotlib.pyplot as plt -# import matplotlib as mpl -# -# # Setup, and create the data to plot -# y = np.random.rand(100000) -# y[50000:] *= 2 -# y[np.geomspace(10, 50000, 400).astype(int)] = -1 -# mpl.rcParams['path.simplify'] = True -# -# mpl.rcParams['path.simplify_threshold'] = 0.0 -# plt.plot(y) -# plt.show() -# -# mpl.rcParams['path.simplify_threshold'] = 1.0 -# plt.plot(y) -# plt.show() -# -# Matplotlib currently defaults to a conservative simplification -# threshold of ``1/9``. To change default settings to use a different -# value, change the :file:`matplotlibrc` file. Alternatively, users -# can create a new style for interactive plotting (with maximal -# simplification) and another style for publication quality plotting -# (with minimal simplification) and activate them as necessary. See -# :doc:`/tutorials/introductory/customizing` for instructions on -# how to perform these actions. -# -# -# The simplification works by iteratively merging line segments -# into a single vector until the next line segment's perpendicular -# distance to the vector (measured in display-coordinate space) -# is greater than the ``path.simplify_threshold`` parameter. -# -# .. note:: -# Changes related to how line segments are simplified were made -# in version 2.1. Rendering time will still be improved by these -# parameters prior to 2.1, but rendering time for some kinds of -# data will be vastly improved in versions 2.1 and greater. -# -# Marker simplification -# --------------------- -# -# Markers can also be simplified, albeit less robustly than -# line segments. Marker simplification is only available -# to :class:`~matplotlib.lines.Line2D` objects (through the -# ``markevery`` property). Wherever -# :class:`~matplotlib.lines.Line2D` construction parameters -# are passed through, such as -# :func:`matplotlib.pyplot.plot` and -# :meth:`matplotlib.axes.Axes.plot`, the ``markevery`` -# parameter can be used:: -# -# plt.plot(x, y, markevery=10) -# -# The ``markevery`` argument allows for naive subsampling, or an -# attempt at evenly spaced (along the *x* axis) sampling. See the -# :doc:`/gallery/lines_bars_and_markers/markevery_demo` -# for more information. -# -# Splitting lines into smaller chunks -# ----------------------------------- -# -# If you are using the Agg backend (see :ref:`what-is-a-backend`), -# then you can make use of :rc:`agg.path.chunksize` -# This allows users to specify a chunk size, and any lines with -# greater than that many vertices will be split into multiple -# lines, each of which has no more than ``agg.path.chunksize`` -# many vertices. (Unless ``agg.path.chunksize`` is zero, in -# which case there is no chunking.) For some kind of data, -# chunking the line up into reasonable sizes can greatly -# decrease rendering time. -# -# The following script will first display the data without any -# chunk size restriction, and then display the same data with -# a chunk size of 10,000. The difference can best be seen when -# the figures are large, try maximizing the GUI and then -# interacting with them:: -# -# import numpy as np -# import matplotlib.pyplot as plt -# import matplotlib as mpl -# mpl.rcParams['path.simplify_threshold'] = 1.0 -# -# # Setup, and create the data to plot -# y = np.random.rand(100000) -# y[50000:] *= 2 -# y[np.geomspace(10, 50000, 400).astype(int)] = -1 -# mpl.rcParams['path.simplify'] = True -# -# mpl.rcParams['agg.path.chunksize'] = 0 -# plt.plot(y) -# plt.show() -# -# mpl.rcParams['agg.path.chunksize'] = 10000 -# plt.plot(y) -# plt.show() -# -# Legends -# ------- -# -# The default legend behavior for axes attempts to find the location -# that covers the fewest data points (``loc='best'``). This can be a -# very expensive computation if there are lots of data points. In -# this case, you may want to provide a specific location. -# -# Using the *fast* style -# ---------------------- -# -# The *fast* style can be used to automatically set -# simplification and chunking parameters to reasonable -# settings to speed up plotting large amounts of data. -# The following code runs it:: -# -# import matplotlib.style as mplstyle -# mplstyle.use('fast') -# -# It is very lightweight, so it works well with other -# styles. Be sure the fast style is applied last -# so that other styles do not overwrite the settings:: -# -# mplstyle.use(['dark_background', 'ggplot', 'fast']) + From 42d2ee46a128020d77b6e7f113ca57191b21447d Mon Sep 17 00:00:00 2001 From: Kinshuk Dua Date: Mon, 11 Oct 2021 22:53:20 +0530 Subject: [PATCH 105/130] Fix linting errors in test_agg.py --- lib/matplotlib/tests/test_agg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 2192f939a284..32fea7cd4590 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -248,6 +248,7 @@ def test_pil_kwargs_tiff(): tags = {TiffTags.TAGS_V2[k].name: v for k, v in im.tag_v2.items()} assert tags["ImageDescription"] == "test image" + def test_pil_kwargs_webp(): plt.plot([0, 1, 2], [0, 1, 0]) buf_small = io.BytesIO() @@ -258,13 +259,13 @@ def test_pil_kwargs_webp(): plt.savefig(buf_large, format="webp", pil_kwargs=pil_kwargs_high) assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes + def test_webp_alpha(): plt.plot([0, 1, 2], [0, 1, 0]) buf = io.BytesIO() plt.savefig(buf, format="webp", transparent=True) im = Image.open(buf) assert im.mode == "RGBA" - def test_draw_path_collection_error_handling(): From f774498a6612e1148356729b3850a4359f9a615f Mon Sep 17 00:00:00 2001 From: "Navid C. Constantinou" Date: Tue, 12 Oct 2021 09:33:29 +1100 Subject: [PATCH 106/130] default shading is :rc:`pcolor.shading` --- lib/matplotlib/axes/_axes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index dde8bfd023bd..719866bbf935 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5642,9 +5642,8 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None, expanded as needed into the appropriate 2D arrays, making a rectangular grid. - shading : {'flat', 'nearest', 'auto'}, optional - The fill style for the quadrilateral; defaults to 'auto' or - :rc:`pcolor.shading`. Possible values: + shading : {'flat', 'nearest', 'auto'}, default: :rc:`pcolor.shading` + The fill style for the quadrilateral. Possible values: - 'flat': A solid color is used for each quad. The color of the quad (i, j), (i+1, j), (i, j+1), (i+1, j+1) is given by From 8653dbfee8f0af8e76536232564f5f6923acd0bc Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 12 Oct 2021 08:40:06 +0200 Subject: [PATCH 107/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- doc/users/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/users/index.rst b/doc/users/index.rst index 09ea86ef5eb8..ac5af88b418a 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -13,7 +13,6 @@ Usage guide getting_started.rst backends.rst performance.rst - ../gallery/usage.rst explain.rst ../faq/index.rst ../api/index.rst From 2d5d1d14fe0d7cf353f823340f12f1d9a27ad69f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 12 Oct 2021 20:49:51 +0200 Subject: [PATCH 108/130] Fix trivial docstring typo. --- lib/matplotlib/artist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 179badd3598e..eee8e63b91d5 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -1016,7 +1016,7 @@ def set_animated(self, b): If True, the artist is excluded from regular drawing of the figure. You have to call `.Figure.draw_artist` / `.Axes.draw_artist` - explicitly on the artist. This appoach is used to speed up animations + explicitly on the artist. This approach is used to speed up animations using blitting. See also `matplotlib.animation` and From c657a2add2a9a6c7e500232ece75948f69a94e18 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 13 Oct 2021 00:01:34 +0200 Subject: [PATCH 109/130] Fix type1font docstring markup/punctuation. --- lib/matplotlib/type1font.py | 56 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/type1font.py b/lib/matplotlib/type1font.py index 4c39ea8750b9..0413cb0016a0 100644 --- a/lib/matplotlib/type1font.py +++ b/lib/matplotlib/type1font.py @@ -7,10 +7,10 @@ Usage:: - >>> font = Type1Font(filename) - >>> clear_part, encrypted_part, finale = font.parts - >>> slanted_font = font.transform({'slant': 0.167}) - >>> extended_font = font.transform({'extend': 1.2}) + font = Type1Font(filename) + clear_part, encrypted_part, finale = font.parts + slanted_font = font.transform({'slant': 0.167}) + extended_font = font.transform({'extend': 1.2}) Sources: @@ -38,18 +38,16 @@ class _Token: """ - A token in a PostScript stream + A token in a PostScript stream. Attributes ---------- pos : int - position, i.e. offset from the beginning of the data - + Position, i.e. offset from the beginning of the data. raw : str - the raw text of the token - + Raw text of the token. kind : str - description of the token (for debugging or testing) + Description of the token (for debugging or testing). """ __slots__ = ('pos', 'raw') kind = '?' @@ -177,9 +175,8 @@ def _tokenize(data: bytes, skip_ws: bool): """ A generator that produces _Token instances from Type-1 font code. - The consumer of the generator may send an integer to the tokenizer - to indicate that the next token should be _BinaryToken of the given - length. + The consumer of the generator may send an integer to the tokenizer to + indicate that the next token should be _BinaryToken of the given length. Parameters ---------- @@ -279,18 +276,16 @@ class _BalancedExpression(_Token): def _expression(initial, tokens, data): """ - Consume some number of tokens and return a balanced PostScript expression + Consume some number of tokens and return a balanced PostScript expression. Parameters ---------- initial : _Token - the token that triggered parsing a balanced expression - + The token that triggered parsing a balanced expression. tokens : iterator of _Token - following tokens - + Following tokens. data : bytes - underlying data that the token positions point to + Underlying data that the token positions point to. Returns ------- @@ -332,17 +327,20 @@ class Type1Font: parts : tuple A 3-tuple of the cleartext part, the encrypted part, and the finale of zeros. + decrypted : bytes - The decrypted form of parts[1]. + The decrypted form of ``parts[1]``. + prop : dict[str, Any] A dictionary of font properties. Noteworthy keys include: - FontName - PostScript name of the font - Encoding - dict from numeric codes to glyph names - FontMatrix - bytes object encoding a matrix - UniqueID - optional font identifier, dropped when modifying the font - CharStrings - dict from glyph names to byte code - Subrs - array of byte code subroutines - OtherSubrs - bytes object encoding some PostScript code + + - FontName: PostScript name of the font + - Encoding: dict from numeric codes to glyph names + - FontMatrix: bytes object encoding a matrix + - UniqueID: optional font identifier, dropped when modifying the font + - CharStrings: dict from glyph names to byte code + - Subrs: array of byte code subroutines + - OtherSubrs: bytes object encoding some PostScript code """ __slots__ = ('parts', 'decrypted', 'prop', '_pos', '_abbr') # the _pos dict contains (begin, end) indices to parts[0] + decrypted @@ -445,7 +443,7 @@ def _split(self, data): @staticmethod def _decrypt(ciphertext, key, ndiscard=4): """ - Decrypt ciphertext using the Type-1 font algorithm + Decrypt ciphertext using the Type-1 font algorithm. The algorithm is described in Adobe's "Adobe Type 1 Font Format". The key argument can be an integer, or one of the strings @@ -467,7 +465,7 @@ def _decrypt(ciphertext, key, ndiscard=4): @staticmethod def _encrypt(plaintext, key, ndiscard=4): """ - Encrypt plaintext using the Type-1 font algorithm + Encrypt plaintext using the Type-1 font algorithm. The algorithm is described in Adobe's "Adobe Type 1 Font Format". The key argument can be an integer, or one of the strings From 92f01875371bc4cad000eefb255a9e7abb159303 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 13 Oct 2021 15:52:23 -0400 Subject: [PATCH 110/130] DOC/STY: remove white space and hedge language --- lib/matplotlib/backends/backend_pgf.py | 4 ++-- tutorials/text/pgf.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 7190aba86de2..ce9797e11b90 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -777,8 +777,8 @@ def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None): %% Make sure the required packages are loaded in your preamble %% \\usepackage{pgf} %% -%% Also ensure that all the required font packages are loaded; for instance, -%% the lmodern package is often necessary when using math font. +%% Also ensure that all the required font packages are loaded; for instance, +%% the lmodern package is sometimes necessary when using math font. %% \\usepackage{lmodern} %% %% Figures using additional raster images can only be included by \\input if diff --git a/tutorials/text/pgf.py b/tutorials/text/pgf.py index cc7584ffba61..929723e8571e 100644 --- a/tutorials/text/pgf.py +++ b/tutorials/text/pgf.py @@ -189,8 +189,9 @@ * Various math font are compiled and rendered only if corresponding font packages are loaded. Specifically, when using ``\mathbf{}`` on Greek letters, - the default computer modern font may not contain them, in which case such font - is not rendered. Is such scenarios, the ``lmodern`` package may be loaded. + the default computer modern font may not contain them, in which case such + font is not rendered. Is such scenarios, the ``lmodern`` package should be + loaded. * If you still need help, please see :ref:`reporting-problems` From 3712476fcd691af6fbadb3e33fe9dc222e712e38 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 13 Oct 2021 15:58:51 -0400 Subject: [PATCH 111/130] DOC: fix typo Co-authored-by: Elliott Sales de Andrade --- tutorials/text/pgf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/text/pgf.py b/tutorials/text/pgf.py index 929723e8571e..8641936a160b 100644 --- a/tutorials/text/pgf.py +++ b/tutorials/text/pgf.py @@ -190,7 +190,7 @@ * Various math font are compiled and rendered only if corresponding font packages are loaded. Specifically, when using ``\mathbf{}`` on Greek letters, the default computer modern font may not contain them, in which case such - font is not rendered. Is such scenarios, the ``lmodern`` package should be + font is not rendered. In such scenarios, the ``lmodern`` package should be loaded. * If you still need help, please see :ref:`reporting-problems` From 04a9cbcc4041e559871361dd1c6ab2770f1c9197 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 14 Oct 2021 00:50:14 +0200 Subject: [PATCH 112/130] Remove plot_gallery setting from conf.py The defined value is the default. If somebody wants to change that for their personal build, the recommended way is passing parameters to make: https://matplotlib.org/devdocs/devel/documenting_mpl.html#building-the-docs --- doc/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index c1b5cc164ca8..d8e554269e62 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -189,7 +189,6 @@ def _check_dependencies(): 'junit': '../test-results/sphinx-gallery/junit.xml' if CIRCLECI else '', } -plot_gallery = 'True' mathmpl_fontsize = 11.0 mathmpl_srcset = ['2x'] From fcf3f7235cbfcd7c31d8f76754fcf35854824d0a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 1 Oct 2021 20:07:37 +0200 Subject: [PATCH 113/130] Make image_comparison work even without the autoclose fixture. ... by capturing only the images created during the execution of the test, and closing them after the comparison. --- lib/matplotlib/testing/decorators.py | 59 +++++++++++++++++++--------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index a38d55fd1dcc..562cb4f43567 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -14,10 +14,7 @@ import matplotlib.style import matplotlib.units import matplotlib.testing -from matplotlib import cbook -from matplotlib import ft2font -from matplotlib import pyplot as plt -from matplotlib import ticker +from matplotlib import cbook, ft2font, pyplot as plt, ticker, _pylab_helpers from .compare import comparable_formats, compare_images, make_test_filename from .exceptions import ImageComparisonFailure @@ -129,6 +126,29 @@ def remove_ticks(ax): remove_ticks(ax) +@contextlib.contextmanager +def _collect_new_figures(): + """ + After:: + + with _collect_new_figures() as figs: + some_code() + + the list *figs* contains the figures that have been created during the + execution of ``some_code``, sorted by figure number. + """ + managers = _pylab_helpers.Gcf.figs + preexisting = [manager for manager in managers.values()] + new_figs = [] + try: + yield new_figs + finally: + new_managers = sorted([manager for manager in managers.values() + if manager not in preexisting], + key=lambda manager: manager.num) + new_figs[:] = [manager.canvas.figure for manager in new_managers] + + def _raise_on_image_difference(expected, actual, tol): __tracebackhide__ = True @@ -178,10 +198,8 @@ def copy_baseline(self, baseline, extension): f"{orig_expected_path}") from err return expected_fname - def compare(self, idx, baseline, extension, *, _lock=False): + def compare(self, fig, baseline, extension, *, _lock=False): __tracebackhide__ = True - fignum = plt.get_fignums()[idx] - fig = plt.figure(fignum) if self.remove_text: remove_ticks_and_titles(fig) @@ -196,7 +214,12 @@ def compare(self, idx, baseline, extension, *, _lock=False): lock = (cbook._lock_path(actual_path) if _lock else contextlib.nullcontext()) with lock: - fig.savefig(actual_path, **kwargs) + try: + fig.savefig(actual_path, **kwargs) + finally: + # Matplotlib has an autouse fixture to close figures, but this + # makes things more convenient for third-party users. + plt.close(fig) expected_path = self.copy_baseline(baseline, extension) _raise_on_image_difference(expected_path, actual_path, self.tol) @@ -235,7 +258,9 @@ def wrapper(*args, extension, request, **kwargs): img = _ImageComparisonBase(func, tol=tol, remove_text=remove_text, savefig_kwargs=savefig_kwargs) matplotlib.testing.set_font_settings_for_testing() - func(*args, **kwargs) + + with _collect_new_figures() as figs: + func(*args, **kwargs) # If the test is parametrized in any way other than applied via # this decorator, then we need to use a lock to prevent two @@ -252,11 +277,11 @@ def wrapper(*args, extension, request, **kwargs): our_baseline_images = request.getfixturevalue( 'baseline_images') - assert len(plt.get_fignums()) == len(our_baseline_images), ( + assert len(figs) == len(our_baseline_images), ( "Test generated {} images but there are {} baseline images" - .format(len(plt.get_fignums()), len(our_baseline_images))) - for idx, baseline in enumerate(our_baseline_images): - img.compare(idx, baseline, extension, _lock=needs_lock) + .format(len(figs), len(our_baseline_images))) + for fig, baseline in zip(figs, our_baseline_images): + img.compare(fig, baseline, extension, _lock=needs_lock) parameters = list(old_sig.parameters.values()) if 'extension' not in old_sig.parameters: @@ -427,11 +452,9 @@ def wrapper(*args, ext, request, **kwargs): try: fig_test = plt.figure("test") fig_ref = plt.figure("reference") - # Keep track of number of open figures, to make sure test - # doesn't create any new ones - n_figs = len(plt.get_fignums()) - func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs) - if len(plt.get_fignums()) > n_figs: + with _collect_new_figures() as figs: + func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs) + if figs: raise RuntimeError('Number of open figures changed during ' 'test. Make sure you are plotting to ' 'fig_test or fig_ref, or if this is ' From c758196c348db75eebe6100b9a2ce8463d33814d Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 09:45:31 +0200 Subject: [PATCH 114/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- doc/users/getting_started.rst | 11 +++-------- lib/matplotlib/artist.py | 2 +- lib/matplotlib/collections.py | 2 +- lib/matplotlib/image.py | 5 ++--- lib/matplotlib/lines.py | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst index d48b6b454ba7..61eccf2a5781 100644 --- a/doc/users/getting_started.rst +++ b/doc/users/getting_started.rst @@ -22,14 +22,9 @@ Installation conda install matplotlib -Further details are available in the :doc:`Installation Guide `. - -Choosing a backend ------------------- - -Matplotlib needs a "backend" to render your figure, either in its own GUI window, -as part of a notebook, or saved as an image to disk. If you need more information on -choosing a backend, see :ref:`backends`. +Further details are available in the :doc:`Installation Guide `. If a +plot does not show up, we probably could not automatically find an appropriate backend; please +check :ref:`troubleshooting-faq`. Draw a first plot ----------------- diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 179badd3598e..cc8f6f61c0ac 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -300,7 +300,7 @@ def stale(self, val): if val and self.stale_callback is not None: self.stale_callback(self, val) - def get_window_extent(self, renderer): + def get_window_extent(self, renderer=None): """ Get the axes bounding box in display space. diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 619c62b5ca14..cf765a267c11 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -303,7 +303,7 @@ def get_datalim(self, transData): return bbox return transforms.Bbox.null() - def get_window_extent(self, renderer): + def get_window_extent(self, renderer=None): # TODO: check to ensure that this does not fail for # cases other than scatter plot legend return self.get_datalim(transforms.IdentityTransform()) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 2d8cdf1691e4..e71659e80303 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1409,12 +1409,11 @@ def __init__(self, bbox, self.bbox = bbox def get_window_extent(self, renderer=None): - if renderer is None: - renderer = self.get_figure()._cachedRenderer - if isinstance(self.bbox, BboxBase): return self.bbox elif callable(self.bbox): + if renderer is None: + renderer = self.get_figure()._cachedRenderer return self.bbox(renderer) else: raise ValueError("Unknown type of bbox") diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index f1efd125a4ed..38284016f051 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -619,7 +619,7 @@ def set_picker(self, p): self.pickradius = p self._picker = p - def get_window_extent(self, renderer): + def get_window_extent(self, renderer=None): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()), From a8d36204a1bb71a9f50ae7c52c3aad30b72409f8 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 13:13:59 +0200 Subject: [PATCH 115/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- doc/users/getting_started.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst index 61eccf2a5781..3197a8a0f3a8 100644 --- a/doc/users/getting_started.rst +++ b/doc/users/getting_started.rst @@ -22,9 +22,7 @@ Installation conda install matplotlib -Further details are available in the :doc:`Installation Guide `. If a -plot does not show up, we probably could not automatically find an appropriate backend; please -check :ref:`troubleshooting-faq`. +Further details are available in the :doc:`Installation Guide `. Draw a first plot ----------------- @@ -45,6 +43,8 @@ Here is a minimal example plot you can try out: ax.plot(x, y) plt.show() +If a plot does not show up, we probably could not automatically find an appropriate backend; +please check :ref:`troubleshooting-faq`. Where to go next ---------------- From edb0ac6a096a26ddb48ab192f38531506e65c641 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 13:32:26 +0200 Subject: [PATCH 116/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- doc/users/getting_started.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/users/getting_started.rst b/doc/users/getting_started.rst index 3197a8a0f3a8..1cb7c97952da 100644 --- a/doc/users/getting_started.rst +++ b/doc/users/getting_started.rst @@ -43,8 +43,7 @@ Here is a minimal example plot you can try out: ax.plot(x, y) plt.show() -If a plot does not show up, we probably could not automatically find an appropriate backend; -please check :ref:`troubleshooting-faq`. +If a plot does not show up please check :ref:`troubleshooting-faq`. Where to go next ---------------- From 3f675401f719b66deec975110cabe5dd89b4fc43 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 15:07:17 +0200 Subject: [PATCH 117/130] DOC: remove test from README.rst --- README.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.rst b/README.rst index 078898049a55..a705ba3ab477 100644 --- a/README.rst +++ b/README.rst @@ -61,16 +61,6 @@ Install For installation instructions and requirements, see `INSTALL.rst `_ or the `install `_ documentation. -Test -==== - -After installation, launch the test suite:: - - python -m pytest - -Read the `testing guide `_ -for more information and alternatives. - Contribute ========== From bb7e24095ef25af3f6d4966df8a0de1d5ca9cf65 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 22:17:10 +0200 Subject: [PATCH 118/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- lib/matplotlib/artist.py | 2 +- lib/matplotlib/collections.py | 2 +- lib/matplotlib/image.py | 5 +++-- lib/matplotlib/lines.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index cc8f6f61c0ac..5f0234c5851c 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -300,7 +300,7 @@ def stale(self, val): if val and self.stale_callback is not None: self.stale_callback(self, val) - def get_window_extent(self, renderer=None): + def get_window_extent(self): """ Get the axes bounding box in display space. diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index cf765a267c11..0c2c2298df7c 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -303,7 +303,7 @@ def get_datalim(self, transData): return bbox return transforms.Bbox.null() - def get_window_extent(self, renderer=None): + def get_window_extent(self): # TODO: check to ensure that this does not fail for # cases other than scatter plot legend return self.get_datalim(transforms.IdentityTransform()) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index e71659e80303..2d8cdf1691e4 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1409,11 +1409,12 @@ def __init__(self, bbox, self.bbox = bbox def get_window_extent(self, renderer=None): + if renderer is None: + renderer = self.get_figure()._cachedRenderer + if isinstance(self.bbox, BboxBase): return self.bbox elif callable(self.bbox): - if renderer is None: - renderer = self.get_figure()._cachedRenderer return self.bbox(renderer) else: raise ValueError("Unknown type of bbox") diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 38284016f051..f1efd125a4ed 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -619,7 +619,7 @@ def set_picker(self, p): self.pickradius = p self._picker = p - def get_window_extent(self, renderer=None): + def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()), From 1c346b8885346e3d7ce00b4b6b955a6a7c2635c1 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 14 Oct 2021 22:18:27 +0200 Subject: [PATCH 119/130] DOC: move usage tutorial info to Users guide rst [skip actions] [skip azp] [skip appveyor] --- lib/matplotlib/artist.py | 2 +- lib/matplotlib/collections.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 5f0234c5851c..179badd3598e 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -300,7 +300,7 @@ def stale(self, val): if val and self.stale_callback is not None: self.stale_callback(self, val) - def get_window_extent(self): + def get_window_extent(self, renderer): """ Get the axes bounding box in display space. diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 0c2c2298df7c..619c62b5ca14 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -303,7 +303,7 @@ def get_datalim(self, transData): return bbox return transforms.Bbox.null() - def get_window_extent(self): + def get_window_extent(self, renderer): # TODO: check to ensure that this does not fail for # cases other than scatter plot legend return self.get_datalim(transforms.IdentityTransform()) From c8a902d3f29a0aa70ab1cf5a4303c5e96b4cf538 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 14 Oct 2021 22:32:47 +0200 Subject: [PATCH 120/130] Add GHA testing whether files were added and deleted in the same PR. --- .github/workflows/clean_pr.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/clean_pr.yml diff --git a/.github/workflows/clean_pr.yml b/.github/workflows/clean_pr.yml new file mode 100644 index 000000000000..fc2acc0b2e4c --- /dev/null +++ b/.github/workflows/clean_pr.yml @@ -0,0 +1,22 @@ +name: PR cleanliness +on: [pull_request] + +jobs: + pr_clean: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '0' + - name: Check for added-and-deleted files + run: | + git fetch --quiet origin "$GITHUB_BASE_REF" + base="$(git merge-base "origin/$GITHUB_BASE_REF" 'HEAD^2')" + ad="$(git log "$base..HEAD^2" --pretty=tformat: --name-status --diff-filter=AD | + cut --fields 2 | sort | uniq --repeated)" + if [[ -n "$ad" ]]; then + printf 'The following files were both added and deleted in this PR:\n%s\n' "$ad" + exit 1 + fi + From 44fa101ff35bac287048fa8f197ee00ee2653879 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 15 Oct 2021 02:13:38 +0200 Subject: [PATCH 121/130] Fix flake8 from #21335 --- tutorials/introductory/usage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tutorials/introductory/usage.py b/tutorials/introductory/usage.py index d40dcb3fd325..45253117a892 100644 --- a/tutorials/introductory/usage.py +++ b/tutorials/introductory/usage.py @@ -249,4 +249,3 @@ def my_plotter(ax, data1, data2, param_dict): ############################################################################### # These examples provide convenience for more complex graphs. - From 394632c79dfd35312529d7bce65332206ab63a82 Mon Sep 17 00:00:00 2001 From: Kinshuk Dua Date: Fri, 15 Oct 2021 10:10:43 +0530 Subject: [PATCH 122/130] remove dpi pil_kwarg from print_webp --- lib/matplotlib/backends/backend_agg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 35cc25579c67..7dacc6356d12 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -613,7 +613,6 @@ def print_webp(self, filename_or_obj, *, pil_kwargs=None): FigureCanvasAgg.draw(self) if pil_kwargs is None: pil_kwargs = {} - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) return (Image.fromarray(np.asarray(self.buffer_rgba())) .save(filename_or_obj, format='webp', **pil_kwargs)) From 977bff2221278df95979edcea1651e819b63b998 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 15 Oct 2021 02:38:46 +0200 Subject: [PATCH 123/130] Simplify wording of allowed errorbar() error values --- lib/matplotlib/axes/_axes.py | 9 +++++---- lib/matplotlib/tests/test_axes.py | 16 +++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 719866bbf935..91badadf088e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3181,7 +3181,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, errors. - *None*: No errorbar. - Note that all error arrays should have *non-negative* values. + All values must be >= 0. See :doc:`/gallery/statistics/errorbar_features` for an example on the usage of ``xerr`` and ``yerr``. @@ -3293,9 +3293,10 @@ def has_negative_values(array): except TypeError: # if array contains 'datetime.timedelta' types return np.any(array < timedelta(0)) - if has_negative_values(xerr) or has_negative_values(yerr): - raise ValueError( - "'xerr' and 'yerr' must have non-negative values") + if has_negative_values(xerr): + raise ValueError("'xerr' must not contain negative values") + if has_negative_values(yerr): + raise ValueError("'yerr' must not contain negative values") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index cb3d79358695..1b349b2a9c31 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3514,18 +3514,20 @@ def test_errorbar_every_invalid(): ax.errorbar(x, y, yerr, errorevery='foobar') -def test_xerr_yerr_positive(): +def test_xerr_yerr_not_negative(): ax = plt.figure().subplots() - error_message = "'xerr' and 'yerr' must have non-negative values" - - with pytest.raises(ValueError, match=error_message): + with pytest.raises(ValueError, + match="'xerr' must not contain negative values"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match=error_message): + with pytest.raises(ValueError, + match="'xerr' must not contain negative values"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match=error_message): + with pytest.raises(ValueError, + match="'yerr' must not contain negative values"): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match=error_message): + with pytest.raises(ValueError, + match="'yerr' must not contain negative values"): x = np.arange(5) y = [datetime.datetime(2021, 9, i * 2 + 1) for i in x] ax.errorbar(x=x, From 59522d0c5eb5a0edbd8f108210b8c1d624eb8002 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 14 Oct 2021 14:14:07 +0200 Subject: [PATCH 124/130] Raise an exception when find_tex_file fails to find a file. The exception message is clearer for end users than downstream callers failing to `open()` a file named `""`. Also update the function's docstring. _tfmfile now never returns None (an exception would have been raised earlier by find_tex_file), so remove the corresponding branch. --- .../deprecations/21356-AL.rst | 5 + lib/matplotlib/backends/backend_pdf.py | 2 +- lib/matplotlib/dviread.py | 116 +++++++++++------- lib/matplotlib/testing/__init__.py | 6 +- lib/matplotlib/tests/test_dviread.py | 2 +- lib/matplotlib/textpath.py | 2 +- 6 files changed, 82 insertions(+), 51 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/21356-AL.rst diff --git a/doc/api/next_api_changes/deprecations/21356-AL.rst b/doc/api/next_api_changes/deprecations/21356-AL.rst new file mode 100644 index 000000000000..de6cb79dd908 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/21356-AL.rst @@ -0,0 +1,5 @@ +In the future, ``dviread.find_tex_file`` will raise a ``FileNotFoundError`` for missing files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Previously, it would return an empty string in such cases. Raising an +exception allows attaching a user-friendly message instead. During the +transition period, a warning is raised. diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index bd0c370f1cba..29838c35316c 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -887,7 +887,7 @@ def dviFontName(self, dvifont): if dvi_info is not None: return dvi_info.pdfname - tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map')) + tex_font_map = dviread.PsfontsMap(dviread._find_tex_file('pdftex.map')) psfont = tex_font_map[dvifont.texname] if psfont.filename is None: raise ValueError( diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 3207a01de8be..7f90a13f1086 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -470,13 +470,12 @@ def _fnt_def_real(self, k, c, s, d, a, l): n = self.file.read(a + l) fontname = n[-l:].decode('ascii') tfm = _tfmfile(fontname) - if tfm is None: - raise FileNotFoundError("missing font metrics file: %s" % fontname) if c != 0 and tfm.checksum != 0 and c != tfm.checksum: raise ValueError('tfm checksum mismatch: %s' % n) - - vf = _vffile(fontname) - + try: + vf = _vffile(fontname) + except FileNotFoundError: + vf = None self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf) @_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1')) @@ -938,9 +937,9 @@ def _parse_and_cache_line(self, line): if basename is None: basename = tfmname if encodingfile is not None: - encodingfile = find_tex_file(encodingfile) + encodingfile = _find_tex_file(encodingfile) if fontfile is not None: - fontfile = find_tex_file(fontfile) + fontfile = _find_tex_file(fontfile) self._parsed[tfmname] = PsFont( texname=tfmname, psname=basename, effects=effects, encoding=encodingfile, filename=fontfile) @@ -992,21 +991,20 @@ def search(self, filename): self._proc.stdin.write(os.fsencode(filename) + b"\n") self._proc.stdin.flush() out = self._proc.stdout.readline().rstrip() - return "" if out == b"nil" else os.fsdecode(out) + return None if out == b"nil" else os.fsdecode(out) @lru_cache() @_api.delete_parameter("3.5", "format") -def find_tex_file(filename, format=None): +def _find_tex_file(filename, format=None): """ - Find a file in the texmf tree. + Find a file in the texmf tree using kpathsea_. - Calls :program:`kpsewhich` which is an interface to the kpathsea - library [1]_. Most existing TeX distributions on Unix-like systems use - kpathsea. It is also available as part of MikTeX, a popular - distribution on Windows. + The kpathsea library, provided by most existing TeX distributions, both + on Unix-like systems and on Windows (MikTeX), is invoked via a long-lived + luatex process if luatex is installed, or via kpsewhich otherwise. - *If the file is not found, an empty string is returned*. + .. _kpathsea: https://www.tug.org/kpathsea/ Parameters ---------- @@ -1016,10 +1014,10 @@ def find_tex_file(filename, format=None): Could be e.g. 'tfm' or 'vf' to limit the search to that type of files. Deprecated. - References - ---------- - .. [1] `Kpathsea documentation `_ - The library that :program:`kpsewhich` is part of. + Raises + ------ + FileNotFoundError + If the file is not found. """ # we expect these to always be ascii encoded, but use utf-8 @@ -1029,39 +1027,63 @@ def find_tex_file(filename, format=None): if isinstance(format, bytes): format = format.decode('utf-8', errors='replace') - if format is None: + try: + lk = _LuatexKpsewhich() + except FileNotFoundError: + lk = None # Fallback to directly calling kpsewhich, as below. + + if lk and format is None: + path = lk.search(filename) + + else: + if os.name == 'nt': + # 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. + kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'}, + 'encoding': 'utf-8'} + else: # On POSIX, run through the equivalent of os.fsdecode(). + kwargs = {'encoding': sys.getfilesystemencoding(), + 'errors': 'surrogateescape'} + + cmd = ['kpsewhich'] + if format is not None: + cmd += ['--format=' + format] + cmd += [filename] try: - lk = _LuatexKpsewhich() - except FileNotFoundError: - pass # Fallback to directly calling kpsewhich, as below. - else: - return lk.search(filename) - - if os.name == 'nt': - # 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. - kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'}, - 'encoding': 'utf-8'} - else: # On POSIX, run through the equivalent of os.fsdecode(). - kwargs = {'encoding': sys.getfilesystemencoding(), - 'errors': 'surrogatescape'} - - cmd = ['kpsewhich'] - if format is not None: - cmd += ['--format=' + format] - cmd += [filename] + path = (cbook._check_and_log_subprocess(cmd, _log, **kwargs) + .rstrip('\n')) + except (FileNotFoundError, RuntimeError): + path = None + + if path: + return path + else: + raise FileNotFoundError( + f"Matplotlib's TeX implementation searched for a file named " + f"{filename!r} in your texmf tree, but could not find it") + + +# After the deprecation period elapses, delete this shim and rename +# _find_tex_file to find_tex_file everywhere. +@_api.delete_parameter("3.5", "format") +def find_tex_file(filename, format=None): try: - result = cbook._check_and_log_subprocess(cmd, _log, **kwargs) - except (FileNotFoundError, RuntimeError): - return '' - return result.rstrip('\n') + return (_find_tex_file(filename, format) if format is not None else + _find_tex_file(filename)) + except FileNotFoundError as exc: + _api.warn_deprecated( + "3.6", message=f"{exc.args[0]}; in the future, this will raise a " + f"FileNotFoundError.") + return "" + + +find_tex_file.__doc__ = _find_tex_file.__doc__ @lru_cache() def _fontfile(cls, suffix, texname): - filename = find_tex_file(texname + suffix) - return cls(filename) if filename else None + return cls(_find_tex_file(texname + suffix)) _tfmfile = partial(_fontfile, Tfm, ".tfm") @@ -1077,7 +1099,7 @@ def _fontfile(cls, suffix, texname): parser.add_argument("dpi", nargs="?", type=float, default=None) args = parser.parse_args() with Dvi(args.filename, args.dpi) as dvi: - fontmap = PsfontsMap(find_tex_file('pdftex.map')) + fontmap = PsfontsMap(_find_tex_file('pdftex.map')) for page in dvi: print(f"=== new page === " f"(w: {page.width}, h: {page.height}, d: {page.descent})") diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index f9c547ce00aa..754277c41f43 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -78,4 +78,8 @@ def _check_for_pgf(texsystem): def _has_tex_package(package): - return bool(mpl.dviread.find_tex_file(f"{package}.sty")) + try: + mpl.dviread._find_tex_file(f"{package}.sty") + return True + except FileNotFoundError: + return False diff --git a/lib/matplotlib/tests/test_dviread.py b/lib/matplotlib/tests/test_dviread.py index a40151fd555f..7e10975f44d5 100644 --- a/lib/matplotlib/tests/test_dviread.py +++ b/lib/matplotlib/tests/test_dviread.py @@ -7,7 +7,7 @@ def test_PsfontsMap(monkeypatch): - monkeypatch.setattr(dr, 'find_tex_file', lambda x: x) + monkeypatch.setattr(dr, '_find_tex_file', lambda x: x) filename = str(Path(__file__).parent / 'baseline_images/dviread/test.map') fontmap = dr.PsfontsMap(filename) diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 9b14e79ec2d2..5ef56e4be885 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -279,7 +279,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, @staticmethod @functools.lru_cache(50) def _get_ps_font_and_encoding(texname): - tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map')) + tex_font_map = dviread.PsfontsMap(dviread._find_tex_file('pdftex.map')) psfont = tex_font_map[texname] if psfont.filename is None: raise ValueError( From eccdda19939d9058318cd4adeaeaba3aa8b46c32 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 15 Oct 2021 19:36:05 -0400 Subject: [PATCH 125/130] DOC: edits from review Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- tutorials/text/pgf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorials/text/pgf.py b/tutorials/text/pgf.py index 8641936a160b..caedf394f359 100644 --- a/tutorials/text/pgf.py +++ b/tutorials/text/pgf.py @@ -187,10 +187,10 @@ using either the ``rasterized=True`` keyword, or ``.set_rasterized(True)`` as per :doc:`this example `. -* Various math font are compiled and rendered only if corresponding font +* Various math fonts are compiled and rendered only if corresponding font packages are loaded. Specifically, when using ``\mathbf{}`` on Greek letters, - the default computer modern font may not contain them, in which case such - font is not rendered. In such scenarios, the ``lmodern`` package should be + the default computer modern font may not contain them, in which case the + letter is not rendered. In such scenarios, the ``lmodern`` package should be loaded. * If you still need help, please see :ref:`reporting-problems` From 14bad633f08215156506a57fb5adbad45e8f0d67 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Oct 2021 17:03:23 +0200 Subject: [PATCH 126/130] Demonstrate inset_axes in scatter_hist example. Currently, the scatter_hist example needs to force the main axes to be square by carefully adjusting the figure size -- shared axes and fixed aspects don't work well together (and manually resizing the figure shows that the aspect is indeed not fixed). In fact, the scatter_hist_locatable_axes example explicitly states that the advantage of using axes_grid1 is to allow the aspect to be fixed. I realized that one can also use inset_axes to position the marginals axes relative to the main axes *and* support a fixed aspect ratio for the main axes. Perhaps this can be considered as a slight API abuse, but I think this is a solution for a real limitation; the question is whether we want to promote this use? --- .../axes_grid1/scatter_hist_locatable_axes.py | 12 +-- .../lines_bars_and_markers/scatter_hist.py | 99 +++++++++---------- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/examples/axes_grid1/scatter_hist_locatable_axes.py b/examples/axes_grid1/scatter_hist_locatable_axes.py index 8d2ee72b9c6b..c605e9e81258 100644 --- a/examples/axes_grid1/scatter_hist_locatable_axes.py +++ b/examples/axes_grid1/scatter_hist_locatable_axes.py @@ -3,16 +3,16 @@ Scatter Histogram (Locatable Axes) ================================== -Show the marginal distributions of a scatter as histograms at the sides of +Show the marginal distributions of a scatter plot as histograms at the sides of the plot. For a nice alignment of the main axes with the marginals, the axes positions -are defined by a ``Divider``, produced via `.make_axes_locatable`. +are defined by a ``Divider``, produced via `.make_axes_locatable`. Note that +the ``Divider`` API allows setting axes sizes and pads in inches, which is its +main feature. -An alternative method to produce a similar figure is shown in the -:doc:`/gallery/lines_bars_and_markers/scatter_hist` example. The advantage of -the locatable axes method shown below is that the marginal axes follow the -fixed aspect ratio of the main axes. +If one wants to set axes sizes and pads relative to the main Figure, see the +:doc:`/gallery/lines_bars_and_markers/scatter_hist` example. """ import numpy as np diff --git a/examples/lines_bars_and_markers/scatter_hist.py b/examples/lines_bars_and_markers/scatter_hist.py index 1ad5d8ac1c8b..1e18932a76db 100644 --- a/examples/lines_bars_and_markers/scatter_hist.py +++ b/examples/lines_bars_and_markers/scatter_hist.py @@ -3,18 +3,22 @@ Scatter plot with histograms ============================ -Show the marginal distributions of a scatter as histograms at the sides of +Show the marginal distributions of a scatter plot as histograms at the sides of the plot. For a nice alignment of the main axes with the marginals, two options are shown -below. +below: -* the axes positions are defined in terms of rectangles in figure coordinates -* the axes positions are defined via a gridspec +.. contents:: + :local: + +While `.Axes.inset_axes` may be a bit more complex, it allows correct handling +of main axes with a fixed aspect ratio. An alternative method to produce a similar figure using the ``axes_grid1`` -toolkit is shown in the -:doc:`/gallery/axes_grid1/scatter_hist_locatable_axes` example. +toolkit is shown in the :doc:`/gallery/axes_grid1/scatter_hist_locatable_axes` +example. Finally, it is also possible to position all axes in absolute +coordinates using `.Figure.add_axes` (not shown here). Let us first define a function that takes x and y data as input, as well as three axes, the main axes for the scatter, and two marginal axes. It will @@ -52,60 +56,53 @@ def scatter_hist(x, y, ax, ax_histx, ax_histy): ############################################################################# # -# Axes in figure coordinates -# -------------------------- -# -# To define the axes positions, `.Figure.add_axes` is provided with a rectangle -# ``[left, bottom, width, height]`` in figure coordinates. The marginal axes -# share one dimension with the main axes. - -# definitions for the axes -left, width = 0.1, 0.65 -bottom, height = 0.1, 0.65 -spacing = 0.005 - - -rect_scatter = [left, bottom, width, height] -rect_histx = [left, bottom + height + spacing, width, 0.2] -rect_histy = [left + width + spacing, bottom, 0.2, height] - -# start with a square Figure -fig = plt.figure(figsize=(8, 8)) - -ax = fig.add_axes(rect_scatter) -ax_histx = fig.add_axes(rect_histx, sharex=ax) -ax_histy = fig.add_axes(rect_histy, sharey=ax) - -# use the previously defined function -scatter_hist(x, y, ax, ax_histx, ax_histy) - -plt.show() - - -############################################################################# -# -# Using a gridspec -# ---------------- +# Defining the axes positions using a gridspec +# -------------------------------------------- # -# We may equally define a gridspec with unequal width- and height-ratios to -# achieve desired layout. Also see the :doc:`/tutorials/intermediate/gridspec` -# tutorial. - -# start with a square Figure -fig = plt.figure(figsize=(8, 8)) +# We define a gridspec with unequal width- and height-ratios to achieve desired +# layout. Also see the :doc:`/tutorials/intermediate/gridspec` tutorial. -# Add a gridspec with two rows and two columns and a ratio of 2 to 7 between +# Start with a square Figure. +fig = plt.figure(figsize=(6, 6)) +# Add a gridspec with two rows and two columns and a ratio of 1 to 4 between # the size of the marginal axes and the main axes in both directions. # Also adjust the subplot parameters for a square plot. -gs = fig.add_gridspec(2, 2, width_ratios=(7, 2), height_ratios=(2, 7), +gs = fig.add_gridspec(2, 2, width_ratios=(4, 1), height_ratios=(1, 4), left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.05, hspace=0.05) - +# Create the Axes. ax = fig.add_subplot(gs[1, 0]) ax_histx = fig.add_subplot(gs[0, 0], sharex=ax) ax_histy = fig.add_subplot(gs[1, 1], sharey=ax) +# Draw the scatter plot and marginals. +scatter_hist(x, y, ax, ax_histx, ax_histy) + -# use the previously defined function +############################################################################# +# +# Defining the axes positions using inset_axes +# -------------------------------------------- +# +# `~.Axes.inset_axes` can be used to position marginals *outside* the main +# axes. The advantage of doing so is that the aspect ratio of the main axes +# can be fixed, and the marginals will always be drawn relative to the position +# of the axes. + +# Create a Figure, which doesn't have to be square. +fig = plt.figure(constrained_layout=True) +# Create the main axes, leaving 25% of the figure space at the top and on the +# right to position marginals. +ax = fig.add_gridspec(top=0.75, right=0.75).subplots() +# The main axes' aspect can be fixed. +ax.set(aspect=1) +# Create marginal axes, which have 25% of the size of the main axes. Note that +# the inset axes are positioned *outside* (on the right and the top) of the +# main axes, by specifying axes coordinates greater than 1. Axes coordinates +# less than 0 would likewise specify positions on the left and the bottom of +# the main axes. +ax_histx = ax.inset_axes([0, 1.05, 1, 0.25], sharex=ax) +ax_histy = ax.inset_axes([1.05, 0, 0.25, 1], sharey=ax) +# Draw the scatter plot and marginals. scatter_hist(x, y, ax, ax_histx, ax_histy) plt.show() @@ -118,8 +115,8 @@ def scatter_hist(x, y, ax, ax_histx, ax_histy): # The use of the following functions, methods, classes and modules is shown # in this example: # -# - `matplotlib.figure.Figure.add_axes` # - `matplotlib.figure.Figure.add_subplot` # - `matplotlib.figure.Figure.add_gridspec` +# - `matplotlib.axes.Axes.inset_axes` # - `matplotlib.axes.Axes.scatter` # - `matplotlib.axes.Axes.hist` From aeba9ac3d4d9b3e03d3a5034d8c602e5309c08e5 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 18 Oct 2021 12:20:12 +0200 Subject: [PATCH 127/130] Factor common parts of saving to different formats using pillow. We don't need to drop the alpha channel for jpeg anymore as imsave handles that. In imsave, also clarify that `sm` is not going to be used in the already-usable-rgba case. Also, spliting out pil_kwargs under "other parameters" seems overkill for an effectively internal method (`savefig` is the user-facing API). --- lib/matplotlib/backends/backend_agg.py | 59 ++++++++------------------ lib/matplotlib/image.py | 8 ++-- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index d19833f7723f..fc495333ddee 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -487,6 +487,16 @@ def print_raw(self, filename_or_obj, *args): print_rgba = print_raw + def _print_pil(self, filename_or_obj, fmt, pil_kwargs, metadata=None): + """ + Draw the canvas, then save it using `.image.imsave` (to which + *pil_kwargs* and *metadata* are forwarded). + """ + FigureCanvasAgg.draw(self) + mpl.image.imsave( + filename_or_obj, self.buffer_rgba(), format=fmt, origin="upper", + dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs) + @_check_savefig_extra_args @_api.delete_parameter("3.5", "args") def print_png(self, filename_or_obj, *args, @@ -537,10 +547,7 @@ def print_png(self, filename_or_obj, *args, If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ - FigureCanvasAgg.draw(self) - mpl.image.imsave( - filename_or_obj, self.buffer_rgba(), format="png", origin="upper", - dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs) + self._print_pil(filename_or_obj, "png", pil_kwargs, metadata) def print_to_buffer(self): FigureCanvasAgg.draw(self) @@ -555,68 +562,38 @@ def print_to_buffer(self): @_check_savefig_extra_args() @_api.delete_parameter("3.5", "args") def print_jpg(self, filename_or_obj, *args, pil_kwargs=None, **kwargs): - """ - Write the figure to a JPEG file. - - Parameters - ---------- - filename_or_obj : str or path-like or file-like - The file to write to. - - Other Parameters - ---------------- - pil_kwargs : dict, optional - Additional keyword arguments that are passed to - `PIL.Image.Image.save` when saving the figure. - """ # Remove transparency by alpha-blending on an assumed white background. r, g, b, a = mcolors.to_rgba(self.figure.get_facecolor()) try: self.figure.set_facecolor(a * np.array([r, g, b]) + 1 - a) - FigureCanvasAgg.draw(self) + self._print_pil(filename_or_obj, "jpeg", pil_kwargs) finally: self.figure.set_facecolor((r, g, b, a)) - if pil_kwargs is None: - pil_kwargs = {} - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - # Drop alpha channel now. - return (Image.fromarray(np.asarray(self.buffer_rgba())[..., :3]) - .save(filename_or_obj, format='jpeg', **pil_kwargs)) print_jpeg = print_jpg @_check_savefig_extra_args def print_tif(self, filename_or_obj, *, pil_kwargs=None): - FigureCanvasAgg.draw(self) - if pil_kwargs is None: - pil_kwargs = {} - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - return (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format='tiff', **pil_kwargs)) + self._print_pil(filename_or_obj, "tiff", pil_kwargs) print_tiff = print_tif @_check_savefig_extra_args def print_webp(self, filename_or_obj, *, pil_kwargs=None): + self._print_pil(filename_or_obj, "webp", pil_kwargs) + + print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map( """ - Write the figure to a WebP file. + Write the figure to a {} file. Parameters ---------- filename_or_obj : str or path-like or file-like The file to write to. - - Other Parameters - ---------------- pil_kwargs : dict, optional Additional keyword arguments that are passed to `PIL.Image.Image.save` when saving the figure. - """ - FigureCanvasAgg.draw(self) - if pil_kwargs is None: - pil_kwargs = {} - return (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format='webp', **pil_kwargs)) + """.format, ["JPEG", "TIFF", "WebP"]) @_Backend.export diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 2d8cdf1691e4..04b95d3d0a37 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1601,20 +1601,20 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, else: # Don't bother creating an image; this avoids rounding errors on the # size when dividing and then multiplying by dpi. - sm = cm.ScalarMappable(cmap=cmap) - sm.set_clim(vmin, vmax) if origin is None: origin = mpl.rcParams["image.origin"] if origin == "lower": arr = arr[::-1] if (isinstance(arr, memoryview) and arr.format == "B" and arr.ndim == 3 and arr.shape[-1] == 4): - # Such an ``arr`` would also be handled fine by sm.to_rgba (after - # casting with asarray), but it is useful to special-case it + # Such an ``arr`` would also be handled fine by sm.to_rgba below + # (after casting with asarray), but it is useful to special-case it # because that's what backend_agg passes, and can be in fact used # as is, saving a few operations. rgba = arr else: + sm = cm.ScalarMappable(cmap=cmap) + sm.set_clim(vmin, vmax) rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: pil_kwargs = {} From 3c6d8a8bdc1355e452c42be40cc32549b7ee144c Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Mon, 18 Oct 2021 14:34:40 +0200 Subject: [PATCH 128/130] Enable tests for text path based markers --- lib/matplotlib/tests/test_marker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 894dead24e84..f50f45bbd4ee 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -236,8 +236,8 @@ def test_marker_init_captyle(): Affine2D().translate(1, 1)), (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), Affine2D().translate(1, 1), Affine2D().translate(2, 2)), - # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), - # Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), (markers.MarkerStyle( markers.TICKLEFT, transform=Affine2D().translate(1, 1)), Affine2D().translate(1, 1), Affine2D().translate(2, 2)), @@ -264,8 +264,8 @@ def test_marker_rotated_invalid(): 10, None, Affine2D().translate(1, 1).rotate_deg(10)), (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), None, 0.01, Affine2D().translate(1, 1).rotate(0.01)), - # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), - # 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), (markers.MarkerStyle( markers.TICKLEFT, transform=Affine2D().translate(1, 1)), 10, None, Affine2D().translate(1, 1).rotate_deg(10)), From 6d155352ce3f612bf1f3abc95aec6dcca0cd50e4 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 19 Oct 2021 18:14:58 +0100 Subject: [PATCH 129/130] Document what indexing a GridSpec returns --- lib/matplotlib/gridspec.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 74b35d41797d..5ab3888000ec 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -330,6 +330,8 @@ class GridSpec(GridSpecBase): The location of the grid cells is determined in a similar way to `~.figure.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace* and *hspace*. + + Indexing a GridSpec instance returns a `.SubplotSpec`. """ def __init__(self, nrows, ncols, figure=None, left=None, bottom=None, right=None, top=None, From f96c73c1ab0173d293da6836a170aa03408ceb9d Mon Sep 17 00:00:00 2001 From: dj4t9n Date: Wed, 20 Oct 2021 13:12:50 +0530 Subject: [PATCH 130/130] Log pixel coordinates in event_handling coords_demo example --- examples/event_handling/coords_demo.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/event_handling/coords_demo.py b/examples/event_handling/coords_demo.py index 4bfb49518ef5..171ec6317d2c 100644 --- a/examples/event_handling/coords_demo.py +++ b/examples/event_handling/coords_demo.py @@ -18,11 +18,12 @@ def on_move(event): - # get the x and y pixel coords - x, y = event.x, event.y if event.inaxes: + # get the x and y pixel coords + x, y = event.x, event.y ax = event.inaxes # the axes instance - print('data coords %f %f' % (event.xdata, event.ydata)) + print('data coords %f %f, pixel coords %f %f' + % (event.xdata, event.ydata, x, y)) def on_click(event):