From c1653c500ec55949fdb4b31894371e3a3392ee21 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 17 Dec 2019 22:08:46 -0500 Subject: [PATCH 001/602] DOC: GSOC and JHEP banners --- doc/_static/mpl.css | 24 ++++++++++++++++++++++++ doc/_templates/layout.html | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index d037550666c4..6063c9a16224 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -926,6 +926,30 @@ figcaption { #unreleased-message a { color: #fff; text-decoration:underline; + +} + +/* top-banner style message. */ +#annc-banner { + box-sizing: border-box; + left: 0; + min-height: 3em; + padding: 0.7em; + top: 0; + width: 100%; + z-index: 10000; + background-image: linear-gradient(90deg, #440154, #482475, #414487, #355f8d, #2a788e, #21908d, #22a884, #42be71, #7ad151, #bddf26, #bddf26); + padding: 5px +} + +#annc-banner a { + font-weight: bold; +} + +#annc-banner p{ + background-color: rgba(255, 255, 255, .8); + padding: 13px; + margin:0; } /* Fork me on GitHub "button" */ diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index 75984fed978a..d81e8c23fb2f 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -5,6 +5,7 @@
  • contents »
  • {%- endblock %} + {%- block relbar1 %}{{ relbar() }}{% endblock %} {%- block relbar2 %}{% endblock %} @@ -27,7 +28,25 @@ Try searching for the released version of this page instead? + {%- endif %} + + +
    +

    Matplotlib is partcipating in GSOC 2020! + See discourse for details. + + Apply by March 31, 2020.

    +
    + +
    +

    John Hunter Excellence in Plotting Contest 2020 + submissions are open! + + Entries are due June 1, 2020.

    +
    + +
    {%- if builder in ('htmlhelp', 'devhelp', 'latex') %} From da544e0177143b51a897b98df1adf428ad39dbd5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 26 Mar 2020 22:25:02 -0400 Subject: [PATCH 002/602] Backport PR #16906: update FAQ on how to register pandas converters --- doc/faq/howto_faq.rst | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/doc/faq/howto_faq.rst b/doc/faq/howto_faq.rst index 4c17fa503196..465bc94a2448 100644 --- a/doc/faq/howto_faq.rst +++ b/doc/faq/howto_faq.rst @@ -21,20 +21,12 @@ Plot `numpy.datetime64` values As of Matplotlib 2.2, `numpy.datetime64` objects are handled the same way as `datetime.datetime` objects. -If you prefer the pandas converters and locators, you can register their -converter with the `matplotlib.units` module:: - - from pandas.tseries import converter as pdtc - pdtc.register() - -If you only want to use the `pandas` converter for `numpy.datetime64` values :: - - from pandas.tseries import converter as pdtc - import matplotlib.units as munits - import numpy as np - - munits.registry[np.datetime64] = pdtc.DatetimeConverter() +If you prefer the pandas converters and locators, you can register them. This +is done automatically when calling a pandas plot function and may be +unnecessary when using pandas instead of Matplotlib directly. :: + from pandas.plotting import register_matplotlib_converters + register_matplotlib_converters() .. _howto-figure-empty: From d9ace33408dede231d7084dad5cd7eddc9f86855 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 28 Mar 2020 21:23:45 -0400 Subject: [PATCH 003/602] Backport PR #15387: fix tooltip display bug Merge pull request #15387 from dorafc/fix_tooltip_display_bug DOC: fix tooltip display bug --- doc/_static/mpl.css | 8 ++++---- requirements/doc/doc-requirements.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 6063c9a16224..0bbcb38cd52a 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -1103,10 +1103,6 @@ p.caption { font-weight: bold; } -div#gallery.section, div#tutorials.section { - overflow: hidden; -} - .sphx-glr-multi-img{ max-width: 99% !important; } @@ -1278,3 +1274,7 @@ div.bullet-box li { flex: 0 0 90%; } } + +div#gallery.section .sphx-glr-clear:first-of-type, div#tutorials.section .sphx-glr-clear:first-of-type{ + display: none; +} \ No newline at end of file diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 5159465b89e3..f04d5de3f5f1 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -13,5 +13,5 @@ ipython ipywidgets numpydoc>=0.8 pillow>=3.4,!=5.4.0 -sphinx-gallery>=0.2 +sphinx-gallery>=0.5 sphinx-copybutton From d99430e826dc1ddffb1688b599499cf37f4d1d75 Mon Sep 17 00:00:00 2001 From: Stefan Mitic Date: Tue, 10 Mar 2020 22:58:56 -0400 Subject: [PATCH 004/602] fix: initialize LocationEvent after button def --- lib/matplotlib/backend_bases.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index b17da96dbee9..8186c395a48a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1394,7 +1394,6 @@ def __init__(self, name, canvas, x, y, button=None, key=None, (*x*, *y*) in figure coords ((0, 0) = bottom left) button pressed None, 1, 2, 3, 'up', 'down' """ - LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) if button in MouseButton.__members__.values(): button = MouseButton(button) self.button = button @@ -1402,6 +1401,9 @@ def __init__(self, name, canvas, x, y, button=None, key=None, self.step = step self.dblclick = dblclick + LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) + + def __str__(self): return (f"{self.name}: " f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) " From e09cbb7a9c5d4b1e71a1c6bcc0bfefbd1ded62ab Mon Sep 17 00:00:00 2001 From: Stefan Mitic Date: Tue, 31 Mar 2020 13:42:15 -0400 Subject: [PATCH 005/602] style: removed extra blank line --- lib/matplotlib/backend_bases.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8186c395a48a..e87c33422755 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1403,7 +1403,6 @@ def __init__(self, name, canvas, x, y, button=None, key=None, LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) - def __str__(self): return (f"{self.name}: " f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) " From 466dcedd5303312c718ff5394f2b52db7974c521 Mon Sep 17 00:00:00 2001 From: hannah Date: Thu, 2 Apr 2020 16:18:54 -0400 Subject: [PATCH 006/602] Backport PR #17006: Fix typo --- lib/matplotlib/colorbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 92926ee6a870..1aab69bc2db2 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -405,7 +405,7 @@ class ColorbarBase(_ColorbarMappableDummy): ticklocation : {'auto', 'left', 'right', 'top', 'bottom'} - extend : {'neiter', 'both', 'min', 'max'} + extend : {'neither', 'both', 'min', 'max'} spacing : {'uniform', 'proportional'} From d271e4eeadf5d07143d324bfb145d0d0ad700923 Mon Sep 17 00:00:00 2001 From: Stefan Mitic Date: Fri, 3 Apr 2020 17:11:00 -0400 Subject: [PATCH 007/602] fix: changed init for KeyEvent --- lib/matplotlib/backend_bases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e87c33422755..72927ae27cdb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1486,8 +1486,8 @@ def on_key(event): cid = fig.canvas.mpl_connect('key_press_event', on_key) """ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): - LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) self.key = key + LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) def _get_renderer(figure, print_method): From 2b2ec2a425b54e849fd243e6371139acce1fe342 Mon Sep 17 00:00:00 2001 From: Stefan Mitic Date: Sat, 4 Apr 2020 19:25:59 -0400 Subject: [PATCH 008/602] fix: added comments to change --- lib/matplotlib/backend_bases.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 72927ae27cdb..7dcf3120dc99 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1401,6 +1401,7 @@ def __init__(self, name, canvas, x, y, button=None, key=None, self.step = step self.dblclick = dblclick + # super-init deferred to the end: callback errors if called before LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) def __str__(self): @@ -1487,6 +1488,7 @@ def on_key(event): """ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): self.key = key + # super-init deferred to the end: callback errors if called before LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) From 7a9bc5ebbcba336b7946cf5def12040353665137 Mon Sep 17 00:00:00 2001 From: Sidharth Bansal <20972099+SidharthBansal@users.noreply.github.com> Date: Wed, 8 Apr 2020 17:24:48 +0530 Subject: [PATCH 009/602] Removed GSoC Banner Removed GSoC Banner --- doc/_templates/layout.html | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index d81e8c23fb2f..401653426b3c 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -31,14 +31,6 @@ {%- endif %} - - -

    John Hunter Excellence in Plotting Contest 2020 submissions are open! From 1a0e4cc6cd5fee0a469e1638bb587a84dc98d071 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 6 Apr 2020 16:56:28 +0200 Subject: [PATCH 010/602] Merge pull request #17045 from anntzer/docrefs Fix missing-references.json. Conflicts: doc/missing-references.json - re-generated the skip list --- doc/conf.py | 4 +- doc/missing-references.json | 490 ++++++++++++++---------------------- 2 files changed, 191 insertions(+), 303 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 6cebe749aa16..6cd891adc551 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -103,7 +103,9 @@ def _check_dependencies(): else: autodoc_default_options = {'members': None, 'undoc-members': None} -nitpicky = True +# missing-references names matches sphinx>=3 behavior, so we can't be nitpicky +# for older sphinxes. +nitpicky = sphinx.version_info >= (3,) # change this to True to update the allowed failures missing_references_write_json = False missing_references_warn_unused_ignores = False diff --git a/doc/missing-references.json b/doc/missing-references.json index 64a887324438..bc19b09eb34a 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -414,12 +414,12 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hexbin:124" ], "np.histogram": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.hist:80", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist:80" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.hist:78", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hist:78" ], "Normalize": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.imshow:27", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.imshow:27" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.imshow:32", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.imshow:32" ], "ax.transAxes": [ "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.indicate_inset:19", @@ -499,7 +499,8 @@ "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.plot_date:21", "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes.Axes.xaxis_date:21", "lib/matplotlib/axes/_base.py:docstring of matplotlib.axes.Axes.yaxis_date:21", - "lib/matplotlib/dates.py:docstring of matplotlib.dates.DateFormatter:44", + "lib/matplotlib/dates.py:docstring of matplotlib.dates.AutoDateLocator:55", + "lib/matplotlib/dates.py:docstring of matplotlib.dates.DateFormatter:45", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.plot_date:21" ], "P_{xx}": [ @@ -508,7 +509,8 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.psd:87" ], "functions=(lambda x: 2 / x, lambda x: 2 / x)": [ - "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.secondary_xaxis:17" + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.secondary_xaxis:17", + "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.secondary_yaxis:17" ], "tricontour(...)": [ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.tricontour:57", @@ -552,7 +554,7 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.violinplot:46" ], "Text": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1089", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1081", "doc/devel/MEP/MEP14.rst:252", "doc/devel/MEP/MEP26.rst:141", "lib/matplotlib/axis.py:docstring of matplotlib.axis.Axis.set_ticklabels:33", @@ -596,7 +598,7 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.colorbar:16" ], "ScalarMappable": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:298", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:290", "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.colorbar:26", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.colorbar:22" ], @@ -650,12 +652,10 @@ "every=5": [ "lib/matplotlib/lines.py:docstring of matplotlib.lines.Line2D.set_markevery:4" ], - "BoxTransmuterBase": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.FancyBboxPatch:5" - ], "Axes": [ "doc/api/_as_gen/matplotlib.pyplot.rst:173::1", "doc/api/prev_api_changes/api_changes_3.0.0.rst:449", + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:135", "doc/users/prev_whats_new/whats_new_1.5.rst:514", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.delaxes:2", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.plotting:33" @@ -854,7 +854,7 @@ "MovieWriterRegistry": [ "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:49", "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:51", - "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:118" + "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:122" ], "Axes.add_line": [ "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:65" @@ -875,40 +875,40 @@ "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:96" ], "Axes.streamplot": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:139" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:174" ], "matplotlib.color.is_colorlike()": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:169" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:204" ], "imshow(A, interpolation='antialiased')": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:180" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:215" ], "RendererSVG": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:198" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:233" ], "figure": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:211" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:246" ], "backend_bases.GraphicsContextBase.set_clip_path": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "blocking_input.BlockingInput.__call__": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "cm.register_cmap": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "dviread.DviFont": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "rcsetup.validate_hatch": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "rcsetup.validate_animation_writer_path": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "spines.Spine": [ - "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:254" + "doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst:293" ], "Locator": [ "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:20", @@ -925,13 +925,13 @@ "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:20" ], "GridFinder": [ - "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:88" + "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:92" ], "Locator.autoscale()": [ - "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:113" + "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:117" ], "Locator.view_limits()": [ - "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:113" + "doc/api/prev_api_changes/api_changes_3.2.0/deprecations.rst:117" ], "GraphicsContextBase": [ "lib/matplotlib/backends/backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.draw_text:8", @@ -959,6 +959,11 @@ "lib/matplotlib/backends/backend_template.py:docstring of matplotlib.backends.backend_template.RendererTemplate.new_gc:2", "lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_markers:16" ], + "Timer": [ + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.new_timer:2", + "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:14", + "lib/matplotlib/backends/backend_nbagg.py:docstring of matplotlib.backends.backend_nbagg.FigureCanvasNbAgg.new_timer:2" + ], "ginput": [ "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.start_event_loop:4", "lib/matplotlib/blocking_input.py:docstring of matplotlib.blocking_input:13" @@ -978,25 +983,6 @@ "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.start_rasterizing:4", "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.stop_rasterizing:5" ], - "_timer_start": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:9" - ], - "_timer_stop": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:12" - ], - "_timer_set_single_shot": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:17" - ], - "Timer": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:17" - ], - "_on_timer": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:17", - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:27" - ], - "_timer_set_interval": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase:23" - ], "*args, **kwargs": [ "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.TimerBase.remove_callback:4" ], @@ -1043,50 +1029,50 @@ ], "{'Creator': 'My software', 'Author': 'Me',\n'Title': 'Awesome fig'}": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:31", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:52", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:51", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:46" ], "'Title'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'Author'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'Subject'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'Keywords'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'Creator'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'Producer'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "'CreationDate'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57" + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56" ], "'ModDate'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57" + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56" ], "'Trapped'": [ "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfFile:36", - "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:57", + "lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.PdfPages:56", "lib/matplotlib/backends/backend_pgf.py:docstring of matplotlib.backends.backend_pgf.PdfPages:51" ], "draw_path": [ @@ -1191,12 +1177,6 @@ "rrulewrapper": [ "lib/matplotlib/dates.py:docstring of matplotlib.dates:105" ], - "strftime": [ - "lib/matplotlib/dates.py:docstring of matplotlib.dates.ConciseDateFormatter:18", - "lib/matplotlib/dates.py:docstring of matplotlib.dates.DateFormatter:2", - "lib/matplotlib/dates.py:docstring of matplotlib.dates.DateFormatter:28", - "lib/matplotlib/dates.py:docstring of matplotlib.dates:129" - ], "numpy.datetime64": [ "doc/api/prev_api_changes/api_changes_2.1.0.rst:95", "doc/faq/howto_faq.rst:18", @@ -1207,6 +1187,9 @@ "doc/users/prev_whats_new/whats_new_2.2.rst:155", "lib/matplotlib/dates.py:docstring of matplotlib.dates.date2num:8" ], + "strftime": [ + "lib/matplotlib/dates.py:docstring of matplotlib.dates.ConciseDateFormatter:18" + ], "_read": [ "lib/matplotlib/dviread.py:docstring of matplotlib.dviread.Vf:25" ], @@ -1352,7 +1335,7 @@ ], "LogLocator": [ "doc/api/prev_api_changes/api_changes_3.0.0.rst:207", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:59", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:51", "doc/api/prev_api_changes/api_changes_3.1.1.rst:14" ], "Axes.axes.streamplot": [ @@ -1432,235 +1415,235 @@ "doc/api/prev_api_changes/api_changes_3.0.1.rst:21" ], "PathCollection.get_offsets": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:30" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:22" ], "PathCollection": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:30" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:22" ], "PathCollection.get_array": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:30" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:22" ], "AutoLocator": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:59" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:51" ], "Axis.remove_overlaping_locs": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:79" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:71" ], "TextPath": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:127" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:119" ], "backend_bases.Timer.remove_callback": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:247" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:239" ], "backend_bases.Timer.remove_callback(func, *args,\n**kwargs)": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:250" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:242" ], "backend_bases.Timer.add_callback(func, *args, **kwargs)": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:250" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:242" ], "backend_bases.Timer.add_callback": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:267" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:259" ], "collections.StemContainer": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:273", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:276" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:265", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:268" ], "Axes.stem": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:276" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:268" ], "collections.LineCollection.get_segements()": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:288" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:280" ], "matplotlib.colorbar.ColorbarBase.set_norm": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:298" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:290" ], "matplotlib.colorbar.ColorbarBase.set_cmap": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:298" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:290" ], "matplotlib.colorbar.ColorbarBase.set_clim": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:298" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:290" ], "Installing": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:319" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:311" ], "Axes.spy(..., origin='lower')": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:367" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:359" ], "str()": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:386" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:378" ], "Axes.fmt_xdata": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:405", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:408" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:397", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:400" ], "Axes.fmt_ydata": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:405", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:408" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:397", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:400" ], "Tick": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:414", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:406", "doc/users/prev_whats_new/whats_new_2.1.0.rst:409", "doc/users/prev_whats_new/whats_new_2.2.rst:244" ], "streamplot.Grid": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:487" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:479" ], "legend.Legend.set_draggable()": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:507" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:499" ], "scipy.stats.norm.pdf": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:573", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:659" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:565", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:651" ], "matplotlib.pylab": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:626" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:618" ], "ImageComparisonTest": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:710" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:702" ], "mathcircled": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:737" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:729" ], "cbook.deprecated": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1081", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:774" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1073", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:766" ], "cbook.warn_deprecated": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:774" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:766" ], "matplotlib.testing.compare.calculate_rms": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:779" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:771" ], "Axes.hist2d": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:798" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:790" ], "Annotation": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:799" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:791" ], "Axes.annotation": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:799" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:791" ], "bezier.find_bezier_t_intersecting_with_closedpath": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:801" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:793" ], "bezier.split_bezier_intersecting_with_closedpath": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:801" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:793" ], "bezier.find_r_to_boundary_of_closedpath": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1026", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:801" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1018", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:793" ], "bezier.split_path_inout": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:801" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:793" ], "bezier.check_if_parallel": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:801" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:793" ], "spine.Spine.is_frame_like": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:823" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:815" ], "axis.Axis.get_ticks_position": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:829" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:821" ], "mpl_toolkits.Axes.AxisDict": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:835" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:827" ], "checkdep_dvipng": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:840" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:832" ], "checkdep_ghostscript": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:841" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:833" ], "checkdep_pdftops": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:842" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:834" ], "checkdep_inkscape": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:843" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:835" ], "ticker.decade_up": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:846" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:838" ], "ticker.decade_down": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:847" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:839" ], "docstring.Appender": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:851" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:843" ], "docstring.dedent": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:852" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:844" ], "docstring.copy_dedent": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:853" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:845" ], "matplotlib.pyplot.get_scale_docs()": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:861" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:853" ], "dates.strpdate2num": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:926" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:918" ], "dates.bytespdate2num": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:927" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:919" ], "axes3d.Axes3D.xaxis": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:941" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:933" ], "axes3d.Axes3D.yaxis": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:941" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:933" ], "axes3d.Axes3D.zaxis": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:941" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:933" ], "pytest.mark.backend(...)": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:949" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:941" ], "matplotlib.testing.conftest.mpl_test_settings": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:949" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:941" ], "Quiver": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:955" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:947" ], "Collection": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:957", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:949", "doc/users/prev_whats_new/whats_new_1.5.rst:321", "doc/users/prev_whats_new/whats_new_2.2.rst:198", "doc/users/prev_whats_new/whats_new_2.2.rst:201" ], "backend_gtk3.FileChooserDialog": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:966" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:958" ], "backend_gtk3.NavigationToolbar2GTK3.get_filechooser": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:967" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:959" ], "backend_gtk3.SaveFigureGTK3.get_filechooser": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:968" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:960" ], "NavigationToolbar2QT.adj_window": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:969" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:961" ], "backend_wx.IDLE_DELAY": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:970" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:962" ], "LogTransform": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1001" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:993" ], "InvertedLogTransform": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1001" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:993" ], "NavigationToolbar2QT.buttons": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1041" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1033" ], "Axis.iter_ticks": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1072" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1064" ], "Axis._update_ticks": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1074" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1066" ], "Formatter.__call__": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1123", - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1129" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1115", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1121" ], "ticker..MaxNLocator": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1156" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:1148" ], "Locator.nonsingular": [ "doc/api/prev_api_changes/api_changes_3.1.1.rst:14" @@ -1685,6 +1668,7 @@ "lib/matplotlib/projections/polar.py:docstring of matplotlib.projections.polar.ThetaTick:4" ], "plot": [ + "doc/tutorials/introductory/customizing.rst:184", "doc/users/prev_whats_new/changelog.rst:232", "lib/matplotlib/sphinxext/plot_directive.py:docstring of matplotlib.sphinxext.plot_directive:4" ], @@ -1743,10 +1727,6 @@ "pytest.mark.usefixtures": [ "lib/matplotlib/testing/decorators.py:docstring of matplotlib.testing.decorators.image_comparison:13" ], - "Transform": [ - "doc/users/prev_whats_new/whats_new_2.2.rst:357", - "doc/users/prev_whats_new/whats_new_2.2.rst:360" - ], "texmanager.TexManager": [ "lib/matplotlib/textpath.py:docstring of matplotlib.textpath.TextToPath.get_texmanager:2" ], @@ -1770,16 +1750,16 @@ "lib/matplotlib/units.py:docstring of matplotlib.units.DecimalConverter.axisinfo:2" ], "active": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.AxesWidget:36" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.AxesWidget:34" ], "figure.canvas.mpl_connect": [ "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.AxesWidget.connect_event:4" ], "xy": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Lasso:8" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Lasso:7" ], "callback": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Lasso:20" + "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Lasso:19" ], "onselect": [ "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.PolygonSelector:20" @@ -1802,15 +1782,6 @@ "valstep": [ "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Slider:70" ], - "facecolor": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Slider:85" - ], - "edgecolor": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Slider:85" - ], - "alpha": [ - "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.Slider:85" - ], "span_selector.active=False": [ "lib/matplotlib/widgets.py:docstring of matplotlib.widgets.SpanSelector:7" ], @@ -1866,7 +1837,7 @@ "setup.py": [ "doc/devel/MEP/MEP11.rst:64", "doc/devel/MEP/MEP11.rst:67", - "doc/devel/contributing.rst:387" + "doc/devel/contributing.rst:403" ], "import numpy": [ "doc/devel/MEP/MEP11.rst:64" @@ -2183,86 +2154,80 @@ "text": [ "doc/devel/MEP/MEP29.rst:28" ], - "Example": [ - "doc/devel/coding_guide.rst:26" - ], - "examples": [ - "doc/devel/coding_guide.rst:26" - ], "tox": [ - "doc/devel/coding_guide.rst:81" + "doc/devel/coding_guide.rst:101" ], "git status": [ - "doc/devel/coding_guide.rst:185" + "doc/devel/coding_guide.rst:207" ], "v2.2.x": [ - "doc/devel/coding_guide.rst:194" + "doc/devel/coding_guide.rst:216" ], "master": [ - "doc/devel/coding_guide.rst:194" + "doc/devel/coding_guide.rst:216" ], "doc/api/api_changes": [ - "doc/devel/contributing.rst:269" + "doc/devel/contributing.rst:271" ], "doc/api/next_api_changes": [ - "doc/devel/contributing.rst:346" + "doc/devel/contributing.rst:362" ], "MatplotlibDeprecationWarning": [ - "doc/devel/contributing.rst:352" + "doc/devel/contributing.rst:368" ], "cbook.warn_deprecated()": [ - "doc/devel/contributing.rst:355" + "doc/devel/contributing.rst:371" ], "package_data": [ - "doc/devel/contributing.rst:387" + "doc/devel/contributing.rst:403" ], "FOO_wrap.cpp": [ - "doc/devel/contributing.rst:399" + "doc/devel/contributing.rst:415" ], "FOO_wrapper.cpp": [ - "doc/devel/contributing.rst:399" + "doc/devel/contributing.rst:415" ], "logger.WARNING": [ - "doc/devel/contributing.rst:493" + "doc/devel/contributing.rst:509" ], "cbook._warn_external": [ - "doc/devel/contributing.rst:518", - "doc/devel/contributing.rst:533", - "doc/devel/contributing.rst:542", - "doc/devel/contributing.rst:567" + "doc/devel/contributing.rst:534", + "doc/devel/contributing.rst:549", + "doc/devel/contributing.rst:558", + "doc/devel/contributing.rst:583" ], "logging.WARNING": [ - "doc/devel/contributing.rst:530" + "doc/devel/contributing.rst:546" ], "warn": [ - "doc/devel/contributing.rst:542" + "doc/devel/contributing.rst:558" ], "./gallery/index.html": [ - "doc/devel/contributing.rst:587" + "doc/devel/contributing.rst:603" ], "lib/matplotlib/mpl-data/sample_data/": [ - "doc/devel/contributing.rst:592" + "doc/devel/contributing.rst:608" ], ":doc:": [ - "doc/devel/documenting_mpl.rst:161" + "doc/devel/documenting_mpl.rst:184" ], "pyplot.getp": [ - "doc/devel/documenting_mpl.rst:520" + "doc/devel/documenting_mpl.rst:582" ], "matplotlib.docstring.dedent_interpd": [ - "doc/devel/documenting_mpl.rst:588" + "doc/devel/documenting_mpl.rst:650" ], "matplotlib.patches.Patch.__init__": [ - "doc/devel/documenting_mpl.rst:621" + "doc/devel/documenting_mpl.rst:683" ], "# docstring inherited": [ - "doc/devel/documenting_mpl.rst:637" + "doc/devel/documenting_mpl.rst:699" ], ":plot:": [ - "doc/devel/documenting_mpl.rst:657" + "doc/devel/documenting_mpl.rst:719" ], "###": [ - "doc/devel/documenting_mpl.rst:744" + "doc/devel/documenting_mpl.rst:806" ], "pytest": [ "doc/devel/testing.rst:50", @@ -2278,19 +2243,16 @@ "doc/gallery/misc/ftface_props.rst:14" ], "ConciseDateConverter": [ - "doc/gallery/ticks_and_spines/date_concise_formatter.rst:215" - ], - "pyplot": [ - "doc/index.rst:44" + "doc/gallery/ticks_and_spines/date_concise_formatter.rst:214" ], "mplot3d": [ - "doc/index.rst:145" + "doc/index.rst:157" ], "axes_grid1": [ - "doc/index.rst:145" + "doc/index.rst:157" ], "axisartist": [ - "doc/index.rst:145" + "doc/index.rst:157" ], "'viridis'": [ "doc/users/dflt_style_changes.rst:121" @@ -2807,6 +2769,10 @@ "examples/user_interfaces/toolmanager_sgskip.py": [ "doc/users/prev_whats_new/whats_new_2.2.rst:317" ], + "Transform": [ + "doc/users/prev_whats_new/whats_new_2.2.rst:357", + "doc/users/prev_whats_new/whats_new_2.2.rst:360" + ], "Figure_1-1.png": [ "doc/users/prev_whats_new/whats_new_3.0.rst:85" ], @@ -2905,10 +2871,6 @@ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.axvspan:4", "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.errorbar:60" ], - "matplotlib.backend_bases.Event.mpl_disconnect": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.mpl_connect:33", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.connect:33" - ], "autoscale_view": [ "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.margins:32" ], @@ -2993,14 +2955,14 @@ "lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Affine2DBase:13" ], "matplotlib.text.Text.__init__": [ - "doc/devel/contributing.rst:425", - "doc/devel/contributing.rst:433" + "doc/devel/contributing.rst:441", + "doc/devel/contributing.rst:449" ], "matplotlib.patches.Rectangle.contains": [ - "doc/users/event_handling.rst:163" + "doc/users/event_handling.rst:164" ], "matplotlib.lines.Line2D.pick": [ - "doc/users/event_handling.rst:482" + "doc/users/event_handling.rst:483" ], "matplotlib.axes.boxplot": [ "doc/users/prev_whats_new/whats_new_1.2.rst:126" @@ -3038,8 +3000,8 @@ "lib/matplotlib/figure.py:docstring of matplotlib.figure.AxesStack:1" ], "matplotlib.contours.ContourSet": [ - "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.colorbar:141", - "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.colorbar:137", + "lib/matplotlib/figure.py:docstring of matplotlib.figure.Figure.colorbar:142", + "lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.colorbar:138", "lib/mpl_toolkits/axes_grid1/colorbar.py:docstring of mpl_toolkits.axes_grid1.colorbar.colorbar:92" ], "matplotlib.patches._Style": [ @@ -3053,13 +3015,13 @@ "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle:38", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle:38" ], - "matplotlib.patches._Bracket": [ + "matplotlib.patches.ArrowStyle._Bracket": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BarAB:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketA:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketAB:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.BracketB:1" ], - "matplotlib.patches._Curve": [ + "matplotlib.patches.ArrowStyle._Curve": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Curve:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveA:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveAB:1", @@ -3068,40 +3030,27 @@ "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledAB:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.CurveFilledB:1" ], - "matplotlib.patches._Base": [ + "matplotlib.patches.ArrowStyle._Base": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Fancy:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Simple:1", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Wedge:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.ArrowStyle.Wedge:1" + ], + "matplotlib.patches.BoxStyle._Base": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Circle:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.DArrow:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.LArrow:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Round4:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Round:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Sawtooth:1", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Square:1", + "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Square:1" + ], + "matplotlib.patches.ConnectionStyle._Base": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle3:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Angle:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc3:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Arc:1", "lib/matplotlib/patches.py:docstring of matplotlib.patches.ConnectionStyle.Bar:1" ], - "BboxTransmuter": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Circle.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.DArrow.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.LArrow.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.RArrow.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Round.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Round4.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Roundtooth.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Sawtooth.transmute:2", - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Square.transmute:2" - ], - "matplotlib.patches.LArrow": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.RArrow:1" - ], - "matplotlib.patches.Sawtooth": [ - "lib/matplotlib/patches.py:docstring of matplotlib.patches.BoxStyle.Roundtooth:1" - ], "matplotlib.patch.Patch": [ "lib/matplotlib/patches.py:docstring of matplotlib.patches.FancyArrowPatch.set_patchA:8", "lib/matplotlib/patches.py:docstring of matplotlib.patches.FancyArrowPatch.set_patchB:8", @@ -3155,41 +3104,24 @@ ":1", "doc/api/_as_gen/mpl_toolkits.axes_grid1.parasite_axes.rst:31::1" ], - "mpl_toolkits.axisartist.axisline_style.SimpleArrow": [ - "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle.FilledArrow:1" - ], "_FancyAxislineStyle.FilledArrow": [ ":1" ], - "mpl_toolkits.axisartist.axisline_style._Base": [ + "mpl_toolkits.axisartist.axisline_style.AxislineStyle._Base": [ "lib/mpl_toolkits/axisartist/axisline_style.py:docstring of mpl_toolkits.axisartist.axisline_style.AxislineStyle.SimpleArrow:1" ], "_FancyAxislineStyle.SimpleArrow": [ ":1" ], - "mpl_toolkits.axisartist.axislines._Base": [ + "mpl_toolkits.axisartist.axislines.AxisArtistHelper._Base": [ "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper.Fixed:1", "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper.Floating:1" ], - "mpl_toolkits.axisartist.axislines.Fixed": [ - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelperRectlinear.Fixed:1", - "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FixedAxisArtistHelper:1" - ], - "mpl_toolkits.axisartist.axislines.Floating": [ - "lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelperRectlinear.Floating:1", - "lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py:docstring of mpl_toolkits.axisartist.grid_helper_curvelinear.FloatingAxisArtistHelper:1" - ], "mpl_toolkits.axisartist.floating_axes.Floating AxesHostAxes": [ ":1", "doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:31::1" ], - "Patch": [ - "lib/matplotlib/spines.py:docstring of matplotlib.spines.Spine.draw:2", - "lib/matplotlib/table.py:docstring of matplotlib.table.Cell.draw:2", - "lib/mpl_toolkits/mplot3d/art3d.py:docstring of mpl_toolkits.mplot3d.art3d.Patch3D.get_facecolor:2" - ], "tzinfo": [ - "lib/matplotlib/dates.py:docstring of matplotlib.dates.AutoDateLocator:36", "lib/matplotlib/dates.py:docstring of matplotlib.dates.DateLocator:24", "lib/matplotlib/dates.py:docstring of matplotlib.dates.RRuleLocator:2", "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.zaxis_date:4" @@ -3233,10 +3165,6 @@ "matplotlib.projections.geo.MollweideAxes": [ "doc/api/artist_api.rst:189" ], - "backend_bases.Timer": [ - "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.FigureCanvasBase.new_timer:2", - "lib/matplotlib/backends/backend_nbagg.py:docstring of matplotlib.backends.backend_nbagg.FigureCanvasNbAgg.new_timer:2" - ], "Cursors": [ "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.NavigationToolbar2.set_cursor:2" ], @@ -3422,7 +3350,7 @@ "doc/tutorials/intermediate/artists.rst:57" ], "matplotlib.patches.AxesImage": [ - "doc/users/event_handling.rst:414" + "doc/users/event_handling.rst:415" ], "mpl_toolkits.axes_grid1.axes_divider.HBox": [ "doc/users/prev_whats_new/whats_new_1.1.rst:210" @@ -3509,45 +3437,6 @@ "matplotlib.cm.ScalarMappable.callbacksSM": [ "doc/api/prev_api_changes/api_changes_0.98.0.rst:10" ], - "transforms.Bbox.bounds": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:91" - ], - "transforms.BboxBase.width": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:93" - ], - "transforms.BboxBase.height": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:96" - ], - "transforms.Bbox.intervalx": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:99" - ], - "transforms.Bbox.intervaly": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:102" - ], - "transforms.Bbox.x0": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:105" - ], - "transforms.BboxBase.xmin": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:105" - ], - "transforms.Bbox.y0": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:109" - ], - "transforms.BboxBase.ymin": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:109" - ], - "transforms.Bbox.x1": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:113" - ], - "transforms.BboxBase.xmax": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:113" - ], - "transforms.Bbox.y1": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:117" - ], - "transforms.BboxBase.ymax": [ - "doc/api/prev_api_changes/api_changes_0.98.0.rst:117" - ], "matplotlib.figure.Figure.patch": [ "doc/api/prev_api_changes/api_changes_0.98.x.rst:89", "doc/tutorials/intermediate/artists.rst:172", @@ -3674,9 +3563,6 @@ "lib/matplotlib/dates.py:docstring of matplotlib.dates.datestr2num:2", "lib/matplotlib/dates.py:docstring of matplotlib.dates:28::1" ], - "strftime": [ - "lib/matplotlib/dates.py:docstring of matplotlib.dates.IndexDateFormatter:23" - ], "matplotlib.pylab.plot": [ "doc/devel/add_new_projection.rst:18" ], @@ -3684,7 +3570,7 @@ "doc/devel/add_new_projection.rst:18" ], "log.debug": [ - "doc/devel/contributing.rst:475" + "doc/devel/contributing.rst:491" ], "matplotlib.test": [ "doc/devel/testing.rst:85" @@ -3816,11 +3702,11 @@ "doc/api/prev_api_changes/api_changes_3.0.0.rst:355" ], "matplotlib.pylab": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:624", + "doc/api/prev_api_changes/api_changes_3.1.0.rst:616", "doc/users/history.rst:63" ], "matplotlib.scales": [ - "doc/api/prev_api_changes/api_changes_3.1.0.rst:1001" + "doc/api/prev_api_changes/api_changes_3.1.0.rst:993" ], "mpl_toolkits.mplot3d.axes3d": [ "doc/api/toolkits/mplot3d.rst:19" @@ -3852,4 +3738,4 @@ "doc/users/prev_whats_new/whats_new_1.5.rst:737" ] } -} +} \ No newline at end of file From 2a55dd1b208bca483e88561fafeee3fa76ada01d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 13 Apr 2020 14:49:26 -0400 Subject: [PATCH 011/602] Backport PR #17120: [DOC] strip_signature_backslash --- doc/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 6cd891adc551..754854f8f182 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,6 +29,10 @@ # General configuration # --------------------- +# Strip backslahes in function's signature +# To be removed when numpydoc > 0.9.x +strip_signature_backslash = True + # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ From 00b4074572b9a7fbaa2c25303d0a2a9ce09bbb56 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 17 Apr 2020 21:13:39 +0100 Subject: [PATCH 012/602] Backport PR #17167: Fix misindented block in example. --- examples/pyplots/auto_subplots_adjust.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/pyplots/auto_subplots_adjust.py b/examples/pyplots/auto_subplots_adjust.py index 3fc58e9394e6..90ebb64f0308 100644 --- a/examples/pyplots/auto_subplots_adjust.py +++ b/examples/pyplots/auto_subplots_adjust.py @@ -28,14 +28,12 @@ def on_draw(event): # want the inverse of that bboxi = bbox.inverse_transformed(fig.transFigure) bboxes.append(bboxi) - - # this is the bbox that bounds all the bboxes, again in relative - # figure coords - bbox = mtransforms.Bbox.union(bboxes) - if fig.subplotpars.left < bbox.width: - # we need to move it over - fig.subplots_adjust(left=1.1*bbox.width) # pad a little - fig.canvas.draw() + # the bbox that bounds all the bboxes, again in relative figure coords + bbox = mtransforms.Bbox.union(bboxes) + if fig.subplotpars.left < bbox.width: + # we need to move it over + fig.subplots_adjust(left=1.1*bbox.width) # pad a little + fig.canvas.draw() fig.canvas.mpl_connect('draw_event', on_draw) From 4876bb561840e2a4d0da8aaaf186b4059fc0e782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20G=C3=A9rard?= <34161+leolchat@users.noreply.github.com> Date: Wed, 29 Apr 2020 20:33:26 -0700 Subject: [PATCH 013/602] Fix ConciseDateFormatter when only micros change When micros only are changing (we are zoomed inside a second), the formatter previously computed a level 0 : year, displaying only years... when microseconds were expected. --- lib/matplotlib/dates.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index ac0b33bc13c8..c9b7e92c9856 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -775,6 +775,10 @@ def format_ticks(self, values): for level in range(5, -1, -1): if len(np.unique(tickdate[:, level])) > 1: break + elif level == 0: + # all tickdate are the same, so only micros might be different + # set to the most precise available (level=6: microseconds doesn't exist...) + level = 5 # level is the basic level we will label at. # now loop through and decide the actual ticklabels From 986a0f9192f4b601ccf25eea45aabde446e09996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20G=C3=A9rard?= Date: Wed, 29 Apr 2020 22:08:09 -0700 Subject: [PATCH 014/602] Separate test --- lib/matplotlib/dates.py | 2 +- lib/matplotlib/tests/test_dates.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c9b7e92c9856..766a8d93708f 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -777,7 +777,7 @@ def format_ticks(self, values): break elif level == 0: # all tickdate are the same, so only micros might be different - # set to the most precise available (level=6: microseconds doesn't exist...) + # set to the most precise (6: microseconds doesn't exist...) level = 5 # level is the basic level we will label at. diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index b27b730c7919..8775f99acd8f 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -449,6 +449,14 @@ def _create_auto_date_locator(date1, date2): assert list(map(str, mdates.num2date(locator()))) == expected +def test_concise_formatter_subsecond(): + locator = mdates.AutoDateLocator(interval_multiples=True) + formatter = mdates.ConciseDateFormatter(locator) + year_1996 = 9861.0 + strings = formatter.format_ticks([year_1996, year_1996 + 500 / mdates.MUSECONDS_PER_DAY, year_1996 + 900 / mdates.MUSECONDS_PER_DAY]) + assert strings == ['00:00', '00.0005', '00.0009'] + + def test_concise_formatter(): def _create_auto_date_locator(date1, date2): fig, ax = plt.subplots() From 2c27a02f5206b02c30266ee0f535261d761166b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20G=C3=A9rard?= Date: Wed, 29 Apr 2020 22:13:10 -0700 Subject: [PATCH 015/602] flake8 fixes --- lib/matplotlib/tests/test_dates.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 8775f99acd8f..0a749588697c 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -453,7 +453,10 @@ def test_concise_formatter_subsecond(): locator = mdates.AutoDateLocator(interval_multiples=True) formatter = mdates.ConciseDateFormatter(locator) year_1996 = 9861.0 - strings = formatter.format_ticks([year_1996, year_1996 + 500 / mdates.MUSECONDS_PER_DAY, year_1996 + 900 / mdates.MUSECONDS_PER_DAY]) + strings = formatter.format_ticks([ + year_1996, + year_1996 + 500 / mdates.MUSECONDS_PER_DAY, + year_1996 + 900 / mdates.MUSECONDS_PER_DAY]) assert strings == ['00:00', '00.0005', '00.0009'] From 6d07be7b5b25cd908ad0ba512420c890418f8981 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Fri, 1 May 2020 21:34:09 -0600 Subject: [PATCH 016/602] Backport PR #17263: you can't call CGDataProviderCreateWithData on a stack pointer --- src/_macosx.m | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index 41ef075eba02..d3e3995ed308 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -1606,24 +1606,25 @@ - (void)setCanvas: (PyObject*)newCanvas static void _buffer_release(void* info, const void* data, size_t size) { PyBuffer_Release((Py_buffer *)info); + free(info); } static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) { - Py_buffer buffer; + Py_buffer *buffer = malloc(sizeof(Py_buffer)); - if (PyObject_GetBuffer(renderer, &buffer, PyBUF_CONTIG_RO) == -1) { + if (PyObject_GetBuffer(renderer, buffer, PyBUF_CONTIG_RO) == -1) { PyErr_Print(); return 1; } - if (buffer.ndim != 3 || buffer.shape[2] != 4) { - PyBuffer_Release(&buffer); + if (buffer->ndim != 3 || buffer->shape[2] != 4) { + _buffer_release(buffer, NULL, 0); return 1; } - const Py_ssize_t nrows = buffer.shape[0]; - const Py_ssize_t ncols = buffer.shape[1]; + const Py_ssize_t nrows = buffer->shape[0]; + const Py_ssize_t ncols = buffer->shape[1]; const size_t bytesPerComponent = 1; const size_t bitsPerComponent = 8 * bytesPerComponent; const size_t nComponents = 4; /* red, green, blue, alpha */ @@ -1632,16 +1633,16 @@ static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); if (!colorspace) { - PyBuffer_Release(&buffer); + _buffer_release(buffer, NULL, 0); return 1; } - CGDataProviderRef provider = CGDataProviderCreateWithData(&buffer, - buffer.buf, - buffer.len, + CGDataProviderRef provider = CGDataProviderCreateWithData(buffer, + buffer->buf, + buffer->len, _buffer_release); if (!provider) { - PyBuffer_Release(&buffer); + _buffer_release(buffer, NULL, 0); CGColorSpaceRelease(colorspace); return 1; } @@ -1662,7 +1663,6 @@ static int _copy_agg_buffer(CGContextRef cr, PyObject *renderer) CGDataProviderRelease(provider); if (!bitmap) { - PyBuffer_Release(&buffer); return 1; } From b73d8055523e0fe23b5f8368e7a08c20fb30e6f2 Mon Sep 17 00:00:00 2001 From: ImportanceOfBeingErnest Date: Thu, 30 Apr 2020 16:22:15 +0200 Subject: [PATCH 017/602] allow numpy arrays in markevery --- lib/matplotlib/lines.py | 7 ++++--- lib/matplotlib/tests/test_lines.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index f5581476498b..6b67566103b6 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -537,7 +537,7 @@ def set_markevery(self, every): Parameters ---------- every : None or int or (int, int) or slice or List[int] or float or \ -(float, float) +(float, float) or List[bool] Which markers to plot. - every=None, every point will be plotted. @@ -549,6 +549,8 @@ def set_markevery(self, every): point start, up to but not including point end, will be plotted. - every=[i, j, m, n], only markers at points i, j, m, and n will be plotted. + - every=[True, False, True], positions that are True will be + plotted. - every=0.1, (i.e. a float) then markers will be spaced at approximately equal distances along the line; the distance along the line between markers is determined by multiplying the @@ -580,9 +582,8 @@ def set_markevery(self, every): axes-bounding-box-diagonal regardless of the actual axes data limits. """ - if self._markevery != every: - self.stale = True self._markevery = every + self.stale = True def get_markevery(self): """ diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index d66509b0ee78..8d8a4dc30dcd 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -199,6 +199,30 @@ def test_step_markers(fig_test, fig_ref): fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2]) +@check_figures_equal(extensions=('png',)) +def test_markevery(fig_test, fig_ref): + np.random.seed(42) + t = np.linspace(0, 3, 14) + y = np.random.rand(len(t)) + + casesA = [None, 4, (2, 5), [1, 5, 11], + [0, -1], slice(5, 10, 2), 0.3, (0.3, 0.4), + np.arange(len(t))[y > 0.5]] + casesB = ["11111111111111", "10001000100010", "00100001000010", + "01000100000100", "10000000000001", "00000101010000", + "11011011011110", "01010011011101", "01110001110110"] + + axsA = fig_ref.subplots(3, 3) + axsB = fig_test.subplots(3, 3) + + for ax, case in zip(axsA.flat, casesA): + ax.plot(t, y, "-gD", markevery=case) + + for ax, case in zip(axsB.flat, casesB): + me = np.array(list(case)).astype(int).astype(bool) + ax.plot(t, y, "-gD", markevery=me) + + def test_marker_as_markerstyle(): fig, ax = plt.subplots() line, = ax.plot([2, 4, 3], marker=MarkerStyle("D")) From b6a273989ffc8ef3889fe16ee61d40b24f79c3e6 Mon Sep 17 00:00:00 2001 From: Sam Tygier Date: Sun, 3 May 2020 17:15:45 +0100 Subject: [PATCH 018/602] Merge consecutive rasterizations In vector output it is possible to flag artists to be rasterized. In many cases with multiple rasterized objects there can be significant file size savings by combining the rendered bitmaps into a single bitmap. This is achieved by moving the depth tracking logic from start_rasterizing() and stop_rasterizing() functions into the allow_rasterization() wrapper. This allows delaying the call to stop_rasterizing() until we are about to draw an non rasterized artist. The outer draw method, i.e. in Figure must be wraped with _finalize_rasterization() to ensure the that rasterization is completed. Figure.suppressComposite can be used to prevent merging. This fixes #17149 --- lib/matplotlib/artist.py | 32 +++++++++++- lib/matplotlib/backend_bases.py | 2 + lib/matplotlib/backends/backend_mixed.py | 62 +++++++++++------------- lib/matplotlib/figure.py | 4 +- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 41e890352fbc..4265db5948e5 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -34,7 +34,17 @@ def allow_rasterization(draw): def draw_wrapper(artist, renderer, *args, **kwargs): try: if artist.get_rasterized(): - renderer.start_rasterizing() + if renderer._raster_depth == 0 and not renderer._rasterizing: + renderer.start_rasterizing() + renderer._rasterizing = True + renderer._raster_depth += 1 + else: + if renderer._raster_depth == 0 and renderer._rasterizing: + # Only stop when we are not in a rasterized parent + # and something has be rasterized since last stop + renderer.stop_rasterizing() + renderer._rasterizing = False + if artist.get_agg_filter() is not None: renderer.start_filter() @@ -43,12 +53,32 @@ def draw_wrapper(artist, renderer, *args, **kwargs): if artist.get_agg_filter() is not None: renderer.stop_filter(artist.get_agg_filter()) if artist.get_rasterized(): + renderer._raster_depth -= 1 + if (renderer._rasterizing and artist.figure and + artist.figure.suppressComposite): + # restart rasterizing to prevent merging renderer.stop_rasterizing() + renderer.start_rasterizing() draw_wrapper._supports_rasterization = True return draw_wrapper +def _finalize_rasterization(draw): + """ + Decorator for Artist.draw method. Needed on the outermost artist, i.e. + Figure, to finish up if the render is still in rasterized mode. + """ + @wraps(draw) + def draw_wrapper(artist, renderer, *args, **kwargs): + result = draw(artist, renderer, *args, **kwargs) + if renderer._rasterizing: + renderer.stop_rasterizing() + renderer._rasterizing = False + return result + return draw_wrapper + + def _stale_axes_callback(self, val): if self.axes: self.axes.stale = val diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index cf9453959853..5dceb2e3736c 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -142,6 +142,8 @@ def __init__(self): super().__init__() self._texmanager = None self._text2path = textpath.TextToPath() + self._raster_depth = 0 + self._rasterizing = False def open_group(self, s, gid=None): """ diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 4bd77e8105e1..9a588ece8f9d 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -52,7 +52,6 @@ def __init__(self, figure, width, height, dpi, vector_renderer, self._vector_renderer = vector_renderer self._raster_renderer = None - self._rasterizing = 0 # A reference to the figure is needed as we need to change # the figure dpi before and after the rasterization. Although @@ -84,47 +83,40 @@ def start_rasterizing(self): r = process_figure_for_rasterizing(self.figure, self._bbox_inches_restore) self._bbox_inches_restore = r - if self._rasterizing == 0: - self._raster_renderer = self._raster_renderer_class( - self._width*self.dpi, self._height*self.dpi, self.dpi) - self._renderer = self._raster_renderer - self._rasterizing += 1 + + self._raster_renderer = self._raster_renderer_class( + self._width*self.dpi, self._height*self.dpi, self.dpi) + self._renderer = self._raster_renderer def stop_rasterizing(self): """ Exit "raster" mode. All of the drawing that was done since the last `start_rasterizing` call will be copied to the vector backend by calling draw_image. - - If `start_rasterizing` has been called multiple times, - `stop_rasterizing` must be called the same number of times before - "raster" mode is exited. """ - self._rasterizing -= 1 - if self._rasterizing == 0: - self._renderer = self._vector_renderer - - height = self._height * self.dpi - buffer, bounds = self._raster_renderer.tostring_rgba_minimized() - l, b, w, h = bounds - if w > 0 and h > 0: - image = np.frombuffer(buffer, dtype=np.uint8) - image = image.reshape((h, w, 4)) - image = image[::-1] - gc = self._renderer.new_gc() - # TODO: If the mixedmode resolution differs from the figure's - # dpi, the image must be scaled (dpi->_figdpi). Not all - # backends support this. - self._renderer.draw_image( - gc, - l * self._figdpi / self.dpi, - (height-b-h) * self._figdpi / self.dpi, - image) - self._raster_renderer = None - self._rasterizing = False - - # restore the figure dpi. - self.figure.set_dpi(self._figdpi) + + self._renderer = self._vector_renderer + + height = self._height * self.dpi + buffer, bounds = self._raster_renderer.tostring_rgba_minimized() + l, b, w, h = bounds + if w > 0 and h > 0: + image = np.frombuffer(buffer, dtype=np.uint8) + image = image.reshape((h, w, 4)) + image = image[::-1] + gc = self._renderer.new_gc() + # TODO: If the mixedmode resolution differs from the figure's + # dpi, the image must be scaled (dpi->_figdpi). Not all + # backends support this. + self._renderer.draw_image( + gc, + l * self._figdpi / self.dpi, + (height-b-h) * self._figdpi / self.dpi, + image) + self._raster_renderer = None + + # restore the figure dpi. + self.figure.set_dpi(self._figdpi) if self._bbox_inches_restore: # when tight bbox is used r = process_figure_for_rasterizing(self.figure, diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b7dc60859525..a619b58d2505 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -18,7 +18,8 @@ from matplotlib import __version__ as _mpl_version import matplotlib.artist as martist -from matplotlib.artist import Artist, allow_rasterization +from matplotlib.artist import ( + Artist, allow_rasterization, _finalize_rasterization) from matplotlib.backend_bases import ( FigureCanvasBase, NonGuiException, MouseButton) import matplotlib.cbook as cbook @@ -1689,6 +1690,7 @@ def clear(self, keep_observers=False): """Clear the figure -- synonym for `clf`.""" self.clf(keep_observers=keep_observers) + @_finalize_rasterization @allow_rasterization def draw(self, renderer): # docstring inherited From 7f9f32b1badb59b6b33237a04e0e10b3330a7686 Mon Sep 17 00:00:00 2001 From: Sam Tygier Date: Sun, 3 May 2020 17:32:01 +0100 Subject: [PATCH 019/602] Rasterization tests Test that rasterization does not significantly change output. Tests that partial rasterization does not effect draw ordering. Tests that right number of elements appear in SVG output, i.e. bitmaps are merged when appropriate. --- lib/matplotlib/tests/test_backend_svg.py | 100 ++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index 7bdc3ca33b0a..c64ffd8e4e69 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -10,7 +10,7 @@ from matplotlib import dviread from matplotlib.figure import Figure import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal needs_usetex = pytest.mark.skipif( @@ -94,6 +94,104 @@ def test_bold_font_output_with_none_fonttype(): ax.set_title('bold-title', fontweight='bold') +@check_figures_equal(tol=20) +def test_rasterized(fig_test, fig_ref): + t = np.arange(0, 100) * (2.3) + x = np.cos(t) + y = np.sin(t) + + ax_ref = fig_ref.subplots() + ax_ref.plot(x, y, "-", c="r", lw=10) + ax_ref.plot(x+1, y, "-", c="b", lw=10) + + ax_test = fig_test.subplots() + ax_test.plot(x, y, "-", c="r", lw=10, rasterized=True) + ax_test.plot(x+1, y, "-", c="b", lw=10, rasterized=True) + + +@check_figures_equal() +def test_rasterized_ordering(fig_test, fig_ref): + t = np.arange(0, 100) * (2.3) + x = np.cos(t) + y = np.sin(t) + + ax_ref = fig_ref.subplots() + ax_ref.set_xlim(0, 3) + ax_ref.set_ylim(-1.1, 1.1) + ax_ref.plot(x, y, "-", c="r", lw=10, rasterized=True) + ax_ref.plot(x+1, y, "-", c="b", lw=10, rasterized=False) + ax_ref.plot(x+2, y, "-", c="g", lw=10, rasterized=True) + ax_ref.plot(x+3, y, "-", c="m", lw=10, rasterized=True) + + ax_test = fig_test.subplots() + ax_test.set_xlim(0, 3) + ax_test.set_ylim(-1.1, 1.1) + ax_test.plot(x, y, "-", c="r", lw=10, rasterized=True, zorder=1.1) + ax_test.plot(x+2, y, "-", c="g", lw=10, rasterized=True, zorder=1.3) + ax_test.plot(x+3, y, "-", c="m", lw=10, rasterized=True, zorder=1.4) + ax_test.plot(x+1, y, "-", c="b", lw=10, rasterized=False, zorder=1.2) + + +def test_count_bitmaps(): + def count_tag(fig, tag): + fd = BytesIO() + fig.savefig(fd, format='svg') + fd.seek(0) + buf = fd.read().decode() + fd.close() + open("test.svg", "w").write(buf) + return buf.count("<%s" % tag) + + # No rasterized elements + fig1 = plt.figure() + ax1 = fig1.add_subplot(1, 1, 1) + ax1.set_axis_off() + for n in range(5): + ax1.plot([0, 20], [0, n], "b-", rasterized=False) + assert count_tag(fig1, "image") == 0 + assert count_tag(fig1, "path") == 6 # axis patch plus lines + + # rasterized can be merged + fig2 = plt.figure() + ax2 = fig2.add_subplot(1, 1, 1) + ax2.set_axis_off() + for n in range(5): + ax2.plot([0, 20], [0, n], "b-", rasterized=True) + assert count_tag(fig2, "image") == 1 + assert count_tag(fig2, "path") == 1 # axis patch + + # rasterized can't be merged without effecting draw order + fig3 = plt.figure() + ax3 = fig3.add_subplot(1, 1, 1) + ax3.set_axis_off() + for n in range(5): + ax3.plot([0, 20], [n, 0], "b-", rasterized=False) + ax3.plot([0, 20], [0, n], "b-", rasterized=True) + assert count_tag(fig3, "image") == 5 + assert count_tag(fig3, "path") == 6 + + # rasterized whole axes + fig4 = plt.figure() + ax4 = fig4.add_subplot(1, 1, 1) + ax4.set_axis_off() + ax4.set_rasterized(True) + for n in range(5): + ax4.plot([0, 20], [n, 0], "b-", rasterized=False) + ax4.plot([0, 20], [0, n], "b-", rasterized=True) + assert count_tag(fig4, "image") == 1 + assert count_tag(fig4, "path") == 1 + + # rasterized can be merged, but inhibited by suppressComposite + fig5 = plt.figure() + fig5.suppressComposite = True + ax5 = fig5.add_subplot(1, 1, 1) + ax5.set_axis_off() + for n in range(5): + ax5.plot([0, 20], [0, n], "b-", rasterized=True) + assert count_tag(fig5, "image") == 5 + assert count_tag(fig5, "path") == 1 # axis patch + + @needs_usetex def test_missing_psfont(monkeypatch): """An error is raised if a TeX font lacks a Type-1 equivalent""" From 8e9c89a53be3d7f9be08f4b80a42bce0bd9e33e0 Mon Sep 17 00:00:00 2001 From: Sam Tygier Date: Sun, 3 May 2020 17:33:52 +0100 Subject: [PATCH 020/602] Changes for Merge consecutive rasterizations Document user and api changes. --- .../2020-04-26-merged-rasterizations.rst | 15 +++++++++++++++ .../2020-04-26-merged-rasterizations.rst | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 doc/api/api_changes/2020-04-26-merged-rasterizations.rst create mode 100644 doc/users/next_whats_new/2020-04-26-merged-rasterizations.rst diff --git a/doc/api/api_changes/2020-04-26-merged-rasterizations.rst b/doc/api/api_changes/2020-04-26-merged-rasterizations.rst new file mode 100644 index 000000000000..5aed41e9f352 --- /dev/null +++ b/doc/api/api_changes/2020-04-26-merged-rasterizations.rst @@ -0,0 +1,15 @@ +Consecutive rasterized draws now merged +--------------------------------------- + +Tracking of depth of raster draws has moved from +`.backend_mixed.MixedModeRenderer.start_rasterizing` and +`.backend_mixed.MixedModeRenderer.stop_rasterizing` into +`.artist.allow_rasterization`. This means the start and stop functions are +only called when the rasterization actually needs to be started and stopped. + +The output of vector backends will change in the case that rasterized +elements are merged. This should not change the appearance of outputs. + +The renders in 3rd party backends are now expected to have +``self._raster_depth`` and ``self._rasterizing`` initialized to ``0`` and +``False`` respectively. diff --git a/doc/users/next_whats_new/2020-04-26-merged-rasterizations.rst b/doc/users/next_whats_new/2020-04-26-merged-rasterizations.rst new file mode 100644 index 000000000000..d6ad7c4a4d0e --- /dev/null +++ b/doc/users/next_whats_new/2020-04-26-merged-rasterizations.rst @@ -0,0 +1,13 @@ +Consecutive rasterized draws now merged +--------------------------------------- + +Elements of a vector output can be individually set to rasterized, using +the ``rasterized`` keyword, or `~.artist.Artist.set_rasterized()`. This can +be useful to reduce file sizes. For figures with multiple raster elements +they are now automatically merged into a smaller number of bitmaps where +this will not effect the visual output. For cases with many elements this +can result in significantly smaller file sizes. + +To ensure this happens do not place vector elements between raster ones. + +To inhibit this merging set ``Figure.suppressComposite`` to True. From 31dd47c0d90c7b131ad58ae87328f0c8eef89812 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Tue, 5 May 2020 00:13:05 -0600 Subject: [PATCH 021/602] Merge pull request #17084 from cbrnr/fix-macosx-segfault Fix macosx segfault --- src/_macosx.m | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index d3e3995ed308..7edca31a3dfa 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -138,7 +138,6 @@ static int wait_for_stdin(void) } NSEvent* event; - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; while (true) { while (true) { event = [NSApp nextEventMatchingMask: NSAnyEventMask @@ -151,7 +150,6 @@ static int wait_for_stdin(void) CFRunLoopRun(); if (interrupted || CFReadStreamHasBytesAvailable(stream)) break; } - [pool release]; if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); CFReadStreamUnscheduleFromRunLoop(stream, @@ -280,7 +278,6 @@ static void lazy_init(void) { PyOS_InputHook = wait_for_stdin; #endif - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WindowServerConnectionManager* connectionManager = [WindowServerConnectionManager sharedManager]; NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; NSNotificationCenter* notificationCenter = [workspace notificationCenter]; @@ -288,7 +285,6 @@ static void lazy_init(void) { selector: @selector(launch:) name: NSWorkspaceDidLaunchApplicationNotification object: nil]; - [pool release]; } static PyObject* @@ -366,11 +362,7 @@ static CGFloat _get_device_scale(CGContextRef cr) if(view) /* The figure may have been closed already */ { - /* Whereas drawRect creates its own autorelease pool, apparently - * [view display] also needs one. Create and release it here. */ - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [view display]; - [pool release]; } Py_RETURN_NONE; @@ -546,7 +538,6 @@ static CGFloat _get_device_scale(CGContextRef cr) close(channel[0]); } - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSDate* date = (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout] : [NSDate distantFuture]; @@ -558,7 +549,6 @@ static CGFloat _get_device_scale(CGContextRef cr) if (!event || [event type]==NSApplicationDefined) break; [NSApp sendEvent: event]; } - [pool release]; if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); @@ -731,7 +721,6 @@ static CGFloat _get_device_scale(CGContextRef cr) rect.size.height = height; rect.size.width = width; - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; self->window = [self->window initWithContentRect: rect styleMask: NSTitledWindowMask | NSClosableWindowMask @@ -749,7 +738,6 @@ static CGFloat _get_device_scale(CGContextRef cr) [window makeFirstResponder: view]; [[window contentView] addSubview: view]; - [pool release]; return 0; } @@ -766,9 +754,7 @@ static CGFloat _get_device_scale(CGContextRef cr) Window* window = self->window; if(window) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window close]; - [pool release]; } Py_TYPE(self)->tp_free((PyObject*)self); } @@ -779,10 +765,8 @@ static CGFloat _get_device_scale(CGContextRef cr) Window* window = self->window; if(window) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window makeKeyAndOrderFront: nil]; [window orderFrontRegardless]; - [pool release]; } Py_RETURN_NONE; } @@ -793,9 +777,7 @@ static CGFloat _get_device_scale(CGContextRef cr) Window* window = self->window; if(window) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [window close]; - [pool release]; self->window = NULL; } Py_RETURN_NONE; @@ -812,12 +794,10 @@ static CGFloat _get_device_scale(CGContextRef cr) Window* window = self->window; if(window) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* ns_title = [[[NSString alloc] initWithCString: title encoding: NSUTF8StringEncoding] autorelease]; [window setTitle: ns_title]; - [pool release]; } Py_RETURN_NONE; } @@ -829,13 +809,11 @@ static CGFloat _get_device_scale(CGContextRef cr) PyObject* result = NULL; if(window) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* title = [window title]; if (title) { const char* cTitle = [title UTF8String]; result = PyUnicode_FromString(cTitle); } - [pool release]; } if (result) { return result; @@ -1164,7 +1142,6 @@ -(void)save_figure:(id)sender if(!PyArg_ParseTuple(args, "s", &basedir)) return -1; - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSRect bounds = [view bounds]; NSWindow* window = [view window]; @@ -1265,8 +1242,6 @@ -(void)save_figure:(id)sender [messagebox release]; [[window contentView] display]; - [pool release]; - self->messagebox = messagebox; return 0; } @@ -1297,10 +1272,9 @@ -(void)save_figure:(id)sender NSText* messagebox = self->messagebox; if (messagebox) - { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + { NSString* text = [NSString stringWithUTF8String: message]; [messagebox setString: text]; - [pool release]; } Py_RETURN_NONE; @@ -2349,14 +2323,12 @@ - (int)index show(PyObject* self) { [NSApp activateIgnoringOtherApps: YES]; - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSArray *windowsArray = [NSApp windows]; NSEnumerator *enumerator = [windowsArray objectEnumerator]; NSWindow *window; while ((window = [enumerator nextObject])) { [window orderFront:nil]; } - [pool release]; Py_BEGIN_ALLOW_THREADS [NSApp run]; Py_END_ALLOW_THREADS From ca714c1fdebfd5876ede84010a2964fed9a134ea Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 22 Apr 2020 17:04:55 -0400 Subject: [PATCH 022/602] Merge pull request #17210 from anntzer/svgconv Fix missing attribute in _SVGConverter. --- lib/matplotlib/testing/compare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 022923d67ad0..4159cada94ab 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -211,9 +211,9 @@ def __call__(self, orig, dest): # Inkscape's output is not localized but gtk's is, so the output # stream probably has a mixed encoding. Using the filesystem # encoding should at least get the filenames right... - self._stderr.seek(0) + self._proc.stderr.seek(0) raise ImageComparisonFailure( - self._stderr.read().decode( + self._proc.stderr.read().decode( sys.getfilesystemencoding(), "replace")) From f52c17ea71fe1a26fa446e304e011c28160004cc Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 12 May 2020 00:09:34 -0400 Subject: [PATCH 023/602] Backport PR #17383: yticks: Fix typos --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 1d28260ef328..ce66e672a100 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1500,7 +1500,7 @@ def yticks(ticks=None, labels=None, **kwargs): Parameters ---------- ticks : array-like, optional - The list of xtick locations. Passing an empty list removes all xticks. + The list of ytick locations. Passing an empty list removes all yticks. labels : array-like, optional The labels to place at the given *ticks* locations. This argument can only be passed if *ticks* is passed as well. From 977ed43741423f960710bdb246d21b98d58b2fbd Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 22 Feb 2020 13:03:11 +0100 Subject: [PATCH 024/602] Don't duplicate docstrings of pyplot-level cmap setters. Dynamically generating these docstrings takes ~40us, which is negligible compared to the import time. flake8 had to be updated, as older versions incorrectly warned about the lack of blank lines between the one-liners. --- lib/matplotlib/pyplot.py | 240 +++++--------------------------- requirements/testing/flake8.txt | 2 +- tools/boilerplate.py | 16 +-- 3 files changed, 36 insertions(+), 222 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 120a888a4183..af3b1299665c 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1978,11 +1978,10 @@ def colormaps(): def _setup_pyplot_info_docstrings(): """ - Generate the plotting docstring. + Setup the docstring of `plotting` and of the colormap-setting functions. - These must be done after the entire module is imported, so it is - called from the end of this module, which is generated by - boilerplate.py. + These must be done after the entire module is imported, so it is called + from the end of this module, which is generated by boilerplate.py. """ commands = get_plot_commands() @@ -2018,6 +2017,15 @@ def _setup_pyplot_info_docstrings(): ] plotting.__doc__ = '\n'.join(lines) + for cm_name in colormaps(): + if cm_name in globals(): + globals()[cm_name].__doc__ = f""" + Set the colormap to {cm_name!r}. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + ## Plotting part 1: manually generated functions and wrappers ## @@ -2979,211 +2987,25 @@ def yscale(value, **kwargs): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def autumn(): - """ - Set the colormap to "autumn". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("autumn") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def bone(): - """ - Set the colormap to "bone". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("bone") +def autumn(): set_cmap('autumn') +def bone(): set_cmap('bone') +def cool(): set_cmap('cool') +def copper(): set_cmap('copper') +def flag(): set_cmap('flag') +def gray(): set_cmap('gray') +def hot(): set_cmap('hot') +def hsv(): set_cmap('hsv') +def jet(): set_cmap('jet') +def pink(): set_cmap('pink') +def prism(): set_cmap('prism') +def spring(): set_cmap('spring') +def summer(): set_cmap('summer') +def winter(): set_cmap('winter') +def magma(): set_cmap('magma') +def inferno(): set_cmap('inferno') +def plasma(): set_cmap('plasma') +def viridis(): set_cmap('viridis') +def nipy_spectral(): set_cmap('nipy_spectral') -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def cool(): - """ - Set the colormap to "cool". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("cool") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def copper(): - """ - Set the colormap to "copper". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("copper") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def flag(): - """ - Set the colormap to "flag". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("flag") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def gray(): - """ - Set the colormap to "gray". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("gray") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def hot(): - """ - Set the colormap to "hot". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("hot") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def hsv(): - """ - Set the colormap to "hsv". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("hsv") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def jet(): - """ - Set the colormap to "jet". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("jet") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def pink(): - """ - Set the colormap to "pink". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("pink") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def prism(): - """ - Set the colormap to "prism". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("prism") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def spring(): - """ - Set the colormap to "spring". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("spring") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def summer(): - """ - Set the colormap to "summer". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("summer") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def winter(): - """ - Set the colormap to "winter". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("winter") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def magma(): - """ - Set the colormap to "magma". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("magma") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def inferno(): - """ - Set the colormap to "inferno". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("inferno") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def plasma(): - """ - Set the colormap to "plasma". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("plasma") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def viridis(): - """ - Set the colormap to "viridis". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("viridis") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def nipy_spectral(): - """ - Set the colormap to "nipy_spectral". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("nipy_spectral") - _setup_pyplot_info_docstrings() diff --git a/requirements/testing/flake8.txt b/requirements/testing/flake8.txt index 9716a81c22c2..b5277c656632 100644 --- a/requirements/testing/flake8.txt +++ b/requirements/testing/flake8.txt @@ -1,5 +1,5 @@ # Extra pip requirements for the GitHub Actions flake8 build -flake8>=3.7 +flake8>=3.8 pydocstyle<4.0 flake8-docstrings diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 749209ba1414..98840d1f09f2 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -57,17 +57,7 @@ def {name}{signature}: return gcf().{called_name}{call} """ -# Used for colormap functions -CMAP_TEMPLATE = AUTOGEN_MSG + ''' -def {name}(): - """ - Set the colormap to "{name}". - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - set_cmap("{name}") -''' +CMAP_TEMPLATE = "def {name}(): set_cmap({name!r})\n" # Colormap functions. class value_formatter: @@ -310,6 +300,8 @@ def boilerplate_gen(): yield generate_function(name, f'Axes.{called_name}', template, sci_command=cmappable.get(name)) + yield AUTOGEN_MSG + yield '\n' cmaps = ( 'autumn', 'bone', @@ -335,7 +327,7 @@ def boilerplate_gen(): for name in cmaps: yield CMAP_TEMPLATE.format(name=name) - yield '\n' + yield '\n\n' yield '_setup_pyplot_info_docstrings()' From ce0d8c07766379ba3d65854ef84a6b8bd2d21757 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 13 May 2020 18:01:18 -0400 Subject: [PATCH 025/602] FIX: don't try to use non-standard functions on standard status bars closes #17085 --- lib/matplotlib/backends/backend_wx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index d74646defd19..46374ebdcb13 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1501,7 +1501,7 @@ def statbar(self): def set_message(self, s): status_bar = self.GetTopLevelParent().GetStatusBar() - if status_bar is not None: + if status_bar is not None and hasattr(status_bar, 'set_function'): status_bar.set_function(s) def set_history_buttons(self): From db91106447e46997e7c58c7d7f55a5827c84c32d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 14 May 2020 23:25:18 +0200 Subject: [PATCH 026/602] Reorder NavigationToolbar2 methods. This PR only moves methods around, and does not change any actual implementation code. Right now NavigationToolbar2 methods are implemented in a very haphazard order. In particular the successively called event handlers for interactive panning and zooming are not defined next to one another, which makes logic hard to follow. This PR moves `home`, `back` and `forward` next to one another (in the same order as they appear in the toolbar); `pan`, `press_pan`, `drag_pan`, and `release_pan` next to one another in that order; and likewise for `zoom`, `press_zoom`, `drag_zoom`, and `release_zoom`. --- lib/matplotlib/backend_bases.py | 160 ++++++++++++++++---------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 1a957b212e96..e0ec6f85852d 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2762,18 +2762,6 @@ def __init__(self, canvas): def set_message(self, s): """Display a message on toolbar or in status bar.""" - def back(self, *args): - """ - Move back up the view lim stack. - - For convenience of being directly connected as a GUI callback, which - often get passed additional parameters, this method accepts arbitrary - parameters, but does not use them. - """ - self._nav_stack.back() - self.set_history_buttons() - self._update_view() - def draw_rubberband(self, event, x0, y0, x1, y1): """ Draw a rectangle rubberband to indicate zoom limits. @@ -2784,27 +2772,39 @@ def draw_rubberband(self, event, x0, y0, x1, y1): def remove_rubberband(self): """Remove the rubberband.""" - def forward(self, *args): + def home(self, *args): """ - Move forward in the view lim stack. + Restore the original view. For convenience of being directly connected as a GUI callback, which often get passed additional parameters, this method accepts arbitrary parameters, but does not use them. """ - self._nav_stack.forward() + self._nav_stack.home() self.set_history_buttons() self._update_view() - def home(self, *args): + def back(self, *args): """ - Restore the original view. + Move back up the view lim stack. For convenience of being directly connected as a GUI callback, which often get passed additional parameters, this method accepts arbitrary parameters, but does not use them. """ - self._nav_stack.home() + self._nav_stack.back() + self.set_history_buttons() + self._update_view() + + def forward(self, *args): + """ + Move forward in the view lim stack. + + For convenience of being directly connected as a GUI callback, which + often get passed additional parameters, this method accepts arbitrary + parameters, but does not use them. + """ + self._nav_stack.forward() self.set_history_buttons() self._update_view() @@ -2908,6 +2908,14 @@ def _zoom_pan_handler(self, event): elif event.name == "button_release_event": self.release_zoom(event) + @cbook.deprecated("3.3") + def press(self, event): + """Called whenever a mouse button is pressed.""" + + @cbook.deprecated("3.3") + def release(self, event): + """Callback for mouse button release.""" + def pan(self, *args): """ Toggle the pan/zoom tool. @@ -2924,10 +2932,6 @@ def pan(self, *args): a.set_navigate_mode(self.mode) self.set_message(self.mode) - @cbook.deprecated("3.3") - def press(self, event): - """Called whenever a mouse button is pressed.""" - def press_pan(self, event): """Callback for mouse button press in pan/zoom mode.""" if event.button in [1, 3]: @@ -2955,6 +2959,49 @@ def press_pan(self, event): if press is not None: press(event) + def drag_pan(self, event): + """Callback for dragging in pan/zoom mode.""" + for a, ind in self._xypress: + #safer to use the recorded button at the press than current button: + #multiple button can get pressed during motion... + a.drag_pan(self._button_pressed, event.key, event.x, event.y) + self.canvas.draw_idle() + + def release_pan(self, event): + """Callback for mouse button release in pan/zoom mode.""" + + if self._button_pressed is None: + return + self.canvas.mpl_disconnect(self._id_drag) + self._id_drag = self.canvas.mpl_connect( + 'motion_notify_event', self.mouse_move) + for a, ind in self._xypress: + a.end_pan() + if not self._xypress: + return + self._xypress = [] + self._button_pressed = None + self.push_current() + release = cbook._deprecate_method_override( + __class__.press, self, since="3.3", message="Calling an " + "overridden release() at pan stop is deprecated since %(since)s " + "and will be removed %(removal)s; override release_pan() instead.") + if release is not None: + release(event) + self._draw() + + def zoom(self, *args): + """Toggle zoom to rect mode.""" + if self.mode == _Mode.ZOOM: + self.mode = _Mode.NONE + self.canvas.widgetlock.release(self) + else: + self.mode = _Mode.ZOOM + self.canvas.widgetlock(self) + for a in self.canvas.figure.get_axes(): + a.set_navigate_mode(self.mode) + self.set_message(self.mode) + def press_zoom(self, event): """Callback for mouse button press in zoom to rect mode.""" if event.button not in [1, 3]: @@ -2982,52 +3029,6 @@ def press_zoom(self, event): if press is not None: press(event) - def push_current(self): - """Push the current view limits and position onto the stack.""" - self._nav_stack.push( - WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.canvas.figure.axes})) - self.set_history_buttons() - - @cbook.deprecated("3.3") - def release(self, event): - """Callback for mouse button release.""" - - def release_pan(self, event): - """Callback for mouse button release in pan/zoom mode.""" - - if self._button_pressed is None: - return - self.canvas.mpl_disconnect(self._id_drag) - self._id_drag = self.canvas.mpl_connect( - 'motion_notify_event', self.mouse_move) - for a, ind in self._xypress: - a.end_pan() - if not self._xypress: - return - self._xypress = [] - self._button_pressed = None - self.push_current() - release = cbook._deprecate_method_override( - __class__.press, self, since="3.3", message="Calling an " - "overridden release() at pan stop is deprecated since %(since)s " - "and will be removed %(removal)s; override release_pan() instead.") - if release is not None: - release(event) - self._draw() - - def drag_pan(self, event): - """Callback for dragging in pan/zoom mode.""" - for a, ind in self._xypress: - #safer to use the recorded button at the press than current button: - #multiple button can get pressed during motion... - a.drag_pan(self._button_pressed, event.key, event.x, event.y) - self.canvas.draw_idle() - def drag_zoom(self, event): """Callback for dragging in zoom mode.""" start_xy = self._zoom_info["start_xy"] @@ -3093,6 +3094,17 @@ def release_zoom(self, event): if release is not None: release(event) + def push_current(self): + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + {ax: (ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), + ax.get_position().frozen())) + for ax in self.canvas.figure.axes})) + self.set_history_buttons() + @cbook.deprecated("3.3", alternative="toolbar.canvas.draw_idle()") def draw(self): """Redraw the canvases, update the locators.""" @@ -3153,18 +3165,6 @@ def update(self): self._nav_stack.clear() self.set_history_buttons() - def zoom(self, *args): - """Toggle zoom to rect mode.""" - if self.mode == _Mode.ZOOM: - self.mode = _Mode.NONE - self.canvas.widgetlock.release(self) - else: - self.mode = _Mode.ZOOM - self.canvas.widgetlock(self) - for a in self.canvas.figure.get_axes(): - a.set_navigate_mode(self.mode) - self.set_message(self.mode) - def set_history_buttons(self): """Enable or disable the back/forward button.""" From 404e0ffd6376dbc6948284d4938c6368162760d1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 15 May 2020 20:28:30 -0400 Subject: [PATCH 027/602] Backport PR #17422: Unstale viewlims before draw()ing polar axes. Merge pull request #17422 from anntzer/polarunstale Unstale viewlims before draw()ing polar axes. Conflicts: lib/matplotlib/projections/polar.py - conflicts from nearyby changes to signature of draw lib/matplotlib/tests/test_polar.py - tests are still in test_axes.py on v3.2.x --- lib/matplotlib/projections/polar.py | 1 + lib/matplotlib/tests/test_axes.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 0f11abdbe119..06aa9be3ea44 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -953,6 +953,7 @@ def get_yaxis_text2_transform(self, pad): return self._yaxis_text_transform + pad_shift, 'center', halign def draw(self, *args, **kwargs): + self._unstale_viewLim() thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) if thetamin > thetamax: thetamin, thetamax = thetamax, thetamin diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 93548b2791ca..3ec602f84c4d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -750,7 +750,11 @@ def test_polar_invertedylim(): def test_polar_invertedylim_rorigin(): fig = plt.figure() ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) - ax.set_ylim(2, 0) + ax.yaxis.set_inverted(True) + # Set the rlims to inverted (2, 0) without calling set_rlim, to check that + # viewlims are correctly unstaled before draw()ing. + ax.plot([0, 0], [0, 2], c="none") + ax.margins(0) ax.set_rorigin(3) From 83f963a06677363e3f1b1fd88b52e18ba6c194e9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 17 May 2020 19:22:29 -0400 Subject: [PATCH 028/602] STY: remove unneeded fstring --- examples/statistics/confidence_ellipse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/statistics/confidence_ellipse.py b/examples/statistics/confidence_ellipse.py index 2db715237d57..ef26845886d3 100644 --- a/examples/statistics/confidence_ellipse.py +++ b/examples/statistics/confidence_ellipse.py @@ -217,7 +217,7 @@ def get_correlated_dataset(n, dependency, mu, scale): ax_kwargs.scatter(x, y, s=0.5) ax_kwargs.scatter(mu[0], mu[1], c='red', s=3) -ax_kwargs.set_title(f'Using kwargs') +ax_kwargs.set_title('Using kwargs') fig.subplots_adjust(hspace=0.25) plt.show() From 2f41868de465b86d2fc357f7ed58ff323d58030f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 13 May 2020 20:19:43 -0400 Subject: [PATCH 029/602] FIX: cancel pending autoscale on manually setting limits closes #17331 --- lib/matplotlib/axes/_base.py | 6 ++++++ lib/matplotlib/tests/test_axes.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index aec40541557c..30376c24280c 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3332,6 +3332,9 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, left, right = sorted([left, right], reverse=bool(reverse)) self._viewLim.intervalx = (left, right) + # Mark viewlims as no longer stale without triggering an autoscale. + for ax in self._shared_x_axes.get_siblings(self): + ax._stale_viewlim_x = False if auto is not None: self._autoscaleXon = bool(auto) @@ -3601,6 +3604,9 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, bottom, top = sorted([bottom, top], reverse=bool(reverse)) self._viewLim.intervaly = (bottom, top) + # Mark viewlims as no longer stale without triggering an autoscale. + for ax in self._shared_y_axes.get_siblings(self): + ax._stale_viewlim_y = False if auto is not None: self._autoscaleYon = bool(auto) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b7cee7b6071b..586808d95e65 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6213,6 +6213,36 @@ def test_ytickcolor_is_not_markercolor(): assert tick.tick1line.get_markeredgecolor() != 'white' +@pytest.mark.parametrize('auto', (True, False, None)) +def test_unautoscaley(auto): + fig, ax = plt.subplots() + x = np.arange(100) + y = np.linspace(-.1, .1, 100) + ax.scatter(x, y) + + post_auto = ax.get_autoscaley_on() if auto is None else auto + + ax.set_ylim((-.5, .5), auto=auto) + assert post_auto == ax.get_autoscaley_on() + fig.canvas.draw() + assert_array_equal(ax.get_ylim(), (-.5, .5)) + + +@pytest.mark.parametrize('auto', (True, False, None)) +def test_unautoscalex(auto): + fig, ax = plt.subplots() + x = np.arange(100) + y = np.linspace(-.1, .1, 100) + ax.scatter(y, x) + + post_auto = ax.get_autoscalex_on() if auto is None else auto + + ax.set_xlim((-.5, .5), auto=auto) + assert post_auto == ax.get_autoscalex_on() + fig.canvas.draw() + assert_array_equal(ax.get_xlim(), (-.5, .5)) + + @check_figures_equal(extensions=["png"]) def test_polar_interpolation_steps_variable_r(fig_test, fig_ref): l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2]) From 0bb70bfcad28df0594c069f769d9a4b4c6bd115d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 19 May 2020 23:09:17 -0400 Subject: [PATCH 030/602] Backport PR #17391: tk/wx: Fix saving after the window is closed --- lib/matplotlib/backends/_backend_tk.py | 8 ++++-- lib/matplotlib/backends/backend_wx.py | 22 ++++++++------- .../tests/test_backends_interactive.py | 27 ++++++++++++++++--- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index c14ee7262933..5c1716ec2477 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -538,8 +538,12 @@ def release(self, event): def set_cursor(self, cursor): window = self.canvas.get_tk_widget().master - window.configure(cursor=cursord[cursor]) - window.update_idletasks() + try: + window.configure(cursor=cursord[cursor]) + except tkinter.TclError: + pass + else: + window.update_idletasks() def _Button(self, text, file, command, extension='.gif'): img_file = str(cbook._get_data_path('images', file + extension)) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 46374ebdcb13..a579b73bf94e 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -702,7 +702,9 @@ def gui_repaint(self, drawDC=None, origin='WX'): The 'WXAgg' backend sets origin accordingly. """ DEBUG_MSG("gui_repaint()", 1, self) - if self.IsShownOnScreen(): + # The "if self" check avoids a "wrapped C/C++ object has been deleted" + # RuntimeError if doing things after window is closed. + if self and self.IsShownOnScreen(): if not drawDC: # not called from OnPaint use a ClientDC drawDC = wx.ClientDC(self) @@ -978,14 +980,11 @@ def _print_image(self, filename, filetype, *args, **kwargs): # Now that we have rendered into the bitmap, save it to the appropriate # file type and clean up. - if isinstance(filename, str): - if not image.SaveFile(filename, filetype): - raise RuntimeError(f'Could not save figure to {filename}') - elif cbook.is_writable_file_like(filename): - if not isinstance(image, wx.Image): - image = image.ConvertToImage() - if not image.SaveStream(filename, filetype): - raise RuntimeError(f'Could not save figure to {filename}') + if (cbook.is_writable_file_like(filename) and + not isinstance(image, wx.Image)): + image = image.ConvertToImage() + if not image.SaveFile(filename, filetype): + raise RuntimeError(f'Could not save figure to {filename}') # Restore everything to normal self.bitmap = origBitmap @@ -997,7 +996,10 @@ def _print_image(self, filename, filetype, *args, **kwargs): # otherwise. if self._isDrawn: self.draw() - self.Refresh() + # The "if self" check avoids a "wrapped C/C++ object has been deleted" + # RuntimeError if doing things after window is closed. + if self: + self.Refresh() ######################################################################## diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index bc3796a41747..7465bb9197e2 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -52,6 +52,7 @@ def _get_testable_interactive_backends(): _test_script = """\ import importlib import importlib.util +import io import sys from unittest import TestCase @@ -107,7 +108,23 @@ def check_alt_backend(alt_backend): # Trigger quitting upon draw. fig.canvas.mpl_connect("draw_event", lambda event: timer.start()) +result = io.BytesIO() +fig.savefig(result, format='png') + plt.show() + +# Ensure that the window is really closed. +plt.pause(0.5) + +# Test that saving works after interactive window is closed, but the figure is +# not deleted. +result_after = io.BytesIO() +fig.savefig(result_after, format='png') + +if not backend.startswith('qt5') and sys.platform == 'darwin': + # FIXME: This should be enabled everywhere once Qt5 is fixed on macOS to + # not resize incorrectly. + assert_equal(result.getvalue(), result_after.getvalue()) """ _test_timeout = 10 # Empirically, 1s is not enough on Travis. @@ -115,9 +132,10 @@ def check_alt_backend(alt_backend): @pytest.mark.parametrize("backend", _get_testable_interactive_backends()) @pytest.mark.flaky(reruns=3) def test_interactive_backend(backend): - proc = subprocess.run([sys.executable, "-c", _test_script], - env={**os.environ, "MPLBACKEND": backend}, - timeout=_test_timeout) + proc = subprocess.run( + [sys.executable, "-c", _test_script], + env={**os.environ, "MPLBACKEND": backend, "SOURCE_DATE_EPOCH": "0"}, + timeout=_test_timeout) if proc.returncode: pytest.fail("The subprocess returned with non-zero exit status " f"{proc.returncode}.") @@ -129,7 +147,8 @@ def test_interactive_backend(backend): def test_webagg(): pytest.importorskip("tornado") proc = subprocess.Popen([sys.executable, "-c", _test_script], - env={**os.environ, "MPLBACKEND": "webagg"}) + env={**os.environ, "MPLBACKEND": "webagg", + "SOURCE_DATE_EPOCH": "0"}) url = "http://{}:{}".format( mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"]) timeout = time.perf_counter() + _test_timeout From 7977b5fb82f77299c9ad129dede3216d237b4436 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 May 2020 02:58:42 -0400 Subject: [PATCH 031/602] Fix leak of input mesh during image resampling. Fixes #15474. --- src/_image_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index a4c0e81db9ad..4c2eb0041400 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -85,7 +85,7 @@ _get_transform_mesh(PyObject *py_affine, npy_intp *dims) PyObject *output_mesh = PyObject_CallMethod( py_inverse, (char *)"transform", (char *)"O", - (char *)input_mesh.pyobj(), NULL); + (char *)input_mesh.pyobj_steal(), NULL); Py_DECREF(py_inverse); From ae0046890f81b2fc2ac8bf57e2d4f13b6247e3d5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 May 2020 17:00:31 -0400 Subject: [PATCH 032/602] Fix leaking objects when saving PNG metadata. --- src/_png.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 0cc883df658e..1d0cfd41b1c2 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -139,11 +139,6 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) numpy::array_view buffer; PyObject *filein; PyObject *metadata = NULL; - PyObject *meta_key, *meta_val; - png_text *text; - Py_ssize_t pos = 0; - int meta_pos = 0; - Py_ssize_t meta_size; double dpi = 0; int compression = 6; int filter = -1; @@ -274,8 +269,14 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) PyErr_SetString(PyExc_TypeError, "metadata must be a dict or None"); goto exit; } - meta_size = PyDict_Size(metadata); - text = new png_text[meta_size]; + + Py_ssize_t meta_size = PyDict_Size(metadata); + png_text *text = new png_text[meta_size]; + PyObject *meta_key, *meta_val; + Py_ssize_t pos = 0; + int meta_pos = 0; + std::vector temp_strs; + temp_strs.reserve(meta_size); while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; @@ -283,6 +284,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "latin_1", "strict"); if (temp_key != NULL) { text[meta_pos].key = PyBytes_AsString(temp_key); + temp_strs.push_back(temp_key); } } else if (PyBytes_Check(meta_key)) { text[meta_pos].key = PyBytes_AsString(meta_key); @@ -295,6 +297,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "latin_1", "strict"); if (temp_val != NULL) { text[meta_pos].text = PyBytes_AsString(temp_val); + temp_strs.push_back(temp_val); } } else if (PyBytes_Check(meta_val)) { text[meta_pos].text = PyBytes_AsString(meta_val); @@ -307,6 +310,10 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) meta_pos++; } png_set_text(png_ptr, info_ptr, text, meta_size); + for (std::vector::iterator it = temp_strs.begin(); + it != temp_strs.end(); ++it) { + Py_DECREF(*it); + } delete[] text; } #endif From 8e13db1b904318e85bfab56eeec5213edf4f1745 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 20 May 2020 17:01:23 -0400 Subject: [PATCH 033/602] Fix cleanup of PNG structures on error. If `info_ptr` isn't created, we should still destroy `png_ptr`. --- src/_png.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 1d0cfd41b1c2..b1bd5a3b8c64 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -346,8 +346,12 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) png_write_end(png_ptr, info_ptr); exit: - if (png_ptr && info_ptr) { - png_destroy_write_struct(&png_ptr, &info_ptr); + if (png_ptr) { + if (info_ptr) { + png_destroy_write_struct(&png_ptr, &info_ptr); + } else { + png_destroy_write_struct(&png_ptr, NULL); + } } if (PyErr_Occurred()) { Py_XDECREF(buff.str); From 871bbfce25d600e7e0b5c138ba3a53cc4911a769 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sat, 23 May 2020 11:06:06 -1000 Subject: [PATCH 034/602] Backport PR #17499: Fix scatter singlecolor Merge pull request #17499 from tacaswell/fix_scatter_singlecolor Fix scatter singlecolor Conflicts: lib/matplotlib/axes/_axes.py - implicitly backport some extra docstring changes --- lib/matplotlib/axes/_axes.py | 32 ++++++++++++++++++++----------- lib/matplotlib/tests/test_axes.py | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f1c5e2c97c3e..c89e5cba8bf6 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4212,19 +4212,26 @@ def invalid_shape_exception(csize, xsize): except ValueError: pass # Failed to convert to float array; must be color specs. else: + # handle the documented special case of a 2D array with 1 + # row which as RGB(A) to broadcast. + if c.shape == (1, 4) or c.shape == (1, 3): + c_is_mapped = False + if c.size != xsize: + valid_shape = False # If c can be either mapped values or a RGB(A) color, prefer # the former if shapes match, the latter otherwise. - if c.size == xsize: + elif c.size == xsize: c = c.ravel() c_is_mapped = True else: # Wrong size; it must not be intended for mapping. if c.shape in ((3,), (4,)): _log.warning( - "'c' argument looks like a single numeric RGB or " + "*c* argument looks like a single numeric RGB or " "RGBA sequence, which should be avoided as value-" "mapping will have precedence in case its length " - "matches with 'x' & 'y'. Please use a 2-D array " - "with a single row if you really want to specify " + "matches with *x* & *y*. Please use the *color* " + "keyword-argument or provide a 2-D array " + "with a single row if you intend to specify " "the same RGB or RGBA value for all points.") valid_shape = False if not c_is_mapped: @@ -4268,14 +4275,14 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, The marker size in points**2. Default is ``rcParams['lines.markersize'] ** 2``. - c : color, sequence, or sequence of colors, optional - The marker color. Possible values: + c : array-like or list of colors or color, optional + The marker colors. Possible values: - - A single color format string. - - A sequence of colors of length n. - A scalar or sequence of n numbers to be mapped to colors using *cmap* and *norm*. - A 2-D array in which the rows are RGB or RGBA. + - A sequence of colors of length n. + - A single color format string. Note that *c* should not be a single numeric RGB or RGBA sequence because that is indistinguishable from an array of values to be @@ -4284,9 +4291,12 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, matching will have precedence in case of a size matching with *x* and *y*. - Defaults to ``None``. In that case the marker color is determined - by the value of ``color``, ``facecolor`` or ``facecolors``. In case - those are not specified or ``None``, the marker color is determined + If you wish to specify a single color for all points + prefer the *color* keyword argument. + + Defaults to `None`. In that case the marker color is determined + by the value of *color*, *facecolor* or *facecolors*. In case + those are not specified or `None`, the marker color is determined by the next color of the ``Axes``' current "shape and fill" color cycle. This cycle defaults to :rc:`axes.prop_cycle`. diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3ec602f84c4d..69b460a7e5f0 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2088,6 +2088,22 @@ def get_next_color(): c=c_case, edgecolors="black", kwargs={}, xsize=xsize, get_next_color_func=get_next_color) + @pytest.mark.style('default') + @check_figures_equal(extensions=["png"]) + def test_scatter_single_color_c(self, fig_test, fig_ref): + rgb = [[1, 0.5, 0.05]] + rgba = [[1, 0.5, 0.05, .5]] + + # set via color kwarg + ax_ref = fig_ref.subplots() + ax_ref.scatter(np.ones(3), range(3), color=rgb) + ax_ref.scatter(np.ones(4)*2, range(4), color=rgba) + + # set via broadcasting via c + ax_test = fig_test.subplots() + ax_test.scatter(np.ones(3), range(3), c=rgb) + ax_test.scatter(np.ones(4)*2, range(4), c=rgba) + def _params(c=None, xsize=2, **kwargs): edgecolors = kwargs.pop('edgecolors', None) From 7aad47a379bf00da00a5e97df2156f609cd7ac3e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 17 Oct 2019 15:23:48 +0200 Subject: [PATCH 035/602] Reuse png metadata handling of imsave() in FigureCanvasAgg.print_png(). This avoids duplicating the conversion of metadata to PngInfo and revealed a bug in the priority between `metadata` and `pil_kwargs` in imsave(). Note that because `np.asarray(self.buffer_rgba())` is already a RGBA uint8 array, there is no colormapping step happening in imsave(). Ideally mplcairo should also be able to use imsave() for saving to png. --- lib/matplotlib/backends/backend_agg.py | 17 ++++------------- lib/matplotlib/image.py | 6 ++++-- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index bae911aa6cac..e45fcb104881 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -33,7 +33,6 @@ import numpy as np from PIL import Image -from PIL.PngImagePlugin import PngInfo import matplotlib as mpl from matplotlib import cbook @@ -502,24 +501,16 @@ def print_png(self, filename_or_obj, *args, if metadata is None: metadata = {} - if pil_kwargs is None: - pil_kwargs = {} metadata = { "Software": f"matplotlib version{mpl.__version__}, http://matplotlib.org/", **metadata, } FigureCanvasAgg.draw(self) - # Only use the metadata kwarg if pnginfo is not set, because the - # semantics of duplicate keys in pnginfo is unclear. - if "pnginfo" not in pil_kwargs: - pnginfo = PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format="png", **pil_kwargs)) + mpl.image.imsave( + filename_or_obj, np.asarray(self.buffer_rgba()), format="png", + origin="upper", dpi=self.figure.dpi, + metadata=metadata, pil_kwargs=pil_kwargs) def print_to_buffer(self): FigureCanvasAgg.draw(self) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 1608db9f5090..3f558d1ab839 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1520,8 +1520,10 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, pil_shape = (rgba.shape[1], rgba.shape[0]) image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) - if format == "png" and metadata is not None: - # cf. backend_agg's print_png. + if (format == "png" + and metadata is not None and "pnginfo" not in pil_kwargs): + # Only use the metadata kwarg if pnginfo is not set, because the + # semantics of duplicate keys in pnginfo is unclear. pnginfo = PIL.PngImagePlugin.PngInfo() for k, v in metadata.items(): pnginfo.add_text(k, v) From 699a03b9af47a61dc04ce0a8856d537489f51324 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 25 May 2020 22:11:51 -0400 Subject: [PATCH 036/602] Fix exception handling in FT2Font init. It needs to return -1 when an exception is set, or Python raises a `SystemError` instead. --- lib/matplotlib/tests/test_font_manager.py | 20 +++++++++++++++++++- src/ft2font_wrapper.cpp | 6 +++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index b78be5d4930d..bc2bb9c8a3e4 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -1,4 +1,4 @@ -from io import BytesIO +from io import BytesIO, StringIO import multiprocessing import os from pathlib import Path @@ -128,6 +128,24 @@ def test_find_ttc(): fig.savefig(BytesIO(), format="ps") +def test_find_invalid(tmpdir): + tmp_path = Path(tmpdir) + + with pytest.raises(FileNotFoundError): + get_font(tmp_path / 'non-existent-font-name.ttf') + + with pytest.raises(FileNotFoundError): + get_font(str(tmp_path / 'non-existent-font-name.ttf')) + + with pytest.raises(FileNotFoundError): + get_font(bytes(tmp_path / 'non-existent-font-name.ttf')) + + # Not really public, but get_font doesn't expose non-filename constructor. + from matplotlib.ft2font import FT2Font + with pytest.raises(TypeError, match='path or binary-mode file'): + FT2Font(StringIO()) + + @pytest.mark.skipif(sys.platform != 'linux', reason='Linux only') def test_user_fonts_linux(tmpdir, monkeypatch): font_test_file = 'mpltest.ttf' diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 3f84088af2ea..102cfbda3dac 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -488,12 +488,14 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) || !PyBytes_Check(data)) { PyErr_SetString(PyExc_TypeError, "First argument must be a path or binary-mode file object"); + Py_CLEAR(data); goto exit; } else { self->py_file = filename; self->stream.close = NULL; Py_INCREF(filename); } + Py_CLEAR(data); CALL_CPP_FULL( "FT2Font", (self->x = new FT2Font(open_args, hinting_factor)), @@ -505,9 +507,7 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) self->fname = filename; exit: - Py_XDECREF(data); - - return 0; + return PyErr_Occurred() ? -1 : 0; } static void PyFT2Font_dealloc(PyFT2Font *self) From 18e3b2a36f9661727e8c21981d2f3cb6400c9955 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 20 Apr 2020 23:43:23 -0400 Subject: [PATCH 037/602] Circle: Convert config into re-usable command style. This allows embedding multiple steps into a command. --- .circleci/config.yml | 201 +++++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 92 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 87388469caba..6d9f3430abcf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,76 +4,95 @@ version: 2.1 -########################################### -# Define some common steps as YAML anchors. +####################################### +# Define some common steps as commands. # -apt-run: &apt-install - name: Install apt packages - command: | - sudo apt -qq update - sudo apt install -y \ - inkscape \ - ffmpeg \ - dvipng \ - lmodern \ - cm-super \ - texlive-latex-base \ - texlive-latex-extra \ - texlive-fonts-recommended \ - texlive-latex-recommended \ - texlive-pictures \ - texlive-xetex \ - graphviz \ - fonts-crosextra-carlito \ - fonts-freefont-otf \ - fonts-humor-sans \ - optipng - -fonts-run: &fonts-install - name: Install custom fonts - command: | - mkdir -p ~/.local/share/fonts - wget -nc https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O ~/.local/share/fonts/Felipa-Regular.ttf || true - fc-cache -f -v - save_cache: - key: fonts-2 - paths: - - ~/.local/share/fonts/ - restore_cache: - key: fonts-2 - -pip-run: &pip-install - # Upgrade pip and setuptools and wheel to get as clean an install as possible - name: Upgrade pip, setuptools, wheel - command: | - python -mpip install --upgrade --user pip - python -mpip install --upgrade --user wheel - python -mpip install --upgrade --user setuptools - -deps-run: &deps-install - name: Install Python dependencies - command: | - python -mpip install --user numpy${NUMPY_VERSION} codecov coverage - python -mpip install --user -r requirements/doc/doc-requirements.txt - -mpl-run: &mpl-install - name: Install Matplotlib - command: python -mpip install --user -ve . - -doc-run: &doc-build - name: Build documentation - command: | - # Set epoch to date of latest tag. - export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" - make html O=-T - rm -r build/html/_sources - working_directory: doc - -doc-bundle-run: &doc-bundle - name: Bundle sphinx-gallery documentation artifacts - command: tar cf doc/build/sphinx-gallery-files.tar.gz doc/api/_as_gen doc/gallery doc/tutorials - when: always +commands: + apt-install: + steps: + - run: + name: Install apt packages + command: | + sudo apt -qq update + sudo apt install -y \ + inkscape \ + ffmpeg \ + dvipng \ + lmodern \ + cm-super \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-latex-recommended \ + texlive-pictures \ + texlive-xetex \ + graphviz \ + fonts-crosextra-carlito \ + fonts-freefont-otf \ + fonts-humor-sans \ + optipng + + fonts-install: + steps: + - run: + name: Install custom fonts + command: | + mkdir -p ~/.local/share/fonts + wget -nc https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O ~/.local/share/fonts/Felipa-Regular.ttf || true + fc-cache -f -v + save_cache: + key: fonts-2 + paths: + - ~/.local/share/fonts/ + restore_cache: + key: fonts-2 + + pip-install: + description: Upgrade pip and setuptools and wheel to get as clean an install as possible + steps: + - run: + name: Upgrade pip, setuptools, wheel + command: | + python -mpip install --upgrade --user pip + python -mpip install --upgrade --user wheel + python -mpip install --upgrade --user setuptools + + deps-install: + parameters: + numpy_version: + type: string + default: "" + steps: + - run: + name: Install Python dependencies + command: | + python -mpip install --user numpy<< parameters.numpy_version >> codecov coverage + python -mpip install --user -r requirements/doc/doc-requirements.txt + + mpl-install: + steps: + - run: + name: Install Matplotlib + command: python -mpip install --user -ve . + + doc-build: + steps: + - run: + name: Build documentation + command: | + # Set epoch to date of latest tag. + export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" + make html O=-T + rm -r build/html/_sources + working_directory: doc + + doc-bundle: + steps: + - run: + name: Bundle sphinx-gallery documentation artifacts + command: tar cf doc/build/sphinx-gallery-files.tar.gz doc/api/_as_gen doc/gallery doc/tutorials + when: always ########################################## @@ -87,18 +106,16 @@ jobs: steps: - checkout - - run: *apt-install - - run: *fonts-install - - run: *pip-install - - run: - <<: *deps-install - environment: - NUMPY_VERSION: "==1.13.0" - - run: *mpl-install + - apt-install + - fonts-install + - pip-install + - deps-install: + numpy_version: "==1.13.0" + - mpl-install - - run: *doc-build + - doc-build - - run: *doc-bundle + - doc-bundle - store_artifacts: path: doc/build/sphinx-gallery-files.tar.gz @@ -111,16 +128,16 @@ jobs: steps: - checkout - - run: *apt-install - - run: *fonts-install - - run: *pip-install + - apt-install + - fonts-install + - pip-install - - run: *deps-install - - run: *mpl-install + - deps-install + - mpl-install - - run: *doc-build + - doc-build - - run: *doc-bundle + - doc-bundle - store_artifacts: path: doc/build/sphinx-gallery-files.tar.gz @@ -133,16 +150,16 @@ jobs: steps: - checkout - - run: *apt-install - - run: *fonts-install - - run: *pip-install + - apt-install + - fonts-install + - pip-install - - run: *deps-install - - run: *mpl-install + - deps-install + - mpl-install - - run: *doc-build + - doc-build - - run: *doc-bundle + - doc-bundle - store_artifacts: path: doc/build/sphinx-gallery-files.tar.gz From ca1c5d9f2119fafd5da8703ea0caafb7c342159e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 17 Apr 2020 21:14:45 -0400 Subject: [PATCH 038/602] Fix caching of fonts in CircleCI builds. The `save_cache` and `restore_cache` entries need to be separate steps, not keys on the `run` step. --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d9f3430abcf..4f8b59221646 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,18 +35,18 @@ commands: fonts-install: steps: + - restore_cache: + key: fonts-2 - run: name: Install custom fonts command: | mkdir -p ~/.local/share/fonts wget -nc https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O ~/.local/share/fonts/Felipa-Regular.ttf || true fc-cache -f -v - save_cache: - key: fonts-2 - paths: - - ~/.local/share/fonts/ - restore_cache: - key: fonts-2 + - save_cache: + key: fonts-2 + paths: + - ~/.local/share/fonts/ pip-install: description: Upgrade pip and setuptools and wheel to get as clean an install as possible From e9a1733afe06f9bf1b861821a70a99aa62b9daea Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 20 Apr 2020 23:47:17 -0400 Subject: [PATCH 039/602] Circle: Remove redundant store_artifacts steps. With re-usable commands, we can put those steps onto the command instead of in each job. --- .circleci/config.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f8b59221646..735742575a37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,6 +93,8 @@ commands: name: Bundle sphinx-gallery documentation artifacts command: tar cf doc/build/sphinx-gallery-files.tar.gz doc/api/_as_gen doc/gallery doc/tutorials when: always + - store_artifacts: + path: doc/build/sphinx-gallery-files.tar.gz ########################################## @@ -116,8 +118,6 @@ jobs: - doc-build - doc-bundle - - store_artifacts: - path: doc/build/sphinx-gallery-files.tar.gz - store_artifacts: path: doc/build/html @@ -138,8 +138,6 @@ jobs: - doc-build - doc-bundle - - store_artifacts: - path: doc/build/sphinx-gallery-files.tar.gz - store_artifacts: path: doc/build/html @@ -160,8 +158,6 @@ jobs: - doc-build - doc-bundle - - store_artifacts: - path: doc/build/sphinx-gallery-files.tar.gz - store_artifacts: path: doc/build/html From 4d656a5d97f594ee5744f1e961221e32328048c8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 21 Apr 2020 00:25:08 -0400 Subject: [PATCH 040/602] Cache built doctrees on CircleCI. This probably doesn't save much time, but will save the intersphinx files in a cache that will hopefully reduce download errors. --- .circleci/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 735742575a37..f0201de420c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,6 +78,10 @@ commands: doc-build: steps: + - restore_cache: + keys: + - sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} + - sphinx-env-v1-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}-{{ .Environment.CIRCLE_JOB }} - run: name: Build documentation command: | @@ -86,6 +90,10 @@ commands: make html O=-T rm -r build/html/_sources working_directory: doc + - save_cache: + key: sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} + paths: + - doc/build/doctrees doc-bundle: steps: From 853358bfbaff90e0fe8c6a9025961aa40bf3d316 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 27 May 2020 21:02:24 -0400 Subject: [PATCH 041/602] Privatize ttconv module. It's an internal detail, much like `_qhull`, and not exposed externally. --- doc/api/api_changes_3.3/deprecations.rst | 4 ++++ lib/matplotlib/backends/backend_pdf.py | 4 ++-- lib/matplotlib/backends/backend_ps.py | 2 +- lib/matplotlib/ttconv.py | 9 +++++++++ setupext.py | 2 +- src/_ttconv.cpp | 2 +- 6 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 lib/matplotlib/ttconv.py diff --git a/doc/api/api_changes_3.3/deprecations.rst b/doc/api/api_changes_3.3/deprecations.rst index 1d5778d7206a..70595495a4a0 100644 --- a/doc/api/api_changes_3.3/deprecations.rst +++ b/doc/api/api_changes_3.3/deprecations.rst @@ -589,3 +589,7 @@ implementers). Passing ``ismath="TeX!"`` to `.RendererAgg.get_text_width_height_descent` is deprecated. Pass ``ismath="TeX"`` instead, consistently with other low-level APIs which support the values True, False, and "TeX" for ``ismath``. + +``matplotlib.ttconv`` +~~~~~~~~~~~~~~~~~~~~~ +This module is deprecated. diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 26af627a525e..b03bd794a8c7 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -41,7 +41,7 @@ from matplotlib.path import Path from matplotlib.dates import UTC from matplotlib import _path -from matplotlib import ttconv +from matplotlib import _ttconv from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -996,7 +996,7 @@ def get_char_width(charcode): # Make the charprocs array (using ttconv to generate the # actual outlines) try: - rawcharprocs = ttconv.get_pdf_charprocs( + rawcharprocs = _ttconv.get_pdf_charprocs( os.fsencode(filename), glyph_ids) except RuntimeError: _log.warning("The PDF backend does not currently support the " diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 7a9c7b817ad7..74bbccc77b9d 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -26,7 +26,7 @@ from matplotlib.cbook import is_writable_file_like, file_requires_unicode from matplotlib.font_manager import is_opentype_cff_font, get_font from matplotlib.ft2font import LOAD_NO_HINTING -from matplotlib.ttconv import convert_ttf_to_ps +from matplotlib._ttconv import convert_ttf_to_ps from matplotlib.mathtext import MathTextParser from matplotlib._mathtext_data import uni2type1 from matplotlib.path import Path diff --git a/lib/matplotlib/ttconv.py b/lib/matplotlib/ttconv.py new file mode 100644 index 000000000000..a2e11ef4e4ee --- /dev/null +++ b/lib/matplotlib/ttconv.py @@ -0,0 +1,9 @@ +""" +Converting and subsetting TrueType fonts to PS types 3 and 42, and PDF type 3. +""" + +from . import cbook +from ._ttconv import convert_ttf_to_ps, get_pdf_charprocs # noqa + + +cbook.warn_deprecated('3.3', name=__name__, obj_type='module') diff --git a/setupext.py b/setupext.py index f344cd1c7466..4a838eedd52f 100644 --- a/setupext.py +++ b/setupext.py @@ -413,7 +413,7 @@ def get_extensions(self): yield ext # ttconv ext = Extension( - "matplotlib.ttconv", [ + "matplotlib._ttconv", [ "src/_ttconv.cpp", "extern/ttconv/pprdrv_tt.cpp", "extern/ttconv/pprdrv_tt2.cpp", diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp index 7ea07d1cbe3a..2fadb9bfa5fa 100644 --- a/src/_ttconv.cpp +++ b/src/_ttconv.cpp @@ -279,7 +279,7 @@ static PyModuleDef ttconv_module = { #pragma GCC visibility push(default) PyMODINIT_FUNC -PyInit_ttconv(void) +PyInit__ttconv(void) { PyObject* m; From c212f2fe79c77424212e26e9f411db9fb7e20322 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 18 Oct 2019 02:23:52 +0200 Subject: [PATCH 042/602] Warn when pil_kwargs["pnginfo"] overrides metadata. --- lib/matplotlib/backends/backend_agg.py | 8 -------- lib/matplotlib/image.py | 20 ++++++++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index e45fcb104881..c2331cb7ee8f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -498,14 +498,6 @@ def print_png(self, filename_or_obj, *args, If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ - - if metadata is None: - metadata = {} - metadata = { - "Software": - f"matplotlib version{mpl.__version__}, http://matplotlib.org/", - **metadata, - } FigureCanvasAgg.draw(self) mpl.image.imsave( filename_or_obj, np.asarray(self.buffer_rgba()), format="png", diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 3f558d1ab839..d2e3c263abac 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1520,14 +1520,22 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, pil_shape = (rgba.shape[1], rgba.shape[0]) image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) - if (format == "png" - and metadata is not None and "pnginfo" not in pil_kwargs): + if format == "png": # Only use the metadata kwarg if pnginfo is not set, because the # semantics of duplicate keys in pnginfo is unclear. - pnginfo = PIL.PngImagePlugin.PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo + if "pnginfo" in pil_kwargs: + if metadata: + cbook._warn_external("'metadata' is overridden by the " + "'pnginfo' entry in 'pil_kwargs'.") + else: + metadata = { + "Software": (f"Matplotlib version{mpl.__version__}, " + f"https://matplotlib.org/"), + **(metadata if metadata is not None else {}), + } + pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo() + for k, v in metadata.items(): + pnginfo.add_text(k, v) if format in ["jpg", "jpeg"]: format = "jpeg" # Pillow doesn't recognize "jpg". facecolor = mpl.rcParams["savefig.facecolor"] From 83f82f22552701871fa3aea4f1971b9dac6f54ed Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 24 May 2020 00:49:28 +0200 Subject: [PATCH 043/602] Save a couple of numpy conversions. --- lib/matplotlib/backends/backend_agg.py | 5 ++--- lib/matplotlib/image.py | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index c2331cb7ee8f..2d1f0a0dfd39 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -500,9 +500,8 @@ def print_png(self, filename_or_obj, *args, """ FigureCanvasAgg.draw(self) mpl.image.imsave( - filename_or_obj, np.asarray(self.buffer_rgba()), format="png", - origin="upper", dpi=self.figure.dpi, - metadata=metadata, pil_kwargs=pil_kwargs) + filename_or_obj, self.buffer_rgba(), format="png", origin="upper", + dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs) def print_to_buffer(self): FigureCanvasAgg.draw(self) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index d2e3c263abac..51f8a39825ff 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1514,7 +1514,15 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, origin = mpl.rcParams["image.origin"] if origin == "lower": arr = arr[::-1] - rgba = sm.to_rgba(arr, bytes=True) + 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 + # because that's what backend_agg passes, and can be in fact used + # as is, saving a few operations. + rgba = arr + else: + rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: pil_kwargs = {} pil_shape = (rgba.shape[1], rgba.shape[0]) From d23d6de4b3c0bf5582efee46ddcd7b57cfc89b18 Mon Sep 17 00:00:00 2001 From: sarthakforwet Date: Thu, 28 May 2020 18:36:51 +0530 Subject: [PATCH 044/602] Added autoscale tutorial --- tutorials/intermediate/autoscale_tutorial.py | 169 +++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 tutorials/intermediate/autoscale_tutorial.py diff --git a/tutorials/intermediate/autoscale_tutorial.py b/tutorials/intermediate/autoscale_tutorial.py new file mode 100644 index 000000000000..59c569cdb6ef --- /dev/null +++ b/tutorials/intermediate/autoscale_tutorial.py @@ -0,0 +1,169 @@ +""" +Matplotlib used to recompute autoscaled limits after every plotting (plot(), +bar(), etc.) call. It now only does so when actually rendering the canvas, or +when the user queries the Axes limits and that is possible through **autoscale** +feature. +This is an improvement over the previous case when user has to manually +autoscale the Axes(or axis) according to data. +This particular method is a part of matplotlib.axes.Axes class. It is used to +scale the Axes(or axis) according to data limits. +Before autoscale one could have to manually struggle with the Axes to scale +itself according to data. +Whenever we use autoscale method the axes(or axis if specified only one axis) +does recalculate its limits with respect to data. +""" + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import PathPatch +from matplotlib.path import Path +from matplotlib.collections import EllipseCollection + + +def autoscale(): + x = np.sin(np.linspace(0,6)) + fig,ax =plt.subplots(ncols=2) + + # Firstly we will explicitly enable autoscaling (However it is by-default enabled for pyplot artist.) + ax[0].autoscale(True) + ax[0].plot(x) + + # Now we disable it . + ax[1].autoscale(False) + ax[1].plot(x) + + +# Utility function to describe autoscaling feature with Margins. +def autoscale_and_margins(autoscale=False): + t = np.arange(-np.pi/2,(3 * np.pi)/2,0.1) + f_t = np.cos(t) + fig,ax = plt.subplots(ncols=2) + for axis in [ax[0],ax[1]]: + axis.plot(t,f_t,color="red") + axis.axhline(y=0,color="magenta",alpha=0.7) + axis.margins(0.2,0.2) + + ax[1].autoscale(tight=True) # tight in ax.margins is by default True but unable to autoscale itself when Zoomed in or Zoomed out. + plt.tight_layout() + + + +def autoscale_and_collections(): + fig,ax = plt.subplots(ncols=2) + x = np.arange(10) + y = np.arange(15) + X, Y = np.meshgrid(x, y) + + XY = np.column_stack((X.ravel(), Y.ravel())) + + ec_1 = EllipseCollection(10, 10, 5, units ='y', + offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. + transOffset = ax[0].transData, + cmap ="jet") + ec_1.set_array((X * Y).ravel()) + + ec_2 = EllipseCollection(10, 10, 5, units ='y', + offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. + transOffset = ax[1].transData, + cmap ="jet") + ec_2.set_array((X * Y).ravel()) + + ax[0].add_collection(ec_1) + ax[1].add_collection(ec_2) + ax[1].autoscale_view() + fig.canvas.draw() + + +def autoscale_and_patches(): + fig,ax = plt.subplots(ncols=2) + vertices = [(0,0),(0,3),(1,0),(0,0)] + codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.MOVETO] + vertices = np.array(vertices,float) + path_1 = Path(vertices,codes) + patches_1 = PathPatch(path_1,facecolor="magenta",alpha=0.7) + # matplotlib.patches.Patch is Base class of PathPatch and it + # does not support autoscaling. + # creating two because re-using of artists not supported. + path_2 = Path(vertices,codes) + patches_2 = PathPatch(path_2,facecolor="magenta",alpha=0.7) + # matplotlib.patches.Patch is Base class of PathPatch and it + # does not support autoscaling. + + ax[0].add_patch(patches_1) + ax[1].add_patch(patches_2) + ax[1].autoscale() + fig.canvas.draw() + +def autoscale_disable(): + x = np.arange(10) + # Disable Autoscaling + plt.autoscale(False) + + plt.xlim(0,2) + plt.plot(x) +""" +autoscale +============= +There are some cases when we have to explicitly enable or disable autoscaling +feature and we would see some of those cases in the following tutorial. Lets +just discuss how we can explicitly enable or disable autoscaling. +""" + +autoscale() + +""" +autoscale and margins +===================== +Whenever we set margins our axes remains invariant of the change caused by +it.Hence we use autoscaling if we want data to be bound with the axes +irrespective of the margin set. +""" + +autoscale_and_margins() + +""" +Artist with autoscale +===================== +Collection and Patch subclasses of Artist class does not support autoscaling by +default. If one wants to enable autoscaling he have to explicitly enable it. See +Axes limits in the below plots. + +For further reference on relation of Autoscale feature with Collection Class +see- +https://matplotlib.org/3.2.1/api/prev_api_changes/api_changes_3.2.0/behavior.html#autoscaling +""" + +""" +autoscale and collections +========================= +""" +autoscale_and_collections() + +""" +autoscale and patches +===================== +""" +autoscale_and_patches() + +""" +Some of the subclasses under Collection class are : +https://matplotlib.org/3.1.1/_images/inheritance-1d05647d989bf64e3e438a24b19fee19432184da.png +reference site : https://matplotlib.org/3.1.1/api/collections_api.html +""" + +""" +Till now we have seen how we can enable autoscaling. Now lets just discuss under +which case we possibly need to disable it. + +Suppose we want to plot a line at 45 degrees to x and y axes and we want data to +be shown within a given range out of the whole and hence we have to set limits +for x and y axes and in that case we have to first disable autoscaling and then +we can set the limits . +""" +""" +autoscale disable +================= +""" + +autoscale_disable() + From 4ee695c97c5add10bb486234a922466fa4a07a01 Mon Sep 17 00:00:00 2001 From: sarthakforwet Date: Thu, 28 May 2020 19:35:34 +0530 Subject: [PATCH 045/602] updated autoscale tutorial --- tutorials/intermediate/autoscale_tutorial.py | 91 +++++++++++++++++--- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/tutorials/intermediate/autoscale_tutorial.py b/tutorials/intermediate/autoscale_tutorial.py index 59c569cdb6ef..fe98d5522bc1 100644 --- a/tutorials/intermediate/autoscale_tutorial.py +++ b/tutorials/intermediate/autoscale_tutorial.py @@ -19,7 +19,7 @@ from matplotlib.path import Path from matplotlib.collections import EllipseCollection - +# Utility function to describe enabling and disabling of autoscaling. def autoscale(): x = np.sin(np.linspace(0,6)) fig,ax =plt.subplots(ncols=2) @@ -32,22 +32,40 @@ def autoscale(): ax[1].autoscale(False) ax[1].plot(x) + fontdict ={ + "fontsize":15, + "fontweight" : 14 + } + + ax[0].set_title("Autoscale Enabled",fontdict = fontdict) + ax[1].set_title("Autoscale Disabled",fontdict = fontdict) + fig.canvas.draw() -# Utility function to describe autoscaling feature with Margins. + +# Utility function to describe autoscaling with margins. def autoscale_and_margins(autoscale=False): + fig,ax = plt.subplots(ncols=2) + t = np.arange(-np.pi/2,(3 * np.pi)/2,0.1) f_t = np.cos(t) - fig,ax = plt.subplots(ncols=2) + for axis in [ax[0],ax[1]]: axis.plot(t,f_t,color="red") axis.axhline(y=0,color="magenta",alpha=0.7) axis.margins(0.2,0.2) - ax[1].autoscale(tight=True) # tight in ax.margins is by default True but unable to autoscale itself when Zoomed in or Zoomed out. + fontdict ={ + "fontsize":15, + "fontweight" : 14 + } + + ax[1].autoscale(tight=True) + ax[0].set_title("Without Autoscale",fontdict=fontdict) + ax[1].set_title("With Autoscale",fontdict=fontdict) plt.tight_layout() + fig.canvas.draw() - - +# Utility function for describing relation between autoscale and Collections Class def autoscale_and_collections(): fig,ax = plt.subplots(ncols=2) x = np.arange(10) @@ -55,7 +73,9 @@ def autoscale_and_collections(): X, Y = np.meshgrid(x, y) XY = np.column_stack((X.ravel(), Y.ravel())) - + + # We have to add create different artist instances for different axis. + ec_1 = EllipseCollection(10, 10, 5, units ='y', offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. transOffset = ax[0].transData, @@ -68,39 +88,75 @@ def autoscale_and_collections(): cmap ="jet") ec_2.set_array((X * Y).ravel()) + fontdict ={ + "fontsize":15, + "fontweight" : 14 + } + ax[0].add_collection(ec_1) ax[1].add_collection(ec_2) ax[1].autoscale_view() + + ax[0].set_title("Without Autoscale",fontdict=fontdict) + ax[1].set_title("With Autoscale",fontdict=fontdict) + fig.canvas.draw() - +# Utility function for describing relationship between autoscale and Patches. def autoscale_and_patches(): fig,ax = plt.subplots(ncols=2) + vertices = [(0,0),(0,3),(1,0),(0,0)] codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.MOVETO] vertices = np.array(vertices,float) + path_1 = Path(vertices,codes) patches_1 = PathPatch(path_1,facecolor="magenta",alpha=0.7) + # matplotlib.patches.Patch is Base class of PathPatch and it # does not support autoscaling. + # creating two because re-using of artists not supported. + path_2 = Path(vertices,codes) patches_2 = PathPatch(path_2,facecolor="magenta",alpha=0.7) - # matplotlib.patches.Patch is Base class of PathPatch and it - # does not support autoscaling. + fontdict ={ + "fontsize":15, + "fontweight" : 14 + } + ax[0].add_patch(patches_1) ax[1].add_patch(patches_2) ax[1].autoscale() + + ax[0].set_title("Without Autoscale",fontdict=fontdict) + ax[1].set_title("With Autoscale",fontdict=fontdict) fig.canvas.draw() +# Utility function for describing case when we need to disable autoscaling. def autoscale_disable(): + fig,ax = plt.subplots(ncols=2) + x = np.arange(10) + # Disable Autoscaling - plt.autoscale(False) + ax[1].autoscale(False) + + for axis in [ax[0],ax[1]]: + axis.set_ylim(0,2) + axis.plot(x) - plt.xlim(0,2) - plt.plot(x) + ax[0].set_title("Autoscale Enabled") + ax[1].set_title("Autoscale Disabled") + plt.tight_layout() + + fig,ax = plt.subplots() + ax.plot(x) + ax.set_title("Original Plot") + plt.tight_layout() + + """ autoscale ============= @@ -158,8 +214,9 @@ def autoscale_disable(): Suppose we want to plot a line at 45 degrees to x and y axes and we want data to be shown within a given range out of the whole and hence we have to set limits for x and y axes and in that case we have to first disable autoscaling and then -we can set the limits . +we can set the limits. """ + """ autoscale disable ================= @@ -167,3 +224,9 @@ def autoscale_disable(): autoscale_disable() +""" +As we can see that setting the y_lim between 0 and 2 worked for both axes but +the one whose autoscaling was not disabled, got autoscaled in the x-axis and the +one which had autoscaling disabled, maintained the default range(between 0 to 1) +for x-axis. +""" \ No newline at end of file From 581a3b073e0cee24c7e3603d19e9419641e43118 Mon Sep 17 00:00:00 2001 From: sarthakforwet Date: Thu, 28 May 2020 19:45:11 +0530 Subject: [PATCH 046/602] Updated autoscale tutorial --- tutorials/intermediate/autoscale_tutorial.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tutorials/intermediate/autoscale_tutorial.py b/tutorials/intermediate/autoscale_tutorial.py index fe98d5522bc1..8b511370c226 100644 --- a/tutorials/intermediate/autoscale_tutorial.py +++ b/tutorials/intermediate/autoscale_tutorial.py @@ -66,7 +66,7 @@ def autoscale_and_margins(autoscale=False): fig.canvas.draw() # Utility function for describing relation between autoscale and Collections Class -def autoscale_and_collections(): +def autoscale_and_Collection(): fig,ax = plt.subplots(ncols=2) x = np.arange(10) y = np.arange(15) @@ -158,8 +158,10 @@ def autoscale_disable(): """ +============= autoscale ============= + There are some cases when we have to explicitly enable or disable autoscaling feature and we would see some of those cases in the following tutorial. Lets just discuss how we can explicitly enable or disable autoscaling. @@ -168,8 +170,10 @@ def autoscale_disable(): autoscale() """ +===================== autoscale and margins ===================== + Whenever we set margins our axes remains invariant of the change caused by it.Hence we use autoscaling if we want data to be bound with the axes irrespective of the margin set. @@ -178,8 +182,10 @@ def autoscale_disable(): autoscale_and_margins() """ +===================== Artist with autoscale ===================== + Collection and Patch subclasses of Artist class does not support autoscaling by default. If one wants to enable autoscaling he have to explicitly enable it. See Axes limits in the below plots. @@ -190,14 +196,18 @@ def autoscale_disable(): """ """ -autoscale and collections ========================= +autoscale and Collection +========================= +Let's have a look at how autoscaling affects Collection instance. """ -autoscale_and_collections() +autoscale_and_Collection() """ +===================== autoscale and patches ===================== +Let's have a look at how autoscaling affects PathPatch instance. """ autoscale_and_patches() @@ -218,8 +228,10 @@ def autoscale_disable(): """ """ +================= autoscale disable ================= +Let's have a look at above case. """ autoscale_disable() From 6115c4ed6c850f3090cfb9737c8e33401c01cf97 Mon Sep 17 00:00:00 2001 From: sarthakforwet Date: Thu, 28 May 2020 20:21:36 +0530 Subject: [PATCH 047/602] Updated autoscale tutorial --- tutorials/intermediate/autoscale_tutorial.py | 244 ------------------- 1 file changed, 244 deletions(-) delete mode 100644 tutorials/intermediate/autoscale_tutorial.py diff --git a/tutorials/intermediate/autoscale_tutorial.py b/tutorials/intermediate/autoscale_tutorial.py deleted file mode 100644 index 8b511370c226..000000000000 --- a/tutorials/intermediate/autoscale_tutorial.py +++ /dev/null @@ -1,244 +0,0 @@ -""" -Matplotlib used to recompute autoscaled limits after every plotting (plot(), -bar(), etc.) call. It now only does so when actually rendering the canvas, or -when the user queries the Axes limits and that is possible through **autoscale** -feature. -This is an improvement over the previous case when user has to manually -autoscale the Axes(or axis) according to data. -This particular method is a part of matplotlib.axes.Axes class. It is used to -scale the Axes(or axis) according to data limits. -Before autoscale one could have to manually struggle with the Axes to scale -itself according to data. -Whenever we use autoscale method the axes(or axis if specified only one axis) -does recalculate its limits with respect to data. -""" - -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.patches import PathPatch -from matplotlib.path import Path -from matplotlib.collections import EllipseCollection - -# Utility function to describe enabling and disabling of autoscaling. -def autoscale(): - x = np.sin(np.linspace(0,6)) - fig,ax =plt.subplots(ncols=2) - - # Firstly we will explicitly enable autoscaling (However it is by-default enabled for pyplot artist.) - ax[0].autoscale(True) - ax[0].plot(x) - - # Now we disable it . - ax[1].autoscale(False) - ax[1].plot(x) - - fontdict ={ - "fontsize":15, - "fontweight" : 14 - } - - ax[0].set_title("Autoscale Enabled",fontdict = fontdict) - ax[1].set_title("Autoscale Disabled",fontdict = fontdict) - fig.canvas.draw() - - -# Utility function to describe autoscaling with margins. -def autoscale_and_margins(autoscale=False): - fig,ax = plt.subplots(ncols=2) - - t = np.arange(-np.pi/2,(3 * np.pi)/2,0.1) - f_t = np.cos(t) - - for axis in [ax[0],ax[1]]: - axis.plot(t,f_t,color="red") - axis.axhline(y=0,color="magenta",alpha=0.7) - axis.margins(0.2,0.2) - - fontdict ={ - "fontsize":15, - "fontweight" : 14 - } - - ax[1].autoscale(tight=True) - ax[0].set_title("Without Autoscale",fontdict=fontdict) - ax[1].set_title("With Autoscale",fontdict=fontdict) - plt.tight_layout() - fig.canvas.draw() - -# Utility function for describing relation between autoscale and Collections Class -def autoscale_and_Collection(): - fig,ax = plt.subplots(ncols=2) - x = np.arange(10) - y = np.arange(15) - X, Y = np.meshgrid(x, y) - - XY = np.column_stack((X.ravel(), Y.ravel())) - - # We have to add create different artist instances for different axis. - - ec_1 = EllipseCollection(10, 10, 5, units ='y', - offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. - transOffset = ax[0].transData, - cmap ="jet") - ec_1.set_array((X * Y).ravel()) - - ec_2 = EllipseCollection(10, 10, 5, units ='y', - offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. - transOffset = ax[1].transData, - cmap ="jet") - ec_2.set_array((X * Y).ravel()) - - fontdict ={ - "fontsize":15, - "fontweight" : 14 - } - - ax[0].add_collection(ec_1) - ax[1].add_collection(ec_2) - ax[1].autoscale_view() - - ax[0].set_title("Without Autoscale",fontdict=fontdict) - ax[1].set_title("With Autoscale",fontdict=fontdict) - - fig.canvas.draw() - -# Utility function for describing relationship between autoscale and Patches. -def autoscale_and_patches(): - fig,ax = plt.subplots(ncols=2) - - vertices = [(0,0),(0,3),(1,0),(0,0)] - codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.MOVETO] - vertices = np.array(vertices,float) - - path_1 = Path(vertices,codes) - patches_1 = PathPatch(path_1,facecolor="magenta",alpha=0.7) - - # matplotlib.patches.Patch is Base class of PathPatch and it - # does not support autoscaling. - - # creating two because re-using of artists not supported. - - path_2 = Path(vertices,codes) - patches_2 = PathPatch(path_2,facecolor="magenta",alpha=0.7) - - fontdict ={ - "fontsize":15, - "fontweight" : 14 - } - - ax[0].add_patch(patches_1) - ax[1].add_patch(patches_2) - ax[1].autoscale() - - ax[0].set_title("Without Autoscale",fontdict=fontdict) - ax[1].set_title("With Autoscale",fontdict=fontdict) - fig.canvas.draw() - -# Utility function for describing case when we need to disable autoscaling. -def autoscale_disable(): - fig,ax = plt.subplots(ncols=2) - - x = np.arange(10) - - # Disable Autoscaling - ax[1].autoscale(False) - - for axis in [ax[0],ax[1]]: - axis.set_ylim(0,2) - axis.plot(x) - - ax[0].set_title("Autoscale Enabled") - ax[1].set_title("Autoscale Disabled") - plt.tight_layout() - - fig,ax = plt.subplots() - ax.plot(x) - ax.set_title("Original Plot") - plt.tight_layout() - - -""" -============= -autoscale -============= - -There are some cases when we have to explicitly enable or disable autoscaling -feature and we would see some of those cases in the following tutorial. Lets -just discuss how we can explicitly enable or disable autoscaling. -""" - -autoscale() - -""" -===================== -autoscale and margins -===================== - -Whenever we set margins our axes remains invariant of the change caused by -it.Hence we use autoscaling if we want data to be bound with the axes -irrespective of the margin set. -""" - -autoscale_and_margins() - -""" -===================== -Artist with autoscale -===================== - -Collection and Patch subclasses of Artist class does not support autoscaling by -default. If one wants to enable autoscaling he have to explicitly enable it. See -Axes limits in the below plots. - -For further reference on relation of Autoscale feature with Collection Class -see- -https://matplotlib.org/3.2.1/api/prev_api_changes/api_changes_3.2.0/behavior.html#autoscaling -""" - -""" -========================= -autoscale and Collection -========================= -Let's have a look at how autoscaling affects Collection instance. -""" -autoscale_and_Collection() - -""" -===================== -autoscale and patches -===================== -Let's have a look at how autoscaling affects PathPatch instance. -""" -autoscale_and_patches() - -""" -Some of the subclasses under Collection class are : -https://matplotlib.org/3.1.1/_images/inheritance-1d05647d989bf64e3e438a24b19fee19432184da.png -reference site : https://matplotlib.org/3.1.1/api/collections_api.html -""" - -""" -Till now we have seen how we can enable autoscaling. Now lets just discuss under -which case we possibly need to disable it. - -Suppose we want to plot a line at 45 degrees to x and y axes and we want data to -be shown within a given range out of the whole and hence we have to set limits -for x and y axes and in that case we have to first disable autoscaling and then -we can set the limits. -""" - -""" -================= -autoscale disable -================= -Let's have a look at above case. -""" - -autoscale_disable() - -""" -As we can see that setting the y_lim between 0 and 2 worked for both axes but -the one whose autoscaling was not disabled, got autoscaled in the x-axis and the -one which had autoscaling disabled, maintained the default range(between 0 to 1) -for x-axis. -""" \ No newline at end of file From 9ac9157f069d0dc0338e01422144cff0cef41dc9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 28 May 2020 20:56:18 +0200 Subject: [PATCH 048/602] Remove unneeded check/comment re: multiprocessing in setup.py. We don't use multiprocessing in setup.py anymore and can thus get rid of the `if __name__ == "__main__"` check. --- setup.py | 220 +++++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 113 deletions(-) diff --git a/setup.py b/setup.py index 5fb374027364..847442c550c2 100644 --- a/setup.py +++ b/setup.py @@ -234,116 +234,110 @@ def run(self): cmdclass['develop'] = develop_with_jquery -# One doesn't normally see `if __name__ == '__main__'` blocks in a setup.py, -# however, this is needed on Windows to avoid creating infinite subprocesses -# when using multiprocessing. -if __name__ == '__main__': - package_data = {} # Will be filled below by the various components. - - # If the user just queries for information, don't bother figuring out which - # packages to build or install. - if not (any('--' + opt in sys.argv - for opt in Distribution.display_option_names + ['help']) - or 'clean' in sys.argv): - # Go through all of the packages and figure out which ones we are - # going to build/install. - print_raw() - print_raw("Edit setup.cfg to change the build options; " - "suppress output with --quiet.") - print_raw() - print_raw("BUILDING MATPLOTLIB") - - good_packages = [] - for package in mpl_packages: - try: - message = package.check() - except setupext.Skipped as e: - print_status(package.name, "no [{e}]".format(e=e)) - continue - if message is not None: - print_status(package.name, - "yes [{message}]".format(message=message)) - good_packages.append(package) - - print_raw() - - # Now collect all of the information we need to build all of the - # packages. - for package in good_packages: - # Extension modules only get added in build_ext, as numpy will have - # been installed (as setup_requires) at that point. - data = package.get_package_data() - for key, val in data.items(): - package_data.setdefault(key, []) - package_data[key] = list(set(val + package_data[key])) - - # Write the default matplotlibrc file - with open('matplotlibrc.template') as fd: - template_lines = fd.read().splitlines(True) - backend_line_idx, = [ # Also asserts that there is a single such line. - idx for idx, line in enumerate(template_lines) - if line.startswith('#backend:')] - if setupext.options['backend']: - template_lines[backend_line_idx] = ( - 'backend: {}'.format(setupext.options['backend'])) - with open('lib/matplotlib/mpl-data/matplotlibrc', 'w') as fd: - fd.write(''.join(template_lines)) - - # Finally, pass this all along to distutils to do the heavy lifting. - setup( - name="matplotlib", - version=__version__, - description="Python plotting package", - author="John D. Hunter, Michael Droettboom", - author_email="matplotlib-users@python.org", - url="https://matplotlib.org", - download_url="https://matplotlib.org/users/installing.html", - project_urls={ - 'Documentation': 'https://matplotlib.org', - 'Source Code': 'https://github.com/matplotlib/matplotlib', - 'Bug Tracker': 'https://github.com/matplotlib/matplotlib/issues', - 'Forum': 'https://discourse.matplotlib.org/', - 'Donate': 'https://numfocus.org/donate-to-matplotlib' - }, - long_description=Path("README.rst").read_text(encoding="utf-8"), - long_description_content_type="text/x-rst", - license="PSF", - platforms="any", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Matplotlib', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Education', - 'License :: OSI Approved :: Python Software Foundation License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Scientific/Engineering :: Visualization', - ], - - package_dir={"": "lib"}, - packages=find_packages("lib"), - namespace_packages=["mpl_toolkits"], - py_modules=["pylab"], - # Dummy extension to trigger build_ext, which will swap it out with - # real extensions that can depend on numpy for the build. - ext_modules=[Extension("", [])], - package_data=package_data, - - python_requires='>={}'.format('.'.join(str(n) for n in min_version)), - setup_requires=[ - "numpy>=1.15", - ], - install_requires=[ - "cycler>=0.10", - "kiwisolver>=1.0.1", - "numpy>=1.15", - "pillow>=6.2.0", - "pyparsing>=2.0.3,!=2.0.4,!=2.1.2,!=2.1.6", - "python-dateutil>=2.1", - ], - - cmdclass=cmdclass, - ) +package_data = {} # Will be filled below by the various components. + +# If the user just queries for information, don't bother figuring out which +# packages to build or install. +if not (any('--' + opt in sys.argv + for opt in Distribution.display_option_names + ['help']) + or 'clean' in sys.argv): + # Go through all of the packages and figure out which ones we are + # going to build/install. + print_raw() + print_raw("Edit setup.cfg to change the build options; " + "suppress output with --quiet.") + print_raw() + print_raw("BUILDING MATPLOTLIB") + + good_packages = [] + for package in mpl_packages: + try: + message = package.check() + except setupext.Skipped as e: + print_status(package.name, "no [{e}]".format(e=e)) + continue + if message is not None: + print_status(package.name, + "yes [{message}]".format(message=message)) + good_packages.append(package) + + print_raw() + + # Now collect all of the information we need to build all of the packages. + for package in good_packages: + # Extension modules only get added in build_ext, as numpy will have + # been installed (as setup_requires) at that point. + data = package.get_package_data() + for key, val in data.items(): + package_data.setdefault(key, []) + package_data[key] = list(set(val + package_data[key])) + + # Write the default matplotlibrc file + with open('matplotlibrc.template') as fd: + template_lines = fd.read().splitlines(True) + backend_line_idx, = [ # Also asserts that there is a single such line. + idx for idx, line in enumerate(template_lines) + if line.startswith('#backend:')] + if setupext.options['backend']: + template_lines[backend_line_idx] = ( + 'backend: {}'.format(setupext.options['backend'])) + with open('lib/matplotlib/mpl-data/matplotlibrc', 'w') as fd: + fd.write(''.join(template_lines)) + +setup( # Finally, pass this all along to distutils to do the heavy lifting. + name="matplotlib", + version=__version__, + description="Python plotting package", + author="John D. Hunter, Michael Droettboom", + author_email="matplotlib-users@python.org", + url="https://matplotlib.org", + download_url="https://matplotlib.org/users/installing.html", + project_urls={ + 'Documentation': 'https://matplotlib.org', + 'Source Code': 'https://github.com/matplotlib/matplotlib', + 'Bug Tracker': 'https://github.com/matplotlib/matplotlib/issues', + 'Forum': 'https://discourse.matplotlib.org/', + 'Donate': 'https://numfocus.org/donate-to-matplotlib' + }, + long_description=Path("README.rst").read_text(encoding="utf-8"), + long_description_content_type="text/x-rst", + license="PSF", + platforms="any", + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Framework :: Matplotlib', + 'Intended Audience :: Science/Research', + 'Intended Audience :: Education', + 'License :: OSI Approved :: Python Software Foundation License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Scientific/Engineering :: Visualization', + ], + + package_dir={"": "lib"}, + packages=find_packages("lib"), + namespace_packages=["mpl_toolkits"], + py_modules=["pylab"], + # Dummy extension to trigger build_ext, which will swap it out with + # real extensions that can depend on numpy for the build. + ext_modules=[Extension("", [])], + package_data=package_data, + + python_requires='>={}'.format('.'.join(str(n) for n in min_version)), + setup_requires=[ + "numpy>=1.15", + ], + install_requires=[ + "cycler>=0.10", + "kiwisolver>=1.0.1", + "numpy>=1.15", + "pillow>=6.2.0", + "pyparsing>=2.0.3,!=2.0.4,!=2.1.2,!=2.1.6", + "python-dateutil>=2.1", + ], + + cmdclass=cmdclass, +) From 413bf6582c52c6ec77ef3ccc68e58d328e59ee7c Mon Sep 17 00:00:00 2001 From: Maoz Gelbart <13831112+MaozGelbart@users.noreply.github.com> Date: Thu, 28 May 2020 22:13:06 +0300 Subject: [PATCH 049/602] Fix title_fontsize description A typo prevented correct parsing by the docs infra --- lib/matplotlib/legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index fe4206f6f526..79a3e827ff46 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -237,7 +237,7 @@ def _update_bbox_to_anchor(self, loc_in_canvas): title : str or None The legend's title. Default is no title (``None``). -title_fontsize: int or {'xx-small', 'x-small', 'small', 'medium', 'large', \ +title_fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \ 'x-large', 'xx-large'}, default: :rc:`legend.title_fontsize` The font size of the legend's title. From 5c7fb5b7c927f674cdd74f1841bab886d072c2b7 Mon Sep 17 00:00:00 2001 From: Benjamin Root Date: Thu, 28 May 2020 14:09:40 -0400 Subject: [PATCH 050/602] Backport PR #17408: FIX: cancel pending autoscale on manually setting limits --- lib/matplotlib/axes/_base.py | 6 ++++++ lib/matplotlib/tests/test_axes.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 694945d57389..cc0c364ee05e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3220,6 +3220,9 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, left, right = sorted([left, right], reverse=bool(reverse)) self._viewLim.intervalx = (left, right) + # Mark viewlims as no longer stale without triggering an autoscale. + for ax in self._shared_x_axes.get_siblings(self): + ax._stale_viewlim_x = False if auto is not None: self._autoscaleXon = bool(auto) @@ -3611,6 +3614,9 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, bottom, top = sorted([bottom, top], reverse=bool(reverse)) self._viewLim.intervaly = (bottom, top) + # Mark viewlims as no longer stale without triggering an autoscale. + for ax in self._shared_y_axes.get_siblings(self): + ax._stale_viewlim_y = False if auto is not None: self._autoscaleYon = bool(auto) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 69b460a7e5f0..75636301d064 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6694,3 +6694,33 @@ def test_invisible_axes(): assert fig.canvas.inaxes((200, 200)) is not None ax.set_visible(False) assert fig.canvas.inaxes((200, 200)) is None + + +@pytest.mark.parametrize('auto', (True, False, None)) +def test_unautoscaley(auto): + fig, ax = plt.subplots() + x = np.arange(100) + y = np.linspace(-.1, .1, 100) + ax.scatter(x, y) + + post_auto = ax.get_autoscaley_on() if auto is None else auto + + ax.set_ylim((-.5, .5), auto=auto) + assert post_auto == ax.get_autoscaley_on() + fig.canvas.draw() + assert_array_equal(ax.get_ylim(), (-.5, .5)) + + +@pytest.mark.parametrize('auto', (True, False, None)) +def test_unautoscalex(auto): + fig, ax = plt.subplots() + x = np.arange(100) + y = np.linspace(-.1, .1, 100) + ax.scatter(y, x) + + post_auto = ax.get_autoscalex_on() if auto is None else auto + + ax.set_xlim((-.5, .5), auto=auto) + assert post_auto == ax.get_autoscalex_on() + fig.canvas.draw() + assert_array_equal(ax.get_xlim(), (-.5, .5)) From 5348428b91c8e199c945db24ca2f10c0517a4fc8 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 2 May 2017 08:53:44 +0200 Subject: [PATCH 051/602] Solve conflicts --- lib/matplotlib/colors.py | 28 +++++++++-- lib/matplotlib/tests/test_colors.py | 76 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index ffc259ac3901..c717caf39ad7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1410,7 +1410,7 @@ class BoundaryNorm(Normalize): interpolation, but using integers seems simpler, and reduces the number of conversions back and forth between integer and floating point. """ - def __init__(self, boundaries, ncolors, clip=False): + def __init__(self, boundaries, ncolors, clip=False, extend='neither'): """ Parameters ---------- @@ -1427,6 +1427,9 @@ def __init__(self, boundaries, ncolors, clip=False): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. + extend : str, optional + 'neither', 'both', 'min', or 'max': select the colors out of + cmap so that the extensions are considered in the interpolation Notes ----- @@ -1442,7 +1445,24 @@ def __init__(self, boundaries, ncolors, clip=False): self.boundaries = np.asarray(boundaries) self.N = len(self.boundaries) self.Ncmap = ncolors - if self.N - 1 == self.Ncmap: + + # Extension. We use the same trick as colorbar.py and add a fake + # boundary were needed. Since colorbar does it too, we have to use a + # private property for that. + _b = list(boundaries) + if extend == 'both': + _b = [_b[0] - 1] + _b + [_b[-1] + 1] + elif extend == 'min': + _b = [_b[0] - 1] + _b + elif extend == 'max': + _b = _b + [_b[-1] + 1] + self._b = np.array(_b) + + # I am not sure if the private _N is necessary (I dont think so, + # but it should be consistent with boundaries I guess. This _N is + # used for interpolation). + self._N = len(self._b) + if self._N - 1 == self.Ncmap: self._interp = False else: self._interp = True @@ -1460,10 +1480,10 @@ def __call__(self, value, clip=None): else: max_col = self.Ncmap iret = np.zeros(xx.shape, dtype=np.int16) - for i, b in enumerate(self.boundaries): + for i, b in enumerate(self._b): iret[xx >= b] = i if self._interp: - scalefac = (self.Ncmap - 1) / (self.N - 2) + scalefac = float(self.Ncmap - 1) / (self._N - 2) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 iret[xx >= self.vmax] = max_col diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index b18035150fbb..e87b396b895d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -265,6 +265,82 @@ def test_BoundaryNorm(): vals = np.ma.masked_invalid([np.Inf]) assert np.all(bn(vals).mask) + # Testing extend keyword + bounds = [1, 2, 3] + cmap = cm.get_cmap('jet') + + refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') + x = np.random.random(100) + 1.5 + np.testing.assert_array_equal(refnorm(x), mynorm(x)) + + # Min and max + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_over('black') + cmref.set_under('white') + + cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) + cmshould.set_over(cmshould(cmshould.N)) + cmshould.set_under(cmshould(0)) + + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [-1, 1.2, 2.3, 9.6] + np.testing.assert_array_equal(cmshould([0, 1, 2, 3]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + np.testing.assert_array_equal(-1, mynorm(-1)) + np.testing.assert_array_equal(1, mynorm(1.1)) + np.testing.assert_array_equal(4, mynorm(12)) + + # Just min + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_under('white') + cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) + cmshould.set_under(cmshould(0)) + + np.testing.assert_array_equal(2, cmref.N) + np.testing.assert_array_equal(3, cmshould.N) + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='min') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [-1, 1.2, 2.3] + np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + # Just max + cmref = mcolors.ListedColormap(['blue', 'red']) + cmref.set_over('black') + cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) + cmshould.set_over(cmshould(2)) + + np.testing.assert_array_equal(2, cmref.N) + np.testing.assert_array_equal(3, cmshould.N) + refnorm = mcolors.BoundaryNorm(bounds, cmref.N) + mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='max') + np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) + np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + x = [1.2, 2.3, 4] + np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + x = np.random.randn(100) * 10 + 2 + np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + + # General case + bounds = [1, 2, 3, 4] + cmap = cm.get_cmap('jet') + mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') + refnorm = mcolors.BoundaryNorm([-100] + bounds + [100], cmap.N) + x = np.random.randn(100) * 10 - 5 + ref = refnorm(x) + ref = np.where(ref == 0, -1, ref) + ref = np.where(ref == cmap.N-1, cmap.N, ref) + np.testing.assert_array_equal(ref, mynorm(x)) + @pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]]) def test_lognorm_invalid(vmin, vmax): From 4be7829094fea5cbb352a44c3d6079cbf88ba3a0 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Wed, 9 Sep 2015 12:21:05 +0200 Subject: [PATCH 052/602] added support for extend in colorbarbase --- lib/matplotlib/colorbar.py | 2 ++ lib/matplotlib/colors.py | 13 +++++++------ lib/matplotlib/tests/test_colors.py | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 7860631e0ce0..e8571ae9ede9 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -430,6 +430,8 @@ def __init__(self, ax, cmap=None, cmap = cm.get_cmap() if norm is None: norm = colors.Normalize() + elif hasattr(norm, 'extend') and norm.extend != 'neither': + extend = norm.extend self.alpha = alpha self.cmap = cmap self.norm = norm diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c717caf39ad7..2a77bb7d9592 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1439,6 +1439,9 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): If the number of bins doesn't equal *ncolors*, the color is chosen by linear interpolation of the bin number onto color numbers. """ + + if clip and extend != 'neither': + raise ValueError("'clip=True' is not compatible with 'extend'") self.clip = clip self.vmin = boundaries[0] self.vmax = boundaries[-1] @@ -1447,8 +1450,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self.Ncmap = ncolors # Extension. We use the same trick as colorbar.py and add a fake - # boundary were needed. Since colorbar does it too, we have to use a - # private property for that. + # boundary were needed. _b = list(boundaries) if extend == 'both': _b = [_b[0] - 1] + _b + [_b[-1] + 1] @@ -1456,11 +1458,10 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): _b = [_b[0] - 1] + _b elif extend == 'max': _b = _b + [_b[-1] + 1] - self._b = np.array(_b) + self.extend = extend - # I am not sure if the private _N is necessary (I dont think so, - # but it should be consistent with boundaries I guess. This _N is - # used for interpolation). + # needed for the interpolation but should not be seen from outside + self._b = np.array(_b) self._N = len(self._b) if self._N - 1 == self.Ncmap: self._interp = False diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index e87b396b895d..a2d597f70517 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -296,6 +296,10 @@ def test_BoundaryNorm(): np.testing.assert_array_equal(1, mynorm(1.1)) np.testing.assert_array_equal(4, mynorm(12)) + # Test raises + assert_raises(ValueError, mcolors.BoundaryNorm, bounds, cmref.N, + extend='both', clip=True) + # Just min cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_under('white') From 8cb7b45e09ba0cad5b8b65a7c3b14b84870ea2dd Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 10 Nov 2015 16:35:50 +0100 Subject: [PATCH 053/602] won't override user extend keyword anymore --- lib/matplotlib/colorbar.py | 9 +++-- .../test_colors/boundarynorm_and_colorbar.png | Bin 0 -> 16613 bytes lib/matplotlib/tests/test_colors.py | 31 +++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index e8571ae9ede9..718a3974cf4d 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -402,7 +402,7 @@ def __init__(self, ax, cmap=None, boundaries=None, orientation='vertical', ticklocation='auto', - extend='neither', + extend=None, spacing='uniform', # uniform or proportional ticks=None, format=None, @@ -430,8 +430,11 @@ def __init__(self, ax, cmap=None, cmap = cm.get_cmap() if norm is None: norm = colors.Normalize() - elif hasattr(norm, 'extend') and norm.extend != 'neither': - extend = norm.extend + if extend is None: + if hasattr(norm, 'extend'): + extend = norm.extend + else: + extend = 'neither' self.alpha = alpha self.cmap = cmap self.norm = norm diff --git a/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png b/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png new file mode 100644 index 0000000000000000000000000000000000000000..999ac38b92e8df5c3a7bcc4f8c8058b8a1bb6133 GIT binary patch literal 16613 zcmeHvc{r4P`}d&GB3endWG|#b)S!}m-*+KoPxf7_HDu44CCdzhvW~4piX?mXvWAeD zvGbl|dY<2XclY=2ect1JI*yL(Xy!ZD_5Ggv`T3kzK`KhpRD0?7A`l2FSs6(+1cHnO zfgshPB!^EDT<_h6%Pyy@vKo}|$D8tIApCpJZ5drB1cKUx@RuYQ1TyZTBwWoXX$Y6MG%SQm=t&8} zh3=X^;d1s7g7BLg`$X@%1^`SLz(PN)e$eOPrD|=J?&pPro;7K2Z*bgSyDNDq! zSoO5pDofDaa&W_qgyc^TR+d?`rr3EYeZFJR<6;+1DShqhn)HR~lpTPfm|Gmk@8sQSEjwmj7GY zPbyx$8t;l(5t9JKDz&DjW`dA;g6C=Vo=aAxlkjYotB<*5)E4S*w!{lu_riZKLUQKO zuo&;%MT)ii!uXR%c2n>jo&gq0mOpe-b1{>;_cnab?CtySuFi$VR&it>o0j`@w$@H0Hsa95&9r>AveLI$d2|y(wmf z3KTMD={lc@?UtwlT*WSQydSHVgY%+ZM*gTzBuRN603 zw<@n)aI+8ES_t?1oMNV^Kpr?e-@`w*=3tnuf$KsLw|UB<<;PT5n&Hxq@+UvnH%ljV z4my#HU@_#G`!!^)j9bT#B*m|@!?XR_nZh%=LcCBFC5E+$5qw}<%IEkdYS#I9c{T5c zoz}nB*)V0jvrSV4&-q=v#-FZ@pU^AzUcJ-r6dIC?$VH*ri@7^G^`BygWMA&6Tfkc3 zN+dx}Z$r8JURs9Fibypm`0pYm85H_)z#}Gei=KFH(}6mXT&%#UE2E8D3U?)5;TgFc8%sYA8ho^va1+7fPv;8VFQpqsIH7}wv}h{9k#;*$>0 zc99&~PcF!1RyZ=QT1wvR$j~1}5YN7gVHuOOcooy0i^!ihVy~t}%*rzIpvcB19$XR4 zmjX#1N>bXfT?Fm;%&`K&5HIVQ082r?E$@COQH@UWcd@+s8u`^t8l_l~!JYFUYI+RI zyl|78J_(n~B*BeKC!OPS_S4i37i`lzhGW!GYg5N|)aWmN&gPM_+y2>f>9^sL!}Ddx z>}!&S{^b}n2{+kMHI)kbw;s%}%Rst+DiJ%K&L<%!FW>cSw5glM#Sz;_Fm!}L?9$L4 zy_#nMO~S>uR)2zw3|v%?vi#E(5#mkp_t=gqD|Ck*S1-0~W$JgrpmO#^NU%KXCm%Xl z5ZP&bpD0btyWiB9gyyg&&Gh@XFLDm;nD3Y1zc^!d*1!#~kD7;%lBj8EWtpM0Vtv!u zID3f;QSTT(>F%4%wC+W5#Kgt*r#NX!{>dBt(I|J*7gCd)(`Dh|nudnSvm)AO(#hE9 z_FJpGz0uH|VM{d;SxFF(XxQkt)rZLF&w@(fm*bzSaNYE3KN|E6Q6XXyp@degpwHTs z9W^TgOCy&}-r1m+KAlfVd&e*k8SP%2w^r{l@eF@TA8#f|sF#&T z0);|duxq-2)5^lQwG)+-N2eZ!W#4Ji^M(uFH;WK0;e|y5I`& zG+Mk#QK%E=4_%nMo61c?`8mbuJu8RAi$fGV&n&1qZ#P_8__fZ17*QF;+OvpCWDf~M zaGyTiA)t9$UR*UY%62DO&>Z{S84V%}sU!OO^d5WNsKTCBw3@agF4X!xq%PjC6P18G zN^ZVitLk!7_YJ;^eIOq1`=b{{FI;7I=C+|XjCEwkd-oLbOX!l*syKQd> zx%Z;jz1qsxv~%Tl1+#l752MeOeopbHn-o9VuIy)o{;;&GdON)JC^K`7_G79UZPoC` zj6rEvuFPk6YiY?LYbMz0HyO+f>Xt>do?buhrwv7&s95G%Y8qKNW#t~Ma5rQ}XN6Qq z{jl;uOnaB2-nLSRk@u7fZmOdZpTv9wPr@o|_BK>C;hc*~W#@OHUgFY+sLja>PD&XZ z;t6?mbyxeH_+%w_1+p<(=SOPGqB?~h4*aMorh}V)YSYv2$Nd5reUBProi76BV^%NI8fnnIj{nF53LFUDBd6rNj_Jr|DI!c>Vm0rn^Hr zhhoA_Fu9O&gd>CWx;R>+>N2X`m9GW#=^uY^KQ`gwW+p~9v}2!*_4viGS=l(=5mL!W zZ?RM%dk^jGt{mm!R^mnjS3@=k%B+5Y_gVE817C{lIrx29$vMrgU}U!I=2*Q#zQ3hNZF{nDc41#&W2 z(%IkrGSjTsi)$x$muO$Q$V@0ORU2IlkGAA#%iB~M`Of-}jE>qVV_6R}vP4Fwq+nbe z@*q)8#fuf~B)@W$oVKvl<|SUwh37q8*~-iiH+AYM0@)rPKQCp~)OlrA?PWhP|J~Nq zm=(`{>O?QN+II8kv15INICtG&rai5j2hyaRo%%Z}qUsHvba%TVFRy0WPbWUJj7up* zPNISm5DT8wYue*lQ1V>sv)gf|+t;_wmZ=7W7hBEFTpGuD+O<|#r*lZKaSID;%g6Gy zscO!sFURj3|5dme-t{G6vu)|mo8G@Kf_ny|{-{xd*uD%qo&?>3It-JGdA+zS>9H9A&hf+-5hmEp)ha8&z6lZN^ z*VA0UsN(HiMNI3Gk?kEFC3BBUtUn1bc5wbm>|BO}nYj7lZw+xq+NokbwyjgQRhd~L zFIaq_d(N#+Q6k@BPfQ;EY`NCkA}vWBIq$J*RAHM^v5BdYQ+r;@J@#nl zOcJ3sc6L2)qLinzYW8@#PVIqWD(LAS6lyxaJRvmwbfbR^Nh&5>c#mfoHRY3la&@bf=O}+Ts@QPWzH(NUpwNS$&^;YWe^uyBgAcm{ z*}wgpecwHwtes{DK4EE?>WFFCN@MYx7oOnNW+4deD%NhsA`t8OH_Al zM|zxARhpR38r0E-AHYSy2T^5zjk*3@swLY=79XuTqZ=ayJ(q6vJJper{Cs{tUlW>< zn7*ut6EBDz4yL5UkXl(RH*t;Q*4JUB&nbt6C?ohGq;8_`ph>N}PtMkSsfe=Af38U> zOy#m&3N2Z>N23ySXyjt6wE;UVpZIucKQw|1sf~4iS9*iy*N4zARa8{yy>Jc+L+Pbr z8*6+wGN;dAu3ogdh;7F2dJq(wT+fx6u&ra!`wCJ0{lQrMZx>_RcGIX39p{w(hyK>zy#vzMH9oR!=u806?_qL|OnScK&MPQya`b#b-WvAGh8;;vh@I}s+3CIHpHRLh*)g`U}x z7E6UJTsT%f;4^<|@Y`69o>!M_vP=F&e^bL8OJTuOl%`slbHDnH-ijkr26p+P*-mHb zvG~KOT_;CC&y`bFY8m{uoDpb&JLmtwVuCq|m zYg<@kF}Z&sF~50JM{oZFW~HmO`{A4RcTY}rb#}5%g(M!U!FB6nmxsJ~`L9VoJ!^8$ z$YcB{I|m2KxYid{P@sM7+I^`|y0o$(PkC8c*;3BBh*q(6g_)j$+HX$F!+y0`w@J0U zBGbADdpvPn+O@dy8SGE@%c71W57gAu&YZa>P1xk8v}-QYo!qj<-qeK_E%eBZA8(BP zT7nL9cGmj&?ItC`O?Sz^fB#<8d!>+Vs@F2LE1Fw9&sMr|C5sS!^RNfdAPVD8W$Uw<7zj? z`4i4thE232W@o4R_WisXucoe^k)EC&GSr5T)we$>VfrpX(5BeB3$>5Q#@=2F)H_$R zZa;EL&f_u@a_Q4GdQs<`k10qrXNvoXzVRp)!{oQ&sF*5dsH>|RnK{}Rep+1I_`Jmj z6<^<)HTrPo{YgG+eR8f*kv8679NcY zp8aYnd@up&RSL~iiB$*3zK|PHP>3$J2*;$R`n?`?bWBpBwW(S4GBPs4#Ks;&;y;_Y zlBd0So}Fzo)&4v(6YOQml$m9!3muo@HCN$))KXJRr(pJ0NfdVAkVt>^y#AXu>n@6cekhe)e8_&)m9o>r;)-`qJto1f)Yk09t@9-DI~B#<>Lr zJ!av^ED|#EZ_BmY)w3dnH@<%^YWnz5OG@g|xpU`=!Fu|mZuABpJTcVdPKr9nwsRy zEiF&zJiP4+-ZMTqiO$bgbf{U)A|yfCvs?lKs_Iz^Z8@qL(FqBtYXJwQo?51~WrU0I zG&(NL7;pFCC3RlFX5dM;etdZi;`&gvI+5r(62y7Iq;A(Hz$e8>ibsQF z`a91L86M_TDn^nW{PMl`C3e1MT|-wl7j`_|t?y8R++)JZ&VGIO?%jGtrVlrKYoUk+ zIoXak#elo-;^I?LRZVNlkhZB0Jg8S@&q#}T)x)8blrzCnj+I2jlI&r~!<#vA*jYSPsT*1q$a-98aeno|@xw&~$Yikd##~4z7U45xtzw@xq zvemnYlX|tjNLr3pyGKSw#x5#-sI{PBiL>f`VSa}}`jXpR^bEGI(B%H~^z;#0+U)dn z*8auYF^Vadb>S?Sz5Di+_-*^n4%cqaOja53&!;z(XJv6zZ{qu>!F3=e*tOm@nd#0~ zx3M|Rnc5fu>X;s=aOx{_NX_KO1g~_(JPr;HhETJOtGEYp7{;26L+rVz&Gj~4zf37j zMTQW9?39a|9R0qHG?c&69$!-gR94h)tLowPZ!%@WzLlj8!@g~k5XhFMvQz1ecIgxJ zP+WY0icwU9o10tI{l~Ku-!&_MOQszB))Rw6LhO_un0R=2h~EC3DkHl5cHbGSv^`vyQj=Txc@9x+pIv$LD!pUovGov~4oQOR09X#i3@anxBV9&CV`A zOF0#@Fxoit_Jo550E%Ao)!DoE9pe%a(T$0Tk&~4z+Aubuh8{NV#@o>20Hdz%j+>Zk z%Lo8mIRAYVr@jF>;_PgI>C%U%=Lv0*iwkad>thBHHTl>xZ7ssXwx$-JStSr(4ke5G z8G+SWTU)D@*$eP(|VtbAM1k(xs7|Oih#^m87dPB<}ao44z+_hL)Bh3zsf;q z*o|cQp@g{VHs}&}&MREQB2|@;=)8rv+6cvR2ieLnM@@vMQwHHawM{GUc0T2w`&3oC zR^s9GaIR`m35xwgll}$|{(`U1BI|bW2+2U*^WIpqTeQf__L&fDfqw`>tdjS*2(p+z zySkT0`^}NbAFD_>oB@4!W7~8CvTL9ZGpr~>%Ph1eL^^ijAIPjfWz^86E6ub^Dyxr< z@fz0*K(TzyecHa#-(+8Uze)=nwnO9(N@#>3WoEU#N%vmneC0#050jgg+f~j(#y8B0tILr z@R9Z>+yemc@uMQb1Lds0s}Yj`T*{WA(@~X|wkI#_janw9Eo#OOiW3* zb05eDMU7oDy1Gxpa`klAf+CY{5*MOb-6Sjtki9uNLBq(%9=Mc)3xDFeFepI(>624) zhQ-9RTwGj$@F@yB5CI|h%JKVv_QXZE*+2;1nFV+u7%x8uDrK4e1M8*Dmi{N!i?ObU z*U0ABa%VSntM%5#D%}4`AXNmfekpYLF}b!bPS_oa72%xo7xohR58Kw&Hr79?H1uDd zd1_SpJ;5^n%e|oFB%dVw@|tH=?63YIA%%^e+-z*6h;Ii05 z;t}uV7;^j*1XbP~ov$aJT4$W7P?r^%UkZ&o!%CW&m#5IEk$+}WQda53j-yNH{Dbq7 ztrL@gc1dvV?!q+Y!t>nJUkkY*Y_zQgt<8>_?R0b>srSwx_K@GFj~38b_{pVzzhaCJ z-0}z2A2_dNSo#!5t=~8=ou9yYHOqVMP?ruv0Giim>rfEJG5qBZb1$>)hhGWW!~!)5 z;Go@{2hj!#$AA)fFvaOIdA49{V^SOxMWACYGljb{_ohYM>q9dh^gw&SU7nq)_ziD$ zAAZ4_2N>=j;8v68ztA~aV^2e$wIu+iDIWR&EFLdl7M-QQ*vThYsYpa-P11gYTa)Au z9R$oXKo1Q;l&3`Tq;M5KFJUgmx%eLldgOHlfA_xtbfr6yv~Zf*=>$Pr-8D7pMg=`Y z>V#GHZ{Qm-^yG1QRq8%RCl=poQ-d}WC^KCk;n;G^%XJ33Js!s!pywd3FOHWw$#?_$NEEE zR)W)qXNeK|tmzhOEg>Bi0vdtmU<#M64F9eob7i*cz()!KCEw|2 zs0|cPS9>2O&iK-^l@<5?D2$9GV5kJxWAKDc*E2KGEM=mcFsOfUT&IsMfSUfvaTy%d zTyM3SNRm?Yy`cN>;X{ZfM^z89L^|h`$bt8%^&h!&jd&<+aLRunuHSl}bHY{$Ob4I? zTbdP`B!V*$u0#(sc=9{PMTosX9ksM{MLw5@L6Eghq{R+>@FOF-dN7Z$+2UKdckbNT ziRXXTXx#JPF!Ure-!y5xBg5==p4!6C9J&6A3;bYrtWsycS&sn6#37)>pO+gmda;z# z&_$J&C35WFc&Sgi5AjTKH-A^@frBw1C+II>egIXXojm}Vo#2)8CI17V14C}Jc03r`q713%TE~ha_3a{yP#gkJ6b2gxOB(D_G_i$xaI=cjaj z@`ClxjQ2ce&J-gX1LG-ykL%Q9FNoC0wcA^9a_3Amqra1uJL4e#P^drMazMpnQSVGe z8{=W_f*u>MFad$Xz{LTA!!959EeD0m_4@Fu%6d_jsENhOh8*M7R8k5f45DZNdE)3; zghj{Ur%xrj=>Hlq$wl^M0U^XwC9E37q4)yXSRVg3f=j!y-`?vG$jMX^6k1pOH-#Rj zbpkB`Abg^%3Jpy3WBiWn{pke!o)~Yhx9L`6=lnq5wyj019Dj0AmL|JZONqs;X>Hl} z27?}x$uO`3pcAVsMW5@8=yQ}@f7e6`u`*I^^5e70U{4=V?NXQKRzlE&HanxdVbU)M>$mt zHbA=w8vq}KisvsemqqShq~&FkCUjygmo6S-@xi}lB8;xN*0c!6t<-IEZ*R=_ zZNC|iBFr3=6wF8W%#@T9n2ha{f2B1)_k0hb4y zs5z6bR}5CV*>q$n0+A34JnxUMZ!}d@Zg?F-$O}0P<%NrHtz1g=`<52q+#=XRXRs>@ zg+`Z{H6BP7agxfAdaPFKyUFJ+Mnm~6*=?u|V^}%1ydLxPX)XnmyTtM1$77F?eGQ2E z#Qz4qxo~Gp29PV+wUMlc4%N8Wx$0%3=H^eT918Kzw2eHj2L%O9_Y|Z~&&+gv`t%F{ z*l+n|LjdMQ&IX+wd)X@1v4)%BUmMt59xeb7_XNOuJHIsc!Rd!rVon213;-(^J9|Uj zIp@sjX=?)N2MB!RQo}OBraQ0g4Yl|Nt|#fD(#c=%l1PKefxKL!vs{TbsabWA5D}^|)-j;keptyp=BEG;wRF%ENPQDw~)80XG~S>SpXWMmvX! z@nfWHk03)!p|$d{Uy4A5Q1wpDRnBvWrAIf8wq2t}W~N;WmKKt7=5sxq*89>D4NY*Okll+yN+Ce)g@?V>S65eQFEi0LPB|A% zB-Ty>qHk?w)dsAAg(Z!sfu|=nmd}t!wy)SKiVsf`X;kewRWX^e-fad9f#;9c6eS*u z7Nl%@4dg<7pTn+za{95mvAJQ3^>jB4%d7BD6;75wRlUdWr<=fH7#dGr{K0!#D=z{J z5A^B){Dw*#pWzsmui%90h??n=xJcMFXK%8C^jGGDM*`}(1fp%nAJMtJwc!D1voWD5 z%4SjM?Ci_|z^Hbus~|ce;s&_Nr^Qxr{(>HK>h{KDY4)poj~7m{1louG>(r@hU_C|1J+b*ZMOPzN zR*v!krmW!yD>c>B>MBeHa!O0Jke*8_kfX3JGg)8+j3RgH@n>dC@uzfvCjrYCqV`Q5 zy@<-o({Obyy{k!SY56fp75J>azP_c2M2DssTwltr`-cu67Bp`**3?%Ew$U~%TQq|jA>!{q^wtKtx z*(zU&_n5CJ9=8{-TZR%0yv5TcSZph zv%Wk}pzmsZKhEHO^67=zSX;OEJJjZb>p)EC!ZXm7+7F5XA9d%a$9~<4JI{fw%Ldi~ z;v_22*swPzCr1WCjdAK}9Tk-y`6sjv96$HK>7nTW*xHf350@YWb>fep8I}Y1j6(N$ zYhs3>`YQmGO<(6hnDhETJG2Dh323KKrlOiHjasmOutV-Jq>(` zmZoOR7QU<&8lca3NxIm$xNI_t0|Z{dxc2a)Z{7L&!VvQ((p!DDRwoIx4G>KDWbxX} z%&~Og^6O)(t#m$f6_ZPe!y6Mb!|T8gb-@7k+R_xgxNntx)q>RS`&X)gHq$G-aW^t5 zN)ypoO zX5$|Nz^<3ve1B-rd$EPX;t?;rM!?s$x;RO&TA-Z34j!Y^NDwsyZXHM+3=o4rhu$fQ z5N8ivfT z?6Pk933V?e-ov;Dze}=kW-SFKjt+iLIYB6gDK-7duNQ3iXRauMz1!XUolc#n6o^QS;6OaeLK7oUaAA06~s&^x}9~9v(P!;~m$8yy<8AY9CrlzJ!vkf$KC<2$RKe!+y)R#hdiA3@N zRE&j>skupa-a?aD`oiW6AOHKswV*)s4WACE!3il|P*`{dhJREElWDS)rwBz94C!bX z8uCJq`1MTy`q{Hr_%jGq`^AZtwUyEE+QroXCQq3j1Er z<-+6_?PX@lT}vM?&EM+57{WURO&>mVl2KoN78<%=KIAU?RQgAf-V&QQsW1i@1mcj5 zgo&k@{4!^#uJnu^ZP!+>@5{$-jb7XmD}7DBU2vcrd^=;K?8yH}K*&T}y34*6F)%ZR-CZ@0_PoCry6of-Q z=f{+f1m|&{dkCX2Zbe?pbBihZCDxiB5{3B}Smk7$oSdp)1a6WcH9kJ@#fujgVa5!= zl>M;+T@0emTmX@e1~B^u1{!Z8JG#1#LfQFc%q)xvNe`$`Fcd7@Jf}~aK{enAV?cp{ z?od9O!XVyLTg&Nkl~;HiuHh~j#mCiZZ!u=Hc7c-MVwK!9V+4$EyoDa>2=rV6moGE4 zUS3q_j*2%u#;bP(#`A(7J$l4-dL1{lIpEaV0G;KDR`G3b+uj%KqYo9#8%zcC^z>Nc znvY!FCQBeHtbSJ-Uma!K{Z;*A&KbM()c(N$UtIk+QS1OA>YA5wag}|7ga+ z!7)KiSYTjUS{m1py+u|XS#VK>BFkMw{dMZ@@62RlkA;TZTZZGsmO-p*4Ru ze&^o(?Vzx*u=w+qN_l!EJTu%1hvSTWJrooa+!fa;D&ze(s}1;s+0#dttLo0p;_!H> zBAw96Iqc~f?&!dxq9Qs5hR{>eVK`)l2|>FFlVL+m*j8K+q58VgoZdyCAZtDw7R>+YUNxWLy0PHP5q zR}m;3>gzx-h!tqVUZlhfN*BT)R}`qc^2_L*3WKHPWx|aQ=b4HgXJmYQHH`*3x1f}i z6fS_`JrAi5Ko?IiNXJG-w|ZzOM`sFZ!(d?P?Wyp~hbj#Cn0!~h=5eZB#bU9hFiGVI zgNK&LhgQ$BvpKrVYWLu`V`ejB75xZEQ4ymaJ`Pi0QO8CX~Ns zg%wlvv*|;r`7suVCT+dF9;)HhYt!iMTC;dprIgFZfqf5dF!f$)lXl$1^;jZ<4i&mD zjNEc?NZqKYsS!&^NHB%>#o+iH66Lp;eOFA5gfx6EwbSobk0-~^_S1p@jUhNH)x0F9 zB{Xd*IHxp~K$!J{*{5q&TWd25(6pHspLv6LD(qkchvq=nrdWON%vEpu{F!cZbF;{8 zen_Xt^m=y`mkA6x($mwYgY;{lKP1=-d`gm>oUE#)#i6&_zqYn!3Nxvy#>UKpgM;=F zuZLmQFX5J*-D4PjJ=4KBcdw!N&cuhOx2&y$0jp^Qa&`o!(uf19I zKVqgJY2U44qa+QIP-u1*Xtj+FQID76P6EjQ446N{oR&2=Vlj z=J6502ExIpKf~L-3?g^fp!qn+%gd|Vdddt&)IuN#u@EG7`}SFK3JT6v!05et9@oOC zq}jsO`n)vw(ud?r!ZRY#{&RD47d?JvhP1ARi(a}k3iyn`*|RhUYln&!$|{eP6c^Y3 z`0-=RC6tPaYSR0?ISg;kgse1UD=W>sy)WBir?M2>#yI$#7r$^%l-*g+kmsvld;9k7 zIh}fqJN3KIqb{`jKYOMQ`!L2f~F9S+lGgkVOK9V*=bo2 z+x!3_1#1Hkm)Gytnkp_1z##+3vHA9K#76%^Qdei^3t&kj&~I8MSHeLus;iOq66+%i)BkhI?uza$hFY^Nlkz`jX%r#vDQ0h#{)pT(-d7n4C44ut;~;D1XpPyI-q Y44$a#(}{F}M Date: Tue, 2 May 2017 09:31:42 +0200 Subject: [PATCH 054/602] Updates for pytest and mpl2 --- lib/matplotlib/colors.py | 1 - .../test_colors/boundarynorm_and_colorbar.png | Bin 16613 -> 16772 bytes lib/matplotlib/tests/test_colors.py | 6 +++--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 2a77bb7d9592..7c2d86913445 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1439,7 +1439,6 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): If the number of bins doesn't equal *ncolors*, the color is chosen by linear interpolation of the bin number onto color numbers. """ - if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") self.clip = clip diff --git a/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png b/lib/matplotlib/tests/baseline_images/test_colors/boundarynorm_and_colorbar.png index 999ac38b92e8df5c3a7bcc4f8c8058b8a1bb6133..59062a1a1900f7b7c0b266b9e2cfc7bd8b444ada 100644 GIT binary patch literal 16772 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

    %2AMdVf`LR90f0+E7vM1@G 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 Y44$a#(}{F}M Date: Tue, 2 May 2017 09:53:59 +0200 Subject: [PATCH 055/602] Add colorbar example to docs --- examples/api/colorbar_only.py | 90 +++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 examples/api/colorbar_only.py diff --git a/examples/api/colorbar_only.py b/examples/api/colorbar_only.py new file mode 100644 index 000000000000..cc852d942284 --- /dev/null +++ b/examples/api/colorbar_only.py @@ -0,0 +1,90 @@ +''' +==================== +Customized colorbars +==================== + +This example shows how to build colorbars without an attached mappable. +''' + +import matplotlib.pyplot as plt +import matplotlib as mpl + +# Make a figure and axes with dimensions as desired. +fig = plt.figure(figsize=(8, 5)) +ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1]) +ax2 = fig.add_axes([0.05, 0.6, 0.9, 0.1]) +ax3 = fig.add_axes([0.05, 0.35, 0.9, 0.1]) +ax4 = fig.add_axes([0.05, 0.1, 0.9, 0.1]) + +# Set the colormap and norm to correspond to the data for which +# the colorbar will be used. +cmap = mpl.cm.cool +norm = mpl.colors.Normalize(vmin=5, vmax=10) + +# ColorbarBase derives from ScalarMappable and puts a colorbar +# in a specified axes, so it has everything needed for a +# standalone colorbar. There are many more kwargs, but the +# following gives a basic continuous colorbar with ticks +# and labels. +cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, + norm=norm, + orientation='horizontal') +cb1.set_label('Some Units') + +# The second example shows how to make a discrete colorbar based on a +# continuous cmapnorm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cmap = mpl.cm.viridis +bounds = [-1, 2, 5, 7, 12, 15] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, + norm=norm, + orientation='horizontal') +cb2.set_label("Discrete intervals with extend='both' keyword") + + +# The third example illustrates the use of a ListedColormap, a +# BoundaryNorm, and extended ends to show the "over" and "under" +# value colors. +cmap = mpl.colors.ListedColormap(['r', 'g', 'b', 'c']) +cmap.set_over('0.25') +cmap.set_under('0.75') + +# If a ListedColormap is used, the length of the bounds array must be +# one greater than the length of the color list. The bounds must be +# monotonically increasing. +bounds = [1, 2, 4, 7, 8] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N) +cb3 = mpl.colorbar.ColorbarBase(ax3, cmap=cmap, + norm=norm, + # to use 'extend', you must + # specify two extra boundaries: + boundaries=[0] + bounds + [13], + extend='both', + ticks=bounds, # optional + spacing='proportional', + orientation='horizontal') +cb3.set_label('Discrete intervals, some other units') + +# The fourth example illustrates the use of custom length colorbar +# extensions, used on a colorbar with discrete intervals. +cmap = mpl.colors.ListedColormap([[0., .4, 1.], [0., .8, 1.], + [1., .8, 0.], [1., .4, 0.]]) +cmap.set_over((1., 0., 0.)) +cmap.set_under((0., 0., 1.)) + +bounds = [-1., -.5, 0., .5, 1.] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N) +cb4 = mpl.colorbar.ColorbarBase(ax4, cmap=cmap, + norm=norm, + boundaries=[-10] + bounds + [10], + extend='both', + # Make the length of each extension + # the same as the length of the + # interior colors: + extendfrac='auto', + ticks=bounds, + spacing='uniform', + orientation='horizontal') +cb4.set_label('Custom extension lengths, some other units') + +plt.show() From 3638d42cfb96e63e303920c6ec3700384398c76d Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Tue, 2 May 2017 12:25:46 +0200 Subject: [PATCH 056/602] what's new --- .../extend_kwarg_to_BoundaryNorm.rst | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst new file mode 100644 index 000000000000..b225b02b86f1 --- /dev/null +++ b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst @@ -0,0 +1,41 @@ +New "extend" keyword to colors.BoundaryNorm +------------------------------------------- + +:func:`~matplotlib.colors.BoundaryNorm` now has an ``extend`` kwarg. This is +useful when creating a discrete colorbar from a continuous colormap: when +setting ``extend`` to ``'both'``, ``'min'`` or ``'max'``, the colors are +interpolated so that the extensions have a different color than the inner +cells. + +Example +``````` +:: + + from matplotlib import pyplot as plt + import matplotlib as mpl + + # Make a figure and axes with dimensions as desired. + fig = plt.figure(figsize=(8, 3)) + ax1 = fig.add_axes([0.05, 0.7, 0.9, 0.2]) + ax2 = fig.add_axes([0.05, 0.2, 0.9, 0.2]) + + # Set the colormap and bounds + bounds = [-1, 2, 5, 7, 12, 15] + cmap = mpl.cm.get_cmap('viridis') + + # Default behavior + norm = mpl.colors.BoundaryNorm(bounds, cmap.N) + cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, + norm=norm, + extend='both', + orientation='horizontal') + cb1.set_label('Default BoundaryNorm ouput') + + # New behavior + norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') + cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, + norm=norm, + orientation='horizontal') + cb2.set_label("With new extend='both' keyword") + + plt.show() \ No newline at end of file From 754fd26a328a14ffaec6e3d0dd24c646a1cfa7dd Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Wed, 22 Aug 2018 14:06:47 +0200 Subject: [PATCH 057/602] Update example --- examples/api/colorbar_only.py | 90 ------------------------------- tutorials/colors/colorbar_only.py | 20 +++++++ 2 files changed, 20 insertions(+), 90 deletions(-) delete mode 100644 examples/api/colorbar_only.py diff --git a/examples/api/colorbar_only.py b/examples/api/colorbar_only.py deleted file mode 100644 index cc852d942284..000000000000 --- a/examples/api/colorbar_only.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -==================== -Customized colorbars -==================== - -This example shows how to build colorbars without an attached mappable. -''' - -import matplotlib.pyplot as plt -import matplotlib as mpl - -# Make a figure and axes with dimensions as desired. -fig = plt.figure(figsize=(8, 5)) -ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1]) -ax2 = fig.add_axes([0.05, 0.6, 0.9, 0.1]) -ax3 = fig.add_axes([0.05, 0.35, 0.9, 0.1]) -ax4 = fig.add_axes([0.05, 0.1, 0.9, 0.1]) - -# Set the colormap and norm to correspond to the data for which -# the colorbar will be used. -cmap = mpl.cm.cool -norm = mpl.colors.Normalize(vmin=5, vmax=10) - -# ColorbarBase derives from ScalarMappable and puts a colorbar -# in a specified axes, so it has everything needed for a -# standalone colorbar. There are many more kwargs, but the -# following gives a basic continuous colorbar with ticks -# and labels. -cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, - norm=norm, - orientation='horizontal') -cb1.set_label('Some Units') - -# The second example shows how to make a discrete colorbar based on a -# continuous cmapnorm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') -cmap = mpl.cm.viridis -bounds = [-1, 2, 5, 7, 12, 15] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') -cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, - norm=norm, - orientation='horizontal') -cb2.set_label("Discrete intervals with extend='both' keyword") - - -# The third example illustrates the use of a ListedColormap, a -# BoundaryNorm, and extended ends to show the "over" and "under" -# value colors. -cmap = mpl.colors.ListedColormap(['r', 'g', 'b', 'c']) -cmap.set_over('0.25') -cmap.set_under('0.75') - -# If a ListedColormap is used, the length of the bounds array must be -# one greater than the length of the color list. The bounds must be -# monotonically increasing. -bounds = [1, 2, 4, 7, 8] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N) -cb3 = mpl.colorbar.ColorbarBase(ax3, cmap=cmap, - norm=norm, - # to use 'extend', you must - # specify two extra boundaries: - boundaries=[0] + bounds + [13], - extend='both', - ticks=bounds, # optional - spacing='proportional', - orientation='horizontal') -cb3.set_label('Discrete intervals, some other units') - -# The fourth example illustrates the use of custom length colorbar -# extensions, used on a colorbar with discrete intervals. -cmap = mpl.colors.ListedColormap([[0., .4, 1.], [0., .8, 1.], - [1., .8, 0.], [1., .4, 0.]]) -cmap.set_over((1., 0., 0.)) -cmap.set_under((0., 0., 1.)) - -bounds = [-1., -.5, 0., .5, 1.] -norm = mpl.colors.BoundaryNorm(bounds, cmap.N) -cb4 = mpl.colorbar.ColorbarBase(ax4, cmap=cmap, - norm=norm, - boundaries=[-10] + bounds + [10], - extend='both', - # Make the length of each extension - # the same as the length of the - # interior colors: - extendfrac='auto', - ticks=bounds, - spacing='uniform', - orientation='horizontal') -cb4.set_label('Custom extension lengths, some other units') - -plt.show() diff --git a/tutorials/colors/colorbar_only.py b/tutorials/colors/colorbar_only.py index a047c3d840ee..712779ad482b 100644 --- a/tutorials/colors/colorbar_only.py +++ b/tutorials/colors/colorbar_only.py @@ -38,6 +38,26 @@ fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='horizontal', label='Some Units') + +############################################################################### +# Extended colorbar with continuous colorscale +# -------------------------------------------- + +# The second example shows how to make a discrete colorbar based on a +# continuous cmap. With the "extend" kwarg the appropriate colors are chosen to +# fill the colorspace, including the extensions: +fig, ax = plt.subplots(figsize=(6, 1)) +fig.subplots_adjust(bottom=0.5) + +cmap = mpl.cm.viridis +bounds = [-1, 2, 5, 7, 12, 15] +norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') +cb2 = mpl.colorbar.ColorbarBase(ax, cmap=cmap, + norm=norm, + orientation='horizontal') +cb2.set_label("Discrete intervals with extend='both' keyword") +fig.show() + ############################################################################### # Discrete intervals colorbar # --------------------------- From b6b9648b1be1a0178dffb942212f5083ef429255 Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Fri, 24 Aug 2018 10:41:33 +0200 Subject: [PATCH 058/602] Review --- .../extend_kwarg_to_BoundaryNorm.rst | 61 ++++++++++--------- lib/matplotlib/colors.py | 5 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst index b225b02b86f1..62886f2385f0 100644 --- a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst +++ b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst @@ -11,31 +11,36 @@ Example ``````` :: - from matplotlib import pyplot as plt - import matplotlib as mpl - - # Make a figure and axes with dimensions as desired. - fig = plt.figure(figsize=(8, 3)) - ax1 = fig.add_axes([0.05, 0.7, 0.9, 0.2]) - ax2 = fig.add_axes([0.05, 0.2, 0.9, 0.2]) - - # Set the colormap and bounds - bounds = [-1, 2, 5, 7, 12, 15] - cmap = mpl.cm.get_cmap('viridis') - - # Default behavior - norm = mpl.colors.BoundaryNorm(bounds, cmap.N) - cb1 = mpl.colorbar.ColorbarBase(ax1, cmap=cmap, - norm=norm, - extend='both', - orientation='horizontal') - cb1.set_label('Default BoundaryNorm ouput') - - # New behavior - norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both') - cb2 = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, - norm=norm, - orientation='horizontal') - cb2.set_label("With new extend='both' keyword") - - plt.show() \ No newline at end of file + import matplotlib.pyplot as plt + from matplotlib.colors import BoundaryNorm + import numpy as np + + # Make the data + dx, dy = 0.05, 0.05 + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) + z = z[:-1, :-1] + + # Z roughly varies between -1 and +1 + # my levels are chosen so that the color bar should be extended + levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] + cmap = plt.get_cmap('PiYG') + + # Before this change + plt.subplot(2, 1, 1) + norm = BoundaryNorm(levels, ncolors=cmap.N) + im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) + plt.colorbar(extend='both') + plt.axis([x.min(), x.max(), y.min(), y.max()]) + plt.title('pcolormesh with extended colorbar') + + # With the new keyword + norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') + plt.subplot(2, 1, 2) + im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) + plt.colorbar() # note that the colorbar is updated accordingly + plt.axis([x.min(), x.max(), y.min(), y.max()]) + plt.title('pcolormesh with extended BoundaryNorm') + + plt.show() diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 7c2d86913445..b3fc47a3b14a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1428,8 +1428,9 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. extend : str, optional - 'neither', 'both', 'min', or 'max': select the colors out of - cmap so that the extensions are considered in the interpolation + 'neither', 'both', 'min', or 'max': reserve the first (last) colors + of the colormap for data values below (above) the first (last) + boundary value. Notes ----- From 97558ce955ce1bbf483d9f80b34cfb6a189a4d54 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 23 Aug 2018 16:40:13 -1000 Subject: [PATCH 059/602] Simplify BoundaryNorm calculation. --- lib/matplotlib/colors.py | 49 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index b3fc47a3b14a..9ef0f9ce916b 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1427,18 +1427,23 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. - extend : str, optional - 'neither', 'both', 'min', or 'max': reserve the first (last) colors - of the colormap for data values below (above) the first (last) - boundary value. + extend : {'neither', 'both', 'min', 'max'}, optional + Extend the number of bins to include one or both of the + regions beyond the boundaries. For example, if ``extend`` + is 'min', then the color to which the region between the first + pair of boundaries is mapped will be distinct from the first + color in the colormap, and by default a + `~matplotlib.colorbar.Colorbar` will be drawn with + the triangle extension on the left side. Notes ----- *boundaries* defines the edges of bins, and data falling within a bin is mapped to the color with the same index. - If the number of bins doesn't equal *ncolors*, the color is chosen - by linear interpolation of the bin number onto color numbers. + If the number of bins, including any extensions, doesn't equal + *ncolors*, the color is chosen by linear interpolation of the + bin number onto color numbers. """ if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") @@ -1448,25 +1453,15 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self.boundaries = np.asarray(boundaries) self.N = len(self.boundaries) self.Ncmap = ncolors - - # Extension. We use the same trick as colorbar.py and add a fake - # boundary were needed. - _b = list(boundaries) - if extend == 'both': - _b = [_b[0] - 1] + _b + [_b[-1] + 1] - elif extend == 'min': - _b = [_b[0] - 1] + _b - elif extend == 'max': - _b = _b + [_b[-1] + 1] self.extend = extend - # needed for the interpolation but should not be seen from outside - self._b = np.array(_b) - self._N = len(self._b) - if self._N - 1 == self.Ncmap: - self._interp = False - else: - self._interp = True + self._N = self.N - 1 # number of colors needed + self._offset = 0 + if extend in ('min', 'both'): + self._N += 1 + self._offset = 1 + if extend in ('max', 'both'): + self._N += 1 def __call__(self, value, clip=None): if clip is None: @@ -1480,11 +1475,9 @@ def __call__(self, value, clip=None): max_col = self.Ncmap - 1 else: max_col = self.Ncmap - iret = np.zeros(xx.shape, dtype=np.int16) - for i, b in enumerate(self._b): - iret[xx >= b] = i - if self._interp: - scalefac = float(self.Ncmap - 1) / (self._N - 2) + iret = np.digitize(xx, self.boundaries) - 1 + self._offset + if self.Ncmap > self._N: + scalefac = (self.Ncmap - 1) / (self._N - 1) iret = (iret * scalefac).astype(np.int16) iret[xx < self.vmin] = -1 iret[xx >= self.vmax] = max_col From d6eb767bd6a45065158f881923ffb299b80dcb0f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 May 2020 18:14:03 -0400 Subject: [PATCH 060/602] Consolidate `test_figimage[01]` into a single test. --- .../baseline_images/test_image/figimage-1.pdf | Bin 59614 -> 0 bytes .../baseline_images/test_image/figimage-1.png | Bin 59270 -> 0 bytes .../{figimage-0.pdf => figimage.pdf} | Bin .../{figimage-0.png => figimage.png} | Bin lib/matplotlib/tests/test_image.py | 17 +++-------------- 5 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf delete mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-1.png rename lib/matplotlib/tests/baseline_images/test_image/{figimage-0.pdf => figimage.pdf} (100%) rename lib/matplotlib/tests/baseline_images/test_image/{figimage-0.png => figimage.png} (100%) diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf b/lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf deleted file mode 100644 index 53b98a11d5cb5531e6ba2d7ce60fef4be3616de0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59614 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πWGPgN+uMdUwPU-^i=0<`!w% zFjU6ySTS75(w(d(*zM6(p1fw+`YcJ3-iWYXsw*P|n}(*P56j&_SNdV)tPv4; z3$O!zI^oL9Q^UXidCkx#pl}S(qcg?)V#q&`Y1V2ji7)qTPcR@XH_^A*{D&dlpXJ`M zniFLw8Ow{IhM|Ur&R+PHYG1O{^he)F3qOu)7><(YnbwJvP9}WNjljNkGj$xMh6BB7b%Mf}TiWK+hQ+q6 zlQW)zI-*!2kw{W1ag|I%lAIF99Ri{fSA8EyhCyDKx==Gd-H+~45hMeih(RwI7oHK4 z8gykaI!@Ue6VwCH5<`XbgwOE(S`**!TVcr$yRT`u7ThmVD@=0{9l0^#(>_oSKITj_%syu`N#HMl?F9LTbrL zdb(_iab(VzYO{2U#evd{Rkw()w*at>bH`lIpr9xYST;PGZ^tiERLYwiK1<7`KttTL({2f7f9g>OAUpL04Z}p z<{J^5{#|llfka=>2QV{~ENPvMQ60@^1bWF1idh1!L(U1zTa89SU7I+rdLb&_5WUF? zSa!32ykvA>Tn`{)ZNs&8VWsl7s^*qwg4-pF>|G=TL##0YtdGSa|0ui)(V*rSWS3AOCyl>IFv31T6hUDO14OgNTybC zH;uJA45qH@9?}uImFtY$P_Y!6)0DZkX27j59|vEfsq^(rT4aM3gBASG79ksu$g8;n zz4Wv}aKw$l^OTAu7_i=fUHH2Zz{bn|%0$Gnu)U70Iv%fstsZs>TAflihvZ3YG6%fK z)<#f9xdy8#le1gZhR13XttP6Tw763tHF4H!MX1wb-XvABx>4Gyfu|4pPtvS(qm)h1 zDz)^$?!n7>!q9SBZO6AI>N#@ZGb0uRv1=>;L{@2vdzQsu>Y-rXAkrrCgs>$qc5Dhj zy@MrU1EgVUkZ@jTP!2KvD@W+@3iEL+q|SE1fzjkJmTeCBU+c?cM%y;Vdkagg8hyGk z-1@cfasraPUw=UI$UIgno;^_YikXovy%o- zDn^Ei)=)CdCeOS~f}!A^y2W8KGsUU^LCwfoFn_rTP2i<_O|1dD`_xlg%lT39bTrK1 z?Ba!ia0|NnG-)$Qi-H#RvPg_jP60g?vTIx^z(tkSYN3fk0^?oe0@9Kw6sTw>Z`R&W zBLrVUJ#056d5*}EpNnXPI5O--J<5UTreM_m{JP)G zS$u6cG0Z6&q&t}d2J`48!?KPQqTaOSrxh`zE7P7Ut~_Ay3qYpkh}%(Wp|5jx&|w-( znJWAzkgVkdzdJSkUyW#M}#Zc zDIDz|emND7E4_XFmrdEjD`Fk)(Dft`<05RmkoJ6PwW;Zf0C^F}%_*CtJBjC0G;GpF ze8LYgm($~m_pfyCo{Pt!-%uWn-zsIizX`e{wiQ`wZu*wBjW%Mvboq%>@$z&%JejT^ zo}*jw z0nOCT_@8GL$16-nr=1~H35XoA#!X_GXBs&b55o_+JN73+2$z!m9L|Gxjc`M@0C6pP z)A{jT-Q7EGG~P3uwla+JBbkrU`Fx(hC`wl*M(C-$Nuf}Fm?ub*;S%GWBlLaVd@5+C zK-5hip*1q3#M2`^(#>b7?T*%_X5@WufW4+0X4lcVAW9Tz5ThEZL?VgPoVieP*OZ{} z$vk?allfDJSH?3kF}mR|d+3t9q7cWFPXOST^|N`qI;&)iK^F4g>Z+Qms8HfqDdho+ zDZ<#+WNv@P0S^^mmh#}zhUm1#fQc{=QHLA*hp#VIDIo$&*zWqJQ}Y2%>~6o6oz=#; zDwj*EM$SJ@T*VMD-X$Ce5laGLa7uOwbfH3$Cjl8L<}%5?QTb)EU+iA&uuBOnrvYFJ zIB&&Tmx~y!6o4^bVge-C*{7O3UL;Q%s}<;BkIEGn{LWGTA~8JIgzBLd^{Lca^&hN< z)q19Ej5jabq$8K^F=|&e7MxmaSBP~sR(kk9ZNPZh&m)4{*JRKQuQ^;o4eLd;rcttr zB(q{Ee*awkGk>=DZSu@1^U>{G9{bO@oB8*0ig7I4AYZFG5d;#&+4W??>CDHevF)Y4 zHu&%X@|tnPZr-6B{4}j2R;4UA&LfanHj&!^@n12sGdbyrbb2DxCTBjXUs-GI#AjNs zY$vO(77B7w|6I+F59b-UO!BcS+O(dVi_)me_-p5J5=LpDO^I9(Zh$T=@pwU;X^K{{ z5J5|=KY=mzk~i^9$$a3>=fCkYCJQNhT37bnt9S|f-^3#fN!Lr8;M%DnbcFz|Pd83Bd7&r^1i}_USn+5#EFiW^Z_ABPka{~oGIQg1=5L3#9?9!%3jBxgHuOH2) z8#S%dW=H$llGlnz0=aH25i7^AE&Bf&H|4pD)IKzypu~8`)Q?xs=4)J^#+A|NZpN<% z7Q|d42!rOqmALEd){;A*PcnzBu5qJUhPhtc`D{3^1bsNFxUYko$-Gw*%?eA8BO&oQ zZ-Ab95`D*=KZ`gL?gX|>Axc4z^=f{#IW(jSbbu4NUg(Zt^C3Lp@Mgy1fc{BbtF zM~PuDI8eNq{~fWsq?N-gO*Si5#g%{c&tiKqAqMj)XpS0Z8V%{Tc9?6PX|Sg8%E^7c z{itu~2v1Hie?CzGuDgsU?7fCmM$1fIBR2z5MNOFilIQYa)XY;*q`9NXLszu6tf3hn zxZ%9N^#Wgc&iPmolI}}uqp@cDk-m?y-sJPOYVo;2F6Z!VCMRKvIl#v=9lL|ck23lf zuHvslLFcZ@oIQWGtUFS9FZK|dV+g!QWKjM&RUpzOu{gwjm-XLIT-1L5c?&I!cX-K| zeQ+}ZCf3P%EHx$z_);8qnRnQG+?U2cVjK8Ufp)CJaxJHFW5ej-pM8=y`QEBOdk-LA z_ZY@k7Jt9D*sx{I{Nz0Y5O(Bvdgle*0+;U0?!jfg?Qy^s2UYwG5P;2IZc;SbY>jna zw{@islr~$tuBVdx%ZA}13vo_uY; zIfby;y0?(jou3TUy^PufV+@i$h4!saudS{b6vqYD6pwk;YQ1WntoK)BzgI{kWy$*s zgrrI1j}4v8nQ}4EAagf4ct;~&*%n13lOc0kElyk}u(kc>KAYD*Un_9x2U$^|K$Puy zqH7+E{yvettFo7eOhhy6(oZ0|T>eB)oAoFAZqd0Z+YHPHr0#=IJ?i1J;Ra9kAWf33 z%u-L`GMTDM=A-L5!L8@lXvOD%`A}UO$!6bd3!>0l9Fec{uGg%bKFp8X``J$#t|{QJ zyB`nV1V0U4r3HLOkkD^@dUtvVn1y{e7f2kbTf|%XR!+mOFHdh3e&m z%9)yTh$>_=t{8S`B39$x_U?fHSi|BVEY*8*rf$;h1DgT@tIbJb@?1t%8i;lzFf+E| zb>|cW4wxH1m$1!}%_$Ja$d605wCk0_eqV33%reno+gzkQWL3(s4{0XeZKJ(gTJvwg zKt`l3&qv3O(Ka$bIfF6`D_jH5GBZFi7ljE>g?HjZ*i~*wK@xvn!2&A~QqWoY?+e^C zLmn1R1%Btx-h+n8-)(~(rhaxmFDDB$ySp^Mgu^;T!e)OE9;q7LF=VRN}}7t%H0De(YR&lgGnB zHaa!S$M`GKF0N7ytMBe>q4NPc+D>ZkQPFADrb7AknVOrstJ7QK-UwOIV>F-KzwhUP ziF8D&VM zaZ@rZMd2p`yR}xg>JwR~g1)8V>eBRK^i|_{#+wzGPNM-VL}7CMr-zwXnJyj*`BgLn zl%rIij5YKW(kzY?hb#rXO3Fp~@wO%|Ud`CQY;U52kZo)Ok@;;MH}by{i;%C_ke?^X z#J7O5aYssBf1H^%(vF`gt~502Tmw0CjASDv)ULVsxU(JHCA~r^6n9XWKoMO0ua|{` z^W^dNbG&9&Y(SgzDJPYjKzoUbP-)>QqPDwS_da)JUrmiR9w zzIC8s%R@ljWI;e#iGGNoQDJ)9J2z2j7*Elek!QN@KsdZ+x-L^^hqfGL!q-Pw7;=hS z^4LG9PW$O)i4?o`JhQuuJGQsgJ;lx&KZBmHpXo-qpUXp@F7p{=a|xA9OldC$bMhn%!6g4hSHq(Y6#cE!!mfrj<$H z<|IZg%y$a&pz8$T?8cg}6f!DeiqMZV1Z@jWyu_0PWhXGrPAL9KgJbHo99b5}pFw#T z8feiL2<4+?zEbK2cqDj4RE$&)76%%!Rj^gFRWtujTVEX)RoAsGB@)6-cPJn*#4wbg zbi>dM5)wm7BOOZ5&@HhJNNT`-}8IE`}xkF*E#E)z1Lbh z*4g{oiyWf-q5Wo*Ih}v@B_N+Ng#3`)sYiN+NGf5>b!*glNqtKu{P^Pro0NO)5ASy8 z8+CE44CbuF%U|m&c29@+5P{M5%FB_=ee&HQT~(Y!g$i@C`HLHh+2C4iG~_(lCx3>J zL`I)4)$}9v0=<0rYp8JP1=RVYi%g$GX_is^#(h!f@SZ9Io?+4?s+kEjUO@_e5}IGG=t{fEmo}4J zx5(VutH-hoQ#o&Yt#@q9IT`)+ zPy^A@{+xNkys{Ah?@)E~VcMyMpDDZDTSgX7pH7oBkN_e`$0;#@uf-(T9q8&cSM0V7 zU}x@YFlN4Jb#zk@^CkP@mN(mXt^rlR_?0X95s_=M$T2qJ_Y1SX-`$PP4}nv zUy&e+=hEV3rJ&>y{DP?g1by=g($H;?<`G|bvvlKiPPB@3kawJ78J2j2HTvVZrPs&d zlb5l|5>a@4q@|VzhuoORhRmS5^mx1S!0?$?Z;_{@^;*Y{dsv&l-VJ(x8RI%-IN2tC8%C7^UN(RL_vpn0A~}9* z>v}53l7A4?i_O$ztxr`oR2k6(#l`mQKZWCx*V-yT#H;(r!I|t@ydNHYBIvM`e|7*6 zqDuMVf$MIdbkr@qv9+uL*EMxHU5-H}0<@a4Iqw>7gihf+&uH-vC$6eW=O62wH(&5u z9uQJ0?V^oP_W)F_FydL;&m6`pcod5kVI+J$02Lr(siaG|`|YtsV>2(v*CYNzfOcJI!_;@Cnd#rezM2-ZLBJ8@4u(43_rh zN1`GKkt`gRM}>+01v!nHN+|5?@dY$INVvq*>ZbEb8JPaUqtF{}ANDPb%&_W@Iv4)3Vd9maz2>AF!y=SbpY9t528d2QC(m z99m!s)wjmw=T)RDWLE|szK3VfT>xSwJQCK`;18(+nVO^PY&Ji?1Af#ge)rord>NUS~P~ye8Utb_~eVb8m`-8H(@GW3`;+ps->V^g zK(Gb3uFpN0e`Lep$OzUNyP+NLli=6p)0JX=gtp@q!?yn}5&#hmnMF zO6#k;ij#p-Qjt%thV3i2bcVUhoM*rWdvH*3>oUb&`q)-@8h8CM+tD8F}YPv~N}{dI>$Q)STd5wUulbOwg`LgEvpBSYDHc zWGakX7yUM!&q}p{x2Y~KW%ibS#oGiP|{ChqOhb+=KVf2$Rmc=*5pmZ4Yj{=PB;!uiV3zqtApEKNts_ zU7KB7tHRB47jlelcGNvXEvC=)L&^m^(uo%9-2zY>oFSn zK(g}y6g({6D~?C1cLxLyJPG>RyU-VNmHT7nhZY=#OSIL$jOisy$5L7Xf!ABRgwpXH zE4NY_@xRlLir$JBX^j&ConLY?`hG|+uJqYj)YSq68s0LSwACJaa>058gJt}^N zGnC=}OB&5Qzsm8#2P{jughBf~qxJKa9Yc3!1+7cM4PhxpX~2A7R~1!rF#jGf@0k4c z+m|6DxkBr#GC)w(H=+tzbPfd+tX@L1HwmCt0zlDkU zHocS5CuhnbKDcQTmxmM!SHL^wg6l08ZQOd87vWL2R%YJo>6&}@6XW0qsLhVG&0n2Q z+O8WZ5~x!4Q$q`H=&x$Jf7@+|`}WNmD6trZAS6u$3w^v!bfp8js>np*%X-%rR*Zi9 zLC}9%c|o(&QAgT_tb;rFIHHR~J53g{w^JCnqGG4&8CFf?F^4~xBZ@V9<2r^XdnmlJ z@cc1}y7`^5kmc>ZWtNqDY(3e_RGR2$y+IZ@^JWN%{cE;+A56b)Z|rhfbn3}o zBDQURtQ<53d9D@2p1bFRpTqX2>H3PGe{+W~`42IsQ=l%eX*R^H3Xuv4umIjjoP>bv7w6semhu?V{?@gUYr$-zqVrscf2 zFCON}I;_|y`D07H{|K$lmTlitHslwj@-{gs_%e;Z?#JC*OJ03cRP)!I_WYSz$OVh_ zUFuZEJd@qh^AWL{c&puPZp$v%5Bmw_^*~8_NXhx;=q0%Xz;~V|jiHoLvFvdG3xECr zIW{4SW}a8jNLQ+7vVZo_HQI}RoO-B+-*>K!t8u{!VztIQJpxKzM zEg+MTkciZW68sh!2Z#fRA#6+33VQ@)EmSU%rW}NXj)_*#Y@phi9OTK1Kiuk2BeP+m zL9<_w6&iOwI}W>NA*%U~pR0w#AWuxPhSIX+EsOY*(^Ysp4psT_)im|3z@A{r%jM9A z(pKKExt^OZoLWt!n91b^Tp&{Z;rUQ?6L;;E>!sHv__rM0dHY>Nt4)RI2mD4$1}&S1 zl)E*S)j6K^T6)$~1-|-z7TXtMv(e!^j+2tjN874_5Q2*&TTIi8CH}7zF z7)>^KMz=J~l4BnGn4E&>%RA>~>eFU}oXj(``rw5l1EI0+tkwL&l7pKz;@JXfyvIF9 z`L{D`A%iPxyDS-N%QJ0sM-)!ue)9}>j*V4a1eeR&b@TJW0` z&2>1leGPl4wBN`s(HV|6k#Axw+kP0hVP3Fu2;R`zJMC>~k><%^_xQ?XwNfC47??g( zm?+-W&LzB3E-Rz01KURRH1rizst-+!rP}#Ju$noVjYmm`5F!?b=ts_VPdWUpb)2?v z?M4i4(^jm9SJ9rP)BV(+en+Nfre?xO@-qlhGfdExWy7(9^SXAJP=AHaghGz}@n!y< zQPm)Y(KVz0!+p@5#bJt1TacslQBRB2I>ou_E+z~np9owgNVEzYhrQh00K0zYo}>*a zlGvbAFJsYcnH~WrL`@#^`fRn^2P4`|LETdHW~wil&Z0c|4o*?+Wr9v#%?u>&Wq^s! zfzcYqHZ(Y^$+JNi;bd%W7c1o^JkE&>d-07YmP$9QZtU0wN!uUw*GFJ^Gg{5MJgJz6 zQq4F<(w~I6E*2E)99v#R%Ij!o2MFjl=rib}Vtje)!=4udZTWp))oOawaem<7HVwar zE%Lzl;-vE=R}*@+9lX1MR>YX&nT55QGg(71v&+lJW?}Dd94{hT<+lOLyGIn)2xWYB zL*Eqm{5G0!Y)IXWs~Xe(gB$!*dA88o<=+oc=|Z*c*(W|#GfJEz-?>Gip0vnAY=2Jp z$8*t8*_uBlYNBF%#X^;%l3U3;@r`FCt8-p%bd;A3M#%fbP)@^iJ0;^5rc_pfA8C-0 zZJ%9b=P*)7-tzd$AlSI8TyQX;@FUZw9(AxcOo7zmBo!IQaIzkHbnsiH<5#sup6;OD zg~bW#t#IjqOz7aIJE0=1V>77tnnF#Zai)d#kXVkMCC<|L`G|PTw|#WmZrcXcMMDC9 zbzGYeNl7ZkGED)jlIz49lV{g9jRC5GwuZN3!#uHoRqBOwY3p}neV%-j&nIXN)Y_dD zYkA>l=Lwe_dbT;)+$y!+XfoN%c1MqF5Lth4iNiz2-yNQ6tegTrV~SYObN6b zAi?9Ti2l%mMwWNeTb~`fhEd?M7?11O`JQA zBRXS9)7&D;?@!WhVrX;PH%Cv$F!SagaS#{8g-VBhJxuj{f9YFk`Aw@(OVi%p>oVn) zxerCE-~_|8SL^1uqDk`4#4F)z;oKezK+3G&TkU}IxA3^~><>*fSvsB^L*tbLA~gOc zIuzrSz(1?#Vf(o4DkmlBYJLSRLg+wxn}T`oQOZ1e7~+zSaL_4FH!@a=C{FtR8e7O+ z`-<|R4rK51R~@f8=MCU1T{pOEtZOzHG&j2`x3ipdKM{TT4_FyNknx z2F&K<{+6+0h5T7V7F((I&IBXi^ns+7nca`1fH1S+GLgXKA`R|QedjD5-lqnFMuO14 zY|MALNz}FN^ebg8%q#bu(kau}fgH7UX5YXDYDtRW5NPU(qJlbMX1?sRPs?;NEX~o= zCe#5sj=zi;zli2dD^qw(ekw`(#B=}UKIrPJn1nw6C3iOkpdh$KV#!UDdub;BSR93$ zHoe3W1es^j;{#7%qPkK6StL+d8KIY8XDrf3T3jj-P6ks8@;c38FxufA_$l_qS&Oig zOkRa+kQ4*MxoIkQ@=TNU5IwMpXi{|(K z_2!RQq|pFrX#V2&EYoAc#G-Y}?$jHX8x}5_9XJopbuDL>P&1B_FSxoX+5WKbDni+v zfPR{^kCXA-aRcf$m6xs=G{RbRxV#?O;@j6n4Ca`(a$mD_$D(*MMC3F`^^&cExuggE zwMw;nq4yp0a1Do6MPw0=pk5X0IE7-mY7e$0BFG>^iP%ccRa2gYAu_Xx<6{p1pR1YC z_AzRn+)-8vZv-~j0;gcQqKcp!w=;S8*Pk`)Iw|kZpM%X=rBNCeKhe0Y%W>cH-Cqd>p|LhJHl9hPDqY2 zmkLBZA+9#Je*`t;w1k@9rtY2(z4woU}1l(WR?Rrd|cQ=FbSo+LSY5_65jq zdl3@UX=*3u+&01Y+#WeL@Us~_2KC%h(X3%X)1YqKu9BGoSe%Vcfy+}2@FLES#b4~z zzeKs-lHzYw-O*G$o%t9>?(#$Al`p46$`U!M;$ zDNmXr@Q$R9=A^?|IB|UFCp-@AQSJ_EvrFd|)8{yGAA9lg>tAq`PFw`8ZcdM9IL*7ogw0YTp>e#?B_+m&6a@NpRB+Ax`%THbq;#IFD>U)YZ?iz^?g zu|8S}Yi7F)V6hV9WYeU$k z*|6g>P>FOEfYV~*N8DkVi!>~3t*UidlCa%F`lJ0$d@xL!c+|PdJh&6MC5ZsB`O@6$ zA%^yL2cb7H^UnpM<-_p)tJ4D0%7-5G36!#5Moue?0c1$Wd$SDk6fL0V zCn2WK2~dKnp8=aKOzC_5niGc$We$s0>-zWcos#8167UMS+4OmpGtp_FC*f z2fi}Xa7Xxca(g8&(dg54ORw!(y}rBKacDpwqMH5|)L}ctVN`JUaZ%_rA+~YG&p?9U zlHN{R>5e_9H=Y|pac^-KVL$dh>8i_C7wA2gYo*HT65Q~IR4iPIcXolP3E~naiP*6L zu;Xn!9|A&Z_0aFdMZ79z7Tm+(>8zUr>(Hq%Blt6JiRoP@Ne;@n>*|2>Kz0|Xa zn?2Xr6Kc}S(`_%$Id;C;T!V8dtM6wp{Sf@7GO^Pj!8~6L_1&LX$hO9F~S1KY-?}nu-YlCQcG4KSzdqNwBQnJC6jAGV zDY0G>?NNQDa7UQeMLD=8{dh#oz1BPICu#$Cq^s1JV71!-zQE1VscRn?D^*=UU zwY$|8MXU*i8m|6b@ZE#pTWOCN14b=D`zSj7)TY`&18e_iGbX2j`{3;S?_9*Q&HYFC z;2Cu6X5r!ek;Jk<$wk{i(G>BR?=4nGQK%eQ+|R?`Ws@Hf+aW`AhazU!llvM0r>6OK zVL`J2I$}^U#W#@I+uwE7`!UX7{i2^QwI)YkU|Jc4HbGT-FpaaEzpRw9CR>YRPOOPa z3y8hgfVrFs=*6hP8}p2d9O)VOns-GbQal_Khc1tkC|YU7zkd#bJKT?kujiTyPKETwk{Hq)iw?1{oBOz> zk6yyoZ6*ntnPAeM79QRGD5Dq4APF3r!Hj&35wRRAM+Idhk>)S#je!?^;U@6um~Fyu z`l#EcemZX}Y=UTI_(T{8A<=D`1UEKK*GuHgM*iymHBqM&@LfY2w}W~OuEOIf<5F7M zhx{&Kr%|h8n!5RkOXGH7{&lIJBUlq;p7pl(Y0_6YP2}TRhuk|O8PMgMasxQa9sU8$ z4WZOt(mnGYPLQa*%X>Che>)N&o88nKM5>x9qZ}uvkO%WbC>J;?EIP zQ6^;RKxtPu(N6u&{k}&G>W>#}tn)L!<#Dh|a1=-F59bbmnbKC!SA2)IP^n}!e#8U$ z3!VC4SM?R|J5U52t}tv>E;t3}J^bC9$W$&*gg-(Fd3rCHw8C-m7SCu4rPXR`xUsib zx1{}&s+U`+vCieeoS;iFMNEQXp7FDEW7W#F=zGvh>Ge%E%n*pA4Eg<*X9_*Abou-y zU?fo9)7K;@g`uV%cdkS8KcQ9x*qE6mnTwB;swv?q#Kq& zoaP?qp}lKxh;x6bv=!jM-SMQd8h97zzH?)?!^x$XFjMho*jZC?EZ32^H+YxBUjbRZuj1^AKDZ)7I*alhV8_j>sRH759qVs-9*X0rJNlaUODQ=!dXFTuF3^qV^g(FccgrqjjevNwZt%KpKq8N8E4l#-z>`TF0)d1uIrL zt4aAVWL*n4C-$-P>BwA|+Y0p(Dkbil=yY5!(FaT=j}tghkR;g<^Bicckr}F7vHdY^ zg>H2|3esoO$#?0S?BO&)z|1q;Xum#Ue8ioR?LG0zrgGr2jDY*r!Ne*daF%t2U@!|i z+jB@W`B5-g2%lVn{#@nvNinj}yZx$Jm@R=<@i#0$-0bMfcT&VUdt>;m^Rv%6ExHyc zb2O}Kxtp@XT0xu^t{Z>4bA!%sD!Fb3_FoS>TZCFz>Gx`acQKcpOUWR? zJK`s}D$GM1dtr^>oun3J>T4vad{ZQ|cVXZILDDMPtw##Sk`8Ml;w%F1s^uEshn0>S z5=%kdy|iwsY+9G6sOp(Nm(a;)r}*aiKS*xBx%wBqOs$yCorbOxl?2xj9I+KoxJOVG zOl1WJ*L=j0HxBDN+=6;$74k%p&3X6#&Ci`4p5v(XCc8+u1D0e6( z+Mju2DucvFvH%>bYSD@uRAe9FV?j53$Ox03S^E}SkteiCEo=+Y14w+P-<=|6T~rN) zZTkcEOR_|7Zk8@+q?&tgaxR99g{)urg?eHE8H+b7^B@}mpPS4e_se!+;z~&EJG&sH z6p7wefW)daiU-Z_88ujXY|=~FUY%LDB}dvOZbOb`zP8rw}u+>;SbXIW? zb=g;$d;MmdjZ&Z2E37|H_>n&*`?d%Kl3cCT%XFY^svEDJx;;IB%aZRP|K)JbxiN3zasOM{EQP~mRMHlp#Fk?U-q zSOHi}#z&?|d$Ff#ws0+3aal|#MC-?Vn^(|{?e8tvfM|^N8p_g;rt%zr#9e7nHfv9# zeHpJ`@zr$j6JTN5D?!h>QKm~V{!BVf zI*z%=!Y>j~{r^yh|0d)9i#jX<5*7Qu)M3H@U=GW>y><0w1+fCbj<#N`uUJ6>|Dx}j zvjX+~-65<%Z7X|-7ps)izh(cv|MwduB>n^S^Mu$D3a|=-2nGIqv5Ja_2#K)TvHlwa ziL(CdF01SR!9>IbgfNBq`}#K~ARsD$$v62=Ob|nY{wF5(-`H~jOpX8X+;bsJng4+a zh+^o||Gh%`0sCsi2Zl@o{Rre79;~!Q&)oG zV&ebAgoQ-JLI04&==5(J{y%lYVB-IwXKzm{$2SnqzfA~Kb+vP275rbL19jcp{`Q6c i>jlllYP^#Sf7s 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

    `s& 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 P00000NkvXXu0mjfAhhzM diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-0.pdf b/lib/matplotlib/tests/baseline_images/test_image/figimage.pdf similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_image/figimage-0.pdf rename to lib/matplotlib/tests/baseline_images/test_image/figimage.pdf diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-0.png b/lib/matplotlib/tests/baseline_images/test_image/figimage.png similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_image/figimage-0.png rename to lib/matplotlib/tests/baseline_images/test_image/figimage.png diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index fea5f8a1ac13..6d5c30feb9b1 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -78,8 +78,9 @@ def test_interp_nearest_vs_none(): ax2.set_title('interpolation nearest') -def do_figimage(suppressComposite): - """Helper for the next two tests.""" +@pytest.mark.parametrize('suppressComposite', [False, True]) +@image_comparison(['figimage'], extensions=['png', 'pdf']) +def test_figimage(suppressComposite): fig = plt.figure(figsize=(2, 2), dpi=100) fig.suppressComposite = suppressComposite x, y = np.ix_(np.arange(100) / 100.0, np.arange(100) / 100) @@ -93,18 +94,6 @@ def do_figimage(suppressComposite): fig.figimage(img[::-1, ::-1], xo=100, yo=100, origin='lower') -@image_comparison(['figimage-0'], extensions=['png', 'pdf']) -def test_figimage0(): - suppressComposite = False - do_figimage(suppressComposite) - - -@image_comparison(['figimage-1'], extensions=['png', 'pdf']) -def test_figimage1(): - suppressComposite = True - do_figimage(suppressComposite) - - def test_image_python_io(): fig, ax = plt.subplots() ax.plot([1, 2, 3]) From 4a991877a3c7e2d330ac17c780f5bedf67b35c99 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 29 May 2020 00:48:02 +0200 Subject: [PATCH 061/602] Update docs on subplot2grid --- lib/matplotlib/pyplot.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 32f63db834c1..389633d790d9 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -872,7 +872,7 @@ def axes(arg=None, **kwargs): The exact behavior of this function depends on the type: - *None*: A new full window axes is added using - ``subplot(111, **kwargs)`` + ``subplot(111, **kwargs)``. - 4-tuple of floats *rect* = ``[left, bottom, width, height]``. A new axes is added with dimensions *rect* in normalized (0, 1) units using `~.Figure.add_axes` on the current figure. @@ -1278,7 +1278,7 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): """ - Create an axis at specific location inside a regular grid. + Create a subplot at a specific location inside a regular grid. Parameters ---------- @@ -1286,14 +1286,24 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): Number of rows and of columns of the grid in which to place axis. loc : (int, int) Row number and column number of the axis location within the grid. - rowspan : int + rowspan : int, default: 1 Number of rows for the axis to span to the right. - colspan : int + colspan : int. default: 1 Number of columns for the axis to span downwards. fig : `.Figure`, optional - Figure to place axis in. Defaults to current figure. + Figure to place the subplot in. Defaults to the current figure. **kwargs - Additional keyword arguments are handed to `add_subplot`. + Additional keyword arguments are handed to `~.Figure.add_subplot`. + + Returns + ------- + `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + + The axes of the subplot. The returned axes base class depends on + the projection used. It is `~.axes.Axes` if rectilinear projection + are used and `.projections.polar.PolarAxes` if polar projection + are used. The returned axes is then a subplot subclass of the + base class. Notes ----- From 5a985413b3252c18ea306ec3b57b485472b15cfc Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 28 May 2020 15:03:45 -1000 Subject: [PATCH 062/602] Add BoundaryNorm extend kwarg to colormapnorms. --- tutorials/colors/colormapnorms.py | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 6090fcc25f77..5c176b925ddf 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -145,7 +145,9 @@ # Another normalization that comes with Matplotlib is `.colors.BoundaryNorm`. # In addition to *vmin* and *vmax*, this takes as arguments boundaries between # which data is to be mapped. The colors are then linearly distributed between -# these "bounds". For instance: +# these "bounds". It can also take an *extend* argument to add upper and/or +# lower out-of-bounds values to the range over which the colors are +# distributed For instance: # # .. ipython:: # @@ -161,30 +163,42 @@ # Note: Unlike the other norms, this norm returns values from 0 to *ncolors*-1. N = 100 -X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)] +X, Y = np.meshgrid(np.linspace(-3, 3, N), np.linspace(-2, 2, N)) Z1 = np.exp(-X**2 - Y**2) Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) -Z = (Z1 - Z2) * 2 +Z = ((Z1 - Z2) * 2)[:-1, :-1] -fig, ax = plt.subplots(3, 1, figsize=(8, 8)) +fig, ax = plt.subplots(2, 2, figsize=(8, 6), constrained_layout=True) ax = ax.flatten() -# even bounds gives a contour-like effect -bounds = np.linspace(-1, 1, 10) -norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) -pcm = ax[0].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r', shading='auto') -fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical') -# uneven bounds changes the colormapping: -bounds = np.array([-0.25, -0.125, 0, 0.5, 1]) +# Default norm: +pcm = ax[0].pcolormesh(X, Y, Z, cmap='RdBu_r') +fig.colorbar(pcm, ax=ax[0], orientation='vertical') +ax[0].set_title('Default norm') + +# Even bounds give a contour-like effect: +bounds = np.linspace(-1.5, 1.5, 7) norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) -pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r', shading='auto') +pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical') +ax[1].set_title('BoundaryNorm: 7 boundaries') -pcm = ax[2].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z), shading='auto') +# Bounds may be unevenly spaced: +bounds = np.array([-0.2, -0.1, 0, 0.5, 1]) +norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256) +pcm = ax[2].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical') +ax[2].set_title('BoundaryNorm: nonuniform') + +# With out-of-bounds colors: +bounds = np.linspace(-1.5, 1.5, 7) +norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256, extend='both') +pcm = ax[3].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r') +# The colorbar inherits the "extend" argument from BoundaryNorm. +fig.colorbar(pcm, ax=ax[3], orientation='vertical') +ax[3].set_title('BoundaryNorm: extend="both"') plt.show() - ############################################################################### # TwoSlopeNorm: Different mapping on either side of a center # ---------------------------------------------------------- From b5053b6a49c3b192796b1a982f77173f315946e6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 May 2020 21:09:13 -0400 Subject: [PATCH 063/602] Combine some boxplot tests. --- lib/matplotlib/tests/test_axes.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3efee619c8f3..cd8a8a961e32 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2610,28 +2610,24 @@ def test_boxplot_no_weird_whisker(): ax1.xaxis.grid(False) -def test_boxplot_bad_medians_1(): +def test_boxplot_bad_medians(): x = np.linspace(-7, 7, 140) x = np.hstack([-25, x, 25]) fig, ax = plt.subplots() with pytest.raises(ValueError): ax.boxplot(x, usermedians=[1, 2]) - - -def test_boxplot_bad_medians_2(): - x = np.linspace(-7, 7, 140) - x = np.hstack([-25, x, 25]) - fig, ax = plt.subplots() with pytest.raises(ValueError): ax.boxplot([x, x], usermedians=[[1, 2], [1, 2]]) -def test_boxplot_bad_ci_1(): +def test_boxplot_bad_ci(): x = np.linspace(-7, 7, 140) x = np.hstack([-25, x, 25]) fig, ax = plt.subplots() with pytest.raises(ValueError): ax.boxplot([x, x], conf_intervals=[[1, 2]]) + with pytest.raises(ValueError): + ax.boxplot([x, x], conf_intervals=[[1, 2], [1]]) def test_boxplot_zorder(): @@ -2641,14 +2637,6 @@ def test_boxplot_zorder(): assert ax.boxplot(x, zorder=10)['boxes'][0].get_zorder() == 10 -def test_boxplot_bad_ci_2(): - x = np.linspace(-7, 7, 140) - x = np.hstack([-25, x, 25]) - fig, ax = plt.subplots() - with pytest.raises(ValueError): - ax.boxplot([x, x], conf_intervals=[[1, 2], [1]]) - - def test_boxplot_marker_behavior(): plt.rcParams['lines.marker'] = 's' plt.rcParams['boxplot.flierprops.marker'] = 'o' From ffce3b91a70ea9ad49620194ae8d90e08210e6db Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 28 May 2020 15:15:49 -1000 Subject: [PATCH 064/602] Move whats_new entry to next_whats_new --- ...020-05-28-extend_kwarg_to_BoundaryNorm.rst | 48 +++++++++++++++++++ .../extend_kwarg_to_BoundaryNorm.rst | 46 ------------------ 2 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst delete mode 100644 doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst diff --git a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst new file mode 100644 index 000000000000..a1e01da016b2 --- /dev/null +++ b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst @@ -0,0 +1,48 @@ +New "extend" keyword to colors.BoundaryNorm +------------------------------------------- + +`~.colors.BoundaryNorm` now has an ``extend`` kwarg, +analogous to ``extend`` in ~.axes._axes.Axes.contourf`. When set to +'both', 'min', or 'max', it interpolates such that the corresponding +out-of-range values are mapped to colors distinct from their in-range +neighbors. The colorbar inherits the ``extend`` argument from the +norm, so with ``extend='both'``, for example, the colorbar will have +triangular extensions for out-of-range values with colors that differ +from adjacent colors. + + .. plot:: + + import matplotlib.pyplot as plt + from matplotlib.colors import BoundaryNorm + import numpy as np + + # Make the data + dx, dy = 0.05, 0.05 + y, x = np.mgrid[slice(1, 5 + dy, dy), + slice(1, 5 + dx, dx)] + z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) + z = z[:-1, :-1] + + # Z roughly varies between -1 and +1. + # Color boundary levels range from -0.8 to 0.8, so there are out-of-bounds + # areas. + levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] + cmap = plt.get_cmap('PiYG') + + fig, axs = plt.subplots(nrows=2, constrained_layout=True, sharex=True) + + # Before this change: + norm = BoundaryNorm(levels, ncolors=cmap.N) + im = axs[0].pcolormesh(x, y, z, cmap=cmap, norm=norm) + fig.colorbar(im, ax=axs[0], extend='both') + axs[0].axis([x.min(), x.max(), y.min(), y.max()]) + axs[0].set_title("Colorbar with extend='both'") + + # With the new keyword: + norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') + im = axs[1].pcolormesh(x, y, z, cmap=cmap, norm=norm) + fig.colorbar(im, ax=axs[1]) # note that the colorbar is updated accordingly + axs[1].axis([x.min(), x.max(), y.min(), y.max()]) + axs[1].set_title("BoundaryNorm with extend='both'") + + plt.show() diff --git a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst b/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst deleted file mode 100644 index 62886f2385f0..000000000000 --- a/doc/users/whats_new/extend_kwarg_to_BoundaryNorm.rst +++ /dev/null @@ -1,46 +0,0 @@ -New "extend" keyword to colors.BoundaryNorm -------------------------------------------- - -:func:`~matplotlib.colors.BoundaryNorm` now has an ``extend`` kwarg. This is -useful when creating a discrete colorbar from a continuous colormap: when -setting ``extend`` to ``'both'``, ``'min'`` or ``'max'``, the colors are -interpolated so that the extensions have a different color than the inner -cells. - -Example -``````` -:: - - import matplotlib.pyplot as plt - from matplotlib.colors import BoundaryNorm - import numpy as np - - # Make the data - dx, dy = 0.05, 0.05 - y, x = np.mgrid[slice(1, 5 + dy, dy), - slice(1, 5 + dx, dx)] - z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) - z = z[:-1, :-1] - - # Z roughly varies between -1 and +1 - # my levels are chosen so that the color bar should be extended - levels = [-0.8, -0.5, -0.2, 0.2, 0.5, 0.8] - cmap = plt.get_cmap('PiYG') - - # Before this change - plt.subplot(2, 1, 1) - norm = BoundaryNorm(levels, ncolors=cmap.N) - im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) - plt.colorbar(extend='both') - plt.axis([x.min(), x.max(), y.min(), y.max()]) - plt.title('pcolormesh with extended colorbar') - - # With the new keyword - norm = BoundaryNorm(levels, ncolors=cmap.N, extend='both') - plt.subplot(2, 1, 2) - im = plt.pcolormesh(x, y, z, cmap=cmap, norm=norm) - plt.colorbar() # note that the colorbar is updated accordingly - plt.axis([x.min(), x.max(), y.min(), y.max()]) - plt.title('pcolormesh with extended BoundaryNorm') - - plt.show() From 92a33aa7d85a8f59e600e579dfd44ee5a7214b8e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 29 May 2020 13:07:50 +0200 Subject: [PATCH 065/602] Skip None entries in png metadata. --- lib/matplotlib/backends/backend_agg.py | 4 ++-- lib/matplotlib/image.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 2d1f0a0dfd39..658503e3c37d 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -484,8 +484,8 @@ def print_png(self, filename_or_obj, *args, Other keywords may be invented for other purposes. - If 'Software' is not given, an autogenerated value for matplotlib - will be used. + If 'Software' is not given, an autogenerated value for Matplotlib + will be used. This can be removed by setting it to *None*. For more details see the `PNG specification`_. diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 51f8a39825ff..803baa673e2d 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1543,7 +1543,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, } pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo() for k, v in metadata.items(): - pnginfo.add_text(k, v) + if v is not None: + pnginfo.add_text(k, v) if format in ["jpg", "jpeg"]: format = "jpeg" # Pillow doesn't recognize "jpg". facecolor = mpl.rcParams["savefig.facecolor"] From fafa132484872141431a5be3727eab1d8b3c7b82 Mon Sep 17 00:00:00 2001 From: Spencer McCoubrey Date: Thu, 5 Mar 2020 19:52:30 -0500 Subject: [PATCH 066/602] changed 'colors' parameter to default to rcParams['lines.color'] in line with issue #16482 added test coverage for changes to pyplot hlines() and vlines() interface adding API changes to api_changes updated Axes.vlines and Axes.hline docstring inline with changes updated spacing to be 2 line compliant updated implementation for iss-16482 inline with review comments removed unnecessary import of image_comparator decorator running boilerplate.py to update vlines/hlines methods updating inline with review comments fixing flake8 errors updating inline with reviewer comments updating inline with reviewer comments updated vlines/hlines default color tests inline with code review --- doc/api/api_changes_3.3/behaviour.rst | 9 +++++++-- lib/matplotlib/axes/_axes.py | 8 ++++---- lib/matplotlib/pyplot.py | 4 ++-- lib/matplotlib/tests/test_axes.py | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/doc/api/api_changes_3.3/behaviour.rst b/doc/api/api_changes_3.3/behaviour.rst index d8a9e377b4d9..40b1bb3d79d8 100644 --- a/doc/api/api_changes_3.3/behaviour.rst +++ b/doc/api/api_changes_3.3/behaviour.rst @@ -181,8 +181,8 @@ explicitly pass a "%1.2f" as the *valfmt* parameter to `.Slider`. Add *normalize* keyword argument to ``Axes.pie`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``pie()`` used to draw a partial pie if the sum of the values was < 1. This behavior -is deprecated and will change to always normalizing the values to a full pie by default. +``pie()`` used to draw a partial pie if the sum of the values was < 1. This behavior +is deprecated and will change to always normalizing the values to a full pie by default. If you want to draw a partial pie, please pass ``normalize=False`` explicitly. ``table.CustomCell`` is now an alias for `.table.Cell` @@ -307,3 +307,8 @@ but will become True in a later release. ... to determine whether a string should be passed to the usetex machinery or not. This allows single strings to be marked as not-usetex even when the rcParam is True. + +`.Axes.vlines`, `.Axes.hlines`, `.pyplot.vlines` and `.pyplot.hlines` *colors* parameter default change +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The *colors* parameter will now default to :rc:`lines.color`, while previously it defaulted to 'k'. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 286bf4b87566..c43c3e133ab5 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1116,7 +1116,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): @_preprocess_data(replace_names=["y", "xmin", "xmax", "colors"], label_namer="y") - def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', + def hlines(self, y, xmin, xmax, colors=None, linestyles='solid', label='', **kwargs): """ Plot horizontal lines at each *y* from *xmin* to *xmax*. @@ -1130,7 +1130,7 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', Respective beginning and end of each line. If scalars are provided, all lines will have same length. - colors : list of colors, default: 'k' + colors : list of colors, default: :rc:`lines.color` linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional @@ -1196,7 +1196,7 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', @_preprocess_data(replace_names=["x", "ymin", "ymax", "colors"], label_namer="x") - def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', + def vlines(self, x, ymin, ymax, colors=None, linestyles='solid', label='', **kwargs): """ Plot vertical lines. @@ -1212,7 +1212,7 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', Respective beginning and end of each line. If scalars are provided, all lines will have same length. - colors : list of colors, default: 'k' + colors : list of colors, default: :rc:`lines.color` linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 32f63db834c1..24b26e49cf75 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2601,7 +2601,7 @@ def hist2d( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hlines) def hlines( - y, xmin, xmax, colors='k', linestyles='solid', label='', *, + y, xmin, xmax, colors=None, linestyles='solid', label='', *, data=None, **kwargs): return gca().hlines( y, xmin, xmax, colors=colors, linestyles=linestyles, @@ -2972,7 +2972,7 @@ def violinplot( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.vlines) def vlines( - x, ymin, ymax, colors='k', linestyles='solid', label='', *, + x, ymin, ymax, colors=None, linestyles='solid', label='', *, data=None, **kwargs): return gca().vlines( x, ymin, ymax, colors=colors, linestyles=linestyles, diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3efee619c8f3..c8563b159f5b 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3815,6 +3815,13 @@ def test_vlines(): ax5.set_xlim(0, 15) +def test_vlines_default(): + fig, ax = plt.subplots() + with mpl.rc_context({'lines.color': 'red'}): + lines = ax.vlines(0.5, 0, 1) + assert mpl.colors.same_color(lines.get_color(), 'red') + + @image_comparison(['hlines_basic', 'hlines_with_nan', 'hlines_masked'], extensions=['png']) def test_hlines(): @@ -3855,6 +3862,13 @@ def test_hlines(): ax5.set_ylim(0, 15) +def test_hlines_default(): + fig, ax = plt.subplots() + with mpl.rc_context({'lines.color': 'red'}): + lines = ax.hlines(0.5, 0, 1) + assert mpl.colors.same_color(lines.get_color(), 'red') + + @pytest.mark.parametrize('data', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) @check_figures_equal(extensions=["png"]) From 152aab5e5b1d817556f7f3abb946f3d1e9e6f011 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 May 2020 21:10:02 -0400 Subject: [PATCH 067/602] Fix some typos in test names. --- ...xp_precentilewhis.png => bxp_percentilewhis.png} | Bin lib/matplotlib/tests/test_axes.py | 6 +++--- lib/matplotlib/tests/test_mlab.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename lib/matplotlib/tests/baseline_images/test_axes/{bxp_precentilewhis.png => bxp_percentilewhis.png} (100%) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png rename to lib/matplotlib/tests/baseline_images/test_axes/bxp_percentilewhis.png diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index cd8a8a961e32..4f2ab1c0d9c6 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2236,10 +2236,10 @@ def test_bxp_rangewhis(): _bxp_test_helper(stats_kwargs=dict(whis=[0, 100])) -@image_comparison(['bxp_precentilewhis.png'], +@image_comparison(['bxp_percentilewhis.png'], savefig_kwarg={'dpi': 40}, style='default') -def test_bxp_precentilewhis(): +def test_bxp_percentilewhis(): _bxp_test_helper(stats_kwargs=dict(whis=[5, 95])) @@ -6208,7 +6208,7 @@ def test_bbox_aspect_axes_init(): assert_allclose(sizes, sizes[0]) -def test_pi_get_negative_values(): +def test_pie_get_negative_values(): # Test the ValueError raised when feeding negative values into axes.pie fig, ax = plt.subplots() with pytest.raises(ValueError): diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index 80389a93c220..d63d60adfdf2 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -303,7 +303,7 @@ def test_apply_window_hanning_2D_axis1(self): assert x.shape == y.shape assert_allclose(yt, y, atol=1e-06) - def test_apply_window_hanning_2D__els1_axis1(self): + def test_apply_window_hanning_2D_els1_axis1(self): x = np.random.standard_normal([10, 1000]) + 100. window = mlab.window_hanning(np.ones(x.shape[1])) window1 = mlab.window_hanning From b197b7d61cd1be8ce7f9bf5fcb20c8b757ac8372 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 May 2020 21:16:44 -0400 Subject: [PATCH 068/602] Move pie tests together. --- lib/matplotlib/tests/test_axes.py | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 4f2ab1c0d9c6..47a7cc623f2e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4500,6 +4500,28 @@ def test_pie_textprops(): assert tx.get_color() == textprops["color"] +def test_pie_get_negative_values(): + # Test the ValueError raised when feeding negative values into axes.pie + fig, ax = plt.subplots() + with pytest.raises(ValueError): + ax.pie([5, 5, -3], explode=[0, .1, .2]) + + +def test_normalize_kwarg_warn_pie(): + fig, ax = plt.subplots() + with pytest.warns(MatplotlibDeprecationWarning): + ax.pie(x=[0], normalize=None) + + +def test_normalize_kwarg_pie(): + fig, ax = plt.subplots() + x = [0.3, 0.3, 0.1] + t1 = ax.pie(x=x, normalize=True) + assert abs(t1[0][-1].theta2 - 360.) < 1e-3 + t2 = ax.pie(x=x, normalize=False) + assert abs(t2[0][-1].theta2 - 360.) > 1e-3 + + @image_comparison(['set_get_ticklabels.png']) def test_set_get_ticklabels(): # test issue 2246 @@ -6208,13 +6230,6 @@ def test_bbox_aspect_axes_init(): assert_allclose(sizes, sizes[0]) -def test_pie_get_negative_values(): - # Test the ValueError raised when feeding negative values into axes.pie - fig, ax = plt.subplots() - with pytest.raises(ValueError): - ax.pie([5, 5, -3], explode=[0, .1, .2]) - - def test_invisible_axes(): # invisible axes should not respond to events... fig, ax = plt.subplots() @@ -6275,18 +6290,3 @@ def test_polar_interpolation_steps_variable_r(fig_test, fig_ref): l.get_path()._interpolation_steps = 100 fig_ref.add_subplot(projection="polar").plot( np.linspace(0, np.pi/2, 101), np.linspace(1, 2, 101)) - - -def test_normalize_kwarg_warn_pie(): - fig, ax = plt.subplots() - with pytest.warns(MatplotlibDeprecationWarning): - ax.pie(x=[0], normalize=None) - - -def test_normalize_kwarg_pie(): - fig, ax = plt.subplots() - x = [0.3, 0.3, 0.1] - t1 = ax.pie(x=x, normalize=True) - assert abs(t1[0][-1].theta2 - 360.) < 1e-3 - t2 = ax.pie(x=x, normalize=False) - assert abs(t2[0][-1].theta2 - 360.) > 1e-3 From 82877b76e18d958a6b5bc1115152afe85a714453 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 29 May 2020 19:43:35 -0400 Subject: [PATCH 069/602] Use symbolic icons for buttons in GTK toolbar. Unfortunately, the only way to trigger this is to rename the files, so add some symlinks to the existing SVG. Switching from PNG to SVG should be scalable as well. --- lib/matplotlib/backends/backend_gtk3.py | 17 ++++++++++------- .../mpl-data/images/back-symbolic.svg | 1 + .../mpl-data/images/filesave-symbolic.svg | 1 + .../mpl-data/images/forward-symbolic.svg | 1 + .../mpl-data/images/help-symbolic.svg | 1 + .../mpl-data/images/home-symbolic.svg | 1 + .../mpl-data/images/move-symbolic.svg | 1 + .../mpl-data/images/subplots-symbolic.svg | 1 + .../mpl-data/images/zoom_to_rect-symbolic.svg | 1 + 9 files changed, 18 insertions(+), 7 deletions(-) create mode 120000 lib/matplotlib/mpl-data/images/back-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/filesave-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/forward-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/help-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/home-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/move-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/subplots-symbolic.svg create mode 120000 lib/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 829331930122..871cea620fad 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -27,7 +27,7 @@ # auto-backend selection logic correctly skips. raise ImportError from e -from gi.repository import GLib, GObject, Gtk, Gdk +from gi.repository import Gio, GLib, GObject, Gtk, Gdk _log = logging.getLogger(__name__) @@ -464,9 +464,11 @@ def __init__(self, canvas, window): if text is None: self.insert(Gtk.SeparatorToolItem(), -1) continue - image = Gtk.Image() - image.set_from_file( - str(cbook._get_data_path('images', image_file + '.png'))) + image = Gtk.Image.new_from_gicon( + Gio.Icon.new_for_string( + str(cbook._get_data_path('images', + f'{image_file}-symbolic.svg'))), + Gtk.IconSize.LARGE_TOOLBAR) self._gtk_ids[text] = tbutton = ( Gtk.ToggleToolButton() if callback in ['zoom', 'pan'] else Gtk.ToolButton()) @@ -623,7 +625,7 @@ def set_history_buttons(self): class ToolbarGTK3(ToolContainerBase, Gtk.Box): - _icon_extension = '.png' + _icon_extension = '-symbolic.svg' def __init__(self, toolmanager): ToolContainerBase.__init__(self, toolmanager) @@ -644,8 +646,9 @@ def add_toolitem(self, name, group, position, image_file, description, tbutton.set_label(name) if image_file is not None: - image = Gtk.Image() - image.set_from_file(image_file) + image = Gtk.Image.new_from_gicon( + Gio.Icon.new_for_string(image_file), + Gtk.IconSize.LARGE_TOOLBAR) tbutton.set_icon_widget(image) if position is None: diff --git a/lib/matplotlib/mpl-data/images/back-symbolic.svg b/lib/matplotlib/mpl-data/images/back-symbolic.svg new file mode 120000 index 000000000000..22b78b6a8d63 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/back-symbolic.svg @@ -0,0 +1 @@ +back.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/filesave-symbolic.svg b/lib/matplotlib/mpl-data/images/filesave-symbolic.svg new file mode 120000 index 000000000000..2bad4deb6655 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/filesave-symbolic.svg @@ -0,0 +1 @@ +filesave.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/forward-symbolic.svg b/lib/matplotlib/mpl-data/images/forward-symbolic.svg new file mode 120000 index 000000000000..16bea25c1a22 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/forward-symbolic.svg @@ -0,0 +1 @@ +forward.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/help-symbolic.svg b/lib/matplotlib/mpl-data/images/help-symbolic.svg new file mode 120000 index 000000000000..74f27a8db5a5 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/help-symbolic.svg @@ -0,0 +1 @@ +help.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/home-symbolic.svg b/lib/matplotlib/mpl-data/images/home-symbolic.svg new file mode 120000 index 000000000000..bab8cc61783f --- /dev/null +++ b/lib/matplotlib/mpl-data/images/home-symbolic.svg @@ -0,0 +1 @@ +home.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/move-symbolic.svg b/lib/matplotlib/mpl-data/images/move-symbolic.svg new file mode 120000 index 000000000000..37362fe44247 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/move-symbolic.svg @@ -0,0 +1 @@ +move.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/subplots-symbolic.svg b/lib/matplotlib/mpl-data/images/subplots-symbolic.svg new file mode 120000 index 000000000000..d5b44c022b3b --- /dev/null +++ b/lib/matplotlib/mpl-data/images/subplots-symbolic.svg @@ -0,0 +1 @@ +subplots.svg \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg b/lib/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg new file mode 120000 index 000000000000..817c9f054330 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/zoom_to_rect-symbolic.svg @@ -0,0 +1 @@ +zoom_to_rect.svg \ No newline at end of file From bc43279e4719821931f013750ef8fa5286e57098 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 26 May 2020 23:00:30 -0400 Subject: [PATCH 070/602] MNT: make sure Axes3D methods handle sharez correctly --- lib/matplotlib/axes/_base.py | 4 -- lib/mpl_toolkits/mplot3d/axes3d.py | 83 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 2f5f4ce7b718..da9ddeebe6d4 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1339,10 +1339,6 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): if cbook._str_equal(aspect, 'equal'): aspect = 1 if not cbook._str_equal(aspect, 'auto'): - if self.name == '3d': - raise NotImplementedError( - 'It is not currently possible to manually set the aspect ' - 'on 3D axes') aspect = float(aspect) # raise ValueError if necessary if share: diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 9ac10918604a..229785dd4716 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -24,6 +24,7 @@ import matplotlib.colors as mcolors import matplotlib.docstring as docstring import matplotlib.scale as mscale +import matplotlib.transforms as mtransforms from matplotlib.axes import Axes, rcParams from matplotlib.axes._base import _axis_method_wrapper from matplotlib.transforms import Bbox @@ -261,10 +262,92 @@ def tunit_edges(self, vals=None, M=None): (tc[7], tc[4])] return edges + def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): + """ + Set the aspect of the axis scaling. + + Parameters + ---------- + aspect : {'auto'} + Possible values: + + ========= ================================================== + value description + ========= ================================================== + 'auto' automatic; fill the position rectangle with data. + ========= ================================================== + + adjustable : None or {'box', 'datalim'}, optional + If not ``None``, this defines which parameter will be adjusted to + meet the required aspect. See `.set_adjustable` for further + details. + + Currently ignored by Axes3D + + anchor : None or str or 2-tuple of float, optional + If not ``None``, this defines where the Axes will be drawn if there + is extra space due to aspect constraints. The most common way to + to specify the anchor are abbreviations of cardinal directions: + + ===== ===================== + value description + ===== ===================== + 'C' centered + 'SW' lower left corner + 'S' middle of bottom edge + 'SE' lower right corner + etc. + ===== ===================== + + See `.set_anchor` for further details. + + share : bool, default: False + If ``True``, apply the settings to all shared Axes. + + """ + if aspect != 'auto': + raise NotImplementedError( + "Axes3D currently only support the aspect arguments " + f"'auto'. You passed in {aspect!r}." + ) + + if share: + axes = {*self._shared_x_axes.get_siblings(self), + *self._shared_y_axes.get_siblings(self), + *self._shared_z_axes.get_siblings(self), + } + else: + axes = {self} + + for ax in axes: + ax._aspect = aspect + ax.stale = True + + if anchor is not None: + self.set_anchor(anchor, share=share) + + def set_anchor(self, anchor, share=False): + # docstring inherited + if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2): + raise ValueError('argument must be among %s' % + ', '.join(mtransforms.Bbox.coefs)) + if share: + axes = {*self._shared_x_axes.get_siblings(self), + *self._shared_y_axes.get_siblings(self), + *self._shared_z_axes.get_siblings(self), + } + else: + axes = {self} + for ax in axes: + ax._anchor = anchor + ax.stale = True + def apply_aspect(self, position=None): if position is None: position = self.get_position(original=True) + aspect = self.get_aspect() + # in the superclass, we would go through and actually deal with axis # scales and box/datalim. Those are all irrelevant - all we need to do # is make sure our coordinate system is square. From b9f9d2ee06d4cd1d72bfca312660a5b55d265f31 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 26 May 2020 23:40:48 -0400 Subject: [PATCH 071/602] ENH: implement get_tightbbox on Axis3D --- lib/mpl_toolkits/mplot3d/axes3d.py | 21 ++++++++++++ lib/mpl_toolkits/mplot3d/axis3d.py | 55 ++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 229785dd4716..7c8815e5a52a 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2853,6 +2853,27 @@ def permutation_matrices(n): return polygons + def get_tightbbox(self, renderer, call_axes_locator=True, + bbox_extra_artists=None, *, for_layout_only=False): + ret = super().get_tightbbox(renderer, + call_axes_locator=call_axes_locator, + bbox_extra_artists=bbox_extra_artists, + for_layout_only=for_layout_only) + batch = [ret] + if self._axis3don: + for axis in self._get_axis_list(): + if axis.get_visible(): + try: + axis_bb = axis.get_tightbbox( + renderer, + for_layout_only=for_layout_only + ) + except TypeError: + # in case downstream library has redefined axis: + axis_bb = axis.get_tightbbox(renderer) + if axis_bb: + batch.append(axis_bb) + return mtransforms.Bbox.union(batch) docstring.interpd.update(Axes3D=artist.kwdoc(Axes3D)) docstring.dedent_interpd(Axes3D.__init__) diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index c3ca45b5df80..767f0a81842e 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -3,7 +3,7 @@ # Parts rewritten by Reinier Heeres import numpy as np - +import matplotlib.transforms as mtransforms from matplotlib import ( artist, lines as mlines, axis as maxis, patches as mpatches, rcParams) from . import art3d, proj3d @@ -398,12 +398,53 @@ def draw(self, renderer): renderer.close_group('axis3d') self.stale = False - # TODO: Get this to work properly when mplot3d supports - # the transforms framework. - def get_tightbbox(self, renderer): - # Currently returns None so that Axis.get_tightbbox - # doesn't return junk info. - return None + # TODO: Get this to work (more) properly when mplot3d supports the + # transforms framework. + def get_tightbbox(self, renderer, *, for_layout_only=False): + # inherited docstring + if not self.get_visible(): + return + # We have to directly access the internal data structures + # (and hope they are up to date) because at draw time we + # shift the ticks and their labels around in (x, y) space + # based on the projection, the current view port, and their + # position in 3D space. If we extend the transforms framework + # into 3D we would not need to do this different book keeping + # than we do in the normal axis + major_locs = self.get_majorticklocs() + minor_locs = self.get_minorticklocs() + + ticks = [*self.get_minor_ticks(len(minor_locs)), + *self.get_major_ticks(len(major_locs))] + view_low, view_high = self.get_view_interval() + if view_low > view_high: + view_low, view_high = view_high, view_low + interval_t = self.get_transform().transform([view_low, view_high]) + + ticks_to_draw = [] + for tick in ticks: + try: + loc_t = self.get_transform().transform(tick.get_loc()) + except AssertionError: + # transforms.transform doesn't allow masked values but + # some scales might make them, so we need this try/except. + pass + else: + if mtransforms._interval_contains_close(interval_t, loc_t): + ticks_to_draw.append(tick) + + ticks = ticks_to_draw + + bb_1, bb_2 = self._get_tick_bboxes(ticks, renderer) + other = [] + + if self.line.get_visible(): + other.append(self.line.get_window_extent(renderer)) + if (self.label.get_visible() and not for_layout_only and + self.label.get_text()): + other.append(self.label.get_window_extent(renderer)) + + return mtransforms.Bbox.union([*bb_1, *bb_2, *other]) @property def d_interval(self): From 8c2529ad55892cc6a228e32c005d676c55a207bf Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Tue, 18 Jul 2017 10:26:20 +0100 Subject: [PATCH 072/602] ENH: Add support for Axes3D.set_pb_aspect This only keeps the pb related changes from the original commit. --- lib/mpl_toolkits/mplot3d/axes3d.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 7c8815e5a52a..1f0f649f0651 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -91,6 +91,11 @@ def __init__( self.zz_viewLim = Bbox.unit() self.xy_dataLim = Bbox.unit() self.zz_dataLim = Bbox.unit() + if 'pb_aspect' in kwargs: + self.pb_aspect = np.asarray(kwargs['pb_aspect']) + else: + # chosen for similarity with the previous initial view + self.pb_aspect = np.array([4, 4, 3]) / 3.5 # inhibit autoscale_view until the axes are defined # they can't be defined until Axes.__init__ has been called self.view_init(self.initial_elev, self.initial_azim) @@ -342,6 +347,9 @@ def set_anchor(self, anchor, share=False): ax._anchor = anchor ax.stale = True + def set_pb_aspect(self, pb_aspect, zoom=1): + self.pb_aspect = pb_aspect * 1.8 * zoom / proj3d.mod(pb_aspect) + def apply_aspect(self, position=None): if position is None: position = self.get_position(original=True) @@ -966,7 +974,12 @@ def set_proj_type(self, proj_type): def get_proj(self): """Create the projection matrix from the current viewing position.""" # chosen for similarity with the initial view before gh-8896 - pb_aspect = np.array([4, 4, 3]) / 3.5 + + # elev stores the elevation angle in the z plane + # azim stores the azimuth angle in the x,y plane + # + # dist is the distance of the eye viewing point from the object + # point. relev, razim = np.pi * self.elev/180, np.pi * self.azim/180 @@ -977,10 +990,10 @@ def get_proj(self): # transform to uniform world coordinates 0-1, 0-1, 0-1 worldM = proj3d.world_transformation(xmin, xmax, ymin, ymax, - zmin, zmax, pb_aspect=pb_aspect) + zmin, zmax, pb_aspect=self.pb_aspect) # look into the middle of the new coordinates - R = pb_aspect / 2 + R = self.pb_aspect / 2 xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist From a4aafd1895f01c091baa126c0e3d95a32a184b71 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sat, 30 May 2020 13:33:09 -1000 Subject: [PATCH 073/602] Documentation tweaks from review. --- ...020-05-28-extend_kwarg_to_BoundaryNorm.rst | 17 ++++++------ lib/matplotlib/colors.py | 2 +- tutorials/colors/colorbar_only.py | 26 +++++++++++-------- tutorials/colors/colormapnorms.py | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst index a1e01da016b2..4f68950bf3dc 100644 --- a/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst +++ b/doc/users/next_whats_new/2020-05-28-extend_kwarg_to_BoundaryNorm.rst @@ -1,14 +1,15 @@ New "extend" keyword to colors.BoundaryNorm ------------------------------------------- -`~.colors.BoundaryNorm` now has an ``extend`` kwarg, -analogous to ``extend`` in ~.axes._axes.Axes.contourf`. When set to -'both', 'min', or 'max', it interpolates such that the corresponding -out-of-range values are mapped to colors distinct from their in-range -neighbors. The colorbar inherits the ``extend`` argument from the -norm, so with ``extend='both'``, for example, the colorbar will have -triangular extensions for out-of-range values with colors that differ -from adjacent colors. +`~.colors.BoundaryNorm` now has an ``extend`` keyword argument, analogous to +``extend`` in ~.axes._axes.Axes.contourf`. When set to 'both', 'min', or 'max', +it maps the corresponding out-of-range values to `~.colors.Colormap` +lookup-table indices near the appropriate ends of their range so that the +colors for out-of range values are adjacent to, but distinct from, their +in-range neighbors. The colorbar inherits the ``extend`` argument from the +norm, so with ``extend='both'``, for example, the colorbar will have triangular +extensions for out-of-range values with colors that differ from adjacent +colors. .. plot:: diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 9ef0f9ce916b..8a713c7cd8fa 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1434,7 +1434,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): pair of boundaries is mapped will be distinct from the first color in the colormap, and by default a `~matplotlib.colorbar.Colorbar` will be drawn with - the triangle extension on the left side. + the triangle extension on the left or lower end. Notes ----- diff --git a/tutorials/colors/colorbar_only.py b/tutorials/colors/colorbar_only.py index 712779ad482b..5d46dde2e755 100644 --- a/tutorials/colors/colorbar_only.py +++ b/tutorials/colors/colorbar_only.py @@ -42,10 +42,10 @@ ############################################################################### # Extended colorbar with continuous colorscale # -------------------------------------------- - +# # The second example shows how to make a discrete colorbar based on a -# continuous cmap. With the "extend" kwarg the appropriate colors are chosen to -# fill the colorspace, including the extensions: +# continuous cmap. With the "extend" keyword argument the appropriate colors +# are chosen to fill the colorspace, including the extensions: fig, ax = plt.subplots(figsize=(6, 1)) fig.subplots_adjust(bottom=0.5) @@ -62,7 +62,7 @@ # Discrete intervals colorbar # --------------------------- # -# The second example illustrates the use of a +# The third example illustrates the use of a # :class:`~matplotlib.colors.ListedColormap` which generates a colormap from a # set of listed colors, `.colors.BoundaryNorm` which generates a colormap # index based on discrete intervals and extended ends to show the "over" and @@ -74,11 +74,15 @@ # bounds array must be one greater than the length of the color list. The # bounds must be monotonically increasing. # -# This time we pass some more arguments in addition to previous arguments to -# `~.Figure.colorbar`. For the out-of-range values to -# display on the colorbar, we have to use the *extend* keyword argument. To use -# *extend*, you must specify two extra boundaries. Finally spacing argument -# ensures that intervals are shown on colorbar proportionally. +# This time we pass additional arguments to +# `~.Figure.colorbar`. For the out-of-range values to display on the colorbar +# without using the *extend* keyword with +# `.colors.BoundaryNorm`, we have to use the *extend* keyword argument directly +# in the colorbar call, and supply an additional boundary on each end of the +# range. Here we also +# use the spacing argument to make +# the length of each colorbar segment proportional to its corresponding +# interval. fig, ax = plt.subplots(figsize=(6, 1)) fig.subplots_adjust(bottom=0.5) @@ -92,7 +96,7 @@ fig.colorbar( mpl.cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax, - boundaries=[0] + bounds + [13], + boundaries=[0] + bounds + [13], # Adding values for extensions. extend='both', ticks=bounds, spacing='proportional', @@ -104,7 +108,7 @@ # Colorbar with custom extension lengths # -------------------------------------- # -# Here we illustrate the use of custom length colorbar extensions, used on a +# Here we illustrate the use of custom length colorbar extensions, on a # colorbar with discrete intervals. To make the length of each extension the # same as the length of the interior colors, use ``extendfrac='auto'``. diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 5c176b925ddf..eca01bebe6c4 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -147,7 +147,7 @@ # which data is to be mapped. The colors are then linearly distributed between # these "bounds". It can also take an *extend* argument to add upper and/or # lower out-of-bounds values to the range over which the colors are -# distributed For instance: +# distributed. For instance: # # .. ipython:: # From 3afa89dbd0c64bc504fa6f6c50713280a5c0065f Mon Sep 17 00:00:00 2001 From: sarthakforwet Date: Sun, 31 May 2020 13:05:35 +0530 Subject: [PATCH 074/602] Added autoscale_tutorial --- tutorials/intermediate/autoscale_tutorial.py | 166 +++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tutorials/intermediate/autoscale_tutorial.py diff --git a/tutorials/intermediate/autoscale_tutorial.py b/tutorials/intermediate/autoscale_tutorial.py new file mode 100644 index 000000000000..b11278f415b0 --- /dev/null +++ b/tutorials/intermediate/autoscale_tutorial.py @@ -0,0 +1,166 @@ +""" +Matplotlib used to recompute autoscaled limits after every plotting (plot(), +bar(), etc.) call. It now only does so when actually rendering the canvas, or +when the user queries the Axes limits and that is possible through **autoscale** +feature. +This is an improvement over the previous case when user has to manually +autoscale the Axes(or axis) according to data. +This particular method is a part of matplotlib.axes.Axes class. It is used to +scale the Axes(or axis) according to data limits. +Before autoscale one could have to manually struggle with the Axes to scale +itself according to data. +Whenever we use autoscale method the axes(or axis if specified only one axis) +does recalculate its limits with respect to data. +""" + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import PathPatch +from matplotlib.path import Path +from matplotlib.collections import EllipseCollection + + +def autoscale(): + x = np.sin(np.linspace(0,6)) + fig,ax =plt.subplots(ncols=2) + + # Firstly we will explicitly enable autoscaling (However it is by-default enabled for pyplot artist.) + ax[0].autoscale(True) + ax[0].plot(x) + + # Now we disable it . + ax[1].autoscale(False) + ax[1].plot(x) + + +# Utility function to describe autoscaling feature with Margins. +def autoscale_and_margins(autoscale=False): + t = np.arange(-np.pi/2,(3 * np.pi)/2,0.1) + f_t = np.cos(t) + fig,ax = plt.subplots(ncols=2) + for axis in [ax[0],ax[1]]: + axis.plot(t,f_t,color="red") + axis.axhline(y=0,color="magenta",alpha=0.7) + axis.margins(0.2,0.2) + + ax[1].autoscale(tight=True) # tight in ax.margins is by default True but unable to autoscale itself when Zoomed in or Zoomed out. + plt.tight_layout() + + + +def autoscale_and_collections(): + fig,ax = plt.subplots(ncols=2) + x = np.arange(10) + y = np.arange(15) + X, Y = np.meshgrid(x, y) + + XY = np.column_stack((X.ravel(), Y.ravel())) + + ec_1 = EllipseCollection(10, 10, 5, units ='y', + offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. + transOffset = ax[0].transData, + cmap ="jet") + ec_1.set_array((X * Y).ravel()) + + ec_2 = EllipseCollection(10, 10, 5, units ='y', + offsets = XY * 0.5, # when autoscaling is enabled then axis is autoscaled to this offset. + transOffset = ax[1].transData, + cmap ="jet") + ec_2.set_array((X * Y).ravel()) + + ax[0].add_collection(ec_1) + ax[1].add_collection(ec_2) + ax[1].autoscale_view() + fig.canvas.draw() + + +def autoscale_and_patches(): + fig,ax = plt.subplots(ncols=2) + vertices = [(0,0),(0,3),(1,0),(0,0)] + codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.MOVETO] + vertices = np.array(vertices,float) + path_1 = Path(vertices,codes) + patches_1 = PathPatch(path_1,facecolor="magenta",alpha=0.7) + # matplotlib.patches.Patch is Base class of PathPatch and it + # does not support autoscaling. + # creating two because re-using of artists not supported. + path_2 = Path(vertices,codes) + patches_2 = PathPatch(path_2,facecolor="magenta",alpha=0.7) + # matplotlib.patches.Patch is Base class of PathPatch and it + # does not support autoscaling. + + ax[0].add_patch(patches_1) + ax[1].add_patch(patches_2) + ax[1].autoscale() + fig.canvas.draw() + +def autoscale_disable(): + x = np.arange(10) + # Disable Autoscaling + plt.autoscale(False) + + plt.xlim(0,2) + plt.plot(x) +""" +autoscale +============= +There are some cases when we have to explicitly enable or disable autoscaling +feature and we would see some of those cases in the following tutorial. Lets +just discuss how we can explicitly enable or disable autoscaling. +""" + +autoscale() + +""" +autoscale and margins +===================== +Whenever we set margins our axes remains invariant of the change caused by +it.Hence we use autoscaling if we want data to be bound with the axes +irrespective of the margin set. +""" + +autoscale_and_margins() + +""" +Artist with autoscale +===================== +Collection and Patch subclasses of Artist class does not support autoscaling by +default. If one wants to enable autoscaling he have to explicitly enable it. See +Axes limits in the below plots. +For further reference on relation of Autoscale feature with Collection Class +see- +https://matplotlib.org/3.2.1/api/prev_api_changes/api_changes_3.2.0/behavior.html#autoscaling +""" + +""" +autoscale and collections +========================= +""" +autoscale_and_collections() + +""" +autoscale and patches +===================== +""" +autoscale_and_patches() + +""" +Some of the subclasses under Collection class are : +https://matplotlib.org/3.1.1/_images/inheritance-1d05647d989bf64e3e438a24b19fee19432184da.png +reference site : https://matplotlib.org/3.1.1/api/collections_api.html +""" + +""" +Till now we have seen how we can enable autoscaling. Now lets just discuss under +which case we possibly need to disable it. +Suppose we want to plot a line at 45 degrees to x and y axes and we want data to +be shown within a given range out of the whole and hence we have to set limits +for x and y axes and in that case we have to first disable autoscaling and then +we can set the limits . +""" +""" +autoscale disable +================= +""" + +autoscale_disable() From d8d755e23f0e496ac844204dc08889df28b1e77c Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 29 May 2020 00:58:55 +0200 Subject: [PATCH 075/602] Update docs on SubplotBase --- lib/matplotlib/axes/_subplots.py | 9 ++++++--- lib/matplotlib/figure.py | 11 +++++------ lib/matplotlib/pyplot.py | 20 +++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index e40754a2acbc..3fa33fbdfeaa 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -30,6 +30,9 @@ def __init__(self, fig, *args, **kwargs): If *nrows*, *ncols*, and *index* are all single digit numbers, then *args* can be passed as a single 3-digit number (e.g. 234 for (2, 3, 4)). + + **kwargs + Keyword arguments are passed to the Axes (sub)class constructor. """ self.figure = fig @@ -78,15 +81,15 @@ def change_geometry(self, numrows, numcols, num): self.set_position(self.figbox) def get_subplotspec(self): - """Return the SubplotSpec instance associated with the subplot.""" + """Return the `.SubplotSpec` instance associated with the subplot.""" return self._subplotspec def set_subplotspec(self, subplotspec): - """Set the SubplotSpec instance associated with the subplot.""" + """Set the `.SubplotSpec`. instance associated with the subplot.""" self._subplotspec = subplotspec def get_gridspec(self): - """Return the GridSpec instance associated with the subplot.""" + """Return the `.GridSpec` instance associated with the subplot.""" return self._subplotspec.get_gridspec() def update_params(self): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 846e112bf5dc..32f90191d75f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1149,11 +1149,10 @@ def add_axes(self, *args, **kwargs): Returns ------- - `~.axes.Axes` (or a subclass of `~.axes.Axes`) + `~.axes.Axes`, or a subclass of `~.axes.Axes` The returned axes class depends on the projection used. It is - `~.axes.Axes` if rectilinear projection are used and - `.projections.polar.PolarAxes` if polar projection - are used. + `~.axes.Axes` if rectilinear projection is used and + `.projections.polar.PolarAxes` if polar projection is used. Other Parameters ---------------- @@ -1309,8 +1308,8 @@ def add_subplot(self, *args, **kwargs): The axes of the subplot. The returned axes base class depends on the projection used. It is `~.axes.Axes` if rectilinear projection - are used and `.projections.polar.PolarAxes` if polar projection - are used. The returned axes is then a subplot subclass of the + is used and `.projections.polar.PolarAxes` if polar projection + is used. The returned axes is then a subplot subclass of the base class. Other Parameters diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 389633d790d9..b9a97a55dd70 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -896,11 +896,10 @@ def axes(arg=None, **kwargs): Returns ------- - `~.axes.Axes` (or a subclass of `~.axes.Axes`) + `~.axes.Axes`, or a subclass of `~.axes.Axes` The returned axes class depends on the projection used. It is - `~.axes.Axes` if rectilinear projection are used and - `.projections.polar.PolarAxes` if polar projection - are used. + `~.axes.Axes` if rectilinear projection is used and + `.projections.polar.PolarAxes` if polar projection is used. Other Parameters ---------------- @@ -1023,13 +1022,12 @@ def subplot(*args, **kwargs): Returns ------- - an `.axes.SubplotBase` subclass of `~.axes.Axes` (or a subclass of \ -`~.axes.Axes`) + `.axes.SubplotBase`, or another subclass of `~.axes.Axes` The axes of the subplot. The returned axes base class depends on the projection used. It is `~.axes.Axes` if rectilinear projection - are used and `.projections.polar.PolarAxes` if polar projection - are used. The returned axes is then a subplot subclass of the + is used and `.projections.polar.PolarAxes` if polar projection + is used. The returned axes is then a subplot subclass of the base class. Other Parameters @@ -1288,7 +1286,7 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): Row number and column number of the axis location within the grid. rowspan : int, default: 1 Number of rows for the axis to span to the right. - colspan : int. default: 1 + colspan : int, default: 1 Number of columns for the axis to span downwards. fig : `.Figure`, optional Figure to place the subplot in. Defaults to the current figure. @@ -1301,8 +1299,8 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): The axes of the subplot. The returned axes base class depends on the projection used. It is `~.axes.Axes` if rectilinear projection - are used and `.projections.polar.PolarAxes` if polar projection - are used. The returned axes is then a subplot subclass of the + is used and `.projections.polar.PolarAxes` if polar projection + is used. The returned axes is then a subplot subclass of the base class. Notes From 3b6ac629c461c62cf6d3a92f03bae0c46641a3b9 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 31 May 2020 06:22:01 -1000 Subject: [PATCH 076/602] Doc: specify default keyword argument for 'extend' Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 8a713c7cd8fa..43a478c65228 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1427,7 +1427,7 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): they are below ``boundaries[0]`` or mapped to *ncolors* if they are above ``boundaries[-1]``. These are then converted to valid indices by `Colormap.__call__`. - extend : {'neither', 'both', 'min', 'max'}, optional + extend : {'neither', 'both', 'min', 'max'}, default: 'neither' Extend the number of bins to include one or both of the regions beyond the boundaries. For example, if ``extend`` is 'min', then the color to which the region between the first From 1b9692173e21558222228ea4585852cf96f2e0df Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 10 Nov 2019 15:51:51 +0100 Subject: [PATCH 077/602] Remove mention that tkagg was derived from PIL. It has basically been entirely rewritten by Matthew Brett and myself (and others), at this point. --- LICENSE/LICENSE.PIL | 12 ------------ src/_tkagg.cpp | 16 +++++++++------- 2 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 LICENSE/LICENSE.PIL diff --git a/LICENSE/LICENSE.PIL b/LICENSE/LICENSE.PIL deleted file mode 100644 index 3f77350b923b..000000000000 --- a/LICENSE/LICENSE.PIL +++ /dev/null @@ -1,12 +0,0 @@ -Software License - -The Python Imaging Library (PIL) is - - Copyright © 1997-2011 by Secret Labs AB - Copyright © 1995-2011 by Fredrik Lundh - -By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions: - -Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. - -SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index bfad8cb41bfe..b87d118a7167 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -1,12 +1,14 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ -/* - * This code is derived from The Python Imaging Library and is covered - * by the PIL license. - * - * See LICENSE/LICENSE.PIL for details. - * - */ +// Where is PIL? +// +// Many years ago, Matplotlib used to include code from PIL (the Python Imaging +// Library). Since then, the code has changed a lot - the organizing principle +// and methods of operation are now quite different. Because our review of +// the codebase showed that all the code that came from PIL was removed or +// rewritten, we have removed the PIL licensing information. If you want PIL, +// you can get it at https://python-pillow.org/ + #define PY_SSIZE_T_CLEAN #include From 03e0cb12ca932f03ce168f575eac810c54153980 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 17 May 2020 13:59:00 +0200 Subject: [PATCH 078/602] Use light icons on dark themes for wx, too. This can be tried e.g. by setting `GTK_THEME` to `Adwaita:dark`. I made the `_icon` API "consistent" across qt and wx, too. --- lib/matplotlib/backends/backend_qt5.py | 4 ++++ lib/matplotlib/backends/backend_wx.py | 32 ++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 356d191d3c1b..1265479d2ead 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -703,6 +703,10 @@ def basedir(self): return str(cbook._get_data_path('images')) def _icon(self, name): + """ + Construct a `.QIcon` from an image file *name*, including the extension + and relative to Matplotlib's "images" data directory. + """ if QtCore.qVersion() >= '5.': name = name.replace('.png', '_large.png') pm = QtGui.QPixmap(str(cbook._get_data_path('images', name))) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 68ad73765241..7db06e9b9641 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -13,6 +13,9 @@ import sys import weakref +import numpy as np +import PIL + import matplotlib as mpl from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, @@ -1114,7 +1117,7 @@ def __init__(self, canvas): self.wx_ids[text] = ( self.AddTool( -1, - bitmap=_load_bitmap(f"{image_file}.png"), + bitmap=self._icon(f"{image_file}.png"), bmpDisabled=wx.NullBitmap, label=text, shortHelp=text, longHelp=tooltip_text, kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"] @@ -1146,6 +1149,31 @@ def __init__(self, canvas): zoomStartX = cbook._deprecate_privatize_attribute("3.3") zoomStartY = cbook._deprecate_privatize_attribute("3.3") + @staticmethod + def _icon(name): + """ + Construct a `wx.Bitmap` suitable for use as icon from an image file + *name*, including the extension and relative to Matplotlib's "images" + data directory. + """ + image = np.array(PIL.Image.open(cbook._get_data_path("images", name))) + try: + dark = wx.SystemSettings.GetAppearance().IsDark() + except AttributeError: # wxpython < 4.1 + # copied from wx's IsUsingDarkBackground / GetLuminance. + bg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + # See wx.Colour.GetLuminance. + bg_lum = (.299 * bg.red + .587 * bg.green + .114 * bg.blue) / 255 + fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255 + dark = fg_lum - bg_lum > .2 + if dark: + fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + black_mask = (image[..., :3] == 0).all(axis=-1) + image[black_mask, :3] = (fg.Red(), fg.Green(), fg.Blue()) + return wx.Bitmap.FromBufferRGBA( + image.shape[1], image.shape[0], image.tobytes()) + def get_canvas(self, frame, fig): return type(self.canvas)(frame, -1, fig) @@ -1375,7 +1403,7 @@ def add_toolitem(self, name, group, position, image_file, description, start = self._get_tool_pos(sep) + 1 idx = start + position if image_file: - bmp = _load_bitmap(image_file) + bmp = NavigationToolbar2Wx._icon(image_file) kind = wx.ITEM_NORMAL if not toggle else wx.ITEM_CHECK tool = self.InsertTool(idx, -1, name, bmp, wx.NullBitmap, kind, description or "") From 49902e963ddf3dd3a88817a6ff0f5d499ca28b80 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 31 May 2020 22:24:13 -1000 Subject: [PATCH 079/602] Improve the tests, docstrings, argument checking The substantive difference is that a ValueError is now raised if BoundaryNorm is called with ncolors < the total number of bins, including any extensions. --- lib/matplotlib/colors.py | 19 ++++-- lib/matplotlib/tests/test_colors.py | 92 ++++++++++++++--------------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 43a478c65228..c6fe81029b1f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -530,7 +530,7 @@ def __call__(self, X, alpha=None, bytes=False): """ Parameters ---------- - X : float, ndarray + X : float or int, ndarray or scalar The data value(s) to convert to RGBA. For floats, X should be in the interval ``[0.0, 1.0]`` to return the RGBA values ``X*100`` percent along the Colormap line. @@ -1410,7 +1410,7 @@ class BoundaryNorm(Normalize): interpolation, but using integers seems simpler, and reduces the number of conversions back and forth between integer and floating point. """ - def __init__(self, boundaries, ncolors, clip=False, extend='neither'): + def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'): """ Parameters ---------- @@ -1436,14 +1436,18 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): `~matplotlib.colorbar.Colorbar` will be drawn with the triangle extension on the left or lower end. + Returns + ------- + int16 scalar or array + Notes ----- *boundaries* defines the edges of bins, and data falling within a bin is mapped to the color with the same index. - If the number of bins, including any extensions, doesn't equal - *ncolors*, the color is chosen by linear interpolation of the - bin number onto color numbers. + If the number of bins, including any extensions, is less than + *ncolors*, the color index is chosen by linear interpolation, mapping + the ``[0, nbins - 1]`` range onto the ``[0, ncolors - 1]`` range. """ if clip and extend != 'neither': raise ValueError("'clip=True' is not compatible with 'extend'") @@ -1462,6 +1466,11 @@ def __init__(self, boundaries, ncolors, clip=False, extend='neither'): self._offset = 1 if extend in ('max', 'both'): self._N += 1 + if self._N > self.Ncmap: + raise ValueError(f"There are {self._N} color bins including " + f"extensions, but ncolors = {ncolors}; " + "ncolors must equal or exceed the number of " + "bins") def __call__(self, value, clip=None): if clip is None: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index ae1fdeb70edc..948aa6d8420d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -265,85 +265,83 @@ def test_BoundaryNorm(): vals = np.ma.masked_invalid([np.Inf]) assert np.all(bn(vals).mask) - # Testing extend keyword - bounds = [1, 2, 3] - cmap = cm.get_cmap('jet') + # Incompatible extend and clip + with pytest.raises(ValueError, match="not compatible"): + mcolors.BoundaryNorm(np.arange(4), 5, extend='both', clip=True) - refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + # Too small ncolors argument + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 2) + + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 3, extend='min') + + with pytest.raises(ValueError, match="ncolors must equal or exceed"): + mcolors.BoundaryNorm(np.arange(4), 4, extend='both') + + # Testing extend keyword, with interpolation (large cmap) + bounds = [1, 2, 3] + cmap = cm.get_cmap('viridis') mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') - x = np.random.random(100) + 1.5 - np.testing.assert_array_equal(refnorm(x), mynorm(x)) + refnorm = mcolors.BoundaryNorm([0] + bounds + [4], cmap.N) + x = np.random.randn(100) * 10 + 2 + ref = refnorm(x) + ref[ref == 0] = -1 + ref[ref == cmap.N - 1] = cmap.N + assert_array_equal(mynorm(x), ref) - # Min and max + # Without interpolation cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_over('black') cmref.set_under('white') - cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) - cmshould.set_over(cmshould(cmshould.N)) - cmshould.set_under(cmshould(0)) refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='both') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) - x = [-1, 1.2, 2.3, 9.6] - np.testing.assert_array_equal(cmshould([0, 1, 2, 3]), cmshould(mynorm(x))) - x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax - np.testing.assert_array_equal(-1, mynorm(-1)) - np.testing.assert_array_equal(1, mynorm(1.1)) - np.testing.assert_array_equal(4, mynorm(12)) + assert mynorm(bounds[0] - 0.1) == -1 # under + assert mynorm(bounds[0] + 0.1) == 1 # first bin -> second color + assert mynorm(bounds[-1] - 0.1) == cmshould.N - 2 # next-to-last color + assert mynorm(bounds[-1] + 0.1) == cmshould.N # over - # Test raises - with pytest.raises(ValueError): - mcolors.BoundaryNorm(bounds, cmref.N, extend='both', clip=True) + x = [-1, 1.2, 2.3, 9.6] + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2, 3])) + x = np.random.randn(100) * 10 + 2 + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just min cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_under('white') cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) - cmshould.set_under(cmshould(0)) - np.testing.assert_array_equal(2, cmref.N) - np.testing.assert_array_equal(3, cmshould.N) + assert cmref.N == 2 + assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='min') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax x = [-1, 1.2, 2.3] - np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2])) x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just max cmref = mcolors.ListedColormap(['blue', 'red']) cmref.set_over('black') cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) - cmshould.set_over(cmshould(2)) - np.testing.assert_array_equal(2, cmref.N) - np.testing.assert_array_equal(3, cmshould.N) + assert cmref.N == 2 + assert cmshould.N == 3 refnorm = mcolors.BoundaryNorm(bounds, cmref.N) mynorm = mcolors.BoundaryNorm(bounds, cmshould.N, extend='max') - np.testing.assert_array_equal(refnorm.vmin, mynorm.vmin) - np.testing.assert_array_equal(refnorm.vmax, mynorm.vmax) + assert mynorm.vmin == refnorm.vmin + assert mynorm.vmax == refnorm.vmax x = [1.2, 2.3, 4] - np.testing.assert_array_equal(cmshould([0, 1, 2]), cmshould(mynorm(x))) + assert_array_equal(cmshould(mynorm(x)), cmshould([0, 1, 2])) x = np.random.randn(100) * 10 + 2 - np.testing.assert_array_equal(cmref(refnorm(x)), cmshould(mynorm(x))) - - # General case - bounds = [1, 2, 3, 4] - cmap = cm.get_cmap('jet') - mynorm = mcolors.BoundaryNorm(bounds, cmap.N, extend='both') - refnorm = mcolors.BoundaryNorm([-100] + bounds + [100], cmap.N) - x = np.random.randn(100) * 10 - 5 - ref = refnorm(x) - ref = np.where(ref == 0, -1, ref) - ref = np.where(ref == cmap.N-1, cmap.N, ref) - np.testing.assert_array_equal(ref, mynorm(x)) + assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) @pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]]) From 43d36ce1735ce9efcdea41648e898f68e63ff889 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 9 Apr 2020 18:38:11 -0400 Subject: [PATCH 080/602] Replace jQuery UI resizable with ResizeObserver API. --- .../backends/backend_webagg_core.py | 7 ++- lib/matplotlib/backends/web_backend/js/mpl.js | 56 +++++++++++-------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 71f68ca52c6b..2155381fbd77 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -322,7 +322,7 @@ def handle_resize(self, event): # canvas size to the figure's new size (which is hopefully # identical or within a pixel or so). self._png_is_old = True - self.manager.resize(*fig.bbox.size) + self.manager.resize(*fig.bbox.size, forward=False) self.resize_event() def handle_send_image_mode(self, event): @@ -427,10 +427,11 @@ def _get_toolbar(self, canvas): toolbar = self.ToolbarCls(canvas) return toolbar - def resize(self, w, h): + def resize(self, w, h, forward=True): self._send_event( 'resize', - size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio)) + size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio), + forward=forward) def set_window_title(self, title): self._send_event('figure_label', label=title) diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index c1455bbd9c5e..fadeeff7276a 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -112,7 +112,11 @@ mpl.figure.prototype._init_canvas = function () { var canvas_div = (this.canvas_div = document.createElement('div')); canvas_div.setAttribute( 'style', - 'position: relative; clear: both; outline: 0' + 'clear: both;' + + 'outline: 0;' + + 'overflow: hidden;' + + 'position: relative;' + + 'resize: both;' ); function on_keyboard_event_closure(name) { @@ -158,26 +162,28 @@ mpl.figure.prototype._init_canvas = function () { 'position: absolute; left: 0; top: 0; z-index: 1;' ); - var pass_mouse_events = true; - - $(canvas_div).resizable({ - start: function (_event, _ui) { - pass_mouse_events = false; - }, - resize: function (_event, ui) { - fig.request_resize(ui.size.width, ui.size.height); - }, - stop: function (_event, ui) { - pass_mouse_events = true; - fig.request_resize(ui.size.width, ui.size.height); - }, + var resizeObserver = new ResizeObserver(function (entries) { + var nentries = entries.length; + for (var i = 0; i < nentries; i++) { + var entry = entries[i]; + if (entry.contentBoxSize) { + fig.request_resize( + entry.contentBoxSize.inlineSize, + entry.contentBoxSize.blockSize + ); + } else { + fig.request_resize( + entry.contentRect.width, + entry.contentRect.height + ); + } + } }); + resizeObserver.observe(canvas_div); function on_mouse_event_closure(name) { return function (event) { - if (pass_mouse_events) { - return fig.mouse_event(event, name); - } + return fig.mouse_event(event, name); }; } @@ -219,11 +225,13 @@ mpl.figure.prototype._init_canvas = function () { this.rubberband_context = rubberband_canvas.getContext('2d'); this.rubberband_context.strokeStyle = '#000000'; - this._resize_canvas = function (width, height) { - // Keep the size of the canvas, canvas container, and rubber band - // canvas in synch. - canvas_div.style.width = width; - canvas_div.style.height = height; + this._resize_canvas = function (width, height, forward) { + // Keep the size of the canvas and rubber band canvas in sync with the + // canvas container. + if (forward) { + canvas_div.style.width = width + 'px'; + canvas_div.style.height = height + 'px'; + } canvas.setAttribute('width', width * mpl.ratio); canvas.setAttribute('height', height * mpl.ratio); @@ -238,7 +246,7 @@ mpl.figure.prototype._init_canvas = function () { // Set the figure to an initial 600x600px, this will subsequently be updated // upon first draw. - this._resize_canvas(600, 600); + this._resize_canvas(600, 600, true); // Disable right mouse context menu. this.rubberband_canvas.addEventListener('contextmenu', function (_e) { @@ -360,7 +368,7 @@ mpl.figure.prototype.handle_save = function (fig, _msg) { mpl.figure.prototype.handle_resize = function (fig, msg) { var size = msg['size']; if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) { - fig._resize_canvas(size[0], size[1]); + fig._resize_canvas(size[0], size[1], msg['forward']); fig.send_message('refresh', {}); } }; From 6c1606f80f69008a692aa32725102ef1efe37d42 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 9 Apr 2020 18:41:14 -0400 Subject: [PATCH 081/602] Embed minimal jQuery UI styling. We only really need clearfix, and some kind of style for the header. We could drop these entirely, but it's necessary to use these names for compatibility with the nbAgg styles. --- .../backends/web_backend/css/mpl.css | 21 +++++++++++++++++++ lib/matplotlib/backends/web_backend/js/mpl.js | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/css/mpl.css b/lib/matplotlib/backends/web_backend/css/mpl.css index 61ceb7163866..e55733d25ecf 100644 --- a/lib/matplotlib/backends/web_backend/css/mpl.css +++ b/lib/matplotlib/backends/web_backend/css/mpl.css @@ -1,3 +1,24 @@ +/* General styling */ +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} + +/* Header */ +.ui-widget-header { + border: 1px solid #dddddd; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + background: #e9e9e9; + color: #333333; + font-weight: bold; +} + /* Toolbar and items */ .mpl-toolbar { width: 100%; diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index fadeeff7276a..d921b7752ad6 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -112,8 +112,8 @@ mpl.figure.prototype._init_canvas = function () { var canvas_div = (this.canvas_div = document.createElement('div')); canvas_div.setAttribute( 'style', - 'clear: both;' + - 'outline: 0;' + + 'border: 1px solid #ddd;' + + 'clear: both;' + 'overflow: hidden;' + 'position: relative;' + 'resize: both;' From 95fb2441110c00de55a4b2176ddb91b0834979dc Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 9 Apr 2020 18:49:02 -0400 Subject: [PATCH 082/602] Remove jQuery and jQuery UI. It's now unused, except specfically in the IPython inline figure HTML, but we know it's available there. --- .gitignore | 2 - LICENSE/LICENSE_JQUERY | 61 ------------------- .../embedding_webagg_sgskip.py | 3 - .../backends/web_backend/all_figures.html | 3 - .../backends/web_backend/single_figure.html | 3 - setup.py | 57 +---------------- 6 files changed, 1 insertion(+), 128 deletions(-) delete mode 100644 LICENSE/LICENSE_JQUERY diff --git a/.gitignore b/.gitignore index 491c72e229b5..9634a3939793 100644 --- a/.gitignore +++ b/.gitignore @@ -102,7 +102,5 @@ lib/z.lib # Vendored dependencies # ######################### - -jquery-ui-*/ lib/matplotlib/backends/web_backend/node_modules/ lib/matplotlib/backends/web_backend/package-lock.json diff --git a/LICENSE/LICENSE_JQUERY b/LICENSE/LICENSE_JQUERY deleted file mode 100644 index f35387a3ab48..000000000000 --- a/LICENSE/LICENSE_JQUERY +++ /dev/null @@ -1,61 +0,0 @@ -Comment found in jQuery source code: - -/*! - * jQuery JavaScript Library v1.11.3 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-04-28T16:19Z - */ - -Comment found in jQuery UI source code: - -/*! jQuery UI - v1.11.4 - 2015-03-11 -* http://jqueryui.com -* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js -* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ - -Text found at http://jquery.org/license: - - jQuery Foundation projects are released under the terms of the license - specified in the project's repository or if not specified, under the - MIT license. - - The MIT License is simple and easy to understand and it places almost - no restrictions on what you can do with a jQuery Foundation project. - - You are free to use any jQuery Foundation project in any other project - (even commercial projects) as long as the copyright header is left - intact. - -The text links to https://tldrlegal.com/license/mit-license -which includes the following as the "Full License Text": - - The MIT License (MIT) - - Copyright (c) - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/examples/user_interfaces/embedding_webagg_sgskip.py b/examples/user_interfaces/embedding_webagg_sgskip.py index 33cafb860d80..d4c0e7e9e6f2 100644 --- a/examples/user_interfaces/embedding_webagg_sgskip.py +++ b/examples/user_interfaces/embedding_webagg_sgskip.py @@ -60,9 +60,6 @@ def create_figure(): type="text/css" /> - - - - diff --git a/lib/matplotlib/backends/web_backend/single_figure.html b/lib/matplotlib/backends/web_backend/single_figure.html index 664cf57006fa..71fe451f6f14 100644 --- a/lib/matplotlib/backends/web_backend/single_figure.html +++ b/lib/matplotlib/backends/web_backend/single_figure.html @@ -4,9 +4,6 @@ - - -