diff --git a/.appveyor.yml b/.appveyor.yml index afd1faa72756..5cdd09539f7e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,16 +12,13 @@ environment: global: PYTHONIOENCODING: UTF-8 - PYTEST_ARGS: -rawR --numprocesses=auto --timeout=300 --durations=25 + PYTEST_ARGS: -raR --numprocesses=auto --timeout=300 --durations=25 --cov-report= --cov=lib -m "not network" matrix: # theoretically the CONDA_INSTALL_LOCN could be only two: one for 32bit, # one for 64bit because we construct envs anyway. But using one for the # right python version is hopefully making it fast due to package caching. - - PYTHON_VERSION: "2.7" - CONDA_INSTALL_LOCN: "C:\\Miniconda-x64" - TEST_ALL: "no" - PYTHON_VERSION: "3.5" CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" TEST_ALL: "no" @@ -62,11 +59,11 @@ install: # - conda create -q -n test-environment python=%PYTHON_VERSION% msinttypes freetype=2.6 "libpng>=1.6.21,<1.7" zlib=1.2 tk=8.5 - pip setuptools numpy mock pandas sphinx tornado + pip setuptools numpy pandas sphinx tornado - activate test-environment - echo %PYTHON_VERSION% %TARGET_ARCH% # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124 - - pip install -q "pytest!=3.3.0,>=3.2.0" "pytest-cov>=2.3.1" pytest-rerunfailures pytest-timeout pytest-xdist + - pip install -q "pytest>=3.4" "pytest-cov>=2.3.1" pytest-rerunfailures pytest-timeout pytest-xdist # Apply patch to `subprocess` on Python versions > 2 and < 3.6.3 # https://github.com/matplotlib/matplotlib/issues/9176 @@ -86,7 +83,7 @@ install: - del %LIBRARY_LIB%\z.lib - set MPLBASEDIRLIST=%CONDA_PREFIX%\Library\;. # enables the local freetype build - - copy ci\travis\setup.cfg . + - set MPLLOCALFREETYPE=1 # Show the installed packages + versions - conda list @@ -109,7 +106,7 @@ test_script: - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" # tests - echo The following args are passed to pytest %PYTEST_ARGS% - - python tests.py %PYTEST_ARGS% + - pytest %PYTEST_ARGS% after_test: # After the tests were a success, build wheels with the static libs diff --git a/.circleci/config.yml b/.circleci/config.yml index a20d7e02ffbf..25864ea9e68c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,9 +88,9 @@ doc-bundle-run: &doc-bundle # jobs: - docs-python35: + docs-python36: docker: - - image: circleci/python:3.5 + - image: circleci/python:3.6 steps: - checkout @@ -121,9 +121,9 @@ jobs: name: "Deploy new docs" command: ./.circleci/deploy-docs.sh - docs-python27: + docs-python35: docker: - - image: circleci/python:2.7 + - image: circleci/python:3.5 steps: - checkout @@ -134,10 +134,7 @@ jobs: - run: <<: *deps-install environment: - NUMPY_VERSION: "==1.7.1" - # Linkchecker only works with python 2.7 for the time being. - # Linkchecker is currently broken with requests 2.10.0 so force an earlier version. - - run: python -mpip install --user $PRE requests==2.9.2 linkchecker + NUMPY_VERSION: "==1.10.0" - run: *mpl-install - run: *doc-build @@ -145,12 +142,6 @@ jobs: # We don't build the LaTeX docs here, so linkchecker will complain - run: touch doc/build/html/Matplotlib.pdf - # Linkchecker only works with python 2.7 for the time being - - run: - name: linkchecker - command: ~/.local/bin/linkchecker build/html/index.html - working_directory: doc - - run: *doc-bundle - store_artifacts: path: doc/build/sphinx-gallery-files.tar.gz @@ -172,4 +163,4 @@ workflows: build: jobs: - docs-python35 - - docs-python27 + - docs-python36 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000000..630d7dfda16e --- /dev/null +++ b/.flake8 @@ -0,0 +1,283 @@ +[flake8] +ignore = + # Normal default + E121,E123,E126,E226,E24,E704,W503,W504, + # Additional ignores: + E111, E114, E115, E116, E122, E124, E125, E127, E128, E129, E131, + E265, E266, + E305, E306, + E722, E741, + F401, F403, F811, F841, + # Some new flake8 ignores: + N801, N802, N803, N806, N812, + +exclude = + .git + build + # External files. + versioneer.py + tools/gh_api.py + tools/github_stats.py + .tox + .eggs + +per-file-ignores = + setup.py: E402 + setupext.py: E501 + + tools/compare_backend_driver_results.py: E501 + tools/subset.py: E221, E231, E251, E261, E302, E501, E701 + + matplotlib/_cm.py: E202, E203, E302 + matplotlib/_mathtext_data.py: E203, E261 + matplotlib/backend_bases.py: E225 + matplotlib/backends/_backend_tk.py: E203, E222, E225, E231, E271, E301, E303, E401, E501, E701 + matplotlib/backends/backend_agg.py: E261, E302, E701 + matplotlib/backends/backend_cairo.py: E203, E221, E261, E303, E402 + matplotlib/backends/backend_gtk3.py: E203, E221, E222, E225, E251, E261, E501 + matplotlib/backends/backend_macosx.py: E231, E261 + matplotlib/backends/backend_pgf.py: E303, E731 + matplotlib/backends/backend_ps.py: E203, E225, E228, E231, E261, E262, E302, E303, E501, E701 + matplotlib/backends/backend_svg.py: E203, E225, E228, E231, E261, E302, E501 + matplotlib/backends/qt_editor/formlayout.py: E301, E501 + matplotlib/backends/tkagg.py: E231, E302, E701 + matplotlib/backends/windowing.py: E301, E302 + matplotlib/font_manager.py: E203, E221, E251, E261, E262, E302, E501 + matplotlib/fontconfig_pattern.py: E201, E203, E221, E222, E225, E302 + matplotlib/legend_handler.py: E201, E501 + matplotlib/mathtext.py: E201, E202, E203, E211, E221, E222, E225, E231, E251, E261, E301, E302, E303, E402, E501 + matplotlib/patheffects.py: E231 + matplotlib/projections/geo.py: E203, E221, E231, E261, E502 + matplotlib/pylab.py: E501 + matplotlib/pyplot.py: E201, E202, E221, E222, E225, E231, E251, E261, E302, E501, E701 + matplotlib/rcsetup.py: E203, E225, E261, E302, E501 + matplotlib/sphinxext/mathmpl.py: E302 + matplotlib/sphinxext/only_directives.py: E302 + matplotlib/sphinxext/plot_directive.py: E261, E302, E402 + matplotlib/tests/test_image.py: E231 + matplotlib/tests/test_mathtext.py: E501 + matplotlib/transforms.py: E201, E202, E203, E501 + matplotlib/tri/triinterpolate.py: E201, E221 + matplotlib/type1font.py: E731 + + mpl_toolkits/__init__.py: E261 + mpl_toolkits/axes_grid/axes_rgb.py: E501 + mpl_toolkits/axes_grid1/axes_divider.py: E402, E501 + mpl_toolkits/axes_grid1/axes_grid.py: E225 + mpl_toolkits/axes_grid1/axes_rgb.py: E231 + mpl_toolkits/axes_grid1/axes_size.py: E261, E501 + mpl_toolkits/axes_grid1/colorbar.py: E225, E231, E261, E262, E302, E303, E501, E701 + mpl_toolkits/axes_grid1/inset_locator.py: E501 + mpl_toolkits/axes_grid1/mpl_axes.py: E303, E501 + mpl_toolkits/axisartist/angle_helper.py: E201, E203, E221, E222, E225, E231, E251, E261, E262, E302, E303, E501 + mpl_toolkits/axisartist/axis_artist.py: E201, E202, E221, E225, E228, E231, E251, E261, E262, E302, E303, E402, E501, E701 + mpl_toolkits/axisartist/axisline_style.py: E231, E261, E262, E302, E303 + mpl_toolkits/axisartist/axislines.py: E225, E231, E261, E303, E501 + mpl_toolkits/axisartist/clip_path.py: E225, E302, E303, E501 + mpl_toolkits/axisartist/floating_axes.py: E225, E231, E261, E262, E302, E303, E402, E501 + mpl_toolkits/axisartist/grid_finder.py: E231, E261, E302, E303, E402 + mpl_toolkits/axisartist/grid_helper_curvelinear.py: E225, E231, E261, E262, E271, E302, E303, E501 + mpl_toolkits/mplot3d/art3d.py: E203, E222, E225, E231 + mpl_toolkits/mplot3d/axes3d.py: E203, E231, E402, E501, E701 + mpl_toolkits/mplot3d/axis3d.py: E231, E302 + mpl_toolkits/mplot3d/proj3d.py: E231, E302, E303 + mpl_toolkits/tests/test_axes_grid1.py: E201, E202 + mpl_toolkits/tests/test_mplot3d.py: E231, E302 + + doc/conf.py: E402, E501 + doc/sphinxext/github.py: E302, E501 + doc/sphinxext/math_symbol_table.py: E302, E501 + doc/sphinxext/skip_deprecated.py: E302 + doc/users/generate_credits.py: E302, E501 + tutorials/advanced/path_tutorial.py: E402, E501 + tutorials/advanced/patheffects_guide.py: E402, E501 + tutorials/advanced/transforms_tutorial.py: E402, E501 + tutorials/colors/colormaps.py: E501 + tutorials/colors/colors.py: E402 + tutorials/intermediate/artists.py: E402, E501 + tutorials/intermediate/constrainedlayout_guide.py: E402, E501 + tutorials/intermediate/gridspec.py: E402, E501 + tutorials/intermediate/legend_guide.py: E402, E501 + tutorials/intermediate/tight_layout_guide.py: E402, E501 + tutorials/introductory/customizing.py: E501 + tutorials/introductory/images.py: E402, E501 + tutorials/introductory/pyplot.py: E402, E501 + tutorials/introductory/sample_plots.py: E501 + tutorials/introductory/usage.py: E402, E501 + tutorials/text/annotations.py: E501 + tutorials/text/mathtext.py: E501 + tutorials/text/pgf.py: E501 + tutorials/text/text_intro.py: E402 + tutorials/text/text_props.py: E501 + tutorials/text/usetex.py: E501 + tutorials/toolkits/axes_grid.py: E501 + tutorials/toolkits/axisartist.py: E501 + + examples/animation/frame_grabbing_sgskip.py: E402 + examples/axes_grid1/inset_locator_demo.py: E402 + examples/axisartist/demo_curvelinear_grid.py: E402 + examples/color/color_by_yvalue.py: E402 + examples/color/color_cycle_default.py: E402 + examples/color/color_cycler.py: E402 + examples/color/color_demo.py: E402 + examples/color/colorbar_basics.py: E402 + examples/color/colormap_reference.py: E402 + examples/color/named_colors.py: E402 + examples/event_handling/data_browser.py: E501 + examples/event_handling/path_editor.py: E501 + examples/event_handling/pick_event_demo.py: E501 + examples/event_handling/poly_editor.py: E501 + examples/event_handling/viewlims.py: E501 + examples/images_contours_and_fields/affine_image.py: E402 + examples/images_contours_and_fields/barb_demo.py: E402, E501 + examples/images_contours_and_fields/barcode_demo.py: E402 + examples/images_contours_and_fields/contour_corner_mask.py: E402 + examples/images_contours_and_fields/contour_demo.py: E402, E501 + examples/images_contours_and_fields/contour_image.py: E402 + examples/images_contours_and_fields/contourf_demo.py: E402, E501 + examples/images_contours_and_fields/contourf_hatching.py: E402 + examples/images_contours_and_fields/contourf_log.py: E402 + examples/images_contours_and_fields/custom_cmap.py: E402 + examples/images_contours_and_fields/demo_bboximage.py: E402 + examples/images_contours_and_fields/image_clip_path.py: E402 + examples/images_contours_and_fields/image_demo.py: E402 + examples/images_contours_and_fields/image_masked.py: E402 + examples/images_contours_and_fields/image_transparency_blend.py: E402 + examples/images_contours_and_fields/image_zcoord.py: E402 + examples/images_contours_and_fields/interpolation_methods.py: E402 + examples/images_contours_and_fields/irregulardatagrid.py: E402 + examples/images_contours_and_fields/layer_images.py: E402 + examples/images_contours_and_fields/matshow.py: E402 + examples/images_contours_and_fields/multi_image.py: E402 + examples/images_contours_and_fields/pcolor_demo.py: E402 + examples/images_contours_and_fields/plot_streamplot.py: E402 + examples/images_contours_and_fields/quadmesh_demo.py: E402 + examples/images_contours_and_fields/quiver_demo.py: E402 + examples/images_contours_and_fields/quiver_simple_demo.py: E402 + examples/images_contours_and_fields/shading_example.py: E402, E501 + examples/images_contours_and_fields/specgram_demo.py: E402, E501 + examples/images_contours_and_fields/spy_demos.py: E402 + examples/images_contours_and_fields/tricontour_demo.py: E201, E402 + examples/images_contours_and_fields/tricontour_smooth_delaunay.py: E402 + examples/images_contours_and_fields/tricontour_smooth_user.py: E402 + examples/images_contours_and_fields/trigradient_demo.py: E402 + examples/images_contours_and_fields/triinterp_demo.py: E402 + examples/images_contours_and_fields/tripcolor_demo.py: E201, E402 + examples/images_contours_and_fields/triplot_demo.py: E201, E402 + examples/images_contours_and_fields/watermark_image.py: E402 + examples/lines_bars_and_markers/fill_between_demo.py: E402 + examples/lines_bars_and_markers/filled_step.py: E402 + examples/lines_bars_and_markers/joinstyle.py: E402 + examples/lines_bars_and_markers/scatter_piecharts.py: E402 + examples/lines_bars_and_markers/span_regions.py: E402 + examples/misc/agg_buffer.py: E402 + examples/misc/anchored_artists.py: E501 + examples/misc/contour_manual.py: E501 + examples/misc/font_indexing.py: E501 + examples/misc/ftface_props.py: E501 + examples/misc/histogram_path.py: E402 + examples/misc/print_stdout_sgskip.py: E402 + examples/misc/svg_filter_line.py: E402, E501 + examples/misc/svg_filter_pie.py: E402, E501 + examples/misc/table_demo.py: E201 + examples/mplot3d/voxels.py: E501 + examples/mplot3d/wire3d_zero_stride.py: E501 + examples/pie_and_polar_charts/nested_pie.py: E402 + examples/pie_and_polar_charts/pie_and_donut_labels.py: E402 + examples/pie_and_polar_charts/pie_demo2.py: E402 + examples/pie_and_polar_charts/pie_features.py: E402 + examples/pie_and_polar_charts/polar_bar.py: E402 + examples/pie_and_polar_charts/polar_demo.py: E402 + examples/pie_and_polar_charts/polar_legend.py: E402 + examples/pie_and_polar_charts/polar_scatter.py: E402 + examples/pyplots/align_ylabels.py: E402 + examples/pyplots/annotate_transform.py: E228, E251, E402, E501 + examples/pyplots/annotation_basic.py: E402 + examples/pyplots/annotation_polar.py: E231, E402 + examples/pyplots/auto_subplots_adjust.py: E231, E261, E302, E402 + examples/pyplots/boxplot_demo_pyplot.py: E231, E402 + examples/pyplots/compound_path_demo.py: E231 + examples/pyplots/dollar_ticks.py: E402 + examples/pyplots/fig_axes_customize_simple.py: E261, E402 + examples/pyplots/fig_axes_labels_simple.py: E402 + examples/pyplots/fig_x.py: E402 + examples/pyplots/pyplot_formatstr.py: E231, E402 + examples/pyplots/pyplot_mathtext.py: E231, E402 + examples/pyplots/pyplot_scales.py: E402 + examples/pyplots/pyplot_simple.py: E231 + examples/pyplots/pyplot_simple.py: E402 + examples/pyplots/pyplot_text.py: E402 + examples/pyplots/pyplot_three.py: E402 + examples/pyplots/pyplot_two_subplots.py: E302, E402 + examples/pyplots/text_commands.py: E231, E402 + examples/pyplots/text_layout.py: E231, E402 + examples/pyplots/whats_new_1_subplot3d.py: E402 + examples/pyplots/whats_new_98_4_fancy.py: E225, E261, E302, E402 + examples/pyplots/whats_new_98_4_fill_between.py: E225, E402 + examples/pyplots/whats_new_98_4_legend.py: E228, E402 + examples/pyplots/whats_new_99_axes_grid.py: E402 + examples/pyplots/whats_new_99_mplot3d.py: E402 + examples/pyplots/whats_new_99_spines.py: E231, E261, E402 + examples/recipes/placing_text_boxes.py: E501 + examples/scales/power_norm.py: E402 + examples/shapes_and_collections/artist_reference.py: E402 + examples/shapes_and_collections/collections.py: E402 + examples/shapes_and_collections/compound_path.py: E402 + examples/shapes_and_collections/dolphin.py: E402, E501 + examples/shapes_and_collections/donut.py: E402 + examples/shapes_and_collections/ellipse_collection.py: E402 + examples/shapes_and_collections/ellipse_demo.py: E402 + examples/shapes_and_collections/fancybox_demo.py: E402 + examples/shapes_and_collections/hatch_demo.py: E402 + examples/shapes_and_collections/line_collection.py: E402 + examples/shapes_and_collections/marker_path.py: E402 + examples/shapes_and_collections/patch_collection.py: E402 + examples/shapes_and_collections/path_patch.py: E402, E501 + examples/shapes_and_collections/quad_bezier.py: E402 + examples/shapes_and_collections/scatter.py: E402 + examples/showcase/firefox.py: E501 + examples/specialty_plots/anscombe.py: E402, E501 + examples/specialty_plots/radar_chart.py: E402 + examples/specialty_plots/sankey_basics.py: E402, E501 + examples/specialty_plots/sankey_links.py: E402 + examples/specialty_plots/sankey_rankine.py: E402 + examples/specialty_plots/skewt.py: E402 + examples/statistics/boxplot_demo.py: E501 + examples/style_sheets/bmh.py: E501 + examples/style_sheets/ggplot.py: E501 + examples/style_sheets/plot_solarizedlight2.py: E501 + examples/subplots_axes_and_figures/axes_margins.py: E402 + examples/subplots_axes_and_figures/axes_zoom_effect.py: E402 + examples/subplots_axes_and_figures/demo_constrained_layout.py: E402 + examples/subplots_axes_and_figures/demo_tight_layout.py: E402 + examples/subplots_axes_and_figures/two_scales.py: E402 + examples/subplots_axes_and_figures/zoom_inset_axes.py: E402 + examples/tests/backend_driver_sgskip.py: E402, E501 + examples/text_labels_and_annotations/annotation_demo.py: E501 + examples/text_labels_and_annotations/custom_legends.py: E402 + examples/text_labels_and_annotations/font_family_rc_sgskip.py: E402 + examples/text_labels_and_annotations/font_file.py: E402 + examples/text_labels_and_annotations/legend.py: E402 + examples/text_labels_and_annotations/line_with_text.py: E402 + examples/text_labels_and_annotations/mathtext_asarray.py: E402 + examples/text_labels_and_annotations/tex_demo.py: E402 + examples/text_labels_and_annotations/watermark_text.py: E402 + examples/ticks_and_spines/auto_ticks.py: E501 + examples/user_interfaces/canvasagg.py: E402 + examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py: E402 + examples/user_interfaces/embedding_in_gtk3_sgskip.py: E402 + examples/user_interfaces/embedding_in_qt_sgskip.py: E402 + examples/user_interfaces/embedding_in_wx2_sgskip.py: E501 + examples/user_interfaces/embedding_in_wx3_sgskip.py: E501 + examples/user_interfaces/embedding_in_wx4_sgskip.py: E501 + examples/user_interfaces/embedding_in_wx5_sgskip.py: E501 + examples/user_interfaces/embedding_webagg_sgskip.py: E501 + examples/user_interfaces/gtk_spreadsheet_sgskip.py: E402 + examples/user_interfaces/mathtext_wx_sgskip.py: E402, E501 + examples/user_interfaces/mpl_with_glade3_sgskip.py: E402 + examples/user_interfaces/pylab_with_gtk_sgskip.py: E402, E501 + examples/user_interfaces/toolmanager_sgskip.py: E402 + examples/userdemo/custom_boxstyle01.py: E402 + examples/userdemo/pgf_preamble_sgskip.py: E402 + examples/userdemo/simple_annotate01.py: E501 + examples/widgets/rectangle_selector.py: E501 diff --git a/.gitignore b/.gitignore index 36d13934bcf0..e77bc182dab0 100644 --- a/.gitignore +++ b/.gitignore @@ -74,12 +74,11 @@ examples/*/*.eps examples/*/*.svgz examples/tests/* !examples/tests/backend_driver.py -texput.log -texput.aux result_images # Nose/Pytest generated files # ############################### +.pytest_cache/ .cache/ .coverage .coverage.* diff --git a/.mailmap b/.mailmap index f638116e560f..2b2d110e0299 100644 --- a/.mailmap +++ b/.mailmap @@ -3,6 +3,8 @@ Adam Ortiz Adrien F. Vincent Adrien F. Vincent +Alvaro Sanchez + Andrew Dawson anykraus @@ -13,16 +15,25 @@ Ben Cohen Ben Root Benjamin Root +Benedikt Daurer + Benjamin Congdon Benjamin Congdon bcongdon +Bruno Zohreh + Casper van der Wel +Chris Holdgraf + Christoph Gohlke cgohlke Christoph Gohlke C. Gohlke +Christoph Gohlke Cimarron Mittelsteadt Cimarron +cldssty + Conner R. Phillips Dan Hickstein @@ -34,6 +45,8 @@ David Kua Devashish Deshpande +Dietmar Schwertberger + endolith Eric Dill @@ -49,10 +62,18 @@ Florian Le Bourdais Francesco Montesano montefra +Gauravjeet + +Hajoon Choi + hannah Hans Moritz Günther +Harshit Patni + +ImportanceOfBeingErnest + J. Goutin JGoutin Jack Kelly @@ -82,6 +103,8 @@ Joseph Fox-Rabinovitz Joseph Fox-Rabinovitz +Julien Lhermitte + Julien Schueller Julien Schueller @@ -100,10 +123,16 @@ Lennart Fricke Levi Kilcher +Leon Yin + Lion Krischer +Marek Rudnicki + Martin Fitzpatrick +Matt Newville + Matthew Emmett Matthew Emmett @@ -112,6 +141,8 @@ Matthias Bussonnier Matthias Lüthi Matthias Lüthi +Matti Picus + Michael Droettboom Michael Droettboom Michael Droettboom @@ -133,8 +164,13 @@ Nicolas P. Rougier OceanWolf +Olivier + Patrick Chen +Paul Ganssle +Paul Ganssle + Paul Hobson Paul Hobson vagrant @@ -151,6 +187,8 @@ Phil Elson productivememberofsociety666 none +Rishikesh + RyanPan Scott Lasley @@ -168,13 +206,25 @@ Stefan van der Walt switham switham +Taehoon Lee + Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell <“tcaswell@gmail.com”> Trish Gillett-Kawamoto +Tuan Dung Tran + +Víctor Zabalza + +Vidur Satija + +WANG Aiyong + Werner F Bruhin Yunfei Yang Yunfei Yang Yunfei Yang Yunfei Yang + +Zac Hatfield-Dodds diff --git a/.travis.yml b/.travis.yml index 6435c9d5c326..20283d195c29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,14 +18,16 @@ addons: paths: - result_images.tar.bz2 apt: + sources: + - sourceline: ppa:jonathonf/ffmpeg-3 packages: - cm-super - dvipng + - ffmpeg - gdb - gir1.2-gtk-3.0 - graphviz - inkscape - - libav-tools - libcairo2 - libgeos-dev - libgirepository-1.0.1 @@ -37,6 +39,7 @@ addons: - texlive-latex-extra - texlive-latex-recommended - texlive-xetex + - texlive-luatex env: global: @@ -44,59 +47,43 @@ env: - ARTIFACTS_BUCKET=matplotlib-test-results - secure: RgJI7BBL8aX5FTOQe7xiXqWHMxWokd6GNUWp1NUV2mRLXPb9dI0RXqZt3UJwKTAzf1z/OtlHDmEkBoTVK81E9iUxK5npwyyjhJ8yTJmwfQtQF2n51Q1Ww9p+XSLORrOzZc7kAo6Kw6FIXN1pfctgYq2bQkrwJPRx/oPR8f6hcbY= - secure: E7OCdqhZ+PlwJcn+Hd6ns9TDJgEUXiUNEI0wu7xjxB2vBRRIKtZMbuaZjd+iKDqCKuVOJKu0ClBUYxmgmpLicTwi34CfTUYt6D4uhrU+8hBBOn1iiK51cl/aBvlUUrqaRLVhukNEBGZcyqAjXSA/Qsnp2iELEmAfOUa92ZYo1sk= - - secure: "dfjNqGKzQG5bu3FnDNwLG8H/C4QoieFo4PfFmZPdM2RY7WIzukwKFNT6kiDfOrpwt+2bR7FhzjOGlDECGtlGOtYPN8XuXGjhcP4a4IfakdbDfF+D3NPIpf5VlE6776k0VpvcZBTMYJKNFIMc7QPkOwjvNJ2aXyfe3hBuGlKJzQU=" - - CYCLER=cycler - - DATEUTIL=python-dateutil - - MOCK= - - NOSE= - - NUMPY=numpy - - PANDAS= - - PILLOW=pillow - - PYPARSING=pyparsing - # pytest-timeout master depends on pytest>=3.6. Testing with pytest 3.1 is - # still supported; this is tested by the first matrix entry. - - PYTEST='pytest>=3.6' - - PYTEST_COV=pytest-cov - - PYTEST_PEP8= - - PYTEST_TIMEOUT=pytest-timeout - - SPHINX=sphinx - - OPENBLAS_NUM_THREADS=1 - - NPROC=2 - - RUN_PEP8= - - PYTEST_ARGS="-rawR --maxfail=50 --timeout=300 --durations=25 --cov-report= --cov=lib -n $NPROC" - - PYTHON_ARGS= + - secure: dfjNqGKzQG5bu3FnDNwLG8H/C4QoieFo4PfFmZPdM2RY7WIzukwKFNT6kiDfOrpwt+2bR7FhzjOGlDECGtlGOtYPN8XuXGjhcP4a4IfakdbDfF+D3NPIpf5VlE6776k0VpvcZBTMYJKNFIMc7QPkOwjvNJ2aXyfe3hBuGlKJzQU= + # Variables controlling the build. + - MPLLOCALFREETYPE=1 + # Variable for the location of an extra pip requirement file + - EXTRAREQS= + # Variable for the location of a pip version file + - PINNEDVERS= + # Variables controlling the test run. - DELETE_FONT_CACHE= + - NO_AT_BRIDGE=1 # Necessary for GTK3 interactive test. + # The number of processes is hardcoded, because using too many causes the + # Travis VM to run out of memory (since so many copies of inkscape and + # ghostscript are running at the same time). + - NPROC=2 + - OPENBLAS_NUM_THREADS=1 + - PYTHONFAULTHANDLER=1 + - PYTEST_ADDOPTS="-raR --maxfail=50 --timeout=300 --durations=25 --cov-report= --cov=lib -n $NPROC" + - RUN_FLAKE8= matrix: include: - - python: 2.7 + - python: 3.5 + dist: trusty # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124. env: - - CYCLER=cycler==0.10 - - DATEUTIL=python-dateutil==2.1 - - MOCK=mock - - NOSE=nose - - NUMPY=numpy==1.7.1 - - PANDAS='pandas<0.21.0' - - PYPARSING=pyparsing==2.0.1 - - PYTEST=pytest==3.1.0 - - PYTEST_COV=pytest-cov==2.3.1 - - PYTEST_TIMEOUT=pytest-timeout==1.2.1 # Newer pytest-timeouts don't support pytest <3.4. - - SPHINX=sphinx==1.3 - - python: 3.4 - env: PYTHON_ARGS=-OO + - PINNEDVERS='-c requirements/testing/travis35.txt' - python: 3.6 - env: DELETE_FONT_CACHE=1 PANDAS='pandas<0.21.0' PYTEST_PEP8=pytest-pep8 RUN_PEP8=--pep8 + env: + - DELETE_FONT_CACHE=1 + - EXTRAREQS='-r requirements/testing/travis36.txt' + - RUN_FLAKE8=1 - python: 3.7 sudo: true - python: "nightly" env: PRE=--pre - os: osx - osx_image: xcode7.3 language: generic # https://github.com/travis-ci/travis-ci/issues/2312 - env: - - MOCK=mock - - PILLOW='pillow!=5.1.0' only: master cache: # As for now travis caches only "$HOME/.cache/pip" @@ -110,104 +97,89 @@ matrix: allow_failures: - python: "nightly" -before_install: - - | - if [[ $TRAVIS_OS_NAME != 'osx' ]]; then - # test with non-ascii in path - mkdir /tmp/λ - export PATH=$PATH:/tmp/λ - export PATH=/usr/lib/ccache:$PATH - else - brew update - brew tap homebrew/gui - brew install python libpng ffmpeg imagemagick mplayer ccache - # We could install ghostscript and inkscape here to test svg and pdf - # but this makes the test time really long. - # brew install ghostscript inkscape - export PATH=/usr/local/opt/ccache/libexec:$PATH - fi +before_install: | + # test with non-ascii in path + if [[ $TRAVIS_OS_NAME != 'osx' ]]; then + export PATH=/usr/lib/ccache:$PATH + else + ci/silence brew update + brew upgrade python + brew install ffmpeg imagemagick mplayer ccache + hash -r + which python + python --version + # We could install ghostscript and inkscape here to test svg and pdf + # but this makes the test time really long. + # brew install ghostscript inkscape + export PATH=/usr/local/opt/python/libexec/bin:/usr/local/opt/ccache/libexec:$PATH + fi install: - # Upgrade pip and setuptools. Mock has issues with the default version of - # setuptools - | - # Setup environment + # Setup environment. ccache -s git describe - # Upgrade pip and setuptools and wheel to get as clean an install as possible - pip install --upgrade pip setuptools wheel + # Upgrade pip and setuptools and wheel to get as clean an install as possible. + python -mpip install --upgrade pip setuptools wheel - | - # Install dependencies from PyPI - pip install --upgrade $PRE \ - codecov \ - coverage \ - $CYCLER \ - $MOCK \ - $NOSE \ - $NUMPY \ - $PANDAS \ - codecov \ - coverage \ - $PILLOW \ - $PYPARSING \ - $DATEUTIL \ - $SPHINX + # Install dependencies from PyPI. + python -mpip install --upgrade $PRE -r requirements/testing/travis_all.txt $EXTRAREQS $PINNEDVERS # GUI toolkits are pip-installable only for some versions of Python so # don't fail if we can't install them. Make it easier to check whether the # install was successful by trying to import the toolkit (sometimes, the # install appears to be successful but shared libraries cannot be loaded at # runtime, so an actual import is a better check). - pip install cairocffi pgi && + python -mpip install --upgrade cairocffi>=0.8 pgi>=0.0.11.2 && python -c 'import pgi as gi; gi.require_version("Gtk", "3.0"); from pgi.repository import Gtk' && echo 'pgi is available' || echo 'pgi is not available' - pip install pyqt5==5.9 && + python -mpip install --upgrade pyqt5 && python -c 'import PyQt5.QtCore' && echo 'PyQt5 is available' || echo 'PyQt5 is not available' - pip install -U --pre \ - --no-index -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-14.04 \ + python -mpip install --upgrade \ + -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-14.04 \ wxPython && python -c 'import wx' && echo 'wxPython is available' || echo 'wxPython is not available' - pip install $PRE \ - $PYTEST \ - $PYTEST_COV \ - pytest-faulthandler \ - $PYTEST_PEP8 \ - pytest-rerunfailures \ - $PYTEST_TIMEOUT \ - pytest-xdist - - # Use the special local version of freetype for testing - cp ci/travis/setup.cfg . - | # Install matplotlib - pip install -ve . + python -mpip install -ve . + +before_script: | + if [[ $TRAVIS_OS_NAME != 'osx' ]]; then + export DISPLAY=:99.0 + sh -e /etc/init.d/xvfb start + fi + if [[ $DELETE_FONT_CACHE == 1 ]]; then + rm -rf ~/.cache/matplotlib + fi -before_script: +script: + # each script we want to run need to go in it's own section and the program you want + # to fail travis need to be the last thing called - | - if [[ $TRAVIS_OS_NAME != 'osx' ]]; then - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start + echo "Calling pytest with the following arguments: $PYTEST_ADDOPTS" + python -mpytest + - | + if [[ $RUN_FLAKE8 == 1 ]]; then + flake8 --statistics && echo "Flake8 passed without any issues!" fi -script: ci/travis/test_script.sh -before_cache: - - rm -rf $HOME/.cache/matplotlib/tex.cache - - rm -rf $HOME/.cache/matplotlib/test_cache +before_cache: | + rm -rf $HOME/.cache/matplotlib/tex.cache + rm -rf $HOME/.cache/matplotlib/test_cache -after_failure: - - | - if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]]; then - tar cjf result_images.tar.bz2 result_images - echo 'See "Uploading Artifacts" near the end of the log for the download URL' - else - echo "The result images will only be uploaded if they are on the matplotlib/matplotlib repo - this is for security reasons to prevent arbitrary PRs echoing security details." - fi +after_failure: | + if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]]; then + tar cjf result_images.tar.bz2 result_images + echo 'See "Uploading Artifacts" near the end of the log for the download URL' + else + echo "The result images will only be uploaded if they are on the matplotlib/matplotlib repo - this is for security reasons to prevent arbitrary PRs echoing security details." + fi -after_success: - - codecov -e TRAVIS_PYTHON_VERSION +after_success: | + codecov -e TRAVIS_PYTHON_VERSION diff --git a/INSTALL.rst b/INSTALL.rst index 99184145f05c..9e1b1338e608 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -14,8 +14,8 @@ Installing Installing an official release ============================== -Matplotlib and most of its dependencies are all available as wheel -packages for macOS, Windows and Linux distributions:: +Matplotlib and its dependencies are available as wheel packages for macOS, +Windows and Linux distributions:: python -mpip install -U pip python -mpip install -U matplotlib @@ -32,61 +32,31 @@ Although not required, we suggest also installing ``IPython`` for interactive use. To easily install a complete Scientific Python stack, see :ref:`install_scipy_dists` below. -.. _installing_windows: - -Windows -------- - -In case Python 2.7 or 3.4 are not installed for all users, -the Microsoft Visual C++ 2008 -(`64 bit `__ -or -`32 bit `__ -for Python 2.7) or Microsoft Visual C++ 2010 -(`64 bit `__ -or -`32 bit `__ -for Python 3.4) redistributable packages need to be installed. - macOS ----- -If you are using Python 2.7 on a Mac you may need to do:: - - xcode-select --install - -so that *subprocess32*, a dependency, may be compiled. - To use the native OSX backend you will need :ref:`a framework build ` build of Python. - -Linux ------ - -On extremely old versions of Linux and Python 2.7 you may need to -install the master version of *subprocess32* (`see comments -`__). - - -Test Data +Test data --------- The wheels (:file:`*.whl`) on the `PyPI download page `_ do not contain test data or example code. + If you want to try the many demos that come in the Matplotlib source distribution, download the :file:`*.tar.gz` file and look in the :file:`examples` subdirectory. -To run the test suite: - * extract the :file:`lib\\matplotlib\\tests` or - :file:`lib\\mpl_toolkits\\tests` directories from the source distribution; - * install test dependencies: `pytest `_, - `mock `_, Pillow, MiKTeX, GhostScript, - ffmpeg, avconv, ImageMagick, and `Inkscape `_; - * run ``py.test path\to\tests\directory``. +To run the test suite: +* extract the :file:`lib/matplotlib/tests` or :file:`lib/mpl_toolkits/tests` + directories from the source distribution; +* install test dependencies: `pytest `_, + Pillow, MiKTeX, GhostScript, ffmpeg, avconv, ImageMagick, and `Inkscape + `_; +* run ``python -mpytest``. Third-party distributions of Matplotlib ======================================= @@ -100,13 +70,12 @@ Scientific Python Distributions `_ and `ActiveState `_ are excellent choices that "just work" out of the box for Windows, macOS and common -Linux platforms. `WinPython `__ is an -option for windows users. All of these distributions include +Linux platforms. `WinPython `_ is an +option for Windows users. All of these distributions include Matplotlib and *lots* of other useful (data) science tools. - -Linux : using your package manager ----------------------------------- +Linux: using your package manager +--------------------------------- If you are on Linux, you might prefer to use your package manager. Matplotlib is packaged for almost every major Linux distribution. @@ -116,8 +85,6 @@ is packaged for almost every major Linux distribution. * Red Hat: ``sudo yum install python3-matplotlib`` * Arch: ``sudo pacman -S python-matplotlib`` - - .. _install_from_source: Installing from source @@ -166,66 +133,62 @@ e.g., if the header of some required library is in Dependencies ------------ -Matplotlib requires a large number of dependencies: - - * `Python `_ (>= 2.7 or >= 3.4) - * `NumPy `_ (>= |minimum_numpy_version|) - * `setuptools `__ - * `dateutil `_ (>= 2.1) - * `pyparsing `__ - * `libpng `__ (>= 1.2) - * `pytz `__ - * FreeType (>= 2.3) - * `cycler `__ (>= 0.10.0) - * `six `_ - * `backports.functools_lru_cache `_ - (for Python 2.7 only) - * `subprocess32 `_ (for Python - 2.7 only, on Linux and macOS only) - * `kiwisolver `__ (>= 1.0.0) +Matplotlib requires the following dependencies: + +* `Python `_ (>= 3.5) +* `FreeType `_ (>= 2.3) +* `libpng `_ (>= 1.2) +* `NumPy `_ (>= |minimum_numpy_version|) +* `setuptools `_ +* `cycler `_ (>= 0.10.0) +* `dateutil `_ (>= 2.1) +* `kiwisolver `_ (>= 1.0.0) +* `pyparsing `_ Optionally, you can also install a number of packages to enable better user interface toolkits. See :ref:`what-is-a-backend` for more details on the optional Matplotlib backends and the capabilities they provide. - * :term:`tk` (>= 8.3, != 8.6.0 or 8.6.1): for the Tk-based backends; - * `PyQt4 `_ (>= 4.6) or - `PySide `_: for the Qt4-based backend; - * `PyQt5 `_: for the Qt5-based backend; - * :term:`pygtk` (>= 2.4): for the GTK and the GTKAgg backend; - * :term:`wxpython` (>= 2.9 or later): for the WX or WXAgg backend; - * `cairocffi `__ (>= - v0.8): for cairo based backends; - * `pycairo `_: for GTK3Cairo; - * `Tornado `_: for the WebAgg backend; +* :term:`tk` (>= 8.3, != 8.6.0 or 8.6.1): for the Tk-based backends; +* `PyQt4 `_ (>= 4.6) or + `PySide `_ (>= 1.0.3): for the Qt4-based + backends; +* `PyQt5 `_: for the Qt5-based backends; +* `PyGObject `_ or + `pgi `_ (>= 0.0.11.2): for the GTK3-based + backends; +* :term:`wxpython` (>= 4): for the WX-based backends; +* `cairocffi `_ (>= 0.8) or + `pycairo `_: for the cairo-based + backends; +* `Tornado `_: for the WebAgg backend; For better support of animation output format and image file formats, LaTeX, etc., you can install the following: - * `ffmpeg `_/`avconv - `_: for saving movies; - * `ImageMagick `_: for saving - animated gifs; - * `Pillow `_ (>=2.0): for a larger selection of - image file formats: JPEG, BMP, and TIFF image files; - * `LaTeX `_ and `GhostScript - `_ (for rendering text with LaTeX). +* `ffmpeg `_/`avconv + `_: for saving movies; +* `ImageMagick `_: for saving + animated gifs; +* `Pillow `_ (>= 3.4): for a larger + selection of image file formats: JPEG, BMP, and TIFF image files; +* `LaTeX `_ and `GhostScript + `_ (for rendering text with LaTeX). .. note:: - Matplotlib depends on a large number of non-Python libraries. - `pkg-config `__ - can be used to find required non-Python libraries and thus make the install - go more smoothly if the libraries and headers are not in the expected - locations. + Matplotlib depends on non-Python libraries. `pkg-config + `_ can be used + to find required non-Python libraries and thus make the install go more + smoothly if the libraries and headers are not in the expected locations. .. note:: The following libraries are shipped with Matplotlib: - - `Agg`: the Anti-Grain Geometry C++ rendering engine; - - `qhull`: to compute Delaunay triangulation; - - `ttconv`: a true type font utility. + - `Agg`: the Anti-Grain Geometry C++ rendering engine; + - `qhull`: to compute Delaunay triangulation; + - `ttconv`: a TrueType font utility. .. _build_linux: @@ -252,7 +215,6 @@ Matplotlib by first installing ``yum-builddep`` and then running:: These commands do not build Matplotlib, but instead get and install the build dependencies, which will make building from source easier. - .. _build_osx: Building on macOS @@ -284,22 +246,21 @@ found that, to run the tests, their PYTHONPATH must include /path/to/anaconda/.../site-packages and their DYLD_FALLBACK_LIBRARY_PATH must include /path/to/anaconda/lib. - .. _build_windows: Building on Windows ------------------- The Python shipped from https://www.python.org is compiled with Visual Studio -2008 for versions before 3.3, Visual Studio 2010 for 3.3 and 3.4, and -Visual Studio 2015 for 3.5 and 3.6. Python extensions are recommended to be compiled -with the same compiler. +2015 for 3.5+. Python extensions should be compiled with the same +compiler, see e.g. +https://packaging.python.org/guides/packaging-binary-extensions/#setting-up-a-build-environment-on-windows +for how to set up a build environment. Since there is no canonical Windows package manager, the methods for building FreeType, zlib, and libpng from source code are documented as a build script at `matplotlib-winbuild `_. - There are a few possibilities to build Matplotlib on Windows: * Wheels via `matplotlib-winbuild `_ @@ -309,25 +270,20 @@ There are a few possibilities to build Matplotlib on Windows: Wheel builds using conda packages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This is a wheel build, but we use conda packages to get all the requirements. The binary -requirements (png, FreeType,...) are statically linked and therefore not needed during the wheel -install. +This is a wheel build, but we use conda packages to get all the requirements. +The binary requirements (png, FreeType,...) are statically linked and therefore +not needed during the wheel install. -The commands below assume that you can compile a native Python lib for the Python version of your -choice. See `this howto `_ -for how to install and setup such environments. If in doubt: use Python >= 3.5 as it mostly works -without fiddling with environment variables:: +:: # create a new environment with the required packages - conda create -n "matplotlib_build" python=3.5 numpy python-dateutil pyparsing pytz tornado "cycler>=0.10" tk libpng zlib freetype + conda create -n "matplotlib_build" python=3.5 numpy python-dateutil pyparsing pytz tornado cycler tk libpng zlib freetype activate matplotlib_build # if you want a qt backend, you also have to install pyqt (be aware that pyqt doesn't mix well if # you have created the environment with conda-forge already activated...) conda install pyqt # this package is only available in the conda-forge channel conda install -c conda-forge msinttypes - # for Python 2.7 - conda install -c conda-forge backports.functools_lru_cache # copy the libs which have "wrong" names set LIBRARY_LIB=%CONDA_PREFIX%\Library\lib @@ -345,23 +301,8 @@ without fiddling with environment variables:: The `build_alllocal.cmd` script in the root folder automates these steps if you have already created and activated the conda environment. - Conda packages ^^^^^^^^^^^^^^ -This needs a `working installed C compiler -`_ -for the version of Python you are compiling the package for but you don't need -to setup the environment variables:: - - # only the first time... - conda install conda-build - - # the Python version you want a package for... - set CONDA_PY=3.5 - - # builds the package, using a clean build environment - conda build ci\conda_recipe - - # install the new package - conda install --use-local matplotlib +The conda packaging scripts for Matplotlib are available at +https://github.com/conda-forge/python-feedstock. diff --git a/MANIFEST.in b/MANIFEST.in index 3e1949dc34e6..39e693f1014f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ include pytest.ini include MANIFEST.in include matplotlibrc.template setup.cfg.template include setupext.py setup.py -include lib/matplotlib/mpl-data/lineprops.glade include lib/matplotlib/mpl-data/matplotlibrc include lib/matplotlib/mpl-data/images/* include lib/matplotlib/mpl-data/fonts/ttf/* @@ -19,7 +18,6 @@ recursive-include lib/matplotlib/mpl-data/sample_data * recursive-include src *.cpp *.c *.h *.m recursive-include tools * recursive-include tutorials * -recursive-include unit * include versioneer.py include lib/matplotlib/_version.py include tests.py diff --git a/README.rst b/README.rst index b13658c300f4..6586a7c73dd9 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,9 @@ platforms. Matplotlib can be used in Python scripts, the Python and IPython shell (à la MATLAB or Mathematica), web application servers, and various graphical user interface toolkits. +NOTE: The current master branch is now Python 3 only. Python 2 support is +being dropped. + `Home page `_ Installation @@ -46,16 +49,16 @@ Testing After installation, you can launch the test suite:: - py.test + pytest Or from the Python interpreter:: import matplotlib matplotlib.test() -Consider reading http://matplotlib.org/devel/coding_guide.html#testing for -more information. Note that the test suite requires pytest and, on Python 2.7, -mock. Please install with pip or your package manager of choice. +Consider reading http://matplotlib.org/devel/coding_guide.html#testing for more +information. Note that the test suite requires pytest. Please install with pip +or your package manager of choice. Contact ======= @@ -80,4 +83,4 @@ You want to tell us about it – best of all! Start at the `contributing guide `_! -Developer notes are now at `_Developer Discussions `_ +Developer notes are now at `Developer Discussions `_ diff --git a/build_alllocal.cmd b/build_alllocal.cmd index 7a357302c4ae..54bc69432fb7 100644 --- a/build_alllocal.cmd +++ b/build_alllocal.cmd @@ -1,13 +1,11 @@ :: This assumes you have installed all the dependencies via conda packages: :: # create a new environment with the required packages -:: conda create -n "matplotlib_build" python=3.4 numpy python-dateutil pyparsing pytz tornado "cycler>=0.10" tk libpng zlib freetype +:: conda create -n "matplotlib_build" python=3.5 numpy python-dateutil pyparsing tornado cycler tk libpng zlib freetype :: activate matplotlib_build :: if you want qt backend, you also have to install pyqt :: conda install pyqt :: # this package is only available in the conda-forge channel :: conda install -c conda-forge msinttypes -:: if you build on py2.7: -:: conda install -c conda-forge backports.functools_lru_cache set TARGET=bdist_wheel IF [%1]==[] ( diff --git a/ci/silence b/ci/silence new file mode 100755 index 000000000000..4889e5d1bd58 --- /dev/null +++ b/ci/silence @@ -0,0 +1,14 @@ +#!/bin/bash + +# Run a command, hiding its standard output and error if its exit +# status is zero. + +stdout=$(mktemp -t stdout) || exit 1 +stderr=$(mktemp -t stderr) || exit 1 +"$@" >$stdout 2>$stderr +code=$? +if [[ $code != 0 ]]; then + cat $stdout + cat $stderr >&2 + exit $code +fi diff --git a/ci/travis/matplotlibDeployKey.enc b/ci/travis/matplotlibDeployKey.enc deleted file mode 100644 index f73fb807cdf5..000000000000 Binary files a/ci/travis/matplotlibDeployKey.enc and /dev/null differ diff --git a/ci/travis/setup.cfg b/ci/travis/setup.cfg deleted file mode 100644 index 61cdc102a0f8..000000000000 --- a/ci/travis/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[test] -local_freetype=True \ No newline at end of file diff --git a/ci/travis/test_script.sh b/ci/travis/test_script.sh deleted file mode 100755 index f0f9e1944433..000000000000 --- a/ci/travis/test_script.sh +++ /dev/null @@ -1,20 +0,0 @@ -#! /bin/bash - -set -ex - -# This script is meant to be called by the "script" step defined in -# .travis.yml. See http://docs.travis-ci.com/ for more details. -# The behavior of the script is controlled by environment variabled defined -# in the .travis.yml in the top level folder of the project. - -# The number of processes is hardcoded, because using too many causes the -# Travis VM to run out of memory (since so many copies of inkscape and -# ghostscript are running at the same time). - -if [[ $DELETE_FONT_CACHE == 1 ]]; then - rm -rf ~/.cache/matplotlib -fi - -echo The following args are passed to pytest $PYTEST_ARGS $RUN_PEP8 - -pytest $PYTEST_ARGS $RUN_PEP8 diff --git a/doc-requirements.txt b/doc-requirements.txt index 446ed9d980da..c0ee2014cf64 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -10,7 +10,6 @@ sphinx>=1.3,!=1.5.0,!=1.6.4,!=1.7.3 colorspacious ipython ipywidgets -mock numpydoc>=0.4 pillow -sphinx-gallery>=0.1.12 +sphinx-gallery>=0.1.13 diff --git a/doc/_static/markers/m00.png b/doc/_static/markers/m00.png new file mode 100644 index 000000000000..59b3ad7fddb0 Binary files /dev/null and b/doc/_static/markers/m00.png differ diff --git a/doc/_static/markers/m01.png b/doc/_static/markers/m01.png new file mode 100644 index 000000000000..e40b5db05243 Binary files /dev/null and b/doc/_static/markers/m01.png differ diff --git a/doc/_static/markers/m02.png b/doc/_static/markers/m02.png new file mode 100644 index 000000000000..1c67ae57345c Binary files /dev/null and b/doc/_static/markers/m02.png differ diff --git a/doc/_static/markers/m03.png b/doc/_static/markers/m03.png new file mode 100644 index 000000000000..552470a2005d Binary files /dev/null and b/doc/_static/markers/m03.png differ diff --git a/doc/_static/markers/m04.png b/doc/_static/markers/m04.png new file mode 100644 index 000000000000..8e2cc09b85b5 Binary files /dev/null and b/doc/_static/markers/m04.png differ diff --git a/doc/_static/markers/m05.png b/doc/_static/markers/m05.png new file mode 100644 index 000000000000..799340390422 Binary files /dev/null and b/doc/_static/markers/m05.png differ diff --git a/doc/_static/markers/m06.png b/doc/_static/markers/m06.png new file mode 100644 index 000000000000..51df3f4b6e2e Binary files /dev/null and b/doc/_static/markers/m06.png differ diff --git a/doc/_static/markers/m07.png b/doc/_static/markers/m07.png new file mode 100644 index 000000000000..cffffd4a25d2 Binary files /dev/null and b/doc/_static/markers/m07.png differ diff --git a/doc/_static/markers/m08.png b/doc/_static/markers/m08.png new file mode 100644 index 000000000000..d8599e7bbd2f Binary files /dev/null and b/doc/_static/markers/m08.png differ diff --git a/doc/_static/markers/m09.png b/doc/_static/markers/m09.png new file mode 100644 index 000000000000..40c754dcd833 Binary files /dev/null and b/doc/_static/markers/m09.png differ diff --git a/doc/_static/markers/m10.png b/doc/_static/markers/m10.png new file mode 100644 index 000000000000..101743620ede Binary files /dev/null and b/doc/_static/markers/m10.png differ diff --git a/doc/_static/markers/m11.png b/doc/_static/markers/m11.png new file mode 100644 index 000000000000..a6a5cbd6935d Binary files /dev/null and b/doc/_static/markers/m11.png differ diff --git a/doc/_static/markers/m12.png b/doc/_static/markers/m12.png new file mode 100644 index 000000000000..68c5ce111d2c Binary files /dev/null and b/doc/_static/markers/m12.png differ diff --git a/doc/_static/markers/m13.png b/doc/_static/markers/m13.png new file mode 100644 index 000000000000..232c8eb686f3 Binary files /dev/null and b/doc/_static/markers/m13.png differ diff --git a/doc/_static/markers/m14.png b/doc/_static/markers/m14.png new file mode 100644 index 000000000000..e49e35635e9b Binary files /dev/null and b/doc/_static/markers/m14.png differ diff --git a/doc/_static/markers/m15.png b/doc/_static/markers/m15.png new file mode 100644 index 000000000000..68bf1ebcebf3 Binary files /dev/null and b/doc/_static/markers/m15.png differ diff --git a/doc/_static/markers/m16.png b/doc/_static/markers/m16.png new file mode 100644 index 000000000000..d3f594b11f4a Binary files /dev/null and b/doc/_static/markers/m16.png differ diff --git a/doc/_static/markers/m17.png b/doc/_static/markers/m17.png new file mode 100644 index 000000000000..2c6c57243b52 Binary files /dev/null and b/doc/_static/markers/m17.png differ diff --git a/doc/_static/markers/m18.png b/doc/_static/markers/m18.png new file mode 100644 index 000000000000..a52d05098b5d Binary files /dev/null and b/doc/_static/markers/m18.png differ diff --git a/doc/_static/markers/m19.png b/doc/_static/markers/m19.png new file mode 100644 index 000000000000..0c40250bd815 Binary files /dev/null and b/doc/_static/markers/m19.png differ diff --git a/doc/_static/markers/m20.png b/doc/_static/markers/m20.png new file mode 100644 index 000000000000..1f75d0297a62 Binary files /dev/null and b/doc/_static/markers/m20.png differ diff --git a/doc/_static/markers/m21.png b/doc/_static/markers/m21.png new file mode 100644 index 000000000000..d3b4dee68ba8 Binary files /dev/null and b/doc/_static/markers/m21.png differ diff --git a/doc/_static/markers/m22.png b/doc/_static/markers/m22.png new file mode 100644 index 000000000000..44e856008fa2 Binary files /dev/null and b/doc/_static/markers/m22.png differ diff --git a/doc/_static/markers/m23.png b/doc/_static/markers/m23.png new file mode 100644 index 000000000000..742b27f0f0f0 Binary files /dev/null and b/doc/_static/markers/m23.png differ diff --git a/doc/_static/markers/m24.png b/doc/_static/markers/m24.png new file mode 100644 index 000000000000..c666d7a8ee1e Binary files /dev/null and b/doc/_static/markers/m24.png differ diff --git a/doc/_static/markers/m25.png b/doc/_static/markers/m25.png new file mode 100644 index 000000000000..d48d2c4659fa Binary files /dev/null and b/doc/_static/markers/m25.png differ diff --git a/doc/_static/markers/m26.png b/doc/_static/markers/m26.png new file mode 100644 index 000000000000..e0b2bbddbd8d Binary files /dev/null and b/doc/_static/markers/m26.png differ diff --git a/doc/_static/markers/m27.png b/doc/_static/markers/m27.png new file mode 100644 index 000000000000..d91c9594ba1a Binary files /dev/null and b/doc/_static/markers/m27.png differ diff --git a/doc/_static/markers/m28.png b/doc/_static/markers/m28.png new file mode 100644 index 000000000000..58ef370d5833 Binary files /dev/null and b/doc/_static/markers/m28.png differ diff --git a/doc/_static/markers/m29.png b/doc/_static/markers/m29.png new file mode 100644 index 000000000000..48b326482ace Binary files /dev/null and b/doc/_static/markers/m29.png differ diff --git a/doc/_static/markers/m30.png b/doc/_static/markers/m30.png new file mode 100644 index 000000000000..bc9b72859ebb Binary files /dev/null and b/doc/_static/markers/m30.png differ diff --git a/doc/_static/markers/m31.png b/doc/_static/markers/m31.png new file mode 100644 index 000000000000..f4aedabe4d29 Binary files /dev/null and b/doc/_static/markers/m31.png differ diff --git a/doc/_static/markers/m32.png b/doc/_static/markers/m32.png new file mode 100644 index 000000000000..e4c8d06605e1 Binary files /dev/null and b/doc/_static/markers/m32.png differ diff --git a/doc/_static/markers/m33.png b/doc/_static/markers/m33.png new file mode 100644 index 000000000000..893ea6a5a8d3 Binary files /dev/null and b/doc/_static/markers/m33.png differ diff --git a/doc/_static/markers/m34.png b/doc/_static/markers/m34.png new file mode 100644 index 000000000000..fd66b50b7dc3 Binary files /dev/null and b/doc/_static/markers/m34.png differ diff --git a/doc/_static/markers/m35.png b/doc/_static/markers/m35.png new file mode 100644 index 000000000000..365d652499c6 Binary files /dev/null and b/doc/_static/markers/m35.png differ diff --git a/doc/_static/markers/m36.png b/doc/_static/markers/m36.png new file mode 100644 index 000000000000..5b6ff5e953e7 Binary files /dev/null and b/doc/_static/markers/m36.png differ diff --git a/doc/_static/markers/m37.png b/doc/_static/markers/m37.png new file mode 100644 index 000000000000..7afebed4557d Binary files /dev/null and b/doc/_static/markers/m37.png differ diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 2c1977b63e0a..1cfb7bec918b 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -23,6 +23,10 @@ a { text-decoration: none; } +a:hover { + color: #2491CF; +} + div.highlight-python a { color: #CA7900; } @@ -35,10 +39,6 @@ strong { font-weight: strong; } -a:hover { - color: #2491CF; -} - pre { font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 0.90em; @@ -142,10 +142,6 @@ dt:target, background-color: #ffffee; } -dl.method, dl.attribute { - border-top: 1px solid #aaa; -} - dl.glossary dt { font-weight: bold; font-size: 1.1em; @@ -249,7 +245,8 @@ div.sphinxsidebar h3 a { div.sphinxsidebar ul { padding-left: 1.5em; - margin-top: 7px; + margin-top: 10px; + margin-bottom: 10px; list-style: none; padding: 0; line-height: 130%; @@ -257,7 +254,9 @@ div.sphinxsidebar ul { div.sphinxsidebar ul ul { list-style: square; - margin-left: 20px; + margin-top: 6px; + margin-bottom: 6px; + margin-left: 16px; } div.sphinxsidebar #searchbox input { @@ -294,10 +293,6 @@ p { margin: 0.8em 0 0.8em 0; } -p.rubric { - font-weight: bold; -} - h1 { margin: 0.5em 0em; padding-top: 0.5em; @@ -395,101 +390,126 @@ div.sphinxsidebar ul.toc ul li { padding: 0; } -div.admonition, div.warning { - font-size: 0.9em; +/* admonitions */ + +div.admonition, div.deprecated { + margin: 10px 0px; + padding: 0.7em 1.4em; + border-left: 5px solid; + } + +div.note { + background-color: #eee; + border-color: #ccc; } -div.warning { - color: #b94a48; +div.seealso { + background-color: #EAF1F7; + border-color: #8EADCC; + color: #3F5E7F; + } + +div.warning, div.important { background-color: #F3E5E5; - border: 1px solid #eed3d7; + border-color: #CC8E8E; + color: #7F1919; } div.deprecated { - color: #606060; background-color: #f0f0f0; - border: 1px solid #404040; + border-color: #404040; + color: #606060; +} + +span.versionmodified { + font-style: italic; } div.deprecated span.versionmodified { - color: #606060; font-weight: bold; + font-style: normal; } div.green, div.hint { - color: #468847; - background-color: #dff0d8; - border: 1px solid #d6e9c6; + background-color: #E1F2DA; + border-color: #A1CC8E; + color: #3F7F3F; } -div.admonition p, div.warning p, div.deprecated p { - margin: 0.5em 1em 0.5em 1em; +div.admonition p.admonition-title { + font-size: 1.2em; + font-weight: bold; +} + +div.admonition p, div.deprecated p { + margin: 0.6em 0; padding: 0; } -div.admonition pre, div.warning pre { - margin: 0.4em 1em 0.4em 1em; +div.admonition pre { + margin: 0.6em 0; } -div.admonition p.admonition-title + p { - display: inline; +div.admonition ul, div.admonition ol { + margin: 0.1em 0.5em 0.5em 2em; + padding: 0; } +div.topic { + background-color: #f4f4f4; + border: 2px solid #ccc; + border-left: 0px; + border-right: 0px; + margin: 10px 0px; + padding: 1em 1.4em; +} -div.admonition p.admonition-title, -div.warning p.admonition-title { - margin: 0; +p.topic-title { + font-size: 1.2em; font-weight: bold; - font-size: 14px; } -div.admonition, div.deprecated { - margin-bottom: 10px; - margin-top: 10px; - padding: 7px; - border-radius: 4px; - -moz-border-radius: 4px; - } - -div.note { - background-color: #eee; - border: 1px solid #ccc; +.contents ul { + list-style-type: none; + padding-left: 2em; } -div.topic { - background-color: #eee; - border: 1px solid #CCC; - margin: 10px 0px; - padding: 7px 7px 0px; - border-radius: 4px; - -moz-border-radius: 4px; +/* first level */ +.contents > ul { + padding-left: 0; } -p.topic-title { - font-size: 1.1em; - font-weight: bold; +.multicol-toc > ul { + column-width: 250px; + column-gap: 60px; + -webkit-column-width: 250px; + -moz-column-width: 250px; + column-rule: 1px solid #ccc; } -div.seealso { - background-color: #FFFBE8; - border: 1px solid #fbeed5; - color: #AF8A4B; - } +.multicol-toc > li { + /* break inside is not yet broadly supported, but we just try */ + break-inside: avoid-column; + -moz-break-inside: avoid-column; + -webkit-break-inside: avoid-column; +} -div.warning { - border: 1px solid #940000; +.contents > ul > li { + padding-top: 0.7em; } -div.warning p.admonition-title { - border-bottom-color: #940000; +.contents ul > li::before { + content: "\25FE"; + color: #bbb; + padding-right: .3em; } -div.admonition ul, div.admonition ol, -div.warning ul, div.warning ol { - margin: 0.1em 0.5em 0.5em 3em; - padding: 0; +.contents > ul > li > a { + font-size: 1.0em; } + + div.versioninfo { margin: 1em 0 0 0; border: 1px solid #ccc; @@ -499,7 +519,6 @@ div.versioninfo { font-size: 0.9em; } - a.headerlink { color: #c60f0f!important; font-size: 1em; @@ -604,12 +623,55 @@ ul.keywordmatches li.goodmatch a { table.docutils { border-spacing: 2px; border-collapse: collapse; - border-top-width: 1px; - border-right-width: 0px; - border-bottom-width: 1px; - border-left-width: 0px; + border: 0px; +} + +table.docutils th { + border-width: 1px 0px; + border-color: #888; + background-color: #f0f0f0; + width: 100px; } +table.docutils td { + border-width: 1px 0px; + border-color: #ccc; +} + +table.docutils tr:last-of-type td { + border-bottom-color: #888; +} + +table.docutils tr:first-of-type td { + border-top-color: #888; +} + +/* Section titles within classes */ +dl.class p.rubric { + font-size: 16px; +} + +/* Attribute tables */ +dl.class p.rubric + table.docutils { + margin-left: 0px; + margin-right: 0px; + margin-bottom: 1.5em; + border-top: 1px solid #888; + border-bottom: 1px solid #888; +} + +dl.class p.rubric + table.docutils td { + padding-left: 0px; + border-color: #ccc; +} + +dl.class p.rubric + table.docutils td:first-of-type > strong { + font-family: monospace; + font-size: 14px; + font-weight: normal; +} + + /* module summary table */ .longtable.docutils { font-size: 12px; @@ -626,17 +688,29 @@ table.docutils { /* tables inside parameter descriptions */ td.field-body table.property-table { width: 100%; + border-spacing: 2px; + border-collapse: collapse; + border: 0px; } td.field-body table.property-table th { padding: 2px 10px; - border: 0; + border-width: 1px 0px; + border-color: #888; + background-color: #f0f0f0; } td.field-body table.property-table td { padding: 2px 10px; + border-width: 1px 0px; + border-color: #ccc; +} + +td.field-body table.property-table tr:last-of-type td { + border-bottom-color: #888; } + /* function and class description */ .descclassname { color: #aaa; @@ -647,85 +721,100 @@ td.field-body table.property-table td { font-family: monospace; } +/*** function and class description ***/ +/* top-level definitions */ +dl.class, dl.function { + border-top: 1px solid #888; + padding-top: 0px; + margin-top: 20px; +} -table.docutils th { - padding: 1px 8px 1px 5px; - background-color: #eee; - width: 100px; +dl.method, dl.classmethod, dl.staticmethod, dl.attribute { + border-top: 1px solid #ccc; + padding-top: 0px; } -table.docutils td { - border-width: 1px 0 1px 0; + +dl.class > dt, dl.classmethod > dt, dl.method > dt, dl.function > dt, +dl.attribute > dt, dl.staticmethod > dt { + background-color: #eff3f4; + padding-left: 6px; + padding-right: 6px; + padding-top: 2px; + padding-bottom: 1px; } +em.property { + margin-right: 4px; +} -dl.class em, dl.function em, dl.class big, dl.function big { - font-weight: normal; - font-family: monospace; +.sig-paren { + font-size: 14px; } -dl.class dd, dl.function dd { - padding: 10px; +.sig-paren ~ em { + font-weight: normal; + font-family: monospace; + font-size: 14px; } -/* function and class description */ -dl.function, dl.method, dl.attribute { - border-top: 1px solid #ccc; - padding-top: 6px; +dl.class big, dl.function big { + font-weight: normal; + font-family: monospace; } -dl.function { - border-top: 1px solid #888; - margin-top: 15px; +dl.class dd, dl.function dd { + padding: 10px; } -dl.class { - padding-top: 6px; - margin-top: 15px; +dl.class > dd { + padding: 10px; + padding-left: 35px; + margin-left: 0px; + border-left: 5px solid #f8f8f8; } .descclassname { color: #aaa; font-weight: normal; font-family: monospace; + font-size: 14px; } + .descname { font-family: monospace; + font-size: 14px; } +/* parameter section table */ table.docutils.field-list { width: 100%; } - -.docutils.field-list th { +.docutils.field-list th.field-name { background-color: #eee; padding: 10px; text-align: left; vertical-align: top; width: 125px; } -.docutils.field-list td { +.docutils.field-list td.field-body { padding: 10px 10px 10px 20px; text-align: left; vertical-align: top; } -.docutils.field-list td blockquote p { +.docutils.field-list td.field-body blockquote p { font-size: 13px; line-height: 18px; } -.docutils.field-list td blockquote p ul li{ +.docutils.field-list td.field-body blockquote p ul li{ font-size: 13px; } + p.rubric { font-weight: bold; font-size: 19px; margin: 15px 0 10px 0; } -p.admonition-title { - font-weight: bold; - text-decoration: underline; -} - #matplotlib-examples ul li{ font-size: large; @@ -772,7 +861,6 @@ figcaption { } - /* "Go to released version" message. */ #unreleased-message { background: #d62728; @@ -781,7 +869,7 @@ figcaption { font-weight: bold; left: 0; min-height: 3em; - padding: 0.5em; + padding: 0.7em; position: fixed; top: 0; width: 100%; @@ -952,6 +1040,10 @@ div#gallery.section, div#tutorials.section { overflow: hidden; } +.sphx-glr-multi-img{ + max-width: 99% !important; +} + .sphx-glr-thumbcontainer { border: solid #d6d6d6 1px !important; text-align: center !important; @@ -972,6 +1064,10 @@ p.sphx-glr-signature { display: none !important; } +div.sphx-glr-download-link-note { + display: none !important; +} + .sphx-glr-thumbcontainer a.internal { font-weight: 400; } diff --git a/doc/_templates/autosummary.rst b/doc/_templates/autosummary.rst index 8991f3c9ebc4..7d8860edd311 100644 --- a/doc/_templates/autosummary.rst +++ b/doc/_templates/autosummary.rst @@ -3,14 +3,22 @@ .. currentmodule:: {{ module }} + +{% if objtype in ['class'] %} +.. auto{{ objtype }}:: {{ objname }} + :show-inheritance: + +{% else %} .. auto{{ objtype }}:: {{ objname }} +{% endif %} + {% if objtype in ['class', 'method', 'function'] %} {% if objname in ['AxesGrid', 'Scalable', 'HostAxes', 'FloatingAxes', 'ParasiteAxesAuxTrans', 'ParasiteAxes'] %} .. Filter out the above aliases to other classes, as sphinx gallery creates no example file for those (sphinx-gallery/sphinx-gallery#365) - + {% else %} .. include:: {{module}}.{{objname}}.examples diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index 77658ec190c9..e95ec1fdf110 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -40,7 +40,7 @@

{{ _('Navigation') }}

  • home
  • examples
  • tutorials
  • -
  • pyplot
  • +
  • API
  • docs »
  • {%- for parent in parents %} diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index 3485672eb968..02c4f3e64999 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -37,6 +37,9 @@ To save an animation to disk use `Animation.save` or `Animation.to_html5_video` See :ref:`ani_writer_classes` below for details about what movie formats are supported. + +.. _func-animation: + ``FuncAnimation`` ----------------- @@ -58,8 +61,8 @@ general gist is to take an existing bit map (in our case a mostly rasterized figure) and then 'blit' one more artist on top. Thus, by managing a saved 'clean' bitmap, we can only re-draw the few artists that are changing at each frame and possibly save significant amounts of -time. When using blitting (by passing ``blit=True``) the core loop of -`FuncAnimation` gets a bit more complicated :: +time. When we use blitting (by passing ``blit=True``), the core loop of +`FuncAnimation` gets a bit more complicated:: ax = fig.gca() diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index fa9ccddf786d..7473c1e79d66 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -3,167 +3,106 @@ API Changes ============= -Log of changes to Matplotlib that affect the outward-facing API. If -updating Matplotlib breaks your scripts, this list may help you figure -out what caused the breakage and how to fix it by updating your code. +A log of changes to the most recent version of Matplotlib that affect the +outward-facing API. If updating Matplotlib breaks your scripts, this list may +help you figure out what caused the breakage and how to fix it by updating +your code. For API changes in older versions see :doc:`api_changes_old`. -For new features that were added to Matplotlib, please see -:ref:`whats-new`. - -.. for a release comment out the toctree below +For new features that were added to Matplotlib, see :ref:`whats-new`. +This pages lists API changes for the most recent version of Matplotlib. .. toctree:: - :glob: :maxdepth: 1 - next_api_changes/* - - -API Changes in 2.2.0 -==================== - - - -New dependency --------------- - -`kiwisolver `__ is now a required -dependency to support the new constrained_layout, see -:doc:`/tutorials/intermediate/constrainedlayout_guide` for -more details. - - -Deprecations ------------- - -Classes, functions, and methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The unused and untested ``Artist.onRemove`` and ``Artist.hitlist`` methods have -been deprecated. - -The now unused ``mlab.less_simple_linear_interpolation`` function is -deprecated. - -The unused ``ContourLabeler.get_real_label_width`` method is deprecated. - -The unused ``FigureManagerBase.show_popup`` method is deprecated. This -introduced in e945059b327d42a99938b939a1be867fa023e7ba in 2005 but never built -out into any of the backends. + api_changes_old -:class:`backend_tkagg.AxisMenu` is deprecated, as it has become -unused since the removal of "classic" toolbars. +.. + .. note:: -Changed function signatures -~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The list below is a table of contents of individual files from the 'next_api_changes' folder. + When a release is made -kwarg ``fig`` to `.GridSpec.get_subplot_params` is -deprecated, use ``figure`` instead. + - The full text list below should be moved into its own file in 'prev_api_changes' + - All the files in 'next_api_changes' should be moved to the bottom of this page + - This note, and the toctree below should be commented out -Using `.pyplot.axes` with an `~matplotlib.axes.Axes` as argument is deprecated. This sets -the current axes, i.e. it has the same effect as `.pyplot.sca`. For clarity -``plt.sca(ax)`` should be preferred over ``plt.axes(ax)``. + .. toctree:: + :glob: + :maxdepth: 1 -Using strings instead of booleans to control grid and tick visibility -is deprecated. Using ``"on"``, ``"off"``, ``"true"``, or ``"false"`` -to control grid and tick visibility has been deprecated. Instead, use -normal booleans (``True``/``False``) or boolean-likes. In the future, -all non-empty strings may be interpreted as ``True``. + next_api_changes/* -When given 2D inputs with non-matching numbers of columns, `~.pyplot.plot` -currently cycles through the columns of the narrower input, until all the -columns of the wider input have been plotted. This behavior is deprecated; in -the future, only broadcasting (1 column to *n* columns) will be performed. - - -rcparams -~~~~~~~~ - -The :rc:`backend.qt4` and :rc:`backend.qt5` rcParams were deprecated -in version 2.2. In order to force the use of a specific Qt binding, -either import that binding first, or set the ``QT_API`` environment -variable. - -Deprecation of the ``nbagg.transparent`` rcParam. To control -transparency of figure patches in the nbagg (or any other) backend, -directly set ``figure.patch.facecolor``, or the ``figure.facecolor`` -rcParam. - -Deprecated `Axis.unit_data` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use `Axis.units` (which has long existed) instead. +API Changes for 3.0.0 +===================== -Removals --------- +Drop support for python 2 +------------------------- -Function Signatures -~~~~~~~~~~~~~~~~~~~ +Matplotlib 3 only supports python 3.5 and higher. -Contouring no longer supports ``legacy`` corner masking. The -deprecated ``ContourSet.vmin`` and ``ContourSet.vmax`` properties have -been removed. -Passing ``None`` instead of ``"none"`` as format to `~.Axes.errorbar` is no -longer supported. +Changes to backend loading +-------------------------- -The ``bgcolor`` keyword argument to ``Axes`` has been removed. +Failure to load backend modules (``macosx`` on non-framework builds and +``gtk3`` when running headless) now raises `ImportError` (instead of +`RuntimeError` and `TypeError`, respectively). -Modules, methods, and functions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Third-party backends that integrate with an interactive framework are now +encouraged to define the ``required_interactive_framework`` global value to one +of the following values: "qt5", "qt4", "gtk3", "wx", "tk", or "macosx". This +information will be used to determine whether it is possible to switch from a +backend to another (specifically, whether they use the same interactive +framework). -The ``matplotlib.finance``, ``mpl_toolkits.exceltools`` and -``mpl_toolkits.gtktools`` modules have been removed. ``matplotlib.finance`` -remains available at https://github.com/matplotlib/mpl_finance. -The ``mpl_toolkits.mplot3d.art3d.iscolor`` function has been removed. -The ``Axes.get_axis_bgcolor``, ``Axes.set_axis_bgcolor``, -``Bbox.update_from_data``, ``Bbox.update_datalim_numerix``, -``MaxNLocator.bin_boundaries`` methods have been removed. +`.Axes.hist2d` now uses `~.Axes.pcolormesh` instead of `~.Axes.pcolorfast` +-------------------------------------------------------------------------- -``mencoder`` can no longer be used to encode animations. +`.Axes.hist2d` now uses `~.Axes.pcolormesh` instead of `~.Axes.pcolorfast`, +which will improve the handling of log-axes. Note that the +returned *image* now is of type `~.matplotlib.collections.QuadMesh` +instead of `~.matplotlib.image.AxesImage`. -The unused `FONT_SCALE` and `fontd` attributes of the `RendererSVG` -class have been removed. -color maps -~~~~~~~~~~ +`.matplotlib.Axes.get_tightbbox` now includes all artists +--------------------------------------------------------- -The ``spectral`` colormap has been removed. The ``Vega*`` colormaps, which -were aliases for the ``tab*`` colormaps, have been removed. +Layout tools like `.Figure.tight_layout`, ``constrained_layout``, +and ``fig.savefig('fname.png', bbox_inches="tight")`` use +`.matplotlib.Axes.get_tightbbox` to determine the bounds of each axes on +a figure and adjust spacing between axes. +In Matplotlib 2.2 ``get_tightbbox`` started to include legends made on the +axes, but still excluded some other artists, like text that may overspill an +axes. For Matplotlib 3.0, *all* artists are now included in the bounding box. -rcparams -~~~~~~~~ +This new default may be overridden in either of two ways: -The following deprecated rcParams have been removed: +1. Make the artist to be excluded a child of the figure, not the axes. E.g., + call ``fig.legend()`` instead of ``ax.legend()`` (perhaps using + `~.matplotlib.Axes.get_legend_handles_labels` to gather handles and labels + from the parent axes). +2. If the artist is a child of the axes, set the artist property + ``artist.set_in_layout(False)``. -- ``axes.color_cycle`` (see ``axes.prop_cycle``), -- ``legend.isaxes``, -- ``svg.embed_char_paths`` (see ``svg.fonttype``), -- ``text.fontstyle``, ``text.fontangle``, ``text.fontvariant``, - ``text.fontweight``, ``text.fontsize`` (renamed to ``text.style``, etc.), -- ``tick.size`` (renamed to ``tick.major.size``). +`Text.set_text` with string argument ``None`` sets string to empty +------------------------------------------------------------------ +`Text.set_text` when passed a string value of ``None`` would set the +string to ``"None"``, so subsequent calls to `Text.get_text` would return +the ambiguous ``"None"`` string. -Only accept string-like for Categorical input ---------------------------------------------- +This change sets text objects passed ``None`` to have empty strings, so that +`Text.get_text` returns an empty string. -Do not accept mixed string / float / int input, only -strings are valid categoricals. -Removal of unused imports -------------------------- -Many unused imports were removed from the codebase. As a result, -trying to import certain classes or functions from the "wrong" module -(e.g. `~.Figure` from :mod:`matplotlib.backends.backend_agg` instead of -:mod:`matplotlib.figure`) will now raise an `ImportError`. ``Axes3D.get_xlim``, ``get_ylim`` and ``get_zlim`` now return a tuple @@ -173,3640 +112,479 @@ They previously returned an array. Returning a tuple is consistent with the behavior for 2D axes. -Exception type changes ----------------------- - -If `MovieWriterRegistry` can't find the requested `MovieWriter`, a -more helpful `RuntimeError` message is now raised instead of the -previously raised `KeyError`. - -`~.tight_layout.auto_adjust_subplotpars` now raises `ValueError` -instead of `RuntimeError` when sizes of input lists don't match - - -`Figure.set_figwidth` and `Figure.set_figheight` default forward to True ------------------------------------------------------------------------- - -`matplotlib.Figure.set_figwidth` and `matplotlib.Figure.set_figheight` -had the kwarg `forward=False` -by default, but `Figure.set_size_inches` now defaults to `forward=True`. -This makes these functions conistent. - - -Do not truncate svg sizes to nearest point ------------------------------------------- - -There is no reason to size the SVG out put in integer points, change -to out putting floats for the *height*, *width*, and *viewBox* attributes -of the *svg* element. - - -Fontsizes less than 1 pt are clipped to be 1 pt. ------------------------------------------------- - -FreeType doesn't allow fonts to get smaller than 1 pt, so all Agg -backends were silently rounding up to 1 pt. PDF (other vector -backends?) were letting us write fonts that were less than 1 pt, but -they could not be placed properly because position information comes from -FreeType. This change makes it so no backends can use fonts smaller than -1 pt, consistent with FreeType and ensuring more consistent results across -backends. - - - -Changes to Qt backend class MRO -------------------------------- - -To support both Agg and cairo rendering for Qt backends all of the -non-Agg specific code previously in -:class:`.backend_qt5agg.FigureCanvasQTAggBase` has been moved to -:class:`.backend_qt5.FigureCanvasQT` so it can be shared with the cairo -implementation. The :meth:`.FigureCanvasQTAggBase.paintEvent`, -:meth:`.FigureCanvasQTAggBase.blit`, and -:meth:`.FigureCanvasQTAggBase.print_figure` methods have moved to -:meth:`.FigureCanvasQTAgg.paintEvent`, :meth:`.FigureCanvasQTAgg.blit`, and -:meth:`.FigureCanvasQTAgg.print_figure`. The first two methods assume that -the instance is also a :class:`QWidget` so to use -:class:`FigureCanvasQTAggBase` it was required to multiple inherit -from a :class:`QWidget` sub-class. - -Having moved all of its methods either up or down the class hierarchy -:class:`FigureCanvasQTAggBase` has been deprecated. To do this with -out warning and to preserve as much API as possible, -:class:`.backend_qt5.FigureCanvasQTAggBase` now inherits from -:class:`.backend_qt5.FigureCanvasQTAgg`. - -The MRO for :class:`FigureCanvasQTAgg` and -:class:`FigureCanvasQTAggBase` used to be :: - - - [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, - matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, - matplotlib.backends.backend_agg.FigureCanvasAgg, - matplotlib.backends.backend_qt5.FigureCanvasQT, - PyQt5.QtWidgets.QWidget, - PyQt5.QtCore.QObject, - sip.wrapper, - PyQt5.QtGui.QPaintDevice, - sip.simplewrapper, - matplotlib.backend_bases.FigureCanvasBase, - object] - -and :: - - - [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, - matplotlib.backends.backend_agg.FigureCanvasAgg, - matplotlib.backend_bases.FigureCanvasBase, - object] - - -respectively. They are now :: - - [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, - matplotlib.backends.backend_agg.FigureCanvasAgg, - matplotlib.backends.backend_qt5.FigureCanvasQT, - PyQt5.QtWidgets.QWidget, - PyQt5.QtCore.QObject, - sip.wrapper, - PyQt5.QtGui.QPaintDevice, - sip.simplewrapper, - matplotlib.backend_bases.FigureCanvasBase, - object] - -and :: - - [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, - matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, - matplotlib.backends.backend_agg.FigureCanvasAgg, - matplotlib.backends.backend_qt5.FigureCanvasQT, - PyQt5.QtWidgets.QWidget, - PyQt5.QtCore.QObject, - sip.wrapper, - PyQt5.QtGui.QPaintDevice, - sip.simplewrapper, - matplotlib.backend_bases.FigureCanvasBase, - object] - - - - -`Axes.imshow` clips RGB values to the valid range -------------------------------------------------- - -When `Axes.imshow` is passed an RGB or RGBA value with out-of-range -values, it now logs a warning and clips them to the valid range. -The old behaviour, wrapping back in to the range, often hid outliers -and made interpreting RGB images unreliable. - - -GTKAgg and GTKCairo backends deprecated ---------------------------------------- - -The GTKAgg and GTKCairo backends have been deprecated. These obsolete backends -allow figures to be rendered via the GTK+ 2 toolkit. They are untested, known -to be broken, will not work with Python 3, and their use has been discouraged -for some time. Instead, use the `GTK3Agg` and `GTK3Cairo` backends for -rendering to GTK+ 3 windows. +``font_manager.list_fonts`` now follows the platform's casefolding semantics +---------------------------------------------------------------------------- -API Changes in 2.1.2 -==================== +i.e., it behaves case-insensitively on Windows only. -`Figure.legend` no longer checks for repeated lines to ignore -------------------------------------------------------------- -`matplotlib.Figure.legend` used to check if a line had the -same label as an existing legend entry. If it also had the same line color -or marker color legend didn't add a new entry for that line. However, the -list of conditions was incomplete, didn't handle RGB tuples, -didn't handle linewidths or linestyles etc. - -This logic did not exist in `Axes.legend`. It was included (erroneously) -in Matplotlib 2.1.1 when the legend argument parsing was unified -[#9324](https://github.com/matplotlib/matplotlib/pull/9324). This change -removes that check in `Axes.legend` again to restore the old behavior. - -This logic has also been dropped from `.Figure.legend`, where it -was previously undocumented. Repeated -lines with the same label will now each have an entry in the legend. If -you do not want the duplicate entries, don't add a label to the line, or -prepend the label with an underscore. - -API Changes in 2.1.1 -==================== - -Default behavior of log scales reverted to clip <= 0 values ------------------------------------------------------------ - -The change it 2.1.0 to mask in logscale by default had more disruptive -changes than anticipated and has been reverted, however the clipping is now -done in a way that fixes the issues that motivated changing the default behavior -to ``'mask'``. - -As a side effect of this change, error bars which go negative now work as expected -on log scales. - -API Changes in 2.1.0 -==================== - - -Default behavior of log scales changed to mask <= 0 values ----------------------------------------------------------- - -Calling `matplotlib.axes.Axes.set_xscale` or `matplotlib.axes.Axes.set_yscale` -now uses 'mask' as the default method to handle invalid values (as opposed to -'clip'). This means that any values <= 0 on a log scale will not be shown. - -Previously they were clipped to a very small number and shown. - - -:meth:`matplotlib.cbook.CallbackRegistry.process` suppresses exceptions by default +``bar`` / ``barh`` no longer accepts ``left`` / ``bottom`` as first named argument ---------------------------------------------------------------------------------- -Matplotlib uses instances of :obj:`~matplotlib.cbook.CallbackRegistry` -as a bridge between user input event from the GUI and user callbacks. -Previously, any exceptions raised in a user call back would bubble out -of of the ``process`` method, which is typically in the GUI event -loop. Most GUI frameworks simple print the traceback to the screen -and continue as there is not always a clear method of getting the -exception back to the user. However PyQt5 now exits the process when -it receives an un-handled python exception in the event loop. Thus, -:meth:`~matplotlib.cbook.CallbackRegistry.process` now suppresses and -prints tracebacks to stderr by default. - -What :meth:`~matplotlib.cbook.CallbackRegistry.process` does with exceptions -is now user configurable via the ``exception_handler`` attribute and kwarg. To -restore the previous behavior pass ``None`` :: - - cb = CallbackRegistry(exception_handler=None) - +These arguments were renamed in 2.0 to ``x`` / ``y`` following the change of the +default alignment from ``edge`` to ``center``. -A function which take and ``Exception`` as its only argument may also be passed :: - - def maybe_reraise(exc): - if isinstance(exc, RuntimeError): - pass - else: - raise exc - - cb = CallbackRegistry(exception_handler=maybe_reraise) - - - -Improved toggling of the axes grids ------------------------------------ - -The `g` key binding now switches the states of the `x` and `y` grids -independently (by cycling through all four on/off combinations). - -The new `G` key binding switches the states of the minor grids. - -Both bindings are disabled if only a subset of the grid lines (in either -direction) is visible, to avoid making irreversible changes to the figure. - - -Ticklabels are turned off instead of being invisible ----------------------------------------------------- - -Internally, the `Tick`'s :func:`~matplotlib.axis.Tick.label1On` attribute -is now used to hide tick labels instead of setting the visibility on the tick -label objects. -This improves overall performance and fixes some issues. -As a consequence, in case those labels ought to be shown, -:func:`~matplotlib.axes.Axes.tick_params` -needs to be used, e.g. - -:: - - ax.tick_params(labelbottom=True) - - -Removal of warning on empty legends ------------------------------------ - -``plt.legend`` used to issue a warning when no labeled artist could be -found. This warning has been removed. - - -More accurate legend autopositioning ------------------------------------- - -Automatic positioning of legends now prefers using the area surrounded -by a `Line2D` rather than placing the legend over the line itself. - - -Cleanup of stock sample data ----------------------------- - -The sample data of stocks has been cleaned up to remove redundancies and -increase portability. The ``AAPL.dat.gz``, ``INTC.dat.gz`` and ``aapl.csv`` -files have been removed entirely and will also no longer be available from -`matplotlib.cbook.get_sample_data`. If a CSV file is required, we suggest using -the ``msft.csv`` that continues to be shipped in the sample data. If a NumPy -binary file is acceptable, we suggest using one of the following two new files. -The ``aapl.npy.gz`` and ``goog.npy`` files have been replaced by ``aapl.npz`` -and ``goog.npz``, wherein the first column's type has changed from -`datetime.date` to `np.datetime64` for better portability across Python -versions. Note that Matplotlib does not fully support `np.datetime64` as yet. - - -Updated qhull to 2015.2 ------------------------ - -The version of qhull shipped with Matplotlib, which is used for -Delaunay triangulation, has been updated from version 2012.1 to -2015.2. - -Improved Delaunay triangulations with large offsets ---------------------------------------------------- - -Delaunay triangulations now deal with large x,y offsets in a better -way. This can cause minor changes to any triangulations calculated -using Matplotlib, i.e. any use of `matplotlib.tri.Triangulation` that -requests that a Delaunay triangulation is calculated, which includes -`matplotlib.pyplot.tricontour`, `matplotlib.pyplot.tricontourf`, -`matplotlib.pyplot.tripcolor`, `matplotlib.pyplot.triplot`, -`matplotlib.mlab.griddata` and -`mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`. +Different exception types for undocumented options +-------------------------------------------------- +- Passing ``style='comma'`` to :meth:`~matplotlib.axes.Axes.ticklabel_format` + was never supported. It now raises ``ValueError`` like all other + unsupported styles, rather than ``NotImplementedError``. -Use ``backports.functools_lru_cache`` instead of ``functools32`` ----------------------------------------------------------------- +- Passing the undocumented ``xmin`` or ``xmax`` arguments to + :meth:`~matplotlib.axes.Axes.set_xlim` would silently override the ``left`` + and ``right`` arguments. :meth:`~matplotlib.axes.Axes.set_ylim` and the + 3D equivalents (e.g. :meth:`~mpl_toolkits.axes.Axes3D.set_zlim3d`) had a + corresponding problem. + The ``_min`` and ``_max`` arguments are now deprecated, and a ``TypeError`` + will be raised if they would override the earlier limit arguments. -It's better maintained and more widely used (by pylint, jaraco, etc). +Improved call signature for ``Axes.margins`` +-------------------------------------------- +:meth:`matplotlib.axes.Axes.margins` and :meth:`mpl_toolkits.mplot3d.Axes3D.margins` +no longer accept arbitrary keywords. ``TypeError`` will therefore be raised +if unknown kwargs are passed; previously they would be silently ignored. -``cbook.is_numlike`` only performs an instance check ----------------------------------------------------- +If too many positional arguments are passed, ``TypeError`` will be raised +instead of ``ValueError``, for consistency with other call-signature violations. -:func:`~matplotlib.cbook.is_numlike` now only checks that its argument -is an instance of ``(numbers.Number, np.Number)``. In particular, -this means that arrays are now not num-like. +``Axes3D.margins`` now raises ``TypeError`` instead of emitting a deprecation +warning if only two positional arguments are passed. To supply only ``x`` and +``y`` margins, use keyword arguments. -Elliptical arcs now drawn between correct angles +Explicit arguments instead of \*args, \*\*kwargs ------------------------------------------------ -The `matplotlib.patches.Arc` patch is now correctly drawn between the given -angles. - -Previously a circular arc was drawn and then stretched into an ellipse, -so the resulting arc did not lie between *theta1* and *theta2*. - - - -``-d$backend`` no longer sets the backend ------------------------------------------ - -It is no longer possible to set the backend by passing ``-d$backend`` -at the command line. Use the ``MPLBACKEND`` environment variable -instead. - - -Path.intersects_bbox always treats the bounding box as filled -------------------------------------------------------------- - -Previously, when ``Path.intersects_bbox`` was called with ``filled`` set to -``False``, it would treat both the path and the bounding box as unfilled. This -behavior was not well documented and it is usually not the desired behavior, -since bounding boxes are used to represent more complex shapes located inside -the bounding box. This behavior has now been changed: when ``filled`` is -``False``, the path will be treated as unfilled, but the bounding box is still -treated as filled. The old behavior was arguably an implementation bug. - -When ``Path.intersects_bbox`` is called with ``filled`` set to ``True`` -(the default value), there is no change in behavior. For those rare cases where -``Path.intersects_bbox`` was called with ``filled`` set to ``False`` and where -the old behavior is actually desired, the suggested workaround is to call -``Path.intersects_path`` with a rectangle as the path:: - - from matplotlib.path import Path - from matplotlib.transforms import Bbox, BboxTransformTo - rect = Path.unit_rectangle().transformed(BboxTransformTo(bbox)) - result = path.intersects_path(rect, filled=False) - - - - -WX no longer calls generates ``IdleEvent`` events or calls ``idle_event`` -------------------------------------------------------------------------- - -Removed unused private method ``_onIdle`` from ``FigureCanvasWx``. - -The ``IdleEvent`` class and ``FigureCanvasBase.idle_event`` method -will be removed in 2.2 - - - -Correct scaling of :func:`magnitude_spectrum()` ------------------------------------------------ - -The functions :func:`matplotlib.mlab.magnitude_spectrum()` and :func:`matplotlib.pyplot.magnitude_spectrum()` implicitly assumed the sum -of windowing function values to be one. In Matplotlib and Numpy the -standard windowing functions are scaled to have maximum value of one, -which usually results in a sum of the order of n/2 for a n-point -signal. Thus the amplitude scaling :func:`magnitude_spectrum()` was -off by that amount when using standard windowing functions (`Bug 8417 -`_ ). Now the -behavior is consistent with :func:`matplotlib.pyplot.psd()` and -:func:`scipy.signal.welch()`. The following example demonstrates the -new and old scaling:: - - import matplotlib.pyplot as plt - import numpy as np - - tau, n = 10, 1024 # 10 second signal with 1024 points - T = tau/n # sampling interval - t = np.arange(n)*T - - a = 4 # amplitude - x = a*np.sin(40*np.pi*t) # 20 Hz sine with amplitude a - - # New correct behavior: Amplitude at 20 Hz is a/2 - plt.magnitude_spectrum(x, Fs=1/T, sides='onesided', scale='linear') - - # Original behavior: Amplitude at 20 Hz is (a/2)*(n/2) for a Hanning window - w = np.hanning(n) # default window is a Hanning window - plt.magnitude_spectrum(x*np.sum(w), Fs=1/T, sides='onesided', scale='linear') - - - - - -Change to signatures of :meth:`~matplotlib.axes.Axes.bar` & :meth:`~matplotlib.axes.Axes.barh` ----------------------------------------------------------------------------------------------- - -For 2.0 the :ref:`default value of *align* ` changed to -``'center'``. However this caused the signature of -:meth:`~matplotlib.axes.Axes.bar` and -:meth:`~matplotlib.axes.Axes.barh` to be misleading as the first parameters were -still *left* and *bottom* respectively:: - - bar(left, height, *, align='center', **kwargs) - barh(bottom, width, *, align='center', **kwargs) - -despite behaving as the center in both cases. The methods now take -``*args, **kwargs`` as input and are documented to have the primary -signatures of:: - - bar(x, height, *, align='center', **kwargs) - barh(y, width, *, align='center', **kwargs) +:PEP:`3102` describes keyword-only arguments, which allow Matplotlib +to provide explicit call signatures - where we previously used +``*args, **kwargs`` and ``kwargs.pop``, we can now expose named +arguments. In some places, unknown kwargs were previously ignored but +now raise ``TypeError`` because ``**kwargs`` has been removed. -Passing *left* and *bottom* as keyword arguments to -:meth:`~matplotlib.axes.Axes.bar` and -:meth:`~matplotlib.axes.Axes.barh` respectively will warn. -Support will be removed in Matplotlib 3.0. +- :meth:`matplotlib.axes.Axes.stem` no longer accepts unknown keywords, + and raises ``TypeError`` instead of emitting a deprecation. +- :meth:`matplotlib.axex.Axes.stem` now raises TypeError when passed + unhandled positional arguments. If two or more arguments are passed + (ie X, Y, [linefmt], ...) and Y cannot be cast to an array, an error + will be raised instead of treating X as Y and Y as linefmt. +- :meth:`mpl_toolkits.axes_grid1.axes_divider.SubPlotDivider` raises + ``TypeError`` instead of ``Exception`` when passed unknown kwargs. -Font cache as json ------------------- -The font cache is now saved as json, rather than a pickle. +Cleanup decorators and test classes no longer destroy warnings filter on exit +----------------------------------------------------------------------------- +The decorators and classes in matplotlib.testing.decorators no longer +destroy the warnings filter on exit. Instead, they restore the warnings +filter that existed before the test started using ``warnings.catch_warnings``. -Invalid (Non-finite) Axis Limit Error -------------------------------------- -When using :func:`~matplotlib.axes.Axes.set_xlim` and -:func:`~matplotlib.axes.Axes.set_ylim`, passing non-finite values now -results in a ``ValueError``. The previous behavior resulted in the -limits being erroneously reset to ``(-0.001, 0.001)``. +Non-interactive FigureManager classes are now aliases of FigureManagerBase +-------------------------------------------------------------------------- -``scatter`` and ``Collection`` offsets are no longer implicitly flattened -------------------------------------------------------------------------- +The `FigureManagerPdf`, `FigureManagerPS`, and `FigureManagerSVG` classes, +which were previously empty subclasses of `FigureManagerBase` (i.e., not +adding or overriding any attribute or method), are now direct aliases for +`FigureManagerBase`. -`~matplotlib.collections.Collection` (and thus both 2D -`~matplotlib.axes.Axes.scatter` and 3D -`~mpl_toolkits.mplot3d.axes3d.Axes3D.scatter`) no -longer implicitly flattens its offsets. As a consequence, ``scatter``'s ``x`` -and ``y`` arguments can no longer be 2+-dimensional arrays. -Deprecations ------------- - -``GraphicsContextBase``\'s ``linestyle`` property. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``GraphicsContextBase.get_linestyle`` and -``GraphicsContextBase.set_linestyle`` methods, which had no effect, -have been deprecated. All of the backends Matplotlib ships use -``GraphicsContextBase.get_dashes`` and -``GraphicsContextBase.set_dashes`` which are more general. -Third-party backends should also migrate to the ``*_dashes`` methods. - - -``NavigationToolbar2.dynamic_update`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use :meth:`draw_idle` method on the ``Canvas`` instance instead. - - -Testing -~~~~~~~ - -`matplotlib.testing.noseclasses` is deprecated and will be removed in 2.3 - - -``EngFormatter`` *num* arg as string -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Passing a string as *num* argument when calling an instance of -`matplotlib.ticker.EngFormatter` is deprecated and will be removed in 2.3. - - -``mpl_toolkits.axes_grid`` module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -All functionally from `mpl_toolkits.axes_grid` can be found in either -`mpl_toolkits.axes_grid1` or `mpl_toolkits.axisartist`. Axes classes -from `mpl_toolkits.axes_grid` based on `Axis` from -`mpl_toolkits.axisartist` can be found in `mpl_toolkits.axisartist`. - - -``Axes`` collision in ``Figure.add_axes`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Adding an axes instance to a figure by using the same arguments as for -a previous axes instance currently reuses the earlier instance. This -behavior has been deprecated in Matplotlib 2.1. In a future version, a -*new* instance will always be created and returned. Meanwhile, in such -a situation, a deprecation warning is raised by -:class:`~matplotlib.figure.AxesStack`. - -This warning can be suppressed, and the future behavior ensured, by passing -a *unique* label to each axes instance. See the docstring of -:meth:`~matplotlib.figure.Figure.add_axes` for more information. - -Additional details on the rationale behind this deprecation can be found -in :ghissue:`7377` and :ghissue:`9024`. - - -Former validators for ``contour.negative_linestyle`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -The former public validation functions ``validate_negative_linestyle`` -and ``validate_negative_linestyle_legacy`` will be deprecated in 2.1 and -may be removed in 2.3. There are no public functions to replace them. - - - -``cbook`` -~~~~~~~~~ - -Many unused or near-unused :mod:`matplotlib.cbook` functions and -classes have been deprecated: ``converter``, ``tostr``, -``todatetime``, ``todate``, ``tofloat``, ``toint``, ``unique``, -``is_string_like``, ``is_sequence_of_strings``, ``is_scalar``, -``Sorter``, ``Xlator``, ``soundex``, ``Null``, ``dict_delall``, -``RingBuffer``, ``get_split_ind``, ``wrap``, -``get_recursive_filelist``, ``pieces``, ``exception_to_str``, -``allequal``, ``alltrue``, ``onetrue``, ``allpairs``, ``finddir``, -``reverse_dict``, ``restrict_dict``, ``issubclass_safe``, -``recursive_remove``, ``unmasked_index_ranges``. - - -Code Removal ------------- - -qt4_compat.py -~~~~~~~~~~~~~ - -Moved to ``qt_compat.py``. Renamed because it now handles Qt5 as well. - - -Previously Deprecated methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``GraphicsContextBase.set_graylevel``, ``FigureCanvasBase.onHilite`` and -``mpl_toolkits.axes_grid1.mpl_axes.Axes.toggle_axisline`` methods have been -removed. - -The ``ArtistInspector.findobj`` method, which was never working due to the lack -of a ``get_children`` method, has been removed. - -The deprecated ``point_in_path``, ``get_path_extents``, -``point_in_path_collection``, ``path_intersects_path``, -``convert_path_to_polygons``, ``cleanup_path`` and ``clip_path_to_rect`` -functions in the ``matplotlib.path`` module have been removed. Their -functionality remains exposed as methods on the ``Path`` class. - -The deprecated ``Artist.get_axes`` and ``Artist.set_axes`` methods -have been removed - - -The ``matplotlib.backends.backend_ps.seq_allequal`` function has been removed. -Use ``np.array_equal`` instead. - -The deprecated ``matplotlib.rcsetup.validate_maskedarray``, -``matplotlib.rcsetup.deprecate_savefig_extension`` and -``matplotlib.rcsetup.validate_tkpythoninspect`` functions, and associated -``savefig.extension`` and ``tk.pythoninspect`` rcparams entries have been -removed. - - -The kwarg ``resolution`` of -:class:`matplotlib.projections.polar.PolarAxes` has been removed. It -has deprecation with no effect from version `0.98.x`. - - -``Axes.set_aspect("normal")`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Support for setting an ``Axes``\'s aspect to ``"normal"`` has been -removed, in favor of the synonym ``"auto"``. - - -``shading`` kwarg to ``pcolor`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``shading`` kwarg to `~matplotlib.axes.Axes.pcolor` has been -removed. Set ``edgecolors`` appropriately instead. - - -Functions removed from the `lines` module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :mod:`matplotlib.lines` module no longer imports the -``pts_to_prestep``, ``pts_to_midstep`` and ``pts_to_poststep`` -functions from :mod:`matplotlib.cbook`. - - -PDF backend functions -~~~~~~~~~~~~~~~~~~~~~ - -The methods ``embedTeXFont`` and ``tex_font_mapping`` of -:class:`matplotlib.backqend_pdf.PdfFile` have been removed. It is -unlikely that external users would have called these methods, which -are related to the font system internal to the PDF backend. - - -matplotlib.delaunay -~~~~~~~~~~~~~~~~~~~ - -Remove the delaunay triangulation code which is now handled by Qhull -via :mod:`matplotlib.tri`. - -API Changes in 2.0.1 -==================== - -Extensions to `matplotlib.backend_bases.GraphicsContextBase` ------------------------------------------------------------- - -To better support controlling the color of hatches, the method -`matplotlib.backend_bases.GraphicsContextBase.set_hatch_color` was -added to the expected API of ``GraphicsContext`` classes. Calls to -this method are currently wrapped with a ``try:...except Attribute:`` -block to preserve back-compatibility with any third-party backends -which do not extend `~matplotlib.backend_bases.GraphicsContextBase`. - -This value can be accessed in the backends via -`matplotlib.backend_bases.GraphicsContextBase.get_hatch_color` (which -was added in 2.0 see :ref:`gc_get_hatch_color_wn`) and should be used -to color the hatches. - -In the future there may also be ``hatch_linewidth`` and -``hatch_density`` related methods added. It is encouraged, but not -required that third-party backends extend -`~matplotlib.backend_bases.GraphicsContextBase` to make adapting to -these changes easier. - - -`afm.get_fontconfig_fonts` returns a list of paths and does not check for existence ------------------------------------------------------------------------------------ - -`afm.get_fontconfig_fonts` used to return a set of paths encoded as a -``{key: 1, ...}`` dict, and checked for the existence of the paths. It now -returns a list and dropped the existence check, as the same check is performed -by the caller (`afm.findSystemFonts`) as well. - - -`bar` now returns rectangles of negative height or width if the corresponding input is negative ------------------------------------------------------------------------------------------------ - -`plt.bar` used to normalize the coordinates of the rectangles that it created, -to keep their height and width positives, even if the corresponding input was -negative. This normalization has been removed to permit a simpler computation -of the correct `sticky_edges` to use. - - -Do not clip line width when scaling dashes +Change to the output of `.image.thumbnail` ------------------------------------------ -The algorithm to scale dashes was changed to no longer clip the -scaling factor: the dash patterns now continue to shrink at thin line widths. -If the line width is smaller than the effective pixel size, this may result in -dashed lines turning into solid gray-ish lines. This also required slightly -tweaking the default patterns for '--', ':', and '.-' so that with the default -line width the final patterns would not change. - -There is no way to restore the old behavior. - - -Deprecate 'Vega' color maps ---------------------------- - -The "Vega" colormaps are deprecated in Matplotlib 2.0.1 and will be -removed in Matplotlib 2.2. Use the "tab" colormaps instead: "tab10", -"tab20", "tab20b", "tab20c". - - -API Changes in 2.0.0 -==================== - -Deprecation and removal ------------------------ +When called with ``preview=False``, `.image.thumbnail` previously returned an +figure whose canvas class was set according to the output file extension. It +now returns a figure whose canvas class is the base `FigureCanvasBase` (and +relies on `FigureCanvasBase.print_figure`) to handle the canvas switching +properly). -Color of Axes -~~~~~~~~~~~~~ -The ``axisbg`` and ``axis_bgcolor`` properties on *Axes* have been -deprecated in favor of ``facecolor``. +As a side effect of this change, `.image.thumbnail` now also supports .ps, .eps, +and .svgz output. -GTK and GDK backends deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The GDK and GTK backends have been deprecated. These obsolete backends -allow figures to be rendered via the GDK API to files and GTK2 figures. -They are untested and known to be broken, and their use has been -discouraged for some time. Instead, use the `GTKAgg` and `GTKCairo` -backends for rendering to GTK2 windows. -WX backend deprecated -~~~~~~~~~~~~~~~~~~~~~ -The WX backend has been deprecated. It is untested, and its -use has been discouraged for some time. Instead, use the `WXAgg` -backend for rendering figures to WX windows. -CocoaAgg backend removed -~~~~~~~~~~~~~~~~~~~~~~~~ -The deprecated and not fully functional CocoaAgg backend has been removed. +`.FuncAnimation` now draws artists according to their zorder when blitting +-------------------------------------------------------------------------- -`round` removed from TkAgg Backend -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The TkAgg backend had its own implementation of the `round` function. This -was unused internally and has been removed. Instead, use either the -`round` builtin function or `numpy.round`. +`.FuncAnimation` now draws artists returned by the user- +function according to their zorder when using blitting, +instead of using the order in which they are being passed. +However, note that only zorder of passed artists will be +respected, as they are drawn on top of any existing artists +(see `#11369 `_). -'hold' functionality deprecated -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The 'hold' keyword argument and all functions and methods related -to it are deprecated, along with the 'axes.hold' `rcParams` entry. -The behavior will remain consistent with the default ``hold=True`` -state that has long been in place. Instead of using a function -or keyword argument (``hold=False``) to change that behavior, -explicitly clear the axes or figure as needed prior to subsequent -plotting commands. +Contour color autoscaling improvements +-------------------------------------- -`Artist.update` has return value --------------------------------- - -The methods `matplotlib.artist.Artist.set`, -`matplotlib.Artist.update`, and the function `matplotlib.artist.setp` -now use a common codepath to look up how to update the given artist -properties (either using the setter methods or an attribute/property). - -The behavior of `matplotlib.Artist.update` is slightly changed to -return a list of the values returned from the setter methods to avoid -changing the API of `matplotlib.Artist.set` and -`matplotlib.artist.setp`. - -The keys passed into `matplotlib.Artist.update` are now converted to -lower case before being processed, to match the behavior of -`matplotlib.Artist.set` and `matplotlib.artist.setp`. This should not -break any user code because there are no set methods with capitals in -their names, but this puts a constraint on naming properties in the future. - +Selection of contour levels is now the same for contour and +contourf; previously, for contour, levels outside the data range were +deleted. (Exception: if no contour levels are found within the +data range, the `levels` attribute is replaced with a list holding +only the minimum of the data range.) -`Legend` initializers gain edgecolor and facecolor kwargs ---------------------------------------------------------- +When contour is called with levels specified as a target number rather +than a list, and the 'extend' kwarg is used, the levels are now chosen +such that some data typically will fall in the extended range. -The :class:`~matplotlib.legend.Legend` background patch (or 'frame') -can have its ``edgecolor`` and ``facecolor`` determined by the -corresponding keyword arguments to the :class:`matplotlib.legend.Legend` -initializer, or to any of the methods or functions that call that -initializer. If left to their default values of `None`, their values -will be taken from ``matplotlib.rcParams``. The previously-existing -``framealpha`` kwarg still controls the alpha transparency of the -patch. +When contour is called with a `LogNorm` or a `LogLocator`, it will now +select colors using the geometric mean rather than the arithmetic mean +of the contour levels. -Qualitative colormaps ---------------------- +Streamplot last row and column fixed +------------------------------------ -Colorbrewer's qualitative/discrete colormaps ("Accent", "Dark2", "Paired", -"Pastel1", "Pastel2", "Set1", "Set2", "Set3") are now implemented as -``ListedColormap`` instead of ``LinearSegmentedColormap``. +A bug was fixed where the last row and column of data in +`~.Axes.axes.streamplot` were being dropped. -To use these for images where categories are specified as integers, for -instance, use:: - plt.imshow(x, cmap='Dark2', norm=colors.NoNorm()) +Changed default `AutoDateLocator` kwarg *interval_multiples* to ``True`` +------------------------------------------------------------------------ +The default value of the tick locator for dates, `.dates.AutoDateLocator` +kwarg *interval_multiples* was set to ``False`` which leads to not-nice +looking automatic ticks in many instances. The much nicer +``interval_multiples=True`` is the new default. See below to get the +old behavior back: -Change in the ``draw_image`` backend API ----------------------------------------- + .. plot:: -The ``draw_image`` method implemented by backends has changed its interface. + import matplotlib.pyplot as plt + import datetime + import matplotlib.dates as mdates -This change is only relevant if the backend declares that it is able -to transform images by returning ``True`` from ``option_scale_image``. -See the ``draw_image`` docstring for more information. + t0 = datetime.datetime(2009, 8, 20, 1, 10, 12) + tf = datetime.datetime(2009, 8, 20, 1, 42, 11) + fig, axs = plt.subplots(1, 2, constrained_layout=True) + ax = axs[0] + ax.axhspan(t0, tf, facecolor="blue", alpha=0.25) + ax.set_ylim(t0 - datetime.timedelta(minutes=3), + tf + datetime.timedelta(minutes=3)) + ax.set_title('NEW DEFAULT') -`matplotlib.ticker.LinearLocator` algorithm update --------------------------------------------------- + ax = axs[1] + ax.axhspan(t0, tf, facecolor="blue", alpha=0.25) + ax.set_ylim(t0 - datetime.timedelta(minutes=3), + tf + datetime.timedelta(minutes=3)) + # old behavior + locator = mdates.AutoDateLocator(interval_multiples=False, ) + ax.yaxis.set_major_locator(locator) + ax.yaxis.set_major_formatter(mdates.AutoDateFormatter(locator)) -The ``matplotlib.ticker.LinearLocator`` is used to define the range and -location of axis ticks when the user wants an exact number of ticks. -``LinearLocator`` thus differs from the default locator ``MaxNLocator``, -for which the user specifies a maximum number of intervals rather than -a precise number of ticks. - -The view range algorithm in ``matplotlib.ticker.LinearLocator`` has been -changed so that more convenient tick locations are chosen. The new algorithm -returns a plot view range that is a multiple of the user-requested number of -ticks. This ensures tick marks will be located at whole integers more -consistently. For example, when both y-axes of a``twinx`` plot use -``matplotlib.ticker.LinearLocator`` with the same number of ticks, -their y-tick locations and grid lines will coincide. - -`matplotlib.ticker.LogLocator` gains numticks kwarg ---------------------------------------------------- - -The maximum number of ticks generated by the -`~matplotlib.ticker.LogLocator` can now be controlled explicitly -via setting the new 'numticks' kwarg to an integer. By default -the kwarg is None which internally sets it to the 'auto' string, -triggering a new algorithm for adjusting the maximum according -to the axis length relative to the ticklabel font size. - -`matplotlib.ticker.LogFormatter`: two new kwargs ------------------------------------------------- + ax.set_title('OLD') + plt.show() -Previously, minor ticks on log-scaled axes were not labeled by -default. An algorithm has been added to the -`~matplotlib.ticker.LogFormatter` to control the labeling of -ticks between integer powers of the base. The algorithm uses -two parameters supplied in a kwarg tuple named 'minor_thresholds'. -See the docstring for further explanation. -To improve support for axes using `~matplotlib.ticker.SymmetricLogLocator`, -a 'linthresh' kwarg was added. +`.Axes.get_position` now returns actual position if aspect changed +------------------------------------------------------------------ +`.Axes.get_position` used to return the original position unless a +draw had been triggered or `.Axes.apply_aspect` had been called, even +if the kwarg *original* was set to ``False``. Now `.Axes.apply_aspect` +is called so ``ax.get_position()`` will return the new modified position. +To get the old behavior use ``ax.get_position(original=True)``. -New defaults for 3D quiver function in mpl_toolkits.mplot3d.axes3d.py ---------------------------------------------------------------------- -Matplotlib has both a 2D and a 3D ``quiver`` function. These changes -affect only the 3D function and make the default behavior of the 3D -function match the 2D version. There are two changes: +The ticks for colorbar now adjust for the size of the colorbar +-------------------------------------------------------------- -1) The 3D quiver function previously normalized the arrows to be the - same length, which makes it unusable for situations where the - arrows should be different lengths and does not match the behavior - of the 2D function. This normalization behavior is now controlled - with the ``normalize`` keyword, which defaults to False. +Colorbar ticks now adjust for the size of the colorbar if the +colorbar is made from a mappable that is not a contour or +doesn't have a BoundaryNorm, or boundaries are not specified. +If boundaries, etc are specified, the colorbar maintains the +original behavior. -2) The ``pivot`` keyword now defaults to ``tail`` instead of - ``tip``. This was done in order to match the default behavior of - the 2D quiver function. -To obtain the previous behavior with the 3D quiver function, one can -call the function with :: +Colorbar for log-scaled hexbin +------------------------------ - ax.quiver(x, y, z, u, v, w, normalize=True, pivot='tip') +When using `hexbin` and plotting with a logarithmic color scale, the colorbar +ticks are now correctly log scaled. Previously the tick values were linear +scaled log(number of counts). -where "ax" is an ``Axes3d`` object created with something like :: +PGF backend now explicitly makes black text black +------------------------------------------------- - import mpl_toolkits.mplot3d.axes3d - ax = plt.sublot(111, projection='3d') +Previous behavior with the pgf backend was for text specified as black to +actually be the default color of whatever was rendering the pgf file (which was +of course usually black). The new behavior is that black text is black, +regardless of the default color. However, this means that there is no way to +fall back on the default color of the renderer. -Stale figure behavior ---------------------- +Blacklisted rcparams no longer updated by `rcdefaults`, `rc_file_defaults`, `rc_file` +------------------------------------------------------------------------------------- -Attempting to draw the figure will now mark it as not stale (independent if -the draw succeeds). This change is to prevent repeatedly trying to re-draw a -figure which is raising an error on draw. The previous behavior would only mark -a figure as not stale after a full re-draw succeeded. +The rc modifier functions `rcdefaults`, `rc_file_defaults` and `rc_file` +now ignore rcParams in the `matplotlib.style.core.STYLE_BLACKLIST` set. In +particular, this prevents the ``backend`` and ``interactive`` rcParams from +being incorrectly modified by these functions. -The spectral colormap is now nipy_spectral ------------------------------------------- -The colormaps formerly known as ``spectral`` and ``spectral_r`` have been -replaced by ``nipy_spectral`` and ``nipy_spectral_r`` since Matplotlib -1.3.0. Even though the colormap was deprecated in Matplotlib 1.3.0, it never -raised a warning. As of Matplotlib 2.0.0, using the old names raises a -deprecation warning. In the future, using the old names will raise an error. +`CallbackRegistry` now stores callbacks using stdlib's `WeakMethod`\s +--------------------------------------------------------------------- -Default install no longer includes test images ----------------------------------------------- +In particular, this implies that ``CallbackRegistry.callbacks[signal]`` is now +a mapping of callback ids to `WeakMethod`\s (i.e., they need to be first called +with no arguments to retrieve the method itself). -To reduce the size of wheels and source installs, the tests and -baseline images are no longer included by default. -To restore installing the tests and images, use a `setup.cfg` with :: +Changes regarding the text.latex.unicode rcParam +------------------------------------------------ - [packages] - tests = True - toolkits_tests = True +The rcParam now defaults to True and is deprecated (i.e., in future versions +of Maplotlib, unicode input will always be supported). -in the source directory at build/install time. +Moreover, the underlying implementation now uses ``\usepackage[utf8]{inputenc}`` +instead of ``\usepackage{ucs}\usepackage[utf8x]{inputenc}``. -Changes in 1.5.3 -================ -``ax.plot(..., marker=None)`` gives default marker +Return type of ArtistInspector.get_aliases changed -------------------------------------------------- -Prior to 1.5.3 kwargs passed to `~matplotlib.Axes.plot` were handled -in two parts -- default kwargs generated internal to -`~matplotlib.Axes.plot` (such as the cycled styles) and user supplied -kwargs. The internally generated kwargs were passed to the -`matplotlib.lines.Line2D.__init__` and the user kwargs were passed to -``ln.set(**kwargs)`` to update the artist after it was created. Now -both sets of kwargs are merged and passed to -`~matplotlib.lines.Line2D.__init__`. This change was made to allow `None` -to be passed in via the user kwargs to mean 'do the default thing' as -is the convention through out mpl rather than raising an exception. - -Unlike most `~matplotlib.lines.Line2D` setter methods -`~matplotlib.lines.Line2D.set_marker` did accept `None` as a valid -input which was mapped to 'no marker'. Thus, by routing this -``marker=None`` through ``__init__`` rather than ``set(...)`` the meaning -of ``ax.plot(..., marker=None)`` changed from 'no markers' to 'default markers -from rcparams'. - -This is change is only evident if ``mpl.rcParams['lines.marker']`` has a value -other than ``'None'`` (which is string ``'None'`` which means 'no marker'). - - -Changes in 1.5.2 -================ - - -Default Behavior Changes ------------------------- - -Changed default ``autorange`` behavior in boxplots -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`ArtistInspector.get_aliases` previously returned the set of aliases as +``{fullname: {alias1: None, alias2: None, ...}}``. The dict-to-None mapping +was used to simulate a set in earlier versions of Python. It has now been +replaced by a set, i.e. ``{fullname: {alias1, alias2, ...}}``. -Prior to v1.5.2, the whiskers of boxplots would extend to the minimum -and maximum values if the quartiles were all equal (i.e., Q1 = median -= Q3). This behavior has been disabled by default to restore consistency -with other plotting packages. +This value is also stored in `ArtistInspector.aliasd`, which has likewise +changed. -To restore the old behavior, simply set ``autorange=True`` when -calling ``plt.boxplot``. +Removed ``pytz`` as a dependency +-------------------------------- -Changes in 1.5.0 -================ +Since ``dateutil`` and ``pytz`` both provide time zones, and +matplotlib already depends on ``dateutil``, matplotlib will now use +``dateutil`` time zones internally and drop the redundant dependency +on ``pytz``. While ``dateutil`` time zones are preferred (and +currently recommended in the Python documentation), the explicit use +of ``pytz`` zones is still supported. -Code Changes +Deprecations ------------ -Reversed `matplotlib.cbook.ls_mapper`, added `ls_mapper_r` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Formerly, `matplotlib.cbook.ls_mapper` was a dictionary with -the long-form line-style names (`"solid"`) as keys and the short -forms (`"-"`) as values. This long-to-short mapping is now done -by `ls_mapper_r`, and the short-to-long mapping is done by the -`ls_mapper`. - -Prevent moving artists between Axes, Property-ify Artist.axes, deprecate Artist.{get,set}_axes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This was done to prevent an Artist that is -already associated with an Axes from being moved/added to a different Axes. -This was never supported as it causes havoc with the transform stack. -The apparent support for this (as it did not raise an exception) was -the source of multiple bug reports and questions on SO. - -For almost all use-cases, the assignment of the axes to an artist should be -taken care of by the axes as part of the ``Axes.add_*`` method, hence the -deprecation of {get,set}_axes. - -Removing the ``set_axes`` method will also remove the 'axes' line from -the ACCEPTS kwarg tables (assuming that the removal date gets here -before that gets overhauled). - -Tightened input validation on 'pivot' kwarg to quiver -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tightened validation so that only {'tip', 'tail', 'mid', and 'middle'} -(but any capitalization) are valid values for the 'pivot' kwarg in -the `Quiver.__init__` (and hence `Axes.quiver` and -`plt.quiver` which both fully delegate to `Quiver`). Previously any -input matching 'mid.*' would be interpreted as 'middle', 'tip.*' as -'tip' and any string not matching one of those patterns as 'tail'. - -The value of `Quiver.pivot` is normalized to be in the set {'tip', -'tail', 'middle'} in `Quiver.__init__`. - -Reordered `Axes.get_children` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The artist order returned by `Axes.get_children` did not -match the one used by `Axes.draw`. They now use the same -order, as `Axes.draw` now calls `Axes.get_children`. - -Changed behaviour of contour plots -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The default behaviour of :func:`~matplotlib.pyplot.contour` and -:func:`~matplotlib.pyplot.contourf` when using a masked array is now determined -by the new keyword argument `corner_mask`, or if this is not specified then -the new rcParam `contour.corner_mask` instead. The new default behaviour is -equivalent to using `corner_mask=True`; the previous behaviour can be obtained -using `corner_mask=False` or by changing the rcParam. The example -http://matplotlib.org/examples/pylab_examples/contour_corner_mask.html -demonstrates the difference. Use of the old contouring algorithm, which is -obtained with `corner_mask='legacy'`, is now deprecated. - -Contour labels may now appear in different places than in earlier versions of -Matplotlib. - -In addition, the keyword argument `nchunk` now applies to -:func:`~matplotlib.pyplot.contour` as well as -:func:`~matplotlib.pyplot.contourf`, and it subdivides the domain into -subdomains of exactly `nchunk` by `nchunk` quads, whereas previously it was -only roughly `nchunk` by `nchunk` quads. - -The C/C++ object that performs contour calculations used to be stored in the -public attribute QuadContourSet.Cntr, but is now stored in a private attribute -and should not be accessed by end users. - -Added set_params function to all Locator types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This was a bug fix targeted at making the api for Locators more consistent. - -In the old behavior, only locators of type MaxNLocator have set_params() -defined, causing its use on any other Locator to raise an AttributeError *( -aside: set_params(args) is a function that sets the parameters of a Locator -instance to be as specified within args)*. The fix involves moving set_params() -to the Locator class such that all subtypes will have this function defined. - -Since each of the Locator subtypes have their own modifiable parameters, a -universal set_params() in Locator isn't ideal. Instead, a default no-operation -function that raises a warning is implemented in Locator. Subtypes extending -Locator will then override with their own implementations. Subtypes that do -not have a need for set_params() will fall back onto their parent's -implementation, which raises a warning as intended. - -In the new behavior, Locator instances will not raise an AttributeError -when set_params() is called. For Locators that do not implement set_params(), -the default implementation in Locator is used. - -Disallow ``None`` as x or y value in ax.plot -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Do not allow ``None`` as a valid input for the ``x`` or ``y`` args in -`ax.plot`. This may break some user code, but this was never officially -supported (ex documented) and allowing ``None`` objects through can lead -to confusing exceptions downstream. - -To create an empty line use :: - - ln1, = ax.plot([], [], ...) - ln2, = ax.plot([], ...) - -In either case to update the data in the `Line2D` object you must update -both the ``x`` and ``y`` data. - - -Removed `args` and `kwargs` from `MicrosecondLocator.__call__` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The call signature of :meth:`~matplotlib.dates.MicrosecondLocator.__call__` -has changed from `__call__(self, *args, **kwargs)` to `__call__(self)`. -This is consistent with the superclass :class:`~matplotlib.ticker.Locator` -and also all the other Locators derived from this superclass. - - -No `ValueError` for the MicrosecondLocator and YearLocator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :class:`~matplotlib.dates.MicrosecondLocator` and -:class:`~matplotlib.dates.YearLocator` objects when called will return -an empty list if the axes have no data or the view has no interval. -Previously, they raised a `ValueError`. This is consistent with all -the Date Locators. - -'OffsetBox.DrawingArea' respects the 'clip' keyword argument -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The call signature was `OffsetBox.DrawingArea(..., clip=True)` but nothing -was done with the `clip` argument. The object did not do any clipping -regardless of that parameter. Now the object can and does clip the -child `Artists` if they are set to be clipped. - -You can turn off the clipping on a per-child basis using -`child.set_clip_on(False)`. +Modules +``````` +The following modules are deprecated: + +- :mod:`matplotlib.compat.subprocess`. This was a python 2 workaround, but all + the functionality can now be found in the python 3 standard library + :mod:`subprocess`. +- :mod:`matplotlib.backends.wx_compat`. Python 3 is only compatible with + wxPython 4, so support for wxPython 3 or earlier can be dropped. + +Classes, methods, functions, and attributes +``````````````````````````````````````````` + +The following classes, methods, functions, and attributes are deprecated: + +- ``RcParams.msg_depr``, ``RcParams.msg_depr_ignore``, + ``RcParams.msg_depr_set``, ``RcParams.msg_obsolete``, + ``RcParams.msg_backend_obsolete`` +- ``afm.parse_afm`` +- ``backend_pdf.PdfFile.texFontMap`` +- ``backend_pgf.get_texcommand`` +- ``backend_ps.get_bbox`` +- ``backend_qt5.FigureCanvasQT.keyAutoRepeat`` (directly check + ``event.guiEvent.isAutoRepeat()`` in the event handler to decide whether to + handle autorepeated key presses). +- ``backend_qt5.error_msg_qt``, ``backend_qt5.exception_handler`` +- ``backend_wx.FigureCanvasWx.macros`` +- ``backends.pylab_setup`` +- ``cbook.GetRealpathAndStat``, ``cbook.Locked`` +- ``cbook.is_numlike`` (use ``isinstance(..., numbers.Number)`` instead), + ``cbook.listFiles``, ``cbook.unicode_safe`` +- ``container.Container.set_remove_method``, +- ``contour.ContourLabeler.cl``, ``.cl_xy``, and ``.cl_cvalues`` +- ``dates.DateFormatter.strftime_pre_1900``, ``dates.DateFormatter.strftime`` +- ``font_manager.TempCache`` +- ``image._ImageBase.iterpnames``, use the ``interpolation_names`` property + instead. (this affects classes that inherit from ``_ImageBase`` including + :class:`FigureImage`, :class:`BboxImage`, and :class:`AxesImage`) +- ``mathtext.unichr_safe`` (use ``chr`` instead) +- ``patches.Polygon.xy`` +- ``table.Table.get_child_artists`` (use ``get_children`` instead) +- ``testing.compare.ImageComparisonTest``, ``testing.compare.compare_float`` +- ``testing.decorators.CleanupTest``, + ``testing.decorators.skip_if_command_unavailable`` +- ``FigureCanvasQT.keyAutoRepeat`` (directly check + ``event.guiEvent.isAutoRepeat()`` in the event handler to decide whether to + handle autorepeated key presses) +- ``FigureCanvasWx.macros`` +- ``_ImageBase.iterpnames``, use the ``interpolation_names`` property instead. + (this affects classes that inherit from ``_ImageBase`` including + :class:`FigureImage`, :class:`BboxImage`, and :class:`AxesImage`) +- ``patches.Polygon.xy`` +- ``texmanager.dvipng_hack_alpha`` +- ``text.Annotation.arrow`` +- `.Legend.draggable()`, in favor of `.Legend.set_draggable()` + (``Legend.draggable`` may be reintroduced as a property in future releases) +- ``textpath.TextToPath.tex_font_map`` +- :class:`matplotlib.cbook.deprecation.mplDeprecation` will be removed + in future versions. It is just an alias for + :class:`matplotlib.cbook.deprecation.MatplotlibDeprecationWarning`. + Please use the + :class:`~matplotlib.cbook.MatplotlibDeprecationWarning` directly if + neccessary. +- The ``matplotlib.cbook.Bunch`` class has been deprecated. Instead, use + `types.SimpleNamespace` from the standard library which provides the same + functionality. +- ``Axes.mouseover_set`` is now a frozenset, and deprecated. Directly + manipulate the artist's ``.mouseover`` attribute to change their mouseover + status. + +The following keyword arguments are deprecated: + +- passing ``verts`` to ``Axes.scatter`` (use ``marker`` instead) +- passing ``obj_type`` to ``cbook.deprecated`` + +The following call signatures are deprecated: + +- passing a ``wx.EvtHandler`` as first argument to ``backend_wx.TimerWx`` + + +rcParams +```````` + +The following rcParams are deprecated: + +- ``examples.directory`` (use ``datapath`` instead) +- ``pgf.debug`` (the pgf backend relies on logging) +- ``text.latex.unicode`` (always True now) + + +marker styles +````````````` +- Using ``(n, 3)`` as marker style to specify a circle marker is deprecated. Use + ``"o"`` instead. +- Using ``([(x0, y0), (x1, y1), ...], 0)`` as marker style to specify a custom + marker path is deprecated. Use ``[(x0, y0), (x1, y1), ...]`` instead. + + +Deprecation of ``LocatableAxes`` in toolkits +```````````````````````````````````````````` + +The ``LocatableAxes`` classes in toolkits have been deprecated. The base `Axes` +classes provide the same functionality to all subclasses, thus these mixins are +no longer necessary. Related functions have also been deprecated. Specifically: + +* ``mpl_toolkits.axes_grid1.axes_divider.LocatableAxesBase``: no specific + replacement; use any other ``Axes``-derived class directly instead. +* ``mpl_toolkits.axes_grid1.axes_divider.locatable_axes_factory``: no specific + replacement; use any other ``Axes``-derived class directly instead. +* ``mpl_toolkits.axes_grid1.axes_divider.Axes``: use + `mpl_toolkits.axes_grid1.mpl_axes.Axes` directly. +* ``mpl_toolkits.axes_grid1.axes_divider.LocatableAxes``: use + `mpl_toolkits.axes_grid1.mpl_axes.Axes` directly. +* ``mpl_toolkits.axisartist.axes_divider.Axes``: use + `mpl_toolkits.axisartist.axislines.Axes` directly. +* ``mpl_toolkits.axisartist.axes_divider.LocatableAxes``: use + `mpl_toolkits.axisartist.axislines.Axes` directly. -Add salt to clipPath id -~~~~~~~~~~~~~~~~~~~~~~~ - -Add salt to the hash used to determine the id of the ``clipPath`` -nodes. This is to avoid conflicts when two svg documents with the same -clip path are included in the same document (see -https://github.com/ipython/ipython/issues/8133 and -https://github.com/matplotlib/matplotlib/issues/4349 ), however this -means that the svg output is no longer deterministic if the same -figure is saved twice. It is not expected that this will affect any -users as the current ids are generated from an md5 hash of properties -of the clip path and any user would have a very difficult time -anticipating the value of the id. - -Changed snap threshold for circle markers to inf -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When drawing circle markers above some marker size (previously 6.0) -the path used to generate the marker was snapped to pixel centers. However, -this ends up distorting the marker away from a circle. By setting the -snap threshold to inf snapping is never done on circles. - -This change broke several tests, but is an improvement. - -Preserve units with Text position -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Previously the 'get_position' method on Text would strip away unit information -even though the units were still present. There was no inherent need to do -this, so it has been changed so that unit data (if present) will be preserved. -Essentially a call to 'get_position' will return the exact value from a call to -'set_position'. - -If you wish to get the old behaviour, then you can use the new method called -'get_unitless_position'. - -New API for custom Axes view changes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Interactive pan and zoom were previously implemented using a Cartesian-specific -algorithm that was not necessarily applicable to custom Axes. Three new private -methods, :meth:`~matplotlib.axes._base._AxesBase._get_view`, -:meth:`~matplotlib.axes._base._AxesBase._set_view`, and -:meth:`~matplotlib.axes._base._AxesBase._set_view_from_bbox`, allow for custom -*Axes* classes to override the pan and zoom algorithms. Implementors of -custom *Axes* who override these methods may provide suitable behaviour for -both pan and zoom as well as the view navigation buttons on the interactive -toolbars. - -MathTex visual changes ----------------------- - -The spacing commands in mathtext have been changed to more closely -match vanilla TeX. - - -Improved spacing in mathtext -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The extra space that appeared after subscripts and superscripts has -been removed. - -No annotation coordinates wrap -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In #2351 for 1.4.0 the behavior of ['axes points', 'axes pixel', -'figure points', 'figure pixel'] as coordinates was change to -no longer wrap for negative values. In 1.4.3 this change was -reverted for 'axes points' and 'axes pixel' and in addition caused -'axes fraction' to wrap. For 1.5 the behavior has been reverted to -as it was in 1.4.0-1.4.2, no wrapping for any type of coordinate. - -Deprecation ------------ - -Deprecated `GraphicsContextBase.set_graylevel` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `GraphicsContextBase.set_graylevel` function has been deprecated in 1.5 and -will be removed in 1.6. It has been unused. The -`GraphicsContextBase.set_foreground` could be used instead. - -deprecated idle_event -~~~~~~~~~~~~~~~~~~~~~ - -The `idle_event` was broken or missing in most backends and causes spurious -warnings in some cases, and its use in creating animations is now obsolete due -to the animations module. Therefore code involving it has been removed from all -but the wx backend (where it partially works), and its use is deprecated. The -animations module may be used instead to create animations. - -`color_cycle` deprecated -~~~~~~~~~~~~~~~~~~~~~~~~ - -In light of the new property cycling feature, -the Axes method *set_color_cycle* is now deprecated. -Calling this method will replace the current property cycle with -one that cycles just the given colors. - -Similarly, the rc parameter *axes.color_cycle* is also deprecated in -lieu of the new *axes.prop_cycle* parameter. Having both parameters in -the same rc file is not recommended as the result cannot be -predicted. For compatibility, setting *axes.color_cycle* will -replace the cycler in *axes.prop_cycle* with a color cycle. -Accessing *axes.color_cycle* will return just the color portion -of the property cycle, if it exists. - -Timeline for removal has not been set. - - -Bundled jquery --------------- - -The version of jquery bundled with the webagg backend has been upgraded -from 1.7.1 to 1.11.3. If you are using the version of jquery bundled -with webagg you will need to update your html files as such - -.. code-block:: diff - - - - + - - -Code Removed ------------- - -Removed `Image` from main namespace -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`Image` was imported from PIL/pillow to test if PIL is available, but -there is no reason to keep `Image` in the namespace once the availability -has been determined. - -Removed `lod` from Artist -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Removed the method *set_lod* and all references to -the attribute *_lod* as the are not used anywhere else in the -code base. It appears to be a feature stub that was never built -out. - -Removed threading related classes from cbook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The classes ``Scheduler``, ``Timeout``, and ``Idle`` were in cbook, but -are not used internally. They appear to be a prototype for the idle event -system which was not working and has recently been pulled out. - -Removed `Lena` images from sample_data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``lena.png`` and ``lena.jpg`` images have been removed from -Matplotlib's sample_data directory. The images are also no longer -available from `matplotlib.cbook.get_sample_data`. We suggest using -`matplotlib.cbook.get_sample_data('grace_hopper.png')` or -`matplotlib.cbook.get_sample_data('grace_hopper.jpg')` instead. - - -Legend -~~~~~~ -Removed handling of `loc` as a positional argument to `Legend` - - -Legend handlers -~~~~~~~~~~~~~~~ -Remove code to allow legend handlers to be callable. They must now -implement a method ``legend_artist``. - - -Axis -~~~~ -Removed method ``set_scale``. This is now handled via a private method which -should not be used directly by users. It is called via ``Axes.set_{x,y}scale`` -which takes care of ensuring the related changes are also made to the Axes -object. - -finance.py -~~~~~~~~~~ - -Removed functions with ambiguous argument order from finance.py - - -Annotation -~~~~~~~~~~ - -Removed ``textcoords`` and ``xytext`` proprieties from Annotation objects. - - -spinxext.ipython_*.py -~~~~~~~~~~~~~~~~~~~~~ - -Both ``ipython_console_highlighting`` and ``ipython_directive`` have been -moved to `IPython`. - -Change your import from 'matplotlib.sphinxext.ipython_directive' to -'IPython.sphinxext.ipython_directive' and from -'matplotlib.sphinxext.ipython_directive' to -'IPython.sphinxext.ipython_directive' - - -LineCollection.color -~~~~~~~~~~~~~~~~~~~~ - -Deprecated in 2005, use ``set_color`` - - -remove ``'faceted'`` as a valid value for `shading` in ``tri.tripcolor`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use `edgecolor` instead. Added validation on ``shading`` to -only be valid values. - - -Remove ``faceted`` kwarg from scatter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Remove support for the ``faceted`` kwarg. This was deprecated in -d48b34288e9651ff95c3b8a071ef5ac5cf50bae7 (2008-04-18!) and replaced by -``edgecolor``. - - -Remove ``set_colorbar`` method from ``ScalarMappable`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Remove ``set_colorbar`` method, use `colorbar` attribute directly. - - -patheffects.svg -~~~~~~~~~~~~~~~ - - - remove ``get_proxy_renderer`` method from ``AbstarctPathEffect`` class - - remove ``patch_alpha`` and ``offset_xy`` from ``SimplePatchShadow`` - - -Remove ``testing.image_util.py`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Contained only a no-longer used port of functionality from PIL - - -Remove ``mlab.FIFOBuffer`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Not used internally and not part of core mission of mpl. - - -Remove ``mlab.prepca`` -~~~~~~~~~~~~~~~~~~~~~~ -Deprecated in 2009. - - -Remove ``NavigationToolbar2QTAgg`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Added no functionality over the base ``NavigationToolbar2Qt`` - - -mpl.py -~~~~~~ - -Remove the module `matplotlib.mpl`. Deprecated in 1.3 by -PR #1670 and commit 78ce67d161625833cacff23cfe5d74920248c5b2 - - -Changes in 1.4.x -================ - -Code changes ------------- - -* A major refactoring of the axes module was made. The axes module has been - split into smaller modules: - - - the `_base` module, which contains a new private _AxesBase class. This - class contains all methods except plotting and labelling methods. - - the `axes` module, which contains the Axes class. This class inherits - from _AxesBase, and contains all plotting and labelling methods. - - the `_subplot` module, with all the classes concerning subplotting. - -There are a couple of things that do not exists in the `axes` module's -namespace anymore. If you use them, you need to import them from their -original location: - - - math -> `import math` - - ma -> `from numpy import ma` - - cbook -> `from matplotlib import cbook` - - docstring -> `from matplotlib import docstring` - - is_sequence_of_strings -> `from matplotlib.cbook import is_sequence_of_strings` - - is_string_like -> `from matplotlib.cbook import is_string_like` - - iterable -> `from matplotlib.cbook import iterable` - - itertools -> `import itertools` - - martist -> `from matplotlib import artist as martist` - - matplotlib -> `import matplotlib` - - mcoll -> `from matplotlib import collections as mcoll` - - mcolors -> `from matplotlib import colors as mcolors` - - mcontour -> `from matplotlib import contour as mcontour` - - mpatches -> `from matplotlib import patches as mpatches` - - mpath -> `from matplotlib import path as mpath` - - mquiver -> `from matplotlib import quiver as mquiver` - - mstack -> `from matplotlib import stack as mstack` - - mstream -> `from matplotlib import stream as mstream` - - mtable -> `from matplotlib import table as mtable` - -* As part of the refactoring to enable Qt5 support, the module - `matplotlib.backends.qt4_compat` was renamed to - `matplotlib.qt_compat`. `qt4_compat` is deprecated in 1.4 and - will be removed in 1.5. - -* The :func:`~matplotlib.pyplot.errorbar` method has been changed such that - the upper and lower limits (*lolims*, *uplims*, *xlolims*, *xuplims*) now - point in the correct direction. - -* The *fmt* kwarg for :func:`~matplotlib.pyplot.errorbar now supports - the string 'none' to suppress drawing of a line and markers; use - of the *None* object for this is deprecated. The default *fmt* - value is changed to the empty string (''), so the line and markers - are governed by the :func:`~matplotlib.pyplot.plot` defaults. - -* A bug has been fixed in the path effects rendering of fonts, which now means - that the font size is consistent with non-path effect fonts. See - https://github.com/matplotlib/matplotlib/issues/2889 for more detail. - -* The Sphinx extensions `ipython_directive` and - `ipython_console_highlighting` have been moved to the IPython - project itself. While they remain in Matplotlib for this release, - they have been deprecated. Update your extensions in `conf.py` to - point to `IPython.sphinxext.ipython_directive` instead of - `matplotlib.sphinxext.ipython_directive`. - -* In `~matplotlib.finance`, almost all functions have been deprecated - and replaced with a pair of functions name `*_ochl` and `*_ohlc`. - The former is the 'open-close-high-low' order of quotes used - previously in this module, and the latter is the - 'open-high-low-close' order that is standard in finance. - -* For consistency the ``face_alpha`` keyword to - :class:`matplotlib.patheffects.SimplePatchShadow` has been deprecated in - favour of the ``alpha`` keyword. Similarly, the keyword ``offset_xy`` is now - named ``offset`` across all :class:`~matplotlib.patheffects.AbstractPathEffect`s. - ``matplotlib.patheffects._Base`` has - been renamed to :class:`matplotlib.patheffects.AbstractPathEffect`. - ``matplotlib.patheffect.ProxyRenderer`` has been renamed to - :class:`matplotlib.patheffects.PathEffectRenderer` and is now a full - RendererBase subclass. - -* The artist used to draw the outline of a `colorbar` has been changed - from a `matplotlib.lines.Line2D` to `matplotlib.patches.Polygon`, - thus `colorbar.ColorbarBase.outline` is now a - `matplotlib.patches.Polygon` object. - -* The legend handler interface has changed from a callable, to any object - which implements the ``legend_artists`` method (a deprecation phase will - see this interface be maintained for v1.4). See - :doc:`/tutorials/intermediate/legend_guide` for further details. Further legend changes - include: - - * :func:`matplotlib.axes.Axes._get_legend_handles` now returns a generator - of handles, rather than a list. - - * The :func:`~matplotlib.pyplot.legend` function's "loc" positional - argument has been deprecated. Use the "loc" keyword instead. - -* The rcParams `savefig.transparent` has been added to control - default transparency when saving figures. - -* Slightly refactored the `Annotation` family. The text location in - `Annotation` is now handled entirely handled by the underlying `Text` - object so `set_position` works as expected. The attributes `xytext` and - `textcoords` have been deprecated in favor of `xyann` and `anncoords` so - that `Annotation` and `AnnotaionBbox` can share a common sensibly named - api for getting/setting the location of the text or box. - - - `xyann` -> set the location of the annotation - - `xy` -> set where the arrow points to - - `anncoords` -> set the units of the annotation location - - `xycoords` -> set the units of the point location - - `set_position()` -> `Annotation` only set location of annotation - -* `matplotlib.mlab.specgram`, `matplotlib.mlab.psd`, `matplotlib.mlab.csd`, - `matplotlib.mlab.cohere`, `matplotlib.mlab.cohere_pairs`, - `matplotlib.pyplot.specgram`, `matplotlib.pyplot.psd`, - `matplotlib.pyplot.csd`, and `matplotlib.pyplot.cohere` now raise - ValueError where they previously raised AssertionError. - -* For `matplotlib.mlab.psd`, `matplotlib.mlab.csd`, - `matplotlib.mlab.cohere`, `matplotlib.mlab.cohere_pairs`, - `matplotlib.pyplot.specgram`, `matplotlib.pyplot.psd`, - `matplotlib.pyplot.csd`, and `matplotlib.pyplot.cohere`, in cases - where a shape (n, 1) array is returned, this is now converted to a (n, ) - array. Previously, (n, m) arrays were averaged to an (n, ) array, but - (n, 1) arrays were returend unchanged. This change makes the dimensions - consistent in both cases. - -* Added the rcParam `axes.fromatter.useoffset` to control the default value - of `useOffset` in `ticker.ScalarFormatter` - -* Added `Formatter` sub-class `StrMethodFormatter` which - does the exact same thing as `FormatStrFormatter`, but for new-style - formatting strings. - -* Deprecated `matplotlib.testing.image_util` and the only function within, - `matplotlib.testing.image_util.autocontrast`. These will be removed - completely in v1.5.0. - -* The ``fmt`` argument of :meth:`~matplotlib.axes.Axes.plot_date` has been - changed from ``bo`` to just ``o``, so color cycling can happen by default. - -* Removed the class `FigureManagerQTAgg` and deprecated `NavigationToolbar2QTAgg` - which will be removed in 1.5. - -* Removed formerly public (non-prefixed) attributes `rect` and - `drawRect` from `FigureCanvasQTAgg`; they were always an - implementation detail of the (preserved) `drawRectangle()` function. - -* The function signatures of `tight_bbox.adjust_bbox` and - `tight_bbox.process_figure_for_rasterizing` have been changed. A new - `fixed_dpi` parameter allows for overriding the `figure.dpi` setting - instead of trying to deduce the intended behaviour from the file format. - -* Added support for horizontal/vertical axes padding to - `mpl_toolkits.axes_grid1.ImageGrid` --- argument ``axes_pad`` can now be - tuple-like if separate axis padding is required. - The original behavior is preserved. - -* Added support for skewed transforms to `matplotlib.transforms.Affine2D`, - which can be created using the `skew` and `skew_deg` methods. - -* Added clockwise parameter to control sectors direction in `axes.pie` - -* In `matplotlib.lines.Line2D` the `markevery` functionality has been extended. - Previously an integer start-index and stride-length could be specified using - either a two-element-list or a two-element-tuple. Now this can only be done - using a two-element-tuple. If a two-element-list is used then it will be - treated as numpy fancy indexing and only the two markers corresponding to the - given indexes will be shown. - -* removed prop kwarg from `mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar` - call. It was passed through to the base-class `__init__` and is only used for - setting padding. Now `fontproperties` (which is what is really used to set - the font properties of `AnchoredSizeBar`) is passed through in place of `prop`. - If `fontpropreties` is not passed in, but `prop` is, then `prop` is used inplace - of `fontpropreties`. If both are passed in, `prop` is silently ignored. - - -* The use of the index 0 in `plt.subplot` and related commands is - deprecated. Due to a lack of validation calling `plt.subplots(2, 2, - 0)` does not raise an exception, but puts an axes in the _last_ - position. This is due to the indexing in subplot being 1-based (to - mirror MATLAB) so before indexing into the `GridSpec` object used to - determine where the axes should go, 1 is subtracted off. Passing in - 0 results in passing -1 to `GridSpec` which results in getting the - last position back. Even though this behavior is clearly wrong and - not intended, we are going through a deprecation cycle in an - abundance of caution that any users are exploiting this 'feature'. - The use of 0 as an index will raise a warning in 1.4 and an - exception in 1.5. - -* Clipping is now off by default on offset boxes. - -* Matplotlib now uses a less-aggressive call to ``gc.collect(1)`` when - closing figures to avoid major delays with large numbers of user objects - in memory. - -* The default clip value of *all* pie artists now defaults to ``False``. - - -Code removal ------------- - -* Removed ``mlab.levypdf``. The code raised a numpy error (and has for - a long time) and was not the standard form of the Levy distribution. - ``scipy.stats.levy`` should be used instead - - -.. _changes_in_1_3: - - -Changes in 1.3.x -================ - -Changes in 1.3.1 ----------------- - -It is rare that we make an API change in a bugfix release, however, -for 1.3.1 since 1.3.0 the following change was made: - -- `text.Text.cached` (used to cache font objects) has been made into a - private variable. Among the obvious encapsulation benefit, this - removes this confusing-looking member from the documentation. - -- The method :meth:`~matplotlib.axes.Axes.hist` now always returns bin - occupancies as an array of type `float`. Previously, it was sometimes - an array of type `int`, depending on the call. - -Code removal ------------- - -* The following items that were deprecated in version 1.2 or earlier - have now been removed completely. - - - The Qt 3.x backends (`qt` and `qtagg`) have been removed in - favor of the Qt 4.x backends (`qt4` and `qt4agg`). - - - The FltkAgg and Emf backends have been removed. - - - The `matplotlib.nxutils` module has been removed. Use the - functionality on `matplotlib.path.Path.contains_point` and - friends instead. - - - Instead of `axes.Axes.get_frame`, use `axes.Axes.patch`. - - - The following `kwargs` to the `legend` function have been - renamed: - - - `pad` -> `borderpad` - - `labelsep` -> `labelspacing` - - `handlelen` -> `handlelength` - - `handletextsep` -> `handletextpad` - - `axespad` -> `borderaxespad` - - Related to this, the following rcParams have been removed: - - - `legend.pad`, `legend.labelsep`, `legend.handlelen`, - `legend.handletextsep` and `legend.axespad` - - - For the `hist` function, instead of `width`, use `rwidth` - (relative width). - - - On `patches.Circle`, the `resolution` kwarg has been removed. - For a circle made up of line segments, use - `patches.CirclePolygon`. - - - The printing functions in the Wx backend have been removed due - to the burden of keeping them up-to-date. - - - `mlab.liaupunov` has been removed. - - - `mlab.save`, `mlab.load`, `pylab.save` and `pylab.load` have - been removed. We recommend using `numpy.savetxt` and - `numpy.loadtxt` instead. - - - `widgets.HorizontalSpanSelector` has been removed. Use - `widgets.SpanSelector` instead. - -Code deprecation ----------------- - -* The CocoaAgg backend has been deprecated, with the possibility for - deletion or resurrection in a future release. - -* The top-level functions in `matplotlib.path` that are implemented in - C++ were never meant to be public. Instead, users should use the - Pythonic wrappers for them in the `path.Path` and - `collections.Collection` classes. Use the following mapping to update - your code: - - - `point_in_path` -> `path.Path.contains_point` - - `get_path_extents` -> `path.Path.get_extents` - - `point_in_path_collection` -> `collection.Collection.contains` - - `path_in_path` -> `path.Path.contains_path` - - `path_intersects_path` -> `path.Path.intersects_path` - - `convert_path_to_polygons` -> `path.Path.to_polygons` - - `cleanup_path` -> `path.Path.cleaned` - - `points_in_path` -> `path.Path.contains_points` - - `clip_path_to_rect` -> `path.Path.clip_to_bbox` - -* `matplotlib.colors.normalize` and `matplotlib.colors.no_norm` have - been deprecated in favour of `matplotlib.colors.Normalize` and - `matplotlib.colors.NoNorm` respectively. - -* The `ScalarMappable` class' `set_colorbar` is now - deprecated. Instead, the - :attr:`matplotlib.cm.ScalarMappable.colorbar` attribute should be - used. In previous Matplotlib versions this attribute was an - undocumented tuple of ``(colorbar_instance, colorbar_axes)`` but is - now just ``colorbar_instance``. To get the colorbar axes it is - possible to just use the - :attr:`~matplotlib.colorbar.ColorbarBase.ax` attribute on a colorbar - instance. - -* The `~matplotlib.mpl` module is now deprecated. Those who relied on this - module should transition to simply using ``import matplotlib as mpl``. - -Code changes ------------- - -* :class:`~matplotlib.patches.Patch` now fully supports using RGBA values for - its ``facecolor`` and ``edgecolor`` attributes, which enables faces and - edges to have different alpha values. If the - :class:`~matplotlib.patches.Patch` object's ``alpha`` attribute is set to - anything other than ``None``, that value will override any alpha-channel - value in both the face and edge colors. Previously, if - :class:`~matplotlib.patches.Patch` had ``alpha=None``, the alpha component - of ``edgecolor`` would be applied to both the edge and face. - -* The optional ``isRGB`` argument to - :meth:`~matplotlib.backend_bases.GraphicsContextBase.set_foreground` (and - the other GraphicsContext classes that descend from it) has been renamed to - ``isRGBA``, and should now only be set to ``True`` if the ``fg`` color - argument is known to be an RGBA tuple. - -* For :class:`~matplotlib.patches.Patch`, the ``capstyle`` used is now - ``butt``, to be consistent with the default for most other objects, and to - avoid problems with non-solid ``linestyle`` appearing solid when using a - large ``linewidth``. Previously, :class:`~matplotlib.patches.Patch` used - ``capstyle='projecting'``. - -* `Path` objects can now be marked as `readonly` by passing - `readonly=True` to its constructor. The built-in path singletons, - obtained through `Path.unit*` class methods return readonly paths. - If you have code that modified these, you will need to make a - deepcopy first, using either:: - - import copy - path = copy.deepcopy(Path.unit_circle()) - - # or - - path = Path.unit_circle().deepcopy() - - Deep copying a `Path` always creates an editable (i.e. non-readonly) - `Path`. - -* The list at ``Path.NUM_VERTICES`` was replaced by a dictionary mapping - Path codes to the number of expected vertices at - :attr:`~matplotlib.path.Path.NUM_VERTICES_FOR_CODE`. - -* To support XKCD style plots, the :func:`matplotlib.path.cleanup_path` - method's signature was updated to require a sketch argument. Users of - :func:`matplotlib.path.cleanup_path` are encouraged to use the new - :meth:`~matplotlib.path.Path.cleaned` Path method. - -* Data limits on a plot now start from a state of having "null" - limits, rather than limits in the range (0, 1). This has an effect - on artists that only control limits in one direction, such as - `axvline` and `axhline`, since their limits will not longer also - include the range (0, 1). This fixes some problems where the - computed limits would be dependent on the order in which artists - were added to the axes. - -* Fixed a bug in setting the position for the right/top spine with data - position type. Previously, it would draw the right or top spine at - +1 data offset. - -* In :class:`~matplotlib.patches.FancyArrow`, the default arrow head - width, ``head_width``, has been made larger to produce a visible - arrow head. The new value of this kwarg is ``head_width = 20 * - width``. - -* It is now possible to provide ``number of levels + 1`` colors in the case of - `extend='both'` for contourf (or just ``number of levels`` colors for an - extend value ``min`` or ``max``) such that the resulting colormap's - ``set_under`` and ``set_over`` are defined appropriately. Any other number - of colors will continue to behave as before (if more colors are provided - than levels, the colors will be unused). A similar change has been applied - to contour, where ``extend='both'`` would expect ``number of levels + 2`` - colors. - -* A new keyword *extendrect* in :meth:`~matplotlib.pyplot.colorbar` and - :class:`~matplotlib.colorbar.ColorbarBase` allows one to control the shape - of colorbar extensions. - -* The extension of :class:`~matplotlib.widgets.MultiCursor` to both vertical - (default) and/or horizontal cursor implied that ``self.line`` is replaced - by ``self.vline`` for vertical cursors lines and ``self.hline`` is added - for the horizontal cursors lines. - -* On POSIX platforms, the :func:`~matplotlib.cbook.report_memory` function - raises :class:`NotImplementedError` instead of :class:`OSError` if the - :command:`ps` command cannot be run. - -* The :func:`matplotlib.cbook.check_output` function has been moved to - :func:`matplotlib.compat.subprocess`. - -Configuration and rcParams --------------------------- - -* On Linux, the user-specific `matplotlibrc` configuration file is now - located in `~/.config/matplotlib/matplotlibrc` to conform to the - `XDG Base Directory Specification - `_. - -* The `font.*` rcParams now affect only text objects created after the - rcParam has been set, and will not retroactively affect already - existing text objects. This brings their behavior in line with most - other rcParams. - -* Removed call of :meth:`~matplotlib.axes.Axes.grid` in - :meth:`~matplotlib.pyplot.plotfile`. To draw the axes grid, set the - ``axes.grid`` rcParam to *True*, or explicitly call - :meth:`~matplotlib.axes.Axes.grid`. - -Changes in 1.2.x -================ - -* The ``classic`` option of the rc parameter ``toolbar`` is deprecated - and will be removed in the next release. - -* The :meth:`~matplotlib.cbook.isvector` method has been removed since it - is no longer functional. - -* The `rasterization_zorder` property on `~matplotlib.axes.Axes` a - zorder below which artists are rasterized. This has defaulted to - -30000.0, but it now defaults to `None`, meaning no artists will be - rasterized. In order to rasterize artists below a given zorder - value, `set_rasterization_zorder` must be explicitly called. - -* In :meth:`~matplotlib.axes.Axes.scatter`, and `~pyplot.scatter`, - when specifying a marker using a tuple, the angle is now specified - in degrees, not radians. - -* Using :meth:`~matplotlib.axes.Axes.twinx` or - :meth:`~matplotlib.axes.Axes.twiny` no longer overrides the current locaters - and formatters on the axes. - -* In :meth:`~matplotlib.axes.Axes.contourf`, the handling of the *extend* - kwarg has changed. Formerly, the extended ranges were mapped - after to 0, 1 after being normed, so that they always corresponded - to the extreme values of the colormap. Now they are mapped - outside this range so that they correspond to the special - colormap values determined by the - :meth:`~matplotlib.colors.Colormap.set_under` and - :meth:`~matplotlib.colors.Colormap.set_over` methods, which - default to the colormap end points. - -* The new rc parameter ``savefig.format`` replaces ``cairo.format`` and - ``savefig.extension``, and sets the default file format used by - :meth:`matplotlib.figure.Figure.savefig`. - -* In :meth:`~matplotlib.pyplot.pie` and :meth:`~matplotlib.Axes.pie`, one can - now set the radius of the pie; setting the *radius* to 'None' (the default - value), will result in a pie with a radius of 1 as before. - -* Use of :func:`~matplotlib.projections.projection_factory` is now deprecated - in favour of axes class identification using - :func:`~matplotlib.projections.process_projection_requirements` followed by - direct axes class invocation (at the time of writing, functions which do this - are: :meth:`~matplotlib.figure.Figure.add_axes`, - :meth:`~matplotlib.figure.Figure.add_subplot` and - :meth:`~matplotlib.figure.Figure.gca`). Therefore:: - - - key = figure._make_key(*args, **kwargs) - ispolar = kwargs.pop('polar', False) - projection = kwargs.pop('projection', None) - if ispolar: - if projection is not None and projection != 'polar': - raise ValueError('polar and projection args are inconsistent') - projection = 'polar' - ax = projection_factory(projection, self, rect, **kwargs) - key = self._make_key(*args, **kwargs) - - # is now - - projection_class, kwargs, key = \ - process_projection_requirements(self, *args, **kwargs) - ax = projection_class(self, rect, **kwargs) - - This change means that third party objects can expose themselves as - Matplotlib axes by providing a ``_as_mpl_axes`` method. See - :ref:`adding-new-scales` for more detail. - -* A new keyword *extendfrac* in :meth:`~matplotlib.pyplot.colorbar` and - :class:`~matplotlib.colorbar.ColorbarBase` allows one to control the size of - the triangular minimum and maximum extensions on colorbars. - -* A new keyword *capthick* in :meth:`~matplotlib.pyplot.errorbar` has been - added as an intuitive alias to the *markeredgewidth* and *mew* keyword - arguments, which indirectly controlled the thickness of the caps on - the errorbars. For backwards compatibility, specifying either of the - original keyword arguments will override any value provided by - *capthick*. - -* Transform subclassing behaviour is now subtly changed. If your transform - implements a non-affine transformation, then it should override the - ``transform_non_affine`` method, rather than the generic ``transform`` method. - Previously transforms would define ``transform`` and then copy the - method into ``transform_non_affine``:: - - class MyTransform(mtrans.Transform): - def transform(self, xy): - ... - transform_non_affine = transform - - - This approach will no longer function correctly and should be changed to:: - - class MyTransform(mtrans.Transform): - def transform_non_affine(self, xy): - ... - - -* Artists no longer have ``x_isdata`` or ``y_isdata`` attributes; instead - any artist's transform can be interrogated with - ``artist_instance.get_transform().contains_branch(ax.transData)`` - -* Lines added to an axes now take into account their transform when updating the - data and view limits. This means transforms can now be used as a pre-transform. - For instance:: - - >>> import matplotlib.pyplot as plt - >>> import matplotlib.transforms as mtrans - >>> ax = plt.axes() - >>> ax.plot(range(10), transform=mtrans.Affine2D().scale(10) + ax.transData) - >>> print(ax.viewLim) - Bbox('array([[ 0., 0.],\n [ 90., 90.]])') - -* One can now easily get a transform which goes from one transform's coordinate - system to another, in an optimized way, using the new subtract method on a - transform. For instance, to go from data coordinates to axes coordinates:: - - >>> import matplotlib.pyplot as plt - >>> ax = plt.axes() - >>> data2ax = ax.transData - ax.transAxes - >>> print(ax.transData.depth, ax.transAxes.depth) - 3, 1 - >>> print(data2ax.depth) - 2 - - for versions before 1.2 this could only be achieved in a sub-optimal way, - using ``ax.transData + ax.transAxes.inverted()`` (depth is a new concept, - but had it existed it would return 4 for this example). - -* ``twinx`` and ``twiny`` now returns an instance of SubplotBase if - parent axes is an instance of SubplotBase. - -* All Qt3-based backends are now deprecated due to the lack of py3k bindings. - Qt and QtAgg backends will continue to work in v1.2.x for py2.6 - and py2.7. It is anticipated that the Qt3 support will be completely - removed for the next release. - -* :class:`~matplotlib.colors.ColorConverter`, - :class:`~matplotlib.colors.Colormap` and - :class:`~matplotlib.colors.Normalize` now subclasses ``object`` - -* ContourSet instances no longer have a ``transform`` attribute. Instead, - access the transform with the ``get_transform`` method. - -Changes in 1.1.x -================ - -* Added new :class:`matplotlib.sankey.Sankey` for generating Sankey diagrams. - -* In :meth:`~matplotlib.pyplot.imshow`, setting *interpolation* to 'nearest' - will now always mean that the nearest-neighbor interpolation is performed. - If you want the no-op interpolation to be performed, choose 'none'. - -* There were errors in how the tri-functions were handling input parameters - that had to be fixed. If your tri-plots are not working correctly anymore, - or you were working around apparent mistakes, please see issue #203 in the - github tracker. When in doubt, use kwargs. - -* The 'symlog' scale had some bad behavior in previous versions. This has now - been fixed and users should now be able to use it without frustrations. - The fixes did result in some minor changes in appearance for some users who - may have been depending on the bad behavior. - -* There is now a common set of markers for all plotting functions. Previously, - some markers existed only for :meth:`~matplotlib.pyplot.scatter` or just for - :meth:`~matplotlib.pyplot.plot`. This is now no longer the case. This merge - did result in a conflict. The string 'd' now means "thin diamond" while - 'D' will mean "regular diamond". - -Changes beyond 0.99.x -===================== - -* The default behavior of :meth:`matplotlib.axes.Axes.set_xlim`, - :meth:`matplotlib.axes.Axes.set_ylim`, and - :meth:`matplotlib.axes.Axes.axis`, and their corresponding - pyplot functions, has been changed: when view limits are - set explicitly with one of these methods, autoscaling is turned - off for the matching axis. A new *auto* kwarg is available to - control this behavior. The limit kwargs have been renamed to - *left* and *right* instead of *xmin* and *xmax*, and *bottom* - and *top* instead of *ymin* and *ymax*. The old names may still - be used, however. - -* There are five new Axes methods with corresponding pyplot - functions to facilitate autoscaling, tick location, and tick - label formatting, and the general appearance of ticks and - tick labels: - - + :meth:`matplotlib.axes.Axes.autoscale` turns autoscaling - on or off, and applies it. - - + :meth:`matplotlib.axes.Axes.margins` sets margins used to - autoscale the :attr:`matplotlib.axes.Axes.viewLim` based on - the :attr:`matplotlib.axes.Axes.dataLim`. - - + :meth:`matplotlib.axes.Axes.locator_params` allows one to - adjust axes locator parameters such as *nbins*. - - + :meth:`matplotlib.axes.Axes.ticklabel_format` is a convenience - method for controlling the :class:`matplotlib.ticker.ScalarFormatter` - that is used by default with linear axes. - - + :meth:`matplotlib.axes.Axes.tick_params` controls direction, size, - visibility, and color of ticks and their labels. - -* The :meth:`matplotlib.axes.Axes.bar` method accepts a *error_kw* - kwarg; it is a dictionary of kwargs to be passed to the - errorbar function. - -* The :meth:`matplotlib.axes.Axes.hist` *color* kwarg now accepts - a sequence of color specs to match a sequence of datasets. - -* The :class:`~matplotlib.collections.EllipseCollection` has been - changed in two ways: - - + There is a new *units* option, 'xy', that scales the ellipse with - the data units. This matches the :class:'~matplotlib.patches.Ellipse` - scaling. - - + The *height* and *width* kwargs have been changed to specify - the height and width, again for consistency with - :class:`~matplotlib.patches.Ellipse`, and to better match - their names; previously they specified the half-height and - half-width. - -* There is a new rc parameter ``axes.color_cycle``, and the color - cycle is now independent of the rc parameter ``lines.color``. - :func:`matplotlib.Axes.set_default_color_cycle` is deprecated. - -* You can now print several figures to one pdf file and modify the - document information dictionary of a pdf file. See the docstrings - of the class :class:`matplotlib.backends.backend_pdf.PdfPages` for - more information. - -* Removed configobj_ and `enthought.traits`_ packages, which are only - required by the experimental traited config and are somewhat out of - date. If needed, install them independently. - -.. _configobj: http://www.voidspace.org.uk/python/configobj.html -.. _`enthought.traits`: http://code.enthought.com/pages/traits.html - -* The new rc parameter ``savefig.extension`` sets the filename extension - that is used by :meth:`matplotlib.figure.Figure.savefig` if its *fname* - argument lacks an extension. - -* In an effort to simplify the backend API, all clipping rectangles - and paths are now passed in using GraphicsContext objects, even - on collections and images. Therefore:: - - draw_path_collection(self, master_transform, cliprect, clippath, - clippath_trans, paths, all_transforms, offsets, - offsetTrans, facecolors, edgecolors, linewidths, - linestyles, antialiaseds, urls) - - # is now - - draw_path_collection(self, gc, master_transform, paths, all_transforms, - offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls) - - - draw_quad_mesh(self, master_transform, cliprect, clippath, - clippath_trans, meshWidth, meshHeight, coordinates, - offsets, offsetTrans, facecolors, antialiased, - showedges) - - # is now - - draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, - coordinates, offsets, offsetTrans, facecolors, - antialiased, showedges) - - - draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None) - - # is now - - draw_image(self, gc, x, y, im) - -* There are four new Axes methods with corresponding pyplot - functions that deal with unstructured triangular grids: - - + :meth:`matplotlib.axes.Axes.tricontour` draws contour lines - on a triangular grid. - - + :meth:`matplotlib.axes.Axes.tricontourf` draws filled contours - on a triangular grid. - - + :meth:`matplotlib.axes.Axes.tripcolor` draws a pseudocolor - plot on a triangular grid. - - + :meth:`matplotlib.axes.Axes.triplot` draws a triangular grid - as lines and/or markers. - -Changes in 0.99 -=============== - -* pylab no longer provides a load and save function. These are - available in matplotlib.mlab, or you can use numpy.loadtxt and - numpy.savetxt for text files, or np.save and np.load for binary - numpy arrays. - -* User-generated colormaps can now be added to the set recognized - by :func:`matplotlib.cm.get_cmap`. Colormaps can be made the - default and applied to the current image using - :func:`matplotlib.pyplot.set_cmap`. - -* changed use_mrecords default to False in mlab.csv2rec since this is - partially broken - -* Axes instances no longer have a "frame" attribute. Instead, use the - new "spines" attribute. Spines is a dictionary where the keys are - the names of the spines (e.g., 'left','right' and so on) and the - values are the artists that draw the spines. For normal - (rectilinear) axes, these artists are Line2D instances. For other - axes (such as polar axes), these artists may be Patch instances. - -* Polar plots no longer accept a resolution kwarg. Instead, each Path - must specify its own number of interpolation steps. This is - unlikely to be a user-visible change -- if interpolation of data is - required, that should be done before passing it to Matplotlib. - -Changes for 0.98.x -================== -* psd(), csd(), and cohere() will now automatically wrap negative - frequency components to the beginning of the returned arrays. - This is much more sensible behavior and makes them consistent - with specgram(). The previous behavior was more of an oversight - than a design decision. - -* Added new keyword parameters *nonposx*, *nonposy* to - :class:`matplotlib.axes.Axes` methods that set log scale - parameters. The default is still to mask out non-positive - values, but the kwargs accept 'clip', which causes non-positive - values to be replaced with a very small positive value. - -* Added new :func:`matplotlib.pyplot.fignum_exists` and - :func:`matplotlib.pyplot.get_fignums`; they merely expose - information that had been hidden in :mod:`matplotlib._pylab_helpers`. - -* Deprecated numerix package. - -* Added new :func:`matplotlib.image.imsave` and exposed it to the - :mod:`matplotlib.pyplot` interface. - -* Remove support for pyExcelerator in exceltools -- use xlwt - instead - -* Changed the defaults of acorr and xcorr to use usevlines=True, - maxlags=10 and normed=True since these are the best defaults - -* Following keyword parameters for :class:`matplotlib.label.Label` are now - deprecated and new set of parameters are introduced. The new parameters - are given as a fraction of the font-size. Also, *scatteryoffsets*, - *fancybox* and *columnspacing* are added as keyword parameters. - - ================ ================ - Deprecated New - ================ ================ - pad borderpad - labelsep labelspacing - handlelen handlelength - handlestextsep handletextpad - axespad borderaxespad - ================ ================ - - -* Removed the configobj and experimental traits rc support - -* Modified :func:`matplotlib.mlab.psd`, :func:`matplotlib.mlab.csd`, - :func:`matplotlib.mlab.cohere`, and :func:`matplotlib.mlab.specgram` - to scale one-sided densities by a factor of 2. Also, optionally - scale the densities by the sampling frequency, which gives true values - of densities that can be integrated by the returned frequency values. - This also gives better MATLAB compatibility. The corresponding - :class:`matplotlib.axes.Axes` methods and :mod:`matplotlib.pyplot` - functions were updated as well. - -* Font lookup now uses a nearest-neighbor approach rather than an - exact match. Some fonts may be different in plots, but should be - closer to what was requested. - -* :meth:`matplotlib.axes.Axes.set_xlim`, - :meth:`matplotlib.axes.Axes.set_ylim` now return a copy of the - :attr:`viewlim` array to avoid modify-in-place surprises. - -* :meth:`matplotlib.afm.AFM.get_fullname` and - :meth:`matplotlib.afm.AFM.get_familyname` no longer raise an - exception if the AFM file does not specify these optional - attributes, but returns a guess based on the required FontName - attribute. - -* Changed precision kwarg in :func:`matplotlib.pyplot.spy`; default is - 0, and the string value 'present' is used for sparse arrays only to - show filled locations. - -* :class:`matplotlib.collections.EllipseCollection` added. - -* Added ``angles`` kwarg to :func:`matplotlib.pyplot.quiver` for more - flexible specification of the arrow angles. - -* Deprecated (raise NotImplementedError) all the mlab2 functions from - :mod:`matplotlib.mlab` out of concern that some of them were not - clean room implementations. - -* Methods :meth:`matplotlib.collections.Collection.get_offsets` and - :meth:`matplotlib.collections.Collection.set_offsets` added to - :class:`~matplotlib.collections.Collection` base class. - -* :attr:`matplotlib.figure.Figure.figurePatch` renamed - :attr:`matplotlib.figure.Figure.patch`; - :attr:`matplotlib.axes.Axes.axesPatch` renamed - :attr:`matplotlib.axes.Axes.patch`; - :attr:`matplotlib.axes.Axes.axesFrame` renamed - :attr:`matplotlib.axes.Axes.frame`. - :meth:`matplotlib.axes.Axes.get_frame`, which returns - :attr:`matplotlib.axes.Axes.patch`, is deprecated. - -* Changes in the :class:`matplotlib.contour.ContourLabeler` attributes - (:func:`matplotlib.pyplot.clabel` function) so that they all have a - form like ``.labelAttribute``. The three attributes that are most - likely to be used by end users, ``.cl``, ``.cl_xy`` and - ``.cl_cvalues`` have been maintained for the moment (in addition to - their renamed versions), but they are deprecated and will eventually - be removed. - -* Moved several functions in :mod:`matplotlib.mlab` and - :mod:`matplotlib.cbook` into a separate module - :mod:`matplotlib.numerical_methods` because they were unrelated to - the initial purpose of mlab or cbook and appeared more coherent - elsewhere. - -Changes for 0.98.1 -================== - -* Removed broken :mod:`matplotlib.axes3d` support and replaced it with - a non-implemented error pointing to 0.91.x - -Changes for 0.98.0 -================== - -* :func:`matplotlib.image.imread` now no longer always returns RGBA data---if - the image is luminance or RGB, it will return a MxN or MxNx3 array - if possible. Also uint8 is no longer always forced to float. - -* Rewrote the :class:`matplotlib.cm.ScalarMappable` callback - infrastructure to use :class:`matplotlib.cbook.CallbackRegistry` - rather than custom callback handling. Any users of - :meth:`matplotlib.cm.ScalarMappable.add_observer` of the - :class:`~matplotlib.cm.ScalarMappable` should use the - :attr:`matplotlib.cm.ScalarMappable.callbacks` - :class:`~matplotlib.cbook.CallbackRegistry` instead. - -* New axes function and Axes method provide control over the plot - color cycle: :func:`matplotlib.axes.set_default_color_cycle` and - :meth:`matplotlib.axes.Axes.set_color_cycle`. - -* Matplotlib now requires Python 2.4, so :mod:`matplotlib.cbook` will - no longer provide :class:`set`, :func:`enumerate`, :func:`reversed` - or :func:`izip` compatibility functions. - -* In Numpy 1.0, bins are specified by the left edges only. The axes - method :meth:`matplotlib.axes.Axes.hist` now uses future Numpy 1.3 - semantics for histograms. Providing ``binedges``, the last value gives - the upper-right edge now, which was implicitly set to +infinity in - Numpy 1.0. This also means that the last bin doesn't contain upper - outliers any more by default. - -* New axes method and pyplot function, - :func:`~matplotlib.pyplot.hexbin`, is an alternative to - :func:`~matplotlib.pyplot.scatter` for large datasets. It makes - something like a :func:`~matplotlib.pyplot.pcolor` of a 2-D - histogram, but uses hexagonal bins. - -* New kwarg, ``symmetric``, in :class:`matplotlib.ticker.MaxNLocator` - allows one require an axis to be centered around zero. - -* Toolkits must now be imported from ``mpl_toolkits`` (not ``matplotlib.toolkits``) - -Notes about the transforms refactoring --------------------------------------- - -A major new feature of the 0.98 series is a more flexible and -extensible transformation infrastructure, written in Python/Numpy -rather than a custom C extension. - -The primary goal of this refactoring was to make it easier to -extend matplotlib to support new kinds of projections. This is -mostly an internal improvement, and the possible user-visible -changes it allows are yet to come. - -See :mod:`matplotlib.transforms` for a description of the design of -the new transformation framework. - -For efficiency, many of these functions return views into Numpy -arrays. This means that if you hold on to a reference to them, -their contents may change. If you want to store a snapshot of -their current values, use the Numpy array method copy(). - -The view intervals are now stored only in one place -- in the -:class:`matplotlib.axes.Axes` instance, not in the locator instances -as well. This means locators must get their limits from their -:class:`matplotlib.axis.Axis`, which in turn looks up its limits from -the :class:`~matplotlib.axes.Axes`. If a locator is used temporarily -and not assigned to an Axis or Axes, (e.g., in -:mod:`matplotlib.contour`), a dummy axis must be created to store its -bounds. Call :meth:`matplotlib.ticker.Locator.create_dummy_axis` to -do so. - -The functionality of :class:`Pbox` has been merged with -:class:`~matplotlib.transforms.Bbox`. Its methods now all return -copies rather than modifying in place. - -The following lists many of the simple changes necessary to update -code from the old transformation framework to the new one. In -particular, methods that return a copy are named with a verb in the -past tense, whereas methods that alter an object in place are named -with a verb in the present tense. - -:mod:`matplotlib.transforms` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -:meth:`Bbox.get_bounds` :attr:`transforms.Bbox.bounds` ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.width` :attr:`transforms.Bbox.width` ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.height` :attr:`transforms.Bbox.height` ------------------------------------------------------------- ------------------------------------------------------------ -`Bbox.intervalx().get_bounds()` :attr:`transforms.Bbox.intervalx` -`Bbox.intervalx().set_bounds()` [:attr:`Bbox.intervalx` is now a property.] ------------------------------------------------------------- ------------------------------------------------------------ -`Bbox.intervaly().get_bounds()` :attr:`transforms.Bbox.intervaly` -`Bbox.intervaly().set_bounds()` [:attr:`Bbox.intervaly` is now a property.] ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.xmin` :attr:`transforms.Bbox.x0` or - :attr:`transforms.Bbox.xmin` [1]_ ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.ymin` :attr:`transforms.Bbox.y0` or - :attr:`transforms.Bbox.ymin` [1]_ ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.xmax` :attr:`transforms.Bbox.x1` or - :attr:`transforms.Bbox.xmax` [1]_ ------------------------------------------------------------- ------------------------------------------------------------ -:meth:`Bbox.ymax` :attr:`transforms.Bbox.y1` or - :attr:`transforms.Bbox.ymax` [1]_ ------------------------------------------------------------- ------------------------------------------------------------ -`Bbox.overlaps(bboxes)` `Bbox.count_overlaps(bboxes)` ------------------------------------------------------------- ------------------------------------------------------------ -`bbox_all(bboxes)` `Bbox.union(bboxes)` - [:meth:`transforms.Bbox.union` is a staticmethod.] ------------------------------------------------------------- ------------------------------------------------------------ -`lbwh_to_bbox(l, b, w, h)` `Bbox.from_bounds(x0, y0, w, h)` - [:meth:`transforms.Bbox.from_bounds` is a staticmethod.] ------------------------------------------------------------- ------------------------------------------------------------ -`inverse_transform_bbox(trans, bbox)` `Bbox.inverse_transformed(trans)` ------------------------------------------------------------- ------------------------------------------------------------ -`Interval.contains_open(v)` `interval_contains_open(tuple, v)` ------------------------------------------------------------- ------------------------------------------------------------ -`Interval.contains(v)` `interval_contains(tuple, v)` ------------------------------------------------------------- ------------------------------------------------------------ -`identity_transform()` :class:`matplotlib.transforms.IdentityTransform` ------------------------------------------------------------- ------------------------------------------------------------ -`blend_xy_sep_transform(xtrans, ytrans)` `blended_transform_factory(xtrans, ytrans)` ------------------------------------------------------------- ------------------------------------------------------------ -`scale_transform(xs, ys)` `Affine2D().scale(xs[, ys])` ------------------------------------------------------------- ------------------------------------------------------------ -`get_bbox_transform(boxin, boxout)` `BboxTransform(boxin, boxout)` or - `BboxTransformFrom(boxin)` or - `BboxTransformTo(boxout)` ------------------------------------------------------------- ------------------------------------------------------------ -`Transform.seq_xy_tup(points)` `Transform.transform(points)` ------------------------------------------------------------- ------------------------------------------------------------ -`Transform.inverse_xy_tup(points)` `Transform.inverted().transform(points)` -============================================================ ============================================================ - -.. [1] The :class:`~matplotlib.transforms.Bbox` is bound by the points - (x0, y0) to (x1, y1) and there is no defined order to these points, - that is, x0 is not necessarily the left edge of the box. To get - the left edge of the :class:`Bbox`, use the read-only property - :attr:`~matplotlib.transforms.Bbox.xmin`. - -:mod:`matplotlib.axes` -~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`Axes.get_position()` :meth:`matplotlib.axes.Axes.get_position` [2]_ ------------------------------------------------------------- ------------------------------------------------------------ -`Axes.set_position()` :meth:`matplotlib.axes.Axes.set_position` [3]_ ------------------------------------------------------------- ------------------------------------------------------------ -`Axes.toggle_log_lineary()` :meth:`matplotlib.axes.Axes.set_yscale` [4]_ ------------------------------------------------------------- ------------------------------------------------------------ -`Subplot` class removed. -============================================================ ============================================================ - -The :class:`Polar` class has moved to :mod:`matplotlib.projections.polar`. - -.. [2] :meth:`matplotlib.axes.Axes.get_position` used to return a list - of points, now it returns a :class:`matplotlib.transforms.Bbox` - instance. - -.. [3] :meth:`matplotlib.axes.Axes.set_position` now accepts either - four scalars or a :class:`matplotlib.transforms.Bbox` instance. - -.. [4] Since the recfactoring allows for more than two scale types - ('log' or 'linear'), it no longer makes sense to have a toggle. - `Axes.toggle_log_lineary()` has been removed. - -:mod:`matplotlib.artist` -~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`Artist.set_clip_path(path)` `Artist.set_clip_path(path, transform)` [5]_ -============================================================ ============================================================ - -.. [5] :meth:`matplotlib.artist.Artist.set_clip_path` now accepts a - :class:`matplotlib.path.Path` instance and a - :class:`matplotlib.transforms.Transform` that will be applied to - the path immediately before clipping. - -:mod:`matplotlib.collections` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`linestyle` `linestyles` [6]_ -============================================================ ============================================================ - -.. [6] Linestyles are now treated like all other collection - attributes, i.e. a single value or multiple values may be - provided. - -:mod:`matplotlib.colors` -~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`ColorConvertor.to_rgba_list(c)` `ColorConvertor.to_rgba_array(c)` - [:meth:`matplotlib.colors.ColorConvertor.to_rgba_array` - returns an Nx4 Numpy array of RGBA color quadruples.] -============================================================ ============================================================ - -:mod:`matplotlib.contour` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`Contour._segments` :meth:`matplotlib.contour.Contour.get_paths`` [Returns a - list of :class:`matplotlib.path.Path` instances.] -============================================================ ============================================================ - -:mod:`matplotlib.figure` -~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`Figure.dpi.get()` / `Figure.dpi.set()` :attr:`matplotlib.figure.Figure.dpi` *(a property)* -============================================================ ============================================================ - -:mod:`matplotlib.patches` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`Patch.get_verts()` :meth:`matplotlib.patches.Patch.get_path` [Returns a - :class:`matplotlib.path.Path` instance] -============================================================ ============================================================ - -:mod:`matplotlib.backend_bases` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============================================================ ============================================================ -Old method New method -============================================================ ============================================================ -`GraphicsContext.set_clip_rectangle(tuple)` `GraphicsContext.set_clip_rectangle(bbox)` ------------------------------------------------------------- ------------------------------------------------------------ -`GraphicsContext.get_clip_path()` `GraphicsContext.get_clip_path()` [7]_ ------------------------------------------------------------- ------------------------------------------------------------ -`GraphicsContext.set_clip_path()` `GraphicsContext.set_clip_path()` [8]_ -============================================================ ============================================================ - -:class:`~matplotlib.backend_bases.RendererBase` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -New methods: - - * :meth:`draw_path(self, gc, path, transform, rgbFace) - ` - - * :meth:`draw_markers(self, gc, marker_path, marker_trans, path, - trans, rgbFace) - ` - - * :meth:`draw_path_collection(self, master_transform, cliprect, - clippath, clippath_trans, paths, all_transforms, offsets, - offsetTrans, facecolors, edgecolors, linewidths, linestyles, - antialiaseds) - ` - *[optional]* - -Changed methods: - - * `draw_image(self, x, y, im, bbox)` is now - :meth:`draw_image(self, x, y, im, bbox, clippath, clippath_trans) - ` - -Removed methods: - - * `draw_arc` - - * `draw_line_collection` - - * `draw_line` - - * `draw_lines` - - * `draw_point` - - * `draw_quad_mesh` - - * `draw_poly_collection` - - * `draw_polygon` - - * `draw_rectangle` - - * `draw_regpoly_collection` - -.. [7] :meth:`matplotlib.backend_bases.GraphicsContext.get_clip_path` - returns a tuple of the form (*path*, *affine_transform*), where - *path* is a :class:`matplotlib.path.Path` instance and - *affine_transform* is a :class:`matplotlib.transforms.Affine2D` - instance. - -.. [8] :meth:`matplotlib.backend_bases.GraphicsContext.set_clip_path` - now only accepts a :class:`matplotlib.transforms.TransformedPath` - instance. - -Changes for 0.91.2 -================== - -* For :func:`csv2rec`, checkrows=0 is the new default indicating all rows - will be checked for type inference - -* A warning is issued when an image is drawn on log-scaled axes, since - it will not log-scale the image data. - -* Moved :func:`rec2gtk` to :mod:`matplotlib.toolkits.gtktools` - -* Moved :func:`rec2excel` to :mod:`matplotlib.toolkits.exceltools` - -* Removed, dead/experimental ExampleInfo, Namespace and Importer - code from :mod:`matplotlib.__init__` - -Changes for 0.91.1 -================== - -Changes for 0.91.0 -================== - -* Changed :func:`cbook.is_file_like` to - :func:`cbook.is_writable_file_like` and corrected behavior. - -* Added ax kwarg to :func:`pyplot.colorbar` and - :meth:`Figure.colorbar` so that one can specify the axes object from - which space for the colorbar is to be taken, if one does not want to - make the colorbar axes manually. - -* Changed :func:`cbook.reversed` so it yields a tuple rather than a - (index, tuple). This agrees with the python reversed builtin, - and cbook only defines reversed if python doesn't provide the - builtin. - -* Made skiprows=1 the default on :func:`csv2rec` - -* The gd and paint backends have been deleted. - -* The errorbar method and function now accept additional kwargs - so that upper and lower limits can be indicated by capping the - bar with a caret instead of a straight line segment. - -* The :mod:`matplotlib.dviread` file now has a parser for files like - psfonts.map and pdftex.map, to map TeX font names to external files. - -* The file :mod:`matplotlib.type1font` contains a new class for Type 1 - fonts. Currently it simply reads pfa and pfb format files and - stores the data in a way that is suitable for embedding in pdf - files. In the future the class might actually parse the font to - allow e.g., subsetting. - -* :mod:`matplotlib.FT2Font` now supports :meth:`FT_Attach_File`. In - practice this can be used to read an afm file in addition to a - pfa/pfb file, to get metrics and kerning information for a Type 1 - font. - -* The :class:`AFM` class now supports querying CapHeight and stem - widths. The get_name_char method now has an isord kwarg like - get_width_char. - -* Changed :func:`pcolor` default to shading='flat'; but as noted now in the - docstring, it is preferable to simply use the edgecolor kwarg. - -* The mathtext font commands (``\cal``, ``\rm``, ``\it``, ``\tt``) now - behave as TeX does: they are in effect until the next font change - command or the end of the grouping. Therefore uses of ``$\cal{R}$`` - should be changed to ``${\cal R}$``. Alternatively, you may use the - new LaTeX-style font commands (``\mathcal``, ``\mathrm``, - ``\mathit``, ``\mathtt``) which do affect the following group, - e.g., ``$\mathcal{R}$``. - -* Text creation commands have a new default linespacing and a new - ``linespacing`` kwarg, which is a multiple of the maximum vertical - extent of a line of ordinary text. The default is 1.2; - ``linespacing=2`` would be like ordinary double spacing, for example. - -* Changed default kwarg in - :meth:`matplotlib.colors.Normalize.__init__`` to ``clip=False``; - clipping silently defeats the purpose of the special over, under, - and bad values in the colormap, thereby leading to unexpected - behavior. The new default should reduce such surprises. - -* Made the emit property of :meth:`~matplotlib.axes.Axes.set_xlim` and - :meth:`~matplotlib.axes.Axes.set_ylim` ``True`` by default; removed - the Axes custom callback handling into a 'callbacks' attribute which - is a :class:`~matplotlib.cbook.CallbackRegistry` instance. This now - supports the 'xlim_changed' and 'ylim_changed' Axes events. - -Changes for 0.90.1 -================== - -:: - - The file dviread.py has a (very limited and fragile) dvi reader - for usetex support. The API might change in the future so don't - depend on it yet. - - Removed deprecated support for a float value as a gray-scale; - now it must be a string, like '0.5'. Added alpha kwarg to - ColorConverter.to_rgba_list. - - New method set_bounds(vmin, vmax) for formatters, locators sets - the viewInterval and dataInterval from floats. - - Removed deprecated colorbar_classic. - - Line2D.get_xdata and get_ydata valid_only=False kwarg is replaced - by orig=True. When True, it returns the original data, otherwise - the processed data (masked, converted) - - Some modifications to the units interface. - units.ConversionInterface.tickers renamed to - units.ConversionInterface.axisinfo and it now returns a - units.AxisInfo object rather than a tuple. This will make it - easier to add axis info functionality (e.g., I added a default label - on this iteration) w/o having to change the tuple length and hence - the API of the client code every time new functionality is added. - Also, units.ConversionInterface.convert_to_value is now simply - named units.ConversionInterface.convert. - - Axes.errorbar uses Axes.vlines and Axes.hlines to draw its error - limits int he vertical and horizontal direction. As you'll see - in the changes below, these functions now return a LineCollection - rather than a list of lines. The new return signature for - errorbar is ylins, caplines, errorcollections where - errorcollections is a xerrcollection, yerrcollection - - Axes.vlines and Axes.hlines now create and returns a LineCollection, not a list - of lines. This is much faster. The kwarg signature has changed, - so consult the docs - - MaxNLocator accepts a new Boolean kwarg ('integer') to force - ticks to integer locations. - - Commands that pass an argument to the Text constructor or to - Text.set_text() now accept any object that can be converted - with '%s'. This affects xlabel(), title(), etc. - - Barh now takes a **kwargs dict instead of most of the old - arguments. This helps ensure that bar and barh are kept in sync, - but as a side effect you can no longer pass e.g., color as a - positional argument. - - ft2font.get_charmap() now returns a dict that maps character codes - to glyph indices (until now it was reversed) - - Moved data files into lib/matplotlib so that setuptools' develop - mode works. Re-organized the mpl-data layout so that this source - structure is maintained in the installation. (i.e., the 'fonts' and - 'images' sub-directories are maintained in site-packages.). - Suggest removing site-packages/matplotlib/mpl-data and - ~/.matplotlib/ttffont.cache before installing - -Changes for 0.90.0 -================== - -:: - - All artists now implement a "pick" method which users should not - call. Rather, set the "picker" property of any artist you want to - pick on (the epsilon distance in points for a hit test) and - register with the "pick_event" callback. See - examples/pick_event_demo.py for details - - Bar, barh, and hist have "log" binary kwarg: log=True - sets the ordinate to a log scale. - - Boxplot can handle a list of vectors instead of just - an array, so vectors can have different lengths. - - Plot can handle 2-D x and/or y; it plots the columns. - - Added linewidth kwarg to bar and barh. - - Made the default Artist._transform None (rather than invoking - identity_transform for each artist only to have it overridden - later). Use artist.get_transform() rather than artist._transform, - even in derived classes, so that the default transform will be - created lazily as needed - - New LogNorm subclass of Normalize added to colors.py. - All Normalize subclasses have new inverse() method, and - the __call__() method has a new clip kwarg. - - Changed class names in colors.py to match convention: - normalize -> Normalize, no_norm -> NoNorm. Old names - are still available for now. - - Removed obsolete pcolor_classic command and method. - - Removed lineprops and markerprops from the Annotation code and - replaced them with an arrow configurable with kwarg arrowprops. - See examples/annotation_demo.py - JDH - -Changes for 0.87.7 -================== - -:: - - Completely reworked the annotations API because I found the old - API cumbersome. The new design is much more legible and easy to - read. See matplotlib.text.Annotation and - examples/annotation_demo.py - - markeredgecolor and markerfacecolor cannot be configured in - matplotlibrc any more. Instead, markers are generally colored - automatically based on the color of the line, unless marker colors - are explicitly set as kwargs - NN - - Changed default comment character for load to '#' - JDH - - math_parse_s_ft2font_svg from mathtext.py & mathtext2.py now returns - width, height, svg_elements. svg_elements is an instance of Bunch ( - cmbook.py) and has the attributes svg_glyphs and svg_lines, which are both - lists. - - Renderer.draw_arc now takes an additional parameter, rotation. - It specifies to draw the artist rotated in degrees anti- - clockwise. It was added for rotated ellipses. - - Renamed Figure.set_figsize_inches to Figure.set_size_inches to - better match the get method, Figure.get_size_inches. - - Removed the copy_bbox_transform from transforms.py; added - shallowcopy methods to all transforms. All transforms already - had deepcopy methods. - - FigureManager.resize(width, height): resize the window - specified in pixels - - barh: x and y args have been renamed to width and bottom - respectively, and their order has been swapped to maintain - a (position, value) order. - - bar and barh: now accept kwarg 'edgecolor'. - - bar and barh: The left, height, width and bottom args can - now all be scalars or sequences; see docstring. - - barh: now defaults to edge aligned instead of center - aligned bars - - bar, barh and hist: Added a keyword arg 'align' that - controls between edge or center bar alignment. - - Collections: PolyCollection and LineCollection now accept - vertices or segments either in the original form [(x,y), - (x,y), ...] or as a 2D numerix array, with X as the first column - and Y as the second. Contour and quiver output the numerix - form. The transforms methods Bbox.update() and - Transformation.seq_xy_tups() now accept either form. - - Collections: LineCollection is now a ScalarMappable like - PolyCollection, etc. - - Specifying a grayscale color as a float is deprecated; use - a string instead, e.g., 0.75 -> '0.75'. - - Collections: initializers now accept any mpl color arg, or - sequence of such args; previously only a sequence of rgba - tuples was accepted. - - Colorbar: completely new version and api; see docstring. The - original version is still accessible as colorbar_classic, but - is deprecated. - - Contourf: "extend" kwarg replaces "clip_ends"; see docstring. - Masked array support added to pcolormesh. - - Modified aspect-ratio handling: - Removed aspect kwarg from imshow - Axes methods: - set_aspect(self, aspect, adjustable=None, anchor=None) - set_adjustable(self, adjustable) - set_anchor(self, anchor) - Pylab interface: - axis('image') - - Backend developers: ft2font's load_char now takes a flags - argument, which you can OR together from the LOAD_XXX - constants. - -Changes for 0.86 -================ - -:: - - Matplotlib data is installed into the matplotlib module. - This is similar to package_data. This should get rid of - having to check for many possibilities in _get_data_path(). - The MATPLOTLIBDATA env key is still checked first to allow - for flexibility. - - 1) Separated the color table data from cm.py out into - a new file, _cm.py, to make it easier to find the actual - code in cm.py and to add new colormaps. Everything - from _cm.py is imported by cm.py, so the split should be - transparent. - 2) Enabled automatic generation of a colormap from - a list of colors in contour; see modified - examples/contour_demo.py. - 3) Support for imshow of a masked array, with the - ability to specify colors (or no color at all) for - masked regions, and for regions that are above or - below the normally mapped region. See - examples/image_masked.py. - 4) In support of the above, added two new classes, - ListedColormap, and no_norm, to colors.py, and modified - the Colormap class to include common functionality. Added - a clip kwarg to the normalize class. - -Changes for 0.85 -================ - -:: - - Made xtick and ytick separate props in rc - - made pos=None the default for tick formatters rather than 0 to - indicate "not supplied" - - Removed "feature" of minor ticks which prevents them from - overlapping major ticks. Often you want major and minor ticks at - the same place, and can offset the major ticks with the pad. This - could be made configurable - - Changed the internal structure of contour.py to a more OO style. - Calls to contour or contourf in axes.py or pylab.py now return - a ContourSet object which contains references to the - LineCollections or PolyCollections created by the call, - as well as the configuration variables that were used. - The ContourSet object is a "mappable" if a colormap was used. - - Added a clip_ends kwarg to contourf. From the docstring: - * clip_ends = True - If False, the limits for color scaling are set to the - minimum and maximum contour levels. - True (default) clips the scaling limits. Example: - if the contour boundaries are V = [-100, 2, 1, 0, 1, 2, 100], - then the scaling limits will be [-100, 100] if clip_ends - is False, and [-3, 3] if clip_ends is True. - Added kwargs linewidths, antialiased, and nchunk to contourf. These - are experimental; see the docstring. - - Changed Figure.colorbar(): - kw argument order changed; - if mappable arg is a non-filled ContourSet, colorbar() shows - lines instead hof polygons. - if mappable arg is a filled ContourSet with clip_ends=True, - the endpoints are not labelled, so as to give the - correct impression of open-endedness. - - Changed LineCollection.get_linewidths to get_linewidth, for - consistency. - - -Changes for 0.84 -================ - -:: - - Unified argument handling between hlines and vlines. Both now - take optionally a fmt argument (as in plot) and a keyword args - that can be passed onto Line2D. - - Removed all references to "data clipping" in rc and lines.py since - these were not used and not optimized. I'm sure they'll be - resurrected later with a better implementation when needed. - - 'set' removed - no more deprecation warnings. Use 'setp' instead. - - Backend developers: Added flipud method to image and removed it - from to_str. Removed origin kwarg from backend.draw_image. - origin is handled entirely by the frontend now. - -Changes for 0.83 -================ - -:: - - - Made HOME/.matplotlib the new config dir where the matplotlibrc - file, the ttf.cache, and the tex.cache live. The new default - filenames in .matplotlib have no leading dot and are not hidden. - e.g., the new names are matplotlibrc, tex.cache, and ttffont.cache. - This is how ipython does it so it must be right. - - If old files are found, a warning is issued and they are moved to - the new location. - - - backends/__init__.py no longer imports new_figure_manager, - draw_if_interactive and show from the default backend, but puts - these imports into a call to pylab_setup. Also, the Toolbar is no - longer imported from WX/WXAgg. New usage: - - from backends import pylab_setup - new_figure_manager, draw_if_interactive, show = pylab_setup() - - - Moved Figure.get_width_height() to FigureCanvasBase. It now - returns int instead of float. - -Changes for 0.82 -================ - -:: - - - toolbar import change in GTKAgg, GTKCairo and WXAgg - - - Added subplot config tool to GTK* backends -- note you must now - import the NavigationToolbar2 from your backend of choice rather - than from backend_gtk because it needs to know about the backend - specific canvas -- see examples/embedding_in_gtk2.py. Ditto for - wx backend -- see examples/embedding_in_wxagg.py - - - - hist bin change - - Sean Richards notes there was a problem in the way we created - the binning for histogram, which made the last bin - underrepresented. From his post: - - I see that hist uses the linspace function to create the bins - and then uses searchsorted to put the values in their correct - bin. That's all good but I am confused over the use of linspace - for the bin creation. I wouldn't have thought that it does - what is needed, to quote the docstring it creates a "Linear - spaced array from min to max". For it to work correctly - shouldn't the values in the bins array be the same bound for - each bin? (i.e. each value should be the lower bound of a - bin). To provide the correct bins for hist would it not be - something like - - def bins(xmin, xmax, N): - if N==1: return xmax - dx = (xmax-xmin)/N # instead of N-1 - return xmin + dx*arange(N) - - - This suggestion is implemented in 0.81. My test script with these - changes does not reveal any bias in the binning - - from matplotlib.numerix.mlab import randn, rand, zeros, Float - from matplotlib.mlab import hist, mean - - Nbins = 50 - Ntests = 200 - results = zeros((Ntests,Nbins), typecode=Float) - for i in range(Ntests): - print 'computing', i - x = rand(10000) - n, bins = hist(x, Nbins) - results[i] = n - print mean(results) - - -Changes for 0.81 -================ - -:: - - - pylab and artist "set" functions renamed to setp to avoid clash - with python2.4 built-in set. Current version will issue a - deprecation warning which will be removed in future versions - - - imshow interpolation arguments changes for advanced interpolation - schemes. See help imshow, particularly the interpolation, - filternorm and filterrad kwargs - - - Support for masked arrays has been added to the plot command and - to the Line2D object. Only the valid points are plotted. A - "valid_only" kwarg was added to the get_xdata() and get_ydata() - methods of Line2D; by default it is False, so that the original - data arrays are returned. Setting it to True returns the plottable - points. - - - contour changes: - - Masked arrays: contour and contourf now accept masked arrays as - the variable to be contoured. Masking works correctly for - contour, but a bug remains to be fixed before it will work for - contourf. The "badmask" kwarg has been removed from both - functions. - - Level argument changes: - - Old version: a list of levels as one of the positional - arguments specified the lower bound of each filled region; the - upper bound of the last region was taken as a very large - number. Hence, it was not possible to specify that z values - between 0 and 1, for example, be filled, and that values - outside that range remain unfilled. - - New version: a list of N levels is taken as specifying the - boundaries of N-1 z ranges. Now the user has more control over - what is colored and what is not. Repeated calls to contourf - (with different colormaps or color specifications, for example) - can be used to color different ranges of z. Values of z - outside an expected range are left uncolored. - - Example: - Old: contourf(z, [0, 1, 2]) would yield 3 regions: 0-1, 1-2, and >2. - New: it would yield 2 regions: 0-1, 1-2. If the same 3 regions were - desired, the equivalent list of levels would be [0, 1, 2, - 1e38]. - -Changes for 0.80 -================ - -:: - - - xlim/ylim/axis always return the new limits regardless of - arguments. They now take kwargs which allow you to selectively - change the upper or lower limits while leaving unnamed limits - unchanged. See help(xlim) for example - -Changes for 0.73 -================ - -:: - - - Removed deprecated ColormapJet and friends - - - Removed all error handling from the verbose object - - - figure num of zero is now allowed - -Changes for 0.72 -================ - -:: - - - Line2D, Text, and Patch copy_properties renamed update_from and - moved into artist base class - - - LineCollecitons.color renamed to LineCollections.set_color for - consistency with set/get introspection mechanism, - - - pylab figure now defaults to num=None, which creates a new figure - with a guaranteed unique number - - - contour method syntax changed - now it is MATLAB compatible - - unchanged: contour(Z) - old: contour(Z, x=Y, y=Y) - new: contour(X, Y, Z) - - see http://matplotlib.sf.net/matplotlib.pylab.html#-contour - - - - Increased the default resolution for save command. - - - Renamed the base attribute of the ticker classes to _base to avoid conflict - with the base method. Sitt for subs - - - subs=none now does autosubbing in the tick locator. - - - New subplots that overlap old will delete the old axes. If you - do not want this behavior, use fig.add_subplot or the axes - command - -Changes for 0.71 -================ - -:: - - Significant numerix namespace changes, introduced to resolve - namespace clashes between python built-ins and mlab names. - Refactored numerix to maintain separate modules, rather than - folding all these names into a single namespace. See the following - mailing list threads for more information and background - - http://sourceforge.net/mailarchive/forum.php?thread_id=6398890&forum_id=36187 - http://sourceforge.net/mailarchive/forum.php?thread_id=6323208&forum_id=36187 - - - OLD usage - - from matplotlib.numerix import array, mean, fft - - NEW usage - - from matplotlib.numerix import array - from matplotlib.numerix.mlab import mean - from matplotlib.numerix.fft import fft - - numerix dir structure mirrors numarray (though it is an incomplete - implementation) - - numerix - numerix/mlab - numerix/linear_algebra - numerix/fft - numerix/random_array - - but of course you can use 'numerix : Numeric' and still get the - symbols. - - pylab still imports most of the symbols from Numerix, MLab, fft, - etc, but is more cautious. For names that clash with python names - (min, max, sum), pylab keeps the builtins and provides the numeric - versions with an a* prefix, e.g., (amin, amax, asum) - -Changes for 0.70 -================ - -:: - - MplEvent factored into a base class Event and derived classes - MouseEvent and KeyEvent - - Removed definct set_measurement in wx toolbar - -Changes for 0.65.1 -================== - -:: - - removed add_axes and add_subplot from backend_bases. Use - figure.add_axes and add_subplot instead. The figure now manages the - current axes with gca and sca for get and set current axes. If you - have code you are porting which called, e.g., figmanager.add_axes, you - can now simply do figmanager.canvas.figure.add_axes. - -Changes for 0.65 -================ - -:: - - - mpl_connect and mpl_disconnect in the MATLAB interface renamed to - connect and disconnect - - Did away with the text methods for angle since they were ambiguous. - fontangle could mean fontstyle (obligue, etc) or the rotation of the - text. Use style and rotation instead. - -Changes for 0.63 -================ - -:: - - Dates are now represented internally as float days since 0001-01-01, - UTC. - - All date tickers and formatters are now in matplotlib.dates, rather - than matplotlib.tickers - - converters have been abolished from all functions and classes. - num2date and date2num are now the converter functions for all date - plots - - Most of the date tick locators have a different meaning in their - constructors. In the prior implementation, the first argument was a - base and multiples of the base were ticked. e.g., - - HourLocator(5) # old: tick every 5 minutes - - In the new implementation, the explicit points you want to tick are - provided as a number or sequence - - HourLocator(range(0,5,61)) # new: tick every 5 minutes - - This gives much greater flexibility. I have tried to make the - default constructors (no args) behave similarly, where possible. - - Note that YearLocator still works under the base/multiple scheme. - The difference between the YearLocator and the other locators is - that years are not recurrent. - - - Financial functions: - - matplotlib.finance.quotes_historical_yahoo(ticker, date1, date2) - - date1, date2 are now datetime instances. Return value is a list - of quotes where the quote time is a float - days since gregorian - start, as returned by date2num - - See examples/finance_demo.py for example usage of new API - -Changes for 0.61 -================ - -:: - - canvas.connect is now deprecated for event handling. use - mpl_connect and mpl_disconnect instead. The callback signature is - func(event) rather than func(widget, event) - -Changes for 0.60 -================ - -:: - - ColormapJet and Grayscale are deprecated. For backwards - compatibility, they can be obtained either by doing - - from matplotlib.cm import ColormapJet - - or - - from matplotlib.matlab import * - - They are replaced by cm.jet and cm.grey - -Changes for 0.54.3 -================== - -:: - - removed the set_default_font / get_default_font scheme from the - font_manager to unify customization of font defaults with the rest of - the rc scheme. See examples/font_properties_demo.py and help(rc) in - matplotlib.matlab. - -Changes for 0.54 -================ - -MATLAB interface ----------------- - -dpi -~~~ - -Several of the backends used a PIXELS_PER_INCH hack that I added to -try and make images render consistently across backends. This just -complicated matters. So you may find that some font sizes and line -widths appear different than before. Apologies for the -inconvenience. You should set the dpi to an accurate value for your -screen to get true sizes. - - -pcolor and scatter -~~~~~~~~~~~~~~~~~~ - -There are two changes to the MATLAB interface API, both involving the -patch drawing commands. For efficiency, pcolor and scatter have been -rewritten to use polygon collections, which are a new set of objects -from matplotlib.collections designed to enable efficient handling of -large collections of objects. These new collections make it possible -to build large scatter plots or pcolor plots with no loops at the -python level, and are significantly faster than their predecessors. -The original pcolor and scatter functions are retained as -pcolor_classic and scatter_classic. - -The return value from pcolor is a PolyCollection. Most of the -propertes that are available on rectangles or other patches are also -available on PolyCollections, e.g., you can say:: - - c = scatter(blah, blah) - c.set_linewidth(1.0) - c.set_facecolor('r') - c.set_alpha(0.5) - -or:: - - c = scatter(blah, blah) - set(c, 'linewidth', 1.0, 'facecolor', 'r', 'alpha', 0.5) - - -Because the collection is a single object, you no longer need to loop -over the return value of scatter or pcolor to set properties for the -entire list. - -If you want the different elements of a collection to vary on a -property, e.g., to have different line widths, see matplotlib.collections -for a discussion on how to set the properties as a sequence. - -For scatter, the size argument is now in points^2 (the area of the -symbol in points) as in MATLAB and is not in data coords as before. -Using sizes in data coords caused several problems. So you will need -to adjust your size arguments accordingly or use scatter_classic. - -mathtext spacing -~~~~~~~~~~~~~~~~ - -For reasons not clear to me (and which I'll eventually fix) spacing no -longer works in font groups. However, I added three new spacing -commands which compensate for this '\ ' (regular space), '\/' (small -space) and '\hspace{frac}' where frac is a fraction of fontsize in -points. You will need to quote spaces in font strings, is:: - - title(r'$\rm{Histogram\ of\ IQ:}\ \mu=100,\ \sigma=15$') - - - -Object interface - Application programmers ------------------------------------------- - -Autoscaling -~~~~~~~~~~~ - - The x and y axis instances no longer have autoscale view. These are - handled by axes.autoscale_view - -Axes creation -~~~~~~~~~~~~~ - - You should not instantiate your own Axes any more using the OO API. - Rather, create a Figure as before and in place of:: - - f = Figure(figsize=(5,4), dpi=100) - a = Subplot(f, 111) - f.add_axis(a) - - use:: - - f = Figure(figsize=(5,4), dpi=100) - a = f.add_subplot(111) - - That is, add_axis no longer exists and is replaced by:: - - add_axes(rect, axisbg=defaultcolor, frameon=True) - add_subplot(num, axisbg=defaultcolor, frameon=True) - -Artist methods -~~~~~~~~~~~~~~ - - If you define your own Artists, you need to rename the _draw method - to draw - -Bounding boxes -~~~~~~~~~~~~~~ - - matplotlib.transforms.Bound2D is replaced by - matplotlib.transforms.Bbox. If you want to construct a bbox from - left, bottom, width, height (the signature for Bound2D), use - matplotlib.transforms.lbwh_to_bbox, as in - - bbox = clickBBox = lbwh_to_bbox(left, bottom, width, height) - - The Bbox has a different API than the Bound2D. e.g., if you want to - get the width and height of the bbox - - OLD:: - width = fig.bbox.x.interval() - height = fig.bbox.y.interval() - - New:: - width = fig.bbox.width() - height = fig.bbox.height() - - - - -Object constructors -~~~~~~~~~~~~~~~~~~~ - - You no longer pass the bbox, dpi, or transforms to the various - Artist constructors. The old way or creating lines and rectangles - was cumbersome because you had to pass so many attributes to the - Line2D and Rectangle classes not related directly to the geometry - and properties of the object. Now default values are added to the - object when you call axes.add_line or axes.add_patch, so they are - hidden from the user. - - If you want to define a custom transformation on these objects, call - o.set_transform(trans) where trans is a Transformation instance. - - In prior versions of you wanted to add a custom line in data coords, - you would have to do - - l = Line2D(dpi, bbox, x, y, - color = color, - transx = transx, - transy = transy, - ) - - now all you need is - - l = Line2D(x, y, color=color) - - and the axes will set the transformation for you (unless you have - set your own already, in which case it will eave it unchanged) - -Transformations -~~~~~~~~~~~~~~~ - - The entire transformation architecture has been rewritten. - Previously the x and y transformations where stored in the xaxis and - yaxis instances. The problem with this approach is it only allows - for separable transforms (where the x and y transformations don't - depend on one another). But for cases like polar, they do. Now - transformations operate on x,y together. There is a new base class - matplotlib.transforms.Transformation and two concrete - implementations, matplotlib.transforms.SeparableTransformation and - matplotlib.transforms.Affine. The SeparableTransformation is - constructed with the bounding box of the input (this determines the - rectangular coordinate system of the input, i.e., the x and y view - limits), the bounding box of the display, and possibly nonlinear - transformations of x and y. The 2 most frequently used - transformations, data coordinates -> display and axes coordinates -> - display are available as ax.transData and ax.transAxes. See - alignment_demo.py which uses axes coords. - - Also, the transformations should be much faster now, for two reasons - - * they are written entirely in extension code - - * because they operate on x and y together, they can do the entire - transformation in one loop. Earlier I did something along the - lines of:: - - xt = sx*func(x) + tx - yt = sy*func(y) + ty - - Although this was done in numerix, it still involves 6 length(x) - for-loops (the multiply, add, and function evaluation each for x - and y). Now all of that is done in a single pass. - - - If you are using transformations and bounding boxes to get the - cursor position in data coordinates, the method calls are a little - different now. See the updated examples/coords_demo.py which shows - you how to do this. - - Likewise, if you are using the artist bounding boxes to pick items - on the canvas with the GUI, the bbox methods are somewhat - different. You will need to see the updated - examples/object_picker.py. - - See unit/transforms_unit.py for many examples using the new - transformations. - - -.. highlight:: none - -Changes for 0.50 -================ - -:: - - * refactored Figure class so it is no longer backend dependent. - FigureCanvasBackend takes over the backend specific duties of the - Figure. matplotlib.backend_bases.FigureBase moved to - matplotlib.figure.Figure. - - * backends must implement FigureCanvasBackend (the thing that - controls the figure and handles the events if any) and - FigureManagerBackend (wraps the canvas and the window for MATLAB - interface). FigureCanvasBase implements a backend switching - mechanism - - * Figure is now an Artist (like everything else in the figure) and - is totally backend independent - - * GDFONTPATH renamed to TTFPATH - - * backend faceColor argument changed to rgbFace - - * colormap stuff moved to colors.py - - * arg_to_rgb in backend_bases moved to class ColorConverter in - colors.py - - * GD users must upgrade to gd-2.0.22 and gdmodule-0.52 since new gd - features (clipping, antialiased lines) are now used. - - * Renderer must implement points_to_pixels - - Migrating code: - - MATLAB interface: - - The only API change for those using the MATLAB interface is in how - you call figure redraws for dynamically updating figures. In the - old API, you did - - fig.draw() - - In the new API, you do - - manager = get_current_fig_manager() - manager.canvas.draw() - - See the examples system_monitor.py, dynamic_demo.py, and anim.py - - API - - There is one important API change for application developers. - Figure instances used subclass GUI widgets that enabled them to be - placed directly into figures. e.g., FigureGTK subclassed - gtk.DrawingArea. Now the Figure class is independent of the - backend, and FigureCanvas takes over the functionality formerly - handled by Figure. In order to include figures into your apps, - you now need to do, for example - - # gtk example - fig = Figure(figsize=(5,4), dpi=100) - canvas = FigureCanvasGTK(fig) # a gtk.DrawingArea - canvas.show() - vbox.pack_start(canvas) - - If you use the NavigationToolbar, this in now initialized with a - FigureCanvas, not a Figure. The examples embedding_in_gtk.py, - embedding_in_gtk2.py, and mpl_with_glade.py all reflect the new - API so use these as a guide. - - All prior calls to - - figure.draw() and - figure.print_figure(args) - - should now be - - canvas.draw() and - canvas.print_figure(args) - - Apologies for the inconvenience. This refactorization brings - significant more freedom in developing matplotlib and should bring - better plotting capabilities, so I hope the inconvenience is worth - it. - -Changes for 0.42 -================ - -:: - - * Refactoring AxisText to be backend independent. Text drawing and - get_window_extent functionality will be moved to the Renderer. - - * backend_bases.AxisTextBase is now text.Text module - - * All the erase and reset functionality removed from AxisText - not - needed with double buffered drawing. Ditto with state change. - Text instances have a get_prop_tup method that returns a hashable - tuple of text properties which you can use to see if text props - have changed, e.g., by caching a font or layout instance in a dict - with the prop tup as a key -- see RendererGTK.get_pango_layout in - backend_gtk for an example. - - * Text._get_xy_display renamed Text.get_xy_display - - * Artist set_renderer and wash_brushes methods removed - - * Moved Legend class from matplotlib.axes into matplotlib.legend - - * Moved Tick, XTick, YTick, Axis, XAxis, YAxis from matplotlib.axes - to matplotlib.axis - - * moved process_text_args to matplotlib.text - - * After getting Text handled in a backend independent fashion, the - import process is much cleaner since there are no longer cyclic - dependencies - - * matplotlib.matlab._get_current_fig_manager renamed to - matplotlib.matlab.get_current_fig_manager to allow user access to - the GUI window attribute, e.g., figManager.window for GTK and - figManager.frame for wx - -Changes for 0.40 -================ - -:: - - - Artist - * __init__ takes a DPI instance and a Bound2D instance which is - the bounding box of the artist in display coords - * get_window_extent returns a Bound2D instance - * set_size is removed; replaced by bbox and dpi - * the clip_gc method is removed. Artists now clip themselves with - their box - * added _clipOn boolean attribute. If True, gc clip to bbox. - - - AxisTextBase - * Initialized with a transx, transy which are Transform instances - * set_drawing_area removed - * get_left_right and get_top_bottom are replaced by get_window_extent - - - Line2D Patches now take transx, transy - * Initialized with a transx, transy which are Transform instances - - - Patches - * Initialized with a transx, transy which are Transform instances - - - FigureBase attributes dpi is a DPI instance rather than scalar and - new attribute bbox is a Bound2D in display coords, and I got rid - of the left, width, height, etc... attributes. These are now - accessible as, for example, bbox.x.min is left, bbox.x.interval() - is width, bbox.y.max is top, etc... - - - GcfBase attribute pagesize renamed to figsize - - - Axes - * removed figbg attribute - * added fig instance to __init__ - * resizing is handled by figure call to resize. - - - Subplot - * added fig instance to __init__ - - - Renderer methods for patches now take gcEdge and gcFace instances. - gcFace=None takes the place of filled=False - - - True and False symbols provided by cbook in a python2.3 compatible - way - - - new module transforms supplies Bound1D, Bound2D and Transform - instances and more - - - Changes to the MATLAB helpers API - - * _matlab_helpers.GcfBase is renamed by Gcf. Backends no longer - need to derive from this class. Instead, they provide a factory - function new_figure_manager(num, figsize, dpi). The destroy - method of the GcfDerived from the backends is moved to the derived - FigureManager. - - * FigureManagerBase moved to backend_bases - - * Gcf.get_all_figwins renamed to Gcf.get_all_fig_managers - - Jeremy: +Removals +-------- - Make sure to self._reset = False in AxisTextWX._set_font. This was - something missing in my backend code. +Hold machinery +`````````````` + +Setting or unsetting ``hold`` (deprecated in version 2.1) has now +been completely removed. Matplotlib now always behaves as if ``hold=True``. +To clear an axes you can manually use :meth:`~.axes.Axes.cla()`, +or to clear an entire figure use :meth:`~.figure.Figure.clf()`. + + +Removal of deprecated backends +`````````````````````````````` + +Deprecated backends have been removed: + +- GTKAgg +- GTKCairo +- GTK +- GDK + + +Deprecated APIs +``````````````` + +The following deprecated API elements have been removed: + +- The deprecated methods ``knownfailureif`` and ``remove_text`` have been removed + from :mod:`matplotlib.testing.decorators`. +- The entire contents of ``testing.noseclasses`` have also been removed. +- ``matplotlib.checkdep_tex``, ``matplotlib.checkdep_xmllint`` +- ``backend_bases.IdleEvent`` +- ``cbook.converter``, ``cbook.tostr``, ``cbook.todatetime``, ``cbook.todate``, + ``cbook.tofloat``, ``cbook.toint``, ``cbook.unique``, + ``cbook.is_string_like``, ``cbook.is_sequence_of_strings``, + ``cbook.is_scalar``, ``cbook.soundex``, ``cbook.dict_delall``, + ``cbook.get_split_ind``, ``cbook.wrap``, ``cbook.get_recursive_filelist``, + ``cbook.pieces``, ``cbook.exception_to_str``, ``cbook.allequal``, + ``cbook.alltrue``, ``cbook.onetrue``, ``cbook.allpairs``, ``cbook.finddir``, + ``cbook.reverse_dict``, ``cbook.restrict_dict``, ``cbook.issubclass_safe``, + ``cbook.recursive_remove``, ``cbook.unmasked_index_ranges``, + ``cbook.Null``, ``cbook.RingBuffer``, ``cbook.Sorter``, ``cbook.Xlator``, +- ``font_manager.weight_as_number``, ``font_manager.ttfdict_to_fnames`` +- ``pyplot.colors``, ``pyplot.spectral`` +- ``rcsetup.validate_negative_linestyle``, + ``rcsetup.validate_negative_linestyle_legacy``, +- ``testing.compare.verifiers``, ``testing.compare.verify`` +- ``testing.decorators.knownfailureif``, + ``testing.decorators.ImageComparisonTest.remove_text`` +- ``tests.assert_str_equal``, ``tests.test_tinypages.file_same`` +- ``texmanager.dvipng_hack_alpha``, +- ``_AxesBase.axesPatch``, ``_AxesBase.set_color_cycle``, + ``_AxesBase.get_cursor_props``, ``_AxesBase.set_cursor_props`` +- ``_ImageBase.iterpnames`` +- ``FigureCanvasBase.start_event_loop_default``; +- ``FigureCanvasBase.stop_event_loop_default``; +- ``Figure.figurePatch``, +- ``FigureCanvasBase.dynamic_update``, ``FigureCanvasBase.idle_event``, + ``FigureCanvasBase.get_linestyle``, ``FigureCanvasBase.set_linestyle`` +- ``FigureCanvasQTAggBase`` +- ``FigureCanvasQTAgg.blitbox`` +- ``FigureCanvasTk.show`` (alternative: ``FigureCanvasTk.draw``) +- ``FigureManagerTkAgg`` (alternative: ``FigureManagerTk``) +- ``NavigationToolbar2TkAgg`` (alternative: ``NavigationToolbar2Tk``) +- ``backend_wxagg.Toolbar`` (alternative: ``backend_wxagg.NavigationToolbar2WxAgg``) +- ``RendererAgg.debug()`` +- passing non-numbers to ``EngFormatter.format_eng`` +- passing ``frac`` to ``PolarAxes.set_theta_grids`` +- any mention of idle events + +The following API elements have been removed: + +- ``backend_cairo.HAS_CAIRO_CFFI`` +- ``sphinxext.sphinx_version`` + + +Proprietary sphinx directives +````````````````````````````` + +The matplotlib documentation used the proprietary sphinx directives +`.. htmlonly::`, and `.. latexonly::`. These have been replaced with the +standard sphinx directives `.. only:: html` and `.. only:: latex`. This +change will not affect any users. Only downstream package maintainers, who +have used the proprietary directives in their docs, will have to switch to the +sphinx directives. + + +lib/mpl_examples symlink +```````````````````````` + +The symlink from lib/mpl_examples to ../examples has been removed. +This is not installed as an importable package and should not affect +end users, however this may require down-stream packagers to adjust. +The content is still available top-level examples directory. diff --git a/doc/api/api_changes_old.rst b/doc/api/api_changes_old.rst new file mode 100644 index 000000000000..ab9381680498 --- /dev/null +++ b/doc/api/api_changes_old.rst @@ -0,0 +1,11 @@ + +================ + Old API Changes +================ + +.. toctree:: + :glob: + :reversed: + :maxdepth: 1 + + prev_api_changes/* diff --git a/doc/api/api_overview.rst b/doc/api/api_overview.rst new file mode 100644 index 000000000000..0b735010cbdb --- /dev/null +++ b/doc/api/api_overview.rst @@ -0,0 +1,55 @@ +API Overview +============ + +Below we describe several common approaches to plotting with Matplotlib. + +.. contents:: + +The pyplot API +-------------- + +`matplotlib.pyplot` is a collection of command style functions that make +Matplotlib work like MATLAB. Each pyplot function makes some change to a +figure: e.g., creates a figure, creates a plotting area in a figure, plots +some lines in a plotting area, decorates the plot with labels, etc. + +`.pyplot` is mainly intended for interactive plots and simple cases of +programmatic plot generation. + +Further reading: + +- The `matplotlib.pyplot` function reference +- :doc:`/tutorials/introductory/pyplot` +- :ref:`Pyplot examples ` + +The object-oriented API +----------------------- + +At its core, Matbplotlib is object-oriented. We recommend directly working +with the objects, if you need more control and customization of your plots. + +In many cases you will create a `.Figure` and one or more +`~matplotlib.axes.Axes` using `.pyplot.subplots` and from then on only work +on these objects. However, it's also possible to create `.Figure`\ s +explicitly (e.g. when including them in GUI applications). + +Further reading: + +- `matplotlib.axes.Axes` and `matplotlib.figure.Figure` for an overview of + plotting functions. +- Most of the :ref:`examples ` use the object-oriented approach + (except for the pyplot section). + + +The pylab API (disapproved) +--------------------------- + +.. warning:: + Since heavily importing into the global namespace may result in unexpected + behavior, the use of pylab is strongly discouraged. Use `matplotlib.pyplot` + instead. + +`matplotlib.pylab` is a module that includes `matplotlib.pyplot`, `numpy` +and some additional functions within a single namespace. It's original puropse +was to mimic a MATLAB-like way of working by importing all functions into the +global namespace. This is considered bad style nowadays. diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index aea657e587e1..a6f39d39f6b0 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -1,8 +1,8 @@ .. _artist-api: -=================== - ``artist`` Module -=================== +****** +artist +****** .. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.BrokenBarHCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.figure.Figure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.Wedge matplotlib.patches.YAArrow matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.CustomCell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.text.TextWithDash :parts: 1 diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index 0e321bf7d6a0..f83da33767f7 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -1,17 +1,38 @@ -================ - ``Axes`` class -================ -.. currentmodule:: matplotlib.axes +==== +axes +==== -.. autoclass:: Axes - :no-members: - :no-undoc-members: +.. currentmodule:: matplotlib.axes .. contents:: Table of Contents :depth: 2 :local: :backlinks: entry + :class: multicol-toc + +.. automodule:: matplotlib.axes + :no-members: + :no-undoc-members: + +The Axes class +============== + +.. autoclass:: Axes + :no-members: + :no-undoc-members: + :show-inheritance: + + +Subplots +======== + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + SubplotBase + subplot_class_factory Plotting ======== @@ -164,6 +185,9 @@ Text and Annotations Axes.text Axes.table Axes.arrow + Axes.inset_axes + Axes.indicate_inset + Axes.indicate_inset_zoom Fields @@ -229,7 +253,6 @@ Property cycle :nosignatures: Axes.set_prop_cycle - Axes.set_color_cycle Axis / limits @@ -509,8 +532,6 @@ Interactive Axes.contains_point Axes.get_cursor_data - Axes.get_cursor_props - Axes.set_cursor_props Children ======== @@ -654,8 +675,6 @@ Other Axes.get_default_bbox_extra_artists Axes.get_transformed_clip_path_and_affine Axes.has_data - Axes.hold - Axes.ishold Inheritance diff --git a/doc/api/backend_gtkagg_api.rst b/doc/api/backend_gtkagg_api.rst deleted file mode 100644 index f5a37bf4d345..000000000000 --- a/doc/api/backend_gtkagg_api.rst +++ /dev/null @@ -1,11 +0,0 @@ - -:mod:`matplotlib.backends.backend_gtkagg` -========================================= - -**TODO** We'll add this later, importing the gtk backends requires an active -X-session, which is not compatible with cron jobs. - -.. .. automodule:: matplotlib.backends.backend_gtkagg -.. :members: -.. :undoc-members: -.. :show-inheritance: diff --git a/doc/api/backend_gtkcairo_api.rst b/doc/api/backend_gtkcairo_api.rst deleted file mode 100644 index 562f8ea6e7ce..000000000000 --- a/doc/api/backend_gtkcairo_api.rst +++ /dev/null @@ -1,11 +0,0 @@ - -:mod:`matplotlib.backends.backend_gtkcairo` -=========================================== - -**TODO** We'll add this later, importing the gtk backends requires an active -X-session, which is not compatible with cron jobs. - -.. .. automodule:: matplotlib.backends.backend_gtkcairo -.. :members: -.. :undoc-members: -.. :show-inheritance: diff --git a/doc/api/blocking_input_api.rst b/doc/api/blocking_input_api.rst new file mode 100644 index 000000000000..840cbe25235f --- /dev/null +++ b/doc/api/blocking_input_api.rst @@ -0,0 +1,11 @@ +************** +blocking_input +************** + +:mod:`matplotlib.blocking_input` +================================ + +.. automodule:: matplotlib.blocking_input + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 779f4ad1c1c4..e6fee3334f33 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -18,6 +18,7 @@ Classes .. autosummary:: :toctree: _as_gen/ :template: autosummary.rst + :nosignatures: AxesStack Figure @@ -29,6 +30,6 @@ Functions .. autosummary:: :toctree: _as_gen/ :template: autosummary.rst + :nosignatures: figaspect - \ No newline at end of file diff --git a/doc/api/index.rst b/doc/api/index.rst index 78669f992177..2646cb783bcd 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -4,23 +4,27 @@ The Matplotlib API #################### -.. htmlonly:: +.. toctree:: + :maxdepth: 1 + + api_overview.rst + api_changes.rst - :Release: |version| - :Date: |today| +Modules +======= .. toctree:: :maxdepth: 1 - pyplot_summary.rst - api_changes.rst matplotlib_configuration_api.rst + pyplot_summary.rst afm_api.rst animation_api.rst artist_api.rst axes_api.rst axis_api.rst index_backend_api.rst + blocking_input_api.rst cbook_api.rst cm_api.rst collections_api.rst @@ -59,22 +63,13 @@ units_api.rst widgets_api.rst -.. currentmodule:: matplotlib - -.. autosummary:: - :toctree: _as_gen - :template: autofunctions.rst - - pyplot - - Toolkits --------- +======== .. toctree:: :maxdepth: 1 - + toolkits/index.rst toolkits/mplot3d.rst toolkits/axes_grid1.rst toolkits/axisartist.rst diff --git a/doc/api/index_backend_api.rst b/doc/api/index_backend_api.rst index 813c3770214e..5141e275a4f9 100644 --- a/doc/api/index_backend_api.rst +++ b/doc/api/index_backend_api.rst @@ -10,8 +10,6 @@ backends backend_tools_api.rst backend_agg_api.rst backend_cairo_api.rst - backend_gtkagg_api.rst - backend_gtkcairo_api.rst backend_gtk3agg_api.rst backend_gtk3cairo_api.rst backend_nbagg_api.rst diff --git a/doc/api/prev_api_changes/api_changes_0.40.rst b/doc/api/prev_api_changes/api_changes_0.40.rst new file mode 100644 index 000000000000..83815ff43157 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.40.rst @@ -0,0 +1,67 @@ + +Changes for 0.40 +================ + +.. code-block:: text + + - Artist + * __init__ takes a DPI instance and a Bound2D instance which is + the bounding box of the artist in display coords + * get_window_extent returns a Bound2D instance + * set_size is removed; replaced by bbox and dpi + * the clip_gc method is removed. Artists now clip themselves with + their box + * added _clipOn boolean attribute. If True, gc clip to bbox. + + - AxisTextBase + * Initialized with a transx, transy which are Transform instances + * set_drawing_area removed + * get_left_right and get_top_bottom are replaced by get_window_extent + + - Line2D Patches now take transx, transy + * Initialized with a transx, transy which are Transform instances + + - Patches + * Initialized with a transx, transy which are Transform instances + + - FigureBase attributes dpi is a DPI instance rather than scalar and + new attribute bbox is a Bound2D in display coords, and I got rid + of the left, width, height, etc... attributes. These are now + accessible as, for example, bbox.x.min is left, bbox.x.interval() + is width, bbox.y.max is top, etc... + + - GcfBase attribute pagesize renamed to figsize + + - Axes + * removed figbg attribute + * added fig instance to __init__ + * resizing is handled by figure call to resize. + + - Subplot + * added fig instance to __init__ + + - Renderer methods for patches now take gcEdge and gcFace instances. + gcFace=None takes the place of filled=False + + - True and False symbols provided by cbook in a python2.3 compatible + way + + - new module transforms supplies Bound1D, Bound2D and Transform + instances and more + + - Changes to the MATLAB helpers API + + * _matlab_helpers.GcfBase is renamed by Gcf. Backends no longer + need to derive from this class. Instead, they provide a factory + function new_figure_manager(num, figsize, dpi). The destroy + method of the GcfDerived from the backends is moved to the derived + FigureManager. + + * FigureManagerBase moved to backend_bases + + * Gcf.get_all_figwins renamed to Gcf.get_all_fig_managers + + Jeremy: + + Make sure to self._reset = False in AxisTextWX._set_font. This was + something missing in my backend code. diff --git a/doc/api/prev_api_changes/api_changes_0.42.rst b/doc/api/prev_api_changes/api_changes_0.42.rst new file mode 100644 index 000000000000..e90d2af0ab2c --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.42.rst @@ -0,0 +1,37 @@ +Changes for 0.42 +================ + +.. code-block:: text + + * Refactoring AxisText to be backend independent. Text drawing and + get_window_extent functionality will be moved to the Renderer. + + * backend_bases.AxisTextBase is now text.Text module + + * All the erase and reset functionality removed from AxisText - not + needed with double buffered drawing. Ditto with state change. + Text instances have a get_prop_tup method that returns a hashable + tuple of text properties which you can use to see if text props + have changed, e.g., by caching a font or layout instance in a dict + with the prop tup as a key -- see RendererGTK.get_pango_layout in + backend_gtk for an example. + + * Text._get_xy_display renamed Text.get_xy_display + + * Artist set_renderer and wash_brushes methods removed + + * Moved Legend class from matplotlib.axes into matplotlib.legend + + * Moved Tick, XTick, YTick, Axis, XAxis, YAxis from matplotlib.axes + to matplotlib.axis + + * moved process_text_args to matplotlib.text + + * After getting Text handled in a backend independent fashion, the + import process is much cleaner since there are no longer cyclic + dependencies + + * matplotlib.matlab._get_current_fig_manager renamed to + matplotlib.matlab.get_current_fig_manager to allow user access to + the GUI window attribute, e.g., figManager.window for GTK and + figManager.frame for wx diff --git a/doc/api/prev_api_changes/api_changes_0.50.rst b/doc/api/prev_api_changes/api_changes_0.50.rst new file mode 100644 index 000000000000..bf1b608d2b63 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.50.rst @@ -0,0 +1,86 @@ + +Changes for 0.50 +================ + +.. code-block:: text + + * refactored Figure class so it is no longer backend dependent. + FigureCanvasBackend takes over the backend specific duties of the + Figure. matplotlib.backend_bases.FigureBase moved to + matplotlib.figure.Figure. + + * backends must implement FigureCanvasBackend (the thing that + controls the figure and handles the events if any) and + FigureManagerBackend (wraps the canvas and the window for MATLAB + interface). FigureCanvasBase implements a backend switching + mechanism + + * Figure is now an Artist (like everything else in the figure) and + is totally backend independent + + * GDFONTPATH renamed to TTFPATH + + * backend faceColor argument changed to rgbFace + + * colormap stuff moved to colors.py + + * arg_to_rgb in backend_bases moved to class ColorConverter in + colors.py + + * GD users must upgrade to gd-2.0.22 and gdmodule-0.52 since new gd + features (clipping, antialiased lines) are now used. + + * Renderer must implement points_to_pixels + + Migrating code: + + MATLAB interface: + + The only API change for those using the MATLAB interface is in how + you call figure redraws for dynamically updating figures. In the + old API, you did + + fig.draw() + + In the new API, you do + + manager = get_current_fig_manager() + manager.canvas.draw() + + See the examples system_monitor.py, dynamic_demo.py, and anim.py + + API + + There is one important API change for application developers. + Figure instances used subclass GUI widgets that enabled them to be + placed directly into figures. e.g., FigureGTK subclassed + gtk.DrawingArea. Now the Figure class is independent of the + backend, and FigureCanvas takes over the functionality formerly + handled by Figure. In order to include figures into your apps, + you now need to do, for example + + # gtk example + fig = Figure(figsize=(5,4), dpi=100) + canvas = FigureCanvasGTK(fig) # a gtk.DrawingArea + canvas.show() + vbox.pack_start(canvas) + + If you use the NavigationToolbar, this in now initialized with a + FigureCanvas, not a Figure. The examples embedding_in_gtk.py, + embedding_in_gtk2.py, and mpl_with_glade.py all reflect the new + API so use these as a guide. + + All prior calls to + + figure.draw() and + figure.print_figure(args) + + should now be + + canvas.draw() and + canvas.print_figure(args) + + Apologies for the inconvenience. This refactorization brings + significant more freedom in developing matplotlib and should bring + better plotting capabilities, so I hope the inconvenience is worth + it. diff --git a/doc/api/prev_api_changes/api_changes_0.54.3.rst b/doc/api/prev_api_changes/api_changes_0.54.3.rst new file mode 100644 index 000000000000..0747a0372927 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.54.3.rst @@ -0,0 +1,10 @@ + +Changes for 0.54.3 +================== + +.. code-block:: text + + removed the set_default_font / get_default_font scheme from the + font_manager to unify customization of font defaults with the rest of + the rc scheme. See examples/font_properties_demo.py and help(rc) in + matplotlib.matlab. diff --git a/doc/api/prev_api_changes/api_changes_0.54.rst b/doc/api/prev_api_changes/api_changes_0.54.rst new file mode 100644 index 000000000000..5ff92935c7d9 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.54.rst @@ -0,0 +1,212 @@ + +Changes for 0.54 +================ + +MATLAB interface +---------------- + +dpi +~~~ + +Several of the backends used a PIXELS_PER_INCH hack that I added to +try and make images render consistently across backends. This just +complicated matters. So you may find that some font sizes and line +widths appear different than before. Apologies for the +inconvenience. You should set the dpi to an accurate value for your +screen to get true sizes. + + +pcolor and scatter +~~~~~~~~~~~~~~~~~~ + +There are two changes to the MATLAB interface API, both involving the +patch drawing commands. For efficiency, pcolor and scatter have been +rewritten to use polygon collections, which are a new set of objects +from matplotlib.collections designed to enable efficient handling of +large collections of objects. These new collections make it possible +to build large scatter plots or pcolor plots with no loops at the +python level, and are significantly faster than their predecessors. +The original pcolor and scatter functions are retained as +pcolor_classic and scatter_classic. + +The return value from pcolor is a PolyCollection. Most of the +propertes that are available on rectangles or other patches are also +available on PolyCollections, e.g., you can say:: + + c = scatter(blah, blah) + c.set_linewidth(1.0) + c.set_facecolor('r') + c.set_alpha(0.5) + +or:: + + c = scatter(blah, blah) + set(c, 'linewidth', 1.0, 'facecolor', 'r', 'alpha', 0.5) + + +Because the collection is a single object, you no longer need to loop +over the return value of scatter or pcolor to set properties for the +entire list. + +If you want the different elements of a collection to vary on a +property, e.g., to have different line widths, see matplotlib.collections +for a discussion on how to set the properties as a sequence. + +For scatter, the size argument is now in points^2 (the area of the +symbol in points) as in MATLAB and is not in data coords as before. +Using sizes in data coords caused several problems. So you will need +to adjust your size arguments accordingly or use scatter_classic. + +mathtext spacing +~~~~~~~~~~~~~~~~ + +For reasons not clear to me (and which I'll eventually fix) spacing no +longer works in font groups. However, I added three new spacing +commands which compensate for this '\ ' (regular space), '\/' (small +space) and '\hspace{frac}' where frac is a fraction of fontsize in +points. You will need to quote spaces in font strings, is:: + + title(r'$\rm{Histogram\ of\ IQ:}\ \mu=100,\ \sigma=15$') + + + +Object interface - Application programmers +------------------------------------------ + +Autoscaling +~~~~~~~~~~~ + + The x and y axis instances no longer have autoscale view. These are + handled by axes.autoscale_view + +Axes creation +~~~~~~~~~~~~~ + + You should not instantiate your own Axes any more using the OO API. + Rather, create a Figure as before and in place of:: + + f = Figure(figsize=(5,4), dpi=100) + a = Subplot(f, 111) + f.add_axis(a) + + use:: + + f = Figure(figsize=(5,4), dpi=100) + a = f.add_subplot(111) + + That is, add_axis no longer exists and is replaced by:: + + add_axes(rect, axisbg=defaultcolor, frameon=True) + add_subplot(num, axisbg=defaultcolor, frameon=True) + +Artist methods +~~~~~~~~~~~~~~ + + If you define your own Artists, you need to rename the _draw method + to draw + +Bounding boxes +~~~~~~~~~~~~~~ + + matplotlib.transforms.Bound2D is replaced by + matplotlib.transforms.Bbox. If you want to construct a bbox from + left, bottom, width, height (the signature for Bound2D), use + matplotlib.transforms.lbwh_to_bbox, as in + + bbox = clickBBox = lbwh_to_bbox(left, bottom, width, height) + + The Bbox has a different API than the Bound2D. e.g., if you want to + get the width and height of the bbox + + OLD:: + width = fig.bbox.x.interval() + height = fig.bbox.y.interval() + + New:: + width = fig.bbox.width() + height = fig.bbox.height() + + + + +Object constructors +~~~~~~~~~~~~~~~~~~~ + + You no longer pass the bbox, dpi, or transforms to the various + Artist constructors. The old way or creating lines and rectangles + was cumbersome because you had to pass so many attributes to the + Line2D and Rectangle classes not related directly to the geometry + and properties of the object. Now default values are added to the + object when you call axes.add_line or axes.add_patch, so they are + hidden from the user. + + If you want to define a custom transformation on these objects, call + o.set_transform(trans) where trans is a Transformation instance. + + In prior versions of you wanted to add a custom line in data coords, + you would have to do + + l = Line2D(dpi, bbox, x, y, + color = color, + transx = transx, + transy = transy, + ) + + now all you need is + + l = Line2D(x, y, color=color) + + and the axes will set the transformation for you (unless you have + set your own already, in which case it will eave it unchanged) + +Transformations +~~~~~~~~~~~~~~~ + + The entire transformation architecture has been rewritten. + Previously the x and y transformations where stored in the xaxis and + yaxis instances. The problem with this approach is it only allows + for separable transforms (where the x and y transformations don't + depend on one another). But for cases like polar, they do. Now + transformations operate on x,y together. There is a new base class + matplotlib.transforms.Transformation and two concrete + implementations, matplotlib.transforms.SeparableTransformation and + matplotlib.transforms.Affine. The SeparableTransformation is + constructed with the bounding box of the input (this determines the + rectangular coordinate system of the input, i.e., the x and y view + limits), the bounding box of the display, and possibly nonlinear + transformations of x and y. The 2 most frequently used + transformations, data coordinates -> display and axes coordinates -> + display are available as ax.transData and ax.transAxes. See + alignment_demo.py which uses axes coords. + + Also, the transformations should be much faster now, for two reasons + + * they are written entirely in extension code + + * because they operate on x and y together, they can do the entire + transformation in one loop. Earlier I did something along the + lines of:: + + xt = sx*func(x) + tx + yt = sy*func(y) + ty + + Although this was done in numerix, it still involves 6 length(x) + for-loops (the multiply, add, and function evaluation each for x + and y). Now all of that is done in a single pass. + + + If you are using transformations and bounding boxes to get the + cursor position in data coordinates, the method calls are a little + different now. See the updated examples/coords_demo.py which shows + you how to do this. + + Likewise, if you are using the artist bounding boxes to pick items + on the canvas with the GUI, the bbox methods are somewhat + different. You will need to see the updated + examples/object_picker.py. + + See unit/transforms_unit.py for many examples using the new + transformations. + + +.. highlight:: none diff --git a/doc/api/prev_api_changes/api_changes_0.60.rst b/doc/api/prev_api_changes/api_changes_0.60.rst new file mode 100644 index 000000000000..d27c5ae1848b --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.60.rst @@ -0,0 +1,15 @@ +Changes for 0.60 +================ + +.. code-block:: text + + ColormapJet and Grayscale are deprecated. For backwards + compatibility, they can be obtained either by doing + + from matplotlib.cm import ColormapJet + + or + + from matplotlib.matlab import * + + They are replaced by cm.jet and cm.grey diff --git a/doc/api/prev_api_changes/api_changes_0.61.rst b/doc/api/prev_api_changes/api_changes_0.61.rst new file mode 100644 index 000000000000..570c8d1d22e4 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.61.rst @@ -0,0 +1,8 @@ +Changes for 0.61 +================ + +.. code-block:: text + + canvas.connect is now deprecated for event handling. use + mpl_connect and mpl_disconnect instead. The callback signature is + func(event) rather than func(widget, event) diff --git a/doc/api/prev_api_changes/api_changes_0.63.rst b/doc/api/prev_api_changes/api_changes_0.63.rst new file mode 100644 index 000000000000..bb896ad55207 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.63.rst @@ -0,0 +1,43 @@ +Changes for 0.63 +================ + +.. code-block:: text + + Dates are now represented internally as float days since 0001-01-01, + UTC. + + All date tickers and formatters are now in matplotlib.dates, rather + than matplotlib.tickers + + converters have been abolished from all functions and classes. + num2date and date2num are now the converter functions for all date + plots + + Most of the date tick locators have a different meaning in their + constructors. In the prior implementation, the first argument was a + base and multiples of the base were ticked. e.g., + + HourLocator(5) # old: tick every 5 minutes + + In the new implementation, the explicit points you want to tick are + provided as a number or sequence + + HourLocator(range(0,5,61)) # new: tick every 5 minutes + + This gives much greater flexibility. I have tried to make the + default constructors (no args) behave similarly, where possible. + + Note that YearLocator still works under the base/multiple scheme. + The difference between the YearLocator and the other locators is + that years are not recurrent. + + + Financial functions: + + matplotlib.finance.quotes_historical_yahoo(ticker, date1, date2) + + date1, date2 are now datetime instances. Return value is a list + of quotes where the quote time is a float - days since gregorian + start, as returned by date2num + + See examples/finance_demo.py for example usage of new API diff --git a/doc/api/prev_api_changes/api_changes_0.65.1.rst b/doc/api/prev_api_changes/api_changes_0.65.1.rst new file mode 100644 index 000000000000..fb75baaa6acb --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.65.1.rst @@ -0,0 +1,10 @@ +Changes for 0.65.1 +================== + +.. code-block:: text + + removed add_axes and add_subplot from backend_bases. Use + figure.add_axes and add_subplot instead. The figure now manages the + current axes with gca and sca for get and set current axes. If you + have code you are porting which called, e.g., figmanager.add_axes, you + can now simply do figmanager.canvas.figure.add_axes. diff --git a/doc/api/prev_api_changes/api_changes_0.65.rst b/doc/api/prev_api_changes/api_changes_0.65.rst new file mode 100644 index 000000000000..43fffb1bcf4e --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.65.rst @@ -0,0 +1,12 @@ +Changes for 0.65 +================ + +.. code-block:: text + + + mpl_connect and mpl_disconnect in the MATLAB interface renamed to + connect and disconnect + + Did away with the text methods for angle since they were ambiguous. + fontangle could mean fontstyle (obligue, etc) or the rotation of the + text. Use style and rotation instead. diff --git a/doc/api/prev_api_changes/api_changes_0.70.rst b/doc/api/prev_api_changes/api_changes_0.70.rst new file mode 100644 index 000000000000..b8094658b249 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.70.rst @@ -0,0 +1,9 @@ +Changes for 0.70 +================ + +.. code-block:: text + + MplEvent factored into a base class Event and derived classes + MouseEvent and KeyEvent + + Removed definct set_measurement in wx toolbar diff --git a/doc/api/prev_api_changes/api_changes_0.71.rst b/doc/api/prev_api_changes/api_changes_0.71.rst new file mode 100644 index 000000000000..d10a7439e672 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.71.rst @@ -0,0 +1,41 @@ +Changes for 0.71 +================ + +.. code-block:: text + + Significant numerix namespace changes, introduced to resolve + namespace clashes between python built-ins and mlab names. + Refactored numerix to maintain separate modules, rather than + folding all these names into a single namespace. See the following + mailing list threads for more information and background + + http://sourceforge.net/mailarchive/forum.php?thread_id=6398890&forum_id=36187 + http://sourceforge.net/mailarchive/forum.php?thread_id=6323208&forum_id=36187 + + + OLD usage + + from matplotlib.numerix import array, mean, fft + + NEW usage + + from matplotlib.numerix import array + from matplotlib.numerix.mlab import mean + from matplotlib.numerix.fft import fft + + numerix dir structure mirrors numarray (though it is an incomplete + implementation) + + numerix + numerix/mlab + numerix/linear_algebra + numerix/fft + numerix/random_array + + but of course you can use 'numerix : Numeric' and still get the + symbols. + + pylab still imports most of the symbols from Numerix, MLab, fft, + etc, but is more cautious. For names that clash with python names + (min, max, sum), pylab keeps the builtins and provides the numeric + versions with an a* prefix, e.g., (amin, amax, asum) diff --git a/doc/api/prev_api_changes/api_changes_0.72.rst b/doc/api/prev_api_changes/api_changes_0.72.rst new file mode 100644 index 000000000000..9529e396f356 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.72.rst @@ -0,0 +1,33 @@ +Changes for 0.72 +================ + +.. code-block:: text + + - Line2D, Text, and Patch copy_properties renamed update_from and + moved into artist base class + + - LineCollecitons.color renamed to LineCollections.set_color for + consistency with set/get introspection mechanism, + + - pylab figure now defaults to num=None, which creates a new figure + with a guaranteed unique number + + - contour method syntax changed - now it is MATLAB compatible + + unchanged: contour(Z) + old: contour(Z, x=Y, y=Y) + new: contour(X, Y, Z) + + see http://matplotlib.sf.net/matplotlib.pylab.html#-contour + + + - Increased the default resolution for save command. + + - Renamed the base attribute of the ticker classes to _base to avoid conflict + with the base method. Sitt for subs + + - subs=none now does autosubbing in the tick locator. + + - New subplots that overlap old will delete the old axes. If you + do not want this behavior, use fig.add_subplot or the axes + command diff --git a/doc/api/prev_api_changes/api_changes_0.73.rst b/doc/api/prev_api_changes/api_changes_0.73.rst new file mode 100644 index 000000000000..ec7c4e34c6ef --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.73.rst @@ -0,0 +1,10 @@ +Changes for 0.73 +================ + +.. code-block:: text + + - Removed deprecated ColormapJet and friends + + - Removed all error handling from the verbose object + + - figure num of zero is now allowed diff --git a/doc/api/prev_api_changes/api_changes_0.80.rst b/doc/api/prev_api_changes/api_changes_0.80.rst new file mode 100644 index 000000000000..1c118fd21aca --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.80.rst @@ -0,0 +1,9 @@ +Changes for 0.80 +================ + +.. code-block:: text + + - xlim/ylim/axis always return the new limits regardless of + arguments. They now take kwargs which allow you to selectively + change the upper or lower limits while leaving unnamed limits + unchanged. See help(xlim) for example diff --git a/doc/api/prev_api_changes/api_changes_0.81.rst b/doc/api/prev_api_changes/api_changes_0.81.rst new file mode 100644 index 000000000000..f571d5dbae2c --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.81.rst @@ -0,0 +1,49 @@ +Changes for 0.81 +================ + +.. code-block:: text + + - pylab and artist "set" functions renamed to setp to avoid clash + with python2.4 built-in set. Current version will issue a + deprecation warning which will be removed in future versions + + - imshow interpolation arguments changes for advanced interpolation + schemes. See help imshow, particularly the interpolation, + filternorm and filterrad kwargs + + - Support for masked arrays has been added to the plot command and + to the Line2D object. Only the valid points are plotted. A + "valid_only" kwarg was added to the get_xdata() and get_ydata() + methods of Line2D; by default it is False, so that the original + data arrays are returned. Setting it to True returns the plottable + points. + + - contour changes: + + Masked arrays: contour and contourf now accept masked arrays as + the variable to be contoured. Masking works correctly for + contour, but a bug remains to be fixed before it will work for + contourf. The "badmask" kwarg has been removed from both + functions. + + Level argument changes: + + Old version: a list of levels as one of the positional + arguments specified the lower bound of each filled region; the + upper bound of the last region was taken as a very large + number. Hence, it was not possible to specify that z values + between 0 and 1, for example, be filled, and that values + outside that range remain unfilled. + + New version: a list of N levels is taken as specifying the + boundaries of N-1 z ranges. Now the user has more control over + what is colored and what is not. Repeated calls to contourf + (with different colormaps or color specifications, for example) + can be used to color different ranges of z. Values of z + outside an expected range are left uncolored. + + Example: + Old: contourf(z, [0, 1, 2]) would yield 3 regions: 0-1, 1-2, and >2. + New: it would yield 2 regions: 0-1, 1-2. If the same 3 regions were + desired, the equivalent list of levels would be [0, 1, 2, + 1e38]. diff --git a/doc/api/prev_api_changes/api_changes_0.82.rst b/doc/api/prev_api_changes/api_changes_0.82.rst new file mode 100644 index 000000000000..31a90fca52d4 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.82.rst @@ -0,0 +1,52 @@ +Changes for 0.82 +================ + +.. code-block:: text + + - toolbar import change in GTKAgg, GTKCairo and WXAgg + + - Added subplot config tool to GTK* backends -- note you must now + import the NavigationToolbar2 from your backend of choice rather + than from backend_gtk because it needs to know about the backend + specific canvas -- see examples/embedding_in_gtk2.py. Ditto for + wx backend -- see examples/embedding_in_wxagg.py + + + - hist bin change + + Sean Richards notes there was a problem in the way we created + the binning for histogram, which made the last bin + underrepresented. From his post: + + I see that hist uses the linspace function to create the bins + and then uses searchsorted to put the values in their correct + bin. That's all good but I am confused over the use of linspace + for the bin creation. I wouldn't have thought that it does + what is needed, to quote the docstring it creates a "Linear + spaced array from min to max". For it to work correctly + shouldn't the values in the bins array be the same bound for + each bin? (i.e. each value should be the lower bound of a + bin). To provide the correct bins for hist would it not be + something like + + def bins(xmin, xmax, N): + if N==1: return xmax + dx = (xmax-xmin)/N # instead of N-1 + return xmin + dx*arange(N) + + + This suggestion is implemented in 0.81. My test script with these + changes does not reveal any bias in the binning + + from matplotlib.numerix.mlab import randn, rand, zeros, Float + from matplotlib.mlab import hist, mean + + Nbins = 50 + Ntests = 200 + results = zeros((Ntests,Nbins), typecode=Float) + for i in range(Ntests): + print 'computing', i + x = rand(10000) + n, bins = hist(x, Nbins) + results[i] = n + print mean(results) diff --git a/doc/api/prev_api_changes/api_changes_0.83.rst b/doc/api/prev_api_changes/api_changes_0.83.rst new file mode 100644 index 000000000000..267951c18aee --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.83.rst @@ -0,0 +1,24 @@ +Changes for 0.83 +================ + +.. code-block:: text + + - Made HOME/.matplotlib the new config dir where the matplotlibrc + file, the ttf.cache, and the tex.cache live. The new default + filenames in .matplotlib have no leading dot and are not hidden. + e.g., the new names are matplotlibrc, tex.cache, and ttffont.cache. + This is how ipython does it so it must be right. + + If old files are found, a warning is issued and they are moved to + the new location. + + - backends/__init__.py no longer imports new_figure_manager, + draw_if_interactive and show from the default backend, but puts + these imports into a call to pylab_setup. Also, the Toolbar is no + longer imported from WX/WXAgg. New usage: + + from backends import pylab_setup + new_figure_manager, draw_if_interactive, show = pylab_setup() + + - Moved Figure.get_width_height() to FigureCanvasBase. It now + returns int instead of float. diff --git a/doc/api/prev_api_changes/api_changes_0.84.rst b/doc/api/prev_api_changes/api_changes_0.84.rst new file mode 100644 index 000000000000..7dabe214e3cc --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.84.rst @@ -0,0 +1,18 @@ +Changes for 0.84 +================ + +.. code-block:: text + + Unified argument handling between hlines and vlines. Both now + take optionally a fmt argument (as in plot) and a keyword args + that can be passed onto Line2D. + + Removed all references to "data clipping" in rc and lines.py since + these were not used and not optimized. I'm sure they'll be + resurrected later with a better implementation when needed. + + 'set' removed - no more deprecation warnings. Use 'setp' instead. + + Backend developers: Added flipud method to image and removed it + from to_str. Removed origin kwarg from backend.draw_image. + origin is handled entirely by the frontend now. diff --git a/doc/api/prev_api_changes/api_changes_0.85.rst b/doc/api/prev_api_changes/api_changes_0.85.rst new file mode 100644 index 000000000000..29f646e0e9d8 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.85.rst @@ -0,0 +1,43 @@ +Changes for 0.85 +================ + +.. code-block:: text + + Made xtick and ytick separate props in rc + + made pos=None the default for tick formatters rather than 0 to + indicate "not supplied" + + Removed "feature" of minor ticks which prevents them from + overlapping major ticks. Often you want major and minor ticks at + the same place, and can offset the major ticks with the pad. This + could be made configurable + + Changed the internal structure of contour.py to a more OO style. + Calls to contour or contourf in axes.py or pylab.py now return + a ContourSet object which contains references to the + LineCollections or PolyCollections created by the call, + as well as the configuration variables that were used. + The ContourSet object is a "mappable" if a colormap was used. + + Added a clip_ends kwarg to contourf. From the docstring: + * clip_ends = True + If False, the limits for color scaling are set to the + minimum and maximum contour levels. + True (default) clips the scaling limits. Example: + if the contour boundaries are V = [-100, 2, 1, 0, 1, 2, 100], + then the scaling limits will be [-100, 100] if clip_ends + is False, and [-3, 3] if clip_ends is True. + Added kwargs linewidths, antialiased, and nchunk to contourf. These + are experimental; see the docstring. + + Changed Figure.colorbar(): + kw argument order changed; + if mappable arg is a non-filled ContourSet, colorbar() shows + lines instead hof polygons. + if mappable arg is a filled ContourSet with clip_ends=True, + the endpoints are not labelled, so as to give the + correct impression of open-endedness. + + Changed LineCollection.get_linewidths to get_linewidth, for + consistency. diff --git a/doc/api/prev_api_changes/api_changes_0.86.rst b/doc/api/prev_api_changes/api_changes_0.86.rst new file mode 100644 index 000000000000..5e0c813347b2 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.86.rst @@ -0,0 +1,28 @@ +Changes for 0.86 +================ + +.. code-block:: text + + Matplotlib data is installed into the matplotlib module. + This is similar to package_data. This should get rid of + having to check for many possibilities in _get_data_path(). + The MATPLOTLIBDATA env key is still checked first to allow + for flexibility. + + 1) Separated the color table data from cm.py out into + a new file, _cm.py, to make it easier to find the actual + code in cm.py and to add new colormaps. Everything + from _cm.py is imported by cm.py, so the split should be + transparent. + 2) Enabled automatic generation of a colormap from + a list of colors in contour; see modified + examples/contour_demo.py. + 3) Support for imshow of a masked array, with the + ability to specify colors (or no color at all) for + masked regions, and for regions that are above or + below the normally mapped region. See + examples/image_masked.py. + 4) In support of the above, added two new classes, + ListedColormap, and no_norm, to colors.py, and modified + the Colormap class to include common functionality. Added + a clip kwarg to the normalize class. diff --git a/doc/api/prev_api_changes/api_changes_0.87.7.rst b/doc/api/prev_api_changes/api_changes_0.87.7.rst new file mode 100644 index 000000000000..c3538c6f7432 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.87.7.rst @@ -0,0 +1,89 @@ + + +Changes for 0.87.7 +================== + +.. code-block:: text + + Completely reworked the annotations API because I found the old + API cumbersome. The new design is much more legible and easy to + read. See matplotlib.text.Annotation and + examples/annotation_demo.py + + markeredgecolor and markerfacecolor cannot be configured in + matplotlibrc any more. Instead, markers are generally colored + automatically based on the color of the line, unless marker colors + are explicitly set as kwargs - NN + + Changed default comment character for load to '#' - JDH + + math_parse_s_ft2font_svg from mathtext.py & mathtext2.py now returns + width, height, svg_elements. svg_elements is an instance of Bunch ( + cmbook.py) and has the attributes svg_glyphs and svg_lines, which are both + lists. + + Renderer.draw_arc now takes an additional parameter, rotation. + It specifies to draw the artist rotated in degrees anti- + clockwise. It was added for rotated ellipses. + + Renamed Figure.set_figsize_inches to Figure.set_size_inches to + better match the get method, Figure.get_size_inches. + + Removed the copy_bbox_transform from transforms.py; added + shallowcopy methods to all transforms. All transforms already + had deepcopy methods. + + FigureManager.resize(width, height): resize the window + specified in pixels + + barh: x and y args have been renamed to width and bottom + respectively, and their order has been swapped to maintain + a (position, value) order. + + bar and barh: now accept kwarg 'edgecolor'. + + bar and barh: The left, height, width and bottom args can + now all be scalars or sequences; see docstring. + + barh: now defaults to edge aligned instead of center + aligned bars + + bar, barh and hist: Added a keyword arg 'align' that + controls between edge or center bar alignment. + + Collections: PolyCollection and LineCollection now accept + vertices or segments either in the original form [(x,y), + (x,y), ...] or as a 2D numerix array, with X as the first column + and Y as the second. Contour and quiver output the numerix + form. The transforms methods Bbox.update() and + Transformation.seq_xy_tups() now accept either form. + + Collections: LineCollection is now a ScalarMappable like + PolyCollection, etc. + + Specifying a grayscale color as a float is deprecated; use + a string instead, e.g., 0.75 -> '0.75'. + + Collections: initializers now accept any mpl color arg, or + sequence of such args; previously only a sequence of rgba + tuples was accepted. + + Colorbar: completely new version and api; see docstring. The + original version is still accessible as colorbar_classic, but + is deprecated. + + Contourf: "extend" kwarg replaces "clip_ends"; see docstring. + Masked array support added to pcolormesh. + + Modified aspect-ratio handling: + Removed aspect kwarg from imshow + Axes methods: + set_aspect(self, aspect, adjustable=None, anchor=None) + set_adjustable(self, adjustable) + set_anchor(self, anchor) + Pylab interface: + axis('image') + + Backend developers: ft2font's load_char now takes a flags + argument, which you can OR together from the LOAD_XXX + constants. diff --git a/doc/api/prev_api_changes/api_changes_0.90.0.rst b/doc/api/prev_api_changes/api_changes_0.90.0.rst new file mode 100644 index 000000000000..7bbdfc06c760 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.90.0.rst @@ -0,0 +1,40 @@ +Changes for 0.90.0 +================== + +.. code-block:: text + + All artists now implement a "pick" method which users should not + call. Rather, set the "picker" property of any artist you want to + pick on (the epsilon distance in points for a hit test) and + register with the "pick_event" callback. See + examples/pick_event_demo.py for details + + Bar, barh, and hist have "log" binary kwarg: log=True + sets the ordinate to a log scale. + + Boxplot can handle a list of vectors instead of just + an array, so vectors can have different lengths. + + Plot can handle 2-D x and/or y; it plots the columns. + + Added linewidth kwarg to bar and barh. + + Made the default Artist._transform None (rather than invoking + identity_transform for each artist only to have it overridden + later). Use artist.get_transform() rather than artist._transform, + even in derived classes, so that the default transform will be + created lazily as needed + + New LogNorm subclass of Normalize added to colors.py. + All Normalize subclasses have new inverse() method, and + the __call__() method has a new clip kwarg. + + Changed class names in colors.py to match convention: + normalize -> Normalize, no_norm -> NoNorm. Old names + are still available for now. + + Removed obsolete pcolor_classic command and method. + + Removed lineprops and markerprops from the Annotation code and + replaced them with an arrow configurable with kwarg arrowprops. + See examples/annotation_demo.py - JDH diff --git a/doc/api/prev_api_changes/api_changes_0.90.1.rst b/doc/api/prev_api_changes/api_changes_0.90.1.rst new file mode 100644 index 000000000000..89311d4ed102 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.90.1.rst @@ -0,0 +1,65 @@ + +Changes for 0.90.1 +================== + +.. code-block:: text + + The file dviread.py has a (very limited and fragile) dvi reader + for usetex support. The API might change in the future so don't + depend on it yet. + + Removed deprecated support for a float value as a gray-scale; + now it must be a string, like '0.5'. Added alpha kwarg to + ColorConverter.to_rgba_list. + + New method set_bounds(vmin, vmax) for formatters, locators sets + the viewInterval and dataInterval from floats. + + Removed deprecated colorbar_classic. + + Line2D.get_xdata and get_ydata valid_only=False kwarg is replaced + by orig=True. When True, it returns the original data, otherwise + the processed data (masked, converted) + + Some modifications to the units interface. + units.ConversionInterface.tickers renamed to + units.ConversionInterface.axisinfo and it now returns a + units.AxisInfo object rather than a tuple. This will make it + easier to add axis info functionality (e.g., I added a default label + on this iteration) w/o having to change the tuple length and hence + the API of the client code every time new functionality is added. + Also, units.ConversionInterface.convert_to_value is now simply + named units.ConversionInterface.convert. + + Axes.errorbar uses Axes.vlines and Axes.hlines to draw its error + limits int he vertical and horizontal direction. As you'll see + in the changes below, these functions now return a LineCollection + rather than a list of lines. The new return signature for + errorbar is ylins, caplines, errorcollections where + errorcollections is a xerrcollection, yerrcollection + + Axes.vlines and Axes.hlines now create and returns a LineCollection, not a list + of lines. This is much faster. The kwarg signature has changed, + so consult the docs + + MaxNLocator accepts a new Boolean kwarg ('integer') to force + ticks to integer locations. + + Commands that pass an argument to the Text constructor or to + Text.set_text() now accept any object that can be converted + with '%s'. This affects xlabel(), title(), etc. + + Barh now takes a **kwargs dict instead of most of the old + arguments. This helps ensure that bar and barh are kept in sync, + but as a side effect you can no longer pass e.g., color as a + positional argument. + + ft2font.get_charmap() now returns a dict that maps character codes + to glyph indices (until now it was reversed) + + Moved data files into lib/matplotlib so that setuptools' develop + mode works. Re-organized the mpl-data layout so that this source + structure is maintained in the installation. (i.e., the 'fonts' and + 'images' sub-directories are maintained in site-packages.). + Suggest removing site-packages/matplotlib/mpl-data and + ~/.matplotlib/ttffont.cache before installing diff --git a/doc/api/prev_api_changes/api_changes_0.91.0.rst b/doc/api/prev_api_changes/api_changes_0.91.0.rst new file mode 100644 index 000000000000..5d871128e5d4 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.91.0.rst @@ -0,0 +1,70 @@ + +Changes for 0.91.0 +================== + +* Changed :func:`cbook.is_file_like` to + :func:`cbook.is_writable_file_like` and corrected behavior. + +* Added ax kwarg to :func:`pyplot.colorbar` and + :meth:`Figure.colorbar` so that one can specify the axes object from + which space for the colorbar is to be taken, if one does not want to + make the colorbar axes manually. + +* Changed :func:`cbook.reversed` so it yields a tuple rather than a + (index, tuple). This agrees with the python reversed builtin, + and cbook only defines reversed if python doesn't provide the + builtin. + +* Made skiprows=1 the default on :func:`csv2rec` + +* The gd and paint backends have been deleted. + +* The errorbar method and function now accept additional kwargs + so that upper and lower limits can be indicated by capping the + bar with a caret instead of a straight line segment. + +* The :mod:`matplotlib.dviread` file now has a parser for files like + psfonts.map and pdftex.map, to map TeX font names to external files. + +* The file :mod:`matplotlib.type1font` contains a new class for Type 1 + fonts. Currently it simply reads pfa and pfb format files and + stores the data in a way that is suitable for embedding in pdf + files. In the future the class might actually parse the font to + allow e.g., subsetting. + +* :mod:`matplotlib.FT2Font` now supports :meth:`FT_Attach_File`. In + practice this can be used to read an afm file in addition to a + pfa/pfb file, to get metrics and kerning information for a Type 1 + font. + +* The :class:`AFM` class now supports querying CapHeight and stem + widths. The get_name_char method now has an isord kwarg like + get_width_char. + +* Changed :func:`pcolor` default to shading='flat'; but as noted now in the + docstring, it is preferable to simply use the edgecolor kwarg. + +* The mathtext font commands (``\cal``, ``\rm``, ``\it``, ``\tt``) now + behave as TeX does: they are in effect until the next font change + command or the end of the grouping. Therefore uses of ``$\cal{R}$`` + should be changed to ``${\cal R}$``. Alternatively, you may use the + new LaTeX-style font commands (``\mathcal``, ``\mathrm``, + ``\mathit``, ``\mathtt``) which do affect the following group, + e.g., ``$\mathcal{R}$``. + +* Text creation commands have a new default linespacing and a new + ``linespacing`` kwarg, which is a multiple of the maximum vertical + extent of a line of ordinary text. The default is 1.2; + ``linespacing=2`` would be like ordinary double spacing, for example. + +* Changed default kwarg in + :meth:`matplotlib.colors.Normalize.__init__`` to ``clip=False``; + clipping silently defeats the purpose of the special over, under, + and bad values in the colormap, thereby leading to unexpected + behavior. The new default should reduce such surprises. + +* Made the emit property of :meth:`~matplotlib.axes.Axes.set_xlim` and + :meth:`~matplotlib.axes.Axes.set_ylim` ``True`` by default; removed + the Axes custom callback handling into a 'callbacks' attribute which + is a :class:`~matplotlib.cbook.CallbackRegistry` instance. This now + supports the 'xlim_changed' and 'ylim_changed' Axes events. diff --git a/doc/api/prev_api_changes/api_changes_0.91.2.rst b/doc/api/prev_api_changes/api_changes_0.91.2.rst new file mode 100644 index 000000000000..3c33abf7e187 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.91.2.rst @@ -0,0 +1,16 @@ + +Changes for 0.91.2 +================== + +* For :func:`csv2rec`, checkrows=0 is the new default indicating all rows + will be checked for type inference + +* A warning is issued when an image is drawn on log-scaled axes, since + it will not log-scale the image data. + +* Moved :func:`rec2gtk` to :mod:`matplotlib.toolkits.gtktools` + +* Moved :func:`rec2excel` to :mod:`matplotlib.toolkits.exceltools` + +* Removed, dead/experimental ExampleInfo, Namespace and Importer + code from :mod:`matplotlib.__init__` diff --git a/doc/api/prev_api_changes/api_changes_0.98.0.rst b/doc/api/prev_api_changes/api_changes_0.98.0.rst new file mode 100644 index 000000000000..ffea743742b9 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.98.0.rst @@ -0,0 +1,312 @@ + + +Changes for 0.98.0 +================== + +* :func:`matplotlib.image.imread` now no longer always returns RGBA data---if + the image is luminance or RGB, it will return a MxN or MxNx3 array + if possible. Also uint8 is no longer always forced to float. + +* Rewrote the :class:`matplotlib.cm.ScalarMappable` callback + infrastructure to use :class:`matplotlib.cbook.CallbackRegistry` + rather than custom callback handling. Any users of + :meth:`matplotlib.cm.ScalarMappable.add_observer` of the + :class:`~matplotlib.cm.ScalarMappable` should use the + :attr:`matplotlib.cm.ScalarMappable.callbacks` + :class:`~matplotlib.cbook.CallbackRegistry` instead. + +* New axes function and Axes method provide control over the plot + color cycle: :func:`matplotlib.axes.set_default_color_cycle` and + :meth:`matplotlib.axes.Axes.set_color_cycle`. + +* Matplotlib now requires Python 2.4, so :mod:`matplotlib.cbook` will + no longer provide :class:`set`, :func:`enumerate`, :func:`reversed` + or :func:`izip` compatibility functions. + +* In Numpy 1.0, bins are specified by the left edges only. The axes + method :meth:`matplotlib.axes.Axes.hist` now uses future Numpy 1.3 + semantics for histograms. Providing ``binedges``, the last value gives + the upper-right edge now, which was implicitly set to +infinity in + Numpy 1.0. This also means that the last bin doesn't contain upper + outliers any more by default. + +* New axes method and pyplot function, + :func:`~matplotlib.pyplot.hexbin`, is an alternative to + :func:`~matplotlib.pyplot.scatter` for large datasets. It makes + something like a :func:`~matplotlib.pyplot.pcolor` of a 2-D + histogram, but uses hexagonal bins. + +* New kwarg, ``symmetric``, in :class:`matplotlib.ticker.MaxNLocator` + allows one require an axis to be centered around zero. + +* Toolkits must now be imported from ``mpl_toolkits`` (not ``matplotlib.toolkits``) + +Notes about the transforms refactoring +-------------------------------------- + +A major new feature of the 0.98 series is a more flexible and +extensible transformation infrastructure, written in Python/Numpy +rather than a custom C extension. + +The primary goal of this refactoring was to make it easier to +extend matplotlib to support new kinds of projections. This is +mostly an internal improvement, and the possible user-visible +changes it allows are yet to come. + +See :mod:`matplotlib.transforms` for a description of the design of +the new transformation framework. + +For efficiency, many of these functions return views into Numpy +arrays. This means that if you hold on to a reference to them, +their contents may change. If you want to store a snapshot of +their current values, use the Numpy array method copy(). + +The view intervals are now stored only in one place -- in the +:class:`matplotlib.axes.Axes` instance, not in the locator instances +as well. This means locators must get their limits from their +:class:`matplotlib.axis.Axis`, which in turn looks up its limits from +the :class:`~matplotlib.axes.Axes`. If a locator is used temporarily +and not assigned to an Axis or Axes, (e.g., in +:mod:`matplotlib.contour`), a dummy axis must be created to store its +bounds. Call :meth:`matplotlib.ticker.Locator.create_dummy_axis` to +do so. + +The functionality of :class:`Pbox` has been merged with +:class:`~matplotlib.transforms.Bbox`. Its methods now all return +copies rather than modifying in place. + +The following lists many of the simple changes necessary to update +code from the old transformation framework to the new one. In +particular, methods that return a copy are named with a verb in the +past tense, whereas methods that alter an object in place are named +with a verb in the present tense. + +:mod:`matplotlib.transforms` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +:meth:`Bbox.get_bounds` :attr:`transforms.Bbox.bounds` +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.width` :attr:`transforms.Bbox.width` +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.height` :attr:`transforms.Bbox.height` +------------------------------------------------------------ ------------------------------------------------------------ +`Bbox.intervalx().get_bounds()` :attr:`transforms.Bbox.intervalx` +`Bbox.intervalx().set_bounds()` [:attr:`Bbox.intervalx` is now a property.] +------------------------------------------------------------ ------------------------------------------------------------ +`Bbox.intervaly().get_bounds()` :attr:`transforms.Bbox.intervaly` +`Bbox.intervaly().set_bounds()` [:attr:`Bbox.intervaly` is now a property.] +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.xmin` :attr:`transforms.Bbox.x0` or + :attr:`transforms.Bbox.xmin` [1]_ +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.ymin` :attr:`transforms.Bbox.y0` or + :attr:`transforms.Bbox.ymin` [1]_ +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.xmax` :attr:`transforms.Bbox.x1` or + :attr:`transforms.Bbox.xmax` [1]_ +------------------------------------------------------------ ------------------------------------------------------------ +:meth:`Bbox.ymax` :attr:`transforms.Bbox.y1` or + :attr:`transforms.Bbox.ymax` [1]_ +------------------------------------------------------------ ------------------------------------------------------------ +`Bbox.overlaps(bboxes)` `Bbox.count_overlaps(bboxes)` +------------------------------------------------------------ ------------------------------------------------------------ +`bbox_all(bboxes)` `Bbox.union(bboxes)` + [:meth:`transforms.Bbox.union` is a staticmethod.] +------------------------------------------------------------ ------------------------------------------------------------ +`lbwh_to_bbox(l, b, w, h)` `Bbox.from_bounds(x0, y0, w, h)` + [:meth:`transforms.Bbox.from_bounds` is a staticmethod.] +------------------------------------------------------------ ------------------------------------------------------------ +`inverse_transform_bbox(trans, bbox)` `Bbox.inverse_transformed(trans)` +------------------------------------------------------------ ------------------------------------------------------------ +`Interval.contains_open(v)` `interval_contains_open(tuple, v)` +------------------------------------------------------------ ------------------------------------------------------------ +`Interval.contains(v)` `interval_contains(tuple, v)` +------------------------------------------------------------ ------------------------------------------------------------ +`identity_transform()` :class:`matplotlib.transforms.IdentityTransform` +------------------------------------------------------------ ------------------------------------------------------------ +`blend_xy_sep_transform(xtrans, ytrans)` `blended_transform_factory(xtrans, ytrans)` +------------------------------------------------------------ ------------------------------------------------------------ +`scale_transform(xs, ys)` `Affine2D().scale(xs[, ys])` +------------------------------------------------------------ ------------------------------------------------------------ +`get_bbox_transform(boxin, boxout)` `BboxTransform(boxin, boxout)` or + `BboxTransformFrom(boxin)` or + `BboxTransformTo(boxout)` +------------------------------------------------------------ ------------------------------------------------------------ +`Transform.seq_xy_tup(points)` `Transform.transform(points)` +------------------------------------------------------------ ------------------------------------------------------------ +`Transform.inverse_xy_tup(points)` `Transform.inverted().transform(points)` +============================================================ ============================================================ + +.. [1] The :class:`~matplotlib.transforms.Bbox` is bound by the points + (x0, y0) to (x1, y1) and there is no defined order to these points, + that is, x0 is not necessarily the left edge of the box. To get + the left edge of the :class:`Bbox`, use the read-only property + :attr:`~matplotlib.transforms.Bbox.xmin`. + +:mod:`matplotlib.axes` +~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`Axes.get_position()` :meth:`matplotlib.axes.Axes.get_position` [2]_ +------------------------------------------------------------ ------------------------------------------------------------ +`Axes.set_position()` :meth:`matplotlib.axes.Axes.set_position` [3]_ +------------------------------------------------------------ ------------------------------------------------------------ +`Axes.toggle_log_lineary()` :meth:`matplotlib.axes.Axes.set_yscale` [4]_ +------------------------------------------------------------ ------------------------------------------------------------ +`Subplot` class removed. +============================================================ ============================================================ + +The :class:`Polar` class has moved to :mod:`matplotlib.projections.polar`. + +.. [2] :meth:`matplotlib.axes.Axes.get_position` used to return a list + of points, now it returns a :class:`matplotlib.transforms.Bbox` + instance. + +.. [3] :meth:`matplotlib.axes.Axes.set_position` now accepts either + four scalars or a :class:`matplotlib.transforms.Bbox` instance. + +.. [4] Since the recfactoring allows for more than two scale types + ('log' or 'linear'), it no longer makes sense to have a toggle. + `Axes.toggle_log_lineary()` has been removed. + +:mod:`matplotlib.artist` +~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`Artist.set_clip_path(path)` `Artist.set_clip_path(path, transform)` [5]_ +============================================================ ============================================================ + +.. [5] :meth:`matplotlib.artist.Artist.set_clip_path` now accepts a + :class:`matplotlib.path.Path` instance and a + :class:`matplotlib.transforms.Transform` that will be applied to + the path immediately before clipping. + +:mod:`matplotlib.collections` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`linestyle` `linestyles` [6]_ +============================================================ ============================================================ + +.. [6] Linestyles are now treated like all other collection + attributes, i.e. a single value or multiple values may be + provided. + +:mod:`matplotlib.colors` +~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`ColorConvertor.to_rgba_list(c)` `ColorConvertor.to_rgba_array(c)` + [:meth:`matplotlib.colors.ColorConvertor.to_rgba_array` + returns an Nx4 Numpy array of RGBA color quadruples.] +============================================================ ============================================================ + +:mod:`matplotlib.contour` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`Contour._segments` :meth:`matplotlib.contour.Contour.get_paths`` [Returns a + list of :class:`matplotlib.path.Path` instances.] +============================================================ ============================================================ + +:mod:`matplotlib.figure` +~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`Figure.dpi.get()` / `Figure.dpi.set()` :attr:`matplotlib.figure.Figure.dpi` *(a property)* +============================================================ ============================================================ + +:mod:`matplotlib.patches` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`Patch.get_verts()` :meth:`matplotlib.patches.Patch.get_path` [Returns a + :class:`matplotlib.path.Path` instance] +============================================================ ============================================================ + +:mod:`matplotlib.backend_bases` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +============================================================ ============================================================ +Old method New method +============================================================ ============================================================ +`GraphicsContext.set_clip_rectangle(tuple)` `GraphicsContext.set_clip_rectangle(bbox)` +------------------------------------------------------------ ------------------------------------------------------------ +`GraphicsContext.get_clip_path()` `GraphicsContext.get_clip_path()` [7]_ +------------------------------------------------------------ ------------------------------------------------------------ +`GraphicsContext.set_clip_path()` `GraphicsContext.set_clip_path()` [8]_ +============================================================ ============================================================ + +:class:`~matplotlib.backend_bases.RendererBase` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +New methods: + + * :meth:`draw_path(self, gc, path, transform, rgbFace) + ` + + * :meth:`draw_markers(self, gc, marker_path, marker_trans, path, + trans, rgbFace) + ` + + * :meth:`draw_path_collection(self, master_transform, cliprect, + clippath, clippath_trans, paths, all_transforms, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds) + ` + *[optional]* + +Changed methods: + + * `draw_image(self, x, y, im, bbox)` is now + :meth:`draw_image(self, x, y, im, bbox, clippath, clippath_trans) + ` + +Removed methods: + + * `draw_arc` + + * `draw_line_collection` + + * `draw_line` + + * `draw_lines` + + * `draw_point` + + * `draw_quad_mesh` + + * `draw_poly_collection` + + * `draw_polygon` + + * `draw_rectangle` + + * `draw_regpoly_collection` + +.. [7] :meth:`matplotlib.backend_bases.GraphicsContext.get_clip_path` + returns a tuple of the form (*path*, *affine_transform*), where + *path* is a :class:`matplotlib.path.Path` instance and + *affine_transform* is a :class:`matplotlib.transforms.Affine2D` + instance. + +.. [8] :meth:`matplotlib.backend_bases.GraphicsContext.set_clip_path` + now only accepts a :class:`matplotlib.transforms.TransformedPath` + instance. diff --git a/doc/api/prev_api_changes/api_changes_0.98.1.rst b/doc/api/prev_api_changes/api_changes_0.98.1.rst new file mode 100644 index 000000000000..fd6e1bae6b20 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.98.1.rst @@ -0,0 +1,5 @@ +Changes for 0.98.1 +================== + +* Removed broken :mod:`matplotlib.axes3d` support and replaced it with + a non-implemented error pointing to 0.91.x diff --git a/doc/api/prev_api_changes/api_changes_0.98.x.rst b/doc/api/prev_api_changes/api_changes_0.98.x.rst new file mode 100644 index 000000000000..4020d8715d36 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.98.x.rst @@ -0,0 +1,110 @@ + +Changes for 0.98.x +================== +* psd(), csd(), and cohere() will now automatically wrap negative + frequency components to the beginning of the returned arrays. + This is much more sensible behavior and makes them consistent + with specgram(). The previous behavior was more of an oversight + than a design decision. + +* Added new keyword parameters *nonposx*, *nonposy* to + :class:`matplotlib.axes.Axes` methods that set log scale + parameters. The default is still to mask out non-positive + values, but the kwargs accept 'clip', which causes non-positive + values to be replaced with a very small positive value. + +* Added new :func:`matplotlib.pyplot.fignum_exists` and + :func:`matplotlib.pyplot.get_fignums`; they merely expose + information that had been hidden in :mod:`matplotlib._pylab_helpers`. + +* Deprecated numerix package. + +* Added new :func:`matplotlib.image.imsave` and exposed it to the + :mod:`matplotlib.pyplot` interface. + +* Remove support for pyExcelerator in exceltools -- use xlwt + instead + +* Changed the defaults of acorr and xcorr to use usevlines=True, + maxlags=10 and normed=True since these are the best defaults + +* Following keyword parameters for :class:`matplotlib.label.Label` are now + deprecated and new set of parameters are introduced. The new parameters + are given as a fraction of the font-size. Also, *scatteryoffsets*, + *fancybox* and *columnspacing* are added as keyword parameters. + + ================ ================ + Deprecated New + ================ ================ + pad borderpad + labelsep labelspacing + handlelen handlelength + handlestextsep handletextpad + axespad borderaxespad + ================ ================ + + +* Removed the configobj and experimental traits rc support + +* Modified :func:`matplotlib.mlab.psd`, :func:`matplotlib.mlab.csd`, + :func:`matplotlib.mlab.cohere`, and :func:`matplotlib.mlab.specgram` + to scale one-sided densities by a factor of 2. Also, optionally + scale the densities by the sampling frequency, which gives true values + of densities that can be integrated by the returned frequency values. + This also gives better MATLAB compatibility. The corresponding + :class:`matplotlib.axes.Axes` methods and :mod:`matplotlib.pyplot` + functions were updated as well. + +* Font lookup now uses a nearest-neighbor approach rather than an + exact match. Some fonts may be different in plots, but should be + closer to what was requested. + +* :meth:`matplotlib.axes.Axes.set_xlim`, + :meth:`matplotlib.axes.Axes.set_ylim` now return a copy of the + :attr:`viewlim` array to avoid modify-in-place surprises. + +* :meth:`matplotlib.afm.AFM.get_fullname` and + :meth:`matplotlib.afm.AFM.get_familyname` no longer raise an + exception if the AFM file does not specify these optional + attributes, but returns a guess based on the required FontName + attribute. + +* Changed precision kwarg in :func:`matplotlib.pyplot.spy`; default is + 0, and the string value 'present' is used for sparse arrays only to + show filled locations. + +* :class:`matplotlib.collections.EllipseCollection` added. + +* Added ``angles`` kwarg to :func:`matplotlib.pyplot.quiver` for more + flexible specification of the arrow angles. + +* Deprecated (raise NotImplementedError) all the mlab2 functions from + :mod:`matplotlib.mlab` out of concern that some of them were not + clean room implementations. + +* Methods :meth:`matplotlib.collections.Collection.get_offsets` and + :meth:`matplotlib.collections.Collection.set_offsets` added to + :class:`~matplotlib.collections.Collection` base class. + +* :attr:`matplotlib.figure.Figure.figurePatch` renamed + :attr:`matplotlib.figure.Figure.patch`; + :attr:`matplotlib.axes.Axes.axesPatch` renamed + :attr:`matplotlib.axes.Axes.patch`; + :attr:`matplotlib.axes.Axes.axesFrame` renamed + :attr:`matplotlib.axes.Axes.frame`. + :meth:`matplotlib.axes.Axes.get_frame`, which returns + :attr:`matplotlib.axes.Axes.patch`, is deprecated. + +* Changes in the :class:`matplotlib.contour.ContourLabeler` attributes + (:func:`matplotlib.pyplot.clabel` function) so that they all have a + form like ``.labelAttribute``. The three attributes that are most + likely to be used by end users, ``.cl``, ``.cl_xy`` and + ``.cl_cvalues`` have been maintained for the moment (in addition to + their renamed versions), but they are deprecated and will eventually + be removed. + +* Moved several functions in :mod:`matplotlib.mlab` and + :mod:`matplotlib.cbook` into a separate module + :mod:`matplotlib.numerical_methods` because they were unrelated to + the initial purpose of mlab or cbook and appeared more coherent + elsewhere. diff --git a/doc/api/prev_api_changes/api_changes_0.99.rst b/doc/api/prev_api_changes/api_changes_0.99.rst new file mode 100644 index 000000000000..4332d3f105d4 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.99.rst @@ -0,0 +1,27 @@ +Changes in 0.99 +=============== + +* pylab no longer provides a load and save function. These are + available in matplotlib.mlab, or you can use numpy.loadtxt and + numpy.savetxt for text files, or np.save and np.load for binary + numpy arrays. + +* User-generated colormaps can now be added to the set recognized + by :func:`matplotlib.cm.get_cmap`. Colormaps can be made the + default and applied to the current image using + :func:`matplotlib.pyplot.set_cmap`. + +* changed use_mrecords default to False in mlab.csv2rec since this is + partially broken + +* Axes instances no longer have a "frame" attribute. Instead, use the + new "spines" attribute. Spines is a dictionary where the keys are + the names of the spines (e.g., 'left','right' and so on) and the + values are the artists that draw the spines. For normal + (rectilinear) axes, these artists are Line2D instances. For other + axes (such as polar axes), these artists may be Patch instances. + +* Polar plots no longer accept a resolution kwarg. Instead, each Path + must specify its own number of interpolation steps. This is + unlikely to be a user-visible change -- if interpolation of data is + required, that should be done before passing it to Matplotlib. diff --git a/doc/api/prev_api_changes/api_changes_0.99.x.rst b/doc/api/prev_api_changes/api_changes_0.99.x.rst new file mode 100644 index 000000000000..d9e8a56fa98d --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_0.99.x.rst @@ -0,0 +1,124 @@ +Changes beyond 0.99.x +===================== + +* The default behavior of :meth:`matplotlib.axes.Axes.set_xlim`, + :meth:`matplotlib.axes.Axes.set_ylim`, and + :meth:`matplotlib.axes.Axes.axis`, and their corresponding + pyplot functions, has been changed: when view limits are + set explicitly with one of these methods, autoscaling is turned + off for the matching axis. A new *auto* kwarg is available to + control this behavior. The limit kwargs have been renamed to + *left* and *right* instead of *xmin* and *xmax*, and *bottom* + and *top* instead of *ymin* and *ymax*. The old names may still + be used, however. + +* There are five new Axes methods with corresponding pyplot + functions to facilitate autoscaling, tick location, and tick + label formatting, and the general appearance of ticks and + tick labels: + + + :meth:`matplotlib.axes.Axes.autoscale` turns autoscaling + on or off, and applies it. + + + :meth:`matplotlib.axes.Axes.margins` sets margins used to + autoscale the :attr:`matplotlib.axes.Axes.viewLim` based on + the :attr:`matplotlib.axes.Axes.dataLim`. + + + :meth:`matplotlib.axes.Axes.locator_params` allows one to + adjust axes locator parameters such as *nbins*. + + + :meth:`matplotlib.axes.Axes.ticklabel_format` is a convenience + method for controlling the :class:`matplotlib.ticker.ScalarFormatter` + that is used by default with linear axes. + + + :meth:`matplotlib.axes.Axes.tick_params` controls direction, size, + visibility, and color of ticks and their labels. + +* The :meth:`matplotlib.axes.Axes.bar` method accepts a *error_kw* + kwarg; it is a dictionary of kwargs to be passed to the + errorbar function. + +* The :meth:`matplotlib.axes.Axes.hist` *color* kwarg now accepts + a sequence of color specs to match a sequence of datasets. + +* The :class:`~matplotlib.collections.EllipseCollection` has been + changed in two ways: + + + There is a new *units* option, 'xy', that scales the ellipse with + the data units. This matches the :class:'~matplotlib.patches.Ellipse` + scaling. + + + The *height* and *width* kwargs have been changed to specify + the height and width, again for consistency with + :class:`~matplotlib.patches.Ellipse`, and to better match + their names; previously they specified the half-height and + half-width. + +* There is a new rc parameter ``axes.color_cycle``, and the color + cycle is now independent of the rc parameter ``lines.color``. + :func:`matplotlib.Axes.set_default_color_cycle` is deprecated. + +* You can now print several figures to one pdf file and modify the + document information dictionary of a pdf file. See the docstrings + of the class :class:`matplotlib.backends.backend_pdf.PdfPages` for + more information. + +* Removed configobj_ and `enthought.traits`_ packages, which are only + required by the experimental traited config and are somewhat out of + date. If needed, install them independently. + +.. _configobj: http://www.voidspace.org.uk/python/configobj.html +.. _`enthought.traits`: http://code.enthought.com/pages/traits.html + +* The new rc parameter ``savefig.extension`` sets the filename extension + that is used by :meth:`matplotlib.figure.Figure.savefig` if its *fname* + argument lacks an extension. + +* In an effort to simplify the backend API, all clipping rectangles + and paths are now passed in using GraphicsContext objects, even + on collections and images. Therefore:: + + draw_path_collection(self, master_transform, cliprect, clippath, + clippath_trans, paths, all_transforms, offsets, + offsetTrans, facecolors, edgecolors, linewidths, + linestyles, antialiaseds, urls) + + # is now + + draw_path_collection(self, gc, master_transform, paths, all_transforms, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls) + + + draw_quad_mesh(self, master_transform, cliprect, clippath, + clippath_trans, meshWidth, meshHeight, coordinates, + offsets, offsetTrans, facecolors, antialiased, + showedges) + + # is now + + draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, + coordinates, offsets, offsetTrans, facecolors, + antialiased, showedges) + + + draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None) + + # is now + + draw_image(self, gc, x, y, im) + +* There are four new Axes methods with corresponding pyplot + functions that deal with unstructured triangular grids: + + + :meth:`matplotlib.axes.Axes.tricontour` draws contour lines + on a triangular grid. + + + :meth:`matplotlib.axes.Axes.tricontourf` draws filled contours + on a triangular grid. + + + :meth:`matplotlib.axes.Axes.tripcolor` draws a pseudocolor + plot on a triangular grid. + + + :meth:`matplotlib.axes.Axes.triplot` draws a triangular grid + as lines and/or markers. diff --git a/doc/api/prev_api_changes/api_changes_1.1.x.rst b/doc/api/prev_api_changes/api_changes_1.1.x.rst new file mode 100644 index 000000000000..8320e2c4fc09 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.1.x.rst @@ -0,0 +1,25 @@ + +Changes in 1.1.x +================ + +* Added new :class:`matplotlib.sankey.Sankey` for generating Sankey diagrams. + +* In :meth:`~matplotlib.pyplot.imshow`, setting *interpolation* to 'nearest' + will now always mean that the nearest-neighbor interpolation is performed. + If you want the no-op interpolation to be performed, choose 'none'. + +* There were errors in how the tri-functions were handling input parameters + that had to be fixed. If your tri-plots are not working correctly anymore, + or you were working around apparent mistakes, please see issue #203 in the + github tracker. When in doubt, use kwargs. + +* The 'symlog' scale had some bad behavior in previous versions. This has now + been fixed and users should now be able to use it without frustrations. + The fixes did result in some minor changes in appearance for some users who + may have been depending on the bad behavior. + +* There is now a common set of markers for all plotting functions. Previously, + some markers existed only for :meth:`~matplotlib.pyplot.scatter` or just for + :meth:`~matplotlib.pyplot.plot`. This is now no longer the case. This merge + did result in a conflict. The string 'd' now means "thin diamond" while + 'D' will mean "regular diamond". diff --git a/doc/api/prev_api_changes/api_changes_1.2.x.rst b/doc/api/prev_api_changes/api_changes_1.2.x.rst new file mode 100644 index 000000000000..05082a1bc321 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.2.x.rst @@ -0,0 +1,145 @@ +Changes in 1.2.x +================ + +* The ``classic`` option of the rc parameter ``toolbar`` is deprecated + and will be removed in the next release. + +* The :meth:`~matplotlib.cbook.isvector` method has been removed since it + is no longer functional. + +* The `rasterization_zorder` property on `~matplotlib.axes.Axes` a + zorder below which artists are rasterized. This has defaulted to + -30000.0, but it now defaults to `None`, meaning no artists will be + rasterized. In order to rasterize artists below a given zorder + value, `set_rasterization_zorder` must be explicitly called. + +* In :meth:`~matplotlib.axes.Axes.scatter`, and `~pyplot.scatter`, + when specifying a marker using a tuple, the angle is now specified + in degrees, not radians. + +* Using :meth:`~matplotlib.axes.Axes.twinx` or + :meth:`~matplotlib.axes.Axes.twiny` no longer overrides the current locaters + and formatters on the axes. + +* In :meth:`~matplotlib.axes.Axes.contourf`, the handling of the *extend* + kwarg has changed. Formerly, the extended ranges were mapped + after to 0, 1 after being normed, so that they always corresponded + to the extreme values of the colormap. Now they are mapped + outside this range so that they correspond to the special + colormap values determined by the + :meth:`~matplotlib.colors.Colormap.set_under` and + :meth:`~matplotlib.colors.Colormap.set_over` methods, which + default to the colormap end points. + +* The new rc parameter ``savefig.format`` replaces ``cairo.format`` and + ``savefig.extension``, and sets the default file format used by + :meth:`matplotlib.figure.Figure.savefig`. + +* In :meth:`~matplotlib.pyplot.pie` and :meth:`~matplotlib.Axes.pie`, one can + now set the radius of the pie; setting the *radius* to 'None' (the default + value), will result in a pie with a radius of 1 as before. + +* Use of :func:`~matplotlib.projections.projection_factory` is now deprecated + in favour of axes class identification using + :func:`~matplotlib.projections.process_projection_requirements` followed by + direct axes class invocation (at the time of writing, functions which do this + are: :meth:`~matplotlib.figure.Figure.add_axes`, + :meth:`~matplotlib.figure.Figure.add_subplot` and + :meth:`~matplotlib.figure.Figure.gca`). Therefore:: + + + key = figure._make_key(*args, **kwargs) + ispolar = kwargs.pop('polar', False) + projection = kwargs.pop('projection', None) + if ispolar: + if projection is not None and projection != 'polar': + raise ValueError('polar and projection args are inconsistent') + projection = 'polar' + ax = projection_factory(projection, self, rect, **kwargs) + key = self._make_key(*args, **kwargs) + + # is now + + projection_class, kwargs, key = \ + process_projection_requirements(self, *args, **kwargs) + ax = projection_class(self, rect, **kwargs) + + This change means that third party objects can expose themselves as + Matplotlib axes by providing a ``_as_mpl_axes`` method. See + :ref:`adding-new-scales` for more detail. + +* A new keyword *extendfrac* in :meth:`~matplotlib.pyplot.colorbar` and + :class:`~matplotlib.colorbar.ColorbarBase` allows one to control the size of + the triangular minimum and maximum extensions on colorbars. + +* A new keyword *capthick* in :meth:`~matplotlib.pyplot.errorbar` has been + added as an intuitive alias to the *markeredgewidth* and *mew* keyword + arguments, which indirectly controlled the thickness of the caps on + the errorbars. For backwards compatibility, specifying either of the + original keyword arguments will override any value provided by + *capthick*. + +* Transform subclassing behaviour is now subtly changed. If your transform + implements a non-affine transformation, then it should override the + ``transform_non_affine`` method, rather than the generic ``transform`` method. + Previously transforms would define ``transform`` and then copy the + method into ``transform_non_affine``:: + + class MyTransform(mtrans.Transform): + def transform(self, xy): + ... + transform_non_affine = transform + + + This approach will no longer function correctly and should be changed to:: + + class MyTransform(mtrans.Transform): + def transform_non_affine(self, xy): + ... + + +* Artists no longer have ``x_isdata`` or ``y_isdata`` attributes; instead + any artist's transform can be interrogated with + ``artist_instance.get_transform().contains_branch(ax.transData)`` + +* Lines added to an axes now take into account their transform when updating the + data and view limits. This means transforms can now be used as a pre-transform. + For instance:: + + >>> import matplotlib.pyplot as plt + >>> import matplotlib.transforms as mtrans + >>> ax = plt.axes() + >>> ax.plot(range(10), transform=mtrans.Affine2D().scale(10) + ax.transData) + >>> print(ax.viewLim) + Bbox('array([[ 0., 0.],\n [ 90., 90.]])') + +* One can now easily get a transform which goes from one transform's coordinate + system to another, in an optimized way, using the new subtract method on a + transform. For instance, to go from data coordinates to axes coordinates:: + + >>> import matplotlib.pyplot as plt + >>> ax = plt.axes() + >>> data2ax = ax.transData - ax.transAxes + >>> print(ax.transData.depth, ax.transAxes.depth) + 3, 1 + >>> print(data2ax.depth) + 2 + + for versions before 1.2 this could only be achieved in a sub-optimal way, + using ``ax.transData + ax.transAxes.inverted()`` (depth is a new concept, + but had it existed it would return 4 for this example). + +* ``twinx`` and ``twiny`` now returns an instance of SubplotBase if + parent axes is an instance of SubplotBase. + +* All Qt3-based backends are now deprecated due to the lack of py3k bindings. + Qt and QtAgg backends will continue to work in v1.2.x for py2.6 + and py2.7. It is anticipated that the Qt3 support will be completely + removed for the next release. + +* :class:`~matplotlib.colors.ColorConverter`, + :class:`~matplotlib.colors.Colormap` and + :class:`~matplotlib.colors.Normalize` now subclasses ``object`` + +* ContourSet instances no longer have a ``transform`` attribute. Instead, + access the transform with the ``get_transform`` method. diff --git a/doc/api/prev_api_changes/api_changes_1.3.x.rst b/doc/api/prev_api_changes/api_changes_1.3.x.rst new file mode 100644 index 000000000000..0c88515ec1ab --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.3.x.rst @@ -0,0 +1,217 @@ +.. _changes_in_1_3: + + +Changes in 1.3.x +================ + +Changes in 1.3.1 +---------------- + +It is rare that we make an API change in a bugfix release, however, +for 1.3.1 since 1.3.0 the following change was made: + +- `text.Text.cached` (used to cache font objects) has been made into a + private variable. Among the obvious encapsulation benefit, this + removes this confusing-looking member from the documentation. + +- The method :meth:`~matplotlib.axes.Axes.hist` now always returns bin + occupancies as an array of type `float`. Previously, it was sometimes + an array of type `int`, depending on the call. + +Code removal +------------ + +* The following items that were deprecated in version 1.2 or earlier + have now been removed completely. + + - The Qt 3.x backends (`qt` and `qtagg`) have been removed in + favor of the Qt 4.x backends (`qt4` and `qt4agg`). + + - The FltkAgg and Emf backends have been removed. + + - The `matplotlib.nxutils` module has been removed. Use the + functionality on `matplotlib.path.Path.contains_point` and + friends instead. + + - Instead of `axes.Axes.get_frame`, use `axes.Axes.patch`. + + - The following `kwargs` to the `legend` function have been + renamed: + + - `pad` -> `borderpad` + - `labelsep` -> `labelspacing` + - `handlelen` -> `handlelength` + - `handletextsep` -> `handletextpad` + - `axespad` -> `borderaxespad` + + Related to this, the following rcParams have been removed: + + - `legend.pad`, `legend.labelsep`, `legend.handlelen`, + `legend.handletextsep` and `legend.axespad` + + - For the `hist` function, instead of `width`, use `rwidth` + (relative width). + + - On `patches.Circle`, the `resolution` kwarg has been removed. + For a circle made up of line segments, use + `patches.CirclePolygon`. + + - The printing functions in the Wx backend have been removed due + to the burden of keeping them up-to-date. + + - `mlab.liaupunov` has been removed. + + - `mlab.save`, `mlab.load`, `pylab.save` and `pylab.load` have + been removed. We recommend using `numpy.savetxt` and + `numpy.loadtxt` instead. + + - `widgets.HorizontalSpanSelector` has been removed. Use + `widgets.SpanSelector` instead. + +Code deprecation +---------------- + +* The CocoaAgg backend has been deprecated, with the possibility for + deletion or resurrection in a future release. + +* The top-level functions in `matplotlib.path` that are implemented in + C++ were never meant to be public. Instead, users should use the + Pythonic wrappers for them in the `path.Path` and + `collections.Collection` classes. Use the following mapping to update + your code: + + - `point_in_path` -> `path.Path.contains_point` + - `get_path_extents` -> `path.Path.get_extents` + - `point_in_path_collection` -> `collection.Collection.contains` + - `path_in_path` -> `path.Path.contains_path` + - `path_intersects_path` -> `path.Path.intersects_path` + - `convert_path_to_polygons` -> `path.Path.to_polygons` + - `cleanup_path` -> `path.Path.cleaned` + - `points_in_path` -> `path.Path.contains_points` + - `clip_path_to_rect` -> `path.Path.clip_to_bbox` + +* `matplotlib.colors.normalize` and `matplotlib.colors.no_norm` have + been deprecated in favour of `matplotlib.colors.Normalize` and + `matplotlib.colors.NoNorm` respectively. + +* The `ScalarMappable` class' `set_colorbar` is now + deprecated. Instead, the + :attr:`matplotlib.cm.ScalarMappable.colorbar` attribute should be + used. In previous Matplotlib versions this attribute was an + undocumented tuple of ``(colorbar_instance, colorbar_axes)`` but is + now just ``colorbar_instance``. To get the colorbar axes it is + possible to just use the + :attr:`~matplotlib.colorbar.ColorbarBase.ax` attribute on a colorbar + instance. + +* The `~matplotlib.mpl` module is now deprecated. Those who relied on this + module should transition to simply using ``import matplotlib as mpl``. + +Code changes +------------ + +* :class:`~matplotlib.patches.Patch` now fully supports using RGBA values for + its ``facecolor`` and ``edgecolor`` attributes, which enables faces and + edges to have different alpha values. If the + :class:`~matplotlib.patches.Patch` object's ``alpha`` attribute is set to + anything other than ``None``, that value will override any alpha-channel + value in both the face and edge colors. Previously, if + :class:`~matplotlib.patches.Patch` had ``alpha=None``, the alpha component + of ``edgecolor`` would be applied to both the edge and face. + +* The optional ``isRGB`` argument to + :meth:`~matplotlib.backend_bases.GraphicsContextBase.set_foreground` (and + the other GraphicsContext classes that descend from it) has been renamed to + ``isRGBA``, and should now only be set to ``True`` if the ``fg`` color + argument is known to be an RGBA tuple. + +* For :class:`~matplotlib.patches.Patch`, the ``capstyle`` used is now + ``butt``, to be consistent with the default for most other objects, and to + avoid problems with non-solid ``linestyle`` appearing solid when using a + large ``linewidth``. Previously, :class:`~matplotlib.patches.Patch` used + ``capstyle='projecting'``. + +* `Path` objects can now be marked as `readonly` by passing + `readonly=True` to its constructor. The built-in path singletons, + obtained through `Path.unit*` class methods return readonly paths. + If you have code that modified these, you will need to make a + deepcopy first, using either:: + + import copy + path = copy.deepcopy(Path.unit_circle()) + + # or + + path = Path.unit_circle().deepcopy() + + Deep copying a `Path` always creates an editable (i.e. non-readonly) + `Path`. + +* The list at ``Path.NUM_VERTICES`` was replaced by a dictionary mapping + Path codes to the number of expected vertices at + :attr:`~matplotlib.path.Path.NUM_VERTICES_FOR_CODE`. + +* To support XKCD style plots, the :func:`matplotlib.path.cleanup_path` + method's signature was updated to require a sketch argument. Users of + :func:`matplotlib.path.cleanup_path` are encouraged to use the new + :meth:`~matplotlib.path.Path.cleaned` Path method. + +* Data limits on a plot now start from a state of having "null" + limits, rather than limits in the range (0, 1). This has an effect + on artists that only control limits in one direction, such as + `axvline` and `axhline`, since their limits will not longer also + include the range (0, 1). This fixes some problems where the + computed limits would be dependent on the order in which artists + were added to the axes. + +* Fixed a bug in setting the position for the right/top spine with data + position type. Previously, it would draw the right or top spine at + +1 data offset. + +* In :class:`~matplotlib.patches.FancyArrow`, the default arrow head + width, ``head_width``, has been made larger to produce a visible + arrow head. The new value of this kwarg is ``head_width = 20 * + width``. + +* It is now possible to provide ``number of levels + 1`` colors in the case of + `extend='both'` for contourf (or just ``number of levels`` colors for an + extend value ``min`` or ``max``) such that the resulting colormap's + ``set_under`` and ``set_over`` are defined appropriately. Any other number + of colors will continue to behave as before (if more colors are provided + than levels, the colors will be unused). A similar change has been applied + to contour, where ``extend='both'`` would expect ``number of levels + 2`` + colors. + +* A new keyword *extendrect* in :meth:`~matplotlib.pyplot.colorbar` and + :class:`~matplotlib.colorbar.ColorbarBase` allows one to control the shape + of colorbar extensions. + +* The extension of :class:`~matplotlib.widgets.MultiCursor` to both vertical + (default) and/or horizontal cursor implied that ``self.line`` is replaced + by ``self.vline`` for vertical cursors lines and ``self.hline`` is added + for the horizontal cursors lines. + +* On POSIX platforms, the :func:`~matplotlib.cbook.report_memory` function + raises :class:`NotImplementedError` instead of :class:`OSError` if the + :command:`ps` command cannot be run. + +* The :func:`matplotlib.cbook.check_output` function has been moved to + :func:`matplotlib.compat.subprocess`. + +Configuration and rcParams +-------------------------- + +* On Linux, the user-specific `matplotlibrc` configuration file is now + located in `~/.config/matplotlib/matplotlibrc` to conform to the + `XDG Base Directory Specification + `_. + +* The `font.*` rcParams now affect only text objects created after the + rcParam has been set, and will not retroactively affect already + existing text objects. This brings their behavior in line with most + other rcParams. + +* Removed call of :meth:`~matplotlib.axes.Axes.grid` in + :meth:`~matplotlib.pyplot.plotfile`. To draw the axes grid, set the + ``axes.grid`` rcParam to *True*, or explicitly call + :meth:`~matplotlib.axes.Axes.grid`. diff --git a/doc/api/prev_api_changes/api_changes_1.4.x.rst b/doc/api/prev_api_changes/api_changes_1.4.x.rst new file mode 100644 index 000000000000..1e73ccba36dc --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.4.x.rst @@ -0,0 +1,208 @@ +Changes in 1.4.x +================ + +Code changes +------------ + +* A major refactoring of the axes module was made. The axes module has been + split into smaller modules: + + - the `_base` module, which contains a new private _AxesBase class. This + class contains all methods except plotting and labelling methods. + - the `axes` module, which contains the Axes class. This class inherits + from _AxesBase, and contains all plotting and labelling methods. + - the `_subplot` module, with all the classes concerning subplotting. + +There are a couple of things that do not exists in the `axes` module's +namespace anymore. If you use them, you need to import them from their +original location: + + - math -> `import math` + - ma -> `from numpy import ma` + - cbook -> `from matplotlib import cbook` + - docstring -> `from matplotlib import docstring` + - is_sequence_of_strings -> `from matplotlib.cbook import is_sequence_of_strings` + - is_string_like -> `from matplotlib.cbook import is_string_like` + - iterable -> `from matplotlib.cbook import iterable` + - itertools -> `import itertools` + - martist -> `from matplotlib import artist as martist` + - matplotlib -> `import matplotlib` + - mcoll -> `from matplotlib import collections as mcoll` + - mcolors -> `from matplotlib import colors as mcolors` + - mcontour -> `from matplotlib import contour as mcontour` + - mpatches -> `from matplotlib import patches as mpatches` + - mpath -> `from matplotlib import path as mpath` + - mquiver -> `from matplotlib import quiver as mquiver` + - mstack -> `from matplotlib import stack as mstack` + - mstream -> `from matplotlib import stream as mstream` + - mtable -> `from matplotlib import table as mtable` + +* As part of the refactoring to enable Qt5 support, the module + `matplotlib.backends.qt4_compat` was renamed to + `matplotlib.qt_compat`. `qt4_compat` is deprecated in 1.4 and + will be removed in 1.5. + +* The :func:`~matplotlib.pyplot.errorbar` method has been changed such that + the upper and lower limits (*lolims*, *uplims*, *xlolims*, *xuplims*) now + point in the correct direction. + +* The *fmt* kwarg for :func:`~matplotlib.pyplot.errorbar now supports + the string 'none' to suppress drawing of a line and markers; use + of the *None* object for this is deprecated. The default *fmt* + value is changed to the empty string (''), so the line and markers + are governed by the :func:`~matplotlib.pyplot.plot` defaults. + +* A bug has been fixed in the path effects rendering of fonts, which now means + that the font size is consistent with non-path effect fonts. See + https://github.com/matplotlib/matplotlib/issues/2889 for more detail. + +* The Sphinx extensions `ipython_directive` and + `ipython_console_highlighting` have been moved to the IPython + project itself. While they remain in Matplotlib for this release, + they have been deprecated. Update your extensions in `conf.py` to + point to `IPython.sphinxext.ipython_directive` instead of + `matplotlib.sphinxext.ipython_directive`. + +* In `~matplotlib.finance`, almost all functions have been deprecated + and replaced with a pair of functions name `*_ochl` and `*_ohlc`. + The former is the 'open-close-high-low' order of quotes used + previously in this module, and the latter is the + 'open-high-low-close' order that is standard in finance. + +* For consistency the ``face_alpha`` keyword to + :class:`matplotlib.patheffects.SimplePatchShadow` has been deprecated in + favour of the ``alpha`` keyword. Similarly, the keyword ``offset_xy`` is now + named ``offset`` across all :class:`~matplotlib.patheffects.AbstractPathEffect`s. + ``matplotlib.patheffects._Base`` has + been renamed to :class:`matplotlib.patheffects.AbstractPathEffect`. + ``matplotlib.patheffect.ProxyRenderer`` has been renamed to + :class:`matplotlib.patheffects.PathEffectRenderer` and is now a full + RendererBase subclass. + +* The artist used to draw the outline of a `colorbar` has been changed + from a `matplotlib.lines.Line2D` to `matplotlib.patches.Polygon`, + thus `colorbar.ColorbarBase.outline` is now a + `matplotlib.patches.Polygon` object. + +* The legend handler interface has changed from a callable, to any object + which implements the ``legend_artists`` method (a deprecation phase will + see this interface be maintained for v1.4). See + :doc:`/tutorials/intermediate/legend_guide` for further details. Further legend changes + include: + + * :func:`matplotlib.axes.Axes._get_legend_handles` now returns a generator + of handles, rather than a list. + + * The :func:`~matplotlib.pyplot.legend` function's "loc" positional + argument has been deprecated. Use the "loc" keyword instead. + +* The rcParams `savefig.transparent` has been added to control + default transparency when saving figures. + +* Slightly refactored the `Annotation` family. The text location in + `Annotation` is now handled entirely handled by the underlying `Text` + object so `set_position` works as expected. The attributes `xytext` and + `textcoords` have been deprecated in favor of `xyann` and `anncoords` so + that `Annotation` and `AnnotaionBbox` can share a common sensibly named + api for getting/setting the location of the text or box. + + - `xyann` -> set the location of the annotation + - `xy` -> set where the arrow points to + - `anncoords` -> set the units of the annotation location + - `xycoords` -> set the units of the point location + - `set_position()` -> `Annotation` only set location of annotation + +* `matplotlib.mlab.specgram`, `matplotlib.mlab.psd`, `matplotlib.mlab.csd`, + `matplotlib.mlab.cohere`, `matplotlib.mlab.cohere_pairs`, + `matplotlib.pyplot.specgram`, `matplotlib.pyplot.psd`, + `matplotlib.pyplot.csd`, and `matplotlib.pyplot.cohere` now raise + ValueError where they previously raised AssertionError. + +* For `matplotlib.mlab.psd`, `matplotlib.mlab.csd`, + `matplotlib.mlab.cohere`, `matplotlib.mlab.cohere_pairs`, + `matplotlib.pyplot.specgram`, `matplotlib.pyplot.psd`, + `matplotlib.pyplot.csd`, and `matplotlib.pyplot.cohere`, in cases + where a shape (n, 1) array is returned, this is now converted to a (n, ) + array. Previously, (n, m) arrays were averaged to an (n, ) array, but + (n, 1) arrays were returend unchanged. This change makes the dimensions + consistent in both cases. + +* Added the rcParam `axes.fromatter.useoffset` to control the default value + of `useOffset` in `ticker.ScalarFormatter` + +* Added `Formatter` sub-class `StrMethodFormatter` which + does the exact same thing as `FormatStrFormatter`, but for new-style + formatting strings. + +* Deprecated `matplotlib.testing.image_util` and the only function within, + `matplotlib.testing.image_util.autocontrast`. These will be removed + completely in v1.5.0. + +* The ``fmt`` argument of :meth:`~matplotlib.axes.Axes.plot_date` has been + changed from ``bo`` to just ``o``, so color cycling can happen by default. + +* Removed the class `FigureManagerQTAgg` and deprecated `NavigationToolbar2QTAgg` + which will be removed in 1.5. + +* Removed formerly public (non-prefixed) attributes `rect` and + `drawRect` from `FigureCanvasQTAgg`; they were always an + implementation detail of the (preserved) `drawRectangle()` function. + +* The function signatures of `tight_bbox.adjust_bbox` and + `tight_bbox.process_figure_for_rasterizing` have been changed. A new + `fixed_dpi` parameter allows for overriding the `figure.dpi` setting + instead of trying to deduce the intended behaviour from the file format. + +* Added support for horizontal/vertical axes padding to + `mpl_toolkits.axes_grid1.ImageGrid` --- argument ``axes_pad`` can now be + tuple-like if separate axis padding is required. + The original behavior is preserved. + +* Added support for skewed transforms to `matplotlib.transforms.Affine2D`, + which can be created using the `skew` and `skew_deg` methods. + +* Added clockwise parameter to control sectors direction in `axes.pie` + +* In `matplotlib.lines.Line2D` the `markevery` functionality has been extended. + Previously an integer start-index and stride-length could be specified using + either a two-element-list or a two-element-tuple. Now this can only be done + using a two-element-tuple. If a two-element-list is used then it will be + treated as numpy fancy indexing and only the two markers corresponding to the + given indexes will be shown. + +* removed prop kwarg from `mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar` + call. It was passed through to the base-class `__init__` and is only used for + setting padding. Now `fontproperties` (which is what is really used to set + the font properties of `AnchoredSizeBar`) is passed through in place of `prop`. + If `fontpropreties` is not passed in, but `prop` is, then `prop` is used inplace + of `fontpropreties`. If both are passed in, `prop` is silently ignored. + + +* The use of the index 0 in `plt.subplot` and related commands is + deprecated. Due to a lack of validation calling `plt.subplots(2, 2, + 0)` does not raise an exception, but puts an axes in the _last_ + position. This is due to the indexing in subplot being 1-based (to + mirror MATLAB) so before indexing into the `GridSpec` object used to + determine where the axes should go, 1 is subtracted off. Passing in + 0 results in passing -1 to `GridSpec` which results in getting the + last position back. Even though this behavior is clearly wrong and + not intended, we are going through a deprecation cycle in an + abundance of caution that any users are exploiting this 'feature'. + The use of 0 as an index will raise a warning in 1.4 and an + exception in 1.5. + +* Clipping is now off by default on offset boxes. + +* Matplotlib now uses a less-aggressive call to ``gc.collect(1)`` when + closing figures to avoid major delays with large numbers of user objects + in memory. + +* The default clip value of *all* pie artists now defaults to ``False``. + + +Code removal +------------ + +* Removed ``mlab.levypdf``. The code raised a numpy error (and has for + a long time) and was not the standard form of the Levy distribution. + ``scipy.stats.levy`` should be used instead diff --git a/doc/api/prev_api_changes/api_changes_1.5.0.rst b/doc/api/prev_api_changes/api_changes_1.5.0.rst new file mode 100644 index 000000000000..76540c234509 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.5.0.rst @@ -0,0 +1,406 @@ + +Changes in 1.5.0 +================ + +Code Changes +------------ + +Reversed `matplotlib.cbook.ls_mapper`, added `ls_mapper_r` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Formerly, `matplotlib.cbook.ls_mapper` was a dictionary with +the long-form line-style names (`"solid"`) as keys and the short +forms (`"-"`) as values. This long-to-short mapping is now done +by `ls_mapper_r`, and the short-to-long mapping is done by the +`ls_mapper`. + +Prevent moving artists between Axes, Property-ify Artist.axes, deprecate Artist.{get,set}_axes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This was done to prevent an Artist that is +already associated with an Axes from being moved/added to a different Axes. +This was never supported as it causes havoc with the transform stack. +The apparent support for this (as it did not raise an exception) was +the source of multiple bug reports and questions on SO. + +For almost all use-cases, the assignment of the axes to an artist should be +taken care of by the axes as part of the ``Axes.add_*`` method, hence the +deprecation of {get,set}_axes. + +Removing the ``set_axes`` method will also remove the 'axes' line from +the ACCEPTS kwarg tables (assuming that the removal date gets here +before that gets overhauled). + +Tightened input validation on 'pivot' kwarg to quiver +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tightened validation so that only {'tip', 'tail', 'mid', and 'middle'} +(but any capitalization) are valid values for the 'pivot' kwarg in +the `Quiver.__init__` (and hence `Axes.quiver` and +`plt.quiver` which both fully delegate to `Quiver`). Previously any +input matching 'mid.*' would be interpreted as 'middle', 'tip.*' as +'tip' and any string not matching one of those patterns as 'tail'. + +The value of `Quiver.pivot` is normalized to be in the set {'tip', +'tail', 'middle'} in `Quiver.__init__`. + +Reordered `Axes.get_children` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The artist order returned by `Axes.get_children` did not +match the one used by `Axes.draw`. They now use the same +order, as `Axes.draw` now calls `Axes.get_children`. + +Changed behaviour of contour plots +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default behaviour of :func:`~matplotlib.pyplot.contour` and +:func:`~matplotlib.pyplot.contourf` when using a masked array is now determined +by the new keyword argument `corner_mask`, or if this is not specified then +the new rcParam `contour.corner_mask` instead. The new default behaviour is +equivalent to using `corner_mask=True`; the previous behaviour can be obtained +using `corner_mask=False` or by changing the rcParam. The example +http://matplotlib.org/examples/pylab_examples/contour_corner_mask.html +demonstrates the difference. Use of the old contouring algorithm, which is +obtained with `corner_mask='legacy'`, is now deprecated. + +Contour labels may now appear in different places than in earlier versions of +Matplotlib. + +In addition, the keyword argument `nchunk` now applies to +:func:`~matplotlib.pyplot.contour` as well as +:func:`~matplotlib.pyplot.contourf`, and it subdivides the domain into +subdomains of exactly `nchunk` by `nchunk` quads, whereas previously it was +only roughly `nchunk` by `nchunk` quads. + +The C/C++ object that performs contour calculations used to be stored in the +public attribute QuadContourSet.Cntr, but is now stored in a private attribute +and should not be accessed by end users. + +Added set_params function to all Locator types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This was a bug fix targeted at making the api for Locators more consistent. + +In the old behavior, only locators of type MaxNLocator have set_params() +defined, causing its use on any other Locator to raise an AttributeError *( +aside: set_params(args) is a function that sets the parameters of a Locator +instance to be as specified within args)*. The fix involves moving set_params() +to the Locator class such that all subtypes will have this function defined. + +Since each of the Locator subtypes have their own modifiable parameters, a +universal set_params() in Locator isn't ideal. Instead, a default no-operation +function that raises a warning is implemented in Locator. Subtypes extending +Locator will then override with their own implementations. Subtypes that do +not have a need for set_params() will fall back onto their parent's +implementation, which raises a warning as intended. + +In the new behavior, Locator instances will not raise an AttributeError +when set_params() is called. For Locators that do not implement set_params(), +the default implementation in Locator is used. + +Disallow ``None`` as x or y value in ax.plot +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Do not allow ``None`` as a valid input for the ``x`` or ``y`` args in +`ax.plot`. This may break some user code, but this was never officially +supported (ex documented) and allowing ``None`` objects through can lead +to confusing exceptions downstream. + +To create an empty line use :: + + ln1, = ax.plot([], [], ...) + ln2, = ax.plot([], ...) + +In either case to update the data in the `Line2D` object you must update +both the ``x`` and ``y`` data. + + +Removed `args` and `kwargs` from `MicrosecondLocator.__call__` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The call signature of :meth:`~matplotlib.dates.MicrosecondLocator.__call__` +has changed from `__call__(self, *args, **kwargs)` to `__call__(self)`. +This is consistent with the superclass :class:`~matplotlib.ticker.Locator` +and also all the other Locators derived from this superclass. + + +No `ValueError` for the MicrosecondLocator and YearLocator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`~matplotlib.dates.MicrosecondLocator` and +:class:`~matplotlib.dates.YearLocator` objects when called will return +an empty list if the axes have no data or the view has no interval. +Previously, they raised a `ValueError`. This is consistent with all +the Date Locators. + +'OffsetBox.DrawingArea' respects the 'clip' keyword argument +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The call signature was `OffsetBox.DrawingArea(..., clip=True)` but nothing +was done with the `clip` argument. The object did not do any clipping +regardless of that parameter. Now the object can and does clip the +child `Artists` if they are set to be clipped. + +You can turn off the clipping on a per-child basis using +`child.set_clip_on(False)`. + +Add salt to clipPath id +~~~~~~~~~~~~~~~~~~~~~~~ + +Add salt to the hash used to determine the id of the ``clipPath`` +nodes. This is to avoid conflicts when two svg documents with the same +clip path are included in the same document (see +https://github.com/ipython/ipython/issues/8133 and +https://github.com/matplotlib/matplotlib/issues/4349 ), however this +means that the svg output is no longer deterministic if the same +figure is saved twice. It is not expected that this will affect any +users as the current ids are generated from an md5 hash of properties +of the clip path and any user would have a very difficult time +anticipating the value of the id. + +Changed snap threshold for circle markers to inf +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When drawing circle markers above some marker size (previously 6.0) +the path used to generate the marker was snapped to pixel centers. However, +this ends up distorting the marker away from a circle. By setting the +snap threshold to inf snapping is never done on circles. + +This change broke several tests, but is an improvement. + +Preserve units with Text position +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously the 'get_position' method on Text would strip away unit information +even though the units were still present. There was no inherent need to do +this, so it has been changed so that unit data (if present) will be preserved. +Essentially a call to 'get_position' will return the exact value from a call to +'set_position'. + +If you wish to get the old behaviour, then you can use the new method called +'get_unitless_position'. + +New API for custom Axes view changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Interactive pan and zoom were previously implemented using a Cartesian-specific +algorithm that was not necessarily applicable to custom Axes. Three new private +methods, :meth:`~matplotlib.axes._base._AxesBase._get_view`, +:meth:`~matplotlib.axes._base._AxesBase._set_view`, and +:meth:`~matplotlib.axes._base._AxesBase._set_view_from_bbox`, allow for custom +*Axes* classes to override the pan and zoom algorithms. Implementors of +custom *Axes* who override these methods may provide suitable behaviour for +both pan and zoom as well as the view navigation buttons on the interactive +toolbars. + +MathTex visual changes +---------------------- + +The spacing commands in mathtext have been changed to more closely +match vanilla TeX. + + +Improved spacing in mathtext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The extra space that appeared after subscripts and superscripts has +been removed. + +No annotation coordinates wrap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In #2351 for 1.4.0 the behavior of ['axes points', 'axes pixel', +'figure points', 'figure pixel'] as coordinates was change to +no longer wrap for negative values. In 1.4.3 this change was +reverted for 'axes points' and 'axes pixel' and in addition caused +'axes fraction' to wrap. For 1.5 the behavior has been reverted to +as it was in 1.4.0-1.4.2, no wrapping for any type of coordinate. + +Deprecation +----------- + +Deprecated `GraphicsContextBase.set_graylevel` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The `GraphicsContextBase.set_graylevel` function has been deprecated in 1.5 and +will be removed in 1.6. It has been unused. The +`GraphicsContextBase.set_foreground` could be used instead. + +deprecated idle_event +~~~~~~~~~~~~~~~~~~~~~ + +The `idle_event` was broken or missing in most backends and causes spurious +warnings in some cases, and its use in creating animations is now obsolete due +to the animations module. Therefore code involving it has been removed from all +but the wx backend (where it partially works), and its use is deprecated. The +animations module may be used instead to create animations. + +`color_cycle` deprecated +~~~~~~~~~~~~~~~~~~~~~~~~ + +In light of the new property cycling feature, +the Axes method *set_color_cycle* is now deprecated. +Calling this method will replace the current property cycle with +one that cycles just the given colors. + +Similarly, the rc parameter *axes.color_cycle* is also deprecated in +lieu of the new *axes.prop_cycle* parameter. Having both parameters in +the same rc file is not recommended as the result cannot be +predicted. For compatibility, setting *axes.color_cycle* will +replace the cycler in *axes.prop_cycle* with a color cycle. +Accessing *axes.color_cycle* will return just the color portion +of the property cycle, if it exists. + +Timeline for removal has not been set. + + +Bundled jquery +-------------- + +The version of jquery bundled with the webagg backend has been upgraded +from 1.7.1 to 1.11.3. If you are using the version of jquery bundled +with webagg you will need to update your html files as such + +.. code-block:: diff + + - + + + + +Code Removed +------------ + +Removed `Image` from main namespace +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`Image` was imported from PIL/pillow to test if PIL is available, but +there is no reason to keep `Image` in the namespace once the availability +has been determined. + +Removed `lod` from Artist +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Removed the method *set_lod* and all references to +the attribute *_lod* as the are not used anywhere else in the +code base. It appears to be a feature stub that was never built +out. + +Removed threading related classes from cbook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The classes ``Scheduler``, ``Timeout``, and ``Idle`` were in cbook, but +are not used internally. They appear to be a prototype for the idle event +system which was not working and has recently been pulled out. + +Removed `Lena` images from sample_data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``lena.png`` and ``lena.jpg`` images have been removed from +Matplotlib's sample_data directory. The images are also no longer +available from `matplotlib.cbook.get_sample_data`. We suggest using +`matplotlib.cbook.get_sample_data('grace_hopper.png')` or +`matplotlib.cbook.get_sample_data('grace_hopper.jpg')` instead. + + +Legend +~~~~~~ +Removed handling of `loc` as a positional argument to `Legend` + + +Legend handlers +~~~~~~~~~~~~~~~ +Remove code to allow legend handlers to be callable. They must now +implement a method ``legend_artist``. + + +Axis +~~~~ +Removed method ``set_scale``. This is now handled via a private method which +should not be used directly by users. It is called via ``Axes.set_{x,y}scale`` +which takes care of ensuring the related changes are also made to the Axes +object. + +finance.py +~~~~~~~~~~ + +Removed functions with ambiguous argument order from finance.py + + +Annotation +~~~~~~~~~~ + +Removed ``textcoords`` and ``xytext`` proprieties from Annotation objects. + + +spinxext.ipython_*.py +~~~~~~~~~~~~~~~~~~~~~ + +Both ``ipython_console_highlighting`` and ``ipython_directive`` have been +moved to `IPython`. + +Change your import from 'matplotlib.sphinxext.ipython_directive' to +'IPython.sphinxext.ipython_directive' and from +'matplotlib.sphinxext.ipython_directive' to +'IPython.sphinxext.ipython_directive' + + +LineCollection.color +~~~~~~~~~~~~~~~~~~~~ + +Deprecated in 2005, use ``set_color`` + + +remove ``'faceted'`` as a valid value for `shading` in ``tri.tripcolor`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use `edgecolor` instead. Added validation on ``shading`` to +only be valid values. + + +Remove ``faceted`` kwarg from scatter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove support for the ``faceted`` kwarg. This was deprecated in +d48b34288e9651ff95c3b8a071ef5ac5cf50bae7 (2008-04-18!) and replaced by +``edgecolor``. + + +Remove ``set_colorbar`` method from ``ScalarMappable`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove ``set_colorbar`` method, use `colorbar` attribute directly. + + +patheffects.svg +~~~~~~~~~~~~~~~ + + - remove ``get_proxy_renderer`` method from ``AbstarctPathEffect`` class + - remove ``patch_alpha`` and ``offset_xy`` from ``SimplePatchShadow`` + + +Remove ``testing.image_util.py`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Contained only a no-longer used port of functionality from PIL + + +Remove ``mlab.FIFOBuffer`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Not used internally and not part of core mission of mpl. + + +Remove ``mlab.prepca`` +~~~~~~~~~~~~~~~~~~~~~~ +Deprecated in 2009. + + +Remove ``NavigationToolbar2QTAgg`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Added no functionality over the base ``NavigationToolbar2Qt`` + + +mpl.py +~~~~~~ + +Remove the module `matplotlib.mpl`. Deprecated in 1.3 by +PR #1670 and commit 78ce67d161625833cacff23cfe5d74920248c5b2 diff --git a/doc/api/prev_api_changes/api_changes_1.5.2.rst b/doc/api/prev_api_changes/api_changes_1.5.2.rst new file mode 100644 index 000000000000..d2ee33546314 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.5.2.rst @@ -0,0 +1,17 @@ +Changes in 1.5.2 +================ + + +Default Behavior Changes +------------------------ + +Changed default ``autorange`` behavior in boxplots +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prior to v1.5.2, the whiskers of boxplots would extend to the minimum +and maximum values if the quartiles were all equal (i.e., Q1 = median += Q3). This behavior has been disabled by default to restore consistency +with other plotting packages. + +To restore the old behavior, simply set ``autorange=True`` when +calling ``plt.boxplot``. diff --git a/doc/api/prev_api_changes/api_changes_1.5.3.rst b/doc/api/prev_api_changes/api_changes_1.5.3.rst new file mode 100644 index 000000000000..9806cbf9bb03 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_1.5.3.rst @@ -0,0 +1,26 @@ +Changes in 1.5.3 +================ + +``ax.plot(..., marker=None)`` gives default marker +-------------------------------------------------- + +Prior to 1.5.3 kwargs passed to `~matplotlib.Axes.plot` were handled +in two parts -- default kwargs generated internal to +`~matplotlib.Axes.plot` (such as the cycled styles) and user supplied +kwargs. The internally generated kwargs were passed to the +`matplotlib.lines.Line2D.__init__` and the user kwargs were passed to +``ln.set(**kwargs)`` to update the artist after it was created. Now +both sets of kwargs are merged and passed to +`~matplotlib.lines.Line2D.__init__`. This change was made to allow `None` +to be passed in via the user kwargs to mean 'do the default thing' as +is the convention through out mpl rather than raising an exception. + +Unlike most `~matplotlib.lines.Line2D` setter methods +`~matplotlib.lines.Line2D.set_marker` did accept `None` as a valid +input which was mapped to 'no marker'. Thus, by routing this +``marker=None`` through ``__init__`` rather than ``set(...)`` the meaning +of ``ax.plot(..., marker=None)`` changed from 'no markers' to 'default markers +from rcparams'. + +This is change is only evident if ``mpl.rcParams['lines.marker']`` has a value +other than ``'None'`` (which is string ``'None'`` which means 'no marker'). diff --git a/doc/api/prev_api_changes/api_changes_2.0.0.rst b/doc/api/prev_api_changes/api_changes_2.0.0.rst new file mode 100644 index 000000000000..047413abe8b0 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.0.0.rst @@ -0,0 +1,204 @@ + +API Changes in 2.0.0 +==================== + +Deprecation and removal +----------------------- + +Color of Axes +~~~~~~~~~~~~~ +The ``axisbg`` and ``axis_bgcolor`` properties on *Axes* have been +deprecated in favor of ``facecolor``. + +GTK and GDK backends deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The GDK and GTK backends have been deprecated. These obsolete backends +allow figures to be rendered via the GDK API to files and GTK2 figures. +They are untested and known to be broken, and their use has been +discouraged for some time. Instead, use the `GTKAgg` and `GTKCairo` +backends for rendering to GTK2 windows. + +WX backend deprecated +~~~~~~~~~~~~~~~~~~~~~ +The WX backend has been deprecated. It is untested, and its +use has been discouraged for some time. Instead, use the `WXAgg` +backend for rendering figures to WX windows. + +CocoaAgg backend removed +~~~~~~~~~~~~~~~~~~~~~~~~ +The deprecated and not fully functional CocoaAgg backend has been removed. + +`round` removed from TkAgg Backend +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The TkAgg backend had its own implementation of the `round` function. This +was unused internally and has been removed. Instead, use either the +`round` builtin function or `numpy.round`. + +'hold' functionality deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The 'hold' keyword argument and all functions and methods related +to it are deprecated, along with the 'axes.hold' `rcParams` entry. +The behavior will remain consistent with the default ``hold=True`` +state that has long been in place. Instead of using a function +or keyword argument (``hold=False``) to change that behavior, +explicitly clear the axes or figure as needed prior to subsequent +plotting commands. + + +`Artist.update` has return value +-------------------------------- + +The methods `matplotlib.artist.Artist.set`, +`matplotlib.Artist.update`, and the function `matplotlib.artist.setp` +now use a common codepath to look up how to update the given artist +properties (either using the setter methods or an attribute/property). + +The behavior of `matplotlib.Artist.update` is slightly changed to +return a list of the values returned from the setter methods to avoid +changing the API of `matplotlib.Artist.set` and +`matplotlib.artist.setp`. + +The keys passed into `matplotlib.Artist.update` are now converted to +lower case before being processed, to match the behavior of +`matplotlib.Artist.set` and `matplotlib.artist.setp`. This should not +break any user code because there are no set methods with capitals in +their names, but this puts a constraint on naming properties in the future. + + +`Legend` initializers gain edgecolor and facecolor kwargs +--------------------------------------------------------- + +The :class:`~matplotlib.legend.Legend` background patch (or 'frame') +can have its ``edgecolor`` and ``facecolor`` determined by the +corresponding keyword arguments to the :class:`matplotlib.legend.Legend` +initializer, or to any of the methods or functions that call that +initializer. If left to their default values of `None`, their values +will be taken from ``matplotlib.rcParams``. The previously-existing +``framealpha`` kwarg still controls the alpha transparency of the +patch. + + +Qualitative colormaps +--------------------- + +Colorbrewer's qualitative/discrete colormaps ("Accent", "Dark2", "Paired", +"Pastel1", "Pastel2", "Set1", "Set2", "Set3") are now implemented as +``ListedColormap`` instead of ``LinearSegmentedColormap``. + +To use these for images where categories are specified as integers, for +instance, use:: + + plt.imshow(x, cmap='Dark2', norm=colors.NoNorm()) + + +Change in the ``draw_image`` backend API +---------------------------------------- + +The ``draw_image`` method implemented by backends has changed its interface. + +This change is only relevant if the backend declares that it is able +to transform images by returning ``True`` from ``option_scale_image``. +See the ``draw_image`` docstring for more information. + + + +`matplotlib.ticker.LinearLocator` algorithm update +-------------------------------------------------- + +The ``matplotlib.ticker.LinearLocator`` is used to define the range and +location of axis ticks when the user wants an exact number of ticks. +``LinearLocator`` thus differs from the default locator ``MaxNLocator``, +for which the user specifies a maximum number of intervals rather than +a precise number of ticks. + +The view range algorithm in ``matplotlib.ticker.LinearLocator`` has been +changed so that more convenient tick locations are chosen. The new algorithm +returns a plot view range that is a multiple of the user-requested number of +ticks. This ensures tick marks will be located at whole integers more +consistently. For example, when both y-axes of a``twinx`` plot use +``matplotlib.ticker.LinearLocator`` with the same number of ticks, +their y-tick locations and grid lines will coincide. + +`matplotlib.ticker.LogLocator` gains numticks kwarg +--------------------------------------------------- + +The maximum number of ticks generated by the +`~matplotlib.ticker.LogLocator` can now be controlled explicitly +via setting the new 'numticks' kwarg to an integer. By default +the kwarg is None which internally sets it to the 'auto' string, +triggering a new algorithm for adjusting the maximum according +to the axis length relative to the ticklabel font size. + +`matplotlib.ticker.LogFormatter`: two new kwargs +------------------------------------------------ + +Previously, minor ticks on log-scaled axes were not labeled by +default. An algorithm has been added to the +`~matplotlib.ticker.LogFormatter` to control the labeling of +ticks between integer powers of the base. The algorithm uses +two parameters supplied in a kwarg tuple named 'minor_thresholds'. +See the docstring for further explanation. + +To improve support for axes using `~matplotlib.ticker.SymmetricLogLocator`, +a 'linthresh' kwarg was added. + + +New defaults for 3D quiver function in mpl_toolkits.mplot3d.axes3d.py +--------------------------------------------------------------------- + +Matplotlib has both a 2D and a 3D ``quiver`` function. These changes +affect only the 3D function and make the default behavior of the 3D +function match the 2D version. There are two changes: + +1) The 3D quiver function previously normalized the arrows to be the + same length, which makes it unusable for situations where the + arrows should be different lengths and does not match the behavior + of the 2D function. This normalization behavior is now controlled + with the ``normalize`` keyword, which defaults to False. + +2) The ``pivot`` keyword now defaults to ``tail`` instead of + ``tip``. This was done in order to match the default behavior of + the 2D quiver function. + +To obtain the previous behavior with the 3D quiver function, one can +call the function with :: + + ax.quiver(x, y, z, u, v, w, normalize=True, pivot='tip') + +where "ax" is an ``Axes3d`` object created with something like :: + + import mpl_toolkits.mplot3d.axes3d + ax = plt.sublot(111, projection='3d') + + +Stale figure behavior +--------------------- + +Attempting to draw the figure will now mark it as not stale (independent if +the draw succeeds). This change is to prevent repeatedly trying to re-draw a +figure which is raising an error on draw. The previous behavior would only mark +a figure as not stale after a full re-draw succeeded. + + +The spectral colormap is now nipy_spectral +------------------------------------------ + +The colormaps formerly known as ``spectral`` and ``spectral_r`` have been +replaced by ``nipy_spectral`` and ``nipy_spectral_r`` since Matplotlib +1.3.0. Even though the colormap was deprecated in Matplotlib 1.3.0, it never +raised a warning. As of Matplotlib 2.0.0, using the old names raises a +deprecation warning. In the future, using the old names will raise an error. + +Default install no longer includes test images +---------------------------------------------- + +To reduce the size of wheels and source installs, the tests and +baseline images are no longer included by default. + +To restore installing the tests and images, use a `setup.cfg` with :: + + [packages] + tests = True + toolkits_tests = True + +in the source directory at build/install time. diff --git a/doc/api/prev_api_changes/api_changes_2.0.1.rst b/doc/api/prev_api_changes/api_changes_2.0.1.rst new file mode 100644 index 000000000000..d1783bcd92bd --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.0.1.rst @@ -0,0 +1,63 @@ + +API Changes in 2.0.1 +==================== + +Extensions to `matplotlib.backend_bases.GraphicsContextBase` +------------------------------------------------------------ + +To better support controlling the color of hatches, the method +`matplotlib.backend_bases.GraphicsContextBase.set_hatch_color` was +added to the expected API of ``GraphicsContext`` classes. Calls to +this method are currently wrapped with a ``try:...except Attribute:`` +block to preserve back-compatibility with any third-party backends +which do not extend `~matplotlib.backend_bases.GraphicsContextBase`. + +This value can be accessed in the backends via +`matplotlib.backend_bases.GraphicsContextBase.get_hatch_color` (which +was added in 2.0 see :ref:`gc_get_hatch_color_wn`) and should be used +to color the hatches. + +In the future there may also be ``hatch_linewidth`` and +``hatch_density`` related methods added. It is encouraged, but not +required that third-party backends extend +`~matplotlib.backend_bases.GraphicsContextBase` to make adapting to +these changes easier. + + +`afm.get_fontconfig_fonts` returns a list of paths and does not check for existence +----------------------------------------------------------------------------------- + +`afm.get_fontconfig_fonts` used to return a set of paths encoded as a +``{key: 1, ...}`` dict, and checked for the existence of the paths. It now +returns a list and dropped the existence check, as the same check is performed +by the caller (`afm.findSystemFonts`) as well. + + +`bar` now returns rectangles of negative height or width if the corresponding input is negative +----------------------------------------------------------------------------------------------- + +`plt.bar` used to normalize the coordinates of the rectangles that it created, +to keep their height and width positives, even if the corresponding input was +negative. This normalization has been removed to permit a simpler computation +of the correct `sticky_edges` to use. + + +Do not clip line width when scaling dashes +------------------------------------------ + +The algorithm to scale dashes was changed to no longer clip the +scaling factor: the dash patterns now continue to shrink at thin line widths. +If the line width is smaller than the effective pixel size, this may result in +dashed lines turning into solid gray-ish lines. This also required slightly +tweaking the default patterns for '--', ':', and '.-' so that with the default +line width the final patterns would not change. + +There is no way to restore the old behavior. + + +Deprecate 'Vega' color maps +--------------------------- + +The "Vega" colormaps are deprecated in Matplotlib 2.0.1 and will be +removed in Matplotlib 2.2. Use the "tab" colormaps instead: "tab10", +"tab20", "tab20b", "tab20c". diff --git a/doc/api/prev_api_changes/api_changes_2.1.0.rst b/doc/api/prev_api_changes/api_changes_2.1.0.rst new file mode 100644 index 000000000000..df3bba17d7bd --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.1.0.rst @@ -0,0 +1,445 @@ + + +API Changes in 2.1.0 +==================== + + +Default behavior of log scales changed to mask <= 0 values +---------------------------------------------------------- + +Calling `matplotlib.axes.Axes.set_xscale` or `matplotlib.axes.Axes.set_yscale` +now uses 'mask' as the default method to handle invalid values (as opposed to +'clip'). This means that any values <= 0 on a log scale will not be shown. + +Previously they were clipped to a very small number and shown. + + +:meth:`matplotlib.cbook.CallbackRegistry.process` suppresses exceptions by default +---------------------------------------------------------------------------------- + +Matplotlib uses instances of :obj:`~matplotlib.cbook.CallbackRegistry` +as a bridge between user input event from the GUI and user callbacks. +Previously, any exceptions raised in a user call back would bubble out +of of the ``process`` method, which is typically in the GUI event +loop. Most GUI frameworks simple print the traceback to the screen +and continue as there is not always a clear method of getting the +exception back to the user. However PyQt5 now exits the process when +it receives an un-handled python exception in the event loop. Thus, +:meth:`~matplotlib.cbook.CallbackRegistry.process` now suppresses and +prints tracebacks to stderr by default. + +What :meth:`~matplotlib.cbook.CallbackRegistry.process` does with exceptions +is now user configurable via the ``exception_handler`` attribute and kwarg. To +restore the previous behavior pass ``None`` :: + + cb = CallbackRegistry(exception_handler=None) + + +A function which take and ``Exception`` as its only argument may also be passed :: + + def maybe_reraise(exc): + if isinstance(exc, RuntimeError): + pass + else: + raise exc + + cb = CallbackRegistry(exception_handler=maybe_reraise) + + + +Improved toggling of the axes grids +----------------------------------- + +The `g` key binding now switches the states of the `x` and `y` grids +independently (by cycling through all four on/off combinations). + +The new `G` key binding switches the states of the minor grids. + +Both bindings are disabled if only a subset of the grid lines (in either +direction) is visible, to avoid making irreversible changes to the figure. + + +Ticklabels are turned off instead of being invisible +---------------------------------------------------- + +Internally, the `Tick`'s :func:`~matplotlib.axis.Tick.label1On` attribute +is now used to hide tick labels instead of setting the visibility on the tick +label objects. +This improves overall performance and fixes some issues. +As a consequence, in case those labels ought to be shown, +:func:`~matplotlib.axes.Axes.tick_params` +needs to be used, e.g. + +:: + + ax.tick_params(labelbottom=True) + + +Removal of warning on empty legends +----------------------------------- + +``plt.legend`` used to issue a warning when no labeled artist could be +found. This warning has been removed. + + +More accurate legend autopositioning +------------------------------------ + +Automatic positioning of legends now prefers using the area surrounded +by a `Line2D` rather than placing the legend over the line itself. + + +Cleanup of stock sample data +---------------------------- + +The sample data of stocks has been cleaned up to remove redundancies and +increase portability. The ``AAPL.dat.gz``, ``INTC.dat.gz`` and ``aapl.csv`` +files have been removed entirely and will also no longer be available from +`matplotlib.cbook.get_sample_data`. If a CSV file is required, we suggest using +the ``msft.csv`` that continues to be shipped in the sample data. If a NumPy +binary file is acceptable, we suggest using one of the following two new files. +The ``aapl.npy.gz`` and ``goog.npy`` files have been replaced by ``aapl.npz`` +and ``goog.npz``, wherein the first column's type has changed from +`datetime.date` to `np.datetime64` for better portability across Python +versions. Note that Matplotlib does not fully support `np.datetime64` as yet. + + +Updated qhull to 2015.2 +----------------------- + +The version of qhull shipped with Matplotlib, which is used for +Delaunay triangulation, has been updated from version 2012.1 to +2015.2. + +Improved Delaunay triangulations with large offsets +--------------------------------------------------- + +Delaunay triangulations now deal with large x,y offsets in a better +way. This can cause minor changes to any triangulations calculated +using Matplotlib, i.e. any use of `matplotlib.tri.Triangulation` that +requests that a Delaunay triangulation is calculated, which includes +`matplotlib.pyplot.tricontour`, `matplotlib.pyplot.tricontourf`, +`matplotlib.pyplot.tripcolor`, `matplotlib.pyplot.triplot`, +`matplotlib.mlab.griddata` and +`mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`. + + + +Use ``backports.functools_lru_cache`` instead of ``functools32`` +---------------------------------------------------------------- + +It's better maintained and more widely used (by pylint, jaraco, etc). + + + +``cbook.is_numlike`` only performs an instance check +---------------------------------------------------- + +:func:`~matplotlib.cbook.is_numlike` now only checks that its argument +is an instance of ``(numbers.Number, np.Number)``. In particular, +this means that arrays are now not num-like. + + + +Elliptical arcs now drawn between correct angles +------------------------------------------------ + +The `matplotlib.patches.Arc` patch is now correctly drawn between the given +angles. + +Previously a circular arc was drawn and then stretched into an ellipse, +so the resulting arc did not lie between *theta1* and *theta2*. + + + +``-d$backend`` no longer sets the backend +----------------------------------------- + +It is no longer possible to set the backend by passing ``-d$backend`` +at the command line. Use the ``MPLBACKEND`` environment variable +instead. + + +Path.intersects_bbox always treats the bounding box as filled +------------------------------------------------------------- + +Previously, when ``Path.intersects_bbox`` was called with ``filled`` set to +``False``, it would treat both the path and the bounding box as unfilled. This +behavior was not well documented and it is usually not the desired behavior, +since bounding boxes are used to represent more complex shapes located inside +the bounding box. This behavior has now been changed: when ``filled`` is +``False``, the path will be treated as unfilled, but the bounding box is still +treated as filled. The old behavior was arguably an implementation bug. + +When ``Path.intersects_bbox`` is called with ``filled`` set to ``True`` +(the default value), there is no change in behavior. For those rare cases where +``Path.intersects_bbox`` was called with ``filled`` set to ``False`` and where +the old behavior is actually desired, the suggested workaround is to call +``Path.intersects_path`` with a rectangle as the path:: + + from matplotlib.path import Path + from matplotlib.transforms import Bbox, BboxTransformTo + rect = Path.unit_rectangle().transformed(BboxTransformTo(bbox)) + result = path.intersects_path(rect, filled=False) + + + + +WX no longer calls generates ``IdleEvent`` events or calls ``idle_event`` +------------------------------------------------------------------------- + +Removed unused private method ``_onIdle`` from ``FigureCanvasWx``. + +The ``IdleEvent`` class and ``FigureCanvasBase.idle_event`` method +will be removed in 2.2 + + + +Correct scaling of :func:`magnitude_spectrum()` +----------------------------------------------- + +The functions :func:`matplotlib.mlab.magnitude_spectrum()` and :func:`matplotlib.pyplot.magnitude_spectrum()` implicitly assumed the sum +of windowing function values to be one. In Matplotlib and Numpy the +standard windowing functions are scaled to have maximum value of one, +which usually results in a sum of the order of n/2 for a n-point +signal. Thus the amplitude scaling :func:`magnitude_spectrum()` was +off by that amount when using standard windowing functions (`Bug 8417 +`_ ). Now the +behavior is consistent with :func:`matplotlib.pyplot.psd()` and +:func:`scipy.signal.welch()`. The following example demonstrates the +new and old scaling:: + + import matplotlib.pyplot as plt + import numpy as np + + tau, n = 10, 1024 # 10 second signal with 1024 points + T = tau/n # sampling interval + t = np.arange(n)*T + + a = 4 # amplitude + x = a*np.sin(40*np.pi*t) # 20 Hz sine with amplitude a + + # New correct behavior: Amplitude at 20 Hz is a/2 + plt.magnitude_spectrum(x, Fs=1/T, sides='onesided', scale='linear') + + # Original behavior: Amplitude at 20 Hz is (a/2)*(n/2) for a Hanning window + w = np.hanning(n) # default window is a Hanning window + plt.magnitude_spectrum(x*np.sum(w), Fs=1/T, sides='onesided', scale='linear') + + + + + +Change to signatures of :meth:`~matplotlib.axes.Axes.bar` & :meth:`~matplotlib.axes.Axes.barh` +---------------------------------------------------------------------------------------------- + +For 2.0 the :ref:`default value of *align* ` changed to +``'center'``. However this caused the signature of +:meth:`~matplotlib.axes.Axes.bar` and +:meth:`~matplotlib.axes.Axes.barh` to be misleading as the first parameters were +still *left* and *bottom* respectively:: + + bar(left, height, *, align='center', **kwargs) + barh(bottom, width, *, align='center', **kwargs) + +despite behaving as the center in both cases. The methods now take +``*args, **kwargs`` as input and are documented to have the primary +signatures of:: + + bar(x, height, *, align='center', **kwargs) + barh(y, width, *, align='center', **kwargs) + +Passing *left* and *bottom* as keyword arguments to +:meth:`~matplotlib.axes.Axes.bar` and +:meth:`~matplotlib.axes.Axes.barh` respectively will warn. +Support will be removed in Matplotlib 3.0. + + +Font cache as json +------------------ + +The font cache is now saved as json, rather than a pickle. + + +Invalid (Non-finite) Axis Limit Error +------------------------------------- + +When using :func:`~matplotlib.axes.Axes.set_xlim` and +:func:`~matplotlib.axes.Axes.set_ylim`, passing non-finite values now +results in a ``ValueError``. The previous behavior resulted in the +limits being erroneously reset to ``(-0.001, 0.001)``. + +``scatter`` and ``Collection`` offsets are no longer implicitly flattened +------------------------------------------------------------------------- + +`~matplotlib.collections.Collection` (and thus both 2D +`~matplotlib.axes.Axes.scatter` and 3D +`~mpl_toolkits.mplot3d.axes3d.Axes3D.scatter`) no +longer implicitly flattens its offsets. As a consequence, ``scatter``'s ``x`` +and ``y`` arguments can no longer be 2+-dimensional arrays. + +Deprecations +------------ + +``GraphicsContextBase``\'s ``linestyle`` property. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``GraphicsContextBase.get_linestyle`` and +``GraphicsContextBase.set_linestyle`` methods, which had no effect, +have been deprecated. All of the backends Matplotlib ships use +``GraphicsContextBase.get_dashes`` and +``GraphicsContextBase.set_dashes`` which are more general. +Third-party backends should also migrate to the ``*_dashes`` methods. + + +``NavigationToolbar2.dynamic_update`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use :meth:`draw_idle` method on the ``Canvas`` instance instead. + + +Testing +~~~~~~~ + +`matplotlib.testing.noseclasses` is deprecated and will be removed in 2.3 + + +``EngFormatter`` *num* arg as string +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing a string as *num* argument when calling an instance of +`matplotlib.ticker.EngFormatter` is deprecated and will be removed in 2.3. + + +``mpl_toolkits.axes_grid`` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All functionally from `mpl_toolkits.axes_grid` can be found in either +`mpl_toolkits.axes_grid1` or `mpl_toolkits.axisartist`. Axes classes +from `mpl_toolkits.axes_grid` based on `Axis` from +`mpl_toolkits.axisartist` can be found in `mpl_toolkits.axisartist`. + + +``Axes`` collision in ``Figure.add_axes`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Adding an axes instance to a figure by using the same arguments as for +a previous axes instance currently reuses the earlier instance. This +behavior has been deprecated in Matplotlib 2.1. In a future version, a +*new* instance will always be created and returned. Meanwhile, in such +a situation, a deprecation warning is raised by +:class:`~matplotlib.figure.AxesStack`. + +This warning can be suppressed, and the future behavior ensured, by passing +a *unique* label to each axes instance. See the docstring of +:meth:`~matplotlib.figure.Figure.add_axes` for more information. + +Additional details on the rationale behind this deprecation can be found +in :ghissue:`7377` and :ghissue:`9024`. + + +Former validators for ``contour.negative_linestyle`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +The former public validation functions ``validate_negative_linestyle`` +and ``validate_negative_linestyle_legacy`` will be deprecated in 2.1 and +may be removed in 2.3. There are no public functions to replace them. + + + +``cbook`` +~~~~~~~~~ + +Many unused or near-unused :mod:`matplotlib.cbook` functions and +classes have been deprecated: ``converter``, ``tostr``, +``todatetime``, ``todate``, ``tofloat``, ``toint``, ``unique``, +``is_string_like``, ``is_sequence_of_strings``, ``is_scalar``, +``Sorter``, ``Xlator``, ``soundex``, ``Null``, ``dict_delall``, +``RingBuffer``, ``get_split_ind``, ``wrap``, +``get_recursive_filelist``, ``pieces``, ``exception_to_str``, +``allequal``, ``alltrue``, ``onetrue``, ``allpairs``, ``finddir``, +``reverse_dict``, ``restrict_dict``, ``issubclass_safe``, +``recursive_remove``, ``unmasked_index_ranges``. + + +Code Removal +------------ + +qt4_compat.py +~~~~~~~~~~~~~ + +Moved to ``qt_compat.py``. Renamed because it now handles Qt5 as well. + + +Previously Deprecated methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``GraphicsContextBase.set_graylevel``, ``FigureCanvasBase.onHilite`` and +``mpl_toolkits.axes_grid1.mpl_axes.Axes.toggle_axisline`` methods have been +removed. + +The ``ArtistInspector.findobj`` method, which was never working due to the lack +of a ``get_children`` method, has been removed. + +The deprecated ``point_in_path``, ``get_path_extents``, +``point_in_path_collection``, ``path_intersects_path``, +``convert_path_to_polygons``, ``cleanup_path`` and ``clip_path_to_rect`` +functions in the ``matplotlib.path`` module have been removed. Their +functionality remains exposed as methods on the ``Path`` class. + +The deprecated ``Artist.get_axes`` and ``Artist.set_axes`` methods +have been removed + + +The ``matplotlib.backends.backend_ps.seq_allequal`` function has been removed. +Use ``np.array_equal`` instead. + +The deprecated ``matplotlib.rcsetup.validate_maskedarray``, +``matplotlib.rcsetup.deprecate_savefig_extension`` and +``matplotlib.rcsetup.validate_tkpythoninspect`` functions, and associated +``savefig.extension`` and ``tk.pythoninspect`` rcparams entries have been +removed. + + +The kwarg ``resolution`` of +:class:`matplotlib.projections.polar.PolarAxes` has been removed. It +has deprecation with no effect from version `0.98.x`. + + +``Axes.set_aspect("normal")`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for setting an ``Axes``\'s aspect to ``"normal"`` has been +removed, in favor of the synonym ``"auto"``. + + +``shading`` kwarg to ``pcolor`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``shading`` kwarg to `~matplotlib.axes.Axes.pcolor` has been +removed. Set ``edgecolors`` appropriately instead. + + +Functions removed from the `lines` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :mod:`matplotlib.lines` module no longer imports the +``pts_to_prestep``, ``pts_to_midstep`` and ``pts_to_poststep`` +functions from :mod:`matplotlib.cbook`. + + +PDF backend functions +~~~~~~~~~~~~~~~~~~~~~ + +The methods ``embedTeXFont`` and ``tex_font_mapping`` of +:class:`matplotlib.backqend_pdf.PdfFile` have been removed. It is +unlikely that external users would have called these methods, which +are related to the font system internal to the PDF backend. + + +matplotlib.delaunay +~~~~~~~~~~~~~~~~~~~ + +Remove the delaunay triangulation code which is now handled by Qhull +via :mod:`matplotlib.tri`. diff --git a/doc/api/prev_api_changes/api_changes_2.1.1.rst b/doc/api/prev_api_changes/api_changes_2.1.1.rst new file mode 100644 index 000000000000..39ebbb635373 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.1.1.rst @@ -0,0 +1,13 @@ +API Changes in 2.1.1 +==================== + +Default behavior of log scales reverted to clip <= 0 values +----------------------------------------------------------- + +The change it 2.1.0 to mask in logscale by default had more disruptive +changes than anticipated and has been reverted, however the clipping is now +done in a way that fixes the issues that motivated changing the default behavior +to ``'mask'``. + +As a side effect of this change, error bars which go negative now work as expected +on log scales. diff --git a/doc/api/prev_api_changes/api_changes_2.1.2.rst b/doc/api/prev_api_changes/api_changes_2.1.2.rst new file mode 100644 index 000000000000..85807e05e61a --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.1.2.rst @@ -0,0 +1,23 @@ + +API Changes in 2.1.2 +==================== + +`Figure.legend` no longer checks for repeated lines to ignore +------------------------------------------------------------- + +`matplotlib.Figure.legend` used to check if a line had the +same label as an existing legend entry. If it also had the same line color +or marker color legend didn't add a new entry for that line. However, the +list of conditions was incomplete, didn't handle RGB tuples, +didn't handle linewidths or linestyles etc. + +This logic did not exist in `Axes.legend`. It was included (erroneously) +in Matplotlib 2.1.1 when the legend argument parsing was unified +[#9324](https://github.com/matplotlib/matplotlib/pull/9324). This change +removes that check in `Axes.legend` again to restore the old behavior. + +This logic has also been dropped from `.Figure.legend`, where it +was previously undocumented. Repeated +lines with the same label will now each have an entry in the legend. If +you do not want the duplicate entries, don't add a label to the line, or +prepend the label with an underscore. diff --git a/doc/api/prev_api_changes/api_changes_2.2.0.rst b/doc/api/prev_api_changes/api_changes_2.2.0.rst new file mode 100644 index 000000000000..dd8ee811a2b5 --- /dev/null +++ b/doc/api/prev_api_changes/api_changes_2.2.0.rst @@ -0,0 +1,289 @@ + +API Changes in 2.2.0 +==================== + + + +New dependency +-------------- + +`kiwisolver `__ is now a required +dependency to support the new constrained_layout, see +:doc:`/tutorials/intermediate/constrainedlayout_guide` for +more details. + + +Deprecations +------------ + +Classes, functions, and methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The unused and untested ``Artist.onRemove`` and ``Artist.hitlist`` methods have +been deprecated. + +The now unused ``mlab.less_simple_linear_interpolation`` function is +deprecated. + +The unused ``ContourLabeler.get_real_label_width`` method is deprecated. + +The unused ``FigureManagerBase.show_popup`` method is deprecated. This +introduced in e945059b327d42a99938b939a1be867fa023e7ba in 2005 but never built +out into any of the backends. + +:class:`backend_tkagg.AxisMenu` is deprecated, as it has become +unused since the removal of "classic" toolbars. + + +Changed function signatures +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +kwarg ``fig`` to `.GridSpec.get_subplot_params` is +deprecated, use ``figure`` instead. + +Using `.pyplot.axes` with an `~matplotlib.axes.Axes` as argument is deprecated. This sets +the current axes, i.e. it has the same effect as `.pyplot.sca`. For clarity +``plt.sca(ax)`` should be preferred over ``plt.axes(ax)``. + + +Using strings instead of booleans to control grid and tick visibility +is deprecated. Using ``"on"``, ``"off"``, ``"true"``, or ``"false"`` +to control grid and tick visibility has been deprecated. Instead, use +normal booleans (``True``/``False``) or boolean-likes. In the future, +all non-empty strings may be interpreted as ``True``. + +When given 2D inputs with non-matching numbers of columns, `~.pyplot.plot` +currently cycles through the columns of the narrower input, until all the +columns of the wider input have been plotted. This behavior is deprecated; in +the future, only broadcasting (1 column to *n* columns) will be performed. + + +rcparams +~~~~~~~~ + +The :rc:`backend.qt4` and :rc:`backend.qt5` rcParams were deprecated +in version 2.2. In order to force the use of a specific Qt binding, +either import that binding first, or set the ``QT_API`` environment +variable. + +Deprecation of the ``nbagg.transparent`` rcParam. To control +transparency of figure patches in the nbagg (or any other) backend, +directly set ``figure.patch.facecolor``, or the ``figure.facecolor`` +rcParam. + +Deprecated `Axis.unit_data` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use `Axis.units` (which has long existed) instead. + + +Removals +-------- + +Function Signatures +~~~~~~~~~~~~~~~~~~~ + +Contouring no longer supports ``legacy`` corner masking. The +deprecated ``ContourSet.vmin`` and ``ContourSet.vmax`` properties have +been removed. + +Passing ``None`` instead of ``"none"`` as format to `~.Axes.errorbar` is no +longer supported. + +The ``bgcolor`` keyword argument to ``Axes`` has been removed. + +Modules, methods, and functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``matplotlib.finance``, ``mpl_toolkits.exceltools`` and +``mpl_toolkits.gtktools`` modules have been removed. ``matplotlib.finance`` +remains available at https://github.com/matplotlib/mpl_finance. + +The ``mpl_toolkits.mplot3d.art3d.iscolor`` function has been removed. + +The ``Axes.get_axis_bgcolor``, ``Axes.set_axis_bgcolor``, +``Bbox.update_from_data``, ``Bbox.update_datalim_numerix``, +``MaxNLocator.bin_boundaries`` methods have been removed. + +``mencoder`` can no longer be used to encode animations. + +The unused `FONT_SCALE` and `fontd` attributes of the `RendererSVG` +class have been removed. + +color maps +~~~~~~~~~~ + +The ``spectral`` colormap has been removed. The ``Vega*`` colormaps, which +were aliases for the ``tab*`` colormaps, have been removed. + + +rcparams +~~~~~~~~ + +The following deprecated rcParams have been removed: + +- ``axes.color_cycle`` (see ``axes.prop_cycle``), +- ``legend.isaxes``, +- ``svg.embed_char_paths`` (see ``svg.fonttype``), +- ``text.fontstyle``, ``text.fontangle``, ``text.fontvariant``, + ``text.fontweight``, ``text.fontsize`` (renamed to ``text.style``, etc.), +- ``tick.size`` (renamed to ``tick.major.size``). + + + +Only accept string-like for Categorical input +--------------------------------------------- + +Do not accept mixed string / float / int input, only +strings are valid categoricals. + +Removal of unused imports +------------------------- +Many unused imports were removed from the codebase. As a result, +trying to import certain classes or functions from the "wrong" module +(e.g. `~.Figure` from :mod:`matplotlib.backends.backend_agg` instead of +:mod:`matplotlib.figure`) will now raise an `ImportError`. + + +``Axes3D.get_xlim``, ``get_ylim`` and ``get_zlim`` now return a tuple +--------------------------------------------------------------------- + +They previously returned an array. Returning a tuple is consistent with the +behavior for 2D axes. + + +Exception type changes +---------------------- + +If `MovieWriterRegistry` can't find the requested `MovieWriter`, a +more helpful `RuntimeError` message is now raised instead of the +previously raised `KeyError`. + +`~.tight_layout.auto_adjust_subplotpars` now raises `ValueError` +instead of `RuntimeError` when sizes of input lists don't match + + +`Figure.set_figwidth` and `Figure.set_figheight` default forward to True +------------------------------------------------------------------------ + +`matplotlib.Figure.set_figwidth` and `matplotlib.Figure.set_figheight` +had the kwarg `forward=False` +by default, but `Figure.set_size_inches` now defaults to `forward=True`. +This makes these functions conistent. + + +Do not truncate svg sizes to nearest point +------------------------------------------ + +There is no reason to size the SVG out put in integer points, change +to out putting floats for the *height*, *width*, and *viewBox* attributes +of the *svg* element. + + +Fontsizes less than 1 pt are clipped to be 1 pt. +------------------------------------------------ + +FreeType doesn't allow fonts to get smaller than 1 pt, so all Agg +backends were silently rounding up to 1 pt. PDF (other vector +backends?) were letting us write fonts that were less than 1 pt, but +they could not be placed properly because position information comes from +FreeType. This change makes it so no backends can use fonts smaller than +1 pt, consistent with FreeType and ensuring more consistent results across +backends. + + + +Changes to Qt backend class MRO +------------------------------- + +To support both Agg and cairo rendering for Qt backends all of the +non-Agg specific code previously in +:class:`.backend_qt5agg.FigureCanvasQTAggBase` has been moved to +:class:`.backend_qt5.FigureCanvasQT` so it can be shared with the cairo +implementation. The :meth:`.FigureCanvasQTAggBase.paintEvent`, +:meth:`.FigureCanvasQTAggBase.blit`, and +:meth:`.FigureCanvasQTAggBase.print_figure` methods have moved to +:meth:`.FigureCanvasQTAgg.paintEvent`, :meth:`.FigureCanvasQTAgg.blit`, and +:meth:`.FigureCanvasQTAgg.print_figure`. The first two methods assume that +the instance is also a :class:`QWidget` so to use +:class:`FigureCanvasQTAggBase` it was required to multiple inherit +from a :class:`QWidget` sub-class. + +Having moved all of its methods either up or down the class hierarchy +:class:`FigureCanvasQTAggBase` has been deprecated. To do this with +out warning and to preserve as much API as possible, +:class:`.backend_qt5.FigureCanvasQTAggBase` now inherits from +:class:`.backend_qt5.FigureCanvasQTAgg`. + +The MRO for :class:`FigureCanvasQTAgg` and +:class:`FigureCanvasQTAggBase` used to be :: + + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] + +and :: + + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backend_bases.FigureCanvasBase, + object] + + +respectively. They are now :: + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] + +and :: + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] + + + + +`Axes.imshow` clips RGB values to the valid range +------------------------------------------------- + +When `Axes.imshow` is passed an RGB or RGBA value with out-of-range +values, it now logs a warning and clips them to the valid range. +The old behaviour, wrapping back in to the range, often hid outliers +and made interpreting RGB images unreliable. + + +GTKAgg and GTKCairo backends deprecated +--------------------------------------- + +The GTKAgg and GTKCairo backends have been deprecated. These obsolete backends +allow figures to be rendered via the GTK+ 2 toolkit. They are untested, known +to be broken, will not work with Python 3, and their use has been discouraged +for some time. Instead, use the `GTK3Agg` and `GTK3Cairo` backends for +rendering to GTK+ 3 windows. diff --git a/doc/api/pyplot_summary.rst b/doc/api/pyplot_summary.rst index 4b290d5452af..83adccb1e02a 100644 --- a/doc/api/pyplot_summary.rst +++ b/doc/api/pyplot_summary.rst @@ -1,35 +1,18 @@ -Below we describe several common approaches to plotting with Matplotlib. +Pyplot function overview +------------------------ -.. contents:: +.. currentmodule:: matplotlib -The Pyplot API --------------- +.. autosummary:: + :toctree: _as_gen + :template: autofunctions.rst -The :mod:`matplotlib.pyplot` module contains functions that allow you to generate -many kinds of plots quickly. For examples that showcase the use -of the :mod:`matplotlib.pyplot` module, see the -:doc:`/tutorials/introductory/pyplot` -or the :ref:`pyplots_examples`. We also recommend that you look into -the object-oriented approach to plotting, described below. + pyplot .. currentmodule:: matplotlib.pyplot .. autofunction:: plotting -The Object-Oriented API ------------------------ - -Most of these functions also exist as methods in the -:class:`matplotlib.axes.Axes` class. You can use them with the -"Object Oriented" approach to Matplotlib. - -While it is easy to quickly generate plots with the -:mod:`matplotlib.pyplot` module, -we recommend using the object-oriented approach for more control -and customization of your plots. See the methods in the -:meth:`matplotlib.axes.Axes` class for many of the same plotting functions. -For examples of the OO approach to Matplotlib, see the -:ref:`API Examples`. Colors in Matplotlib -------------------- @@ -40,4 +23,6 @@ Below we list several ways in which color can be utilized in Matplotlib. For a more in-depth look at colormaps, see the :doc:`/tutorials/colors/colormaps` tutorial. +.. currentmodule:: matplotlib.pyplot + .. autofunction:: colormaps diff --git a/doc/api/transformations.rst b/doc/api/transformations.rst index 1164031094fb..a50d35b080b8 100644 --- a/doc/api/transformations.rst +++ b/doc/api/transformations.rst @@ -1,6 +1,6 @@ -============================== - Working with transformations -============================== +*************** +transformations +*************** .. inheritance-diagram:: matplotlib.transforms matplotlib.path :parts: 1 diff --git a/doc/citing.rst b/doc/citing.rst index d22673c53503..682e05b973d2 100644 --- a/doc/citing.rst +++ b/doc/citing.rst @@ -5,7 +5,7 @@ Citing Matplotlib If Matplotlib contributes to a project that leads to a scientific publication, please acknowledge this fact by citing `Hunter et al (2007) -`_ using this ready-made BibTeX entry: +`_ using this ready-made BibTeX entry: .. code-block:: bibtex diff --git a/doc/conf.py b/doc/conf.py index 4b4b5ed5f383..c67b3f335ee5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Matplotlib documentation build configuration file, created by # sphinx-quickstart on Fri May 2 12:33:25 2008. # @@ -11,12 +9,12 @@ # All configuration values have a default value; values that are commented out # serve to show the default value. -import matplotlib import os +import shutil import sys + +import matplotlib import sphinx -import six -from glob import glob # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it @@ -40,7 +38,6 @@ 'numpydoc', # Needs to be loaded *after* autodoc. 'sphinx_gallery.gen_gallery', 'matplotlib.sphinxext.mathmpl', - 'matplotlib.sphinxext.only_directives', 'matplotlib.sphinxext.plot_directive', 'sphinxext.custom_roles', 'sphinxext.github', @@ -59,8 +56,6 @@ def _check_deps(): "numpydoc": 'numpydoc', "PIL.Image": 'pillow', "sphinx_gallery": 'sphinx_gallery'} - if sys.version_info < (3, 3): - names["mock"] = 'mock' missing = [] for name in names: try: @@ -75,17 +70,13 @@ def _check_deps(): _check_deps() # Import only after checking for dependencies. -from sphinx_gallery.sorting import ExplicitOrder -# This is only necessary to monkey patch the signature later on. +# gallery_order.py from the sphinxext folder provides the classes that +# allow custom ordering of sections and subsections of the gallery +import sphinxext.gallery_order as gallery_order +# The following import is only necessary to monkey patch the signature later on from sphinx_gallery import gen_rst -if six.PY2: - from distutils.spawn import find_executable - has_dot = find_executable('dot') is not None -else: - from shutil import which # Python >= 3.3 - has_dot = which('dot') is not None -if not has_dot: +if shutil.which('dot') is None: raise OSError( "No binary named dot - you need to install the Graph Visualization " "software (usually packaged as 'graphviz') to build the documentation") @@ -93,7 +84,10 @@ def _check_deps(): autosummary_generate = True autodoc_docstring_signature = True -autodoc_default_flags = ['members', 'undoc-members'] +if sphinx.version_info < (1, 8): + autodoc_default_flags = ['members', 'undoc-members'] +else: + autodoc_default_options = {'members': None, 'undoc-members': None} intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), @@ -103,27 +97,6 @@ def _check_deps(): 'cycler': ('https://matplotlib.org/cycler', None), } -explicit_order_folders = [ - '../examples/api', - '../examples/pyplots', - '../examples/subplots_axes_and_figures', - '../examples/color', - '../examples/statistics', - '../examples/lines_bars_and_markers', - '../examples/images_contours_and_fields', - '../examples/shapes_and_collections', - '../examples/text_labels_and_annotations', - '../examples/pie_and_polar_charts', - '../examples/style_sheets', - '../examples/axes_grid', - '../examples/showcase', - '../tutorials/introductory', - '../tutorials/intermediate', - '../tutorials/advanced'] -for folder in sorted(glob('../examples/*') + glob('../tutorials/*')): - if not os.path.isdir(folder) or folder in explicit_order_folders: - continue - explicit_order_folders.append(folder) # Sphinx gallery configuration sphinx_gallery_conf = { @@ -137,7 +110,8 @@ def _check_deps(): 'scipy': 'https://docs.scipy.org/doc/scipy/reference', }, 'backreferences_dir': 'api/_as_gen', - 'subsection_order': ExplicitOrder(explicit_order_folders), + 'subsection_order': gallery_order.sectionorder, + 'within_subsection_order': gallery_order.subsectionorder, 'min_reported_time': 1, } @@ -166,7 +140,7 @@ def _check_deps(): master_doc = 'contents' # General substitutions. -from matplotlib.compat.subprocess import check_output +from subprocess import check_output SHA = check_output(['git', 'describe', '--dirty']).decode('utf-8').strip() html_context = {'sha': SHA} @@ -177,7 +151,6 @@ def _check_deps(): 'team; 2012 - 2018 The Matplotlib development team') - # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # diff --git a/doc/contents.rst b/doc/contents.rst index 104e143a07c2..8102de243339 100644 --- a/doc/contents.rst +++ b/doc/contents.rst @@ -3,7 +3,7 @@ Overview ======== -.. htmlonly:: +.. only:: html :Release: |version| :Date: |today| @@ -16,14 +16,13 @@ Overview users/index.rst faq/index.rst - api/toolkits/index.rst + api/index.rst resources/index.rst thirdpartypackages/index.rst - api/index.rst devel/index.rst glossary/index.rst -.. htmlonly:: +.. only:: html * :ref:`genindex` * :ref:`modindex` diff --git a/doc/devel/MEP/MEP28.rst b/doc/devel/MEP/MEP28.rst index 6cd9814b805d..c5e4ce49a8a5 100644 --- a/doc/devel/MEP/MEP28.rst +++ b/doc/devel/MEP/MEP28.rst @@ -254,13 +254,13 @@ This MEP can be divided into a few loosely coupled components: With this approach, #2 depends and #1, and #4 depends on #3. There are two possible approaches to #2. The first and most direct would -be to mirror the new ``transform_in`` and ``tranform_out`` parameters of +be to mirror the new ``transform_in`` and ``transform_out`` parameters of ``cbook.boxplot_stats`` in ``Axes.boxplot`` and pass them directly. The second approach would be to add ``statfxn`` and ``statfxn_args`` parameters to ``Axes.boxplot``. Under this implementation, the default value of ``statfxn`` would be ``cbook.boxplot_stats``, but users could -pass their own function. Then ``transform_in`` and ``tranform_out`` would +pass their own function. Then ``transform_in`` and ``transform_out`` would then be passed as elements of the ``statfxn_args`` parameter. .. code:: python diff --git a/doc/devel/MEP/index.rst b/doc/devel/MEP/index.rst index 06c454ad240f..6753626aa567 100644 --- a/doc/devel/MEP/index.rst +++ b/doc/devel/MEP/index.rst @@ -2,7 +2,7 @@ .. include:: README.rst -.. htmlonly:: +.. only:: html :Release: |version| :Date: |today| diff --git a/doc/devel/add_new_projection.rst b/doc/devel/add_new_projection.rst index 9ef7c8f27ac0..7cf766267e44 100644 --- a/doc/devel/add_new_projection.rst +++ b/doc/devel/add_new_projection.rst @@ -39,30 +39,29 @@ Adding a new scale consists of defining a subclass of :class:`matplotlib.scale.ScaleBase`, that includes the following elements: - - A transformation from data coordinates into display coordinates. +- A transformation from data coordinates into display coordinates. - - An inverse of that transformation. This is used, for example, to - convert mouse positions from screen space back into data space. +- An inverse of that transformation. This is used, for example, to + convert mouse positions from screen space back into data space. - - A function to limit the range of the axis to acceptable values - (``limit_range_for_scale()``). A log scale, for instance, would - prevent the range from including values less than or equal to - zero. +- A function to limit the range of the axis to acceptable values + (``limit_range_for_scale()``). A log scale, for instance, would + prevent the range from including values less than or equal to zero. - - Locators (major and minor) that determine where to place ticks in - the plot, and optionally, how to adjust the limits of the plot to - some "good" values. Unlike ``limit_range_for_scale()``, which is - always enforced, the range setting here is only used when - automatically setting the range of the plot. +- Locators (major and minor) that determine where to place ticks in + the plot, and optionally, how to adjust the limits of the plot to + some "good" values. Unlike ``limit_range_for_scale()``, which is + always enforced, the range setting here is only used when + automatically setting the range of the plot. - - Formatters (major and minor) that specify how the tick labels - should be drawn. +- Formatters (major and minor) that specify how the tick labels + should be drawn. Once the class is defined, it must be registered with matplotlib so that the user can select it. A full-fledged and heavily annotated example is in -:file:`examples/api/custom_scale_example.py`. There are also some classes +:doc:`/gallery/scales/custom_scale`. There are also some classes in :mod:`matplotlib.scale` that may be used as starting points. @@ -75,55 +74,52 @@ Adding a new projection consists of defining a projection axes which subclasses :class:`matplotlib.axes.Axes` and includes the following elements: - - A transformation from data coordinates into display coordinates. +- A transformation from data coordinates into display coordinates. - - An inverse of that transformation. This is used, for example, to - convert mouse positions from screen space back into data space. +- An inverse of that transformation. This is used, for example, to + convert mouse positions from screen space back into data space. - - Transformations for the gridlines, ticks and ticklabels. Custom - projections will often need to place these elements in special - locations, and matplotlib has a facility to help with doing so. +- Transformations for the gridlines, ticks and ticklabels. Custom + projections will often need to place these elements in special + locations, and matplotlib has a facility to help with doing so. - - Setting up default values (overriding - :meth:`~matplotlib.axes.Axes.cla`), since the defaults for a - rectilinear axes may not be appropriate. +- Setting up default values (overriding :meth:`~matplotlib.axes.Axes.cla`), + since the defaults for a rectilinear axes may not be appropriate. - - Defining the shape of the axes, for example, an elliptical axes, - that will be used to draw the background of the plot and for - clipping any data elements. +- Defining the shape of the axes, for example, an elliptical axes, that will be + used to draw the background of the plot and for clipping any data elements. - - Defining custom locators and formatters for the projection. For - example, in a geographic projection, it may be more convenient to - display the grid in degrees, even if the data is in radians. +- Defining custom locators and formatters for the projection. For + example, in a geographic projection, it may be more convenient to + display the grid in degrees, even if the data is in radians. - - Set up interactive panning and zooming. This is left as an - "advanced" feature left to the reader, but there is an example of - this for polar plots in :mod:`matplotlib.projections.polar`. +- Set up interactive panning and zooming. This is left as an + "advanced" feature left to the reader, but there is an example of + this for polar plots in :mod:`matplotlib.projections.polar`. - - Any additional methods for additional convenience or features. +- Any additional methods for additional convenience or features. Once the projection axes is defined, it can be used in one of two ways: - - By defining the class attribute ``name``, the projection axes can be - registered with :func:`matplotlib.projections.register_projection` - and subsequently simply invoked by name:: +- By defining the class attribute ``name``, the projection axes can be + registered with :func:`matplotlib.projections.register_projection` + and subsequently simply invoked by name:: - plt.axes(projection='my_proj_name') + plt.axes(projection='my_proj_name') - - For more complex, parameterisable projections, a generic "projection" - object may be defined which includes the method ``_as_mpl_axes``. - ``_as_mpl_axes`` should take no arguments and return the projection's - axes subclass and a dictionary of additional arguments to pass to the - subclass' ``__init__`` method. Subsequently a parameterised projection - can be initialised with:: +- For more complex, parameterisable projections, a generic "projection" object + may be defined which includes the method ``_as_mpl_axes``. ``_as_mpl_axes`` + should take no arguments and return the projection's axes subclass and a + dictionary of additional arguments to pass to the subclass' ``__init__`` + method. Subsequently a parameterised projection can be initialised with:: - plt.axes(projection=MyProjection(param1=param1_value)) + plt.axes(projection=MyProjection(param1=param1_value)) - where MyProjection is an object which implements a ``_as_mpl_axes`` method. + where MyProjection is an object which implements a ``_as_mpl_axes`` method. A full-fledged and heavily annotated example is in -:file:`examples/api/custom_projection_example.py`. The polar plot +:doc:`/gallery/misc/custom_projection`. The polar plot functionality in :mod:`matplotlib.projections.polar` may also be of interest. diff --git a/doc/devel/contributing.rst b/doc/devel/contributing.rst index 35f376beea66..b40a46a6fa92 100644 --- a/doc/devel/contributing.rst +++ b/doc/devel/contributing.rst @@ -142,18 +142,17 @@ Additionally you will need to copy :file:`setup.cfg.template` to In either case you can then run the tests to check your work environment is set up properly:: - python tests.py + pytest .. _pytest: http://doc.pytest.org/en/latest/ .. _pep8: https://pep8.readthedocs.io/en/latest/ -.. _mock: https://docs.python.org/dev/library/unittest.mock.html .. _Ghostscript: https://www.ghostscript.com/ .. _Inkscape: https://inkscape.org> .. note:: - **Additional dependencies for testing**: pytest_ (version 3.1 or later), - mock_ (if Python 2), Ghostscript_, Inkscape_ + **Additional dependencies for testing**: pytest_ (version 3.4 or later), + Ghostscript_, Inkscape_ .. seealso:: @@ -266,7 +265,7 @@ tools: * Code with a good unittest coverage (at least 70%, better 100%), check with:: python -mpip install coverage - python tests.py --with-coverage + pytest --cov=matplotlib --showlocals -v * No pyflakes warnings, check with:: @@ -395,42 +394,21 @@ on, use the key/value keyword args in the function definition rather than the ``**kwargs`` idiom. In some cases, you may want to consume some keys in the local -function, and let others pass through. You can ``pop`` the ones to be -used locally and pass on the rest. For example, in +function, and let others pass through. Instead of poping arguments to +use off ``**kwargs``, specify them as keyword-only arguments to the local +function. This makes it obvious at a glance which arguments will be +consumed in the function. For example, in :meth:`~matplotlib.axes.Axes.plot`, ``scalex`` and ``scaley`` are local arguments and the rest are passed on as :meth:`~matplotlib.lines.Line2D` keyword arguments:: # in axes/_axes.py - def plot(self, *args, **kwargs): - scalex = kwargs.pop('scalex', True) - scaley = kwargs.pop('scaley', True) - if not self._hold: self.cla() + def plot(self, *args, scalex=True, scaley=True, **kwargs): lines = [] for line in self._get_lines(*args, **kwargs): self.add_line(line) lines.append(line) -Note: there is a use case when ``kwargs`` are meant to be used locally -in the function (not passed on), but you still need the ``**kwargs`` -idiom. That is when you want to use ``*args`` to allow variable -numbers of non-keyword args. In this case, python will not allow you -to use named keyword args after the ``*args`` usage, so you will be -forced to use ``**kwargs``. An example is -:meth:`matplotlib.contour.ContourLabeler.clabel`:: - - # in contour.py - def clabel(self, *args, **kwargs): - fontsize = kwargs.get('fontsize', None) - inline = kwargs.get('inline', 1) - self.fmt = kwargs.get('fmt', '%1.3f') - colors = kwargs.get('colors', None) - if len(args) == 0: - levels = self.levels - indices = range(len(self.levels)) - elif len(args) == 1: - ...etc... - .. _using_logging: Using logging for debug messages diff --git a/doc/devel/documenting_mpl.rst b/doc/devel/documenting_mpl.rst index 2760ed7816c4..42f5bd89cb88 100644 --- a/doc/devel/documenting_mpl.rst +++ b/doc/devel/documenting_mpl.rst @@ -4,6 +4,13 @@ Writing documentation ===================== +.. contents:: Contents + :depth: 3 + :local: + :backlinks: top + :class: multicol-toc + + Getting started =============== @@ -27,11 +34,11 @@ the docstrings of the classes in the Matplotlib library. Except for :file:`doc/api/api_changes/`, these ``.rst`` files are created when the documentation is built. -Similarly, the contents of :file:`docs/gallery` and :file:`docs/tutorials` are +Similarly, the contents of :file:`doc/gallery` and :file:`doc/tutorials` are generated by the `Sphinx Gallery`_ from the sources in :file:`examples` and :file:`tutorials`. These sources consist of python scripts that have ReST_ documentation built into their comments. Don't directly edit the -``.rst`` files in :file:`docs/gallery` and :file:`docs/tutorials` as they are +``.rst`` files in :file:`doc/gallery` and :file:`doc/tutorials` as they are regenerated when the documentation are built. Installing dependencies @@ -42,13 +49,12 @@ using the Sphinx_ documentation generation tool. There are several extra requirements that are needed to build the documentation. They are listed in :file:`doc-requirements.txt` and listed below: -* Sphinx>=1.3, !=1.5.0, !=1.6.4 +* Sphinx>=1.3, !=1.5.0, !=1.6.4, !=1.7.3 * colorspacious * IPython -* mock * numpydoc>=0.4 * Pillow -* sphinx-gallery>=0.1.12 +* sphinx-gallery>=0.1.13 * graphviz .. note:: @@ -214,6 +220,7 @@ is better than: In addition, since underscores are widely used by Sphinx itself, use hyphens to separate words. +.. _referring-to-other-code: Referring to other code ----------------------- @@ -221,31 +228,44 @@ Referring to other code To link to other methods, classes, or modules in Matplotlib you can use back ticks, for example: -.. code-block:: python +.. code-block:: rst - `~matplotlib.collections.LineCollection` + `matplotlib.collections.LineCollection` -returns a link to the documentation of -`~matplotlib.collections.LineCollection`. For the full path of the -class to be shown, omit the tilde: +generates a link like this: `matplotlib.collections.LineCollection`. -.. code-block:: python +*Note:* We use the sphinx setting ``default_role = 'obj'`` so that you don't +have to use qualifiers like ``:class:``, ``:func:``, ``:meth:`` and the likes. - `matplotlib.collections.LineCollection` +Often, you don't want to show the full package and module name. As long as the +target is unanbigous you can simply leave them out: -to get `matplotlib.collections.LineCollection`. It is often not -necessary to fully specify the class hierarchy unless there is a namespace -collision between two packages: +.. code-block:: rst -.. code-block:: python + `.LineCollection` - `~.LineCollection` +and the link still works: `.LineCollection`. -links just as well: `~.LineCollection`. +If there are multiple code elements with the same name (e.g. ``plot()`` is a +method in multiple classes), you'll have to extend the definition: -Other packages can also be linked via ``intersphinx``: +.. code-block:: rst -.. code-block:: Python + `.pyplot.plot` or `.Axes.plot` + +These will show up as `.pyplot.plot` or `.Axes.plot`. To still show only the +last segment you can add a tilde as prefix: + +.. code-block:: rst + + `~.pyplot.plot` or `~.Axes.plot` + +will render as `~.pyplot.plot` or `~.Axes.plot`. + +Other packages can also be linked via +`intersphinx `_: + +.. code-block:: rst `numpy.mean` @@ -285,7 +305,7 @@ so plots from the examples directory can be included using .. code-block:: rst - .. plot:: gallery/pylab_examples/simple_plot.py + .. plot:: gallery/lines_bars_and_markers/simple_plot.py Note that the python script that generates the plot is referred to, rather than any plot that is created. Sphinx-gallery will provide the correct reference @@ -297,13 +317,19 @@ when the documentation is built. Writing docstrings ================== -Much of the documentation lives in "docstrings". These are comment blocks -in source code that explain how the code works. All new or edited docstrings -should conform to the numpydoc guidelines. These split the docstring into a -number of sections - see the `numpy documentation howto`_ -for more details and a guide to how docstrings should be formatted. Much of -the ReST_ syntax discussed above (:ref:writing-rest-pages) can be used for -links and references. These docstrings eventually populate the +Most of the API documentation is written in docstrings. These are comment +blocks in source code that explain how the code works. + +.. note:: + + Some parts of the documentation do not yet conform to the current + documentation style. If in doubt, follow the rules given here and not what + you may see in the source code. Pull requests updating docstrings to + the current style are very welcome. + +All new or edited docstrings should conform to the `numpydoc docstring guide`_. +Much of the ReST_ syntax discussed above (:ref:`writing-rest-pages`) can be +used for links and references. These docstrings eventually populate the :file:`doc/api` directory and form the reference documentation for the library. @@ -314,21 +340,21 @@ An example docstring looks like: .. code-block:: python - def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', - label='', **kwargs): + def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', + label='', **kwargs): """ Plot horizontal lines at each *y* from *xmin* to *xmax*. Parameters ---------- - y : scalar or sequence of scalar + y : float or array-like y-indexes where to plot the lines. - xmin, xmax : scalar or 1D array_like + xmin, xmax : float or array-like Respective beginning and end of each line. If scalars are - provided, all lines will have same length. + provided, all lines will have the same length. - colors : array_like of colors, optional, default: 'k' + colors : array-like of colors, optional, default: 'k' linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional @@ -353,104 +379,144 @@ See the `~.Axes.hlines` documentation for how this renders. The Sphinx_ website also contains plenty of documentation_ concerning ReST markup and working with Sphinx in general. -.. note:: - - Some parts of the documentation do not yet conform to the current - documentation style. If in doubt, follow the rules given here and not what - you may see in the source code. Pull requests updating docstrings to - the current style are very welcome. - Formatting conventions ---------------------- -The basic docstring conventions are covered in the `numpy documentation howto`_ +The basic docstring conventions are covered in the `numpydoc docstring guide`_ and the Sphinx_ documentation. Some Matplotlib-specific formatting conventions to keep in mind: -* Matplotlib does not have a convention whether to use single-quotes or - double-quotes. There is a mixture of both in the current code. +Function arguments +~~~~~~~~~~~~~~~~~~ +Function arguments and keywords within docstrings should be referred to +using the ``*emphasis*`` role. This will keep Matplotlib's documentation +consistent with Python's documentation: -* Long parameter lists should be wrapped using a ``\`` for continuation and - starting on the new line without any indent: +.. code-block:: rst - .. code-block:: python + If *linestyles* is *None*, the 'solid' is used. - def add_axes(self, *args, **kwargs): - """ - ... +Do not use the ```default role``` or the ````literal```` role: - Parameters - ---------- - projection : - {'aitoff', 'hammer', 'lambert', 'mollweide', 'polar', \ - 'rectilinear'}, optional - The projection type of the axes. +.. code-block:: rst - ... - """ + Neither `argument` nor ``argument`` should be used. - Alternatively, you can describe the valid parameter values in a dedicated - section of the docstring. -* Generally, do not add markup to types for ``Parameters`` and ``Returns``. - This is usually not needed because Sphinx will link them automatically and - would unnecessarily clutter the docstring. However, it does seem to fail in - some situations. If you encounter such a case, you are allowed to add markup: +Quotes for strings +~~~~~~~~~~~~~~~~~~ +Matplotlib does not have a convention whether to use single-quotes or +double-quotes. There is a mixture of both in the current code. - .. code-block:: rst +Use simple single or double quotes when giving string values, e.g.:: rst - Returns - ------- - lines : `~matplotlib.collections.LineCollection` +.. code-block:: rst -* rcParams can be referenced with the custom ``:rc:`` role: - :literal:`:rc:\`foo\`` yields ``rcParams["foo"]``. + If 'tight', try to figure out the tight bbox of the figure. -Deprecated formatting conventions ---------------------------------- -* Formerly, we have used square brackets for explicit parameter lists - ``['solid' | 'dashed' | 'dotted']``. With numpydoc we have switched to their - standard using curly braces ``{'solid', 'dashed', 'dotted'}``. +Parameter type descriptions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The main goal for parameter type descriptions is to be readable and +understandable by humans. If the possible types are too complex use a +simplification for the type description and explain the type more +precisely in the text. -Linking to other code ---------------------- -To link to other methods, classes, or modules in Matplotlib you can encase -the name to refer to in back ticks, for example: +Generally, the `numpydoc docstring guide`_ conventions apply. The following +rules expand on them where the numpydoc conventions are not specific. -.. code-block:: python +Use ``float`` for a type that can be any number. - `~matplotlib.collections.LineCollection` +Use ``array-like`` for homogeneous numeric sequences, which could +typically be a numpy.array. Dimensionality may be specified using ``2D``, +``3D``, ``n-dimensional``. If you need to have variables denoting the +sizes of the dimensions, use capital letters in brackets +(``array-like (M, N)``). When refering to them in the text they are easier +read and no special formatting is needed. -It is also possible to add links to code in Python, Numpy, Scipy, or Pandas. -Sometimes it is tricky to get external Sphinx linking to work; to check that -a something exists to link to the following shell command outputs a list of all -objects that can be referenced (in this case for Numpy):: +``float`` is the implicit default dtype for array-likes. For other dtypes +use ``array-like of int``. - python -m sphinx.ext.intersphinx 'https://docs.scipy.org/doc/numpy/objects.inv' +Some possible uses:: + 2D array-like + array-like (N) + array-like (M, N) + array-like (M, N, 3) + array-like of int -Function arguments ------------------- -Function arguments and keywords within docstrings should be referred to using -the ``*emphasis*`` role. This will keep Matplotlib's documentation consistent -with Python's documentation: +Non-numeric homogeneous sequences are described as lists, e.g.:: -.. code-block:: rst + list of str + list of `.Artist` - Here is a description of *argument* +Referencing types +~~~~~~~~~~~~~~~~~ +Generally, the rules from referring-to-other-code_ apply. More specifically: -Do not use the ```default role```: +Use full references ```~matplotlib.colors.Normalize``` with an +abbreviation tilde in parameter types. While the full name helps the +reader of plain text docstrings, the HTML does not need to show the full +name as it links to it. Hence, the ``~``-shortening keeps it more readable. +Use abbreviated links ```.Normalize``` in the text. .. code-block:: rst - Do not describe `argument` like this. + norm : `~matplotlib.colors.Normalize`, optional + A `.Normalize` instance is used to scale luminance data to 0, 1. -nor the ````literal```` role: +``See also`` sections +~~~~~~~~~~~~~~~~~~~~~ +Sphinx automatically links code elements in the definition blocks of ``See +also`` sections. No need to use backticks there:: + + See also + -------- + vlines : vertical lines + axhline: horizontal line across the axes + +Wrapping parameter lists +~~~~~~~~~~~~~~~~~~~~~~~~ +Long parameter lists should be wrapped using a ``\`` for continuation and +starting on the new line without any indent: + +.. code-block:: python + + def add_axes(self, *args, **kwargs): + """ + ... + + Parameters + ---------- + projection : + {'aitoff', 'hammer', 'lambert', 'mollweide', 'polar', \ + 'rectilinear'}, optional + The projection type of the axes. + + ... + """ + +Alternatively, you can describe the valid parameter values in a dedicated +section of the docstring. + +rcParams +~~~~~~~~ +rcParams can be referenced with the custom ``:rc:`` role: +:literal:`:rc:\`foo\`` yields ``rcParams["foo"]``. Use `= [default-val]` +to indicate the default value of the parameter. The default value should be +literal, i.e. enclosed in double backticks. For simplicity these may be +omitted for string default values. .. code-block:: rst - Do not describe ``argument`` like this. + If not provided, defaults to :rc:`figure.figsize` = ``[6.4, 4.8]``. + If not provided, defaults to :rc:`figure.facecolor` = 'w'. + +Deprecated formatting conventions +--------------------------------- +Formerly, we have used square brackets for explicit parameter lists +``['solid' | 'dashed' | 'dotted']``. With numpydoc we have switched to their +standard using curly braces ``{'solid', 'dashed', 'dotted'}``. Setters and getters ------------------- @@ -461,6 +527,12 @@ By convention, these setters and getters are named ``set_PROPERTYNAME`` and ``get_PROPERTYNAME``; the list of properties thusly defined on an artist and their values can be listed by the `~.pyplot.setp` and `~.pyplot.getp` functions. +.. note:: + + ``ACCEPTS`` blocks have recently become optional. You may now use a + numpydoc ``Parameters`` block because the accepted values can now be read + from the type description of the first parameter. + Property setter methods should indicate the values they accept using a (legacy) special block in the docstring, starting with ``ACCEPTS``, as follows: @@ -494,7 +566,6 @@ Sphinx by making it a ReST comment (i.e. use ``.. ACCEPTS:``): """ - Keyword arguments ----------------- @@ -567,22 +638,22 @@ Adding figures As above (see :ref:`rst-figures-and-includes`), figures in the examples gallery can be referenced with a `:plot:` directive pointing to the python script that created the figure. For instance the `~.Axes.legend` docstring references -the file :file:`examples/api/legend.py`: +the file :file:`examples/text_labels_and_annotations/legend.py`: .. code-block:: python """ - ... + ... Examples -------- - .. plot:: gallery/api/legend.py + .. plot:: gallery/text_labels_and_annotations/legend.py """ -Note that ``examples/api/legend.py`` has been mapped to -``gallery/api/legend.py``, a redirection that may be fixed in future -re-organization of the docs. +Note that ``examples/text_labels_and_annotations/legend.py`` has been mapped to +``gallery/text_labels_and_annotations/legend.py``, a redirection that may be +fixed in future re-organization of the docs. Plots can also be directly placed inside docstrings. Details are in :doc:`/devel/plot_directive`. A short example is: @@ -590,7 +661,7 @@ Plots can also be directly placed inside docstrings. Details are in .. code-block:: python """ - ... + ... Examples -------- @@ -681,6 +752,28 @@ are delimited by a line of `###` characters: In this way text, code, and figures are output in a "notebook" style. +Order of examples in the gallery +-------------------------------- + +The order of the sections of the :ref:`tutorials` and the :ref:`gallery`, as +well as the order of the examples within each section are determined in a +two step process from within the :file:`/doc/sphinxext/gallery_order.py`: + +* *Explicit order*: This file contains a list of folders for the section order + and a list of examples for the subsection order. The order of the items + shown in the doc pages is the order those items appear in those lists. +* *Implicit order*: If a folder or example is not in those lists, it will be + appended after the explicitely ordered items and all of those additional + items will be ordered by pathname (for the sections) or by filename + (for the subsections). + +As a consequence, if you want to let your example appear in a certain +position in the gallery, extend those lists with your example. +In case no explicit order is desired or necessary, still make sure +to name your example consistently, i.e. use the main function or subject +of the example as first word in the filename; e.g. an image example +should ideally be named similar to :file:`imshow_mynewexample.py`. + Miscellaneous ============= @@ -791,4 +884,4 @@ Some helpful functions:: .. _index: http://www.sphinx-doc.org/markup/para.html#index-generating-markup .. _`Sphinx Gallery`: https://sphinx-gallery.readthedocs.io/en/latest/ .. _references: http://www.sphinx-doc.org/en/stable/markup/inline.html -.. _`numpy documentation howto`: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt +.. _`numpydoc docstring guide`: https://numpydoc.readthedocs.io/en/latest/format.html diff --git a/doc/devel/index.rst b/doc/devel/index.rst index 16a2d7d5d393..ff190d0288c0 100644 --- a/doc/devel/index.rst +++ b/doc/devel/index.rst @@ -4,7 +4,7 @@ The Matplotlib Developers' Guide ################################ -.. htmlonly:: +.. only:: html :Release: |version| :Date: |today| @@ -17,7 +17,6 @@ The Matplotlib Developers' Guide documenting_mpl.rst plot_directive.rst add_new_projection.rst - portable_code.rst gitwash/index.rst coding_guide.rst release_guide.rst diff --git a/doc/devel/portable_code.rst b/doc/devel/portable_code.rst deleted file mode 100644 index 9274c181ac2b..000000000000 --- a/doc/devel/portable_code.rst +++ /dev/null @@ -1,124 +0,0 @@ - -.. _portable_code: - -===================================================== -Developer's tips for writing code for Python 2 and 3 -===================================================== - -As of matplotlib 1.4, the `six `_ -library is used to support Python 2 and 3 from a single code base. -The `2to3` tool is no longer used. - -This document describes some of the issues with that approach and some -recommended solutions. It is not a complete guide to Python 2 and 3 -compatibility. - -Welcome to the ``__future__`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The top of every `.py` file should include the following:: - - from __future__ import (absolute_import, division, - print_function, unicode_literals) - import six - -This will make the Python 2 interpreter behave as close to Python 3 as -possible. - -All matplotlib files should also import `six`, whether they are using -it or not, just to make moving code between modules easier, as `six` -gets used *a lot*. - - -Finding places to use six -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The only way to make sure code works on both Python 2 and 3 is to make sure it -is covered by unit tests. - -However, the `2to3` commandline tool can also be used to locate places -that require special handling with `six`. - -(The `modernize `_ tool may -also be handy, though I've never used it personally). - -The `six `_ documentation serves as a -good reference for the sorts of things that need to be updated. - -The dreaded ``\u`` escapes -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When `from __future__ import unicode_literals` is used, all string -literals (not preceded with a `b`) will become unicode literals. - -Normally, one would use "raw" string literals to encode strings that -contain a lot of slashes that we don't want Python to interpret as -special characters. A common example in matplotlib is when it deals -with TeX and has to represent things like ``r"\usepackage{foo}"``. -Unfortunately, on Python 2there is no way to represent `\u` in a raw -unicode string literal, since it will always be interpreted as the -start of a unicode character escape, such as `\u20af`. The only -solution is to use a regular (non-raw) string literal and repeat all -slashes, e.g. ``"\\usepackage{foo}"``. - -The following shows the problem on Python 2:: - - >>> ur'\u' - File "", line 1 - SyntaxError: (unicode error) 'rawunicodeescape' codec can't decode bytes in - position 0-1: truncated \uXXXX - >>> ur'\\u' - u'\\\\u' - >>> u'\u' - File "", line 1 - SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in - position 0-1: truncated \uXXXX escape - >>> u'\\u' - u'\\u' - -This bug has been fixed in Python 3, however, we can't take advantage -of that and still support Python 2:: - - >>> r'\u' - '\\u' - >>> r'\\u' - '\\\\u' - >>> '\u' - File "", line 1 - SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in - position 0-1: truncated \uXXXX escape - >>> '\\u' - '\\u' - -Iteration -^^^^^^^^^ - -The behavior of the methods for iterating over the items, values and -keys of a dictionary has changed in Python 3. Additionally, other -built-in functions such as `zip`, `range` and `map` have changed to -return iterators rather than temporary lists. - -In many cases, the performance implications of iterating vs. creating -a temporary list won't matter, so it's tempting to use the form that -is simplest to read. However, that results in code that behaves -differently on Python 2 and 3, leading to subtle bugs that may not be -detected by the regression tests. Therefore, unless the loop in -question is provably simple and doesn't call into other code, the -`six` versions that ensure the same behavior on both Python 2 and 3 -should be used. The following table shows the mapping of equivalent -semantics between Python 2, 3 and six for `dict.items()`: - -============================== ============================== ============================== -Python 2 Python 3 six -============================== ============================== ============================== -``d.items()`` ``list(d.items())`` ``list(six.iteritems(d))`` -``d.iteritems()`` ``d.items()`` ``six.iteritems(d)`` -============================== ============================== ============================== - -Numpy-specific things -^^^^^^^^^^^^^^^^^^^^^ - -When specifying dtypes, all strings must be byte strings on Python 2 -and unicode strings on Python 3. The best way to handle this is to -force cast them using `str()`. The same is true of structure -specifiers in the `struct` built-in module. diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst index 73d7eb8bea39..48350c79d02b 100644 --- a/doc/devel/release_guide.rst +++ b/doc/devel/release_guide.rst @@ -20,7 +20,8 @@ We use `travis-ci `__ for continuous integration. When preparing for a release, the final tagged commit should be tested locally before it is uploaded:: - python tests.py --processes=8 --process-timeout=300 + pytest -n 8 . + In addition the following two tests should be run and manually inspected:: diff --git a/doc/devel/testing.rst b/doc/devel/testing.rst index 8334a132a74a..0d4a5e6f352a 100644 --- a/doc/devel/testing.rst +++ b/doc/devel/testing.rst @@ -9,7 +9,6 @@ Matplotlib's testing infrastructure depends on pytest_. The tests are in infrastructure are in :mod:`matplotlib.testing`. .. _pytest: http://doc.pytest.org/en/latest/ -.. _mock: https://docs.python.org/3/library/unittest.mock.html .. _Ghostscript: https://www.ghostscript.com/ .. _Inkscape: https://inkscape.org .. _pytest-cov: https://pytest-cov.readthedocs.io/en/latest/ @@ -26,17 +25,16 @@ local FreeType build The following software is required to run the tests: - - pytest_ (>=3.1) - - mock_, when running Python 2 - - Ghostscript_ (to render PDF files) - - Inkscape_ (to render SVG files) +- pytest_ (>=3.4) +- Ghostscript_ (to render PDF files) +- Inkscape_ (to render SVG files) Optionally you can install: - - pytest-cov_ (>=2.3.1) to collect coverage information - - pytest-pep8_ to test coding standards - - pytest-timeout_ to limit runtime in case of stuck tests - - pytest-xdist_ to run tests in parallel +- pytest-cov_ (>=2.3.1) to collect coverage information +- pytest-pep8_ to test coding standards +- pytest-timeout_ to limit runtime in case of stuck tests +- pytest-xdist_ to run tests in parallel Running the tests @@ -44,11 +42,12 @@ Running the tests Running the tests is simple. Make sure you have pytest installed and run:: - py.test + pytest or:: - python tests.py + pytest . + in the root directory of the distribution. The script takes a set of commands, such as: @@ -74,22 +73,22 @@ To run a single test from the command line, you can provide a file path, optionally followed by the function separated by two colons, e.g., (tests do not need to be installed, but Matplotlib should be):: - py.test lib/matplotlib/tests/test_simplification.py::test_clipping + pytest lib/matplotlib/tests/test_simplification.py::test_clipping or, if tests are installed, a dot-separated path to the module, optionally followed by the function separated by two colons, such as:: - py.test --pyargs matplotlib.tests.test_simplification::test_clipping + pytest --pyargs matplotlib.tests.test_simplification::test_clipping If you want to run the full test suite, but want to save wall time try running the tests in parallel:: - py.test --verbose -n 5 + pytest --verbose -n 5 Depending on your version of Python and pytest-xdist, you may need to set ``PYTHONHASHSEED`` to a fixed value when running in parallel:: - PYTHONHASHSEED=0 py.test --verbose -n 5 + PYTHONHASHSEED=0 pytest --verbose -n 5 An alternative implementation that does not look at command line arguments and works from within Python is to run the tests from the Matplotlib library @@ -190,16 +189,14 @@ a feature dependent on that backend. There are two optional keyword arguments to the `image_comparison` decorator: - - `extensions`: If you only wish to test additional image formats - (rather than just `png`), pass any additional file types in the - list of the extensions to test. When copying the new - baseline files be sure to only copy the output files, not their - conversions to ``png``. For example only copy the files - ending in ``pdf``, not in ``_pdf.png``. +- `extensions`: If you only wish to test additional image formats (rather than + just `png`), pass any additional file types in the list of the extensions to + test. When copying the new baseline files be sure to only copy the output + files, not their conversions to ``png``. For example only copy the files + ending in ``pdf``, not in ``_pdf.png``. - - `tol`: This is the image matching tolerance, the default `1e-3`. - If some variation is expected in the image between runs, this - value may be adjusted. +- `tol`: This is the image matching tolerance, the default `1e-3`. If some + variation is expected in the image between runs, this value may be adjusted. Known failing tests ------------------- @@ -264,10 +261,10 @@ Using tox `Tox `_ is a tool for running tests against multiple Python environments, including multiple versions of Python -(e.g., 2.7, 3.5, 3.6) and even different Python implementations +(e.g., 3.5, 3.6) and even different Python implementations altogether (e.g., CPython, PyPy, Jython, etc.) -Testing all versions of Python (2.7, 3.6, 3.7, ...) requires +Testing all versions of Python (3.5, 3.6, ...) requires having multiple versions of Python installed on your system and on the PATH. Depending on your operating system, you may want to use your package manager (such as apt-get, yum or MacPorts) to do this. @@ -284,7 +281,7 @@ You can also run tox on a subset of environments: .. code-block:: bash - $ tox -e py27,py36,py37 + $ tox -e py36,py37 Tox processes everything serially so it can take a long time to test several environments. To speed it up, you might try using a new, diff --git a/doc/faq/howto_faq.rst b/doc/faq/howto_faq.rst index 0c4c27109248..dd48905e27f8 100644 --- a/doc/faq/howto_faq.rst +++ b/doc/faq/howto_faq.rst @@ -136,6 +136,10 @@ Finally, the multipage pdf object has to be closed:: pp.close() +The same can be done using the pgf backend:: + + from matplotlib.backends.backend_pgf import PdfPages + .. _howto-subplots-adjust: @@ -346,7 +350,7 @@ and patches, respectively:: line, = ax.plot(x, y, zorder=10) -.. htmlonly:: +.. only:: html See :doc:`/gallery/misc/zorder_demo` for a complete example. @@ -365,7 +369,7 @@ some ratio which controls the ratio:: ax = fig.add_subplot(111, aspect='equal') -.. htmlonly:: +.. only:: html See :doc:`/gallery/subplots_axes_and_figures/axis_equal_demo` for a complete example. @@ -409,9 +413,10 @@ locators as desired because the two axes are independent. plt.show() -.. htmlonly:: +.. only:: html - See :doc:`/gallery/api/two_scales` for a complete example + See :doc:`/gallery/subplots_axes_and_figures/two_scales` for a + complete example. .. _howto-batch: @@ -657,7 +662,7 @@ For more on configuring your backend, see Alternatively, you can avoid pylab/pyplot altogether, which will give you a little more control, by calling the API directly as shown in -:doc:`/gallery/api/agg_oo_sgskip`. +:doc:`/gallery/user_interfaces/canvasagg`. You can either generate hardcopy on the filesystem by calling savefig:: @@ -740,7 +745,7 @@ Cite Matplotlib If you want to refer to Matplotlib in a publication, you can use "Matplotlib: A 2D Graphics Environment" by J. D. Hunter In Computing in Science & Engineering, Vol. 9, No. 3. (2007), pp. 90-95 (see `this -reference page `_):: +reference page `_):: @article{Hunter:2007, Address = {10662 LOS VAQUEROS CIRCLE, PO BOX 3014, LOS ALAMITOS, CA 90720-1314 USA}, diff --git a/doc/faq/index.rst b/doc/faq/index.rst index 12feae4ed01c..26b171ed0b48 100644 --- a/doc/faq/index.rst +++ b/doc/faq/index.rst @@ -4,7 +4,7 @@ The Matplotlib FAQ ################## -.. htmlonly:: +.. only:: html :Release: |version| :Date: |today| diff --git a/doc/faq/installing_faq.rst b/doc/faq/installing_faq.rst index 67c4689f0ebf..6aa37b8ed0d2 100644 --- a/doc/faq/installing_faq.rst +++ b/doc/faq/installing_faq.rst @@ -74,7 +74,7 @@ of NumPy, Scipy and Matplotlib means that these packages are difficult to upgrade (see `system python packages`_). For that reason we strongly suggest that you install a fresh version of Python and use that as the basis for installing libraries such as NumPy and Matplotlib. One convenient way to -install matplotlib with other useful Python software is to use one of the +install Matplotlib with other useful Python software is to use one of the excellent Python scientific software collections that are now available: .. _system python packages: @@ -108,62 +108,24 @@ or Python.org Python. Installing OSX binary wheels ---------------------------- -If you are using recent Python from https://www.python.org, Macports or -Homebrew, then you can use the standard pip installer to install Matplotlib -binaries in the form of wheels. +If you are using Python from https://www.python.org, Homebrew, or Macports, +then you can use the standard pip installer to install Matplotlib binaries in +the form of wheels. -Python.org Python -^^^^^^^^^^^^^^^^^ - -Install pip following the `standard pip install instructions -`_. For the impatient, -open a new Terminal.app window and:: - - curl -O https://bootstrap.pypa.io/get-pip.py - -Then (Python 2):: - - python get-pip.py - -or (Python 3):: - - python3 get-pip.py - -You can now install matplotlib and all its dependencies with :: - - python -mpip install matplotlib - -or :: - - python3 -mpip install matplotlib - -Macports Python -^^^^^^^^^^^^^^^ - -For Python 2:: - - sudo port install py27-pip - sudo python2 -mpip install matplotlib - -For Python 3:: +pip is installed by default with python.org and Homebrew Python, but needs to +be manually installed on Macports with :: sudo port install py36-pip - sudo python3.6 -mpip install matplotlib - -Homebrew Python -^^^^^^^^^^^^^^^ - -For Python 2:: - python2 -mpip install matplotlib - -For Python 3:: +Once pip is installed, you can install Matplotlib and all its dependencies with +from the Terminal.app command line:: python3 -mpip install matplotlib -You might also want to install IPython or the Jupyter notebook (``pythonX -mpip -install ipython``, ``pythonX -mpip install notebook``, where ``pythonX`` is set -as above). +(``sudo python3.6 ...`` on Macports). + +You might also want to install IPython or the Jupyter notebook (``python3 -mpip +install ipython notebook``). pip problems ^^^^^^^^^^^^ @@ -178,49 +140,35 @@ Checking your installation -------------------------- The new version of Matplotlib should now be on your Python "path". Check this -with one of these commands at the Terminal.app command line:: - - python2 -c 'import matplotlib; print matplotlib.__version__, matplotlib.__file__' - -(Python 2) or:: +at the Terminal.app command line:: python3 -c 'import matplotlib; print(matplotlib.__version__, matplotlib.__file__)' -(Python 3). You should see something like this:: +You should see something like :: - 2.1.0 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/matplotlib/__init__.pyc + 3.0.0 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/matplotlib/__init__.py -where ``2.1.0`` is the Matplotlib version you just installed, and the path +where ``3.0.0`` is the Matplotlib version you just installed, and the path following depends on whether you are using Python.org Python, Homebrew or -Macports. If you see another version, or you get an error like this:: +Macports. If you see another version, or you get an error like :: Traceback (most recent call last): File "", line 1, in ImportError: No module named matplotlib -then check that the Python binary is the one you expected by doing one of -these commands in Terminal.app:: - - which python2 - -or:: +then check that the Python binary is the one you expected by running :: which python3 -If you get the result ``/usr/bin/python2.7``, then you are getting the Python -installed with OSX, which is probably not what you want. Try closing and -restarting Terminal.app before running the check again. If that doesn't fix the -problem, depending on which Python you wanted to use, consider reinstalling +If you get a result like ``/usr/bin/python...``, then you are getting the +Python installed with OSX, which is probably not what you want. Try closing +and restarting Terminal.app before running the check again. If that doesn't fix +the problem, depending on which Python you wanted to use, consider reinstalling Python.org Python, or check your homebrew or macports setup. Remember that the disk image installer only works for Python.org Python, and will not get picked up by other Pythons. If all these fail, please :ref:`let us know `. -Windows Notes -============= - -See :ref:`installing_windows`. - .. _install-from-git: Install from source diff --git a/doc/faq/osx_framework.rst b/doc/faq/osx_framework.rst index 7639eb5429b2..999ec680cccc 100644 --- a/doc/faq/osx_framework.rst +++ b/doc/faq/osx_framework.rst @@ -7,8 +7,6 @@ Working with Matplotlib on OSX .. contents:: :backlinks: none -.. highlight:: bash - .. _osxframework_introduction: Introduction @@ -16,126 +14,38 @@ Introduction On OSX, two different types of Python builds exist: a regular build and a framework build. In order to interact correctly with OSX through the native -GUI frameworks you need a framework build of Python. At the time of writing +GUI frameworks, you need a framework build of Python. At the time of writing the ``macosx`` and ``WXAgg`` backends require a framework build to function -correctly. This can result in issues for a Python installation not build as a -framework and may also happen in virtual envs and when using (Ana)Conda. From +correctly. This can result in issues for a Python installation not build as a +framework and may also happen in virtual envs and when using (Ana)conda. From Matplotlib 1.5 onwards, both backends check that a framework build is available -and fail if a non framework build is found. - -Without this check a partially functional figure is created. -Among the issues with it is that it is produced in the background and -cannot be put in front of any other window. Several solutions and work -arounds exist see below. +and fail if a non framework build is found. (Without this check a partially +functional figure is created. In particular, it is produced in the background +and cannot be put in front of any other window.) -Short version -============= - -VirtualEnv +virtualenv ---------- -If you are on Python 3, use -`venv `_ -instead of `virtualenv `_:: - - python -m venv my-virtualenv - source my-virtualenv/bin/activate +In a virtualenv_, a non-framework build is used even when the environment is +created from a framework build (`virtualenv bug #54`_, `virtualenv bug #609`_). -Otherwise you will need one of the workarounds below. +The solution is to not use virtualenv, but instead the stdlib's venv_, which +provides similar functionality but without exhibiting this issue. -Pyenv ------ - -If you are using pyenv and virtualenv you can enable your python version to be installed as a framework:: +If you absolutely need to use virtualenv rather than venv, then from within +the environment you can set the ``PYTHONHOME`` environment variable to +``$VIRTUAL_ENV``, then invoke Python using the full path to the framework build +(typically ``/usr/local/bin/python``). - PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install x.x.x +.. _virtualenv: https://virtualenv.pypa.io/ +.. _virtualenv bug #54: https://github.com/pypa/virtualenv/issues/54 +.. _virtualenv bug #609: https://github.com/pypa/virtualenv/issues/609 +.. _venv: https://docs.python.org/3/library/venv.html -Conda +conda ----- -The default python provided in (Ana)Conda is not a framework -build. However, the Conda developers have made it easy to install -a framework build in both the main environment and in Conda envs. -To use this install python.app ``conda install python.app`` and -use ``pythonw`` rather than ``python`` - - -Long version -============ - -Unfortunately virtualenv creates a non -framework build even if created from a framework build of Python. -As documented above you can use venv as an alternative on Python 3. - -The issue has been reported on the virtualenv bug tracker `here -`__ and `here -`__ - -Until this is fixed, one of the following workarounds can be used: - -``PYTHONHOME`` Function ------------------------ - -The best known work around is to use the non -virtualenv python along with the PYTHONHOME environment variable. -This can be done by defining a function in your ``.bashrc`` using :: - - function frameworkpython { - if [[ ! -z "$VIRTUAL_ENV" ]]; then - PYTHONHOME=$VIRTUAL_ENV /usr/local/bin/python "$@" - else - /usr/local/bin/python "$@" - fi - } - -This function can then be used in all of your virtualenvs without having to -fix every single one of them. - -With this in place you can run ``frameworkpython`` to get an interactive -framework build within the virtualenv. To run a script you can do -``frameworkpython test.py`` where ``test.py`` is a script that requires a -framework build. To run an interactive ``IPython`` session with the framework -build within the virtual environment you can do ``frameworkpython -m IPython`` - -``PYTHONHOME`` and Jupyter -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This approach can be followed even if using `Jupyter `_ -notebooks: you just need to setup a kernel with the suitable ``PYTHONHOME`` -definition. The `jupyter-virtualenv-osx `_ -script automates the creation of such a kernel. - - -``PYTHONHOME`` Script -^^^^^^^^^^^^^^^^^^^^^ - -An alternative work around borrowed from the `WX wiki -`_, is to use the non -virtualenv python along with the PYTHONHOME environment variable. This can be -implemented in a script as below. To use this modify ``PYVER`` and -``PATHTOPYTHON`` and put the script in the virtualenv bin directory i.e. -``PATHTOVENV/bin/frameworkpython`` :: - - #!/bin/bash - - # what real Python executable to use - PYVER=2.7 - PATHTOPYTHON=/usr/local/bin/ - PYTHON=${PATHTOPYTHON}python${PYVER} - - # find the root of the virtualenv, it should be the parent of the dir this script is in - ENV=`$PYTHON -c "import os; print(os.path.abspath(os.path.join(os.path.dirname(\"$0\"), '..')))"` - - # now run Python with the virtualenv set as Python's HOME - export PYTHONHOME=$ENV - exec $PYTHON "$@" - -With this in place you can run ``frameworkpython`` as above but will need to add this script -to every virtualenv - -PythonW Compiler ----------------- - -In addition -`virtualenv-pythonw-osx `_ -provides an alternative workaround which may be used to solve the issue. +The default python provided in (Ana)conda is not a framework build. However, +a framework build can easily be installed, both in the main environment and +in conda envs: install python.app (``conda install python.app``) and use +``pythonw`` rather than ``python`` diff --git a/doc/faq/virtualenv_faq.rst b/doc/faq/virtualenv_faq.rst index ad21c1e22baa..3406f4f75763 100644 --- a/doc/faq/virtualenv_faq.rst +++ b/doc/faq/virtualenv_faq.rst @@ -4,13 +4,11 @@ Working with Matplotlib in Virtual environments *********************************************** -When running Matplotlib in a `virtual environment -`_ you may discover a few issues. -Matplotlib itself has no issue with virtual environments. However, some of -the external GUI frameworks that Matplotlib uses for interactive figures may -be tricky to install in a virtual environment. Everything below assumes some -familiarity with the Matplotlib backends as found in :ref:`What is a backend? -`. +While Matplotlib itself runs fine in a `virtual environment +`_ (venv), some of the GUI +frameworks that Matplotlib uses for interactive figures are tricky to install +in a venv. Everything below assumes some familiarity with the Matplotlib +backends as found in :ref:`What is a backend? `. If you only use the IPython and Jupyter Notebook's ``inline`` and ``notebook`` backends, or non-interactive backends, you should not have any issues and can @@ -23,43 +21,44 @@ installed. Otherwise, the situation (at the time of writing) is as follows: -============= ========================== ================================= -GUI framework pip-installable? conda or conda-forge-installable? -============= ========================== ================================= -PyQt5 on Python>=3.5 yes -------------- -------------------------- --------------------------------- -PyQt4 PySide: on Windows and OSX yes -------------- -------------------------- --------------------------------- -PyGObject no on Linux -------------- -------------------------- --------------------------------- -PyGTK no no -------------- -------------------------- --------------------------------- -wxPython yes [#]_ yes -============= ========================== ================================= +========= ========= ================ ================================= +framework bindings pip-installable? conda or conda-forge-installable? +========= ========= ================ ================================= +Qt5 PyQt5 yes yes +Qt5 PySide2 yes yes +Qt4 PyQt4 no yes +Qt4 PySide OSX and Windows yes +GTK3 PyGObject yes [#]_ Linux and OSX +wxWidgets wxPython yes [#]_ yes +========= ========= ================ ================================= + +.. [#] No wheels available, see + https://pygobject.readthedocs.io/en/latest/devguide/dev_environ.html + for build instructions. .. [#] OSX and Windows wheels available on PyPI. Linux wheels available but not on PyPI, see https://wxpython.org/pages/downloads/. -In other cases, you need to install the package in the global (system) -site-packages, and somehow make it available from within the virtual -environment. This can be achieved by any of the following methods (in all -cases, the system-wide Python and the virtualenv Python must be of the same +For cases where the framework is not installable in a venv, it needs to be +installed in the global (system) site-packages, and then made available from +within the venv. This can be achieved by either of the following methods (in +all cases, the system-wide Python and the venv Python must be of the same version): -- Using ``virtualenv``\'s ``--system-site-packages`` option when creating - an environment adds all system-wide packages to the virtual environment. - However, this breaks the isolation between the virtual environment and the - system install. Among other issues it results in hard to debug problems - with system packages shadowing the environment packages. If you use - `virtualenvwrapper `_, this can be - toggled with the ``toggleglobalsitepackages`` command. - - `vext `_ allows controlled access - from within the virtualenv to specific system-wide packages without the - overall shadowing issue. A specific package needs to be installed for each - framework, e.g. `vext.pyqt5 `_, etc. - It is recommended to use ``vext>=0.7.0`` as earlier versions misconfigure the - logging system. + from within the venv to specific system-wide packages. A specific + package needs to be installed for each framework, e.g. `vext.pyqt5 + `_, etc. It is recommended to use + ``vext>=0.7.0`` as earlier versions misconfigure the logging system. + +- Using the ``--system-site-packages`` option when creating an environment + adds all system-wide packages to the virtual environment. However, this + breaks the isolation between the virtual environment and the system + install. Among other issues it results in hard to debug problems with + system packages shadowing the environment packages. If you use `virtualenv + ` (rather than the stdlib's ``venv``) together + with `virtualenvwrapper `_, this + can be toggled with the ``toggleglobalsitepackages`` command. If you are using Matplotlib on OSX, you may also want to consider the :ref:`OSX framework FAQ `. diff --git a/doc/glossary/index.rst b/doc/glossary/index.rst index 487caed10f4a..b951faef48e1 100644 --- a/doc/glossary/index.rst +++ b/doc/glossary/index.rst @@ -74,16 +74,9 @@ Glossary features of PyGObject. However Matplotlib does not use any of these missing features. - pygtk - `pygtk `_ provides python wrappers for - the :term:`GTK` widgets library for use with the GTK or GTKAgg - backend. Widely used on linux, and is often packages as - 'python-gtk2' - PyGObject - Like :term:`pygtk`, `PyGObject ` provides - python wrappers for the :term:`GTK` widgets library; unlike pygtk, - PyGObject wraps GTK3 instead of the now obsolete GTK2. + `PyGObject `_ provides Python wrappers for the + :term:`GTK` widgets library pyqt `pyqt `_ provides python @@ -97,12 +90,6 @@ Glossary language widely used for scripting, application development, web application servers, scientific computing and more. - pytz - `pytz `_ provides the Olson tz - database in Python. it allows accurate and cross platform - timezone calculations and solves the issue of ambiguous times at - the end of daylight savings - Qt `Qt `__ is a cross-platform application framework for desktop and embedded development. diff --git a/doc/matplotlibrc b/doc/matplotlibrc index 5c9685987e24..5c623fc6862f 100644 --- a/doc/matplotlibrc +++ b/doc/matplotlibrc @@ -1,16 +1,4 @@ -backend : Agg - -figure.figsize : 5.5, 4.5 # figure size in inches -savefig.dpi : 80 # figure dots per inch -docstring.hardcopy : True # set this when you want to generate hardcopy docstring - -# these parameters are useful for packagers who want to build the docs -# w/o invoking file downloads for the sampledata (see -# matplotlib.cbook.get_sample_data. Unpack -# mpl_sampledata-VERSION.tar.gz and point examples.directory to it. -# You can use a relative path for examples.directory and it must be -# relative to this matplotlibrc file - -#examples.download : False # False to bypass downloading mechanism -#examples.directory : /your/path/to/sample_data/ # directory to look in if download is false - +backend : Agg +figure.figsize : 5.5, 4.5 # figure size in inches +savefig.dpi : 80 # figure dots per inch +docstring.hardcopy : True # set this when you want to generate hardcopy docstring diff --git a/doc/sphinxext/custom_roles.py b/doc/sphinxext/custom_roles.py index 67492c0d5ac7..2232f1032424 100644 --- a/doc/sphinxext/custom_roles.py +++ b/doc/sphinxext/custom_roles.py @@ -1,9 +1,19 @@ from docutils import nodes +from os.path import sep def rcparam_role(name, rawtext, text, lineno, inliner, options={}, content=[]): rendered = nodes.Text('rcParams["{}"]'.format(text)) - return [nodes.literal(rawtext, rendered)], [] + + source = inliner.document.attributes['source'].replace(sep, '/') + rel_source = source.split('/doc/', 1)[1] + + levels = rel_source.count('/') + refuri = ('../' * levels + + 'tutorials/introductory/customizing.html#matplotlib-rcparams') + + ref = nodes.reference(rawtext, rendered, refuri=refuri) + return [nodes.literal('', '', ref)], [] def setup(app): diff --git a/doc/sphinxext/gallery_order.py b/doc/sphinxext/gallery_order.py new file mode 100644 index 000000000000..9229ea7ef97e --- /dev/null +++ b/doc/sphinxext/gallery_order.py @@ -0,0 +1,85 @@ +""" +Configuration for the order of gallery sections and examples. +Paths are relative to the conf.py file. + +""" + +from sphinx_gallery.sorting import ExplicitOrder + +# Gallery sections shall be diplayed in the following order. +# Non-matching sections are appended. +explicit_order_folders = [ + '../examples/lines_bars_and_markers', + '../examples/images_contours_and_fields', + '../examples/subplots_axes_and_figures', + '../examples/statistics', + '../examples/pie_and_polar_charts', + '../examples/text_labels_and_annotations', + '../examples/pyplots', + '../examples/color', + '../examples/shapes_and_collections', + '../examples/style_sheets', + '../examples/axes_grid1', + '../examples/axisartist', + '../examples/showcase', + '../tutorials/introductory', + '../tutorials/intermediate', + '../tutorials/advanced'] + + +class MplExplicitOrder(ExplicitOrder): + """ for use within the 'subsection_order' key""" + def __call__(self, item): + """Return a string determining the sort order.""" + if item in self.ordered_list: + return "{:04d}".format(self.ordered_list.index(item)) + else: + # ensure not explicitly listed items come last. + return "zzz" + item + + +# Subsection order: +# Subsections are ordered by filename, unless they appear in the following +# lists in which case the list order determines the order within the section. +# Examples/tutorials that do not appear in a list will be appended. + +list_all = [ + # **Tutorials** + # introductory + "usage", "pyplot", "sample_plots", "images", "lifecycle", "customizing", + # intermediate + "artists", "legend_guide", "color_cycle", "gridspec", + "constrainedlayout_guide", "tight_layout_guide", + # advanced + # text + "text_intro", "text_props", + # colors + "colors", + + # **Examples** + # color + "color_demo", + # pies + "pie_features", "pie_demo2", + ] +explicit_subsection_order = [item + ".py" for item in list_all] + + +class MplExplicitSubOrder(object): + """ for use within the 'within_subsection_order' key """ + def __init__(self, src_dir): + self.src_dir = src_dir # src_dir is unused here + self.ordered_list = explicit_subsection_order + + def __call__(self, item): + """Return a string determining the sort order.""" + if item in self.ordered_list: + return "{:04d}".format(self.ordered_list.index(item)) + else: + # ensure not explicitly listed items come last. + return "zzz" + item + + +# Provide the above classes for use in conf.py +sectionorder = MplExplicitOrder(explicit_order_folders) +subsectionorder = MplExplicitSubOrder diff --git a/doc/sphinxext/github.py b/doc/sphinxext/github.py index 8f0ffc0d9782..75c5ce10ae9d 100644 --- a/doc/sphinxext/github.py +++ b/doc/sphinxext/github.py @@ -75,7 +75,6 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] app = inliner.document.settings.env.app - #app.info('issue %r' % text) if 'pull' in name.lower(): category = 'pull' elif 'issue' in name.lower(): @@ -105,7 +104,6 @@ def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param content: The directive content for customization. """ app = inliner.document.settings.env.app - #app.info('user link %r' % text) ref = 'https://www.github.com/' + text node = nodes.reference(rawtext, text, refuri=ref, **options) return [node], [] @@ -126,7 +124,6 @@ def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param content: The directive content for customization. """ app = inliner.document.settings.env.app - #app.info('user link %r' % text) try: base = app.config.github_project_url if not base: @@ -146,7 +143,6 @@ def setup(app): :param app: Sphinx application context. """ - app.info('Initializing GitHub plugin') app.add_role('ghissue', ghissue_role) app.add_role('ghpull', ghissue_role) app.add_role('ghuser', ghuser_role) diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index 26645b3e5665..5e1969228e3f 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -1,4 +1,3 @@ -from __future__ import print_function symbols = [ ["Lower-case Greek", 6, diff --git a/doc/sphinxext/mock_gui_toolkits.py b/doc/sphinxext/mock_gui_toolkits.py index ab2b98676d8e..a7be59a940c5 100644 --- a/doc/sphinxext/mock_gui_toolkits.py +++ b/doc/sphinxext/mock_gui_toolkits.py @@ -1,13 +1,9 @@ import sys - -try: - from unittest.mock import MagicMock -except ImportError: - from mock import MagicMock +from unittest.mock import MagicMock class MyCairoCffi(MagicMock): - version_info = (1, 4, 0) + __name__ = "cairocffi" class MyWX(MagicMock): @@ -20,13 +16,13 @@ class ToolBar(object): class Frame(object): pass - VERSION_STRING = '2.9' + class StatusBar(object): + pass def setup(app): - sys.modules['cairocffi'] = MyCairoCffi() - sys.modules['wx'] = MyWX() - sys.modules['wxversion'] = MagicMock() - - metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} - return metadata + sys.modules.update( + cairocffi=MyCairoCffi(), + wx=MyWX(), + ) + return {'parallel_read_safe': True, 'parallel_write_safe': True} diff --git a/doc/users/credits.rst b/doc/users/credits.rst index 6d737f282955..4d33e9d2ca23 100644 --- a/doc/users/credits.rst +++ b/doc/users/credits.rst @@ -1,3 +1,5 @@ +.. Note: This file is auto-generated using generate_credits.py + .. _credits: ******* @@ -17,138 +19,259 @@ git revision control history of the project: 4over7, Aaron Boushley, +Aashil Patel, +AbdealiJK, Acanthostega, +Adam, Adam Ginsburg, Adam Heck, Adam Ortiz, +Adam Williamson, Adrian Price-Whelan, +Adrien Chardon, Adrien F. Vincent, Ahmet Bakan, +Akshay Nair, +Alan Bernstein, Alan Du, +Alberto, Alejandro Dubrovsky, +Aleksey Bilogur, Alex C. Szatmary, Alex Loew, +Alex Rothberg, +AlexCav, +Alexander Buchkovsky, +Alexander Harnisch, Alexander Taylor, Alexei Colin, +Alexis Bienvenüe, Ali Mehdi, +Ali Uneri, Alistair Muldal, Allan Haldane, +Alvaro Sanchez, Amit Aronovitch, Amy, AmyTeegarden, +AndersonDaniel, +Andras Deak, Andrea Bedini, Andreas Hilboll, +Andreas Mayer, +Andreas Mueller, Andreas Wallner, Andrew Dawson, Andrew Merrill, Andrew Straw, +Andy Mastbaum, Andy Zhu, +Anthony Scopatz, Anton Akhmerov, Antony Lee, Arie, Ariel Hernán Curiale, Arnaud Gardelein, Arpad Horvath, +Arthur Paulino, +Arvind, Aseem Bansal, +Atharva Khare, +BHT, +BTWS, +Bastian Bechtold, Behram Mistree, +Ben, Ben Cohen, Ben Gamari, Ben Keller, Ben Root, +Benedikt Daurer, +Benjamin Berg, +Benjamin Congdon, Benjamin Reedlunn, +Bernhard M. Wiedemann, +Bianca Gibson, Binglin Chang, +Björn Dahlgren, +Blaise Thompson, +Boaz Mohar, Bradley M. Froehle, Brandon Liu, +Brendan Zhang, +Brennan Magee, Brett Cannon, Brett Graham, Brian Mattern, Brian McLaughlin, Bruno Beltran, +Bruno Zohreh, CJ Carey, Cameron Bates, Cameron Davidson-Pilon, +Cameron Fackler, Carissa Brittain, Carl Michal, Carwyn Pelley, Casey Webster, Casper van der Wel, Charles Moad, +Charles Ruan, +Chen Karako, Chris Beaumont, Chris G, +Chris Holdgraf, Christian Brueffer, Christian Stade-Schuldt, Christoph Dann, +Christoph Deil, Christoph Gohlke, Christoph Hoffmann, Cimarron Mittelsteadt, +Cody Scot, +Colin, +Conner R. Phillips, Corey Farwell, +Craig Citro, Craig M, Craig Tenney, +DaCoEx, +Dakota Blair, +Damian, Damon McDougall, Dan Hickstein, +Dana, +Daniel C. Marcu, Daniel Hyams, +Daniel Laidig, Daniel O'Connor, +Danny Hermes, Dara Adib, Darren Dale, +DaveL17, +David A, David Anderson, David Haberthür, David Huard, David Kaplan, +David Kent, David Kua, -David Trémouilles, +David Stansby, +David Trémouilles, Dean Malmgren, +Derek Kim, +Derek Tropf, +Devashish Deshpande, +Diego Mora Cespedes, +Dietmar Schwertberger, +Dietrich Brunn, +Divyam Madaan, Dmitry Lupyan, +Dmitry Shachnev, DonaldSeo, Dora Fraeman, +DoriekeMG, +Dorota Jarecka, +Doug Blank, +Drew J. Sonne, Duncan Macleod, +Dylan Evans, +E. G. Patrick Bos, Edin Salkovic, +Egor Panfilov, Elena Glassman, Elias Pipping, +Elijah Schutz, Elliott Sales de Andrade, +Elvis Stansvik, Emil Mikulic, +Emlyn Price, Eric Dill, Eric Firing, +Eric Larson, Eric Ma, Eric O. LEBIGOT (EOL), +Eric Wieser, Erik Bray, +Erik M. Bray, +Erin Pintozzi, Eugen Beck, Eugene Yurtsev, Evan Davey, Ezra Peisach, +Fabian Kloosterman, +Fabian-Robert Stöter, Fabien Maussion, Fabio Zanini, +FedeMiorelli, Federico Ariza, Felipe, +Felix, +Felix Kohlgrüber, +Felix Yan, Fernando Perez, +Filip Dimitrovski, Filipe Fernandes, +Florencia Noriega, +Florian Le Bourdais, Florian Rhiem, Francesco Montesano, Francis Colas, +Franco Vaccari, +Francoise Provencher, +Frank Yu, François Magimel, +Gabe, +Gabriel Munteanu, +Gauravjeet, Gaute Hope, Gellule Xg, +Geoffrey Spear, Geoffroy Billotey, Gerald Storer, Giovanni, Graham Poulter, Gregory Ashton, Gregory R. Lee, +Grillard, Grégory Lielens, Guillaume Gay, +Guillermo Breto, Gustavo Braganca, +Gustavo Goretkin, +HHest, +Hajoon Choi, +Hakan Kucukdereli, Hans Dembinski, Hans Meine, Hans Moritz Günther, +Harshit Patni, Hassan Kibirige, +Hastings Greer, +Heath Henley, +Heiko Oberdiek, +Helder, +Henning Pohl, +Herbert Kruitbosch, Holger Peters, Hubert Holin, Ian Thomas, +Ida Hjorth, Ignas Anikevicius (gns_ank), Ilia Kurenkov, +Ilya Flyamer, +ImSoErgodic, +ImportanceOfBeingErnest, Ioannis Filippidis, +Isa Hassen, +Isaac Schwabacher, +Isaac Slavitt, Ismo Toijala, +J Alammar, J. Goutin, +Jaap Versteegh, Jack Kelly, +Jacob McDonald, Jae-Joon Lee, Jaime Fernandez, Jake Vanderplas, @@ -156,18 +279,25 @@ James A. Bednar, James Pallister, James R. Evans, JamesMakela, +Jamie Nunez, +Jan Schlüter, Jan Schulz, Jan-Philip Gehrcke, Jan-willem De Bleser, Jarrod Millman, Jascha Ulrich, Jason Grout, +Jason King, Jason Liw Yan Chong, Jason Miller, +Jason Neal, +Jason Zheng, JayP16, Jeff Lutgen, Jeff Whitaker, Jeffrey Bingham, +Jeffrey Hokanson @ Loki, +JelsB, Jens Hedegaard Nielsen, Jeremy Fix, Jeremy O'Donoghue, @@ -178,45 +308,80 @@ Jochen Voss, Jody Klymak, Joe Kington, Joel B. Mohler, +Johannes Wienke, +John Hoffman, John Hunter, +John Vandenberg, +Johnny Gill, +JojoBoulix, Jonathan Waltman, Jorrit Wronski, Josef Heinen, +Joseph Albert, +Joseph Fox-Rabinovitz, Joseph Jon Booker, +Joseph Martinot-Lagarde, José Ricardo, Jouni K. Seppänen, +Juan Nunez-Iglesias, +Julia Sprenger, Julian Mehne, Julian Taylor, +Julian V. Modesto, JulianCienfuegos, Julien Lhermitte, Julien Schueller, Julien Woillez, Julien-Charles Lévesque, +Jun Tan, +Justin Cai, +Jörg Dietrich, +Kacper Kowalik (Xarthisius), +Kanchana Ranasinghe, Kanwar245, Katy Huff, +Keerysanth Sribaskaran, Ken McIvor, +Kenneth Ma, Kevin Chan, Kevin Davies, +Kevin Ji, Kevin Keating, +Kevin Rose, +Kexuan Sun, +Kieran Ramos, Kimmo Palin, +Kjartan Myrdal, +Kjell Le, +Klara Gerlei, Konrad Förstner, Konstantin Tretyakov, Kristen M. Thyng, +Kyle Bridgemohansingh, +Kyler Brown, Lance Hepler, +Laptop11_ASPP2016, Larry Bradley, Leeonadoh, Lennart Fricke, Leo Singer, +Leon Yin, Levi Kilcher, +Liam Brannigan, Lion Krischer, +Lionel Miller, Lodato Luciano, Lori J, Loïc Estève, Loïc Séguin-C, +Luca Verginer, +Luis Pedro Coelho, +Maarten Baert, Magnus Nord, Majid alDosari, Maksym P, Manuel GOACOLOU, +Manuel Jung, Manuel Metz, Marc Abramowitz, Marcos Duarte, @@ -231,100 +396,169 @@ Martin Spacek, Martin Teichmann, Martin Thoma, Martin Ueding, +Massimo Santini, Masud Rahman, Mathieu Duponchelle, Matt Giuca, +Matt Hancock, Matt Klein, Matt Li, Matt Newville, Matt Shen, Matt Terry, +Matthew Bell, Matthew Brett, Matthew Emmett, Matthias Bussonnier, +Matthias Lüthi, Matthieu Caneill, +Matti Picus, Matěj Týč, Maximilian Albert, +Maximilian Maahn, +Maximilian Nöthe, Maximilian Trescher, Mellissa Cross, +Mher Kazandjian, Michael, Michael Droettboom, Michael Sarahan, +Michael Scott Cuthbert, +Michael Seifert, Michael Welter, Michiel de Hoon, Michka Popoff, +Mike Henninger, +Mike Jarvis, Mike Kaufman, Mikhail Korobov, MinRK, Minty Zhang, MirandaXM, Miriam Sierig, +Mitar, +Molly Rossow, +Moritz Boehle, +Mudit Surana, Muhammad Mehdi, +Naoya Kanai, +Nathan Goldbaum, +Nathan Musoke, +Nathaniel M. Beaver, Neil, Neil Crighton, Nelle Varoquaux, Niall Robinson, Nic Eggert, Nicholas Devenish, +Nick Garvey, +Nick Papior, Nick Semenkovich, +Nico Schlömer, Nicolas P. Rougier, Nicolas Pinto, +Nicolas Tessore, +Nik Quibin, Nikita Kniazev, Niklas Koep, Nikolay Vyahhi, +Nils Werner, +Ninad Bhat, Norbert Nemec, +Norman Fomferra, OceanWolf, Oleg Selivanov, Olga Botvinnik, Oliver Willekens, +Olivier, +Orso Meneghini, +Osarumwense, +Pankaj Pandey, Parfenov Sergey, Pascal Bugnion, +Pastafarianist, Patrick Chen, Patrick Marsh, Paul, Paul Barret, -Paul G, +Paul Ganssle, Paul Hobson, Paul Ivanov, +Paul Kirow, +Paul Romano, +Paul Seyfert, Pauli Virtanen, +Pavol Juhas, Per Parker, Perry Greenfield, Pete Bachant, +Pete Huang, +Pete Peterson, Peter Iannucci, +Peter Mortensen, Peter St. John, Peter Würtz, +Petr Danecek, Phil Elson, +Phil Ruffwind, Pierre Haessig, +Pierre de Buyl, Pim Schellart, Piti Ongmongkolkul, +Pranav Garg, +Przemysław Dąbek, Puneeth Chaganti, +Qingpeng "Q.P." Zhang, +RAKOTOARISON Herilalaina, Ramiro Gómez, Randy Olson, Reinier Heeres, Remi Rampin, +Richard Gowers, Richard Hattersley, Richard Trieu, Ricky, +Rishikesh, +Rob Harrigan, Robert Johansson, Robin Dunn, +Robin Neatherway, +Robin Wilson, Rohan Walker, Roland Wirth, +Ronald Hartley-Davies, +Roy Smith, +Rui Lopes, Russell Owen, RutgerK, +Ryan, Ryan Blomberg, Ryan D'Souza, Ryan Dale, Ryan May, +Ryan Morshead, Ryan Nelson, RyanPan, +Saket Choudhary, +Salganos, Salil Vanvari, +Salinder Sidhu, +Sam Vaughan, Sameer D'Costa, +Samson, +Samuel St-Jean, +Sander, Sandro Tosi, +Scott Howard, Scott Lasley, Scott Lawrence, Scott Stevenson, +Sean Farley, Sebastian Pinnau, Sebastian Raschka, +Sebastián Vanrell, +Seraphim Alvanides, +Sergey B Kirpichev, Sergey Kholodilov, Sergey Koposov, Silviu Tantos, @@ -333,136 +567,255 @@ Simon Gibbons, Skelpdar, Skipper Seabold, Slav Basharov, +Sourav Singh, Spencer McIntyre, Stanley, Simon, Stefan Lehmann, +Stefan Pfenninger, Stefan van der Walt, Stefano Rivera, +Stephan Erb, Stephen Horst, Sterling Smith, Steve Chaplin, Steven Silvester, +Steven Tilley, Stuart Mumford, +TD22057, +Tadeo Corradi, +Taehoon Lee, Takafumi Arakaki, Takeshi Kanmae, Tamas Gal, +Tanuj, +Ted Petrou, +Terrence J. Katzenbaer, +Terrence Katzenbaer, +The Gitter Badger, Thomas A Caswell, Thomas Hisch, Thomas Kluyver, Thomas Lake, +Thomas Mansencal, Thomas Robitaille, Thomas Spura, +Thomas VINCENT, +Thorsten Liebig, +Tian Xia, Till Stensitzki, +Tim Hoffmann, Timo Vanwynsberghe, +Tobias Froehlich, Tobias Hoppe, Tobias Megies, Todd Jennings, Todd Miller, +Tom, +Tom Augspurger, +Tom Dupré la Tour, Tomas Kazmar, Tony S Yu, Tor Colvin, Travis Oliphant, Trevor Bekolay, +Trish Gillett-Kawamoto, +Truong Pham, +Tuan Dung Tran, Ulrich Dobramysl, Umair Idris, +V. R, Vadim Markovtsev, Valentin Haenel, -Victor Zabalza, +Valentin Schmidt, +Vedant Nanda, +Vidur Satija, Viktor Kerkez, Vlad Seghete, Víctor Terrón, Víctor Zabalza, +WANG Aiyong, +Warren Weckesser, Wen Li, Wendell Smith, Werner F Bruhin, Wes Campaigne, Wieland Hoffmann, +Will Silva, +William Granados, +William Mallard, William Manley, Wouter Overmeire, Xiaowen Tang, +Xufeng Wang, Yann Tambouret, +Yao-Yuan Mao, Yaron de Leeuw, Yu Feng, Yunfei Yang, Yuri D'Elia, Yuval Langer, +ZWL, Zac Hatfield-Dodds, Zach Pincus, Zair Mubashar, +Zbigniew Jędrzejewski-Szmek, +ahed87, +akrherz, +alcinos, alex, +alvarosg, +aneda, anykraus, +apodemus, arokem, +as691454, aseagram, +ash13, aszilagyi, bblay, +bduick, bev-a-tron, blackw1ng, blah blah, +buefox, burrbull, butterw, cammil, captainwhippet, +cclauss, +ch3rn0v, chadawagner, chebee7i, +chelseatroy, +cknd, +cldssty, +clintval, danielballan, davidovitch, daydreamt, +deeenes, +deepyaman, +dlmccaffrey, domspad, donald, drevicko, e-q, elpres, endolith, +et2010, fardal, ffteja, fgb, fibersnet, +fredrik-1, frenchwr, +fuzzythecat, fvgoto, +gcallah, gitj, gluap, +gnaggnoyil, goir, +goldstarwebs, +greg-roper, +gregorybchris, +hannah, +helmiriawan, hugadams, +ilivni, insertroar, itziakos, +jacob-on-github, jbbrokaw, +jbhopkins, +jerrylui803, +jhelie, +jli, +jonchar, juan.gonzalez, kcrisman, +keithbriggs, kelsiegr, khyox, kikocorreoso, +klaus, +klonuo, kramer65, kshramt, +lboogaard, +legitz7, lichri12, limtaesu, +lspvic, +luz.paz, +lzkelley, +mamrehn, marky, masamson, mbyt, mcelrath, +mcquin, mdipierro, -mrkrd, +mitch, +mlub, +mobando, +muahah, +myyc, +navdeep rana, +nbrunett, +nemanja, +neok-m4700, +nepix32, nickystringer, +nmartensen, nwin, +ob, +pdubcali, pkienzle, +productivememberofsociety666, profholzer, pupssman, rahiel, +rebot, rhoef, rsnape, +ruin, +rvhbooth, +s0vereign, s9w, +scls19fr, +scott-vsi, sdementen, +serv-inc, +settheory, sfroid, +shaunwbell, +simonpf, +sindunuragarp, sohero, spiessbuerger, stahlous, +stone, +stonebig, switham, syngron, +thuvejan, +tmdavison, +tomoemon, +tonyyli, torfbolt, u55, ugurthemaster, +ultra-andy, +vab9, vbr, +vraelvrangr, +watkinrt, xbtsw, -and xuanyuansen. +xuanyuansen, +zhangeugenia, +zhoubecky, +Élie Gouzien Some earlier contributors not included above are (with apologies to any we have missed): diff --git a/doc/users/dflt_style_changes.rst b/doc/users/dflt_style_changes.rst index 22624683892b..a2a687ae7620 100644 --- a/doc/users/dflt_style_changes.rst +++ b/doc/users/dflt_style_changes.rst @@ -275,12 +275,12 @@ Plotting functions The following changes were made to the default behavior of `~matplotlib.axes.Axes.scatter` - - The default size of the elements in a scatter plot is now based on - the rcParam ``lines.markersize`` so it is consistent with ``plot(X, - Y, 'o')``. The old value was 20, and the new value is 36 (6^2). - - scatter markers no longer have a black edge. - - if the color of the markers is not specified it will follow the - property cycle, pulling from the 'patches' cycle on the ``Axes``. +- The default size of the elements in a scatter plot is now based on + the rcParam ``lines.markersize`` so it is consistent with ``plot(X, + Y, 'o')``. The old value was 20, and the new value is 36 (6^2). +- Scatter markers no longer have a black edge. +- If the color of the markers is not specified it will follow the + property cycle, pulling from the 'patches' cycle on the ``Axes``. .. plot:: @@ -323,10 +323,10 @@ a per-call basis pass the following kwargs:: The following changes were made to the default behavior of `~matplotlib.axes.Axes.plot` - - the default linewidth increased from 1 to 1.5 - - the dash patterns associated with ``'--'``, ``':'``, and ``'-.'`` have - changed - - the dash patterns now scale with line width +- the default linewidth increased from 1 to 1.5 +- the dash patterns associated with ``'--'``, ``':'``, and ``'-.'`` have + changed +- the dash patterns now scale with line width .. plot:: @@ -642,18 +642,18 @@ Hatching The color of the lines in the hatch is now determined by - - If an edge color is explicitly set, use that for the hatch color - - If the edge color is not explicitly set, use ``rcParam['hatch.color']`` which - is looked up at artist creation time. +- If an edge color is explicitly set, use that for the hatch color +- If the edge color is not explicitly set, use ``rcParam['hatch.color']`` which + is looked up at artist creation time. The width of the lines in a hatch pattern is now configurable by the rcParams `hatch.linewidth`, which defaults to 1 point. The old behavior for the line width was different depending on backend: - - PDF: 0.1 pt - - SVG: 1.0 pt - - PS: 1 px - - Agg: 1 px +- PDF: 0.1 pt +- SVG: 1.0 pt +- PS: 1 px +- Agg: 1 px The old line width behavior can not be restored across all backends simultaneously, but can be restored for a single backend by setting:: diff --git a/doc/users/event_handling.rst b/doc/users/event_handling.rst index 0b4fdddb7e97..58154411a04c 100644 --- a/doc/users/event_handling.rst +++ b/doc/users/event_handling.rst @@ -4,7 +4,7 @@ Event handling and picking ************************** -matplotlib works with a number of user interface toolkits (wxpython, +Matplotlib works with a number of user interface toolkits (wxpython, tkinter, qt4, gtk, and macosx) and in order to support features like interactive panning and zooming of figures, it is helpful to the developers to have an API for interacting with the figure via key @@ -47,14 +47,16 @@ disconnect the callback, just call:: fig.canvas.mpl_disconnect(cid) .. note:: - The canvas retains only weak references to the callbacks. Therefore - if a callback is a method of a class instance, you need to retain - a reference to that instance. Otherwise the instance will be - garbage-collected and the callback will vanish. + The canvas retains only weak references to instance methods used as + callbacks. Therefore, you need to retain a reference to instances owning + such methods. Otherwise the instance will be garbage-collected and the + callback will vanish. + + This does not affect free functions used as callbacks. Here are the events that you can connect to, the class instances that -are sent back to you when the event occurs, and the event descriptions +are sent back to you when the event occurs, and the event descriptions: ======================= ============================================================================================= diff --git a/doc/users/generate_credits.py b/doc/users/generate_credits.py new file mode 100755 index 000000000000..a81d9c5cb8f7 --- /dev/null +++ b/doc/users/generate_credits.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# +# This script generates credits.rst with an up-to-date list of contributors +# to the matplotlib github repository. + +from collections import Counter +import locale +import re +import subprocess + +TEMPLATE = """.. Note: This file is auto-generated using generate_credits.py + +.. _credits: + +******* +Credits +******* + + +Matplotlib was written by John D. Hunter, with contributions from +an ever-increasing number of users and developers. +The current co-lead developers are Michael Droettboom +and Thomas A. Caswell; they are assisted by many +`active +`_ developers. + +The following is a list of contributors extracted from the +git revision control history of the project: + +{contributors} + +Some earlier contributors not included above are (with apologies +to any we have missed): + +Charles Twardy, +Gary Ruben, +John Gill, +David Moore, +Paul Barrett, +Jared Wahlstrand, +Jim Benson, +Paul Mcguire, +Andrew Dalke, +Nadia Dencheva, +Baptiste Carvello, +Sigve Tjoraand, +Ted Drain, +James Amundson, +Daishi Harada, +Nicolas Young, +Paul Kienzle, +John Porter, +and Jonathon Taylor. + +We also thank all who have reported bugs, commented on +proposed changes, or otherwise contributed to Matplotlib's +development and usefulness. +""" + +def check_duplicates(): + text = subprocess.check_output(['git', 'shortlog', '--summary', '--email']) + lines = text.decode('utf8').split('\n') + contributors = [line.split('\t', 1)[1].strip() for line in lines if line] + emails = [re.match('.*<(.*)>', line).group(1) for line in contributors] + email_counter = Counter(emails) + + if email_counter.most_common(1)[0][1] > 1: + print('DUPLICATE CHECK: The following email addesses are used with more ' + 'than one name.\nConsider adding them to .mailmap.\n') + for email, count in email_counter.items(): + if count > 1: + print('%s\n%s' % (email, '\n'.join(l for l in lines if email in l))) + + +def generate_credits(): + text = subprocess.check_output(['git', 'shortlog', '--summary']) + lines = text.decode('utf8').split('\n') + contributors = [line.split('\t', 1)[1].strip() for line in lines if line] + contributors.sort(key=locale.strxfrm) + with open('credits.rst', 'w') as f: + f.write(TEMPLATE.format(contributors=',\n'.join(contributors))) + + +if __name__ == '__main__': + check_duplicates() + generate_credits() diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 04ef8df2d36b..45488c6168b8 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -3,176 +3,841 @@ GitHub Stats ============ -GitHub stats for 2018/03/05 - 2018/08/09 (tag: v2.2.0) +GitHub stats for 2018/03/05 - 2018/09/17 (tag: v2.2.0) These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 27 issues and merged 101 pull requests. -The full list can be seen `on GitHub `__ +We closed 110 issues and merged 580 pull requests. +The full list can be seen `on GitHub `__ -The following 28 authors contributed 347 commits. +The following 131 authors contributed 2794 commits. * 816-8055 +* Adrien F. Vincent +* akrherz +* Akshay Nair +* Alexander Harnisch +* AlexCav +* Allen Downey * Andreas Gustafsson * Andrew Nelson * Antony Lee +* aparamon +* Avinash Sharma +* Bastian Bechtold +* Ben * Ben Root +* Boaz Mohar +* Brendan Zhang +* cclauss +* ch3rn0v * Charles Ruan +* Chris Holdgraf +* Christoph Gohlke +* cldssty +* Cody Scot * dahlbaek +* daronjp +* David Brooks * David Stansby -* DietmarSchwertberger +* Derek Tropf +* Dietmar Schwertberger +* Elijah Schutz * Elizabeth Seiver * Elliott Sales de Andrade * Eric Firing +* Eric Galloway +* Eric Wang (Mac) +* Eric Wieser +* Ethan Ligon * Federico Ariza +* Franco Vaccari * fredrik-1 +* Gauravjeet +* Graeme Smecher +* gregorybchris +* Hajoon Choi * hannah -* Importance of Being Ernest +* Harnesser +* Harshal Prakash Patankar +* Helder +* HHest +* Hubert Holin +* Ida Hjorth +* Ildar Akhmetgaleev * ImportanceOfBeingErnest +* Jae-Joon Lee +* Jan Koehler +* Jason Neal +* jdollichon +* JelsB * Jens Hedegaard Nielsen +* Jerry Lui +* jerrylui803 * Jody Klymak * joelostblom +* Johnny Gill +* Joseph Fox-Rabinovitz +* Joseph Martinot-Lagarde * Jouni K. Seppänen +* Katrin Leinweber +* Kevin Davies +* Kieran Ramos +* Kjell Le +* Kyle Sunden +* lboogaard +* Leo Singer +* Lionel Miller +* luz.paz +* Manish Devgan +* Martin Spacek +* Matthew Bell +* Matti Picus +* Maximilian Maahn +* Maximilian Nöthe * Michael Droettboom +* Mitar +* Nathan Goldbaum +* Nathaniel M. Beaver * Nelle Varoquaux +* Nick Forrington +* Nick Garvey +* Nik Quibin +* ob +* Olivier +* Osarumwense +* Paramonov Andrey +* Pastafarianist +* Paul Ganssle * Paul Hobson * Paul Ivanov +* Paul Seyfert +* pdubcali +* Peter Mackenzie-Helnwein +* Peter Würtz +* pharshalp +* Pranav Garg * Ryan May +* Salinder Sidhu +* Sam Vaughan +* Sourav Singh +* Steven Tilley +* stone +* stonebig +* Taehoon Lee +* Tanuj +* Taras +* Taras Kuzyo +* TD22057 +* terranjp * Thomas A Caswell +* Thomas Levine +* Thomas Spura +* thuvejan * Tim Hoffmann +* Trish Gillett-Kawamoto +* WANG Aiyong +* y1thof +* Zac Hatfield-Dodds +* zhangeugenia +* ZhaoZhonglun1991 +* zhoubecky +* Андрей Парамонов GitHub issues and pull requests: -Pull Requests (101): +Pull Requests (580): -* :ghpull:`11835`: Backport PR #11831 on branch v2.2.x -* :ghpull:`11831`: animation api documentation changes -* :ghpull:`11834`: Backport PR #11824 on branch v2.2.x -* :ghpull:`11824`: facecolor stale missing -* :ghpull:`11827`: FIX: correct macro logic in file_compat.h -* :ghpull:`11817`: Merge pull request #11533 from tacaswell/update_for_py37 -* :ghpull:`11533`: MNT: updates for python 3.7 -* :ghpull:`11813`: Backport axes grid work -* :ghpull:`11808`: updates error message to run pytest instead of tests.py standalone -* :ghpull:`11812`: Final backports -* :ghpull:`11811`: Backport: resolve conflict to get this into v2.2.3 -* :ghpull:`9993`: Rewrite and greatly simplify qt_compat.py. -* :ghpull:`11786`: Fix collections import warnings for Python 3.7 -* :ghpull:`11733`: MAINT: use collections.abc for 3.7 -* :ghpull:`11697`: Backport PR #11694 on branch v2.2.x -* :ghpull:`11694`: moving toolmanager initialization up before toolbar -* :ghpull:`11671`: refresh the github stats -* :ghpull:`11669`: Fix invalid escape sequences on branch v2.2.x -* :ghpull:`11638`: Backport PR #11636 on branch v2.2.x -* :ghpull:`11634`: Backport #11624 -* :ghpull:`11636`: Don't flush file object opened for reading -* :ghpull:`11624`: Don't use Popen(..., shell=True). -* :ghpull:`11599`: Backport PR #11559 on branch v2.2.x -* :ghpull:`11598`: Backport PR #10915 on branch v2.2.x -* :ghpull:`11588`: DOC: warn if user is using constrained layout and use subplots_adjust -* :ghpull:`11580`: Use plt.subplots() -* :ghpull:`11574`: Backport PR #11565 on branch v2.2.x -* :ghpull:`11565`: Update docstring of pyplot.matshow() -* :ghpull:`11559`: FIX: Toolbar disappears in TkAgg if window if resized -* :ghpull:`11567`: Backport PR #11500 on branch v2.2.x -* :ghpull:`11500`: Fix shims with PyQt5 5.11 -* :ghpull:`11060`: Fix inset_axes + doc -* :ghpull:`11535`: Backport PR #11517 on branch v2.2.x -* :ghpull:`11517`: Update legend loc default value in docstring -* :ghpull:`11489`: Backport PR #11485 on branch v2.2.x -* :ghpull:`11485`: Use null character instead of recasting NULL to char -* :ghpull:`11419`: Backport PR #11409: plt.box_bug_fix -* :ghpull:`11379`: Backport PR #11363 on branch v2.2.x -* :ghpull:`11409`: plt.box_bug_fix -* :ghpull:`11407`: Properly position markers in step plots. -* :ghpull:`11330`: FIX: Don't let constrained_layout counter overflow -* :ghpull:`11363`: Improve docstring of Axes.pcolorfast -* :ghpull:`11354`: Backport PR #11344 on branch v2.2.x -* :ghpull:`11344`: Improve docstring of Axes.pcolormesh -* :ghpull:`11336`: Use raw string literals for docstrings with escapes -* :ghpull:`11334`: Backport pull request #10858 -* :ghpull:`11333`: Backport #10806 to v2.2.x -* :ghpull:`11312`: Replace :ref:``sphx_glr_...`` by :doc:``/...``. -* :ghpull:`11327`: Backport PR #11317 on branch v2.2.x -* :ghpull:`11317`: Improve docstring of Axes.pcolor -* :ghpull:`11262`: Use dummy_threading if threading not available -* :ghpull:`11260`: Backport PR #11189 on branch v2.2.x -* :ghpull:`11047`: FIX: image respect norm limits w/ None -* :ghpull:`11173`: Define ``__repr__``, not ``__str__`` for transforms. -* :ghpull:`11189`: FIX: remove recursive call to str in transform repr -* :ghpull:`11184`: Manual backport #11035 -* :ghpull:`11035`: FIX: constrained_layout and repeated calls to suptitle -* :ghpull:`11171`: FIX: str method for SymmetricalLogTransform -* :ghpull:`11162`: Backport PR #11161 on branch v2.2.x -* :ghpull:`11161`: DOC: small-doc-improvements1 -* :ghpull:`11079`: Resurrecting axes_grid1 documentation -* :ghpull:`11105`: When drawing markers, don't set the GraphicsContext alpha. -* :ghpull:`10881`: FIX: make un-used ticks not be visible -* :ghpull:`10968`: Improve docstring of contour -* :ghpull:`11120`: Backport PR #10981 on branch v2.2.x -* :ghpull:`10981`: Document change of label visibility on shared axes -* :ghpull:`11114`: Backport PR #10792 on branch v2.2.x -* :ghpull:`10792`: Fixed not being able to set vertical/horizontal alignments in polar graphs -* :ghpull:`11111`: Pin sphinx!=1.7.3 on v2.2.x -* :ghpull:`11107`: Pin sphinx to 1.7.2 to unbreak the doc build. -* :ghpull:`11090`: Clean docstring of CountourSet -* :ghpull:`11084`: Backport PR #10899: Update cycler docstrings and favor kwarg over two… -* :ghpull:`10899`: Update cycler docstrings and favor kwarg over two-args form -* :ghpull:`11071`: Add note about hist2d resetting axis limits -* :ghpull:`11067`: Remove unnecessary shebang. -* :ghpull:`11054`: Backport PR #11052 on branch v2.2.x -* :ghpull:`11052`: Fix CircleCI build. -* :ghpull:`11023`: Backport PR #11022 on branch v2.2.x -* :ghpull:`11022`: MNT: remove distutils.sysconfig import from toplevel module -* :ghpull:`11011`: Backport PR #11002 on branch v2.2.x -* :ghpull:`11002`: Fix logic error in ScalarMappable.to_rgba -* :ghpull:`10952`: Backport PR #10951 on branch v2.2.x -* :ghpull:`10979`: Backport PR #10978 on branch v2.2.x -* :ghpull:`10978`: Remove to-be-deprecated NumPy imports. -* :ghpull:`10951`: fix wx rubberband: correctly ensure x0<=x1 -* :ghpull:`10937`: Backport PR #10935 on branch v2.2.x -* :ghpull:`10935`: FIX: Postscript allow empty markers -* :ghpull:`10858`: FIX: ioerror font cache, second try -* :ghpull:`10929`: Fix rctemplate tests when user config file exists. -* :ghpull:`10927`: Backport PR #10919 on branch v2.2.x -* :ghpull:`10919`: Fix overflow when resizing path-to-string buffer. -* :ghpull:`10877`: Fix invalid escape sequence in docstring. -* :ghpull:`10868`: Backport PR #10867 on branch v2.2.x -* :ghpull:`10867`: MNT: move bz2 back to function level -* :ghpull:`10860`: Backport PR #10856 on branch v2.2.x -* :ghpull:`10856`: Fix xkcd style garbage collection. -* :ghpull:`10853`: Backport PR #10662 on branch v2.2.x -* :ghpull:`10662`: Update docs on Axes.set_prop_cycle -* :ghpull:`10833`: Propagate marker antialias setting to GraphicsContext. -* :ghpull:`10806`: MNT: catch more illegal '\' -* :ghpull:`10595`: Improve Figure docstrings +* :ghpull:`12145`: Doc final 3.0 docs +* :ghpull:`12143`: Backport PR #12142 on branch v3.0.x (Unbreak formlayout for image edits.) +* :ghpull:`12142`: Unbreak formlayout for image edits. +* :ghpull:`12135`: Backport PR #12131 on branch v3.0.x (Fixes currently release version of cartopy) +* :ghpull:`12131`: Fixes currently release version of cartopy +* :ghpull:`12129`: Backports for 3.0 +* :ghpull:`12132`: Backport PR #12130 on branch v3.0.x (Mention colorbar.minorticks_on/off in references) +* :ghpull:`12130`: Mention colorbar.minorticks_on/off in references +* :ghpull:`12099`: FIX: make sure all ticks show up for colorbar minor tick +* :ghpull:`11962`: Propagate changes to backend loading to setup/setupext. +* :ghpull:`12128`: Unbreak the Sphinx 1.8 build by renaming :math: to :mathmpl:. +* :ghpull:`12126`: Backport PR #12117 on branch v3.0.x (Fix Agg extent calculations for empty draws) +* :ghpull:`12113`: Backport PR #12112 on branch v3.0.x (Reword the LockDraw docstring.) +* :ghpull:`12112`: Reword the LockDraw docstring. +* :ghpull:`12110`: Backport PR #12109 on branch v3.0.x (Pin to sphinx<1.8; unremove sphinxext.mathmpl.) +* :ghpull:`12109`: Pin to sphinx<1.8; unremove sphinxext.mathmpl. +* :ghpull:`12084`: DOC: link palettable +* :ghpull:`12096`: Backport PR #12092 on branch v3.0.x (Update backend_qt5agg to fix PySide2 mem issues) +* :ghpull:`12083`: Backport PR #12012 on branch v3.0.x (FIX: fallback text renderer to fig._cachedRenderer, if none found) +* :ghpull:`12081`: Backport PR #12037 on branch v3.0.x (Fix ArtistInspector.get_aliases.) +* :ghpull:`12080`: Backport PR #12053 on branch v3.0.x (Fix up some OSX backend issues) +* :ghpull:`12037`: Fix ArtistInspector.get_aliases. +* :ghpull:`12053`: Fix up some OSX backend issues +* :ghpull:`12064`: Backport PR #11971 on branch v3.0.x (FIX: use cached renderer on Legend.get_window_extent) +* :ghpull:`12063`: Backport PR #12036 on branch v3.0.x (Interactive tests update) +* :ghpull:`11928`: Update doc/conf.py to avoid warnings with (future) sphinx 1.8. +* :ghpull:`12048`: Backport PR #12047 on branch v3.0.x (Remove asserting about current backend at the end of mpl_test_settings.) +* :ghpull:`11971`: FIX: use cached renderer on Legend.get_window_extent +* :ghpull:`12036`: Interactive tests update +* :ghpull:`12029`: Backport PR #12022 on branch v3.0.x (Remove intent to deprecate rcParams["backend_fallback"].) +* :ghpull:`12047`: Remove asserting about current backend at the end of mpl_test_settings. +* :ghpull:`12020`: Backport PR #12019 on branch v3.0.x (typo: s/unmultipled/unmultiplied) +* :ghpull:`12022`: Remove intent to deprecate rcParams["backend_fallback"]. +* :ghpull:`12028`: Backport PR #12023 on branch v3.0.x (Fix deprecation check in wx Timer.) +* :ghpull:`12023`: Fix deprecation check in wx Timer. +* :ghpull:`12019`: typo: s/unmultipled/unmultiplied +* :ghpull:`12017`: Backport PR #12016 on branch v3.0.x (Fix AttributeError in GTK3Agg backend) +* :ghpull:`12016`: Fix AttributeError in GTK3Agg backend +* :ghpull:`11991`: Backport PR #11988 on branch v3.0.x +* :ghpull:`11978`: Backport PR #11973 on branch v3.0.x +* :ghpull:`11968`: Backport PR #11963 on branch v3.0.x +* :ghpull:`11967`: Backport PR #11961 on branch v3.0.x +* :ghpull:`11969`: Fix an invalid escape sequence. +* :ghpull:`11963`: Fix some lgtm convention alerts +* :ghpull:`11961`: Downgrade backend_version log to DEBUG level. +* :ghpull:`11953`: Backport PR #11896 on branch v3.0.x +* :ghpull:`11896`: Resolve backend in rcParams.__getitem__("backend"). +* :ghpull:`11950`: Backport PR #11934 on branch v3.0.x +* :ghpull:`11952`: Backport PR #11949 on branch v3.0.x +* :ghpull:`11949`: Remove test2.png from examples. +* :ghpull:`11934`: Suppress the "non-GUI backend" warning from the .. plot:: directive... +* :ghpull:`11918`: Backport PR #11917 on branch v3.0.x +* :ghpull:`11917`: BUG: make arg 'N' and kwarg 'levels' behave the same when scalar +* :ghpull:`11916`: Backport PR #11897 on branch v3.0.x +* :ghpull:`11915`: Backport PR #11591 on branch v3.0.x +* :ghpull:`11897`: HTMLWriter, put initialisation of frames in setup +* :ghpull:`11591`: BUG: correct the scaling in the floating-point slop test. +* :ghpull:`11910`: Backport PR #11907 on branch v3.0.x +* :ghpull:`11907`: Move TOC back to top in axes documentation +* :ghpull:`11904`: Backport PR #11900 on branch v3.0.x +* :ghpull:`11889`: Backport PR #11847 on branch v3.0.x +* :ghpull:`11890`: Backport PR #11850 on branch v3.0.x +* :ghpull:`11850`: FIX: macosx framework check +* :ghpull:`11883`: Backport PR #11862 on branch v3.0.x +* :ghpull:`11847`: Use ImageMagick's magick.exe if convert.exe is not installed +* :ghpull:`11882`: Backport PR #11876 on branch v3.0.x +* :ghpull:`11876`: MAINT Better error message for number of colors versus number of data… +* :ghpull:`11862`: Fix NumPy FutureWarning for non-tuple indexing. +* :ghpull:`11845`: Use Format_ARGB32_Premultiplied instead of RGBA8888 for Qt backends. +* :ghpull:`11843`: Remove unnecessary use of nose. +* :ghpull:`11600`: backend switching -- don't create a public fallback API +* :ghpull:`11833`: adding show inheritance to autosummary template +* :ghpull:`11828`: changed warning in animation +* :ghpull:`11829`: func animation warning changes +* :ghpull:`11826`: DOC documented more of the gridspec options +* :ghpull:`11818`: Merge v2.2.x +* :ghpull:`11821`: DOC: remove multicolumns from examples +* :ghpull:`11819`: DOC: fix minor typo in figure example +* :ghpull:`11722`: Remove unnecessary hacks from setup.py. +* :ghpull:`11802`: gridspec tutorial edits +* :ghpull:`11801`: update annotations +* :ghpull:`11734`: Small cleanups to backend_agg. +* :ghpull:`11785`: Add missing API changes +* :ghpull:`11788`: Fix DeprecationWarning on LocatableAxes +* :ghpull:`11558`: Added xkcd Style for Markers (plot only) +* :ghpull:`11755`: Add description for metadata argument of savefig +* :ghpull:`11703`: FIX: make update-from also set the original face/edgecolor +* :ghpull:`11765`: DOC: reorder examples and fix top level heading +* :ghpull:`11724`: Fix cairo's image inversion and alpha misapplication. +* :ghpull:`11726`: Consolidate agg-buffer examples. +* :ghpull:`11754`: FIX: update spine positions before get extents +* :ghpull:`11779`: Remove unused attribute in tests. +* :ghpull:`11770`: Correct errors in documentation +* :ghpull:`11778`: Unpin pandas in the CI. +* :ghpull:`11772`: Clarifying an error message +* :ghpull:`11760`: Switch grid documentation to numpydoc style +* :ghpull:`11705`: Suppress/fix some test warnings. +* :ghpull:`11763`: Pin OSX CI to numpy<1.15 to unbreak the build. +* :ghpull:`11767`: Add tolerance to csd frequency test +* :ghpull:`11757`: PGF backend output text color even if black +* :ghpull:`11751`: Remove the unused 'verbose' option from setupext. +* :ghpull:`9084`: Require calling a _BoundMethodProxy to get the underlying callable. +* :ghpull:`11752`: Fix section level of Previous Whats New +* :ghpull:`10513`: Replace most uses of getfilesystemencoding by os.fs{en,de}code. +* :ghpull:`11739`: fix tight_layout bug #11737 +* :ghpull:`11744`: minor doc update on axes_grid1's inset_axes +* :ghpull:`11729`: Pass 'figure' as kwarg to FigureCanvasQt5Agg super __init__. +* :ghpull:`11736`: Remove unused needs_sphinx marker; move importorskip to toplevel. +* :ghpull:`11731`: Directly get the size of the renderer buffer from the renderer. +* :ghpull:`11717`: DOC: fix broken link in inset-locator example +* :ghpull:`11723`: Start work on making colormaps picklable. +* :ghpull:`11721`: Remove some references to colorConverter. +* :ghpull:`11713`: Don't assume cwd in test_ipynb. +* :ghpull:`11026`: ENH add an inset_axes to the axes class +* :ghpull:`11712`: Fix drawing on qt+retina. +* :ghpull:`11714`: docstring for Figure.tight_layout don't include renderer parameter +* :ghpull:`8951`: Let QPaintEvent tell us what region to repaint. +* :ghpull:`11234`: Add fig.add_artist method +* :ghpull:`11706`: Remove unused private method. +* :ghpull:`11637`: Split API changes into individual pages +* :ghpull:`10403`: Deprecate LocatableAxes from toolkits +* :ghpull:`11699`: Dedent overindented rst bullet lists. +* :ghpull:`11701`: Use skipif instead of xfail when test dependencies are missing. +* :ghpull:`11700`: Don't use pytest -rw now that pytest-warnings is builtin. +* :ghpull:`11696`: Don't force backend in toolmanager example. +* :ghpull:`11690`: Avoid using private APIs in examples. +* :ghpull:`11684`: Style +* :ghpull:`11666`: TESTS: Increase tolerance for aarch64 tests +* :ghpull:`11680`: Boring style fixes. +* :ghpull:`11678`: Use super() instead of manually fetching supermethods for parasite axes. +* :ghpull:`11676`: Remove unused C++ code. +* :ghpull:`11010`: ENH: Add gridspec method to figure, and subplotspecs +* :ghpull:`11672`: Add comment re: use of lru_cache in PsfontsMap. +* :ghpull:`11674`: Boring style fixes. +* :ghpull:`10954`: Cache various dviread constructs globally. +* :ghpull:`9150`: Don't update style-blacklisted rcparams in rc\_\* functions +* :ghpull:`10936`: Simplify tkagg C extension. +* :ghpull:`11378`: SVG Backend gouraud_triangle Correction +* :ghpull:`11383`: FIX: Improve *c* (color) kwarg checking in scatter and the related exceptions +* :ghpull:`11627`: FIX: CL avoid fully collapsed axes +* :ghpull:`11504`: Bump pgi requirement to 0.0.11.2. +* :ghpull:`11640`: Fix barplot color if none and alpha is set +* :ghpull:`11443`: changed paths in kwdocs +* :ghpull:`11626`: Minor docstring fixes +* :ghpull:`11631`: DOC: better tight_layout error handling +* :ghpull:`11651`: Remove unused imports in examples +* :ghpull:`11633`: Clean up next api_changes +* :ghpull:`11643`: Fix deprecation messages. +* :ghpull:`9223`: Set norm to log if bins=='log' in hexbin +* :ghpull:`11622`: FIX: be forgiving about the event for enterEvent not having a pos +* :ghpull:`11581`: backend switching. +* :ghpull:`11616`: Fix some doctest issues +* :ghpull:`10872`: Cleanup _plot_args_replacer logic +* :ghpull:`11617`: Clean up what's new +* :ghpull:`11610`: FIX: let colorbar extends work for PowerNorm +* :ghpull:`11615`: Revert glyph warnings +* :ghpull:`11614`: CI: don't run tox to test pytz +* :ghpull:`11603`: Doc merge up +* :ghpull:`11613`: Make flake8 exceptions explicit +* :ghpull:`11611`: Fix css for parameter types +* :ghpull:`10001`: MAINT/BUG: Don't use 5-sided quadrilaterals in Axes3D.plot_surface +* :ghpull:`10234`: PowerNorm: do not clip negative values +* :ghpull:`11398`: Simplify retrieval of cache and config directories +* :ghpull:`10682`: ENH have ax.get_tightbbox have a bbox around all artists attached to axes. +* :ghpull:`11590`: Don't associate Wx timers with the parent frame. +* :ghpull:`10245`: Cache paths of fonts shipped with mpl relative to the mpl data path. +* :ghpull:`11381`: Deprecate text.latex.unicode. +* :ghpull:`11601`: FIX: subplots don't mutate kwargs passed by user. +* :ghpull:`11609`: Remove _macosx.NavigationToolbar. +* :ghpull:`11608`: Remove some conditional branches in examples for wx<4. +* :ghpull:`11604`: TST: Place animation files in a temp dir. +* :ghpull:`11605`: Suppress a spurious missing-glyph warning with ft2font. +* :ghpull:`11360`: Pytzectomy +* :ghpull:`10885`: Move GTK3 setupext checks to within the process. +* :ghpull:`11081`: Help tool for Wx backends +* :ghpull:`10851`: Wx Toolbar for ToolManager +* :ghpull:`11247`: Remove mplDeprecation +* :ghpull:`9795`: Backend switching +* :ghpull:`9426`: Don't mark a patch transform as set if the parent transform is not set. +* :ghpull:`9175`: Warn on freetype missing glyphs. +* :ghpull:`11412`: Make contour and contourf color assignments consistent. +* :ghpull:`11477`: Enable flake8 and re-enable it everywhere +* :ghpull:`11165`: Fix figure window icon +* :ghpull:`11584`: ENH: fix colorbar bad minor ticks +* :ghpull:`11438`: ENH: add get_gridspec convenience method to subplots +* :ghpull:`11451`: Cleanup Matplotlib API docs +* :ghpull:`11579`: DOC update some examples to use constrained_layout=True +* :ghpull:`11593`: Skip wx interactive tests on OSX. +* :ghpull:`11592`: Remove some extra spaces in docstrings/comments. +* :ghpull:`11585`: Some doc cleanup of Triangulation +* :ghpull:`10474`: Use TemporaryDirectory instead of mkdtemp in a few places. +* :ghpull:`11240`: Deprecate the examples.directory rcParam. +* :ghpull:`11370`: Sorting drawn artists by their zorder when blitting using FuncAnimation +* :ghpull:`11576`: Add parameter doc to save_diff_image +* :ghpull:`11573`: Inline setup_external_compile into setupext. +* :ghpull:`11571`: Cleanup stix_fonts_demo example. +* :ghpull:`11563`: Use explicit signature in pyplot.close() +* :ghpull:`9801`: ENH: Change default Autodatelocator *interval_multiples* +* :ghpull:`11570`: More simplifications to FreeType setup on Windows. +* :ghpull:`11401`: Some py3fications. +* :ghpull:`11566`: Cleanups. +* :ghpull:`11520`: Add private API retrieving the current event loop and backend GUI info. +* :ghpull:`11544`: Restore axes sharedness when unpickling. +* :ghpull:`11568`: Figure.text changes +* :ghpull:`11248`: Simplify FreeType Windows build. +* :ghpull:`11556`: Fix colorbar bad ticks +* :ghpull:`11494`: Fix CI install of wxpython. +* :ghpull:`11564`: triinterpolate cleanups. +* :ghpull:`11548`: Use numpydoc-style parameter lists for choices +* :ghpull:`9583`: Add edgecolors kwarg to contourf +* :ghpull:`10275`: Update contour.py and widget.py +* :ghpull:`11547`: Fix example links +* :ghpull:`11555`: Fix spelling in title +* :ghpull:`11404`: FIX: don't include text at -inf in bbox +* :ghpull:`11455`: Fixing the issue where right column and top row generate wrong stream… +* :ghpull:`11297`: Prefer warn_deprecated instead of warnings.warn. +* :ghpull:`11495`: Update the documentation guidelines +* :ghpull:`11545`: Doc: fix x(filled) marker image +* :ghpull:`11287`: Maintain artist addition order in Axes.mouseover_set. +* :ghpull:`11530`: FIX: Ensuring both x and y attrs of LocationEvent are int +* :ghpull:`10336`: Use Integral and Real in typechecks rather than explicit types. +* :ghpull:`10298`: Apply gtk3 background. +* :ghpull:`10297`: Fix gtk3agg alpha channel. +* :ghpull:`9094`: axisbelow should just set zorder. +* :ghpull:`11459`: Doc changes in add_subplot and add_axes +* :ghpull:`10908`: Make draggable callbacks check that artist has not been removed. +* :ghpull:`11522`: Small cleanups. +* :ghpull:`11539`: DOC: talk about sticky edges in Axes.margins +* :ghpull:`11540`: adding axes to module list +* :ghpull:`11537`: Fix invalid value warning when autoscaling with no data limits +* :ghpull:`11512`: Skip 3D rotation example in sphinx gallery +* :ghpull:`11538`: Re-enable pep8 on examples folder +* :ghpull:`11136`: Move remaining examples from api/ +* :ghpull:`11519`: Raise ImportError on failure to import backends. +* :ghpull:`11529`: add documentation for quality in savefig +* :ghpull:`11528`: Replace an unnecessary zip() in mplot3d by numpy ops. +* :ghpull:`11492`: add __repr__ to GridSpecBase +* :ghpull:`11521`: Add missing ``.`` to rcParam +* :ghpull:`11491`: Fixed the source path on windows in rcparam_role +* :ghpull:`11514`: Remove embedding_in_tk_canvas, which demonstrated a private API. +* :ghpull:`11507`: Fix embedding_in_tk_canvas example. +* :ghpull:`11513`: Changed docstrings in Text +* :ghpull:`11503`: Remove various mentions of the now removed GTK(2) backend. +* :ghpull:`11493`: Update a test to a figure-equality test. +* :ghpull:`11501`: Treat empty $MPLBACKEND as an unset value. +* :ghpull:`11395`: Various fixes to deprecated and warn_deprecated. +* :ghpull:`11408`: Figure equality-based tests. +* :ghpull:`11461`: Fixed bug in rendering font property kwargs list +* :ghpull:`11397`: Replace ACCEPTS by standard numpydoc params table. +* :ghpull:`11483`: Use pip requirements files for travis build +* :ghpull:`11481`: remove more pylab references +* :ghpull:`10940`: Run flake8 instead of pep8 on Python 3.6 +* :ghpull:`11476`: Remove pylab references +* :ghpull:`11424`: DOC: point align-ylabel demo to new align-label functions +* :ghpull:`11454`: add subplots to axes documentation +* :ghpull:`11470`: Hyperlink DOIs against preferred resolver +* :ghpull:`11457`: Search $CPATH for include directories +* :ghpull:`11293`: Lim parameter naming +* :ghpull:`11447`: Do not use class attributes as defaults for instance attributes +* :ghpull:`11224`: Add deprecation messages for unused kwargs in FancyArrowPatch +* :ghpull:`11437`: Doc markersupdate +* :ghpull:`11417`: FIX: better default spine path (for logit) +* :ghpull:`11406`: Backport PR #11403 on branch v2.2.2-doc +* :ghpull:`11427`: FIX: pathlib in nbagg +* :ghpull:`11428`: Doc: Remove huge note box from examples. +* :ghpull:`11392`: Deprecate the ``verts`` kwarg to ``scatter``. +* :ghpull:`8834`: WIP: Contour log extension +* :ghpull:`11402`: Remove unnecessary str calls. +* :ghpull:`11399`: Autogenerate credits.rst +* :ghpull:`11382`: plt.subplots and plt.figure docstring changes +* :ghpull:`11396`: Remove some (minor) comments regarding Py2. +* :ghpull:`11210`: FIX: don't pad axes for ticks if they aren't visible or axis off +* :ghpull:`11362`: Fix tox configuration +* :ghpull:`11366`: Improve docstring of Axes.spy +* :ghpull:`11289`: io.open and codecs.open are redundant with open on Py3. +* :ghpull:`11213`: MNT: deprecate patches.YAArrow +* :ghpull:`11352`: Catch a couple of test warnings +* :ghpull:`11292`: Simplify cleanup decorator implementation. +* :ghpull:`11349`: Remove non-existent files from MANIFEST.IN +* :ghpull:`8774`: Git issue #7216 - Add a "ruler" tool to the plot UI +* :ghpull:`11348`: Make OSX's blit() have a consistent signature with other backends. +* :ghpull:`11345`: Revert "Deprecate text.latex.unicode." +* :ghpull:`11250`: [WIP] Add tutorial for LogScale +* :ghpull:`11223`: Add an arrow tutorial +* :ghpull:`10212`: Categorical refactor +* :ghpull:`11339`: Convert Ellipse docstring to numpydoc +* :ghpull:`11255`: Deprecate text.latex.unicode. +* :ghpull:`11338`: Fix typos +* :ghpull:`11332`: Let plt.rc = matplotlib.rc, instead of being a trivial wrapper. +* :ghpull:`11331`: multiprocessing.set_start_method() --> mp.set_start_method() +* :ghpull:`9948`: Add ``ealpha`` option to ``errorbar`` +* :ghpull:`11329`: Minor docstring update of thumbnail +* :ghpull:`9551`: Refactor backend loading +* :ghpull:`11328`: Undeprecate Polygon.xy from #11299 +* :ghpull:`11318`: Improve docstring of imread() and imsave() +* :ghpull:`11311`: Simplify image.thumbnail. +* :ghpull:`11225`: Add stacklevel=2 to some more warnings.warn() calls +* :ghpull:`11313`: Add changelog entry for removal of proprietary sphinx directives. +* :ghpull:`11323`: Fix infinite loop for connectionstyle + add some tests +* :ghpull:`11314`: API changes: use the heading format defined in README.txt +* :ghpull:`11320`: Py3fy multiprocess example. +* :ghpull:`6254`: adds two new cyclic color schemes +* :ghpull:`11268`: DOC: Sanitize some internal documentation links +* :ghpull:`11300`: Start replacing ACCEPTS table by parsing numpydoc. +* :ghpull:`11298`: Automagically set the stacklevel on warnings. +* :ghpull:`11277`: Avoid using MacRoman encoding. +* :ghpull:`11295`: Use sphinx builtin only directive instead of custom one. +* :ghpull:`11305`: Reuse the noninteractivity warning from Figure.show in _Backend.show. +* :ghpull:`11304`: Re-remove six from INSTALL.rst. +* :ghpull:`11301`: Undefined name: cbook --> matplotlib.cbook +* :ghpull:`11267`: FIX: allow nan values in data for plt.hist +* :ghpull:`11271`: Better argspecs for Axes.stem +* :ghpull:`11280`: Trivial cleanups +* :ghpull:`10514`: Cleanup/update cairo + gtk compatibility matrix. +* :ghpull:`11282`: Reduce the use of C++ exceptions +* :ghpull:`11263`: Fail gracefully if can't decode font names +* :ghpull:`11278`: Remove conditional path for sphinx <1.3 in plot_directive. +* :ghpull:`11273`: Include template matplotlibrc in package_data. +* :ghpull:`11249`: Simplify FreeType build. +* :ghpull:`11158`: Remove dependency on six - we're Py3 only now! +* :ghpull:`10050`: Update Legend draggable API +* :ghpull:`11206`: More cleanups +* :ghpull:`11001`: DOC: improve legend bbox_to_anchor description +* :ghpull:`11258`: Removed comment in AGG backend that is no longer applicable +* :ghpull:`11062`: FIX: call constrained_layout twice +* :ghpull:`11251`: Re-run boilerplate.py. +* :ghpull:`11228`: Don't bother checking luatex's version. +* :ghpull:`11207`: Update venv gui docs wrt availability of PySide2. +* :ghpull:`11236`: Minor cleanups to setupext. +* :ghpull:`11239`: Reword the timeout error message in cbook._lock_path. +* :ghpull:`11204`: Test that boilerplate.py is correctly run. +* :ghpull:`11172`: ENH add rcparam to legend_title +* :ghpull:`11229`: Simplify lookup of animation external commands. +* :ghpull:`9086`: Add SVG animation. +* :ghpull:`11212`: Fix CirclePolygon __str__ + adding tests +* :ghpull:`6737`: Ternary +* :ghpull:`11216`: Yet another set of simplifications. +* :ghpull:`11056`: Simplify travis setup a bit. +* :ghpull:`11205`: Minor cleanups to pyplot. +* :ghpull:`11174`: Replace numeric loc by position string +* :ghpull:`11208`: Don't crash qt figure options on unknown marker styles. +* :ghpull:`11195`: Some unrelated cleanups. +* :ghpull:`11192`: Don't use deprecated get_texcommand in backend_pgf. +* :ghpull:`11197`: Simplify demo_ribbon_box.py. +* :ghpull:`11137`: Convert \*\*kwargs to named arguments for a clearer API +* :ghpull:`10982`: Improve docstring of Axes.imshow +* :ghpull:`11182`: Use GLib.MainLoop() instead of deprecated GObject.MainLoop() +* :ghpull:`11185`: Fix undefined name error in backend_pgf. +* :ghpull:`10321`: Ability to scale axis by a fixed factor +* :ghpull:`8787`: Faster path drawing for the cairo backend (cairocffi only) +* :ghpull:`4559`: tight_layout: Use a different default gridspec +* :ghpull:`11179`: Convert internal tk focus helper to a context manager +* :ghpull:`11176`: Allow creating empty closed paths +* :ghpull:`10339`: Pass explicit font paths to fontspec in backend_pgf. +* :ghpull:`9832`: Minor cleanup to Text class. +* :ghpull:`11141`: Remove mpl_examples symlink. +* :ghpull:`10715`: ENH: add title_fontsize to legend +* :ghpull:`11166`: Set stacklevel to 2 for backend_wx +* :ghpull:`10934`: Autogenerate (via boilerplate) more of pyplot. +* :ghpull:`9298`: Cleanup blocking_input. +* :ghpull:`6329`: Set _text to '' if Text.set_text argument is None +* :ghpull:`11146`: Explicit args and refactor Axes.margins +* :ghpull:`11145`: Use kwonlyargs instead of popping from kwargs +* :ghpull:`11119`: PGF: Get unitless positions from Text elements (fix #11116) +* :ghpull:`9078`: New anchored direction arrows +* :ghpull:`11144`: Remove toplevel unit/ directory. +* :ghpull:`11148`: remove use of subprocess compatibility shim +* :ghpull:`11143`: Use debug level for debugging messages +* :ghpull:`11142`: Finish removing future imports. +* :ghpull:`11130`: Don't include the postscript title if it is not latin-1 encodable. +* :ghpull:`11093`: DOC: Fixup to AnchoredArtist examples in the gallery +* :ghpull:`11132`: pillow-dependency update +* :ghpull:`10446`: implementation of the copy canvas tool +* :ghpull:`9131`: FIX: prevent the canvas from jump sizes due to DPI changes +* :ghpull:`9454`: Batch ghostscript converter. +* :ghpull:`10545`: Change manual kwargs popping to kwonly arguments. +* :ghpull:`10950`: Actually ignore invalid log-axis limit setting +* :ghpull:`11096`: Remove support for bar(left=...) (as opposed to bar(x=...)). +* :ghpull:`11106`: py3fy art3d. +* :ghpull:`11085`: Use GtkShortcutsWindow for Help tool. +* :ghpull:`11099`: Deprecate certain marker styles that have simpler synonyms. +* :ghpull:`11100`: Some more deprecations of old, old stuff. +* :ghpull:`11098`: Make Marker.get_snap_threshold() always return a scalar. +* :ghpull:`11097`: Schedule a removal date for passing normed (instead of density) to hist. +* :ghpull:`9706`: Masking invalid x and/or weights in hist +* :ghpull:`11080`: Py3fy backend_qt5 + other cleanups to the backend. +* :ghpull:`10967`: updated the pyplot fill_between example to elucidate the premise;maki… +* :ghpull:`11075`: Drop alpha channel when saving comparison failure diff image. +* :ghpull:`9022`: Help tool +* :ghpull:`11076`: Don't create texput.{aux,log} in rootdir everytime tests are run. +* :ghpull:`11073`: py3fication of some tests. +* :ghpull:`11074`: bytes % args is back since py3.5 +* :ghpull:`11066`: Use chained comparisons where reasonable. +* :ghpull:`11061`: Changed tight_layout doc strings +* :ghpull:`11064`: Minor docstring format cleanup +* :ghpull:`11055`: Remove setup_tests_only.py. +* :ghpull:`11057`: Update Ellipse position with ellipse.center +* :ghpull:`10435`: Pathlibify font_manager (only internally, doesn't change the API). +* :ghpull:`10442`: Make the filternorm prop of Images a boolean rather than a {0,1} scalar. +* :ghpull:`9855`: ENH: make ax.get_position apply aspect +* :ghpull:`9987`: MNT: hist2d now uses pcolormesh instead of pcolorfast +* :ghpull:`11014`: Merge v2.2.x into master +* :ghpull:`11000`: FIX: improve Text repr to not error if non-float x and y. +* :ghpull:`10910`: FIX: return proper legend window extent +* :ghpull:`10915`: FIX: tight_layout having negative width axes +* :ghpull:`10408`: Factor out common code in _process_unit_info +* :ghpull:`10960`: Added share_tickers parameter to axes._AxesBase.twinx/y +* :ghpull:`10971`: Skip pillow animation test if pillow not importable +* :ghpull:`10970`: Simplify/fix some manual manipulation of len(args). +* :ghpull:`10958`: Simplify the grouper implementation. +* :ghpull:`10508`: Deprecate FigureCanvasQT.keyAutoRepeat. +* :ghpull:`10607`: Move notify_axes_change to FigureManagerBase class. +* :ghpull:`10215`: Test timers and (a bit) key_press_event for interactive backends. +* :ghpull:`10955`: Py3fy cbook, compare_backend_driver_results +* :ghpull:`10680`: Rewrite the tk C blitting code +* :ghpull:`9498`: Move title up if x-axis is on the top of the figure +* :ghpull:`10942`: Make active param in CheckBottons optional, default false +* :ghpull:`10943`: Allow pie textprops to take alignment and rotation arguments +* :ghpull:`10780`: Fix scaling of RadioButtons +* :ghpull:`10938`: Fix two undefined names +* :ghpull:`10685`: fix plt.show doesn't warn if a non-GUI backend +* :ghpull:`10689`: Declare global variables that are created elsewhere +* :ghpull:`10845`: WIP: first draft at replacing linkcheker +* :ghpull:`10898`: Replace "matplotlibrc" by "rcParams" in the docs where applicable. +* :ghpull:`10926`: Some more removals of deprecated APIs. +* :ghpull:`9173`: dynamically generate pyplot functions +* :ghpull:`10918`: Use function signatures in boilerplate.py. +* :ghpull:`10914`: Changed pie charts default shape to circle and added tests +* :ghpull:`10864`: ENH: Stop mangling default figure file name if file exists +* :ghpull:`10562`: Remove deprecated code in image.py +* :ghpull:`10798`: FIX: axes limits reverting to automatic when sharing +* :ghpull:`10485`: Remove the 'hold' kwarg from codebase +* :ghpull:`10571`: Use np.full{,_like} where appropriate. [requires numpy>=1.12] +* :ghpull:`10913`: Rely a bit more on rc_context. +* :ghpull:`10299`: Invalidate texmanager cache when any text.latex.\* rc changes. +* :ghpull:`10906`: Deprecate ImageComparisonTest. +* :ghpull:`10904`: Improve docstring of clabel() +* :ghpull:`10912`: remove unused matplotlib.testing import +* :ghpull:`10876`: [wip] Replace _remove_method by _on_remove list of callbacks +* :ghpull:`10692`: Update afm docs and internal data structures +* :ghpull:`10896`: Update INSTALL.rst. +* :ghpull:`10905`: Inline knownfailureif. +* :ghpull:`10907`: No need to mark (unicode) strings as u"foo" anymore. +* :ghpull:`10903`: Py3fy testing machinery. +* :ghpull:`10901`: Remove Py2/3 portable code guide. +* :ghpull:`10900`: Remove some APIs deprecated in mpl2.1. +* :ghpull:`10902`: Kill some Py2 docs. +* :ghpull:`10887`: Added feature (Make pie charts circular by default #10789) +* :ghpull:`10884`: Style fixes to setupext.py. +* :ghpull:`10879`: Deprecate two-args for cycler() and set_prop_cycle() +* :ghpull:`10865`: DOC: use OO-ish interface in image, contour, field examples +* :ghpull:`8479`: FIX markerfacecolor / mfc not in rcparams +* :ghpull:`10314`: setattr context manager. +* :ghpull:`10013`: Allow rasterization for 3D plots +* :ghpull:`10158`: Allow mplot3d rasterization; adjacent cleanups. +* :ghpull:`10871`: Rely on rglob support rather than os.walk. +* :ghpull:`10708`: Py3fy webagg/nbagg. +* :ghpull:`10862`: py3ify table.py and correct some docstrings +* :ghpull:`10810`: Fix for plt.plot() does not support structured arrays as data= kwarg +* :ghpull:`10861`: More python3 cleanup +* :ghpull:`9903`: ENH: adjustable colorbar ticks +* :ghpull:`10831`: Minor docstring updates on binning related plot functions +* :ghpull:`9571`: Remove LaTeX checking in setup.py. +* :ghpull:`10097`: Reset extents in RectangleSelector when not interactive on press. +* :ghpull:`10686`: fix BboxConnectorPatch does not show facecolor +* :ghpull:`10801`: Fix undefined name. Add animation tests. +* :ghpull:`10857`: FIX: ioerror font cache, second try +* :ghpull:`10796`: Added descriptions for line bars and markers examples +* :ghpull:`10846`: Unsixification +* :ghpull:`10852`: Update docs re: pygobject in venv. +* :ghpull:`10847`: Py3fy axis.py. +* :ghpull:`10834`: Minor docstring updates on spectral plot functions +* :ghpull:`10778`: wx_compat is no more. +* :ghpull:`10609`: More wx cleanup. +* :ghpull:`10826`: Py3fy dates.py. +* :ghpull:`10837`: Correctly display error when running setup.py test. +* :ghpull:`10838`: Don't use private attribute in tk example. Fix Toolbar class rename. +* :ghpull:`10823`: Add some basic smoketesting for webagg (and wx). +* :ghpull:`10828`: Add print_rgba to backend_cairo. +* :ghpull:`10830`: Make function signatures more explicit +* :ghpull:`10829`: Use long color names for default rcParams +* :ghpull:`9776`: WIP: Lockout new converters Part 2 +* :ghpull:`10799`: DOC: make legend docstring interpolated +* :ghpull:`10818`: Deprecate vestigial Annotation.arrow. +* :ghpull:`10817`: Add test to imread from url. +* :ghpull:`10696`: Simplify venv docs. +* :ghpull:`10724`: Py3fication of unicode. +* :ghpull:`10815`: API: shift deprecation of TempCache class to 3.0 +* :ghpull:`10725`: FIX/TST constrained_layout remove test8 duplication +* :ghpull:`10705`: FIX: enable extend kwargs with log scale colorbar +* :ghpull:`10400`: numpydoc-ify art3d docstrings +* :ghpull:`10723`: repr style fixes. +* :ghpull:`10592`: Rely on generalized \* and \*\* unpackings where possible. +* :ghpull:`9475`: Declare property aliases in a single place +* :ghpull:`10794`: fixed comment typo +* :ghpull:`10768`: Fix crash when imshow encounters longdouble data +* :ghpull:`10774`: Remove dead wx testing code. +* :ghpull:`10756`: Fixes png showing inconsistent inset_axes position +* :ghpull:`10773`: Consider alpha channel from RGBA color of text for SVG backend text opacity rendering +* :ghpull:`10772`: API: check locator and formatter args when passed +* :ghpull:`10713`: Implemented support for 'markevery' in prop_cycle +* :ghpull:`10751`: make centre_baseline legal for Text.set_verticalalignment +* :ghpull:`10771`: FIX/TST OS X builds +* :ghpull:`10742`: FIX: reorder linewidth setting before linestyle +* :ghpull:`10714`: sys.platform is normalized to "linux" on Py3. +* :ghpull:`10542`: Minor cleanup: PEP8, PEP257 +* :ghpull:`10636`: Remove some wx version checks. +* :ghpull:`9731`: Make legend title fontsize obey fontsize kwarg by default +* :ghpull:`10697`: Remove special-casing of _remove_method when pickling. +* :ghpull:`10701`: Autoadd removal version to deprecation message. +* :ghpull:`10699`: Remove incorrect warning in gca(). +* :ghpull:`10674`: Fix getting polar axes in plt.polar() +* :ghpull:`10564`: Nested classes and instancemethods are directly picklable on Py3.5+. +* :ghpull:`10107`: Fix stay_span to reset onclick in SpanSelector. +* :ghpull:`10693`: Make markerfacecolor work for 3d scatterplots +* :ghpull:`10596`: Switch to per-file locking. +* :ghpull:`10532`: Py3fy backend_pgf. +* :ghpull:`10618`: Fixes #10501. python3 support and pep8 in jpl_units +* :ghpull:`10652`: Some py3fication for matplotlib/__init__, setupext. +* :ghpull:`10522`: Py3fy font_manager. +* :ghpull:`10666`: More figure-related doc updates +* :ghpull:`10507`: Remove Python 2 code from C extensions +* :ghpull:`10679`: Small fixes to gtk3 examples. +* :ghpull:`10426`: Delete deprecated backends +* :ghpull:`10488`: Bug Fix - Polar plot rectangle patch not transformed correctly (#8521) +* :ghpull:`9814`: figure_enter_event uses now LocationEvent instead of Event. Fix issue #9812. +* :ghpull:`9918`: Remove old nose testing code +* :ghpull:`10672`: Deprecation fixes. +* :ghpull:`10608`: Remove most APIs deprecated in 2.1. +* :ghpull:`10653`: Mock is in stdlib in Py3. +* :ghpull:`10603`: Remove workarounds for numpy<1.10. +* :ghpull:`10660`: Work towards removing reuse-of-axes-on-collision. +* :ghpull:`10661`: Homebrew python is now python 3 +* :ghpull:`10656`: Minor fixes to event handling docs. +* :ghpull:`10635`: Simplify setupext by using globs. +* :ghpull:`10632`: Support markers from Paths that consist of one line segment +* :ghpull:`10558`: Remove if six.PY2 code paths from boilerplate.py +* :ghpull:`10640`: Fix extra and missing spaces in constrainedlayout warning. +* :ghpull:`10624`: Some trivial py3fications. +* :ghpull:`10548`: Implement PdfPages for backend pgf +* :ghpull:`10614`: Use np.stack instead of list(zip()) in colorbar.py. +* :ghpull:`10621`: Cleanup and py3fy backend_gtk3. +* :ghpull:`10615`: More style fixes. +* :ghpull:`10604`: Minor style fixes. +* :ghpull:`10565`: Strip python 2 code from subprocess.py +* :ghpull:`10605`: Bump a tolerance in test_axisartist_floating_axes. +* :ghpull:`7853`: Use exact types for Py_BuildValue. +* :ghpull:`10591`: Switch to @-matrix multiplication. +* :ghpull:`10570`: Fix check_shared in test_subplots. +* :ghpull:`10569`: Various style fixes. +* :ghpull:`10593`: Use 'yield from' where appropriate. +* :ghpull:`10577`: Minor simplification to Figure.__getstate__ logic. +* :ghpull:`10549`: Source typos +* :ghpull:`10525`: Convert six.moves.xrange() to range() for Python 3 +* :ghpull:`10541`: More argumentless (py3) super() +* :ghpull:`10539`: TST: Replace assert_equal with plain asserts. +* :ghpull:`10534`: Modernize cbook.get_realpath_and_stat. +* :ghpull:`10524`: Remove unused private _StringFuncParser. +* :ghpull:`10470`: Remove Python 2 code from setup +* :ghpull:`10528`: py3fy examples +* :ghpull:`10520`: Py3fy mathtext.py. +* :ghpull:`10527`: Switch to argumentless (py3) super(). +* :ghpull:`10523`: The current master branch is now python 3 only. +* :ghpull:`10515`: Use feature detection instead of version detection +* :ghpull:`10432`: Use some new Python3 types +* :ghpull:`10475`: Use HTTP Secure for matplotlib.org +* :ghpull:`10383`: Fix some C++ warnings +* :ghpull:`10498`: Tell the lgtm checker that the project is Python 3 only +* :ghpull:`10505`: Remove backport of which() +* :ghpull:`10483`: Remove backports.functools_lru_cache +* :ghpull:`10492`: Avoid UnboundLocalError in drag_pan. +* :ghpull:`10491`: Simplify Mac builds on Travis +* :ghpull:`10481`: Remove python 2 compatibility code from dviread +* :ghpull:`10447`: Remove Python 2 compatibility code from backend_pdf.py +* :ghpull:`10468`: Replace is_numlike by isinstance(..., numbers.Number). +* :ghpull:`10439`: mkdir is in the stdlib in Py3. +* :ghpull:`10392`: FIX: make set_text(None) keep string empty instead of "None" +* :ghpull:`10425`: API: only support python 3.5+ +* :ghpull:`10316`: TST FIX pyqt5 5.9 +* :ghpull:`4625`: hist2d() is now using pcolormesh instead of pcolorfast -Issues (27): +Issues (110): -* :ghissue:`11822`: matplotlib.pyplot import gives error on MacOSX -* :ghissue:`11832`: Marker disappears with markerfacecolor='None' when saving as eps file -* :ghissue:`11810`: Figure gets collapsed after nth iteration using constrained_layout -* :ghissue:`11389`: Incorrect processing of plot_args when using data argument -* :ghissue:`11635`: "import matplotlib.pyplot" fails on NetBSD with version 2.2.2 -* :ghissue:`6781`: Toolbar disappears in TkAgg if window if resized -* :ghissue:`10890`: Bug causes to_rgba to fail inside cm.py -* :ghissue:`9582`: axes_grid1 docs do not appear on mpl.org anymore. -* :ghissue:`11484`: Matplotlib does not build on OpenBSD using clang -* :ghissue:`11302`: [DOC] Build of documentation fails -* :ghissue:`11031`: wrong x-position of marker with drawstyle='steps-xxx' -* :ghissue:`11372`: Symbols not showing in eps format -* :ghissue:`10814`: manually backport #10806 -* :ghissue:`10072`: imshow doesn't properly display some images -* :ghissue:`11163`: RecursionError when calling get_yaxis_transform() on a Symlog-scaled axis. -* :ghissue:`11156`: Error in documentation of pyplot.errorbar() -* :ghissue:`11104`: pyplot.plot conflict: markerfacecolor ='none' and alpha -* :ghissue:`11126`: Legend positioning behavior change from matplotlib {2.1.0 -> 2.2.2} -* :ghissue:`10633`: Type Error when saving jpg image (Pillow 3) -* :ghissue:`10911`: Get ticklabels back on shared axis -* :ghissue:`5609`: Segfaults with wxpython 3.0.2.0 backend -* :ghissue:`11021`: remove ``import distutils.sysconfig`` from ``lib/matplotlib/__init__.py`` -* :ghissue:`10949`: Qt5Agg blitting issue with matplotlib 2.2.2 -* :ghissue:`10966`: pillow animation writer failing macOS tests -* :ghissue:`10921`: No markers in EPS if fillstyle='none' -* :ghissue:`10889`: memory error using savefig with ylim to create pdf of box plots -* :ghissue:`10866`: bz2 mistakenly made a hard requirement of matplotlib 2.2.2 +* :ghissue:`11966`: CartoPy code gives attribute error +* :ghissue:`11844`: Backend related issues with matplotlib 3.0.0rc1 +* :ghissue:`12095`: colorbar minorticks (possibly release critical for 3.0) +* :ghissue:`12108`: Broken doc build with sphinx 1.8 +* :ghissue:`7366`: handle repaint requests better it qtAgg +* :ghissue:`11985`: Single shot timer not working correctly with MacOSX backend +* :ghissue:`10948`: OSX backend raises deprecation warning for enter_notify_event +* :ghissue:`11970`: Legend.get_window_extent now requires a renderer +* :ghissue:`8293`: investigate whether using a single instance of ghostscript for ps->png conversion can speed up the Windows build +* :ghissue:`7707`: Replace pep8 by pycodestyle for style checking +* :ghissue:`9135`: rcdefaults, rc_file_defaults, rc_file should not update backend if it has already been selected +* :ghissue:`12015`: AttributeError with GTK3Agg backend +* :ghissue:`11913`: plt.contour levels parameter don't work as intended if receive a single int +* :ghissue:`11846`: macosx backend won't load +* :ghissue:`11792`: Newer versions of ImageMagickWriter not found on windows +* :ghissue:`11858`: Adding "pie of pie" and "bar of pie" functionality +* :ghissue:`11852`: get_backend() backward compatibility +* :ghissue:`11629`: Importing qt_compat when no Qt binding is installed fails with NameError instead of ImportError +* :ghissue:`11842`: Failed nose import in test_annotation_update +* :ghissue:`11252`: Some API removals not documented +* :ghissue:`9404`: Drop support for python 2 +* :ghissue:`2625`: Markers in XKCD style +* :ghissue:`11749`: metadata kwarg to savefig is not documented +* :ghissue:`11702`: Setting alpha on legend handle changes patch color +* :ghissue:`8798`: gtk3cairo draw_image does not respect origin and mishandles alpha +* :ghissue:`11737`: Bug in tight_layout +* :ghissue:`11373`: Passing an incorrectly sized colour list to scatter should raise a relevant error +* :ghissue:`11756`: pgf backend doesn't set color of text when the color is black +* :ghissue:`11766`: test_axes.py::test_csd_freqs failing with numpy 1.15.0 on macOS +* :ghissue:`11750`: previous whats new is overindented on "what's new in mpl3.0 page" +* :ghissue:`11728`: Qt5 Segfaults on window resize +* :ghissue:`11709`: Repaint region is wrong on Retina display with Qt5 +* :ghissue:`11578`: wx segfaulting on OSX travis tests +* :ghissue:`11628`: edgecolor argument not working in matplotlib.pyplot.bar +* :ghissue:`11625`: plt.tight_layout() does not work with plt.subplot2grid +* :ghissue:`4993`: Version ~/.cache/matplotlib +* :ghissue:`7842`: If hexbin has logarithmic bins, use log formatter for colorbar +* :ghissue:`11607`: AttributeError: 'QEvent' object has no attribute 'pos' +* :ghissue:`11486`: Colorbar does not render with PowerNorm and min extend when using imshow +* :ghissue:`11582`: wx segfault +* :ghissue:`11515`: using 'sharex' once in 'subplots' function can affect subsequent calles to 'subplots' +* :ghissue:`10269`: input() blocks any rendering and event handling +* :ghissue:`10345`: Python 3.4 with Matplotlib 1.5 vs Python 3.6 with Matplotlib 2.1 +* :ghissue:`10443`: Drop use of pytz dependency in next major release +* :ghissue:`10572`: contour and contourf treat levels differently +* :ghissue:`11123`: Crash when interactively adding a number of subplots +* :ghissue:`11550`: Undefined names: 'obj_type' and 'cbook' +* :ghissue:`11138`: Only the first figure window has mpl icon, all other figures have default tk icon. +* :ghissue:`11510`: extra minor-ticks on the colorbar when used with the extend option +* :ghissue:`11369`: zorder of Artists not being respected when blitting with FuncAnimation +* :ghissue:`11452`: Streamplot ignores rightmost column and topmost row of velocity data +* :ghissue:`11284`: imshow of multiple images produces old pixel values printed in status bar +* :ghissue:`11496`: MouseEvent.x and .y have different types +* :ghissue:`11534`: Cross-reference margins and sticky edges +* :ghissue:`8556`: Add images of markers to the list of markers +* :ghissue:`11386`: Logit scale doesn't position x/ylabel correctly first draw +* :ghissue:`11384`: Undefined name 'Path' in backend_nbagg.py +* :ghissue:`11426`: nbagg broken on master. 'Path' is not defined... +* :ghissue:`11390`: Internal use of deprecated code +* :ghissue:`11203`: tight_layout reserves tick space even if disabled +* :ghissue:`11361`: Tox.ini does not work out of the box +* :ghissue:`11253`: Problem while changing current figure size in Jupyter notebook +* :ghissue:`11219`: Write an arrow tutorial +* :ghissue:`11322`: Really deprecate Patches.xy? +* :ghissue:`11294`: ConnectionStyle Angle3 hangs with specific parameters +* :ghissue:`9518`: Some ConnectionStyle not working +* :ghissue:`11077`: Font "DejaVu Sans" can only be used through fallback +* :ghissue:`10717`: Failure to find matplotlibrc when testing installed distribution +* :ghissue:`9912`: Cleaning up variable argument signatures +* :ghissue:`3701`: unit tests should compare pyplot.py with output from boilerplate.py +* :ghissue:`11183`: Undefined name 'system_fonts' in backend_pgf.py +* :ghissue:`11101`: Crash on empty patches +* :ghissue:`11124`: [Bug] savefig cannot save file with a Unicode name +* :ghissue:`11070`: Add a "density" kwarg to hist2d +* :ghissue:`7733`: Trying to set_ylim(bottom=0) on a log scaled axis changes plot +* :ghissue:`10319`: TST: pyqt 5.10 breaks pyqt5 interactive tests +* :ghissue:`10676`: Add source code to documentation +* :ghissue:`9207`: axes has no method to return new position after box is adjusted due to aspect ratio... +* :ghissue:`4615`: hist2d with log xy axis +* :ghissue:`10996`: Plotting text with datetime axis causes warning +* :ghissue:`7582`: Report date and time of cursor position on a plot_date plot +* :ghissue:`10114`: Remove mlab from examples +* :ghissue:`10342`: imshow longdouble not truly supported +* :ghissue:`8062`: tight_layout + lots of subplots + long ylabels inverts yaxis +* :ghissue:`4413`: Long axis title alters xaxis length and direction with ``plt.tight_layout()`` +* :ghissue:`1415`: Plot title should be shifted up when xticks are set to the top of the plot +* :ghissue:`10789`: Make pie charts circular by default +* :ghissue:`10941`: Cannot set text alignment in pie chart +* :ghissue:`7908`: plt.show doesn't warn if a non-GUI backend is being used +* :ghissue:`10502`: 'FigureManager' is an undefined name in backend_wx.py +* :ghissue:`10062`: axes limits revert to automatic on sharing axes? +* :ghissue:`9246`: ENH: make default colorbar ticks adjust as nicely as axes ticks +* :ghissue:`8818`: plt.plot() does not support structured arrays as data= kwarg +* :ghissue:`10533`: Recognize pandas Timestamp objects for DateConverter? +* :ghissue:`8358`: Minor ticks on log-scale colorbar are not cleared +* :ghissue:`10075`: RectangleSelector does not work if start and end points are identical +* :ghissue:`8576`: support 'markevery' in prop_cycle +* :ghissue:`8874`: Crash in python setup.py test +* :ghissue:`3871`: replace use of _tkcanvas with get_tk_widget() +* :ghissue:`10550`: Use long color names for rc defaultParams +* :ghissue:`10722`: Duplicated test name in test_constrainedlayout +* :ghissue:`10419`: svg backend does not respect alpha channel of text *when passed as rgba* +* :ghissue:`10769`: DOC: set_major_locator could check that its getting a Locator (was EngFormatter broken?) +* :ghissue:`10719`: Need better type error checking for linewidth in ax.grid +* :ghissue:`7776`: tex cache lockfile retries should be configurable +* :ghissue:`10556`: Special conversions of xrange() +* :ghissue:`10501`: cmp() is an undefined name in Python 3 +* :ghissue:`9812`: figure_enter_event generates base Event and not LocationEvent +* :ghissue:`10602`: Random image failures with test_curvelinear4 +* :ghissue:`7795`: Incorrect uses of is_numlike diff --git a/doc/users/index.rst b/doc/users/index.rst index 84590f9d29a0..60eaf7fb6d4a 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -4,7 +4,7 @@ User's Guide ############ -.. htmlonly:: +.. only:: html :Release: |version| :Date: |today| @@ -12,11 +12,11 @@ User's Guide .. toctree:: :maxdepth: 2 - history.rst installing.rst ../tutorials/index.rst interactive.rst whats_new.rst + history.rst github_stats.rst license.rst credits.rst diff --git a/doc/users/navigation_toolbar.rst b/doc/users/navigation_toolbar.rst index 22e6e5bb1a14..162dc6b6e98a 100644 --- a/doc/users/navigation_toolbar.rst +++ b/doc/users/navigation_toolbar.rst @@ -109,31 +109,34 @@ automatically for every figure. If you are writing your own user interface code, you can add the toolbar as a widget. The exact syntax depends on your UI, but we have examples for every supported UI in the ``matplotlib/examples/user_interfaces`` directory. Here is some -example code for GTK:: +example code for GTK+ 3:: - import gtk + import gi + gi.require_version('Gtk', '3.0') + from gi.repository import Gtk from matplotlib.figure import Figure - from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas - from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar + from matplotlib.backends.backend_gtk3agg import FigureCanvas + from matplotlib.backends.backend_gtk3 import ( + NavigationToolbar2GTK3 as NavigationToolbar) - win = gtk.Window() - win.connect("destroy", lambda x: gtk.main_quit()) + win = Gtk.Window() + win.connect("destroy", lambda x: Gtk.main_quit()) win.set_default_size(400,300) win.set_title("Embedding in GTK") - vbox = gtk.VBox() + vbox = Gtk.VBox() win.add(vbox) fig = Figure(figsize=(5,4), dpi=100) ax = fig.add_subplot(111) ax.plot([1,2,3]) - canvas = FigureCanvas(fig) # a gtk.DrawingArea - vbox.pack_start(canvas) + canvas = FigureCanvas(fig) # a Gtk.DrawingArea + vbox.pack_start(canvas, True, True, 0) toolbar = NavigationToolbar(canvas, win) - vbox.pack_start(toolbar, False, False) + vbox.pack_start(toolbar, False, False, 0) win.show_all() - gtk.main() + Gtk.main() diff --git a/doc/users/prev_whats_new/whats_new_1.1.rst b/doc/users/prev_whats_new/whats_new_1.1.rst index cf9d38dd234a..502f63e4e0cc 100644 --- a/doc/users/prev_whats_new/whats_new_1.1.rst +++ b/doc/users/prev_whats_new/whats_new_1.1.rst @@ -17,11 +17,12 @@ Sankey Diagrams Kevin Davies has extended Yannick Copin's original Sankey example into a module (:mod:`~matplotlib.sankey`) and provided new examples -(:doc:`/gallery/api/sankey_basics`, :doc:`/gallery/api/sankey_links`, -:doc:`/gallery/api/sankey_rankine`). +(:doc:`/gallery/specialty_plots/sankey_basics`, +:doc:`/gallery/specialty_plots/sankey_links`, +:doc:`/gallery/specialty_plots/sankey_rankine`). -.. figure:: ../../gallery/api/images/sphx_glr_sankey_rankine_001.png - :target: ../../gallery/api/sankey_rankine.html +.. figure:: ../../gallery/specialty_plots/images/sphx_glr_sankey_rankine_001.png + :target: ../../gallery/specialty_plots/sankey_rankine.html :align: center :scale: 50 diff --git a/doc/users/prev_whats_new/whats_new_1.4.rst b/doc/users/prev_whats_new/whats_new_1.4.rst index 48dbd8266e22..70146e1e8c57 100644 --- a/doc/users/prev_whats_new/whats_new_1.4.rst +++ b/doc/users/prev_whats_new/whats_new_1.4.rst @@ -160,8 +160,8 @@ matplotlib internals were cleaned up to support using such transforms in :class:`~matplotlib.Axes`. This transform is important for some plot types, specifically the Skew-T used in meteorology. -.. figure:: ../../gallery/api/images/sphx_glr_skewt_001.png - :target: ../../gallery/api/skewt.html +.. figure:: ../../gallery/specialty_plots/images/sphx_glr_skewt_001.png + :target: ../../gallery/specialty_plots/skewt.html :align: center :scale: 50 diff --git a/doc/users/prev_whats_new/whats_new_1.5.rst b/doc/users/prev_whats_new/whats_new_1.5.rst index 854cb889f746..9e7ca186252b 100644 --- a/doc/users/prev_whats_new/whats_new_1.5.rst +++ b/doc/users/prev_whats_new/whats_new_1.5.rst @@ -377,8 +377,8 @@ kwargs names is not ideal, but `Axes.fill_between` already has a This is particularly useful for plotting pre-binned histograms. -.. figure:: ../../gallery/api/images/sphx_glr_filled_step_001.png - :target: ../../gallery/api/filled_step.html +.. figure:: ../../gallery/lines_bars_and_markers/images/sphx_glr_filled_step_001.png + :target: ../../gallery/lines_bars_and_markers/filled_step.html :align: center :scale: 50 diff --git a/doc/users/prev_whats_new/whats_new_2.1.0.rst b/doc/users/prev_whats_new/whats_new_2.1.0.rst index 86b416ae1284..20231c0332f0 100644 --- a/doc/users/prev_whats_new/whats_new_2.1.0.rst +++ b/doc/users/prev_whats_new/whats_new_2.1.0.rst @@ -261,11 +261,11 @@ rectangle for the size bar. fig, ax = plt.subplots(figsize=(3, 3)) - bar0 = AnchoredSizeBar(ax.transData, 0.3, 'unfilled', loc=3, frameon=False, - size_vertical=0.05, fill_bar=False) + bar0 = AnchoredSizeBar(ax.transData, 0.3, 'unfilled', loc='lower left', + frameon=False, size_vertical=0.05, fill_bar=False) ax.add_artist(bar0) - bar1 = AnchoredSizeBar(ax.transData, 0.3, 'filled', loc=4, frameon=False, - size_vertical=0.05, fill_bar=True) + bar1 = AnchoredSizeBar(ax.transData, 0.3, 'filled', loc='lower right', + frameon=False, size_vertical=0.05, fill_bar=True) ax.add_artist(bar1) plt.show() diff --git a/doc/users/prev_whats_new/whats_new_2.2.rst b/doc/users/prev_whats_new/whats_new_2.2.rst new file mode 100644 index 000000000000..c6dfdf2aac48 --- /dev/null +++ b/doc/users/prev_whats_new/whats_new_2.2.rst @@ -0,0 +1,388 @@ +.. _whats-new-2-2-0: + +New in Matplotlib 2.2 +===================== + +Constrained Layout Manager +-------------------------- + +.. warning:: + + Constrained Layout is **experimental**. The + behaviour and API are subject to change, or the whole functionality + may be removed without a deprecation period. + + +A new method to automatically decide spacing between subplots and their +organizing ``GridSpec`` instances has been added. It is meant to +replace the venerable ``tight_layout`` method. It is invoked via +a new ``constrained_layout=True`` kwarg to +`~.figure.Figure` or `~.figure.subplots`. + +There are new ``rcParams`` for this package, and spacing can be +more finely tuned with the new `~.set_constrained_layout_pads`. + +Features include: + + - Automatic spacing for subplots with a fixed-size padding in inches around + subplots and all their decorators, and space between as a fraction + of subplot size between subplots. + - Spacing for `~.figure.suptitle`, and colorbars that are attached to + more than one axes. + - Nested `~.GridSpec` layouts using `~.GridSpecFromSubplotSpec`. + + For more details and capabilities please see the new tutorial: + :doc:`/tutorials/intermediate/constrainedlayout_guide` + +Note the new API to access this: + +New ``plt.figure`` and ``plt.subplots`` kwarg: ``constrained_layout`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:meth:`~matplotlib.pyplot.figure` and :meth:`~matplotlib.pyplot.subplots` +can now be called with ``constrained_layout=True`` kwarg to enable +constrained_layout. + +New ``ax.set_position`` behaviour +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:meth:`~matplotlib.axes.set_position` now makes the specified axis no +longer responsive to ``constrained_layout``, consistent with the idea that the +user wants to place an axis manually. + +Internally, this means that old ``ax.set_position`` calls *inside* the library +are changed to private ``ax._set_position`` calls so that +``constrained_layout`` will still work with these axes. + +New ``figure`` kwarg for ``GridSpec`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to facilitate ``constrained_layout``, ``GridSpec`` now accepts a +``figure`` keyword. This is backwards compatible, in that not supplying this +will simply cause ``constrained_layout`` to not operate on the subplots +orgainzed by this ``GridSpec`` instance. Routines that use ``GridSpec`` (e.g. +``fig.subplots``) have been modified to pass the figure to ``GridSpec``. + + +xlabels and ylabels can now be automatically aligned +---------------------------------------------------- + +Subplot axes ``ylabels`` can be misaligned horizontally if the tick labels +are very different widths. The same can happen to ``xlabels`` if the +ticklabels are rotated on one subplot (for instance). The new methods +on the `Figure` class: `Figure.align_xlabels` and `Figure.align_ylabels` +will now align these labels horizontally or vertically. If the user only +wants to align some axes, a list of axes can be passed. If no list is +passed, the algorithm looks at all the labels on the figure. + +Only labels that have the same subplot locations are aligned. i.e. the +ylabels are aligned only if the subplots are in the same column of the +subplot layout. + +Alignemnt is persistent and automatic after these are called. + +A convenience wrapper `Figure.align_labels` calls both functions at once. + +.. plot:: + + import matplotlib.gridspec as gridspec + + fig = plt.figure(figsize=(5, 3), tight_layout=True) + gs = gridspec.GridSpec(2, 2) + + ax = fig.add_subplot(gs[0,:]) + ax.plot(np.arange(0, 1e6, 1000)) + ax.set_ylabel('Test') + for i in range(2): + ax = fig.add_subplot(gs[1, i]) + ax.set_ylabel('Booooo') + ax.set_xlabel('Hello') + if i == 0: + for tick in ax.get_xticklabels(): + tick.set_rotation(45) + fig.align_labels() + + +Axes legends now included in tight_bbox +--------------------------------------- + +Legends created via ``ax.legend`` can sometimes overspill the limits of +the axis. Tools like ``fig.tight_layout()`` and +``fig.savefig(bbox_inches='tight')`` would clip these legends. A change +was made to include them in the ``tight`` calculations. + + +Cividis colormap +---------------- + +A new dark blue/yellow colormap named 'cividis' was added. Like +viridis, cividis is perceptually uniform and colorblind +friendly. However, cividis also goes a step further: not only is it +usable by colorblind users, it should actually look effectively +identical to colorblind and non-colorblind users. For more details, +see Nunez J, Anderton C, and Renslow R. (submitted). Optimizing +colormaps with consideration for color vision deficiency to enable +accurate interpretation of scientific data." + +.. plot:: + + import matplotlib.pyplot as plt + import numpy as np + + fig, ax = plt.subplots() + pcm = ax.pcolormesh(np.random.rand(32,32), cmap='cividis') + fig.colorbar(pcm) + + +New style colorblind-friendly color cycle +----------------------------------------- + +A new style defining a color cycle has been added, +tableau-colorblind10, to provide another option for +colorblind-friendly plots. A demonstration of this new +style can be found in the reference_ of style sheets. To +load this color cycle in place of the default one:: + + import matplotlib.pyplot as plt + plt.style.use('tableau-colorblind10') + +.. _reference: https://matplotlib.org/gallery/style_sheets/style_sheets_reference.html + + +Support for numpy.datetime64 +---------------------------- + +Matplotlib has supported `datetime.datetime` dates for a long time in +`matplotlib.dates`. We +now support `numpy.datetime64` dates as well. Anywhere that +`dateime.datetime` could be used, `numpy.datetime64` can be used. eg:: + + time = np.arange('2005-02-01', '2005-02-02', dtype='datetime64[h]') + plt.plot(time) + + + +Writing animations with Pillow +------------------------------ +It is now possible to use Pillow as an animation writer. Supported output +formats are currently gif (Pillow>=3.4) and webp (Pillow>=5.0). Use e.g. as :: + + from __future__ import division + + from matplotlib import pyplot as plt + from matplotlib.animation import FuncAnimation, PillowWriter + + fig, ax = plt.subplots() + line, = plt.plot([0, 1]) + + def animate(i): + line.set_ydata([0, i / 20]) + return [line] + + anim = FuncAnimation(fig, animate, 20, blit=True) + anim.save("movie.gif", writer=PillowWriter(fps=24)) + plt.show() + + +Slider UI widget can snap to discrete values +-------------------------------------------- + +The slider UI widget can take the optional argument *valstep*. Doing so +forces the slider to take on only discrete values, starting from *valmin* and +counting up to *valmax* with steps of size *valstep*. + +If *closedmax==True*, then the slider will snap to *valmax* as well. + + + +``capstyle`` and ``joinstyle`` attributes added to `Collection` +--------------------------------------------------------------- + +The `Collection` class now has customizable ``capstyle`` and ``joinstyle`` +attributes. This allows the user for example to set the ``capstyle`` of +errorbars. + + +*pad* kwarg added to ax.set_title +--------------------------------- + +The method `axes.set_title` now has a *pad* kwarg, that specifies the +distance from the top of an axes to where the title is drawn. The units +of *pad* is points, and the default is the value of the (already-existing) +``rcParams['axes.titlepad']``. + + +Comparison of 2 colors in Matplotlib +------------------------------------ + +As the colors in Matplotlib can be specified with a wide variety of ways, the +`matplotlib.colors.same_color` method has been added which checks if +two `~matplotlib.colors` are the same. + + +Autoscaling a polar plot snaps to the origin +-------------------------------------------- + +Setting the limits automatically in a polar plot now snaps the radial limit +to zero if the automatic limit is nearby. This means plotting from zero doesn't +automatically scale to include small negative values on the radial axis. + +The limits can still be set manually in the usual way using `set_ylim`. + + +PathLike support +---------------- + +On Python 3.6+, `~matplotlib.pyplot.savefig`, `~matplotlib.pyplot.imsave`, +`~matplotlib.pyplot.imread`, and animation writers now accept `os.PathLike`\s +as input. + + +`Axes.tick_params` can set gridline properties +---------------------------------------------- + +`Tick` objects hold gridlines as well as the tick mark and its label. +`Axis.set_tick_params`, `Axes.tick_params` and `pyplot.tick_params` +now have keyword arguments 'grid_color', 'grid_alpha', 'grid_linewidth', +and 'grid_linestyle' for overriding the defaults in `rcParams`: +'grid.color', etc. + + +`Axes.imshow` clips RGB values to the valid range +------------------------------------------------- + +When `Axes.imshow` is passed an RGB or RGBA value with out-of-range +values, it now logs a warning and clips them to the valid range. +The old behaviour, wrapping back in to the range, often hid outliers +and made interpreting RGB images unreliable. + + +Properties in `matplotlibrc` to place xaxis and yaxis tick labels +----------------------------------------------------------------- + +Introducing four new boolean properties in `.matplotlibrc` for default +positions of xaxis and yaxis tick labels, namely, +`xtick.labeltop`, `xtick.labelbottom`, `ytick.labelright` and +`ytick.labelleft`. These can also be changed in rcParams. + + +PGI bindings for gtk3 +--------------------- + +The GTK3 backends can now use PGI_ instead of PyGObject_. PGI is a fairly +incomplete binding for GObject, thus its use is not recommended; its main +benefit is its availability on Travis (thus allowing CI testing for the gtk3agg +and gtk3cairo backends). + +The binding selection rules are as follows: +- if ``gi`` has already been imported, use it; else +- if ``pgi`` has already been imported, use it; else +- if ``gi`` can be imported, use it; else +- if ``pgi`` can be imported, use it; else +- error out. + +Thus, to force usage of PGI when both bindings are installed, import it first. + +.. _PGI: https://pgi.readthedocs.io/en/latest/ +.. _PyGObject: http://pygobject.readthedocs.io/en/latest/# + + + +Cairo rendering for Qt, WX, and Tk canvases +------------------------------------------- + +The new ``Qt4Cairo``, ``Qt5Cairo``, ``WXCairo``, and ``TkCairo`` +backends allow Qt, Wx, and Tk canvases to use Cairo rendering instead of +Agg. + + +Added support for QT in new ToolManager +--------------------------------------- + +Now it is possible to use the ToolManager with Qt5 +For example + + import matplotlib + + matplotlib.use('QT5AGG') + matplotlib.rcParams['toolbar'] = 'toolmanager' + import matplotlib.pyplot as plt + + plt.plot([1,2,3]) + plt.show() + + +Treat the new Tool classes experimental for now, the API will likely change and perhaps the rcParam as well + +The main example `examples/user_interfaces/toolmanager_sgskip.py` shows more +details, just adjust the header to use QT instead of GTK3 + + + +TkAgg backend reworked to support PyPy +-------------------------------------- + +PyPy_ can now plot using the TkAgg backend, supported on PyPy 5.9 +and greater (both PyPy for python 2.7 and PyPy for python 3.5). + +.. _PyPy: https:/www.pypy.org + + + +Python logging library used for debug output +-------------------------------------------- + +Matplotlib has in the past (sporadically) used an internal +verbose-output reporter. This version converts those calls to using the +standard python `logging` library. + +Support for the old ``rcParams`` ``verbose.level`` and ``verbose.fileo`` is +dropped. + +The command-line options ``--verbose-helpful`` and ``--verbose-debug`` are +still accepted, but deprecated. They are now equivalent to setting +``logging.INFO`` and ``logging.DEBUG``. + +The logger's root name is ``matplotlib`` and can be accessed from programs +as:: + + import logging + mlog = logging.getLogger('matplotlib') + +Instructions for basic usage are in :ref:`troubleshooting-faq` and for +developers in :ref:`contributing`. + +.. _logging: https://docs.python.org/3/library/logging.html + +Improved `repr` for `Transform`\s +--------------------------------- + +`Transform`\s now indent their `repr`\s in a more legible manner: + +.. code-block:: ipython + + In [1]: l, = plt.plot([]); l.get_transform() + Out[1]: + CompositeGenericTransform( + TransformWrapper( + BlendedAffine2D( + IdentityTransform(), + IdentityTransform())), + CompositeGenericTransform( + BboxTransformFrom( + TransformedBbox( + Bbox(x0=-0.05500000000000001, y0=-0.05500000000000001, x1=0.05500000000000001, y1=0.05500000000000001), + TransformWrapper( + BlendedAffine2D( + IdentityTransform(), + IdentityTransform())))), + BboxTransformTo( + TransformedBbox( + Bbox(x0=0.125, y0=0.10999999999999999, x1=0.9, y1=0.88), + BboxTransformTo( + TransformedBbox( + Bbox(x0=0.0, y0=0.0, x1=6.4, y1=4.8), + Affine2D( + [[ 100. 0. 0.] + [ 0. 100. 0.] + [ 0. 0. 1.]]))))))) diff --git a/doc/users/shell.rst b/doc/users/shell.rst index c9e659fdf249..09b1d9c1558f 100644 --- a/doc/users/shell.rst +++ b/doc/users/shell.rst @@ -100,7 +100,7 @@ up python. Then:: >>> xlabel('hi mom') should work out of the box. This is also likely to work with recent -versions of the qt4agg and gtkagg backends, and with the macosx backend +versions of the qt4agg and gtk3agg backends, and with the macosx backend on the Macintosh. Note, in batch mode, i.e. when making figures from scripts, interactive mode can be slow since it redraws diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index aab9f09dc9d0..2106f1cb3028 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -1,8 +1,8 @@ .. _whats-new: -========================== - What's new in Matplotlib -========================== +============================= + What's new in Matplotlib 3.0 +============================= For a list of all of the issues and pull requests since the last revision, see the :ref:`github-stats`. @@ -15,406 +15,243 @@ revision, see the :ref:`github-stats`. For a release, add a new section after this, then comment out the include and toctree below by indenting them. Uncomment them after the release. -.. toctree:: - :glob: - :maxdepth: 1 - - next_whats_new/* - + .. include:: next_whats_new/README.rst + .. toctree:: + :glob: + :maxdepth: 1 -New in Matplotlib 2.2 -===================== + next_whats_new/* -Constrained Layout Manager --------------------------- +Improved default backend selection +---------------------------------- -.. warning:: +The default backend no longer must be set as part of the build +process. Instead, at run time, the builtin backends are tried in +sequence until one of them imports. - Constrained Layout is **experimental**. The - behaviour and API are subject to change, or the whole functionality - may be removed without a deprecation period. +Headless linux servers (identified by the DISPLAY env not being defined) +will not select a GUI backend. +Cyclic colormaps +---------------- -A new method to automatically decide spacing between subplots and their -organizing ``GridSpec`` instances has been added. It is meant to -replace the venerable ``tight_layout`` method. It is invoked via -a new ``constrained_layout=True`` kwarg to -`~.figure.Figure` or `~.figure.subplots`. - -There are new ``rcParams`` for this package, and spacing can be -more finely tuned with the new `~.set_constrained_layout_pads`. +Two new colormaps named 'twilight' and 'twilight_shifted' have been +added. These colormaps start and end on the same color, and have two +symmetric halves with equal lightness, but diverging color. Since they +wrap around, they are a good choice for cyclic data such as phase +angles, compass directions, or time of day. Like *viridis* and +*cividis*, *twilight* is perceptually uniform and colorblind friendly. -Features include: - - Automatic spacing for subplots with a fixed-size padding in inches around - subplots and all their decorators, and space between as a fraction - of subplot size between subplots. - - Spacing for `~.figure.suptitle`, and colorbars that are attached to - more than one axes. - - Nested `~.GridSpec` layouts using `~.GridSpecFromSubplotSpec`. +Ability to scale axis by a fixed order of magnitude +--------------------------------------------------- - For more details and capabilities please see the new tutorial: - :doc:`/tutorials/intermediate/constrainedlayout_guide` +To scale an axis by a fixed order of magnitude, set the *scilimits* argument of +`.Axes.ticklabel_format` to the same (non-zero) lower and upper limits. Say to scale +the y axis by a million (1e6), use -Note the new API to access this: +.. code-block:: python -New ``plt.figure`` and ``plt.subplots`` kwarg: ``constrained_layout`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ax.ticklabel_format(style='sci', scilimits=(6, 6), axis='y') -:meth:`~matplotlib.pyplot.figure` and :meth:`~matplotlib.pyplot.subplots` -can now be called with ``constrained_layout=True`` kwarg to enable -constrained_layout. +The behavior of ``scilimits=(0, 0)`` is unchanged. With this setting, Matplotlib will adjust +the order of magnitude depending on the axis values, rather than keeping it fixed. Previously, setting +``scilimits=(m, m)`` was equivalent to setting ``scilimits=(0, 0)``. -New ``ax.set_position`` behaviour -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:meth:`~matplotlib.axes.set_position` now makes the specified axis no -longer responsive to ``constrained_layout``, consistent with the idea that the -user wants to place an axis manually. +Add ``AnchoredDirectionArrows`` feature to mpl_toolkits +-------------------------------------------------------- -Internally, this means that old ``ax.set_position`` calls *inside* the library -are changed to private ``ax._set_position`` calls so that -``constrained_layout`` will still work with these axes. +A new mpl_toolkits class +:class:`~mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows` +draws a pair of orthogonal arrows to indicate directions on a 2D plot. A +minimal working example takes in the transformation object for the coordinate +system (typically ax.transAxes), and arrow labels. There are several optional +parameters that can be used to alter layout. For example, the arrow pairs can +be rotated and the color can be changed. By default the labels and arrows have +the same color, but the class may also pass arguments for customizing arrow +and text layout, these are passed to :class:`matplotlib.text.TextPath` and +`matplotlib.patches.FancyArrowPatch`. Location, length and width for both +arrow tail and head can be adjusted, the the direction arrows and labels can +have a frame. Padding and separation parameters can be adjusted. -New ``figure`` kwarg for ``GridSpec`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order to facilitate ``constrained_layout``, ``GridSpec`` now accepts a -``figure`` keyword. This is backwards compatible, in that not supplying this -will simply cause ``constrained_layout`` to not operate on the subplots -orgainzed by this ``GridSpec`` instance. Routines that use ``GridSpec`` (e.g. -``fig.subplots``) have been modified to pass the figure to ``GridSpec``. +Add ``minorticks_on()/off()`` methods for colorbar +-------------------------------------------------- +A new method :meth:`.colorbar.Colobar.minorticks_on` has been added +to correctly display minor ticks on a colorbar. This method +doesn't allow the minor ticks to extend into the regions beyond vmin and vmax +when the extend `kwarg` (used while creating the colorbar) is set to 'both', +'max' or 'min'. +A complementary method :meth:`.colorbar.Colobar.minorticks_off` +has also been added to remove the minor ticks on the colorbar. -xlabels and ylabels can now be automatically aligned ----------------------------------------------------- -Subplot axes ``ylabels`` can be misaligned horizontally if the tick labels -are very different widths. The same can happen to ``xlabels`` if the -ticklabels are rotated on one subplot (for instance). The new methods -on the `Figure` class: `Figure.align_xlabels` and `Figure.align_ylabels` -will now align these labels horizontally or vertically. If the user only -wants to align some axes, a list of axes can be passed. If no list is -passed, the algorithm looks at all the labels on the figure. +Colorbar ticks can now be automatic +----------------------------------- -Only labels that have the same subplot locations are aligned. i.e. the -ylabels are aligned only if the subplots are in the same column of the -subplot layout. +The number of ticks placed on colorbars was previously appropriate for a large +colorbar, but looked bad if the colorbar was made smaller (i.e. via the ``shrink`` kwarg). +This has been changed so that the number of ticks is now responsive to how +large the colorbar is. -Alignemnt is persistent and automatic after these are called. -A convenience wrapper `Figure.align_labels` calls both functions at once. -.. plot:: +Don't automatically rename duplicate file names +----------------------------------------------- - import matplotlib.gridspec as gridspec +Previously, when saving a figure to a file using the GUI's +save dialog box, if the default filename (based on the +figure window title) already existed on disk, Matplotlib +would append a suffix (e.g. `Figure_1-1.png`), preventing +the dialog from prompting to overwrite the file. This +behaviour has been removed. Now if the file name exists on +disk, the user is prompted whether or not to overwrite it. +This eliminates guesswork, and allows intentional +overwriting, especially when the figure name has been +manually set using `.figure.Figure.canvas.set_window_title()`. - fig = plt.figure(figsize=(5, 3), tight_layout=True) - gs = gridspec.GridSpec(2, 2) - ax = fig.add_subplot(gs[0,:]) - ax.plot(np.arange(0, 1e6, 1000)) - ax.set_ylabel('Test') - for i in range(2): - ax = fig.add_subplot(gs[1, i]) - ax.set_ylabel('Booooo') - ax.set_xlabel('Hello') - if i == 0: - for tick in ax.get_xticklabels(): - tick.set_rotation(45) - fig.align_labels() +Legend now has a *title_fontsize* kwarg (and rcParam) +----------------------------------------------------- +The title for a `.Figure.legend` and `.Axes.legend` can now have its +fontsize set via the ``title_fontsize`` kwarg. There is also a new +:rc:`legend.title_fontsize`. Both default to ``None``, which means +the legend title will have the same fontsize as the axes default fontsize +(*not* the legend fontsize, set by the ``fontsize`` kwarg or +:rc:`legend.fontsize`). -Axes legends now included in tight_bbox ---------------------------------------- -Legends created via ``ax.legend`` can sometimes overspill the limits of -the axis. Tools like ``fig.tight_layout()`` and -``fig.savefig(bbox_inches='tight')`` would clip these legends. A change -was made to include them in the ``tight`` calculations. +Support for axes.prop_cycle property *markevery* in rcParams +------------------------------------------------------------ +The Matplotlib ``rcParams`` settings object now supports configuration +of the attribute `axes.prop_cycle` with cyclers using the `markevery` +Line2D object property. An example of this feature is provided at +`~/matplotlib/examples/lines_bars_and_markers/markevery_prop_cycle.py` -Cividis colormap ----------------- +Multipage PDF support for pgf backend +------------------------------------- -A new dark blue/yellow colormap named 'cividis' was added. Like -viridis, cividis is perceptually uniform and colorblind -friendly. However, cividis also goes a step further: not only is it -usable by colorblind users, it should actually look effectively -identical to colorblind and non-colorblind users. For more details, -see Nunez J, Anderton C, and Renslow R. (submitted). Optimizing -colormaps with consideration for color vision deficiency to enable -accurate interpretation of scientific data." +The pgf backend now also supports multipage PDF files. -.. plot:: +.. code-block:: python + from matplotlib.backends.backend_pgf import PdfPages import matplotlib.pyplot as plt - import numpy as np - - fig, ax = plt.subplots() - pcm = ax.pcolormesh(np.random.rand(32,32), cmap='cividis') - fig.colorbar(pcm) - - -New style colorblind-friendly color cycle ------------------------------------------ - -A new style defining a color cycle has been added, -tableau-colorblind10, to provide another option for -colorblind-friendly plots. A demonstration of this new -style can be found in the reference_ of style sheets. To -load this color cycle in place of the default one:: - - import matplotlib.pyplot as plt - plt.style.use('tableau-colorblind10') - -.. _reference: https://matplotlib.org/gallery/style_sheets/style_sheets_reference.html - - -Support for numpy.datetime64 ----------------------------- - -Matplotlib has supported `datetime.datetime` dates for a long time in -`matplotlib.dates`. We -now support `numpy.datetime64` dates as well. Anywhere that -`dateime.datetime` could be used, `numpy.datetime64` can be used. eg:: - - time = np.arange('2005-02-01', '2005-02-02', dtype='datetime64[h]') - plt.plot(time) + with PdfPages('multipage.pdf') as pdf: + # page 1 + plt.plot([2, 1, 3]) + pdf.savefig() + # page 2 + plt.cla() + plt.plot([3, 1, 2]) + pdf.savefig() -Writing animations with Pillow ------------------------------- -It is now possible to use Pillow as an animation writer. Supported output -formats are currently gif (Pillow>=3.4) and webp (Pillow>=5.0). Use e.g. as :: - from __future__ import division - - from matplotlib import pyplot as plt - from matplotlib.animation import FuncAnimation, PillowWriter - - fig, ax = plt.subplots() - line, = plt.plot([0, 1]) - - def animate(i): - line.set_ydata([0, i / 20]) - return [line] - - anim = FuncAnimation(fig, animate, 20, blit=True) - anim.save("movie.gif", writer=PillowWriter(fps=24)) - plt.show() +Pie charts are now circular by default +-------------------------------------- +We acknowledge that the majority of people do not like egg-shaped pies. +Therefore, an axes to which a pie chart is plotted will be set to have +equal aspect ratio by default. This ensures that the pie appears circular +independent on the axes size or units. To revert to the previous behaviour +set the axes' aspect ratio to automatic by using ``ax.set_aspect("auto")`` or +``plt.axis("auto")``. + +Add ``ax.get_gridspec`` to `.SubplotBase` +----------------------------------------- +New method `.SubplotBase.get_gridspec` is added so that users can +easily get the gridspec that went into making an axes: -Slider UI widget can snap to discrete values --------------------------------------------- + .. code:: -The slider UI widget can take the optional argument *valstep*. Doing so -forces the slider to take on only discrete values, starting from *valmin* and -counting up to *valmax* with steps of size *valstep*. + import matplotlib.pyplot as plt -If *closedmax==True*, then the slider will snap to *valmax* as well. + fig, axs = plt.subplots(3, 2) + gs = axs[0, -1].get_gridspec() + # remove the last column + for ax in axs[:,-1].flatten(): + ax.remove() + # make a subplot in last column that spans rows. + ax = fig.add_subplot(gs[:, -1]) + plt.show() -``capstyle`` and ``joinstyle`` attributes added to `Collection` ---------------------------------------------------------------- -The `Collection` class now has customizable ``capstyle`` and ``joinstyle`` -attributes. This allows the user for example to set the ``capstyle`` of -errorbars. +Axes titles will no longer overlap xaxis +---------------------------------------- +Previously an axes title had to be moved manually if an xaxis overlapped +(usually when the xaxis was put on the top of the axes). Now, the title +will be automatically moved above the xaxis and its decorators (including +the xlabel) if they are at the top. -*pad* kwarg added to ax.set_title ---------------------------------- +If desired, the title can still be placed manually. There is a slight kludge; +the algorithm checks if the y-position of the title is 1.0 (the default), +and moves if it is. If the user places the title in the default location +(i.e. ``ax.title.set_position(0.5, 1.0)``), the title will still be moved +above the xaxis. If the user wants to avoid this, they can +specify a number that is close (i.e. ``ax.title.set_position(0.5, 1.01)``) +and the title will not be moved via this algorithm. -The method `axes.set_title` now has a *pad* kwarg, that specifies the -distance from the top of an axes to where the title is drawn. The units -of *pad* is points, and the default is the value of the (already-existing) -``rcParams['axes.titlepad']``. -Comparison of 2 colors in Matplotlib +New convenience methods for GridSpec ------------------------------------ -As the colors in Matplotlib can be specified with a wide variety of ways, the -`matplotlib.colors.same_color` method has been added which checks if -two `~matplotlib.colors` are the same. - - -Autoscaling a polar plot snaps to the origin --------------------------------------------- - -Setting the limits automatically in a polar plot now snaps the radial limit -to zero if the automatic limit is nearby. This means plotting from zero doesn't -automatically scale to include small negative values on the radial axis. +There are new convenience methods for `.gridspec.GridSpec` and +`.gridspec.GridSpecFromSubplotSpec`. Instead of the former we can +now call `.Figure.add_gridspec` and for the latter `.SubplotSpec.subgridspec`. -The limits can still be set manually in the usual way using `set_ylim`. +.. code-block:: python + import matplotlib.pyplot as plt -PathLike support ----------------- - -On Python 3.6+, `~matplotlib.pyplot.savefig`, `~matplotlib.pyplot.imsave`, -`~matplotlib.pyplot.imread`, and animation writers now accept `os.PathLike`\s -as input. - - -`Axes.tick_params` can set gridline properties ----------------------------------------------- - -`Tick` objects hold gridlines as well as the tick mark and its label. -`Axis.set_tick_params`, `Axes.tick_params` and `pyplot.tick_params` -now have keyword arguments 'grid_color', 'grid_alpha', 'grid_linewidth', -and 'grid_linestyle' for overriding the defaults in `rcParams`: -'grid.color', etc. + fig = plt.figure() + gs0 = fig.add_gridspec(3, 1) + ax1 = fig.add_subplot(gs0[0]) + ax2 = fig.add_subplot(gs0[1]) + gssub = gs0[2].subgridspec(1, 3) + for i in range(3): + fig.add_subplot(gssub[0, i]) -`Axes.imshow` clips RGB values to the valid range +Figure has an `~.figure.Figure.add_artist` method ------------------------------------------------- -When `Axes.imshow` is passed an RGB or RGBA value with out-of-range -values, it now logs a warning and clips them to the valid range. -The old behaviour, wrapping back in to the range, often hid outliers -and made interpreting RGB images unreliable. - - -Properties in `matplotlibrc` to place xaxis and yaxis tick labels ------------------------------------------------------------------ - -Introducing four new boolean properties in `.matplotlibrc` for default -positions of xaxis and yaxis tick labels, namely, -`xtick.labeltop`, `xtick.labelbottom`, `ytick.labelright` and -`ytick.labelleft`. These can also be changed in rcParams. +A method `~.figure.Figure.add_artist` has been added to the +:class:`~.figure.Figure` class, which allows artists to be added directly +to a figure. E.g. :: + circ = plt.Circle((.7, .5), .05) + fig.add_artist(circ) -PGI bindings for gtk3 ---------------------- - -The GTK3 backends can now use PGI_ instead of PyGObject_. PGI is a fairly -incomplete binding for GObject, thus its use is not recommended; its main -benefit is its availability on Travis (thus allowing CI testing for the gtk3agg -and gtk3cairo backends). - -The binding selection rules are as follows: -- if ``gi`` has already been imported, use it; else -- if ``pgi`` has already been imported, use it; else -- if ``gi`` can be imported, use it; else -- if ``pgi`` can be imported, use it; else -- error out. - -Thus, to force usage of PGI when both bindings are installed, import it first. - -.. _PGI: https://pgi.readthedocs.io/en/latest/ -.. _PyGObject: http://pygobject.readthedocs.io/en/latest/# - - - -Cairo rendering for Qt, WX, and Tk canvases -------------------------------------------- - -The new ``Qt4Cairo``, ``Qt5Cairo``, ``WXCairo``, and ``TkCairo`` -backends allow Qt, Wx, and Tk canvases to use Cairo rendering instead of -Agg. - - -Added support for QT in new ToolManager ---------------------------------------- - -Now it is possible to use the ToolManager with Qt5 -For example - - import matplotlib - - matplotlib.use('QT5AGG') - matplotlib.rcParams['toolbar'] = 'toolmanager' - import matplotlib.pyplot as plt - - plt.plot([1,2,3]) - plt.show() - - -Treat the new Tool classes experimental for now, the API will likely change and perhaps the rcParam as well - -The main example `examples/user_interfaces/toolmanager_sgskip.py` shows more -details, just adjust the header to use QT instead of GTK3 - - - -TkAgg backend reworked to support PyPy --------------------------------------- - -PyPy_ can now plot using the TkAgg backend, supported on PyPy 5.9 -and greater (both PyPy for python 2.7 and PyPy for python 3.5). - -.. _PyPy: https:/www.pypy.org - - - -Python logging library used for debug output --------------------------------------------- - -Matplotlib has in the past (sporadically) used an internal -verbose-output reporter. This version converts those calls to using the -standard python `logging` library. - -Support for the old ``rcParams`` ``verbose.level`` and ``verbose.fileo`` is -dropped. - -The command-line options ``--verbose-helpful`` and ``--verbose-debug`` are -still accepted, but deprecated. They are now equivalent to setting -``logging.INFO`` and ``logging.DEBUG``. - -The logger's root name is ``matplotlib`` and can be accessed from programs -as:: - - import logging - mlog = logging.getLogger('matplotlib') - -Instructions for basic usage are in :ref:`troubleshooting-faq` and for -developers in :ref:`contributing`. - -.. _logging: https://docs.python.org/3/library/logging.html - -Improved `repr` for `Transform`\s ---------------------------------- - -`Transform`\s now indent their `repr`\s in a more legible manner: - -.. code-block:: ipython - - In [1]: l, = plt.plot([]); l.get_transform() - Out[1]: - CompositeGenericTransform( - TransformWrapper( - BlendedAffine2D( - IdentityTransform(), - IdentityTransform())), - CompositeGenericTransform( - BboxTransformFrom( - TransformedBbox( - Bbox(x0=-0.05500000000000001, y0=-0.05500000000000001, x1=0.05500000000000001, y1=0.05500000000000001), - TransformWrapper( - BlendedAffine2D( - IdentityTransform(), - IdentityTransform())))), - BboxTransformTo( - TransformedBbox( - Bbox(x0=0.125, y0=0.10999999999999999, x1=0.9, y1=0.88), - BboxTransformTo( - TransformedBbox( - Bbox(x0=0.0, y0=0.0, x1=6.4, y1=4.8), - Affine2D( - [[ 100. 0. 0.] - [ 0. 100. 0.] - [ 0. 0. 1.]]))))))) +In case the added artist has no transform set previously, it will be set to +the figure transform (``fig.transFigure``). +This new method may be useful for adding artists to figures without axes or to +easily position static elements in figure coordinates. +``:math:`` directive renamed to ``:mathmpl:`` +--------------------------------------------- +The ``:math:`` rst role provided by `matplotlib.sphinxext.mathmpl` has been +renamed to ``:mathmpl:`` to avoid conflicting with the ``:math:`` role that +Sphinx 1.8 provides by default. (``:mathmpl:`` uses Matplotlib to render math +expressions to images embedded in html, whereas Sphinx uses MathJax.) +When using Sphinx<1.8, both names (``:math:`` and ``:mathmpl:``) remain +available for backcompatibility. +================== Previous Whats New ================== diff --git a/examples/README b/examples/README index 1e89277d289b..746f2e334664 100644 --- a/examples/README +++ b/examples/README @@ -16,8 +16,6 @@ Below is a brief description of the different directories found here: * animation - Dynamic plots, see the documentation at http://matplotlib.org/api/animation_api.html - * api - working with the Matplotlib API directly. - * axes_grid1 - Examples related to the axes_grid1 toolkit. * axisartist - Examples related to the axisartist toolkit. diff --git a/examples/README.txt b/examples/README.txt index 72ae0134857e..f2331df7246a 100644 --- a/examples/README.txt +++ b/examples/README.txt @@ -2,6 +2,7 @@ .. _gallery: +======= Gallery ======= diff --git a/examples/api/README.txt b/examples/api/README.txt deleted file mode 100644 index c0359dc24319..000000000000 --- a/examples/api/README.txt +++ /dev/null @@ -1,18 +0,0 @@ -.. _api_examples: - -Matplotlib API -============== - -These examples use the Matplotlib api rather than the pylab/pyplot -procedural state machine. For robust, production level scripts, or -for applications or web application servers, we recommend you use the -Matplotlib API directly as it gives you the maximum control over your -figures, axes and plottng commands. - -The example agg_oo.py is the simplest example of using the Agg backend -which is readily ported to other output formats. This example is a -good starting point if your are a web application developer. Many of -the other examples in this directory use ``matplotlib.pyplot`` just to -create the figure and show calls, and use the API for everything else. -This is a good solution for production quality scripts. For full -fledged GUI applications, see the user_interfaces examples. diff --git a/examples/api/agg_oo_sgskip.py b/examples/api/agg_oo_sgskip.py deleted file mode 100644 index 6b4d463ddc85..000000000000 --- a/examples/api/agg_oo_sgskip.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -============================= -The object-oriented interface -============================= - -A pure OO (look Ma, no pyplot!) example using the agg backend. -""" - -from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas -from matplotlib.figure import Figure - -fig = Figure() -# A canvas must be manually attached to the figure (pyplot would automatically -# do it). This is done by instantiating the canvas with the figure as -# argument. -FigureCanvas(fig) -ax = fig.add_subplot(111) -ax.plot([1, 2, 3]) -ax.set_title('hi mom') -ax.grid(True) -ax.set_xlabel('time') -ax.set_ylabel('volts') -fig.savefig('test') diff --git a/examples/axes_grid1/demo_anchored_direction_arrows.py b/examples/axes_grid1/demo_anchored_direction_arrows.py new file mode 100644 index 000000000000..d9b1eb2cc584 --- /dev/null +++ b/examples/axes_grid1/demo_anchored_direction_arrows.py @@ -0,0 +1,78 @@ +""" +============================= +Demo Anchored Direction Arrow +============================= + +""" +import matplotlib.pyplot as plt +import numpy as np +from mpl_toolkits.axes_grid1.anchored_artists import AnchoredDirectionArrows +import matplotlib.font_manager as fm + +fig, ax = plt.subplots() +ax.imshow(np.random.random((10, 10))) + +# Simple example +simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y') +ax.add_artist(simple_arrow) + +# High contrast arrow +high_contrast_part_1 = AnchoredDirectionArrows( + ax.transAxes, + '111', r'11$\overline{2}$', + loc='upper right', + arrow_props={'ec': 'w', 'fc': 'none', 'alpha': 1, + 'lw': 2} + ) +ax.add_artist(high_contrast_part_1) + +high_contrast_part_2 = AnchoredDirectionArrows( + ax.transAxes, + '111', r'11$\overline{2}$', + loc='upper right', + arrow_props={'ec': 'none', 'fc': 'k'}, + text_props={'ec': 'w', 'fc': 'k', 'lw': 0.4} + ) +ax.add_artist(high_contrast_part_2) + +# Rotated arrow +fontprops = fm.FontProperties(family='serif') + +roatated_arrow = AnchoredDirectionArrows( + ax.transAxes, + '30', '120', + loc='center', + color='w', + angle=30, + fontproperties=fontprops + ) +ax.add_artist(roatated_arrow) + +# Altering arrow directions +a1 = AnchoredDirectionArrows( + ax.transAxes, 'A', 'B', loc='lower center', + length=-0.15, + sep_x=0.03, sep_y=0.03, + color='r' + ) +ax.add_artist(a1) + +a2 = AnchoredDirectionArrows( + ax.transAxes, 'A', ' B', loc='lower left', + aspect_ratio=-1, + sep_x=0.01, sep_y=-0.02, + color='orange' + ) +ax.add_artist(a2) + + +a3 = AnchoredDirectionArrows( + ax.transAxes, ' A', 'B', loc='lower right', + length=-0.15, + aspect_ratio=-1, + sep_y=-0.1, sep_x=0.04, + color='cyan' + ) +ax.add_artist(a3) + +plt.show() diff --git a/examples/axes_grid1/demo_axes_divider.py b/examples/axes_grid1/demo_axes_divider.py index 92df89ca198a..5f8f440af562 100644 --- a/examples/axes_grid1/demo_axes_divider.py +++ b/examples/axes_grid1/demo_axes_divider.py @@ -28,16 +28,16 @@ def demo_simple_image(ax): def demo_locatable_axes_hard(fig1): - from mpl_toolkits.axes_grid1 \ - import SubplotDivider, LocatableAxes, Size + from mpl_toolkits.axes_grid1 import SubplotDivider, Size + from mpl_toolkits.axes_grid1.mpl_axes import Axes divider = SubplotDivider(fig1, 2, 2, 2, aspect=True) # axes for image - ax = LocatableAxes(fig1, divider.get_position()) + ax = Axes(fig1, divider.get_position()) # axes for colorbar - ax_cb = LocatableAxes(fig1, divider.get_position()) + ax_cb = Axes(fig1, divider.get_position()) h = [Size.AxesX(ax), # main axes Size.Fixed(0.05), # padding, 0.1 inch @@ -126,7 +126,6 @@ def demo(): ax = fig1.add_subplot(2, 2, 4) demo_images_side_by_side(ax) - plt.draw() plt.show() diff --git a/examples/axes_grid1/demo_axes_grid.py b/examples/axes_grid1/demo_axes_grid.py index 44a70a90c931..fafac4f1ea49 100644 --- a/examples/axes_grid1/demo_axes_grid.py +++ b/examples/axes_grid1/demo_axes_grid.py @@ -135,5 +135,4 @@ def demo_grid_with_each_cbar_labelled(fig): demo_grid_with_each_cbar(F) demo_grid_with_each_cbar_labelled(F) - plt.draw() plt.show() diff --git a/examples/axes_grid1/demo_axes_grid2.py b/examples/axes_grid1/demo_axes_grid2.py index 26752dd0d5a4..ed1af8529b98 100644 --- a/examples/axes_grid1/demo_axes_grid2.py +++ b/examples/axes_grid1/demo_axes_grid2.py @@ -60,7 +60,7 @@ def add_inner_title(ax, title, loc, size=None, **kwargs): ax.cax.colorbar(im) for ax, im_title in zip(grid, ["Image 1", "Image 2", "Image 3"]): - t = add_inner_title(ax, im_title, loc=3) + t = add_inner_title(ax, im_title, loc='lower left') t.patch.set_alpha(0.5) for ax, z in zip(grid, ZS): @@ -109,12 +109,11 @@ def add_inner_title(ax, title, loc, size=None, **kwargs): ax.cax.toggle_label(True) for ax, im_title in zip(grid2, ["(a)", "(b)", "(c)"]): - t = add_inner_title(ax, im_title, loc=2) + t = add_inner_title(ax, im_title, loc='upper left') t.patch.set_ec("none") t.patch.set_alpha(0.5) grid2[0].set_xticks([-2, 0]) grid2[0].set_yticks([-2, 0, 2]) - plt.draw() plt.show() diff --git a/examples/axes_grid1/demo_colorbar_of_inset_axes.py b/examples/axes_grid1/demo_colorbar_of_inset_axes.py index 88069c8f49cf..7d330a4de58e 100644 --- a/examples/axes_grid1/demo_colorbar_of_inset_axes.py +++ b/examples/axes_grid1/demo_colorbar_of_inset_axes.py @@ -28,7 +28,7 @@ def get_demo_image(): ylim=(-20, 5)) -axins = zoomed_inset_axes(ax, 2, loc=2) # zoom = 6 +axins = zoomed_inset_axes(ax, zoom=2, loc='upper left') im = axins.imshow(Z, extent=extent, interpolation="nearest", origin="lower") @@ -40,7 +40,7 @@ def get_demo_image(): cax = inset_axes(axins, width="5%", # width = 10% of parent_bbox width height="100%", # height : 50% - loc=3, + loc='lower left', bbox_to_anchor=(1.05, 0., 1, 1), bbox_transform=axins.transAxes, borderpad=0, diff --git a/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/examples/axes_grid1/demo_colorbar_with_inset_locator.py index cc3b221691af..79d2001c1ea6 100644 --- a/examples/axes_grid1/demo_colorbar_with_inset_locator.py +++ b/examples/axes_grid1/demo_colorbar_with_inset_locator.py @@ -13,7 +13,7 @@ axins1 = inset_axes(ax1, width="50%", # width = 10% of parent_bbox width height="5%", # height : 50% - loc=1) + loc='upper right') im1 = ax1.imshow([[1, 2], [2, 3]]) plt.colorbar(im1, cax=axins1, orientation="horizontal", ticks=[1, 2, 3]) @@ -22,7 +22,7 @@ axins = inset_axes(ax2, width="5%", # width = 10% of parent_bbox width height="50%", # height : 50% - loc=3, + loc='lower left', bbox_to_anchor=(1.05, 0., 1, 1), bbox_transform=ax2.transAxes, borderpad=0, @@ -35,5 +35,4 @@ im = ax2.imshow([[1, 2], [2, 3]]) plt.colorbar(im, cax=axins, ticks=[1, 2, 3]) -plt.draw() plt.show() diff --git a/examples/axes_grid1/demo_edge_colorbar.py b/examples/axes_grid1/demo_edge_colorbar.py index 3e2b9d98b2e7..b7540b261226 100644 --- a/examples/axes_grid1/demo_edge_colorbar.py +++ b/examples/axes_grid1/demo_edge_colorbar.py @@ -90,5 +90,4 @@ def demo_right_cbar(fig): demo_bottom_cbar(F) demo_right_cbar(F) - plt.draw() plt.show() diff --git a/examples/axes_grid1/demo_fixed_size_axes.py b/examples/axes_grid1/demo_fixed_size_axes.py index a96df03f858f..b9153b521e2e 100644 --- a/examples/axes_grid1/demo_fixed_size_axes.py +++ b/examples/axes_grid1/demo_fixed_size_axes.py @@ -6,7 +6,8 @@ """ import matplotlib.pyplot as plt -from mpl_toolkits.axes_grid1 import Divider, LocatableAxes, Size +from mpl_toolkits.axes_grid1 import Divider, Size +from mpl_toolkits.axes_grid1.mpl_axes import Axes def demo_fixed_size_axes(): @@ -20,7 +21,7 @@ def demo_fixed_size_axes(): divider = Divider(fig1, (0.0, 0.0, 1., 1.), h, v, aspect=False) # the width and height of the rectangle is ignored. - ax = LocatableAxes(fig1, divider.get_position()) + ax = Axes(fig1, divider.get_position()) ax.set_axes_locator(divider.new_locator(nx=1, ny=1)) fig1.add_axes(ax) @@ -39,7 +40,7 @@ def demo_fixed_pad_axes(): divider = Divider(fig, (0.0, 0.0, 1., 1.), h, v, aspect=False) # the width and height of the rectangle is ignored. - ax = LocatableAxes(fig, divider.get_position()) + ax = Axes(fig, divider.get_position()) ax.set_axes_locator(divider.new_locator(nx=1, ny=1)) fig.add_axes(ax) diff --git a/examples/axes_grid1/inset_locator_demo.py b/examples/axes_grid1/inset_locator_demo.py index 878d3a5b1b04..c2772dfa8606 100644 --- a/examples/axes_grid1/inset_locator_demo.py +++ b/examples/axes_grid1/inset_locator_demo.py @@ -6,11 +6,11 @@ """ ############################################################################### -# The `.inset_locator`'s `~.inset_axes` allows to easily place insets in the -# corners of the axes by specifying a width and height and optionally -# a location (loc) which accepts locations as codes, similar to -# `~matplotlib.axes.Axes.legend`. -# By default, the inset is offset by some points from the axes - this is +# The `.inset_locator`'s `~.inset_locator.inset_axes` allows +# easily placing insets in the corners of the axes by specifying a width and +# height and optionally a location (loc) that accepts locations as codes, +# similar to `~matplotlib.axes.Axes.legend`. +# By default, the inset is offset by some points from the axes, # controlled via the `borderpad` parameter. import matplotlib.pyplot as plt diff --git a/examples/axes_grid1/parasite_simple2.py b/examples/axes_grid1/parasite_simple2.py index 4b10b84e7bfa..cbf768e892c7 100644 --- a/examples/axes_grid1/parasite_simple2.py +++ b/examples/axes_grid1/parasite_simple2.py @@ -44,5 +44,4 @@ ax_kms.set_ylim(950, 3100) # xlim and ylim of ax_pms will be automatically adjusted. -plt.draw() plt.show() diff --git a/examples/axes_grid1/scatter_hist_locatable_axes.py b/examples/axes_grid1/scatter_hist_locatable_axes.py index 0a2621460f57..5786a382e31a 100644 --- a/examples/axes_grid1/scatter_hist_locatable_axes.py +++ b/examples/axes_grid1/scatter_hist_locatable_axes.py @@ -51,5 +51,4 @@ axHisty.set_xticks([0, 50, 100]) -plt.draw() plt.show() diff --git a/examples/axes_grid1/simple_anchored_artists.py b/examples/axes_grid1/simple_anchored_artists.py index bbfd245122d8..9a8ae92335e9 100644 --- a/examples/axes_grid1/simple_anchored_artists.py +++ b/examples/axes_grid1/simple_anchored_artists.py @@ -8,19 +8,24 @@ An implementation of a similar figure, but without use of the toolkit, can be found in :doc:`/gallery/misc/anchored_artists`. """ + import matplotlib.pyplot as plt def draw_text(ax): + """ + Draw two text-boxes, anchored by different corners to the upper-left + corner of the figure. + """ from matplotlib.offsetbox import AnchoredText at = AnchoredText("Figure 1a", - loc=2, prop=dict(size=8), frameon=True, + loc='upper left', prop=dict(size=8), frameon=True, ) at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") ax.add_artist(at) at2 = AnchoredText("Figure 1(b)", - loc=3, prop=dict(size=8), frameon=True, + loc='lower left', prop=dict(size=8), frameon=True, bbox_to_anchor=(0., 1.), bbox_transform=ax.transAxes ) @@ -28,45 +33,52 @@ def draw_text(ax): ax.add_artist(at2) -def draw_circle(ax): # circle in the canvas coordinate +def draw_circle(ax): + """ + Draw a circle in axis coordinates + """ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredDrawingArea from matplotlib.patches import Circle ada = AnchoredDrawingArea(20, 20, 0, 0, - loc=1, pad=0., frameon=False) + loc='upper right', pad=0., frameon=False) p = Circle((10, 10), 10) ada.da.add_artist(p) ax.add_artist(ada) def draw_ellipse(ax): + """ + Draw an ellipse of width=0.1, height=0.15 in data coordinates + """ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredEllipse - # draw an ellipse of width=0.1, height=0.15 in the data coordinate ae = AnchoredEllipse(ax.transData, width=0.1, height=0.15, angle=0., - loc=3, pad=0.5, borderpad=0.4, frameon=True) + loc='lower left', pad=0.5, borderpad=0.4, + frameon=True) ax.add_artist(ae) def draw_sizebar(ax): + """ + Draw a horizontal bar with length of 0.1 in data coordinates, + with a fixed label underneath. + """ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar - # draw a horizontal bar with length of 0.1 in Data coordinate - # (ax.transData) with a label underneath. asb = AnchoredSizeBar(ax.transData, 0.1, r"1$^{\prime}$", - loc=8, + loc='lower center', pad=0.1, borderpad=0.5, sep=5, frameon=False) ax.add_artist(asb) -if 1: - ax = plt.gca() - ax.set_aspect(1.) +ax = plt.gca() +ax.set_aspect(1.) - draw_text(ax) - draw_circle(ax) - draw_ellipse(ax) - draw_sizebar(ax) +draw_text(ax) +draw_circle(ax) +draw_ellipse(ax) +draw_sizebar(ax) - plt.show() +plt.show() diff --git a/examples/axes_grid1/simple_axesgrid2.py b/examples/axes_grid1/simple_axesgrid2.py index 8cd11953cfd1..51783b58dc3c 100644 --- a/examples/axes_grid1/simple_axesgrid2.py +++ b/examples/axes_grid1/simple_axesgrid2.py @@ -35,5 +35,4 @@ def get_demo_image(): ax.imshow(im, origin="lower", vmin=vmin, vmax=vmax, interpolation="nearest") -plt.draw() plt.show() diff --git a/examples/axes_grid1/simple_axisline4.py b/examples/axes_grid1/simple_axisline4.py index afdecccfc057..91b76cf3e956 100644 --- a/examples/axes_grid1/simple_axisline4.py +++ b/examples/axes_grid1/simple_axisline4.py @@ -20,5 +20,4 @@ ax2.axis["right"].major_ticklabels.set_visible(False) ax2.axis["top"].major_ticklabels.set_visible(True) -plt.draw() plt.show() diff --git a/examples/axisartist/demo_curvelinear_grid.py b/examples/axisartist/demo_curvelinear_grid.py index 30a3705a7c62..4dd02b85ce0d 100644 --- a/examples/axisartist/demo_curvelinear_grid.py +++ b/examples/axisartist/demo_curvelinear_grid.py @@ -137,5 +137,4 @@ def curvelinear_test2(fig): curvelinear_test1(fig) curvelinear_test2(fig) - plt.draw() plt.show() diff --git a/examples/axisartist/demo_parasite_axes.py b/examples/axisartist/demo_parasite_axes.py index 022f195a9e3a..d7e852f96fe6 100644 --- a/examples/axisartist/demo_parasite_axes.py +++ b/examples/axisartist/demo_parasite_axes.py @@ -3,6 +3,17 @@ Demo Parasite Axes ================== +Create a parasite axes. Such axes would share the x scale with a host axes, +but show a different scale in y direction. + +Note that this approach uses the `~mpl_toolkits.axes_grid1.parasite_axes`\' +`~.mpl_toolkits.axes_grid1.parasite_axes.HostAxes` and +`~.mpl_toolkits.axes_grid1.parasite_axes.ParasiteAxes`. An alternative +approach using the :ref:`toolkit_axesgrid1-index` and +:ref:`toolkit_axisartist-index` +is found in the :doc:`/gallery/axisartist/demo_parasite_axes2` example. +An alternative approach using the usual matplotlib subplots is shown in +the :doc:`/gallery/ticks_and_spines/multiple_yaxis_with_spines` example. """ from mpl_toolkits.axisartist.parasite_axes import HostAxes, ParasiteAxes import matplotlib.pyplot as plt diff --git a/examples/axisartist/demo_parasite_axes2.py b/examples/axisartist/demo_parasite_axes2.py index bb98d040fdd0..77bb674c272a 100644 --- a/examples/axisartist/demo_parasite_axes2.py +++ b/examples/axisartist/demo_parasite_axes2.py @@ -5,12 +5,22 @@ Parasite axis demo -The following code is an example of a parasite axis. It aims to show a user how +The following code is an example of a parasite axis. It aims to show how to plot multiple different values onto one single plot. Notice how in this example, par1 and par2 are both calling twinx meaning both are tied directly to the x-axis. From there, each of those two axis can behave separately from the each other, meaning they can take on separate values from themselves as well as the x-axis. + +Note that this approach uses the `mpl_toolkits.axes_grid1.parasite_axes`\' +`~mpl_toolkits.axes_grid1.parasite_axes.host_subplot` and +`mpl_toolkits.axisartist.axislines.Axes`. An alternative approach using the +`~mpl_toolkits.axes_grid1.parasite_axes`\'s +`~.mpl_toolkits.axes_grid1.parasite_axes.HostAxes` and +`~.mpl_toolkits.axes_grid1.parasite_axes.ParasiteAxes` is the +:doc:`/gallery/axisartist/demo_parasite_axes` example. +An alternative approach using the usual matplotlib subplots is shown in +the :doc:`/gallery/ticks_and_spines/multiple_yaxis_with_spines` example. """ from mpl_toolkits.axes_grid1 import host_subplot import mpl_toolkits.axisartist as AA @@ -52,5 +62,4 @@ par1.axis["right"].label.set_color(p2.get_color()) par2.axis["right"].label.set_color(p3.get_color()) -plt.draw() plt.show() diff --git a/examples/color/color_cycler.py b/examples/color/color_cycler.py index f4aba2018706..fe1ee0a49454 100644 --- a/examples/color/color_cycler.py +++ b/examples/color/color_cycler.py @@ -8,11 +8,10 @@ This example demonstrates two different APIs: - 1. Setting the default - :ref:`rc parameter` - specifying the property cycle. This affects all subsequent axes - (but not axes already created). - 2. Setting the property cycle for a single pair of axes. +1. Setting the default :doc:`rc parameter` + specifying the property cycle. This affects all subsequent axes (but not + axes already created). +2. Setting the property cycle for a single pair of axes. """ from cycler import cycler import numpy as np @@ -28,7 +27,7 @@ plt.rc('lines', linewidth=4) plt.rc('axes', prop_cycle=(cycler(color=['r', 'g', 'b', 'y']) + cycler(linestyle=['-', '--', ':', '-.']))) -fig, (ax0, ax1) = plt.subplots(nrows=2) +fig, (ax0, ax1) = plt.subplots(nrows=2, constrained_layout=True) ax0.plot(yy) ax0.set_title('Set default color cycle to rgby') @@ -42,8 +41,6 @@ ax1.plot(yy) ax1.set_title('Set axes color cycle to cmyk') -# Tweak spacing between subplots to prevent labels from overlapping -fig.subplots_adjust(hspace=0.3) plt.show() ############################################################################# diff --git a/examples/color/colorbar_basics.py b/examples/color/colorbar_basics.py index 6ba4d00477b6..df88c02dd51e 100644 --- a/examples/color/colorbar_basics.py +++ b/examples/color/colorbar_basics.py @@ -20,7 +20,7 @@ Zpos = np.ma.masked_less(Z, 0) Zneg = np.ma.masked_greater(Z, 0) -fig, (ax1, ax2) = plt.subplots(figsize=(8, 3), ncols=2) +fig, (ax1, ax2, ax3) = plt.subplots(figsize=(13, 3), ncols=3) # plot just the positive data and save the # color "mappable" object returned by ax1.imshow @@ -35,6 +35,13 @@ neg = ax2.imshow(Zneg, cmap='Reds_r', interpolation='none') fig.colorbar(neg, ax=ax2) +# Plot both positive and negative values betwen +/- 1.2 +pos_neg_clipped = ax3.imshow(Z, cmap='RdBu', vmin=-1.2, vmax=1.2, + interpolation='none') +# Add minorticks on the colorbar to make it easy to read the +# values off the colorbar. +cbar = fig.colorbar(pos_neg_clipped, ax=ax3, extend='both') +cbar.minorticks_on() plt.show() ############################################################################# @@ -48,7 +55,10 @@ # in this example: import matplotlib +import matplotlib.colorbar matplotlib.axes.Axes.imshow matplotlib.pyplot.imshow matplotlib.figure.Figure.colorbar matplotlib.pyplot.colorbar +matplotlib.colorbar.Colorbar.minorticks_on +matplotlib.colorbar.Colorbar.minorticks_off diff --git a/examples/color/colormap_reference.py b/examples/color/colormap_reference.py index 8e4d3d14f7ee..afb578ffad1b 100644 --- a/examples/color/colormap_reference.py +++ b/examples/color/colormap_reference.py @@ -29,32 +29,34 @@ ('Diverging', [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), + ('Cyclic', ['twilight', 'twilight_shifted', 'hsv']), ('Qualitative', [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c']), ('Miscellaneous', [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', - 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv', + 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] -nrows = max(len(cmap_list) for cmap_category, cmap_list in cmaps) gradient = np.linspace(0, 1, 256) gradient = np.vstack((gradient, gradient)) -def plot_color_gradients(cmap_category, cmap_list, nrows): - fig, axes = plt.subplots(nrows=nrows) - fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99) +def plot_color_gradients(cmap_category, cmap_list): + # Create figure and adjust figure height to number of colormaps + nrows = len(cmap_list) + figh = 0.35 + 0.15 + (nrows + (nrows-1)*0.1)*0.22 + fig, axes = plt.subplots(nrows=nrows, figsize=(6.4, figh)) + fig.subplots_adjust(top=1-.35/figh, bottom=.15/figh, left=0.2, right=0.99) + axes[0].set_title(cmap_category + ' colormaps', fontsize=14) for ax, name in zip(axes, cmap_list): ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(name)) - pos = list(ax.get_position().bounds) - x_text = pos[0] - 0.01 - y_text = pos[1] + pos[3]/2. - fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10) + ax.text(-.01, .5, name, va='center', ha='right', fontsize=10, + transform=ax.transAxes) # Turn off *all* ticks & spines, not just the ones with colormaps. for ax in axes: @@ -62,7 +64,7 @@ def plot_color_gradients(cmap_category, cmap_list, nrows): for cmap_category, cmap_list in cmaps: - plot_color_gradients(cmap_category, cmap_list, nrows) + plot_color_gradients(cmap_category, cmap_list) plt.show() diff --git a/examples/color/named_colors.py b/examples/color/named_colors.py index 062dd4c54fff..5a16c2d813f1 100644 --- a/examples/color/named_colors.py +++ b/examples/color/named_colors.py @@ -11,7 +11,6 @@ * the `matplotlib.colors` API; * the :doc:`/gallery/color/color_demo`. """ -from __future__ import division import matplotlib.pyplot as plt from matplotlib import colors as mcolors diff --git a/examples/event_handling/README.txt b/examples/event_handling/README.txt index 0f99de02dace..165cb66cb15a 100644 --- a/examples/event_handling/README.txt +++ b/examples/event_handling/README.txt @@ -1,13 +1,12 @@ .. _event_handling_examples: -Event Handling +Event handling ============== -Matplotlib supports event handling with a GUI neutral event model, so -you can connect to Matplotlib events without knowledge of what user -interface Matplotlib will ultimately be plugged in to. This has two -advantages: the code you write will be more portable, and Matplotlib -events are aware of things like data coordinate space and which axes -the event occurs in so you don't have to mess with low level -transformation details to go from canvas space to data space. Object -picking examples are also included. +Matplotlib supports :doc:`event handling` with a GUI +neutral event model, so you can connect to Matplotlib events without knowledge +of what user interface Matplotlib will ultimately be plugged in to. This has +two advantages: the code you write will be more portable, and Matplotlib events +are aware of things like data coordinate space and which axes the event occurs +in so you don't have to mess with low level transformation details to go from +canvas space to data space. Object picking examples are also included. diff --git a/examples/event_handling/close_event.py b/examples/event_handling/close_event.py index c7b7fbd56c7d..7613ec45bec9 100644 --- a/examples/event_handling/close_event.py +++ b/examples/event_handling/close_event.py @@ -5,7 +5,6 @@ Example to show connecting events that occur when the figure closes. """ -from __future__ import print_function import matplotlib.pyplot as plt diff --git a/examples/event_handling/coords_demo.py b/examples/event_handling/coords_demo.py index 89ee85fc4d21..249c318cb23e 100644 --- a/examples/event_handling/coords_demo.py +++ b/examples/event_handling/coords_demo.py @@ -6,7 +6,6 @@ An example of how to interact with the plotting canvas by connecting to move and click events """ -from __future__ import print_function import sys import matplotlib.pyplot as plt import numpy as np diff --git a/examples/event_handling/figure_axes_enter_leave.py b/examples/event_handling/figure_axes_enter_leave.py index 703e72058c73..b1c81b6dd5ba 100644 --- a/examples/event_handling/figure_axes_enter_leave.py +++ b/examples/event_handling/figure_axes_enter_leave.py @@ -6,7 +6,6 @@ Illustrate the figure and axes enter and leave events by changing the frame colors on enter and leave """ -from __future__ import print_function import matplotlib.pyplot as plt diff --git a/examples/event_handling/ginput_demo_sgskip.py b/examples/event_handling/ginput_demo_sgskip.py index 77227032b16a..482cdbd5356a 100644 --- a/examples/event_handling/ginput_demo_sgskip.py +++ b/examples/event_handling/ginput_demo_sgskip.py @@ -7,7 +7,6 @@ """ -from __future__ import print_function import matplotlib.pyplot as plt import numpy as np diff --git a/examples/event_handling/ginput_manual_clabel_sgskip.py b/examples/event_handling/ginput_manual_clabel_sgskip.py index 25ee40e4eb23..96104cace49d 100644 --- a/examples/event_handling/ginput_manual_clabel_sgskip.py +++ b/examples/event_handling/ginput_manual_clabel_sgskip.py @@ -7,20 +7,16 @@ waitforbuttonpress and manual clabel placement. This script must be run interactively using a backend that has a -graphical user interface (for example, using GTKAgg backend, but not +graphical user interface (for example, using GTK3Agg backend, but not PS backend). See also ginput_demo.py """ -from __future__ import print_function - import time -import matplotlib + import numpy as np -import matplotlib.cm as cm -import matplotlib.mlab as mlab import matplotlib.pyplot as plt @@ -41,8 +37,7 @@ def tellme(s): plt.waitforbuttonpress() -happy = False -while not happy: +while True: pts = [] while len(pts) < 3: tellme('Select 3 corners with mouse') @@ -55,12 +50,12 @@ def tellme(s): tellme('Happy? Key click for yes, mouse click for no') - happy = plt.waitforbuttonpress() + if plt.waitforbuttonpress(): + break # Get rid of fill - if not happy: - for p in ph: - p.remove() + for p in ph: + p.remove() ################################################## @@ -89,13 +84,11 @@ def f(x, y, pts): tellme('Now do a nested zoom, click to begin') plt.waitforbuttonpress() -happy = False -while not happy: +while True: tellme('Select two corners of zoom, middle mouse button to finish') pts = np.asarray(plt.ginput(2, timeout=-1)) - happy = len(pts) < 2 - if happy: + if len(pts) < 2: break pts = np.sort(pts, axis=0) diff --git a/examples/event_handling/image_slices_viewer.py b/examples/event_handling/image_slices_viewer.py index 3409c5ee28b8..2816a802c7f8 100644 --- a/examples/event_handling/image_slices_viewer.py +++ b/examples/event_handling/image_slices_viewer.py @@ -5,7 +5,6 @@ Scroll through 2D image slices of a 3D array. """ -from __future__ import print_function import numpy as np import matplotlib.pyplot as plt diff --git a/examples/event_handling/keypress_demo.py b/examples/event_handling/keypress_demo.py index f0380e11ff3f..149cb1ba3103 100644 --- a/examples/event_handling/keypress_demo.py +++ b/examples/event_handling/keypress_demo.py @@ -5,7 +5,6 @@ Show how to connect to keypress events """ -from __future__ import print_function import sys import numpy as np import matplotlib.pyplot as plt diff --git a/examples/event_handling/pick_event_demo.py b/examples/event_handling/pick_event_demo.py index 22770d33f253..4f2a924e1d23 100644 --- a/examples/event_handling/pick_event_demo.py +++ b/examples/event_handling/pick_event_demo.py @@ -66,7 +66,6 @@ def pick_handler(event): The examples below illustrate each of these methods. """ -from __future__ import print_function import matplotlib.pyplot as plt from matplotlib.lines import Line2D from matplotlib.patches import Rectangle diff --git a/examples/event_handling/pipong.py b/examples/event_handling/pipong.py index c68abac61a7f..c7a925a7db9f 100644 --- a/examples/event_handling/pipong.py +++ b/examples/event_handling/pipong.py @@ -8,7 +8,6 @@ pipong.py was written by Paul Ivanov """ -from __future__ import print_function import numpy as np import matplotlib.pyplot as plt diff --git a/examples/event_handling/pong_sgskip.py b/examples/event_handling/pong_sgskip.py index e07f037c2fda..c7ddb8abe5fb 100644 --- a/examples/event_handling/pong_sgskip.py +++ b/examples/event_handling/pong_sgskip.py @@ -10,7 +10,6 @@ This example requires :download:`pipong.py ` """ -from __future__ import print_function, division import time diff --git a/examples/frontpage/3D.py b/examples/frontpage/3D.py index d08b52f54b24..ee93026ed06e 100644 --- a/examples/frontpage/3D.py +++ b/examples/frontpage/3D.py @@ -6,7 +6,9 @@ This example reproduces the frontpage 3D example. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + from matplotlib import cbook from matplotlib import cm from matplotlib.colors import LightSource diff --git a/examples/images_contours_and_fields/contour_demo.py b/examples/images_contours_and_fields/contour_demo.py index 9cdc568597bb..bfbffe41eb45 100644 --- a/examples/images_contours_and_fields/contour_demo.py +++ b/examples/images_contours_and_fields/contour_demo.py @@ -97,8 +97,7 @@ plt.setp(zc, linewidth=4) ax.clabel(CS, levels[1::2], # label every second level - inline=1, fmt='%1.1f', - cmap='flag', fontsize=14) + inline=1, fmt='%1.1f', fontsize=14) # make a colorbar for the contour lines CB = fig.colorbar(CS, shrink=0.8, extend='both') diff --git a/examples/images_contours_and_fields/contour_label_demo.py b/examples/images_contours_and_fields/contour_label_demo.py index af26ab997c87..f09a7f6a141d 100644 --- a/examples/images_contours_and_fields/contour_label_demo.py +++ b/examples/images_contours_and_fields/contour_label_demo.py @@ -12,7 +12,6 @@ import matplotlib import numpy as np -import matplotlib.cm as cm import matplotlib.ticker as ticker import matplotlib.pyplot as plt diff --git a/examples/images_contours_and_fields/contourf_demo.py b/examples/images_contours_and_fields/contourf_demo.py index 04e3dd6bdd0e..e6a145063921 100644 --- a/examples/images_contours_and_fields/contourf_demo.py +++ b/examples/images_contours_and_fields/contourf_demo.py @@ -38,7 +38,7 @@ # occur on nice boundaries, but we do it here for purposes # of illustration. -fig1, ax2 = plt.subplots() +fig1, ax2 = plt.subplots(constrained_layout=True) CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.bone, origin=origin) # Note that in the following, we explicitly pass in a subset of @@ -58,7 +58,7 @@ # Add the contour line levels to the colorbar cbar.add_lines(CS2) -fig2, ax2 = plt.subplots() +fig2, ax2 = plt.subplots(constrained_layout=True) # Now make a contour plot with the levels specified, # and with the colormap generated automatically from a list # of colors. @@ -95,12 +95,11 @@ # no effect: # cmap.set_bad("red") -fig3, axs = plt.subplots(2, 2) -fig3.subplots_adjust(hspace=0.3) +fig, axs = plt.subplots(2, 2, constrained_layout=True) for ax, extend in zip(axs.ravel(), extends): cs = ax.contourf(X, Y, Z, levels, cmap=cmap, extend=extend, origin=origin) - fig3.colorbar(cs, ax=ax, shrink=0.9) + fig.colorbar(cs, ax=ax, shrink=0.9) ax.set_title("extend = %s" % extend) ax.locator_params(nbins=4) diff --git a/examples/images_contours_and_fields/contourf_log.py b/examples/images_contours_and_fields/contourf_log.py index d7696e516676..f728946dae6c 100644 --- a/examples/images_contours_and_fields/contourf_log.py +++ b/examples/images_contours_and_fields/contourf_log.py @@ -42,9 +42,7 @@ # lev_exp = np.arange(np.floor(np.log10(z.min())-1), # np.ceil(np.log10(z.max())+1)) # levs = np.power(10, lev_exp) -# cs = P.contourf(X, Y, z, levs, norm=colors.LogNorm()) - -# The 'extend' kwarg does not work yet with a log scale. +# cs = ax.contourf(X, Y, z, levs, norm=colors.LogNorm()) cbar = fig.colorbar(cs) diff --git a/examples/images_contours_and_fields/custom_cmap.py b/examples/images_contours_and_fields/custom_cmap.py index 153176ccb1ae..f01a4e8a8e64 100644 --- a/examples/images_contours_and_fields/custom_cmap.py +++ b/examples/images_contours_and_fields/custom_cmap.py @@ -145,12 +145,13 @@ # Make a modified version of cdict3 with some transparency # in the middle of the range. -cdict4 = cdict3.copy() -cdict4['alpha'] = ((0.0, 1.0, 1.0), +cdict4 = {**cdict3, + 'alpha': ((0.0, 1.0, 1.0), # (0.25,1.0, 1.0), - (0.5, 0.3, 0.3), + (0.5, 0.3, 0.3), # (0.75,1.0, 1.0), - (1.0, 1.0, 1.0)) + (1.0, 1.0, 1.0)), + } ############################################################################### diff --git a/examples/images_contours_and_fields/figimage_demo.py b/examples/images_contours_and_fields/figimage_demo.py index b2ce013d77a5..ef805576cae0 100644 --- a/examples/images_contours_and_fields/figimage_demo.py +++ b/examples/images_contours_and_fields/figimage_demo.py @@ -8,7 +8,6 @@ """ import numpy as np import matplotlib -import matplotlib.cm as cm import matplotlib.pyplot as plt diff --git a/examples/images_contours_and_fields/image_demo.py b/examples/images_contours_and_fields/image_demo.py index 123bfc2c9d5c..6ccb196b97d0 100644 --- a/examples/images_contours_and_fields/image_demo.py +++ b/examples/images_contours_and_fields/image_demo.py @@ -10,7 +10,6 @@ functionality of imshow and the many images you can create. """ -from __future__ import print_function import numpy as np import matplotlib.cm as cm diff --git a/examples/images_contours_and_fields/image_nonuniform.py b/examples/images_contours_and_fields/image_nonuniform.py index e1123840f4a3..b429826c8285 100644 --- a/examples/images_contours_and_fields/image_nonuniform.py +++ b/examples/images_contours_and_fields/image_nonuniform.py @@ -25,8 +25,7 @@ z = np.sqrt(x[np.newaxis, :]**2 + y[:, np.newaxis]**2) -fig, axs = plt.subplots(nrows=2, ncols=2) -fig.subplots_adjust(bottom=0.07, hspace=0.3) +fig, axs = plt.subplots(nrows=2, ncols=2, constrained_layout=True) fig.suptitle('NonUniformImage class', fontsize='large') ax = axs[0, 0] im = NonUniformImage(ax, interpolation=interp, extent=(-4, 4, -4, 4), diff --git a/examples/api/image_zcoord.py b/examples/images_contours_and_fields/image_zcoord.py similarity index 60% rename from examples/api/image_zcoord.py rename to examples/images_contours_and_fields/image_zcoord.py index d1851ccc843e..3036bd59c7d1 100644 --- a/examples/api/image_zcoord.py +++ b/examples/images_contours_and_fields/image_zcoord.py @@ -4,7 +4,10 @@ ================================== Modify the coordinate formatter to report the image "z" -value of the nearest pixel given x and y +value of the nearest pixel given x and y. +This functionality is built in by default, but it +is still useful to show how to customize the +`~.axes.Axes.format_coord` function. """ import numpy as np import matplotlib.pyplot as plt @@ -32,3 +35,17 @@ def format_coord(x, y): ax.format_coord = format_coord plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.format_coord +matplotlib.axes.Axes.imshow diff --git a/examples/images_contours_and_fields/layer_images.py b/examples/images_contours_and_fields/layer_images.py index ada6757490bc..5b2ba0738a68 100644 --- a/examples/images_contours_and_fields/layer_images.py +++ b/examples/images_contours_and_fields/layer_images.py @@ -5,7 +5,6 @@ Layer images above one another using alpha blending """ -from __future__ import division import matplotlib.pyplot as plt import numpy as np diff --git a/examples/images_contours_and_fields/quadmesh_demo.py b/examples/images_contours_and_fields/quadmesh_demo.py index 0e517a886a10..5488ddd83637 100644 --- a/examples/images_contours_and_fields/quadmesh_demo.py +++ b/examples/images_contours_and_fields/quadmesh_demo.py @@ -11,7 +11,7 @@ import copy -from matplotlib import cm, colors, pyplot as plt +from matplotlib import cm, pyplot as plt import numpy as np n = 12 diff --git a/examples/images_contours_and_fields/quiver_demo.py b/examples/images_contours_and_fields/quiver_demo.py index 7c1711710076..696b0a50ad7e 100644 --- a/examples/images_contours_and_fields/quiver_demo.py +++ b/examples/images_contours_and_fields/quiver_demo.py @@ -13,7 +13,6 @@ """ import matplotlib.pyplot as plt import numpy as np -from numpy import ma X, Y = np.meshgrid(np.arange(0, 2 * np.pi, .2), np.arange(0, 2 * np.pi, .2)) U = np.cos(X) diff --git a/examples/images_contours_and_fields/tricontour_smooth_delaunay.py b/examples/images_contours_and_fields/tricontour_smooth_delaunay.py index 6b3566fd64d4..22d5c95609a5 100644 --- a/examples/images_contours_and_fields/tricontour_smooth_delaunay.py +++ b/examples/images_contours_and_fields/tricontour_smooth_delaunay.py @@ -61,7 +61,7 @@ def experiment_res(x, y): min_circle_ratio = .01 # Minimum circle ratio - border triangles with circle # ratio below this will be masked if they touch a - # border. Suggested value 0.01 ; Use -1 to keep + # border. Suggested value 0.01; use -1 to keep # all triangles. # Random points diff --git a/examples/api/watermark_image.py b/examples/images_contours_and_fields/watermark_image.py similarity index 60% rename from examples/api/watermark_image.py rename to examples/images_contours_and_fields/watermark_image.py index fc057dd2c8ad..91cf43302668 100644 --- a/examples/api/watermark_image.py +++ b/examples/images_contours_and_fields/watermark_image.py @@ -3,9 +3,8 @@ Watermark image =============== -Use a PNG file as a watermark +Using a PNG file as a watermark. """ -from __future__ import print_function import numpy as np import matplotlib.cbook as cbook import matplotlib.image as image @@ -27,3 +26,19 @@ fig.figimage(im, 10, 10, zorder=3) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.image +matplotlib.image.imread +matplotlib.pyplot.imread +matplotlib.figure.Figure.figimage diff --git a/examples/api/filled_step.py b/examples/lines_bars_and_markers/filled_step.py similarity index 92% rename from examples/api/filled_step.py rename to examples/lines_bars_and_markers/filled_step.py index a384438fe5a2..e885a5b21fa5 100644 --- a/examples/api/filled_step.py +++ b/examples/lines_bars_and_markers/filled_step.py @@ -14,7 +14,6 @@ import matplotlib.pyplot as plt import matplotlib.ticker as mticker from cycler import cycler -from six.moves import zip def filled_hist(ax, edges, values, bottoms=None, orientation='v', @@ -49,7 +48,7 @@ def filled_hist(ax, edges, values, bottoms=None, orientation='v', Artist added to the Axes """ print(orientation) - if orientation not in set('hv'): + if orientation not in 'hv': raise ValueError("orientation must be in {{'h', 'v'}} " "not {o}".format(o=orientation)) @@ -150,8 +149,8 @@ def stack_hist(ax, stacked_data, sty_cycle, bottoms=None, labels = itertools.repeat(None) if label_data: - loop_iter = enumerate((stacked_data[lab], lab, s) for lab, s in - zip(labels, sty_cycle)) + loop_iter = enumerate((stacked_data[lab], lab, s) + for lab, s in zip(labels, sty_cycle)) else: loop_iter = enumerate(zip(stacked_data, labels, sty_cycle)) @@ -222,3 +221,18 @@ def stack_hist(ax, stacked_data, sty_cycle, bottoms=None, ax2.set_ylabel('x') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.fill_betweenx +matplotlib.axes.Axes.fill_between +matplotlib.axis.Axis.set_major_locator diff --git a/examples/api/joinstyle.py b/examples/lines_bars_and_markers/joinstyle.py similarity index 74% rename from examples/api/joinstyle.py rename to examples/lines_bars_and_markers/joinstyle.py index 1d4562a4702d..7ac68883cfd4 100644 --- a/examples/api/joinstyle.py +++ b/examples/lines_bars_and_markers/joinstyle.py @@ -31,3 +31,17 @@ def plot_angle(ax, x, y, angle, style): ax.set_xlim(-.5, 2.75) ax.set_ylim(-.5, 5.5) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.plot +matplotlib.pyplot.plot diff --git a/examples/lines_bars_and_markers/line_styles_reference.py b/examples/lines_bars_and_markers/line_styles_reference.py index dee949489471..4db1d40f7059 100644 --- a/examples/lines_bars_and_markers/line_styles_reference.py +++ b/examples/lines_bars_and_markers/line_styles_reference.py @@ -20,16 +20,12 @@ def format_axes(ax): ax.set_axis_off() -def nice_repr(text): - return repr(text).lstrip('u') - - # Plot all line styles. fig, ax = plt.subplots() linestyles = ['-', '--', '-.', ':'] for y, linestyle in enumerate(linestyles): - ax.text(-0.1, y, nice_repr(linestyle), **text_style) + ax.text(-0.1, y, repr(linestyle), **text_style) ax.plot(y * points, linestyle=linestyle, color=color, linewidth=3) format_axes(ax) ax.set_title('line styles') diff --git a/examples/lines_bars_and_markers/marker_fillstyle_reference.py b/examples/lines_bars_and_markers/marker_fillstyle_reference.py index 4960b8cd9122..cffe2cbc401f 100644 --- a/examples/lines_bars_and_markers/marker_fillstyle_reference.py +++ b/examples/lines_bars_and_markers/marker_fillstyle_reference.py @@ -4,6 +4,10 @@ ===================== Reference for marker fill-styles included with Matplotlib. + +Also refer to the +:doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference` +and :doc:`/gallery/shapes_and_collections/marker_path` examples. """ import numpy as np import matplotlib.pyplot as plt @@ -22,15 +26,11 @@ def format_axes(ax): ax.set_axis_off() -def nice_repr(text): - return repr(text).lstrip('u') - - fig, ax = plt.subplots() # Plot all fill styles. for y, fill_style in enumerate(Line2D.fillStyles): - ax.text(-0.5, y, nice_repr(fill_style), **text_style) + ax.text(-0.5, y, repr(fill_style), **text_style) ax.plot(y * points, fillstyle=fill_style, **marker_style) format_axes(ax) ax.set_title('fill style') diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index ee85ce6af535..c275f5d503f5 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -1,11 +1,15 @@ """ -================================ -Filled and unfilled-marker types -================================ +================ +Marker Reference +================ -Reference for filled- and unfilled-marker types included with Matplotlib. +Reference for filled-, unfilled- and custom marker types with Matplotlib. + +For a list of all markers see the `matplotlib.markers` documentation. Also +refer to the :doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference` +and :doc:`/gallery/shapes_and_collections/marker_path` examples. """ -from six import iteritems + import numpy as np import matplotlib.pyplot as plt from matplotlib.lines import Line2D @@ -14,42 +18,51 @@ points = np.ones(3) # Draw 3 points for each line text_style = dict(horizontalalignment='right', verticalalignment='center', fontsize=12, fontdict={'family': 'monospace'}) -marker_style = dict(linestyle=':', color='cornflowerblue', markersize=10) +marker_style = dict(linestyle=':', color='0.8', markersize=10, + mfc="C0", mec="C0") def format_axes(ax): ax.margins(0.2) ax.set_axis_off() + ax.invert_yaxis() def nice_repr(text): return repr(text).lstrip('u') +def math_repr(text): + tx = repr(text).lstrip('u').strip("'").strip("$") + return r"'\${}\$'".format(tx) + + def split_list(a_list): i_half = len(a_list) // 2 return (a_list[:i_half], a_list[i_half:]) + ############################################################################### +# Filled and unfilled-marker types +# ================================ +# +# # Plot all un-filled markers fig, axes = plt.subplots(ncols=2) +fig.suptitle('un-filled markers', fontsize=14) # Filter out filled markers and marker settings that do nothing. -# We use iteritems from six to make sure that we get an iterator -# in both python 2 and 3 -unfilled_markers = [m for m, func in iteritems(Line2D.markers) +unfilled_markers = [m for m, func in Line2D.markers.items() if func != 'nothing' and m not in Line2D.filled_markers] -# Reverse-sort for pretty. We use our own sort key which is essentially -# a python3 compatible reimplementation of python2 sort. -unfilled_markers = sorted(unfilled_markers, - key=lambda x: (str(type(x)), str(x)))[::-1] + for ax, markers in zip(axes, split_list(unfilled_markers)): for y, marker in enumerate(markers): ax.text(-0.5, y, nice_repr(marker), **text_style) ax.plot(y * points, marker=marker, **marker_style) format_axes(ax) -fig.suptitle('un-filled markers', fontsize=14) + +plt.show() ############################################################################### @@ -64,3 +77,30 @@ def split_list(a_list): fig.suptitle('filled markers', fontsize=14) plt.show() + + +############################################################################### +# Custom Markers with MathText +# ============================ +# +# +# Use :doc:`MathText `, to use custom marker symbols, +# like e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer +# to the `STIX font table `_. +# Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`. + + +fig, ax = plt.subplots() +fig.subplots_adjust(left=0.4) + +marker_style.update(mec="None", markersize=15) +markers = ["$1$", r"$\frac{1}{2}$", "$f$", "$\u266B$", + r"$\mathcircled{m}$"] + + +for y, marker in enumerate(markers): + ax.text(-0.5, y, math_repr(marker), **text_style) + ax.plot(y * points, marker=marker, **marker_style) +format_axes(ax) + +plt.show() diff --git a/examples/lines_bars_and_markers/markevery_demo.py b/examples/lines_bars_and_markers/markevery_demo.py index 8141c8d4bb49..62eda10de3bc 100644 --- a/examples/lines_bars_and_markers/markevery_demo.py +++ b/examples/lines_bars_and_markers/markevery_demo.py @@ -20,7 +20,6 @@ """ -from __future__ import division import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec diff --git a/examples/lines_bars_and_markers/markevery_prop_cycle.py b/examples/lines_bars_and_markers/markevery_prop_cycle.py new file mode 100644 index 000000000000..ac020e1ba713 --- /dev/null +++ b/examples/lines_bars_and_markers/markevery_prop_cycle.py @@ -0,0 +1,63 @@ +""" +================================================================= +Implemented support for prop_cycle property markevery in rcParams +================================================================= + +This example demonstrates a working solution to issue #8576, providing full +support of the markevery property for axes.prop_cycle assignments through +rcParams. Makes use of the same list of markevery cases from the +:doc:`markevery demo +`. + +Renders a plot with shifted-sine curves along each column with +a unique markevery value for each sine curve. +""" +from cycler import cycler +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt + +# Define a list of markevery cases and color cases to plot +cases = [None, + 8, + (30, 8), + [16, 24, 30], + [0, -1], + slice(100, 200, 3), + 0.1, + 0.3, + 1.5, + (0.0, 0.1), + (0.45, 0.1)] + +colors = ['#1f77b4', + '#ff7f0e', + '#2ca02c', + '#d62728', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf', + '#1a55FF'] + +# Configure rcParams axes.prop_cycle to simultaneously cycle cases and colors. +mpl.rcParams['axes.prop_cycle'] = cycler(markevery=cases, color=colors) + +# Create data points and offsets +x = np.linspace(0, 2 * np.pi) +offsets = np.linspace(0, 2 * np.pi, 11, endpoint=False) +yy = np.transpose([np.sin(x + phi) for phi in offsets]) + +# Set the plot curve with markers and a title +fig = plt.figure() +ax = fig.add_axes([0.1, 0.1, 0.6, 0.75]) + +for i in range(len(cases)): + ax.plot(yy[:, i], marker='o', label=str(cases[i])) + ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.) + +plt.title('Support for axes.prop_cycle cycler with markevery') + +plt.show() diff --git a/examples/lines_bars_and_markers/psd_demo.py b/examples/lines_bars_and_markers/psd_demo.py index a96d98149365..cd0d35882630 100644 --- a/examples/lines_bars_and_markers/psd_demo.py +++ b/examples/lines_bars_and_markers/psd_demo.py @@ -12,11 +12,11 @@ import matplotlib.pyplot as plt import numpy as np import matplotlib.mlab as mlab +import matplotlib.gridspec as gridspec # Fixing random state for reproducibility np.random.seed(19680801) - dt = 0.01 t = np.arange(0, 10, dt) nse = np.random.randn(len(t)) @@ -59,14 +59,16 @@ y = y + np.random.randn(*t.shape) # Plot the raw time series -fig = plt.figure() -fig.subplots_adjust(hspace=0.45, wspace=0.3) -ax = fig.add_subplot(2, 1, 1) +fig = plt.figure(constrained_layout=True) +gs = gridspec.GridSpec(2, 3, figure=fig) +ax = fig.add_subplot(gs[0, :]) ax.plot(t, y) +ax.set_xlabel('time [s]') +ax.set_ylabel('signal') # Plot the PSD with different amounts of zero padding. This uses the entire # time series at once -ax2 = fig.add_subplot(2, 3, 4) +ax2 = fig.add_subplot(gs[1, 0]) ax2.psd(y, NFFT=len(t), pad_to=len(t), Fs=fs) ax2.psd(y, NFFT=len(t), pad_to=len(t) * 2, Fs=fs) ax2.psd(y, NFFT=len(t), pad_to=len(t) * 4, Fs=fs) @@ -74,7 +76,7 @@ # Plot the PSD with different block sizes, Zero pad to the length of the # original data sequence. -ax3 = fig.add_subplot(2, 3, 5, sharex=ax2, sharey=ax2) +ax3 = fig.add_subplot(gs[1, 1], sharex=ax2, sharey=ax2) ax3.psd(y, NFFT=len(t), pad_to=len(t), Fs=fs) ax3.psd(y, NFFT=len(t) // 2, pad_to=len(t), Fs=fs) ax3.psd(y, NFFT=len(t) // 4, pad_to=len(t), Fs=fs) @@ -82,7 +84,7 @@ plt.title('block size') # Plot the PSD with different amounts of overlap between blocks -ax4 = fig.add_subplot(2, 3, 6, sharex=ax2, sharey=ax2) +ax4 = fig.add_subplot(gs[1, 2], sharex=ax2, sharey=ax2) ax4.psd(y, NFFT=len(t) // 2, pad_to=len(t), noverlap=0, Fs=fs) ax4.psd(y, NFFT=len(t) // 2, pad_to=len(t), noverlap=int(0.05 * len(t) / 2.), Fs=fs) @@ -106,9 +108,8 @@ xn = (A * np.sin(2 * np.pi * f * t)).sum(axis=0) xn += 5 * np.random.randn(*t.shape) -fig, (ax0, ax1) = plt.subplots(ncols=2) +fig, (ax0, ax1) = plt.subplots(ncols=2, constrained_layout=True) -fig.subplots_adjust(hspace=0.45, wspace=0.3) yticks = np.arange(-50, 30, 10) yrange = (yticks[0], yticks[-1]) xticks = np.arange(0, 550, 100) @@ -147,9 +148,8 @@ f = np.array([150, 140]).reshape(-1, 1) xn = (A * np.exp(2j * np.pi * f * t)).sum(axis=0) + 5 * prng.randn(*t.shape) -fig, (ax0, ax1) = plt.subplots(ncols=2) +fig, (ax0, ax1) = plt.subplots(ncols=2, constrained_layout=True) -fig.subplots_adjust(hspace=0.45, wspace=0.3) yticks = np.arange(-50, 30, 10) yrange = (yticks[0], yticks[-1]) xticks = np.arange(-500, 550, 200) diff --git a/examples/lines_bars_and_markers/scatter_custom_symbol.py b/examples/lines_bars_and_markers/scatter_custom_symbol.py index 9d4b71ee7ede..a66410c31caf 100644 --- a/examples/lines_bars_and_markers/scatter_custom_symbol.py +++ b/examples/lines_bars_and_markers/scatter_custom_symbol.py @@ -3,6 +3,8 @@ Scatter Custom Symbol ===================== +Creating a custom ellipse symbol in scatter plot. + """ import matplotlib.pyplot as plt import numpy as np @@ -17,6 +19,6 @@ s *= 10**2. fig, ax = plt.subplots() -ax.scatter(x, y, s, c, marker=None, verts=verts) +ax.scatter(x, y, s, c, marker=verts) plt.show() diff --git a/examples/lines_bars_and_markers/scatter_hist.py b/examples/lines_bars_and_markers/scatter_hist.py index b4320d686725..f4737a321c16 100644 --- a/examples/lines_bars_and_markers/scatter_hist.py +++ b/examples/lines_bars_and_markers/scatter_hist.py @@ -3,6 +3,9 @@ Scatter Hist ============ +Creates histogram from scatter plot +and adds them to the sides of the plot. + """ import numpy as np import matplotlib.pyplot as plt diff --git a/examples/lines_bars_and_markers/scatter_masked.py b/examples/lines_bars_and_markers/scatter_masked.py index a0c463e03161..e5dc3d58491f 100644 --- a/examples/lines_bars_and_markers/scatter_masked.py +++ b/examples/lines_bars_and_markers/scatter_masked.py @@ -3,6 +3,9 @@ Scatter Masked ============== +Mask some data points and add a line demarking +masked regions. + """ import matplotlib.pyplot as plt import numpy as np diff --git a/examples/api/scatter_piecharts.py b/examples/lines_bars_and_markers/scatter_piecharts.py similarity index 75% rename from examples/api/scatter_piecharts.py rename to examples/lines_bars_and_markers/scatter_piecharts.py index 5826f0ddc25b..d4ffaa34d152 100644 --- a/examples/api/scatter_piecharts.py +++ b/examples/lines_bars_and_markers/scatter_piecharts.py @@ -38,11 +38,25 @@ s3 = np.abs(xy3).max() fig, ax = plt.subplots() -ax.scatter(range(3), range(3), marker=(xy1, 0), +ax.scatter(range(3), range(3), marker=xy1, s=s1 ** 2 * sizes, facecolor='blue') -ax.scatter(range(3), range(3), marker=(xy2, 0), +ax.scatter(range(3), range(3), marker=xy2, s=s2 ** 2 * sizes, facecolor='green') -ax.scatter(range(3), range(3), marker=(xy3, 0), +ax.scatter(range(3), range(3), marker=xy3, s=s3 ** 2 * sizes, facecolor='red') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.scatter +matplotlib.pyplot.scatter diff --git a/examples/lines_bars_and_markers/scatter_star_poly.py b/examples/lines_bars_and_markers/scatter_star_poly.py index 1cf83cebcb30..6dafbf27c832 100644 --- a/examples/lines_bars_and_markers/scatter_star_poly.py +++ b/examples/lines_bars_and_markers/scatter_star_poly.py @@ -3,6 +3,9 @@ Scatter Star Poly ================= +Create multiple scatter plots with different +star symbols. + """ import numpy as np import matplotlib.pyplot as plt @@ -23,9 +26,7 @@ verts = np.array([[-1, -1], [1, -1], [1, 1], [-1, -1]]) plt.subplot(323) -plt.scatter(x, y, s=80, c=z, marker=(verts, 0)) -# equivalent: -# plt.scatter(x, y, s=80, c=z, marker=None, verts=verts) +plt.scatter(x, y, s=80, c=z, marker=verts) plt.subplot(324) plt.scatter(x, y, s=80, c=z, marker=(5, 1)) diff --git a/examples/lines_bars_and_markers/scatter_symbol.py b/examples/lines_bars_and_markers/scatter_symbol.py index bee25f03ddc7..d99b6a80a740 100644 --- a/examples/lines_bars_and_markers/scatter_symbol.py +++ b/examples/lines_bars_and_markers/scatter_symbol.py @@ -3,10 +3,11 @@ Scatter Symbol ============== +Scatter plot with clover symbols. + """ import matplotlib.pyplot as plt import numpy as np -import matplotlib # Fixing random state for reproducibility np.random.seed(19680801) @@ -20,5 +21,5 @@ label="Luck") plt.xlabel("Leprechauns") plt.ylabel("Gold") -plt.legend(loc=2) +plt.legend(loc='upper left') plt.show() diff --git a/examples/api/span_regions.py b/examples/lines_bars_and_markers/span_regions.py similarity index 66% rename from examples/api/span_regions.py rename to examples/lines_bars_and_markers/span_regions.py index 7627e96992d5..e1a9b85c1407 100644 --- a/examples/api/span_regions.py +++ b/examples/lines_bars_and_markers/span_regions.py @@ -4,7 +4,7 @@ ================ Illustrate some helper functions for shading regions where a logical -mask is True +mask is True. See :meth:`matplotlib.collections.BrokenBarHCollection.span_where` """ @@ -33,3 +33,20 @@ plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.collections.BrokenBarHCollection +matplotlib.collections.BrokenBarHCollection.span_where +matplotlib.axes.Axes.add_collection +matplotlib.axes.Axes.axhline diff --git a/examples/lines_bars_and_markers/stackplot_demo.py b/examples/lines_bars_and_markers/stackplot_demo.py index 92a25ac79c62..27db8ebd5a8a 100644 --- a/examples/lines_bars_and_markers/stackplot_demo.py +++ b/examples/lines_bars_and_markers/stackplot_demo.py @@ -23,7 +23,7 @@ fig, ax = plt.subplots() ax.stackplot(x, y1, y2, y3, labels=labels) -ax.legend(loc=2) +ax.legend(loc='upper left') plt.show() fig, ax = plt.subplots() diff --git a/examples/lines_bars_and_markers/timeline.py b/examples/lines_bars_and_markers/timeline.py new file mode 100644 index 000000000000..6b508ddc5e46 --- /dev/null +++ b/examples/lines_bars_and_markers/timeline.py @@ -0,0 +1,71 @@ +""" +=============================================== +Creating a timeline with lines, dates, and text +=============================================== + +How to create a simple timeline using Matplotlib release dates. + +Timelines can be created with a collection of dates and text. In this example, +we show how to create a simple timeline using the dates for recent releases +of Matplotlib. First, we'll pull the data from GitHub. +""" + +import matplotlib.pyplot as plt +import numpy as np +import matplotlib.dates as mdates +from datetime import datetime + +# A list of Matplotlib releases and their dates +# Taken from https://api.github.com/repos/matplotlib/matplotlib/releases +names = ['v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0', 'v2.0.2', + 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1', 'v1.5.0', 'v1.4.3', + 'v1.4.2', 'v1.4.1', 'v1.4.0'] + +dates = ['2018-03-17T03:00:07Z', '2018-03-16T22:06:39Z', + '2018-03-06T12:53:32Z', '2018-01-18T04:56:47Z', + '2017-12-10T04:47:38Z', '2017-10-07T22:35:12Z', + '2017-05-10T02:11:15Z', '2017-05-02T01:59:49Z', + '2017-01-17T02:59:36Z', '2016-09-09T03:00:52Z', + '2016-07-03T15:52:01Z', '2016-01-10T22:38:50Z', + '2015-10-29T21:40:23Z', '2015-02-16T04:22:54Z', + '2014-10-26T03:24:13Z', '2014-10-18T18:56:23Z', + '2014-08-26T21:06:04Z'] +dates = [datetime.strptime(ii, "%Y-%m-%dT%H:%M:%SZ") for ii in dates] + +############################################################################## +# Next, we'll iterate through each date and plot it on a horizontal line. +# We'll add some styling to the text so that overlaps aren't as strong. +# +# Note that Matplotlib will automatically plot datetime inputs. + +levels = np.array([-5, 5, -3, 3, -1, 1]) +fig, ax = plt.subplots(figsize=(8, 5)) + +# Create the base line +start = min(dates) +stop = max(dates) +ax.plot((start, stop), (0, 0), 'k', alpha=.5) + +# Iterate through releases annotating each one +for ii, (iname, idate) in enumerate(zip(names, dates)): + level = levels[ii % 6] + vert = 'top' if level < 0 else 'bottom' + + ax.scatter(idate, 0, s=100, facecolor='w', edgecolor='k', zorder=9999) + # Plot a line up to the text + ax.plot((idate, idate), (0, level), c='r', alpha=.7) + # Give the text a faint background and align it properly + ax.text(idate, level, iname, + horizontalalignment='right', verticalalignment=vert, fontsize=14, + backgroundcolor=(1., 1., 1., .3)) +ax.set(title="Matplotlib release dates") +# Set the xticks formatting +# format xaxis with 3 month intervals +ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=3)) +ax.get_xaxis().set_major_formatter(mdates.DateFormatter("%b %Y")) +fig.autofmt_xdate() + +# Remove components for a cleaner look +plt.setp((ax.get_yticklabels() + ax.get_yticklines() + + list(ax.spines.values())), visible=False) +plt.show() diff --git a/examples/misc/agg_buffer.py b/examples/misc/agg_buffer.py index aa76efe291dc..096c7e856115 100644 --- a/examples/misc/agg_buffer.py +++ b/examples/misc/agg_buffer.py @@ -9,14 +9,8 @@ import numpy as np -import matplotlib.pyplot as plt from matplotlib.backends.backend_agg import FigureCanvasAgg - - -try: - from PIL import Image -except ImportError: - raise SystemExit("Pillow must be installed to run this example") +import matplotlib.pyplot as plt plt.plot([1, 2, 3]) @@ -24,19 +18,14 @@ agg = canvas.switch_backends(FigureCanvasAgg) agg.draw() -s = agg.tostring_rgb() - -# get the width and the height to resize the matrix -l, b, w, h = agg.figure.bbox.bounds -w, h = int(w), int(h) +s, (width, height) = agg.print_to_buffer() -X = np.fromstring(s, np.uint8).reshape((h, w, 3)) +# Convert to a NumPy array. +X = np.fromstring(s, np.uint8).reshape((height, width, 4)) -try: - im = Image.fromstring("RGB", (w, h), s) -except Exception: - im = Image.frombytes("RGB", (w, h), s) +# Pass off to PIL. +from PIL import Image +im = Image.frombytes("RGBA", (width, height), s) -# Uncomment this line to display the image using ImageMagick's -# `display` tool. +# Uncomment this line to display the image using ImageMagick's `display` tool. # im.show() diff --git a/examples/misc/anchored_artists.py b/examples/misc/anchored_artists.py index 3f7ad99c3030..cd829f80fb25 100644 --- a/examples/misc/anchored_artists.py +++ b/examples/misc/anchored_artists.py @@ -10,48 +10,48 @@ implemented using only the matplotlib namespace, without the help of additional toolkits. """ -from matplotlib.patches import Rectangle, Ellipse -from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox, VPacker,\ - TextArea, DrawingArea +from matplotlib import pyplot as plt +from matplotlib.patches import Rectangle, Ellipse +from matplotlib.offsetbox import ( + AnchoredOffsetbox, AuxTransformBox, DrawingArea, TextArea, VPacker) class AnchoredText(AnchoredOffsetbox): def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, frameon=True): + self.txt = TextArea(s, minimumdescent=False) + super().__init__(loc, pad=pad, borderpad=borderpad, + child=self.txt, prop=prop, frameon=frameon) - self.txt = TextArea(s, - minimumdescent=False) - - super(AnchoredText, self).__init__(loc, pad=pad, borderpad=borderpad, - child=self.txt, - prop=prop, - frameon=frameon) +def draw_text(ax): + """ + Draw a text-box anchored to the upper-left corner of the figure. + """ + at = AnchoredText("Figure 1a", loc='upper left', frameon=True) + at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") + ax.add_artist(at) -class AnchoredSizeBar(AnchoredOffsetbox): - def __init__(self, transform, size, label, loc, - pad=0.1, borderpad=0.1, sep=2, prop=None, frameon=True): - """ - Draw a horizontal bar with the size in data coordinate of the given - axes. A label will be drawn underneath (center-aligned). - - pad, borderpad in fraction of the legend font size (or prop) - sep in points. - """ - self.size_bar = AuxTransformBox(transform) - self.size_bar.add_artist(Rectangle((0, 0), size, 0, ec="black", lw=1.0)) - self.txt_label = TextArea(label, minimumdescent=False) +class AnchoredDrawingArea(AnchoredOffsetbox): + def __init__(self, width, height, xdescent, ydescent, + loc, pad=0.4, borderpad=0.5, prop=None, frameon=True): + self.da = DrawingArea(width, height, xdescent, ydescent) + super().__init__(loc, pad=pad, borderpad=borderpad, + child=self.da, prop=None, frameon=frameon) - self._box = VPacker(children=[self.size_bar, self.txt_label], - align="center", - pad=0, sep=sep) - AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, - child=self._box, - prop=prop, - frameon=frameon) +def draw_circle(ax): + """ + Draw a circle in axis coordinates + """ + from matplotlib.patches import Circle + ada = AnchoredDrawingArea(20, 20, 0, 0, + loc='upper right', pad=0., frameon=False) + p = Circle((10, 10), 10) + ada.da.add_artist(p) + ax.add_artist(ada) class AnchoredEllipse(AnchoredOffsetbox): @@ -65,60 +65,64 @@ def __init__(self, transform, width, height, angle, loc, self._box = AuxTransformBox(transform) self.ellipse = Ellipse((0, 0), width, height, angle) self._box.add_artist(self.ellipse) + super().__init__(loc, pad=pad, borderpad=borderpad, + child=self._box, prop=prop, frameon=frameon) - AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, - child=self._box, - prop=prop, - frameon=frameon) - - -class AnchoredDrawingArea(AnchoredOffsetbox): - def __init__(self, width, height, xdescent, ydescent, - loc, pad=0.4, borderpad=0.5, prop=None, frameon=True): - - self.da = DrawingArea(width, height, xdescent, ydescent) - super(AnchoredDrawingArea, self).__init__(loc, pad=pad, - borderpad=borderpad, - child=self.da, - prop=None, - frameon=frameon) +def draw_ellipse(ax): + """ + Draw an ellipse of width=0.1, height=0.15 in data coordinates + """ + ae = AnchoredEllipse(ax.transData, width=0.1, height=0.15, angle=0., + loc='lower left', pad=0.5, borderpad=0.4, + frameon=True) + ax.add_artist(ae) -if __name__ == "__main__": - import matplotlib.pyplot as plt +class AnchoredSizeBar(AnchoredOffsetbox): + def __init__(self, transform, size, label, loc, + pad=0.1, borderpad=0.1, sep=2, prop=None, frameon=True): + """ + Draw a horizontal bar with the size in data coordinate of the given + axes. A label will be drawn underneath (center-aligned). - ax = plt.gca() - ax.set_aspect(1.) + pad, borderpad in fraction of the legend font size (or prop) + sep in points. + """ + self.size_bar = AuxTransformBox(transform) + self.size_bar.add_artist(Rectangle((0, 0), size, 0, ec="black", lw=1.0)) - at = AnchoredText("Figure 1a", - loc=2, frameon=True) - at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") - ax.add_artist(at) + self.txt_label = TextArea(label, minimumdescent=False) - from matplotlib.patches import Circle - ada = AnchoredDrawingArea(20, 20, 0, 0, - loc=1, pad=0., frameon=False) - p = Circle((10, 10), 10) - ada.da.add_artist(p) - ax.add_artist(ada) + self._box = VPacker(children=[self.size_bar, self.txt_label], + align="center", + pad=0, sep=sep) - # draw an ellipse of width=0.1, height=0.15 in the data coordinate - ae = AnchoredEllipse(ax.transData, width=0.1, height=0.15, angle=0., - loc=3, pad=0.5, borderpad=0.4, frameon=True) + super().__init__(loc, pad=pad, borderpad=borderpad, + child=self._box, prop=prop, frameon=frameon) - ax.add_artist(ae) - # draw a horizontal bar with length of 0.1 in Data coordinate - # (ax.transData) with a label underneath. +def draw_sizebar(ax): + """ + Draw a horizontal bar with length of 0.1 in data coordinates, + with a fixed label underneath. + """ asb = AnchoredSizeBar(ax.transData, 0.1, r"1$^{\prime}$", - loc=8, + loc='lower center', pad=0.1, borderpad=0.5, sep=5, frameon=False) ax.add_artist(asb) - plt.draw() - plt.show() + +ax = plt.gca() +ax.set_aspect(1.) + +draw_text(ax) +draw_circle(ax) +draw_ellipse(ax) +draw_sizebar(ax) + +plt.show() diff --git a/examples/misc/cursor_demo_sgskip.py b/examples/misc/cursor_demo_sgskip.py index a9e3c68c4410..7354b4bb0735 100644 --- a/examples/misc/cursor_demo_sgskip.py +++ b/examples/misc/cursor_demo_sgskip.py @@ -16,7 +16,6 @@ https://github.com/joferkington/mpldatacursor https://github.com/anntzer/mplcursors """ -from __future__ import print_function import matplotlib.pyplot as plt import numpy as np diff --git a/examples/misc/custom_projection.py b/examples/misc/custom_projection.py index c446120bb89d..39fb58a1e768 100644 --- a/examples/misc/custom_projection.py +++ b/examples/misc/custom_projection.py @@ -3,13 +3,9 @@ Custom projection ================= -Showcase Hammer projection by alleviating many features of -matplotlib. +Showcase Hammer projection by alleviating many features of Matplotlib. """ - -from __future__ import unicode_literals - import matplotlib from matplotlib.axes import Axes from matplotlib.patches import Circle @@ -399,18 +395,17 @@ def __init__(self, resolution): self._resolution = resolution def transform_non_affine(self, ll): - longitude = ll[:, 0:1] - latitude = ll[:, 1:2] + longitude, latitude = ll.T # Pre-compute some values - half_long = longitude / 2.0 + half_long = longitude / 2 cos_latitude = np.cos(latitude) - sqrt2 = np.sqrt(2.0) + sqrt2 = np.sqrt(2) - alpha = np.sqrt(1.0 + cos_latitude * np.cos(half_long)) - x = (2.0 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha + alpha = np.sqrt(1 + cos_latitude * np.cos(half_long)) + x = (2 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha y = (sqrt2 * np.sin(latitude)) / alpha - return np.concatenate((x, y), 1) + return np.column_stack([x, y]) transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ def transform_path_non_affine(self, path): diff --git a/examples/misc/demo_ribbon_box.py b/examples/misc/demo_ribbon_box.py index 54d6a2e6b52e..c0d460753790 100644 --- a/examples/misc/demo_ribbon_box.py +++ b/examples/misc/demo_ribbon_box.py @@ -4,98 +4,60 @@ =============== """ -import matplotlib.pyplot as plt + import numpy as np -from matplotlib.image import BboxImage -from matplotlib._png import read_png -import matplotlib.colors -from matplotlib.cbook import get_sample_data +from matplotlib import cbook, colors as mcolors +from matplotlib.image import BboxImage +import matplotlib.pyplot as plt -class RibbonBox(object): +class RibbonBox: - original_image = read_png(get_sample_data("Minduka_Present_Blue_Pack.png", - asfileobj=False)) + original_image = plt.imread( + cbook.get_sample_data("Minduka_Present_Blue_Pack.png")) cut_location = 70 - b_and_h = original_image[:, :, 2] - color = original_image[:, :, 2] - original_image[:, :, 0] - alpha = original_image[:, :, 3] + b_and_h = original_image[:, :, 2:3] + color = original_image[:, :, 2:3] - original_image[:, :, 0:1] + alpha = original_image[:, :, 3:4] nx = original_image.shape[1] def __init__(self, color): - rgb = matplotlib.colors.to_rgba(color)[:3] - - im = np.empty(self.original_image.shape, - self.original_image.dtype) - - im[:, :, :3] = self.b_and_h[:, :, np.newaxis] - im[:, :, :3] -= self.color[:, :, np.newaxis]*(1. - np.array(rgb)) - im[:, :, 3] = self.alpha - - self.im = im + rgb = mcolors.to_rgba(color)[:3] + self.im = np.dstack( + [self.b_and_h - self.color * (1 - np.array(rgb)), self.alpha]) def get_stretched_image(self, stretch_factor): stretch_factor = max(stretch_factor, 1) ny, nx, nch = self.im.shape ny2 = int(ny*stretch_factor) - - stretched_image = np.empty((ny2, nx, nch), - self.im.dtype) - cut = self.im[self.cut_location, :, :] - stretched_image[:, :, :] = cut - stretched_image[:self.cut_location, :, :] = \ - self.im[:self.cut_location, :, :] - stretched_image[-(ny - self.cut_location):, :, :] = \ - self.im[-(ny - self.cut_location):, :, :] - - self._cached_im = stretched_image - return stretched_image + return np.vstack( + [self.im[:self.cut_location], + np.broadcast_to( + self.im[self.cut_location], (ny2 - ny, nx, nch)), + self.im[self.cut_location:]]) class RibbonBoxImage(BboxImage): zorder = 1 - def __init__(self, bbox, color, - cmap=None, - norm=None, - interpolation=None, - origin=None, - filternorm=1, - filterrad=4.0, - resample=False, - **kwargs - ): - - BboxImage.__init__(self, bbox, - cmap=cmap, - norm=norm, - interpolation=interpolation, - origin=origin, - filternorm=filternorm, - filterrad=filterrad, - resample=resample, - **kwargs - ) - + def __init__(self, bbox, color, **kwargs): + super().__init__(bbox, **kwargs) self._ribbonbox = RibbonBox(color) - self._cached_ny = None def draw(self, renderer, *args, **kwargs): - bbox = self.get_window_extent(renderer) stretch_factor = bbox.height / bbox.width ny = int(stretch_factor*self._ribbonbox.nx) - if self._cached_ny != ny: + if self.get_array() is None or self.get_array().shape[0] != ny: arr = self._ribbonbox.get_stretched_image(stretch_factor) self.set_array(arr) - self._cached_ny = ny - BboxImage.draw(self, renderer, *args, **kwargs) + super().draw(renderer, *args, **kwargs) -if 1: +if True: from matplotlib.transforms import Bbox, TransformedBbox from matplotlib.ticker import ScalarFormatter @@ -126,11 +88,8 @@ def draw(self, renderer, *args, **kwargs): ax.annotate(r"%d" % (int(h/100.)*100), (year, h), va="bottom", ha="center") - patch_gradient = BboxImage(ax.bbox, - interpolation="bicubic", - zorder=0.1, - ) - gradient = np.zeros((2, 2, 4), dtype=float) + patch_gradient = BboxImage(ax.bbox, interpolation="bicubic", zorder=0.1) + gradient = np.zeros((2, 2, 4)) gradient[:, :, :3] = [1, 1, 0.] gradient[:, :, 3] = [[0.1, 0.3], [0.3, 0.5]] # alpha channel patch_gradient.set_array(gradient) @@ -139,5 +98,4 @@ def draw(self, renderer, *args, **kwargs): ax.set_xlim(years[0] - 0.5, years[-1] + 0.5) ax.set_ylim(0, 10000) - fig.savefig('ribbon_box.png') plt.show() diff --git a/examples/misc/font_indexing.py b/examples/misc/font_indexing.py index 6a1f29260085..7625671968bd 100644 --- a/examples/misc/font_indexing.py +++ b/examples/misc/font_indexing.py @@ -7,7 +7,6 @@ tables relate to one another. Mainly for mpl developers.... """ -from __future__ import print_function import matplotlib from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, KERNING_UNFITTED, KERNING_UNSCALED diff --git a/examples/misc/ftface_props.py b/examples/misc/ftface_props.py index 575af193e7b2..b40a892715ae 100644 --- a/examples/misc/ftface_props.py +++ b/examples/misc/ftface_props.py @@ -8,7 +8,6 @@ individual character metrics, use the Glyph object, as returned by load_char """ -from __future__ import print_function import matplotlib import matplotlib.ft2font as ft diff --git a/examples/api/histogram_path.py b/examples/misc/histogram_path.py similarity index 51% rename from examples/api/histogram_path.py rename to examples/misc/histogram_path.py index 197706f83387..4eb4d68ba2d5 100644 --- a/examples/api/histogram_path.py +++ b/examples/misc/histogram_path.py @@ -8,10 +8,10 @@ the faster method of using PolyCollections, were implemented before we had proper paths with moveto/lineto, closepoly etc in mpl. Now that we have them, we can draw collections of regularly shaped objects with -homogeneous properties more efficiently with a PathCollection. This -example makes a histogram -- its more work to set up the vertex arrays +homogeneous properties more efficiently with a PathCollection. This +example makes a histogram -- it's more work to set up the vertex arrays at the outset, but it should be much faster for large numbers of -objects +objects. """ import numpy as np @@ -53,3 +53,48 @@ ax.set_ylim(bottom.min(), top.max()) plt.show() + +############################################################################# +# It should be noted that instead of creating a three-dimensional array and +# using `~.path.Path.make_compound_path_from_polys`, we could as well create +# the compound path directly using vertices and codes as shown below + +nrects = len(left) +nverts = nrects*(1+3+1) +verts = np.zeros((nverts, 2)) +codes = np.ones(nverts, int) * path.Path.LINETO +codes[0::5] = path.Path.MOVETO +codes[4::5] = path.Path.CLOSEPOLY +verts[0::5, 0] = left +verts[0::5, 1] = bottom +verts[1::5, 0] = left +verts[1::5, 1] = top +verts[2::5, 0] = right +verts[2::5, 1] = top +verts[3::5, 0] = right +verts[3::5, 1] = bottom + +barpath = path.Path(verts, codes) + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.patches +matplotlib.patches.PathPatch +matplotlib.path +matplotlib.path.Path +matplotlib.path.Path.make_compound_path_from_polys +matplotlib.axes.Axes.add_patch +matplotlib.collections.PathCollection + +# This example shows an alternative to +matplotlib.collections.PolyCollection +matplotlib.axes.Axes.hist diff --git a/examples/misc/image_thumbnail_sgskip.py b/examples/misc/image_thumbnail_sgskip.py index c9d02eb82303..ae82e616743b 100644 --- a/examples/misc/image_thumbnail_sgskip.py +++ b/examples/misc/image_thumbnail_sgskip.py @@ -10,7 +10,6 @@ """ -from __future__ import print_function # build thumbnails of all images in a directory import sys import os diff --git a/examples/misc/load_converter.py b/examples/misc/load_converter.py index f6201ac2d650..9488972cae87 100644 --- a/examples/misc/load_converter.py +++ b/examples/misc/load_converter.py @@ -4,11 +4,9 @@ ============== """ -from __future__ import print_function import numpy as np import matplotlib.pyplot as plt import matplotlib.cbook as cbook -import matplotlib.dates as mdates from matplotlib.dates import bytespdate2num datafile = cbook.get_sample_data('msft.csv', asfileobj=False) diff --git a/examples/api/logos2.py b/examples/misc/logos2.py similarity index 100% rename from examples/api/logos2.py rename to examples/misc/logos2.py diff --git a/examples/misc/multipage_pdf.py b/examples/misc/multipage_pdf.py index 532d771849cb..9986237c7f29 100644 --- a/examples/misc/multipage_pdf.py +++ b/examples/misc/multipage_pdf.py @@ -5,6 +5,10 @@ This is a demo of creating a pdf file with several pages, as well as adding metadata and annotations to pdf files. + +If you want to use a multipage pdf file using LaTeX, you need +to use `from matplotlib.backends.backend_pgf import PdfPages`. +This version however does not support `attach_note`. """ import datetime @@ -43,7 +47,7 @@ # We can also set the file's metadata via the PdfPages object: d = pdf.infodict() d['Title'] = 'Multipage PDF Example' - d['Author'] = u'Jouni K. Sepp\xe4nen' + d['Author'] = 'Jouni K. Sepp\xe4nen' d['Subject'] = 'How to create a multipage pdf file and set its metadata' d['Keywords'] = 'PdfPages multipage keywords author title subject' d['CreationDate'] = datetime.datetime(2009, 11, 13) diff --git a/examples/misc/multiprocess_sgskip.py b/examples/misc/multiprocess_sgskip.py index 1cf1ecea1593..517fdc392556 100644 --- a/examples/misc/multiprocess_sgskip.py +++ b/examples/misc/multiprocess_sgskip.py @@ -8,26 +8,12 @@ Written by Robert Cimrman """ -from __future__ import print_function +import multiprocessing as mp import time -import numpy as np - -from multiprocessing import Process, Pipe - -# This example will likely not work with the native OSX backend. -# Uncomment the following lines to use the qt5 backend instead. -# -# import matplotlib -# matplotlib.use('qt5agg') -# -# Alternatively, with Python 3.4+ you may add the line -# -# import multiprocessing as mp; mp.set_start_method("forkserver") -# -# immediately after the ``if __name__ == "__main__"`` check. import matplotlib.pyplot as plt +import numpy as np # Fixing random state for reproducibility np.random.seed(19680801) @@ -91,13 +77,10 @@ def __call__(self, pipe): class NBPlot(object): def __init__(self): - self.plot_pipe, plotter_pipe = Pipe() + self.plot_pipe, plotter_pipe = mp.Pipe() self.plotter = ProcessPlotter() - self.plot_process = Process( - target=self.plotter, - args=(plotter_pipe,) - ) - self.plot_process.daemon = True + self.plot_process = mp.Process( + target=self.plotter, args=(plotter_pipe,), daemon=True) self.plot_process.start() def plot(self, finished=False): @@ -118,4 +101,6 @@ def main(): if __name__ == '__main__': + if plt.get_backend() == "MacOSX": + mp.set_start_method("forkserver") main() diff --git a/examples/misc/patheffect_demo.py b/examples/misc/patheffect_demo.py index 7319304e0315..0d3627ff5c4d 100644 --- a/examples/misc/patheffect_demo.py +++ b/examples/misc/patheffect_demo.py @@ -41,7 +41,7 @@ # shadow as a path effect ax3 = plt.subplot(133) p1, = ax3.plot([0, 1], [0, 1]) - leg = ax3.legend([p1], ["Line 1"], fancybox=True, loc=2) + leg = ax3.legend([p1], ["Line 1"], fancybox=True, loc='upper left') leg.legendPatch.set_path_effects([PathEffects.withSimplePatchShadow()]) plt.show() diff --git a/examples/misc/plotfile_demo.py b/examples/misc/plotfile_demo.py index b927b4870add..94bd1bd9b2bb 100644 --- a/examples/misc/plotfile_demo.py +++ b/examples/misc/plotfile_demo.py @@ -6,8 +6,6 @@ Example use of ``plotfile`` to plot data directly from a file. """ import matplotlib.pyplot as plt -import numpy as np - import matplotlib.cbook as cbook fname = cbook.get_sample_data('msft.csv', asfileobj=False) diff --git a/examples/misc/print_stdout_sgskip.py b/examples/misc/print_stdout_sgskip.py index da86a2c3cb16..69b0b33616d8 100644 --- a/examples/misc/print_stdout_sgskip.py +++ b/examples/misc/print_stdout_sgskip.py @@ -15,8 +15,4 @@ import matplotlib.pyplot as plt plt.plot([1, 2, 3]) - -if sys.version_info[0] >= 3: - plt.savefig(sys.stdout.buffer) -else: - plt.savefig(sys.stdout) +plt.savefig(sys.stdout.buffer) diff --git a/examples/misc/set_and_get.py b/examples/misc/set_and_get.py index 990fd6c5a3d0..3239d39518b0 100644 --- a/examples/misc/set_and_get.py +++ b/examples/misc/set_and_get.py @@ -67,7 +67,6 @@ these properties will be listed as 'fullname or aliasname'. """ -from __future__ import print_function import matplotlib.pyplot as plt import numpy as np diff --git a/examples/misc/svg_filter_line.py b/examples/misc/svg_filter_line.py index aaef954dd7ba..dc098f5276f9 100644 --- a/examples/misc/svg_filter_line.py +++ b/examples/misc/svg_filter_line.py @@ -9,7 +9,6 @@ support it. """ -from __future__ import print_function import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms @@ -62,7 +61,7 @@ # filter definition for a gaussian blur filter_def = """ - + diff --git a/examples/misc/svg_filter_pie.py b/examples/misc/svg_filter_pie.py index 47f24b7595a9..b97f13f1cbad 100644 --- a/examples/misc/svg_filter_pie.py +++ b/examples/misc/svg_filter_pie.py @@ -58,7 +58,7 @@ # that, Inkscape's exporting also may not support it. filter_def = """ - + diff --git a/examples/misc/tight_bbox_test.py b/examples/misc/tight_bbox_test.py index 3b4740d427ec..f9dbe3b00f2e 100644 --- a/examples/misc/tight_bbox_test.py +++ b/examples/misc/tight_bbox_test.py @@ -4,7 +4,6 @@ =============== """ -from __future__ import print_function import matplotlib.pyplot as plt import numpy as np diff --git a/examples/misc/transoffset.py b/examples/misc/transoffset.py index 127ca6bf6769..7328ce2103f3 100644 --- a/examples/misc/transoffset.py +++ b/examples/misc/transoffset.py @@ -23,7 +23,6 @@ import matplotlib.transforms as mtransforms import numpy as np -from matplotlib.transforms import offset_copy xs = np.arange(7) ys = xs**2 diff --git a/examples/misc/webapp_demo_sgskip.py b/examples/misc/webapp_demo_sgskip.py deleted file mode 100644 index 8a67dfb2af93..000000000000 --- a/examples/misc/webapp_demo_sgskip.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -=========== -Webapp Demo -=========== - -This example shows how to use the agg backend directly to create -images, which may be of use to web application developers who want -full control over their code without using the pyplot interface to -manage figures, figure closing etc. - -.. note:: - - It is not necessary to avoid using the pyplot interface in order to - create figures without a graphical front-end - simply setting - the backend to "Agg" would be sufficient. - -It is also worth noting that, because matplotlib can save figures to file-like -object, matplotlib can also be used inside a cgi-script *without* needing to -write a figure to disk. - -""" - -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.figure import Figure -import numpy as np - -# Fixing random state for reproducibility -np.random.seed(19680801) - - -def make_fig(): - """ - Make a figure and save it to "webagg.png". - - """ - fig = Figure() - ax = fig.add_subplot(1, 1, 1) - - ax.plot([1, 2, 3], 'ro--', markersize=12, markerfacecolor='g') - - # make a translucent scatter collection - x = np.random.rand(100) - y = np.random.rand(100) - area = np.pi * (10 * np.random.rand(100)) ** 2 # 0 to 10 point radii - c = ax.scatter(x, y, area) - c.set_alpha(0.5) - - # add some text decoration - ax.set_title('My first image') - ax.set_ylabel('Some numbers') - ax.set_xticks((.2, .4, .6, .8)) - labels = ax.set_xticklabels(('Bill', 'Fred', 'Ted', 'Ed')) - - # To set object properties, you can either iterate over the - # objects manually, or define you own set command, as in setapi - # above. - for label in labels: - label.set_rotation(45) - label.set_fontsize(12) - - FigureCanvasAgg(fig).print_png('webapp.png', dpi=150) - - -make_fig() diff --git a/examples/mplot3d/2dcollections3d.py b/examples/mplot3d/2dcollections3d.py index 21a7a4148749..589e1083f7f5 100644 --- a/examples/mplot3d/2dcollections3d.py +++ b/examples/mplot3d/2dcollections3d.py @@ -7,7 +7,9 @@ selective axes of a 3D plot. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import numpy as np import matplotlib.pyplot as plt diff --git a/examples/mplot3d/3d_bars.py b/examples/mplot3d/3d_bars.py index abacacc85dce..483dbcc4fb71 100644 --- a/examples/mplot3d/3d_bars.py +++ b/examples/mplot3d/3d_bars.py @@ -10,7 +10,8 @@ import numpy as np import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import # setup the figure and axes diff --git a/examples/mplot3d/bars3d.py b/examples/mplot3d/bars3d.py index 2cb2a5078b35..e30175ffac41 100644 --- a/examples/mplot3d/bars3d.py +++ b/examples/mplot3d/bars3d.py @@ -7,7 +7,9 @@ planes y=0, y=1, etc. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/custom_shaded_3d_surface.py b/examples/mplot3d/custom_shaded_3d_surface.py index 2c37bfae0fc9..366658856470 100644 --- a/examples/mplot3d/custom_shaded_3d_surface.py +++ b/examples/mplot3d/custom_shaded_3d_surface.py @@ -6,7 +6,9 @@ Demonstrates using custom hillshading in a 3D surface plot. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + from matplotlib import cbook from matplotlib import cm from matplotlib.colors import LightSource diff --git a/examples/mplot3d/hist3d.py b/examples/mplot3d/hist3d.py index 603645b651e0..4cab341a64a2 100644 --- a/examples/mplot3d/hist3d.py +++ b/examples/mplot3d/hist3d.py @@ -6,7 +6,9 @@ Demo of a histogram for 2 dimensional data as a bar graph in 3D. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np @@ -20,18 +22,14 @@ hist, xedges, yedges = np.histogram2d(x, y, bins=4, range=[[0, 4], [0, 4]]) # Construct arrays for the anchor positions of the 16 bars. -# Note: np.meshgrid gives arrays in (ny, nx) so we use 'F' to flatten xpos, -# ypos in column-major order. For numpy >= 1.7, we could instead call meshgrid -# with indexing='ij'. -xpos, ypos = np.meshgrid(xedges[:-1] + 0.25, yedges[:-1] + 0.25) -xpos = xpos.flatten('F') -ypos = ypos.flatten('F') -zpos = np.zeros_like(xpos) +xpos, ypos = np.meshgrid(xedges[:-1] + 0.25, yedges[:-1] + 0.25, indexing="ij") +xpos = xpos.ravel() +ypos = ypos.ravel() +zpos = 0 # Construct arrays with the dimensions for the 16 bars. -dx = 0.5 * np.ones_like(zpos) -dy = dx.copy() -dz = hist.flatten() +dx = dy = 0.5 * np.ones_like(zpos) +dz = hist.ravel() ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b', zsort='average') diff --git a/examples/mplot3d/lines3d.py b/examples/mplot3d/lines3d.py index 34ef9328c663..e0e45b1c051c 100644 --- a/examples/mplot3d/lines3d.py +++ b/examples/mplot3d/lines3d.py @@ -6,12 +6,14 @@ This example demonstrates plotting a parametric curve in 3D. ''' -import matplotlib as mpl -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import numpy as np import matplotlib.pyplot as plt -mpl.rcParams['legend.fontsize'] = 10 + +plt.rcParams['legend.fontsize'] = 10 fig = plt.figure() ax = fig.gca(projection='3d') diff --git a/examples/mplot3d/lorenz_attractor.py b/examples/mplot3d/lorenz_attractor.py index 47b21292b4cb..5a1328a769d3 100644 --- a/examples/mplot3d/lorenz_attractor.py +++ b/examples/mplot3d/lorenz_attractor.py @@ -15,7 +15,8 @@ import numpy as np import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import def lorenz(x, y, z, s=10, r=28, b=2.667): diff --git a/examples/mplot3d/mixed_subplots.py b/examples/mplot3d/mixed_subplots.py index fd1af313b6e3..0a13715aad1b 100644 --- a/examples/mplot3d/mixed_subplots.py +++ b/examples/mplot3d/mixed_subplots.py @@ -5,7 +5,9 @@ This example shows a how to plot a 2D and 3D plot on the same figure. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/offset.py b/examples/mplot3d/offset.py index da846c8cc26a..04c56ed2066e 100644 --- a/examples/mplot3d/offset.py +++ b/examples/mplot3d/offset.py @@ -13,7 +13,9 @@ automatically trigger it. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/pathpatch3d.py b/examples/mplot3d/pathpatch3d.py index 55d33dc94823..5976adf728a5 100644 --- a/examples/mplot3d/pathpatch3d.py +++ b/examples/mplot3d/pathpatch3d.py @@ -9,11 +9,11 @@ import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Circle, PathPatch -# register Axes3D class with matplotlib by importing Axes3D -from mpl_toolkits.mplot3d import Axes3D -import mpl_toolkits.mplot3d.art3d as art3d from matplotlib.text import TextPath from matplotlib.transforms import Affine2D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import +import mpl_toolkits.mplot3d.art3d as art3d def text3d(ax, xyz, s, zdir="z", size=None, angle=0, usetex=False, **kwargs): diff --git a/examples/mplot3d/polys3d.py b/examples/mplot3d/polys3d.py index a7c115785200..2f7769bb784c 100644 --- a/examples/mplot3d/polys3d.py +++ b/examples/mplot3d/polys3d.py @@ -8,7 +8,9 @@ of 'jagged stained glass' effect. """ -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + from matplotlib.collections import PolyCollection import matplotlib.pyplot as plt from matplotlib import colors as mcolors @@ -30,7 +32,7 @@ def polygon_under_graph(xlist, ylist): Construct the vertex list which defines the polygon filling the space under the (xlist, ylist) line graph. Assumes the xs are in ascending order. ''' - return [(xlist[0], 0.)] + list(zip(xlist, ylist)) + [(xlist[-1], 0.)] + return [(xlist[0], 0.), *zip(xlist, ylist), (xlist[-1], 0.)] fig = plt.figure() diff --git a/examples/mplot3d/quiver3d.py b/examples/mplot3d/quiver3d.py index 16ba7eab0190..6921b4a1d26c 100644 --- a/examples/mplot3d/quiver3d.py +++ b/examples/mplot3d/quiver3d.py @@ -6,7 +6,9 @@ Demonstrates plotting directional arrows at points on a 3d meshgrid. ''' -from mpl_toolkits.mplot3d import axes3d +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/rotate_axes3d.py b/examples/mplot3d/rotate_axes3d_sgskip.py similarity index 83% rename from examples/mplot3d/rotate_axes3d.py rename to examples/mplot3d/rotate_axes3d_sgskip.py index aa12f3ce9a9a..666bb68f6cd7 100644 --- a/examples/mplot3d/rotate_axes3d.py +++ b/examples/mplot3d/rotate_axes3d_sgskip.py @@ -6,6 +6,9 @@ A very simple animation of a rotating 3D plot. See wire3d_animation_demo for another simple example of animating a 3D plot. + +(This example is skipped when building the documentation gallery because it +intentionally takes a long time to run) ''' from mpl_toolkits.mplot3d import axes3d diff --git a/examples/mplot3d/scatter3d.py b/examples/mplot3d/scatter3d.py index 090ace56e1ed..d8c6a05606cd 100644 --- a/examples/mplot3d/scatter3d.py +++ b/examples/mplot3d/scatter3d.py @@ -6,7 +6,9 @@ Demonstration of a basic scatterplot in 3D. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/subplot3d.py b/examples/mplot3d/subplot3d.py index 9ece9f7ddc02..e9c1c3f2d712 100644 --- a/examples/mplot3d/subplot3d.py +++ b/examples/mplot3d/subplot3d.py @@ -7,10 +7,13 @@ ''' import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data from matplotlib import cm import numpy as np +from mpl_toolkits.mplot3d.axes3d import get_test_data +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + # set up a figure twice as wide as it is tall fig = plt.figure(figsize=plt.figaspect(0.5)) diff --git a/examples/mplot3d/surface3d.py b/examples/mplot3d/surface3d.py index 44133c271c02..eac122b6aa13 100644 --- a/examples/mplot3d/surface3d.py +++ b/examples/mplot3d/surface3d.py @@ -10,7 +10,9 @@ z axis tick labels. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt from matplotlib import cm from matplotlib.ticker import LinearLocator, FormatStrFormatter diff --git a/examples/mplot3d/surface3d_2.py b/examples/mplot3d/surface3d_2.py index bfc60ae563ac..fe3c2fe476ea 100644 --- a/examples/mplot3d/surface3d_2.py +++ b/examples/mplot3d/surface3d_2.py @@ -6,7 +6,9 @@ Demonstrates a very basic plot of a 3D surface using a solid color. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/surface3d_3.py b/examples/mplot3d/surface3d_3.py index 06ab93ab0a2c..d75dc6680152 100644 --- a/examples/mplot3d/surface3d_3.py +++ b/examples/mplot3d/surface3d_3.py @@ -6,9 +6,10 @@ Demonstrates plotting a 3D surface colored in a checkerboard pattern. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt -from matplotlib import cm from matplotlib.ticker import LinearLocator import numpy as np diff --git a/examples/mplot3d/surface3d_radial.py b/examples/mplot3d/surface3d_radial.py index 9125624eca45..521f6195330d 100644 --- a/examples/mplot3d/surface3d_radial.py +++ b/examples/mplot3d/surface3d_radial.py @@ -10,7 +10,9 @@ Example contributed by Armin Moser. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np diff --git a/examples/mplot3d/text3d.py b/examples/mplot3d/text3d.py index d26cf2b74eaf..ed4934faf5a1 100644 --- a/examples/mplot3d/text3d.py +++ b/examples/mplot3d/text3d.py @@ -16,7 +16,9 @@ ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt diff --git a/examples/mplot3d/tricontour3d.py b/examples/mplot3d/tricontour3d.py index 7e9e6971bb62..feb187cbaa16 100644 --- a/examples/mplot3d/tricontour3d.py +++ b/examples/mplot3d/tricontour3d.py @@ -9,8 +9,10 @@ tricontourf3d_demo shows the filled version of this example. """ +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D import matplotlib.tri as tri import numpy as np diff --git a/examples/mplot3d/tricontourf3d.py b/examples/mplot3d/tricontourf3d.py index eebb3ef62e6a..d25b2dbd1ea5 100644 --- a/examples/mplot3d/tricontourf3d.py +++ b/examples/mplot3d/tricontourf3d.py @@ -9,8 +9,10 @@ tricontour3d_demo shows the unfilled version of this example. """ +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D import matplotlib.tri as tri import numpy as np diff --git a/examples/mplot3d/trisurf3d.py b/examples/mplot3d/trisurf3d.py index 192d4eb8aa06..070a3154f2cb 100644 --- a/examples/mplot3d/trisurf3d.py +++ b/examples/mplot3d/trisurf3d.py @@ -6,7 +6,9 @@ Plot a 3D surface with a triangular mesh. ''' -from mpl_toolkits.mplot3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np @@ -16,10 +18,7 @@ # Make radii and angles spaces (radius r=0 omitted to eliminate duplication). radii = np.linspace(0.125, 1.0, n_radii) -angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False) - -# Repeat all angles for each radius. -angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) +angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)[..., np.newaxis] # Convert polar (radii, angles) coords to cartesian (x, y) coords. # (0, 0) is manually added at this stage, so there will be no duplicate diff --git a/examples/mplot3d/trisurf3d_2.py b/examples/mplot3d/trisurf3d_2.py index 24d19e60b498..b948c1b14b5d 100644 --- a/examples/mplot3d/trisurf3d_2.py +++ b/examples/mplot3d/trisurf3d_2.py @@ -12,9 +12,11 @@ import numpy as np import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D import matplotlib.tri as mtri +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + fig = plt.figure(figsize=plt.figaspect(0.5)) diff --git a/examples/mplot3d/voxels.py b/examples/mplot3d/voxels.py index 76cf64c33a00..4ba96fff6c65 100644 --- a/examples/mplot3d/voxels.py +++ b/examples/mplot3d/voxels.py @@ -8,7 +8,10 @@ import matplotlib.pyplot as plt import numpy as np -from mpl_toolkits.mplot3d import Axes3D + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + # prepare some coordinates x, y, z = np.indices((8, 8, 8)) diff --git a/examples/mplot3d/voxels_numpy_logo.py b/examples/mplot3d/voxels_numpy_logo.py index 648a3cff7822..38b00b49f4de 100644 --- a/examples/mplot3d/voxels_numpy_logo.py +++ b/examples/mplot3d/voxels_numpy_logo.py @@ -7,7 +7,9 @@ ''' import matplotlib.pyplot as plt import numpy as np -from mpl_toolkits.mplot3d import Axes3D + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import def explode(data): diff --git a/examples/mplot3d/voxels_rgb.py b/examples/mplot3d/voxels_rgb.py index 1b577cad47fe..7b012b2a61f8 100644 --- a/examples/mplot3d/voxels_rgb.py +++ b/examples/mplot3d/voxels_rgb.py @@ -8,7 +8,9 @@ import matplotlib.pyplot as plt import numpy as np -from mpl_toolkits.mplot3d import Axes3D + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import def midpoints(x): diff --git a/examples/mplot3d/voxels_torus.py b/examples/mplot3d/voxels_torus.py index 4f60e31403d8..3112f82792da 100644 --- a/examples/mplot3d/voxels_torus.py +++ b/examples/mplot3d/voxels_torus.py @@ -9,7 +9,9 @@ import matplotlib.pyplot as plt import matplotlib.colors import numpy as np -from mpl_toolkits.mplot3d import Axes3D + +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import def midpoints(x): diff --git a/examples/mplot3d/wire3d_animation.py b/examples/mplot3d/wire3d_animation_sgskip.py similarity index 81% rename from examples/mplot3d/wire3d_animation.py rename to examples/mplot3d/wire3d_animation_sgskip.py index 1083f006436f..4e727817264c 100644 --- a/examples/mplot3d/wire3d_animation.py +++ b/examples/mplot3d/wire3d_animation_sgskip.py @@ -4,11 +4,15 @@ ========================== A very simple 'animation' of a 3D plot. See also rotate_axes3d_demo. + +(This example is skipped when building the documentation gallery because it +intentionally takes a long time to run) """ -from __future__ import print_function -from mpl_toolkits.mplot3d import axes3d +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + import matplotlib.pyplot as plt import numpy as np import time diff --git a/examples/pie_and_polar_charts/pie_and_donut_labels.py b/examples/pie_and_polar_charts/pie_and_donut_labels.py index 49e75606cd2c..a04b087c5e60 100644 --- a/examples/pie_and_polar_charts/pie_and_donut_labels.py +++ b/examples/pie_and_polar_charts/pie_and_donut_labels.py @@ -17,7 +17,7 @@ # # We can provide a function to the ``autopct`` argument, which will expand # automatic percentage labeling by showing absolute values; we calculate -# the latter back from realtive data and the known sum of all values. +# the latter back from relative data and the known sum of all values. # # We then create the pie and store the returned objects for later. # The first returned element of the returned tuple is a list of the wedges. diff --git a/examples/pie_and_polar_charts/pie_demo2.py b/examples/pie_and_polar_charts/pie_demo2.py index f5c4dae4cf74..fc173eda78e3 100644 --- a/examples/pie_and_polar_charts/pie_demo2.py +++ b/examples/pie_and_polar_charts/pie_demo2.py @@ -3,60 +3,58 @@ Pie Demo2 ========= -Make a pie charts of varying size - see -https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.pie for the -docstring. - -This example shows a basic pie charts with labels optional features, -like autolabeling the percentage, offsetting a slice with "explode" -and adding a shadow, in different sizes. +Make a pie charts using :meth:`~.axes.Axes.pie`. +This example demonstrates some pie chart features like labels, varying size, +autolabeling the percentage, offsetting a slice and adding a shadow. """ + import matplotlib.pyplot as plt -from matplotlib.gridspec import GridSpec # Some data - labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' fracs = [15, 30, 45, 10] -explode = (0, 0.05, 0, 0) - -# Make square figures and axes - -the_grid = GridSpec(2, 2) - -plt.subplot(the_grid[0, 0], aspect=1) - -plt.pie(fracs, labels=labels, autopct='%1.1f%%', shadow=True) - -plt.subplot(the_grid[0, 1], aspect=1) - -plt.pie(fracs, explode=explode, labels=labels, autopct='%.0f%%', shadow=True) - -plt.subplot(the_grid[1, 0], aspect=1) - -patches, texts, autotexts = plt.pie(fracs, labels=labels, - autopct='%.0f%%', - shadow=True, radius=0.5) - -# Make the labels on the small plot easier to read. -for t in texts: - t.set_size('smaller') -for t in autotexts: - t.set_size('x-small') -autotexts[0].set_color('y') - -plt.subplot(the_grid[1, 1], aspect=1) - -# Turn off shadow for tiny plot with exploded slice. -patches, texts, autotexts = plt.pie(fracs, explode=explode, - labels=labels, autopct='%.0f%%', - shadow=False, radius=0.5) -for t in texts: - t.set_size('smaller') -for t in autotexts: - t.set_size('x-small') -autotexts[0].set_color('y') +# Make figure and axes +fig, axs = plt.subplots(2, 2) + +# A standard pie plot +axs[0, 0].pie(fracs, labels=labels, autopct='%1.1f%%', shadow=True) + +# Shift the second slice using explode +axs[0, 1].pie(fracs, labels=labels, autopct='%.0f%%', shadow=True, + explode=(0, 0.1, 0, 0)) + +# Adapt radius and text size for a smaller pie +patches, texts, autotexts = axs[1, 0].pie(fracs, labels=labels, + autopct='%.0f%%', + textprops={'size': 'smaller'}, + shadow=True, radius=0.5) +# Make percent texts even smaller +plt.setp(autotexts, size='x-small') +autotexts[0].set_color('white') + +# Use a smaller explode and turn of the shadow for better visibility +patches, texts, autotexts = axs[1, 1].pie(fracs, labels=labels, + autopct='%.0f%%', + textprops={'size': 'smaller'}, + shadow=False, radius=0.5, + explode=(0, 0.05, 0, 0)) +plt.setp(autotexts, size='x-small') +autotexts[0].set_color('white') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.pie +matplotlib.pyplot.pie diff --git a/examples/pyplots/align_ylabels.py b/examples/pyplots/align_ylabels.py index 63558b0c8b45..09711eef28aa 100644 --- a/examples/pyplots/align_ylabels.py +++ b/examples/pyplots/align_ylabels.py @@ -1,38 +1,90 @@ """ -============= -Align Ylabels -============= +============== +Align y-labels +============== + +Two methods are shown here, one using a short call to `.Figure.align_ylabels` +and the second a manual way to align the labels. """ import numpy as np import matplotlib.pyplot as plt -box = dict(facecolor='yellow', pad=5, alpha=0.2) -fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2) +def make_plot(axs): + box = dict(facecolor='yellow', pad=5, alpha=0.2) + + # Fixing random state for reproducibility + np.random.seed(19680801) + ax1 = axs[0, 0] + ax1.plot(2000*np.random.rand(10)) + ax1.set_title('ylabels not aligned') + ax1.set_ylabel('misaligned 1', bbox=box) + ax1.set_ylim(0, 2000) + + ax3 = axs[1, 0] + ax3.set_ylabel('misaligned 2', bbox=box) + ax3.plot(np.random.rand(10)) + + ax2 = axs[0, 1] + ax2.set_title('ylabels aligned') + ax2.plot(2000*np.random.rand(10)) + ax2.set_ylabel('aligned 1', bbox=box) + ax2.set_ylim(0, 2000) + + ax4 = axs[1, 1] + ax4.plot(np.random.rand(10)) + ax4.set_ylabel('aligned 2', bbox=box) + + +# Plot 1: +fig, axs = plt.subplots(2, 2) fig.subplots_adjust(left=0.2, wspace=0.6) +make_plot(axs) -# Fixing random state for reproducibility -np.random.seed(19680801) +# just align the last column of axes: +fig.align_ylabels(axs[:, 1]) +plt.show() -ax1.plot(2000*np.random.rand(10)) -ax1.set_title('ylabels not aligned') -ax1.set_ylabel('misaligned 1', bbox=box) -ax1.set_ylim(0, 2000) +############################################################################# +# +# .. seealso:: +# `.Figure.align_ylabels` and `.Figure.align_labels` for a direct method +# of doing the same thing. +# Also :doc:`/gallery/subplots_axes_and_figures/align_labels_demo` +# +# +# Or we can manually align the axis labels between subplots manually using the +# `set_label_coords` method of the y-axis object. Note this requires we know +# a good offset value which is hardcoded. -ax3.set_ylabel('misaligned 2',bbox=box) -ax3.plot(np.random.rand(10)) +fig, axs = plt.subplots(2, 2) +fig.subplots_adjust(left=0.2, wspace=0.6) -labelx = -0.3 # axes coords +make_plot(axs) -ax2.set_title('ylabels aligned') -ax2.plot(2000*np.random.rand(10)) -ax2.set_ylabel('aligned 1', bbox=box) -ax2.yaxis.set_label_coords(labelx, 0.5) -ax2.set_ylim(0, 2000) +labelx = -0.3 # axes coords -ax4.plot(np.random.rand(10)) -ax4.set_ylabel('aligned 2', bbox=box) -ax4.yaxis.set_label_coords(labelx, 0.5) +for j in range(2): + axs[j, 1].yaxis.set_label_coords(labelx, 0.5) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.figure.Figure.align_ylabels +matplotlib.axis.Axis.set_label_coords +matplotlib.axes.Axes.plot +matplotlib.pyplot.plot +matplotlib.axes.Axes.set_title +matplotlib.axes.Axes.set_ylabel +matplotlib.axes.Axes.set_ylim diff --git a/examples/pyplots/annotate_transform.py b/examples/pyplots/annotate_transform.py index 2fd6d3722a8d..249dee2efdc1 100644 --- a/examples/pyplots/annotate_transform.py +++ b/examples/pyplots/annotate_transform.py @@ -3,6 +3,9 @@ Annotate Transform ================== +This example shows how to use different coordinate systems for annotations. +For a complete overview of the annotation capabilities, also see the +:doc:`annotation tutorial`. """ import numpy as np import matplotlib.pyplot as plt @@ -37,3 +40,18 @@ plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.transforms.Transform.transform_point +matplotlib.axes.Axes.annotate +matplotlib.pyplot.annotate diff --git a/examples/pyplots/annotation_basic.py b/examples/pyplots/annotation_basic.py index c1ad76dbcc2f..1b2e6ec1a09c 100644 --- a/examples/pyplots/annotation_basic.py +++ b/examples/pyplots/annotation_basic.py @@ -6,6 +6,8 @@ This example shows how to annotate a plot with an arrow pointing to provided coordinates. We modify the defaults of the arrow, to "shrink" it. +For a complete overview of the annotation capabilities, also see the +:doc:`annotation tutorial`. """ import numpy as np import matplotlib.pyplot as plt @@ -21,3 +23,17 @@ ) ax.set_ylim(-2, 2) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.annotate +matplotlib.pyplot.annotate diff --git a/examples/pyplots/annotation_polar.py b/examples/pyplots/annotation_polar.py index 129291aae167..e900c70d102d 100644 --- a/examples/pyplots/annotation_polar.py +++ b/examples/pyplots/annotation_polar.py @@ -3,6 +3,10 @@ Annotation Polar ================ +This example shows how to create an annotation on a polar graph. + +For a complete overview of the annotation capabilities, also see the +:doc:`annotation tutorial`. """ import numpy as np import matplotlib.pyplot as plt @@ -25,3 +29,18 @@ verticalalignment='bottom', ) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.projections.polar +matplotlib.axes.Axes.annotate +matplotlib.pyplot.annotate diff --git a/examples/pyplots/auto_subplots_adjust.py b/examples/pyplots/auto_subplots_adjust.py index 55397f3f3475..574fb15a8bb3 100644 --- a/examples/pyplots/auto_subplots_adjust.py +++ b/examples/pyplots/auto_subplots_adjust.py @@ -3,6 +3,13 @@ Auto Subplots Adjust ==================== +Automatically adjust subplot parameters. This example shows a way to determine +a subplot parameter from the extent of the ticklabels using a callback on the +:doc:`draw_event`. + +Note that a similar result would be achieved using `~.Figure.tight_layout` +or `~.Figure.contrained_layout`; this example shows how one could customize +the subplot parameter adjustment. """ import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms @@ -33,3 +40,21 @@ def on_draw(event): plt.show() +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.artist.Artist.get_window_extent +matplotlib.transforms.Bbox +matplotlib.transforms.Bbox.inverse_transformed +matplotlib.transforms.Bbox.union +matplotlib.figure.Figure.subplots_adjust +matplotlib.figure.SubplotParams +matplotlib.backend_bases.FigureCanvasBase.mpl_connect diff --git a/examples/pyplots/boxplot_demo_pyplot.py b/examples/pyplots/boxplot_demo_pyplot.py index 04e349a8dae3..26e4fcd9b72b 100644 --- a/examples/pyplots/boxplot_demo_pyplot.py +++ b/examples/pyplots/boxplot_demo_pyplot.py @@ -17,7 +17,7 @@ center = np.ones(25) * 50 flier_high = np.random.rand(10) * 100 + 100 flier_low = np.random.rand(10) * -100 -data = np.concatenate((spread, center, flier_high, flier_low), 0) +data = np.concatenate((spread, center, flier_high, flier_low)) ############################################################################### @@ -64,7 +64,7 @@ center = np.ones(25) * 40 flier_high = np.random.rand(10) * 100 + 100 flier_low = np.random.rand(10) * -100 -d2 = np.concatenate((spread, center, flier_high, flier_low), 0) +d2 = np.concatenate((spread, center, flier_high, flier_low)) data.shape = (-1, 1) d2.shape = (-1, 1) @@ -80,3 +80,17 @@ ax7.boxplot(data) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.boxplot +matplotlib.pyplot.boxplot diff --git a/examples/pyplots/compound_path_demo.py b/examples/pyplots/compound_path_demo.py deleted file mode 100644 index 6310cf8b021f..000000000000 --- a/examples/pyplots/compound_path_demo.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -================== -Compound Path Demo -================== - -""" -import numpy as np - -import matplotlib.pyplot as plt -import matplotlib.patches as patches -import matplotlib.path as path - -fig = plt.figure() -ax = fig.add_subplot(111) - -# Fixing random state for reproducibility -np.random.seed(19680801) - -# histogram our data with numpy -data = np.random.randn(1000) -n, bins = np.histogram(data, 100) - -# get the corners of the rectangles for the histogram -left = np.array(bins[:-1]) -right = np.array(bins[1:]) -bottom = np.zeros(len(left)) -top = bottom + n -nrects = len(left) - -nverts = nrects*(1+3+1) -verts = np.zeros((nverts, 2)) -codes = np.ones(nverts, int) * path.Path.LINETO -codes[0::5] = path.Path.MOVETO -codes[4::5] = path.Path.CLOSEPOLY -verts[0::5,0] = left -verts[0::5,1] = bottom -verts[1::5,0] = left -verts[1::5,1] = top -verts[2::5,0] = right -verts[2::5,1] = top -verts[3::5,0] = right -verts[3::5,1] = bottom - -barpath = path.Path(verts, codes) -patch = patches.PathPatch(barpath, facecolor='green', edgecolor='yellow', alpha=0.5) -ax.add_patch(patch) - -ax.set_xlim(left[0], right[-1]) -ax.set_ylim(bottom.min(), top.max()) - -plt.show() diff --git a/examples/pyplots/dollar_ticks.py b/examples/pyplots/dollar_ticks.py index 2f8c864e977c..e980de104848 100644 --- a/examples/pyplots/dollar_ticks.py +++ b/examples/pyplots/dollar_ticks.py @@ -3,6 +3,7 @@ Dollar Ticks ============ +Use a `~.ticker.FormatStrFormatter` to prepend dollar signs on y axis labels. """ import numpy as np import matplotlib.pyplot as plt @@ -23,3 +24,20 @@ tick.label2.set_color('green') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.ticker +matplotlib.ticker.FormatStrFormatter +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.Axis.get_major_ticks +matplotlib.axis.Tick diff --git a/examples/pyplots/fig_axes_customize_simple.py b/examples/pyplots/fig_axes_customize_simple.py index 0152be313c4e..47d8eafbc647 100644 --- a/examples/pyplots/fig_axes_customize_simple.py +++ b/examples/pyplots/fig_axes_customize_simple.py @@ -3,8 +3,9 @@ Fig Axes Customize Simple ========================= +Customize the background, labels and ticks of a simple plot. """ -import numpy as np + import matplotlib.pyplot as plt ############################################################################### @@ -32,3 +33,25 @@ line.set_markeredgewidth(3) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axis.Axis.get_ticklabels +matplotlib.axis.Axis.get_ticklines +matplotlib.text.Text.set_rotation +matplotlib.text.Text.set_fontsize +matplotlib.text.Text.set_color +matplotlib.lines.Line2D +matplotlib.lines.Line2D.set_color +matplotlib.lines.Line2D.set_markersize +matplotlib.lines.Line2D.set_markeredgewidth +matplotlib.patches.Patch.set_facecolor diff --git a/examples/pyplots/fig_axes_labels_simple.py b/examples/pyplots/fig_axes_labels_simple.py index b36967912c89..2d8e6a14a9b8 100644 --- a/examples/pyplots/fig_axes_labels_simple.py +++ b/examples/pyplots/fig_axes_labels_simple.py @@ -3,6 +3,7 @@ Fig Axes Labels Simple ====================== +Label the axes of a plot. """ import numpy as np import matplotlib.pyplot as plt @@ -26,3 +27,21 @@ ax2.set_xlabel('time (s)') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.set_xlabel +matplotlib.axes.Axes.set_ylabel +matplotlib.axes.Axes.set_title +matplotlib.axes.Axes.plot +matplotlib.axes.Axes.hist +matplotlib.figure.Figure.add_axes diff --git a/examples/pyplots/fig_x.py b/examples/pyplots/fig_x.py index ecd28e2f5740..d8a8c1dfaa3d 100644 --- a/examples/pyplots/fig_x.py +++ b/examples/pyplots/fig_x.py @@ -3,8 +3,8 @@ Fig X ===== +Add lines to a figure (without axes). """ -import numpy as np import matplotlib.pyplot as plt import matplotlib.lines as lines @@ -18,3 +18,18 @@ fig.lines.extend([l1, l2]) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.figure +matplotlib.lines +matplotlib.lines.Line2D diff --git a/examples/pyplots/pyplot_formatstr.py b/examples/pyplots/pyplot_formatstr.py index e558fb4f35ca..057cba15b92f 100644 --- a/examples/pyplots/pyplot_formatstr.py +++ b/examples/pyplots/pyplot_formatstr.py @@ -3,8 +3,24 @@ Pyplot Formatstr ================ +Use a format string to colorize a `~matplotlib.axes.Axes.plot` and set its +markers. """ import matplotlib.pyplot as plt plt.plot([1,2,3,4], [1,4,9,16], 'ro') plt.axis([0, 6, 0, 20]) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.plot +matplotlib.axes.Axes.plot diff --git a/examples/pyplots/pyplot_mathtext.py b/examples/pyplots/pyplot_mathtext.py index 4924b1a4665d..709488bcc933 100644 --- a/examples/pyplots/pyplot_mathtext.py +++ b/examples/pyplots/pyplot_mathtext.py @@ -3,6 +3,8 @@ Pyplot Mathtext =============== +Use mathematical expressions in text labels. For an overview over MathText +see :doc:`/tutorials/text/mathtext`. """ import numpy as np import matplotlib.pyplot as plt @@ -17,3 +19,17 @@ plt.xlabel('time (s)') plt.ylabel('volts (mV)') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.text +matplotlib.axes.Axes.text diff --git a/examples/pyplots/pyplot_scales.py b/examples/pyplots/pyplot_scales.py index 944f339e83b7..a45647b88dda 100644 --- a/examples/pyplots/pyplot_scales.py +++ b/examples/pyplots/pyplot_scales.py @@ -3,6 +3,9 @@ Pyplot Scales ============= +Create plots on different scales. Here a linear, a logarithmic, a symmetric +logarithmic and a logit scale are shown. For further examples also see the +:ref:`scales_examples` section of the gallery. """ import numpy as np import matplotlib.pyplot as plt @@ -59,3 +62,21 @@ wspace=0.35) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplot +matplotlib.pyplot.subplots_adjust +matplotlib.pyplot.gca +matplotlib.pyplot.yscale +matplotlib.ticker.NullFormatter +matplotlib.axis.Axis.set_minor_formatter diff --git a/examples/pyplots/pyplot_simple.py b/examples/pyplots/pyplot_simple.py index c97c40d6a143..6ad0483ebe2e 100644 --- a/examples/pyplots/pyplot_simple.py +++ b/examples/pyplots/pyplot_simple.py @@ -3,8 +3,24 @@ Pyplot Simple ============= +A most simple plot, where a list of numbers is plotted against their index. """ import matplotlib.pyplot as plt plt.plot([1,2,3,4]) plt.ylabel('some numbers') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.plot +matplotlib.pyplot.ylabel +matplotlib.pyplot.show diff --git a/examples/pyplots/pyplot_text.py b/examples/pyplots/pyplot_text.py index b070e06595e6..4492ce438c01 100644 --- a/examples/pyplots/pyplot_text.py +++ b/examples/pyplots/pyplot_text.py @@ -24,3 +24,21 @@ plt.axis([40, 160, 0, 0.03]) plt.grid(True) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.hist +matplotlib.pyplot.xlabel +matplotlib.pyplot.ylabel +matplotlib.pyplot.text +matplotlib.pyplot.grid +matplotlib.pyplot.show diff --git a/examples/pyplots/pyplot_three.py b/examples/pyplots/pyplot_three.py index 8576b36802b1..9026e4acae1f 100644 --- a/examples/pyplots/pyplot_three.py +++ b/examples/pyplots/pyplot_three.py @@ -3,6 +3,7 @@ Pyplot Three ============ +Plot three line plots in a single call to `~matplotlib.pyplot.plot`. """ import numpy as np import matplotlib.pyplot as plt @@ -13,3 +14,17 @@ # red dashes, blue squares and green triangles plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.plot +matplotlib.axes.Axes.plot diff --git a/examples/pyplots/pyplot_two_subplots.py b/examples/pyplots/pyplot_two_subplots.py index dbc64d628b5d..964e1fdfd6e7 100644 --- a/examples/pyplots/pyplot_two_subplots.py +++ b/examples/pyplots/pyplot_two_subplots.py @@ -3,6 +3,7 @@ Pyplot Two Subplots =================== +Create a figure with two subplots with `pyplot.subplot`. """ import numpy as np import matplotlib.pyplot as plt @@ -20,3 +21,17 @@ def f(t): plt.subplot(212) plt.plot(t2, np.cos(2*np.pi*t2), 'r--') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.figure +matplotlib.pyplot.subplot diff --git a/examples/pyplots/text_commands.py b/examples/pyplots/text_commands.py index 0d4e3d559a45..d7df2ecfca3d 100644 --- a/examples/pyplots/text_commands.py +++ b/examples/pyplots/text_commands.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ============= Text Commands @@ -6,6 +5,7 @@ Plotting text of many different kinds. """ + import matplotlib.pyplot as plt fig = plt.figure() @@ -23,7 +23,7 @@ ax.text(2, 6, r'an equation: $E=mc^2$', fontsize=15) -ax.text(3, 2, u'unicode: Institut f\374r Festk\366rperphysik') +ax.text(3, 2, 'unicode: Institut f\374r Festk\366rperphysik') ax.text(0.95, 0.01, 'colored text in axes coords', verticalalignment='bottom', horizontalalignment='right', @@ -38,3 +38,23 @@ ax.axis([0, 10, 0, 10]) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.figure.Figure.suptitle +matplotlib.figure.Figure.add_subplot +matplotlib.figure.Figure.subplots_adjust +matplotlib.axes.Axes.set_title +matplotlib.axes.Axes.set_xlabel +matplotlib.axes.Axes.set_ylabel +matplotlib.axes.Axes.text +matplotlib.axes.Axes.annotate diff --git a/examples/pyplots/text_layout.py b/examples/pyplots/text_layout.py index 48ea9a4a5676..4e28cf98904c 100644 --- a/examples/pyplots/text_layout.py +++ b/examples/pyplots/text_layout.py @@ -3,6 +3,7 @@ Text Layout =========== +Create text with different alignment and rotation. """ import matplotlib.pyplot as plt import matplotlib.patches as patches @@ -81,3 +82,17 @@ ax.set_axis_off() plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.text +matplotlib.pyplot.text diff --git a/examples/pyplots/whats_new_1_subplot3d.py b/examples/pyplots/whats_new_1_subplot3d.py index 7b4bd33a8ed8..96886b0e1f8c 100644 --- a/examples/pyplots/whats_new_1_subplot3d.py +++ b/examples/pyplots/whats_new_1_subplot3d.py @@ -3,8 +3,11 @@ Whats New 1 Subplot3d ===================== +Create two three-dimensional plots in the same figure. """ -from mpl_toolkits.mplot3d.axes3d import Axes3D +# This import registers the 3D projection, but is otherwise unused. +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + from matplotlib import cm #from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter import matplotlib.pyplot as plt @@ -34,3 +37,19 @@ plt.show() +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +import mpl_toolkits +matplotlib.figure.Figure.add_subplot +mpl_toolkits.mplot3d.axes3d.Axes3D.plot_surface +mpl_toolkits.mplot3d.axes3d.Axes3D.plot_wireframe +mpl_toolkits.mplot3d.axes3d.Axes3D.set_zlim3d diff --git a/examples/pyplots/whats_new_98_4_fancy.py b/examples/pyplots/whats_new_98_4_fancy.py index 24691a8abbb6..b8c1d2fbdf90 100644 --- a/examples/pyplots/whats_new_98_4_fancy.py +++ b/examples/pyplots/whats_new_98_4_fancy.py @@ -3,6 +3,7 @@ Whats New 0.98.4 Fancy ====================== +Create fancy box and arrow styles. """ import matplotlib.patches as mpatch import matplotlib.pyplot as plt @@ -58,3 +59,22 @@ def make_arrowstyles(ax): plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.patches +matplotlib.patches.BoxStyle +matplotlib.patches.BoxStyle.get_styles +matplotlib.patches.ArrowStyle +matplotlib.patches.ArrowStyle.get_styles +matplotlib.axes.Axes.text +matplotlib.axes.Axes.annotate diff --git a/examples/pyplots/whats_new_98_4_fill_between.py b/examples/pyplots/whats_new_98_4_fill_between.py index ed4b7f4ac7d5..8719a5428e03 100644 --- a/examples/pyplots/whats_new_98_4_fill_between.py +++ b/examples/pyplots/whats_new_98_4_fill_between.py @@ -1,20 +1,34 @@ """ -============================= -Whats New 0.98.4 Fill Between -============================= +============ +Fill Between +============ +Fill the area between two curves. """ import matplotlib.pyplot as plt import numpy as np -x = np.arange(0.0, 2, 0.01) -y1 = np.sin(2*np.pi*x) -y2 = 1.2*np.sin(4*np.pi*x) +x = np.arange(-5, 5, 0.01) +y1 = -5*x*x + x + 10 +y2 = 5*x*x + x fig, ax = plt.subplots() ax.plot(x, y1, x, y2, color='black') -ax.fill_between(x, y1, y2, where=y2>y1, facecolor='green') -ax.fill_between(x, y1, y2, where=y2<=y1, facecolor='red') -ax.set_title('fill between where') +ax.fill_between(x, y1, y2, where=y2 >y1, facecolor='yellow', alpha=0.5) +ax.fill_between(x, y1, y2, where=y2 <=y1, facecolor='red', alpha=0.5) +ax.set_title('Fill Between') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.fill_between diff --git a/examples/pyplots/whats_new_98_4_legend.py b/examples/pyplots/whats_new_98_4_legend.py index c60d91588cb0..ed534ca18992 100644 --- a/examples/pyplots/whats_new_98_4_legend.py +++ b/examples/pyplots/whats_new_98_4_legend.py @@ -3,6 +3,7 @@ Whats New 0.98.4 Legend ======================= +Create a legend and tweak it with a shadow and a box. """ import matplotlib.pyplot as plt import numpy as np @@ -18,3 +19,19 @@ plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.legend +matplotlib.pyplot.legend +matplotlib.legend.Legend +matplotlib.legend.Legend.get_frame diff --git a/examples/pyplots/whats_new_99_axes_grid.py b/examples/pyplots/whats_new_99_axes_grid.py index 73c75daf5798..c77f23f1213f 100644 --- a/examples/pyplots/whats_new_99_axes_grid.py +++ b/examples/pyplots/whats_new_99_axes_grid.py @@ -3,6 +3,7 @@ Whats New 0.99 Axes Grid ======================== +Create RGB composite images. """ import numpy as np import matplotlib.pyplot as plt @@ -47,6 +48,18 @@ def get_rgb(): ax.RGB.set_xlim(0., 9.5) ax.RGB.set_ylim(0.9, 10.6) - -plt.draw() plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import mpl_toolkits +mpl_toolkits.axes_grid1.axes_rgb.RGBAxes +mpl_toolkits.axes_grid1.axes_rgb.RGBAxes.imshow_rgb diff --git a/examples/pyplots/whats_new_99_mplot3d.py b/examples/pyplots/whats_new_99_mplot3d.py index f3037740e2e1..6a85c0a383c4 100644 --- a/examples/pyplots/whats_new_99_mplot3d.py +++ b/examples/pyplots/whats_new_99_mplot3d.py @@ -3,6 +3,7 @@ Whats New 0.99 Mplot3d ====================== +Create a 3D surface plot. """ import numpy as np import matplotlib.pyplot as plt @@ -20,3 +21,17 @@ ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.viridis) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import mpl_toolkits +mpl_toolkits.mplot3d.Axes3D +mpl_toolkits.mplot3d.Axes3D.plot_surface diff --git a/examples/pyplots/whats_new_99_spines.py b/examples/pyplots/whats_new_99_spines.py index 4d0d5bc09f74..1c8461497a98 100644 --- a/examples/pyplots/whats_new_99_spines.py +++ b/examples/pyplots/whats_new_99_spines.py @@ -50,3 +50,22 @@ def adjust_spines(ax,spines): adjust_spines(ax,['bottom']) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axis.Axis.set_ticks +matplotlib.axis.XAxis.set_ticks_position +matplotlib.axis.YAxis.set_ticks_position +matplotlib.spines +matplotlib.spines.Spine +matplotlib.spines.Spine.set_color +matplotlib.spines.Spine.set_position diff --git a/examples/recipes/transparent_legends.py b/examples/recipes/transparent_legends.py index 3aa6f2ee6066..3289e2f6d4ab 100644 --- a/examples/recipes/transparent_legends.py +++ b/examples/recipes/transparent_legends.py @@ -9,10 +9,10 @@ ax.legend(loc='upper right') -Other times you don't know where your data is, and loc='best' will try -and place the legend:: +Other times you don't know where your data is, and the default loc='best' +will try and place the legend:: - ax.legend(loc='best') + ax.legend() but still, your legend may overlap your data, and in these cases it's nice to make the legend frame transparent. @@ -27,7 +27,7 @@ ax.plot(np.random.rand(300), 's-', label='uniform distribution') ax.set_ylim(-3, 3) -ax.legend(loc='best', fancybox=True, framealpha=0.5) +ax.legend(fancybox=True, framealpha=0.5) ax.set_title('fancy, transparent legends') plt.show() diff --git a/examples/scales/aspect_loglog.py b/examples/scales/aspect_loglog.py index c0d4f3ccc73d..90c0422ca389 100644 --- a/examples/scales/aspect_loglog.py +++ b/examples/scales/aspect_loglog.py @@ -23,5 +23,4 @@ ax2.set_aspect(1) ax2.set_title("adjustable = datalim") -plt.draw() plt.show() diff --git a/examples/scales/custom_scale.py b/examples/scales/custom_scale.py index 574f90ebad80..b19025e4c0d4 100644 --- a/examples/scales/custom_scale.py +++ b/examples/scales/custom_scale.py @@ -3,13 +3,10 @@ Custom scale ============ -Create a custom scale, by implementing the -scaling use for latitude data in a Mercator Projection. +Create a custom scale, by implementing the scaling use for latitude data in a +Mercator Projection. """ - -from __future__ import unicode_literals - import numpy as np from numpy import ma from matplotlib import scale as mscale @@ -47,7 +44,7 @@ class MercatorLatitudeScale(mscale.ScaleBase): # scale. name = 'mercator' - def __init__(self, axis, **kwargs): + def __init__(self, axis, *, thresh=np.deg2rad(85), **kwargs): """ Any keyword arguments passed to ``set_xscale`` and ``set_yscale`` will be passed along to the scale's @@ -56,8 +53,7 @@ def __init__(self, axis, **kwargs): thresh: The degree above which to crop the data. """ mscale.ScaleBase.__init__(self) - thresh = kwargs.pop("thresh", np.radians(85)) - if thresh >= np.pi / 2.0: + if thresh >= np.pi / 2: raise ValueError("thresh must be less than pi/2") self.thresh = thresh diff --git a/examples/scales/log_demo.py b/examples/scales/log_demo.py index 3fde3d0a6d6e..19bfb858983e 100644 --- a/examples/scales/log_demo.py +++ b/examples/scales/log_demo.py @@ -40,7 +40,7 @@ ax4.set(title='Errorbars go negative') ax4.errorbar(x, y, xerr=0.1 * x, yerr=5.0 + 0.75 * y) # ylim must be set after errorbar to allow errorbar to autoscale limits -ax4.set_ylim(ymin=0.1) +ax4.set_ylim(bottom=0.1) fig.tight_layout() plt.show() diff --git a/examples/api/power_norm.py b/examples/scales/power_norm.py similarity index 70% rename from examples/api/power_norm.py rename to examples/scales/power_norm.py index 025841a51751..25db8bd2834a 100644 --- a/examples/api/power_norm.py +++ b/examples/scales/power_norm.py @@ -32,3 +32,19 @@ fig.tight_layout() plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.colors +matplotlib.colors.PowerNorm +matplotlib.axes.Axes.hist2d +matplotlib.pyplot.hist2d diff --git a/examples/shapes_and_collections/arrow_guide.py b/examples/shapes_and_collections/arrow_guide.py new file mode 100644 index 000000000000..1c77d8a7e98b --- /dev/null +++ b/examples/shapes_and_collections/arrow_guide.py @@ -0,0 +1,110 @@ +""" +=========== +Arrow guide +=========== + +Adding arrow patches to plots. + +Arrows are often used to annotate plots. This tutorial shows how to plot arrows +that behave differently when the data limits on a plot are changed. In general, +points on a plot can either be fixed in "data space" or "display space". +Something plotted in data space moves when the data limits are altered - an +example would the points in a scatter plot. Something plotted in display space +stays static when data limits are altered - an example would be a figure title +or the axis labels. + +Arrows consist of a head (and possibly a tail) and a stem drawn between a +start point and and end point, called 'anchor points' from now on. +Here we show three use cases for plotting arrows, depending on whether the +head or anchor points need to be fixed in data or display space: + + 1. Head shape fixed in display space, anchor points fixed in data space + 2. Head shape and anchor points fixed in display space + 3. Entire patch fixed in data space + +Below each use case is presented in turn. +""" +import matplotlib.patches as mpatches +import matplotlib.pyplot as plt +x_tail = 0.1 +y_tail = 0.1 +x_head = 0.9 +y_head = 0.9 +dx = x_head - x_tail +dy = y_head - y_tail + + +############################################################################### +# Head shape fixed in display space and anchor points fixed in data space +# ----------------------------------------------------------------------- +# +# This is useful if you are annotating a plot, and don't want the arrow to +# to change shape or position if you pan or scale the plot. Note that when +# the axis limits change +# +# In this case we use `.patches.FancyArrowPatch` +# +# Note that when the axis limits are changed, the arrow shape stays the same, +# but the anchor points move. + +fig, axs = plt.subplots(nrows=2) +arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy), + mutation_scale=100) +axs[0].add_patch(arrow) + +arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy), + mutation_scale=100) +axs[1].add_patch(arrow) +axs[1].set_xlim(0, 2) +axs[1].set_ylim(0, 2) + +############################################################################### +# Head shape and anchor points fixed in display space +# --------------------------------------------------- +# +# This is useful if you are annotating a plot, and don't want the arrow to +# to change shape or position if you pan or scale the plot. +# +# In this case we use `.patches.FancyArrowPatch`, and pass the keyword argument +# ``transform=ax.transAxes`` where ``ax`` is the axes we are adding the patch +# to. +# +# Note that when the axis limits are changed, the arrow shape and location +# stays the same. + +fig, axs = plt.subplots(nrows=2) +arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy), + mutation_scale=100, + transform=axs[0].transAxes) +axs[0].add_patch(arrow) + +arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy), + mutation_scale=100, + transform=axs[1].transAxes) +axs[1].add_patch(arrow) +axs[1].set_xlim(0, 2) +axs[1].set_ylim(0, 2) + + +############################################################################### +# Head shape and anchor points fixed in data space +# ------------------------------------------------ +# +# In this case we use `.patches.Arrow` +# +# Note that when the axis limits are changed, the arrow shape and location +# changes. + +fig, axs = plt.subplots(nrows=2) + +arrow = mpatches.Arrow(x_tail, y_tail, dx, dy) +axs[0].add_patch(arrow) + +arrow = mpatches.Arrow(x_tail, y_tail, dx, dy) +axs[1].add_patch(arrow) +axs[1].set_xlim(0, 2) +axs[1].set_ylim(0, 2) + +############################################################################### + +plt.show() diff --git a/examples/shapes_and_collections/ellipse_collection.py b/examples/shapes_and_collections/ellipse_collection.py index 9dbcd845cf67..9b7a71f55643 100644 --- a/examples/shapes_and_collections/ellipse_collection.py +++ b/examples/shapes_and_collections/ellipse_collection.py @@ -15,7 +15,7 @@ y = np.arange(15) X, Y = np.meshgrid(x, y) -XY = np.hstack((X.ravel()[:, np.newaxis], Y.ravel()[:, np.newaxis])) +XY = np.column_stack((X.ravel(), Y.ravel())) ww = X / 10.0 hh = Y / 15.0 diff --git a/examples/shapes_and_collections/fancybox_demo.py b/examples/shapes_and_collections/fancybox_demo.py index 60415ef8db58..9b58c1a43442 100644 --- a/examples/shapes_and_collections/fancybox_demo.py +++ b/examples/shapes_and_collections/fancybox_demo.py @@ -189,7 +189,6 @@ def test_all(): ax.set_ylim(0., 1.) ax.set_aspect(2.) - plt.draw() plt.show() diff --git a/examples/shapes_and_collections/line_collection.py b/examples/shapes_and_collections/line_collection.py index e7dd201213b2..343c4a124ac5 100644 --- a/examples/shapes_and_collections/line_collection.py +++ b/examples/shapes_and_collections/line_collection.py @@ -22,7 +22,7 @@ # Here are many sets of y to plot vs x ys = x[:50, np.newaxis] + x[np.newaxis, :] -segs = np.zeros((50, 100, 2), float) +segs = np.zeros((50, 100, 2)) segs[:, :, 1] = ys segs[:, :, 0] = x diff --git a/examples/api/patch_collection.py b/examples/shapes_and_collections/patch_collection.py similarity index 71% rename from examples/api/patch_collection.py rename to examples/shapes_and_collections/patch_collection.py index d3e6ec5d0556..05e343465ffe 100644 --- a/examples/api/patch_collection.py +++ b/examples/shapes_and_collections/patch_collection.py @@ -3,11 +3,11 @@ Circles, Wedges and Polygons ============================ -This example demonstrates how to use patch collections. +This example demonstrates how to use +:class:`patch collections<~.collections.PatchCollection>`. """ import numpy as np -import matplotlib from matplotlib.patches import Circle, Wedge, Polygon from matplotlib.collections import PatchCollection import matplotlib.pyplot as plt @@ -56,3 +56,23 @@ fig.colorbar(p, ax=ax) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.patches +matplotlib.patches.Circle +matplotlib.patches.Wedge +matplotlib.patches.Polygon +matplotlib.collections.PatchCollection +matplotlib.collections.Collection.set_array +matplotlib.axes.Axes.add_collection +matplotlib.figure.Figure.colorbar diff --git a/examples/api/quad_bezier.py b/examples/shapes_and_collections/quad_bezier.py similarity index 51% rename from examples/api/quad_bezier.py rename to examples/shapes_and_collections/quad_bezier.py index 6cf190ee073a..0aacd26c55f4 100644 --- a/examples/api/quad_bezier.py +++ b/examples/shapes_and_collections/quad_bezier.py @@ -3,8 +3,8 @@ Bezier Curve ============ -This example showcases the PathPatch object to create a Bezier polycurve path -patch. +This example showcases the `~.patches.PathPatch` object to create a Bezier +polycurve path patch. """ import matplotlib.path as mpath @@ -24,3 +24,20 @@ ax.set_title('The red point should be on the path') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.path +matplotlib.path.Path +matplotlib.patches +matplotlib.patches.PathPatch +matplotlib.axes.Axes.add_patch diff --git a/examples/showcase/integral.py b/examples/showcase/integral.py index a4c0d8947df7..f39174d03f64 100644 --- a/examples/showcase/integral.py +++ b/examples/showcase/integral.py @@ -32,7 +32,7 @@ def func(x): # Make the shaded region ix = np.linspace(a, b) iy = func(ix) -verts = [(a, 0)] + list(zip(ix, iy)) + [(b, 0)] +verts = [(a, 0), *zip(ix, iy), (b, 0)] poly = Polygon(verts, facecolor='0.9', edgecolor='0.5') ax.add_patch(poly) diff --git a/examples/specialty_plots/anscombe.py b/examples/specialty_plots/anscombe.py index fd1ecd0bbe58..3f5d98ef914c 100644 --- a/examples/specialty_plots/anscombe.py +++ b/examples/specialty_plots/anscombe.py @@ -4,7 +4,6 @@ ================== """ -from __future__ import print_function """ Edward Tufte uses this example from Anscombe to show 4 datasets of x and y that have the same mean, standard deviation, and regression diff --git a/examples/specialty_plots/leftventricle_bulleye.py b/examples/specialty_plots/leftventricle_bulleye.py index 9269c007b9d4..7940a38cdb79 100644 --- a/examples/specialty_plots/leftventricle_bulleye.py +++ b/examples/specialty_plots/leftventricle_bulleye.py @@ -61,7 +61,7 @@ def bullseye_plot(ax, data, segBold=None, cmap=None, norm=None): for i in range(r.shape[0]): ax.plot(theta, np.repeat(r[i], theta.shape), '-k', lw=linewidth) - # Create the bounds for the segments 1-12 + # Create the bounds for the segments 1-12 for i in range(6): theta_i = np.deg2rad(i * 60) ax.plot([theta_i, theta_i], [r[1], 1], '-k', lw=linewidth) diff --git a/examples/specialty_plots/mri_with_eeg.py b/examples/specialty_plots/mri_with_eeg.py index 0fd3be3d6eda..98ffd9fcabea 100644 --- a/examples/specialty_plots/mri_with_eeg.py +++ b/examples/specialty_plots/mri_with_eeg.py @@ -7,7 +7,6 @@ histogram and some EEG traces. """ -from __future__ import division, print_function import numpy as np import matplotlib.pyplot as plt @@ -41,11 +40,10 @@ ax1.set_ylabel('MRI density') # Load the EEG data -numSamples, numRows = 800, 4 +n_samples, n_rows = 800, 4 with cbook.get_sample_data('eeg.dat') as eegfile: - data = np.fromfile(eegfile, dtype=float) -data.shape = (numSamples, numRows) -t = 10.0 * np.arange(numSamples) / numSamples + data = np.fromfile(eegfile, dtype=float).reshape((n_samples, n_rows)) +t = 10 * np.arange(n_samples) / n_samples # Plot the EEG ticklocs = [] @@ -56,15 +54,15 @@ dmax = data.max() dr = (dmax - dmin) * 0.7 # Crowd them a bit. y0 = dmin -y1 = (numRows - 1) * dr + dmax +y1 = (n_rows - 1) * dr + dmax ax2.set_ylim(y0, y1) segs = [] -for i in range(numRows): - segs.append(np.hstack((t[:, np.newaxis], data[:, i, np.newaxis]))) +for i in range(n_rows): + segs.append(np.column_stack((t, data[:, i]))) ticklocs.append(i * dr) -offsets = np.zeros((numRows, 2), dtype=float) +offsets = np.zeros((n_rows, 2), dtype=float) offsets[:, 1] = ticklocs lines = LineCollection(segs, offsets=offsets, transOffset=None) diff --git a/examples/api/radar_chart.py b/examples/specialty_plots/radar_chart.py similarity index 90% rename from examples/api/radar_chart.py rename to examples/specialty_plots/radar_chart.py index 2f6fd8ac4e3d..6df32b3f1a8f 100644 --- a/examples/api/radar_chart.py +++ b/examples/specialty_plots/radar_chart.py @@ -60,18 +60,17 @@ class RadarAxes(PolarAxes): draw_patch = patch_dict[frame] def __init__(self, *args, **kwargs): - super(RadarAxes, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # rotate plot such that the first axis is at the top self.set_theta_zero_location('N') - def fill(self, *args, **kwargs): + def fill(self, *args, closed=True, **kwargs): """Override fill so that line is closed by default""" - closed = kwargs.pop('closed', True) - return super(RadarAxes, self).fill(closed=closed, *args, **kwargs) + return super().fill(closed=closed, *args, **kwargs) def plot(self, *args, **kwargs): """Override plot so that line is closed by default""" - lines = super(RadarAxes, self).plot(*args, **kwargs) + lines = super().plot(*args, **kwargs) for line in lines: self._close_line(line) @@ -91,7 +90,7 @@ def _gen_axes_patch(self): def _gen_axes_spines(self): if frame == 'circle': - return PolarAxes._gen_axes_spines(self) + return super()._gen_axes_spines() # The following is a hack to get the spines (i.e. the axes frame) # to draw correctly for a polygon frame. @@ -122,7 +121,7 @@ def unit_poly_verts(theta): def example_data(): # The following data is from the Denver Aerosol Sources and Health study. - # See doi:10.1016/j.atmosenv.2008.12.017 + # See doi:10.1016/j.atmosenv.2008.12.017 # # The data are pollution source profile estimates for five modeled # pollution sources (e.g., cars, wood-burning, etc) that emit 7-9 chemical @@ -203,3 +202,24 @@ def example_data(): size='large') plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.path +matplotlib.path.Path +matplotlib.spines +matplotlib.spines.Spine +matplotlib.projections +matplotlib.projections.polar +matplotlib.projections.polar.PolarAxes +matplotlib.projections.register_projection diff --git a/examples/api/sankey_basics.py b/examples/specialty_plots/sankey_basics.py similarity index 88% rename from examples/api/sankey_basics.py rename to examples/specialty_plots/sankey_basics.py index 3c69a97a99f8..f625a59a8c45 100644 --- a/examples/api/sankey_basics.py +++ b/examples/specialty_plots/sankey_basics.py @@ -5,7 +5,7 @@ Demonstrate the Sankey class by producing three basic diagrams. """ -import numpy as np + import matplotlib.pyplot as plt from matplotlib.sankey import Sankey @@ -67,10 +67,9 @@ # Notice: # # 1. Since the sum of the flows is nonzero, the width of the trunk isn't -# uniform. If verbose.level is helpful (in matplotlibrc), a message is -# given in the terminal window. -# 2. The second flow doesn't appear because its value is zero. Again, if -# verbose.level is helpful, a message is given in the terminal window. +# uniform. The matplotlib logging system logs this at the DEBUG level. +# 2. The second flow doesn't appear because its value is zero. Again, this is +# logged at the DEBUG level. ############################################################################### @@ -92,7 +91,7 @@ orientations=[-1, -1, -1], prior=0, connect=(0, 0)) diagrams = sankey.finish() diagrams[-1].patch.set_hatch('/') -plt.legend(loc='best') +plt.legend() ############################################################################### # Notice that only one connection is specified, but the systems form a @@ -100,3 +99,20 @@ # orientation and ordering of the flows is mirrored. plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.sankey +matplotlib.sankey.Sankey +matplotlib.sankey.Sankey.add +matplotlib.sankey.Sankey.finish diff --git a/examples/api/sankey_links.py b/examples/specialty_plots/sankey_links.py similarity index 83% rename from examples/api/sankey_links.py rename to examples/specialty_plots/sankey_links.py index b8de4744edb3..61dfc06d41b0 100644 --- a/examples/api/sankey_links.py +++ b/examples/specialty_plots/sankey_links.py @@ -6,8 +6,6 @@ Demonstrate/test the Sankey class by producing a long chain of connections. """ -from itertools import cycle - import matplotlib.pyplot as plt from matplotlib.sankey import Sankey @@ -56,3 +54,20 @@ def corner(sankey): # accordingly. plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.sankey +matplotlib.sankey.Sankey +matplotlib.sankey.Sankey.add +matplotlib.sankey.Sankey.finish diff --git a/examples/api/sankey_rankine.py b/examples/specialty_plots/sankey_rankine.py similarity index 91% rename from examples/api/sankey_rankine.py rename to examples/specialty_plots/sankey_rankine.py index 59f1174184a6..cec4b7001f93 100644 --- a/examples/api/sankey_rankine.py +++ b/examples/specialty_plots/sankey_rankine.py @@ -82,3 +82,20 @@ # must be adjusted manually, and that is a bit tricky. plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.sankey +matplotlib.sankey.Sankey +matplotlib.sankey.Sankey.add +matplotlib.sankey.Sankey.finish diff --git a/examples/api/skewt.py b/examples/specialty_plots/skewt.py similarity index 96% rename from examples/api/skewt.py rename to examples/specialty_plots/skewt.py index 93891f5a2122..5faf0572625f 100644 --- a/examples/api/skewt.py +++ b/examples/specialty_plots/skewt.py @@ -28,7 +28,7 @@ def update_position(self, loc): # This ensures that the new value of the location is set before # any other updates take place self._loc = loc - super(SkewXTick, self).update_position(loc) + super().update_position(loc) def _has_default_loc(self): return self.get_loc() is None @@ -180,10 +180,10 @@ def upper_xlim(self): if __name__ == '__main__': # Now make a simple example using the custom projection. + from io import StringIO from matplotlib.ticker import (MultipleLocator, NullFormatter, ScalarFormatter) import matplotlib.pyplot as plt - from six import StringIO import numpy as np # Some examples data @@ -291,3 +291,22 @@ def upper_xlim(self): ax.set_xlim(-50, 50) plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.transforms +matplotlib.spines +matplotlib.spines.Spine +matplotlib.spines.Spine.register_axis +matplotlib.projections +matplotlib.projections.register_projection diff --git a/examples/statistics/barchart_demo.py b/examples/statistics/barchart_demo.py index e4ea9022806c..c8642a3ec745 100644 --- a/examples/statistics/barchart_demo.py +++ b/examples/statistics/barchart_demo.py @@ -77,9 +77,9 @@ def attach_ordinal(num): 1 -> 1st 56 -> 56th """ - suffixes = dict((str(i), v) for i, v in - enumerate(['th', 'st', 'nd', 'rd', 'th', - 'th', 'th', 'th', 'th', 'th'])) + suffixes = {str(i): v + for i, v in enumerate(['th', 'st', 'nd', 'rd', 'th', + 'th', 'th', 'th', 'th', 'th'])} v = str(num) # special case early teens @@ -170,7 +170,7 @@ def plot_student_results(student, scores, cohort_size): rankStr = attach_ordinal(width) # The bars aren't wide enough to print the ranking inside - if (width < 5): + if width < 5: # Shift the text to the right side of the right edge xloc = width + 1 # Black against white background diff --git a/examples/statistics/boxplot_demo.py b/examples/statistics/boxplot_demo.py index 0c1be8dc6147..83a566e0a2fa 100644 --- a/examples/statistics/boxplot_demo.py +++ b/examples/statistics/boxplot_demo.py @@ -23,7 +23,7 @@ center = np.ones(25) * 50 flier_high = np.random.rand(10) * 100 + 100 flier_low = np.random.rand(10) * -100 -data = np.concatenate((spread, center, flier_high, flier_low), 0) +data = np.concatenate((spread, center, flier_high, flier_low)) fig, axs = plt.subplots(2, 3) @@ -59,7 +59,7 @@ center = np.ones(25) * 40 flier_high = np.random.rand(10) * 100 + 100 flier_low = np.random.rand(10) * -100 -d2 = np.concatenate((spread, center, flier_high, flier_low), 0) +d2 = np.concatenate((spread, center, flier_high, flier_low)) data.shape = (-1, 1) d2.shape = (-1, 1) # Making a 2-D array only works if all the columns are the diff --git a/examples/statistics/histogram_features.py b/examples/statistics/histogram_features.py index 0e28a0a22637..5baf7e4b1ef8 100644 --- a/examples/statistics/histogram_features.py +++ b/examples/statistics/histogram_features.py @@ -3,20 +3,20 @@ Demo of the histogram (hist) function with a few features ========================================================= -In addition to the basic histogram, this demo shows a few optional -features: +In addition to the basic histogram, this demo shows a few optional features: - * Setting the number of data bins - * The ``normed`` flag, which normalizes bin heights so that the - integral of the histogram is 1. The resulting histogram is an - approximation of the probability density function. - * Setting the face color of the bars - * Setting the opacity (alpha value). +* Setting the number of data bins. +* The ``normed`` flag, which normalizes bin heights so that the integral of + the histogram is 1. The resulting histogram is an approximation of the + probability density function. +* Setting the face color of the bars. +* Setting the opacity (alpha value). -Selecting different bin counts and sizes can significantly affect the -shape of a histogram. The Astropy docs have a great section on how to -select these parameters: -http://docs.astropy.org/en/stable/visualization/histogram.html +Selecting different bin counts and sizes can significantly affect the shape +of a histogram. The Astropy docs have a great section_ on how to select these +parameters. + +.. _section: http://docs.astropy.org/en/stable/visualization/histogram.html """ import matplotlib diff --git a/examples/statistics/violinplot.py b/examples/statistics/violinplot.py index 03640bc56272..667f23e2a308 100644 --- a/examples/statistics/violinplot.py +++ b/examples/statistics/violinplot.py @@ -16,7 +16,6 @@ have a great section: http://scikit-learn.org/stable/modules/density.html """ -import random import numpy as np import matplotlib.pyplot as plt diff --git a/examples/subplots_axes_and_figures/axes_margins.py b/examples/subplots_axes_and_figures/axes_margins.py index 451725d360f0..5580aaadbb51 100644 --- a/examples/subplots_axes_and_figures/axes_margins.py +++ b/examples/subplots_axes_and_figures/axes_margins.py @@ -1,10 +1,14 @@ """ -===================================== -Zooming in and out using Axes.margins -===================================== +===================================================================== +Zooming in and out using Axes.margins and the subject of "stickiness" +===================================================================== + +The first figure in this example shows how to zoom in and out of a +plot using `~.Axes.margins` instead of `~.Axes.set_xlim` and +`~.Axes.set_ylim`. The second figure demonstrates the concept of +edge "stickiness" introduced by certain methods and artists and how +to effectively work around that. -This example shows how to zoom in and out of a plot using `~.Axes.margins` -instead of `~.Axes.set_xlim` and `~.Axes.set_ylim`. """ import numpy as np @@ -32,3 +36,57 @@ def f(t): ax3.set_title('Zoomed in') plt.show() + + +############################################################################# +# +# On the "stickiness" of certain plotting methods +# """"""""""""""""""""""""""""""""""""""""""""""" +# +# Some plotting functions make the axis limits "sticky" or immune to the will +# of the `~.Axes.margins` methods. For instance, `~.Axes.imshow` and +# `~.Axes.pcolor` expect the user to want the limits to be tight around the +# pixels shown in the plot. If this behavior is not desired, you need to set +# `~.Axes.use_sticky_edges` to `False`. Consider the following example: + +y, x = np.mgrid[:5, 1:6] +poly_coords = [ + (0.25, 2.75), (3.25, 2.75), + (2.25, 0.75), (0.25, 0.75) +] +fig, (ax1, ax2) = plt.subplots(ncols=2) + +# Here we set the stickiness of the axes object... +# ax1 we'll leave as the default, which uses sticky edges +# and we'll turn off stickiness for ax2 +ax2.use_sticky_edges = False + +for ax, status in zip((ax1, ax2), ('Is', 'Is Not')): + cells = ax.pcolor(x, y, x+y, cmap='inferno') # sticky + ax.add_patch( + plt.Polygon(poly_coords, color='forestgreen', alpha=0.5) + ) # not sticky + ax.margins(x=0.1, y=0.05) + ax.set_aspect('equal') + ax.set_title('{} Sticky'.format(status)) + +plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.margins +matplotlib.pyplot.margins +matplotlib.axes.Axes.use_sticky_edges +matplotlib.axes.Axes.pcolor +matplotlib.pyplot.pcolor +matplotlib.pyplot.Polygon diff --git a/examples/subplots_axes_and_figures/axes_zoom_effect.py b/examples/subplots_axes_and_figures/axes_zoom_effect.py index 0cae0a04a5d3..70b03b8076f6 100644 --- a/examples/subplots_axes_and_figures/axes_zoom_effect.py +++ b/examples/subplots_axes_and_figures/axes_zoom_effect.py @@ -4,19 +4,21 @@ ================ """ -from matplotlib.transforms import Bbox, TransformedBbox, \ - blended_transform_factory +from matplotlib.transforms import ( + Bbox, TransformedBbox, blended_transform_factory) -from mpl_toolkits.axes_grid1.inset_locator import BboxPatch, BboxConnector,\ - BboxConnectorPatch +from mpl_toolkits.axes_grid1.inset_locator import ( + BboxPatch, BboxConnector, BboxConnectorPatch) def connect_bbox(bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, prop_lines, prop_patches=None): if prop_patches is None: - prop_patches = prop_lines.copy() - prop_patches["alpha"] = prop_patches.get("alpha", 1) * 0.2 + prop_patches = { + **prop_lines, + "alpha": prop_lines.get("alpha", 1) * 0.2, + } c1 = BboxConnector(bbox1, bbox2, loc1=loc1a, loc2=loc2a, **prop_lines) c1.set_clip_on(False) @@ -55,14 +57,12 @@ def zoom_effect01(ax1, ax2, xmin, xmax, **kwargs): mybbox1 = TransformedBbox(bbox, trans1) mybbox2 = TransformedBbox(bbox, trans2) - prop_patches = kwargs.copy() - prop_patches["ec"] = "none" - prop_patches["alpha"] = 0.2 + prop_patches = {**kwargs, "ec": "none", "alpha": 0.2} - c1, c2, bbox_patch1, bbox_patch2, p = \ - connect_bbox(mybbox1, mybbox2, - loc1a=3, loc2a=2, loc1b=4, loc2b=1, - prop_lines=kwargs, prop_patches=prop_patches) + c1, c2, bbox_patch1, bbox_patch2, p = connect_bbox( + mybbox1, mybbox2, + loc1a=3, loc2a=2, loc1b=4, loc2b=1, + prop_lines=kwargs, prop_patches=prop_patches) ax1.add_patch(bbox_patch1) ax2.add_patch(bbox_patch2) @@ -88,14 +88,12 @@ def zoom_effect02(ax1, ax2, **kwargs): mybbox1 = ax1.bbox mybbox2 = TransformedBbox(ax1.viewLim, trans) - prop_patches = kwargs.copy() - prop_patches["ec"] = "none" - prop_patches["alpha"] = 0.2 + prop_patches = {**kwargs, "ec": "none", "alpha": 0.2} - c1, c2, bbox_patch1, bbox_patch2, p = \ - connect_bbox(mybbox1, mybbox2, - loc1a=3, loc2a=2, loc1b=4, loc2b=1, - prop_lines=kwargs, prop_patches=prop_patches) + c1, c2, bbox_patch1, bbox_patch2, p = connect_bbox( + mybbox1, mybbox2, + loc1a=3, loc2a=2, loc1b=4, loc2b=1, + prop_lines=kwargs, prop_patches=prop_patches) ax1.add_patch(bbox_patch1) ax2.add_patch(bbox_patch2) diff --git a/examples/subplots_axes_and_figures/custom_figure_class.py b/examples/subplots_axes_and_figures/custom_figure_class.py index 7e7b92721739..db1315f1f590 100644 --- a/examples/subplots_axes_and_figures/custom_figure_class.py +++ b/examples/subplots_axes_and_figures/custom_figure_class.py @@ -12,12 +12,11 @@ class MyFigure(Figure): - def __init__(self, *args, **kwargs): + def __init__(self, *args, figtitle='hi mom', **kwargs): """ custom kwarg figtitle is a figure title """ - figtitle = kwargs.pop('figtitle', 'hi mom') - Figure.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.text(0.5, 0.95, figtitle, ha='center') diff --git a/examples/subplots_axes_and_figures/demo_constrained_layout.py b/examples/subplots_axes_and_figures/demo_constrained_layout.py new file mode 100644 index 000000000000..f269ac9b4468 --- /dev/null +++ b/examples/subplots_axes_and_figures/demo_constrained_layout.py @@ -0,0 +1,76 @@ +""" +===================================== +Resizing axes with constrained layout +===================================== + +Constrained layout attempts to resize subplots in +a figure so that there are no overlaps between axes objects and labels +on the axes. + +See :doc:`/tutorials/intermediate/constrainedlayout_guide` for more details and +:doc:`/tutorials/intermediate/tight_layout_guide` for an alternative. + +""" + +import matplotlib.pyplot as plt +import itertools +import warnings + + +def example_plot(ax): + ax.plot([1, 2]) + ax.set_xlabel('x-label', fontsize=12) + ax.set_ylabel('y-label', fontsize=12) + ax.set_title('Title', fontsize=14) + + +############################################################################### +# If we don't use constrained_layout, then labels overlap the axes + +fig, axs = plt.subplots(nrows=2, ncols=2, constrained_layout=False) + +for ax in axs.flatten(): + example_plot(ax) + +############################################################################### +# adding ``constrained_layout=True`` automatically adjusts. + +fig, axs = plt.subplots(nrows=2, ncols=2, constrained_layout=True) + +for ax in axs.flatten(): + example_plot(ax) + +############################################################################### +# Below is a more complicated example using nested gridspecs. + +fig = plt.figure(constrained_layout=True) + +import matplotlib.gridspec as gridspec + +gs0 = gridspec.GridSpec(1, 2, figure=fig) + +gs1 = gridspec.GridSpecFromSubplotSpec(3, 1, subplot_spec=gs0[0]) +for n in range(3): + ax = fig.add_subplot(gs1[n]) + example_plot(ax) + + +gs2 = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=gs0[1]) +for n in range(2): + ax = fig.add_subplot(gs2[n]) + example_plot(ax) + +plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions and methods is shown in this example: + +import matplotlib +matplotlib.gridspec.GridSpec +matplotlib.gridspec.GridSpecFromSubplotSpec diff --git a/examples/subplots_axes_and_figures/demo_tight_layout.py b/examples/subplots_axes_and_figures/demo_tight_layout.py index c05b8940d154..60c1bf2e91c9 100644 --- a/examples/subplots_axes_and_figures/demo_tight_layout.py +++ b/examples/subplots_axes_and_figures/demo_tight_layout.py @@ -1,7 +1,14 @@ """ -================= -Demo Tight Layout -================= +=============================== +Resizing axes with tight layout +=============================== + +`~.figure.Figure.tight_layout` attempts to resize subplots in +a figure so that there are no overlaps between axes objects and labels +on the axes. + +See :doc:`/tutorials/intermediate/tight_layout_guide` for more details and +:doc:`/tutorials/intermediate/constrainedlayout_guide` for an alternative. """ @@ -133,3 +140,19 @@ def example_plot(ax): gs2.update(top=top, bottom=bottom) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions and methods is shown in this example: + +import matplotlib +matplotlib.pyplot.tight_layout +matplotlib.figure.Figure.tight_layout +matplotlib.figure.Figure.add_subplot +matplotlib.pyplot.subplot2grid +matplotlib.gridspec.GridSpec diff --git a/examples/subplots_axes_and_figures/figure_title.py b/examples/subplots_axes_and_figures/figure_title.py index c4c2408b92b7..1c54c3887be7 100644 --- a/examples/subplots_axes_and_figures/figure_title.py +++ b/examples/subplots_axes_and_figures/figure_title.py @@ -5,7 +5,6 @@ Create a figure with separate subplot titles and a centered figure title. """ -from matplotlib.font_manager import FontProperties import matplotlib.pyplot as plt import numpy as np @@ -20,19 +19,16 @@ def f(t): t3 = np.arange(0.0, 2.0, 0.01) -plt.subplot(121) -plt.plot(t1, f(t1), 'o', t2, f(t2), '-') -plt.title('subplot 1') -plt.ylabel('Damped oscillation') -plt.suptitle('This is a somewhat long figure title', fontsize=16) +fig, axs = plt.subplots(2, 1, constrained_layout=True) +axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-') +axs[0].set_title('subplot 1') +axs[0].set_xlabel('distance (m)') +axs[0].set_ylabel('Damped oscillation') +fig.suptitle('This is a somewhat long figure title', fontsize=16) - -plt.subplot(122) -plt.plot(t3, np.cos(2*np.pi*t3), '--') -plt.xlabel('time (s)') -plt.title('subplot 2') -plt.ylabel('Undamped') - -plt.subplots_adjust(left=0.2, wspace=0.8, top=0.8) +axs[1].plot(t3, np.cos(2*np.pi*t3), '--') +axs[1].set_xlabel('time (s)') +axs[1].set_title('subplot 2') +axs[1].set_ylabel('Undamped') plt.show() diff --git a/examples/subplots_axes_and_figures/gridspec_and_subplots.py b/examples/subplots_axes_and_figures/gridspec_and_subplots.py new file mode 100644 index 000000000000..a263ef8893a2 --- /dev/null +++ b/examples/subplots_axes_and_figures/gridspec_and_subplots.py @@ -0,0 +1,24 @@ +""" +================================================== +Combining two subplots using subplots and GridSpec +================================================== + +Sometimes we want to combine two subplots in an axes layout created with +`~.Figure.subplots`. We can get the `~.gridspec.GridSpec` from the axes +and then remove the covered axes and fill the gap with a new bigger axes. +Here we create a layout with the bottom two axes in the last column combined. + +See: :doc:`/tutorials/intermediate/gridspec` +""" +import matplotlib.pyplot as plt + +fig, axs = plt.subplots(ncols=3, nrows=3) +gs = axs[1, 2].get_gridspec() +# remove the underlying axes +for ax in axs[1:, -1]: + ax.remove() +axbig = fig.add_subplot(gs[1:, -1]) +axbig.annotate('Big Axes \nGridSpec[1:, -1]', (0.1, 0.5), + xycoords='axes fraction', va='center') + +fig.tight_layout() diff --git a/examples/subplots_axes_and_figures/gridspec_multicolumn.py b/examples/subplots_axes_and_figures/gridspec_multicolumn.py new file mode 100644 index 000000000000..5a22aa2d310c --- /dev/null +++ b/examples/subplots_axes_and_figures/gridspec_multicolumn.py @@ -0,0 +1,33 @@ +""" +======================================================= +Using Gridspec to make multi-column/row subplot layouts +======================================================= + +`.GridSpec` is a flexible way to layout +subplot grids. Here is an example with a 3x3 grid, and +axes spanning all three columns, two columns, and two rows. + +""" +import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec + + +def format_axes(fig): + for i, ax in enumerate(fig.axes): + ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center") + ax.tick_params(labelbottom=False, labelleft=False) + +fig = plt.figure(constrained_layout=True) + +gs = GridSpec(3, 3, figure=fig) +ax1 = fig.add_subplot(gs[0, :]) +# identical to ax1 = plt.subplot(gs.new_subplotspec((0, 0), colspan=3)) +ax2 = fig.add_subplot(gs[1, :-1]) +ax3 = fig.add_subplot(gs[1:, -1]) +ax4 = fig.add_subplot(gs[-1, 0]) +ax5 = fig.add_subplot(gs[-1, -2]) + +fig.suptitle("GridSpec") +format_axes(fig) + +plt.show() diff --git a/examples/userdemo/demo_gridspec04.py b/examples/subplots_axes_and_figures/gridspec_nested.py similarity index 76% rename from examples/userdemo/demo_gridspec04.py rename to examples/subplots_axes_and_figures/gridspec_nested.py index bb5b3e37757f..e233e643ec68 100644 --- a/examples/userdemo/demo_gridspec04.py +++ b/examples/subplots_axes_and_figures/gridspec_nested.py @@ -1,24 +1,26 @@ """ -=============== -Demo Gridspec04 -=============== +================ +Nested Gridspecs +================ + +GridSpecs can be nested, so that a subplot from a parent GridSpec can +set the position for a nested grid of subplots. """ import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec -def make_ticklabels_invisible(fig): +def format_axes(fig): for i, ax in enumerate(fig.axes): ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center") ax.tick_params(labelbottom=False, labelleft=False) # gridspec inside gridspec - f = plt.figure() -gs0 = gridspec.GridSpec(1, 2) +gs0 = gridspec.GridSpec(1, 2, figure=f) gs00 = gridspec.GridSpecFromSubplotSpec(3, 3, subplot_spec=gs0[0]) @@ -40,6 +42,6 @@ def make_ticklabels_invisible(fig): f.add_subplot(ax6) plt.suptitle("GridSpec Inside GridSpec") -make_ticklabels_invisible(f) +format_axes(f) plt.show() diff --git a/examples/api/two_scales.py b/examples/subplots_axes_and_figures/two_scales.py similarity index 64% rename from examples/api/two_scales.py rename to examples/subplots_axes_and_figures/two_scales.py index 8e650c6f17f8..6238732e47f9 100644 --- a/examples/api/two_scales.py +++ b/examples/subplots_axes_and_figures/two_scales.py @@ -3,15 +3,14 @@ Plots with different scales =========================== -Demonstrate how to do two plots on the same axes with different left and -right scales. +Two plots on the same axes with different left and right scales. The trick is to use *two different axes* that share the same *x* axis. You can use separate `matplotlib.ticker` formatters and locators as desired since the two axes are independent. -Such axes are generated by calling the `Axes.twinx` method. Likewise, -`Axes.twiny` is available to generate axes that share a *y* axis but +Such axes are generated by calling the :meth:`.Axes.twinx` method. Likewise, +:meth:`.Axes.twiny` is available to generate axes that share a *y* axis but have different top and bottom scales. """ import numpy as np @@ -39,3 +38,18 @@ fig.tight_layout() # otherwise the right y-label is slightly clipped plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.twinx +matplotlib.axes.Axes.twiny +matplotlib.axes.Axes.tick_params diff --git a/examples/subplots_axes_and_figures/zoom_inset_axes.py b/examples/subplots_axes_and_figures/zoom_inset_axes.py new file mode 100644 index 000000000000..f75200d87af2 --- /dev/null +++ b/examples/subplots_axes_and_figures/zoom_inset_axes.py @@ -0,0 +1,60 @@ +""" +====================== +Zoom region inset axes +====================== + +Example of an inset axes and a rectangle showing where the zoom is located. + +""" + +import matplotlib.pyplot as plt +import numpy as np + + +def get_demo_image(): + from matplotlib.cbook import get_sample_data + import numpy as np + f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False) + z = np.load(f) + # z is a numpy array of 15x15 + return z, (-3, 4, -4, 3) + +fig, ax = plt.subplots(figsize=[5, 4]) + +# make data +Z, extent = get_demo_image() +Z2 = np.zeros([150, 150], dtype="d") +ny, nx = Z.shape +Z2[30:30 + ny, 30:30 + nx] = Z + +ax.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") + +# inset axes.... +axins = ax.inset_axes([0.5, 0.5, 0.47, 0.47]) +axins.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") +# sub region of the original image +x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 +axins.set_xlim(x1, x2) +axins.set_ylim(y1, y2) +axins.set_xticklabels('') +axins.set_yticklabels('') + +ax.indicate_inset_zoom(axins) + +plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions and methods is shown in this example: + +import matplotlib +matplotlib.axes.Axes.inset_axes +matplotlib.axes.Axes.indicate_inset_zoom +matplotlib.axes.Axes.imshow diff --git a/examples/tests/backend_driver_sgskip.py b/examples/tests/backend_driver_sgskip.py index 2741283784ce..8878c6273bce 100644 --- a/examples/tests/backend_driver_sgskip.py +++ b/examples/tests/backend_driver_sgskip.py @@ -21,7 +21,6 @@ switches with a --. """ -from __future__ import print_function, division import os import time import sys @@ -316,7 +315,7 @@ def report_missing(dir, flist): globstr = os.path.join(dir, '*.py') fnames = glob.glob(globstr) - pyfiles = {os.path.split(fullpath)[-1] for fullpath in set(fnames)} + pyfiles = {os.path.split(fullpath)[-1] for fullpath in fnames} exclude = set(excluded.get(dir, [])) flist = set(flist) @@ -340,7 +339,7 @@ def report_all_missing(directories): ) -from matplotlib.compat import subprocess +import subprocess def run(arglist): @@ -358,7 +357,6 @@ def drive(backend, directories, python=['python'], switches=[]): # Clear the destination directory for the examples path = backend if os.path.exists(path): - import glob for fname in os.listdir(path): os.unlink(os.path.join(path, fname)) else: @@ -383,16 +381,12 @@ def drive(backend, directories, python=['python'], switches=[]): tmpfile_name = '_tmp_%s.py' % basename tmpfile = open(tmpfile_name, 'w') - future_imports = 'from __future__ import division, print_function' for line in open(fullpath): line_lstrip = line.lstrip() if line_lstrip.startswith("#"): tmpfile.write(line) - elif 'unicode_literals' in line: - future_imports = future_imports + ', unicode_literals' tmpfile.writelines(( - future_imports + '\n', 'import sys\n', 'sys.path.append("%s")\n' % fpath.replace('\\', '\\\\'), 'import matplotlib\n', @@ -402,11 +396,7 @@ def drive(backend, directories, python=['python'], switches=[]): 'numpy.seterr(invalid="ignore")\n', )) for line in open(fullpath): - line_lstrip = line.lstrip() - if (line_lstrip.startswith('from __future__ import') or - line_lstrip.startswith('matplotlib.use') or - line_lstrip.startswith('savefig') or - line_lstrip.startswith('show')): + if line.lstrip().startswith(('matplotlib.use', 'savefig', 'show')): continue tmpfile.write(line) if backend in rcsetup.interactive_bk: diff --git a/examples/text_labels_and_annotations/accented_text.py b/examples/text_labels_and_annotations/accented_text.py index ac088f4d70f9..c7f4523e600c 100644 --- a/examples/text_labels_and_annotations/accented_text.py +++ b/examples/text_labels_and_annotations/accented_text.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" ================================= Using accented text in matplotlib @@ -13,7 +12,6 @@ \^y """ -from __future__ import unicode_literals import matplotlib.pyplot as plt # Mathtext demo diff --git a/examples/text_labels_and_annotations/annotation_demo.py b/examples/text_labels_and_annotations/annotation_demo.py index 9fc9a613a1de..92958dde25f5 100644 --- a/examples/text_labels_and_annotations/annotation_demo.py +++ b/examples/text_labels_and_annotations/annotation_demo.py @@ -7,7 +7,7 @@ This includes highlighting specific points of interest and using various visual tools to call attention to this point. For a more complete and in-depth description of the annotation and text tools in :mod:`matplotlib`, see the -`tutorial on annotation `_. +:doc:`tutorial on annotation `. """ import matplotlib.pyplot as plt diff --git a/examples/text_labels_and_annotations/arrow_demo.py b/examples/text_labels_and_annotations/arrow_demo.py index 49b07205d0c8..2232579e529a 100644 --- a/examples/text_labels_and_annotations/arrow_demo.py +++ b/examples/text_labels_and_annotations/arrow_demo.py @@ -311,6 +311,4 @@ def draw_arrow(pair, alpha=alpha, ec=ec, labelcolor=labelcolor): make_arrow_plot(d, display=display, linewidth=0.001, edgecolor=None, normalize_data=scaled, head_starts_at_zero=True, size=size) - plt.draw() - plt.show() diff --git a/examples/text_labels_and_annotations/date.py b/examples/text_labels_and_annotations/date.py index 383db6353e13..90e9f94a511e 100644 --- a/examples/text_labels_and_annotations/date.py +++ b/examples/text_labels_and_annotations/date.py @@ -16,7 +16,6 @@ :class:`numpy.datetime64` objects. """ -import datetime import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates diff --git a/examples/text_labels_and_annotations/custom_date_formatter.py b/examples/text_labels_and_annotations/date_index_formatter.py similarity index 97% rename from examples/text_labels_and_annotations/custom_date_formatter.py rename to examples/text_labels_and_annotations/date_index_formatter.py index 9e117dd91c8d..389fd2e0353e 100644 --- a/examples/text_labels_and_annotations/custom_date_formatter.py +++ b/examples/text_labels_and_annotations/date_index_formatter.py @@ -7,7 +7,6 @@ to leave out days on which there is no data, i.e. weekends. The example below shows how to use an 'index formatter' to achieve the desired plot """ -from __future__ import print_function import numpy as np import matplotlib.pyplot as plt import matplotlib.cbook as cbook diff --git a/examples/text_labels_and_annotations/demo_text_path.py b/examples/text_labels_and_annotations/demo_text_path.py index 29720da8e3f3..d1e2d421b07c 100644 --- a/examples/text_labels_and_annotations/demo_text_path.py +++ b/examples/text_labels_and_annotations/demo_text_path.py @@ -66,9 +66,7 @@ def draw(self, renderer=None): ax = plt.subplot(211) - from matplotlib._png import read_png - fn = get_sample_data("grace_hopper.png", asfileobj=False) - arr = read_png(fn) + arr = plt.imread(get_sample_data("grace_hopper.png")) text_path = TextPath((0, 0), "!?", size=150) p = PathClippedImagePatch(text_path, arr, ec="k", @@ -81,7 +79,8 @@ def draw(self, renderer=None): offsetbox.add_artist(p) # make anchored offset box - ao = AnchoredOffsetbox(loc=2, child=offsetbox, frameon=True, borderpad=0.2) + ao = AnchoredOffsetbox(loc='upper left', child=offsetbox, frameon=True, + borderpad=0.2) ax.add_artist(ao) # another text @@ -154,5 +153,4 @@ def draw(self, renderer=None): ax.set_xlim(0, 1) ax.set_ylim(0, 1) - plt.draw() plt.show() diff --git a/examples/text_labels_and_annotations/engineering_formatter.py b/examples/text_labels_and_annotations/engineering_formatter.py index 523cae8090a6..7be2d0fe59cf 100644 --- a/examples/text_labels_and_annotations/engineering_formatter.py +++ b/examples/text_labels_and_annotations/engineering_formatter.py @@ -35,7 +35,7 @@ # `sep` (separator between the number and the prefix/unit). ax1.set_title('SI-prefix only ticklabels, 1-digit precision & ' 'thin space separator') -formatter1 = EngFormatter(places=1, sep=u"\N{THIN SPACE}") # U+2009 +formatter1 = EngFormatter(places=1, sep="\N{THIN SPACE}") # U+2009 ax1.xaxis.set_major_formatter(formatter1) ax1.plot(xs, ys) ax1.set_xlabel('Frequency [Hz]') diff --git a/examples/text_labels_and_annotations/fancyarrow_demo.py b/examples/text_labels_and_annotations/fancyarrow_demo.py index 6eb904433b67..75e988264650 100644 --- a/examples/text_labels_and_annotations/fancyarrow_demo.py +++ b/examples/text_labels_and_annotations/fancyarrow_demo.py @@ -51,6 +51,4 @@ def to_texstring(s): ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) - -plt.draw() plt.show() diff --git a/examples/text_labels_and_annotations/fancytextbox_demo.py b/examples/text_labels_and_annotations/fancytextbox_demo.py index 4ad00bc846fa..cca4f6d50f92 100644 --- a/examples/text_labels_and_annotations/fancytextbox_demo.py +++ b/examples/text_labels_and_annotations/fancytextbox_demo.py @@ -22,6 +22,4 @@ ) ) - -plt.draw() plt.show() diff --git a/examples/api/font_family_rc_sgskip.py b/examples/text_labels_and_annotations/font_family_rc_sgskip.py similarity index 100% rename from examples/api/font_family_rc_sgskip.py rename to examples/text_labels_and_annotations/font_family_rc_sgskip.py diff --git a/examples/api/font_file.py b/examples/text_labels_and_annotations/font_file.py similarity index 66% rename from examples/api/font_file.py rename to examples/text_labels_and_annotations/font_file.py index af035bf7e95f..ed1341c14356 100644 --- a/examples/api/font_file.py +++ b/examples/text_labels_and_annotations/font_file.py @@ -10,7 +10,8 @@ Here, we use the Computer Modern roman font (``cmr10``) shipped with Matplotlib. -For a more flexible solution, see :doc:`/gallery/api/font_family_rc_sgskip` and +For a more flexible solution, see +:doc:`/gallery/text_labels_and_annotations/font_family_rc_sgskip` and :doc:`/gallery/text_labels_and_annotations/fonts_demo`. """ @@ -27,3 +28,18 @@ ax.set_xlabel('This is the default font') plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.font_manager.FontProperties +matplotlib.axes.Axes.set_title diff --git a/examples/text_labels_and_annotations/font_table_ttf_sgskip.py b/examples/text_labels_and_annotations/font_table_ttf_sgskip.py index 880453b55089..6de73e68dea3 100644 --- a/examples/text_labels_and_annotations/font_table_ttf_sgskip.py +++ b/examples/text_labels_and_annotations/font_table_ttf_sgskip.py @@ -19,9 +19,6 @@ from matplotlib.font_manager import FontProperties import matplotlib.pyplot as plt -import six -from six import unichr - # the font table grid labelc = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', @@ -47,7 +44,7 @@ if ccode >= 256: continue r, c = divmod(ccode, 16) - s = unichr(ccode) + s = chr(ccode) chars[r][c] = s lightgrn = (0.5, 0.8, 0.5) diff --git a/examples/text_labels_and_annotations/fonts_demo_kw.py b/examples/text_labels_and_annotations/fonts_demo_kw.py index f323570cada5..bfcec95f93f2 100644 --- a/examples/text_labels_and_annotations/fonts_demo_kw.py +++ b/examples/text_labels_and_annotations/fonts_demo_kw.py @@ -8,9 +8,7 @@ See :doc:`fonts_demo` to achieve the same effect using setters. """ -from matplotlib.font_manager import FontProperties import matplotlib.pyplot as plt -import numpy as np plt.subplot(111, facecolor='w') alignment = {'horizontalalignment': 'center', 'verticalalignment': 'baseline'} diff --git a/examples/api/legend.py b/examples/text_labels_and_annotations/legend.py similarity index 61% rename from examples/api/legend.py rename to examples/text_labels_and_annotations/legend.py index 457565542a14..7e6162a51c25 100644 --- a/examples/api/legend.py +++ b/examples/text_labels_and_annotations/legend.py @@ -24,6 +24,22 @@ legend = ax.legend(loc='upper center', shadow=True, fontsize='x-large') # Put a nicer background color on the legend. -legend.get_frame().set_facecolor('#00FFCC') +legend.get_frame().set_facecolor('C0') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.plot +matplotlib.pyplot.plot +matplotlib.axes.Axes.legend +matplotlib.pyplot.legend diff --git a/examples/text_labels_and_annotations/legend_demo.py b/examples/text_labels_and_annotations/legend_demo.py index 77db192ebe85..18ebc827ef8c 100644 --- a/examples/text_labels_and_annotations/legend_demo.py +++ b/examples/text_labels_and_annotations/legend_demo.py @@ -10,15 +10,12 @@ First we'll show off how to make a legend for specific lines. """ -from __future__ import (absolute_import, division, - print_function, unicode_literals) import matplotlib.pyplot as plt -import numpy as np -from matplotlib.legend_handler import (HandlerLineCollection, - HandlerTuple) import matplotlib.collections as mcol +from matplotlib.legend_handler import HandlerLineCollection, HandlerTuple from matplotlib.lines import Line2D +import numpy as np t1 = np.arange(0.0, 2.0, 0.1) t2 = np.arange(0.0, 2.0, 0.01) @@ -66,7 +63,7 @@ ############################################################################### # Here we attach legends to more complex plots. -fig, axes = plt.subplots(3, 1) +fig, axes = plt.subplots(3, 1, constrained_layout=True) top_ax, middle_ax, bottom_ax = axes top_ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, label="Bar 1", @@ -84,13 +81,12 @@ bottom_ax.stem([0.3, 1.5, 2.7], [1, 3.6, 2.7], label="stem test") bottom_ax.legend() -plt.subplots_adjust(hspace=0.7) plt.show() ############################################################################### # Now we'll showcase legend entries with more than one legend key. -fig, (ax1, ax2) = plt.subplots(2, 1) +fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True) # First plot: two legend keys for a single entry p1 = ax1.scatter([1], [5], c='r', marker='s', s=100) @@ -117,7 +113,6 @@ l = ax2.legend([(rpos, rneg), (rneg, rpos)], ['pad!=0', 'pad=0'], handler_map={(rpos, rneg): HandlerTuple(ndivide=None), (rneg, rpos): HandlerTuple(ndivide=None, pad=0.)}) - plt.show() ############################################################################### @@ -175,7 +170,6 @@ def create_artists(self, legend, orig_handle, for i, color, style in zip(range(5), colors, styles): ax.plot(x, np.sin(x) - .1 * i, c=color, ls=style) - # make proxy artists # make list of one line -- doesn't matter what the coordinates are line = [[(0, 0)]] diff --git a/examples/api/line_with_text.py b/examples/text_labels_and_annotations/line_with_text.py similarity index 72% rename from examples/api/line_with_text.py rename to examples/text_labels_and_annotations/line_with_text.py index b20d6f44f761..c876f6887948 100644 --- a/examples/api/line_with_text.py +++ b/examples/text_labels_and_annotations/line_with_text.py @@ -20,7 +20,7 @@ def __init__(self, *args, **kwargs): lines.Line2D.__init__(self, *args, **kwargs) # we can't access the label attr until *after* the line is - # inited + # initiated self.text.set_text(self.get_label()) def set_figure(self, figure): @@ -59,8 +59,33 @@ def draw(self, renderer): line.text.set_color('red') line.text.set_fontsize(16) - ax.add_line(line) - plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.lines +matplotlib.lines.Line2D +matplotlib.lines.Line2D.set_data +matplotlib.artist +matplotlib.artist.Artist +matplotlib.artist.Artist.draw +matplotlib.artist.Artist.set_transform +matplotlib.text +matplotlib.text.Text +matplotlib.text.Text.set_color +matplotlib.text.Text.set_fontsize +matplotlib.text.Text.set_position +matplotlib.axes.Axes.add_line +matplotlib.transforms +matplotlib.transforms.Affine2D diff --git a/examples/api/mathtext_asarray.py b/examples/text_labels_and_annotations/mathtext_asarray.py similarity index 64% rename from examples/api/mathtext_asarray.py rename to examples/text_labels_and_annotations/mathtext_asarray.py index 258ad0fa4b5e..ee107d099bf3 100644 --- a/examples/api/mathtext_asarray.py +++ b/examples/text_labels_and_annotations/mathtext_asarray.py @@ -26,3 +26,20 @@ fig.figimage(rgba2, 100, 300) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.mathtext +matplotlib.mathtext.MathTextParser +matplotlib.mathtext.MathTextParser.to_png +matplotlib.mathtext.MathTextParser.to_rgba +matplotlib.figure.Figure.figimage diff --git a/examples/text_labels_and_annotations/mathtext_examples.py b/examples/text_labels_and_annotations/mathtext_examples.py index 86c349f10d11..6272099819f5 100644 --- a/examples/text_labels_and_annotations/mathtext_examples.py +++ b/examples/text_labels_and_annotations/mathtext_examples.py @@ -5,12 +5,10 @@ Selected features of Matplotlib's math rendering engine. """ -from __future__ import print_function import matplotlib.pyplot as plt import subprocess import sys import re -import gc # Selection of features following "Writing mathematical expressions" tutorial mathtext_titles = { diff --git a/examples/text_labels_and_annotations/multiline.py b/examples/text_labels_and_annotations/multiline.py index e1227207c4d5..ce2cb158af85 100644 --- a/examples/text_labels_and_annotations/multiline.py +++ b/examples/text_labels_and_annotations/multiline.py @@ -42,5 +42,4 @@ plt.title("test line spacing for multiline text") plt.subplots_adjust(bottom=0.25, top=0.75) -plt.draw() plt.show() diff --git a/examples/text_labels_and_annotations/rainbow_text.py b/examples/text_labels_and_annotations/rainbow_text.py index b326be24c5f0..bcfd9a38d750 100644 --- a/examples/text_labels_and_annotations/rainbow_text.py +++ b/examples/text_labels_and_annotations/rainbow_text.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ============ Rainbow text @@ -49,7 +48,8 @@ def rainbow_text(x, y, strings, colors, ax=None, **kw): text = ax.text(x, y, s + " ", color=c, transform=t, **kw) text.draw(canvas.get_renderer()) ex = text.get_window_extent() - t = transforms.offset_copy(text._transform, x=ex.width, units='dots') + t = transforms.offset_copy( + text.get_transform(), x=ex.width, units='dots') # vertical version for s, c in zip(strings, colors): @@ -57,7 +57,8 @@ def rainbow_text(x, y, strings, colors, ax=None, **kw): rotation=90, va='bottom', ha='center', **kw) text.draw(canvas.get_renderer()) ex = text.get_window_extent() - t = transforms.offset_copy(text._transform, y=ex.height, units='dots') + t = transforms.offset_copy( + text.get_transform(), y=ex.height, units='dots') rainbow_text(0, 0, "all unicorns poop rainbows ! ! !".split(), diff --git a/examples/text_labels_and_annotations/stix_fonts_demo.py b/examples/text_labels_and_annotations/stix_fonts_demo.py index 411b0ae41728..59f2ed330478 100644 --- a/examples/text_labels_and_annotations/stix_fonts_demo.py +++ b/examples/text_labels_and_annotations/stix_fonts_demo.py @@ -4,16 +4,11 @@ =============== """ -from __future__ import unicode_literals -import subprocess -import sys -import re -import gc import matplotlib.pyplot as plt import numpy as np -stests = [ +tests = [ r'$\mathcircled{123} \mathrm{\mathcircled{123}}' r' \mathbf{\mathcircled{123}}$', r'$\mathsf{Sans \Omega} \mathrm{\mathsf{Sans \Omega}}' @@ -26,39 +21,12 @@ r'$\mathfrak{Fraktur} \mathbf{\mathfrak{Fraktur}}$', r'$\mathscr{Script}$'] -if sys.maxunicode > 0xffff: - s = r'Direct Unicode: $\u23ce \mathrm{\ue0f2 \U0001D538}$' +plt.figure(figsize=(8, (len(tests) * 1) + 2)) +plt.plot([0, 0], 'r') +plt.axis([0, 3, -len(tests), 0]) +plt.yticks(-np.arange(len(tests))) +for i, s in enumerate(tests): + plt.text(0.1, -i, s, fontsize=32) -def doall(): - tests = stests - - plt.figure(figsize=(8, (len(tests) * 1) + 2)) - plt.plot([0, 0], 'r') - plt.grid(False) - plt.axis([0, 3, -len(tests), 0]) - plt.yticks(np.arange(len(tests)) * -1) - for i, s in enumerate(tests): - plt.text(0.1, -i, s, fontsize=32) - - plt.savefig('stix_fonts_example') - plt.show() - - -if '--latex' in sys.argv: - fd = open("stix_fonts_examples.ltx", "w") - fd.write("\\documentclass{article}\n") - fd.write("\\begin{document}\n") - fd.write("\\begin{enumerate}\n") - - for i, s in enumerate(stests): - s = re.sub(r"(?`. An alternative approach for parasite +axes is shown in the :doc:`/gallery/axisartist/demo_parasite_axes` and +:doc:`/gallery/axisartist/demo_parasite_axes2` examples. """ import matplotlib.pyplot as plt diff --git a/examples/ticks_and_spines/scalarformatter.py b/examples/ticks_and_spines/scalarformatter.py index 87b6c1717f9c..b17bc345e538 100644 --- a/examples/ticks_and_spines/scalarformatter.py +++ b/examples/ticks_and_spines/scalarformatter.py @@ -1,6 +1,6 @@ """ ========================================= -Tick formatting using the ScalarFromatter +Tick formatting using the ScalarFormatter ========================================= The example shows use of ScalarFormatter with different settings. diff --git a/examples/ticks_and_spines/tick_xlabel_top.py b/examples/ticks_and_spines/tick_xlabel_top.py index 5180f1acd2df..a437810b06a3 100644 --- a/examples/ticks_and_spines/tick_xlabel_top.py +++ b/examples/ticks_and_spines/tick_xlabel_top.py @@ -7,9 +7,10 @@ (default False) and :rc:`xtick.labelbottom` (default True) and :rc:`xtick.bottom` (default True) to control where on the axes ticks and their labels appear. -These properties can also be set in the ``.matplotlib/matplotlibrc``. +These properties can also be set in ``.matplotlib/matplotlibrc``. """ + import matplotlib.pyplot as plt import numpy as np @@ -22,6 +23,6 @@ fig, ax = plt.subplots() ax.plot(x) -ax.set_title('xlabel top', pad=24) # increase padding to make room for labels +ax.set_title('xlabel top') # Note title moves to make room for ticks plt.show() diff --git a/examples/units/basic_units.py b/examples/units/basic_units.py index d43ee0527298..0af2e1dd576d 100644 --- a/examples/units/basic_units.py +++ b/examples/units/basic_units.py @@ -4,7 +4,6 @@ =========== """ -import six import math @@ -12,7 +11,6 @@ import matplotlib.units as units import matplotlib.ticker as ticker -from matplotlib.axes import Axes from matplotlib.cbook import iterable @@ -26,13 +24,13 @@ def __get__(self, obj, objtype=None): class TaggedValueMeta(type): - def __init__(cls, name, bases, dict): - for fn_name in cls._proxies: + def __init__(self, name, bases, dict): + for fn_name in self._proxies: try: - dummy = getattr(cls, fn_name) + dummy = getattr(self, fn_name) except AttributeError: - setattr(cls, fn_name, - ProxyDelegate(fn_name, cls._proxies[fn_name])) + setattr(self, fn_name, + ProxyDelegate(fn_name, self._proxies[fn_name])) class PassThroughProxy(object): @@ -110,7 +108,7 @@ def __call__(self, *args): return TaggedValue(ret, ret_unit) -class TaggedValue(six.with_metaclass(TaggedValueMeta)): +class TaggedValue(metaclass=TaggedValueMeta): _proxies = {'__add__': ConvertAllProxy, '__sub__': ConvertAllProxy, @@ -156,7 +154,7 @@ def __array_wrap__(self, array, context): return TaggedValue(array, self.unit) def __repr__(self): - return 'TaggedValue(' + repr(self.value) + ', ' + repr(self.unit) + ')' + return 'TaggedValue({!r}, {!r})'.format(self.value, self.unit) def __str__(self): return str(self.value) + ' in ' + str(self.unit) @@ -251,13 +249,13 @@ def get_unit(self): class UnitResolver(object): def addition_rule(self, units): for unit_1, unit_2 in zip(units[:-1], units[1:]): - if (unit_1 != unit_2): + if unit_1 != unit_2: return NotImplemented return units[0] def multiplication_rule(self, units): non_null = [u for u in units if u] - if (len(non_null) > 1): + if len(non_null) > 1: return NotImplemented return non_null[0] @@ -270,7 +268,7 @@ def multiplication_rule(self, units): '__rsub__': addition_rule} def __call__(self, operation, units): - if (operation not in self.op_dict): + if operation not in self.op_dict: return NotImplemented return self.op_dict[operation](self, units) diff --git a/examples/user_interfaces/canvasagg.py b/examples/user_interfaces/canvasagg.py new file mode 100644 index 000000000000..81999d4856c3 --- /dev/null +++ b/examples/user_interfaces/canvasagg.py @@ -0,0 +1,69 @@ +""" +============== +CanvasAgg demo +============== + +This example shows how to use the agg backend directly to create images, which +may be of use to web application developers who want full control over their +code without using the pyplot interface to manage figures, figure closing etc. + +.. note:: + + It is not necessary to avoid using the pyplot interface in order to + create figures without a graphical front-end - simply setting + the backend to "Agg" would be sufficient. + +In this example, we show how to save the contents of the agg canvas to a file, +and how to extract them to a string, which can in turn be passed off to PIL or +put in a numpy array. The latter functionality allows e.g. to use Matplotlib +inside a cgi-script *without* needing to write a figure to disk. +""" + +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.figure import Figure +import numpy as np + +fig = Figure(figsize=(5, 4), dpi=100) +# A canvas must be manually attached to the figure (pyplot would automatically +# do it). This is done by instantiating the canvas with the figure as +# argument. +canvas = FigureCanvasAgg(fig) + +# Do some plotting. +ax = fig.add_subplot(111) +ax.plot([1, 2, 3]) + +# Option 1: Save the figure to a file; can also be a file-like object (BytesIO, +# etc.). +fig.savefig("test.png") + +# Option 2: Save the figure to a string. +canvas.draw() +s, (width, height) = canvas.print_to_buffer() + +# Option 2a: Convert to a NumPy array. +X = np.fromstring(s, np.uint8).reshape((height, width, 4)) + +# Option 2b: Pass off to PIL. +from PIL import Image +im = Image.frombytes("RGBA", (width, height), s) + +# Uncomment this line to display the image using ImageMagick's `display` tool. +# im.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.backends.backend_agg.FigureCanvasAgg +matplotlib.figure.Figure +matplotlib.figure.Figure.add_subplot +matplotlib.figure.Figure.savefig +matplotlib.axes.Axes.plot diff --git a/examples/user_interfaces/embedding_in_gtk2_sgskip.py b/examples/user_interfaces/embedding_in_gtk2_sgskip.py deleted file mode 100644 index 176809367f0c..000000000000 --- a/examples/user_interfaces/embedding_in_gtk2_sgskip.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -================= -Embedding In GTK2 -================= - -show how to add a matplotlib FigureCanvasGTK or FigureCanvasGTKAgg widget and -a toolbar to a gtk.Window -""" -import gtk - -from matplotlib.figure import Figure -import numpy as np - -# uncomment to select /GTK/GTKAgg/GTKCairo -#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas -from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas -#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas - -# or NavigationToolbar for classic -#from matplotlib.backends.backend_gtk import NavigationToolbar2GTK as NavigationToolbar -from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar - -# implement the default mpl key bindings -from matplotlib.backend_bases import key_press_handler - -win = gtk.Window() -win.connect("destroy", lambda x: gtk.main_quit()) -win.set_default_size(400, 300) -win.set_title("Embedding in GTK") - -vbox = gtk.VBox() -win.add(vbox) - -fig = Figure(figsize=(5, 4), dpi=100) -ax = fig.add_subplot(111) -t = np.arange(0.0, 3.0, 0.01) -s = np.sin(2*np.pi*t) - -ax.plot(t, s) - - -canvas = FigureCanvas(fig) # a gtk.DrawingArea -vbox.pack_start(canvas) -toolbar = NavigationToolbar(canvas, win) -vbox.pack_start(toolbar, False, False) - - -def on_key_event(event): - print('you pressed %s' % event.key) - key_press_handler(event, canvas, toolbar) - -canvas.mpl_connect('key_press_event', on_key_event) - -win.show_all() -gtk.main() diff --git a/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py b/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py index ebead87f6ed6..7dfd1a5c56d5 100644 --- a/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py +++ b/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py @@ -6,6 +6,8 @@ Demonstrate NavigationToolbar with GTK3 accessed via pygobject. """ +import gi +gi.require_version('Gtk', '3.0') from gi.repository import Gtk from matplotlib.backends.backend_gtk3 import ( diff --git a/examples/user_interfaces/embedding_in_gtk3_sgskip.py b/examples/user_interfaces/embedding_in_gtk3_sgskip.py index a5e6271488ba..af76a0d724f2 100644 --- a/examples/user_interfaces/embedding_in_gtk3_sgskip.py +++ b/examples/user_interfaces/embedding_in_gtk3_sgskip.py @@ -7,6 +7,8 @@ GTK3 accessed via pygobject. """ +import gi +gi.require_version('Gtk', '3.0') from gi.repository import Gtk from matplotlib.backends.backend_gtk3agg import ( diff --git a/examples/user_interfaces/embedding_in_gtk_sgskip.py b/examples/user_interfaces/embedding_in_gtk_sgskip.py deleted file mode 100644 index 7da96306a982..000000000000 --- a/examples/user_interfaces/embedding_in_gtk_sgskip.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -================ -Embedding In GTK -================ - -Show how to add a matplotlib FigureCanvasGTK or FigureCanvasGTKAgg widget to a -gtk.Window -""" - -import gtk - -from matplotlib.figure import Figure -import numpy as np - -# uncomment to select /GTK/GTKAgg/GTKCairo -#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas -from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas -#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas - - -win = gtk.Window() -win.connect("destroy", lambda x: gtk.main_quit()) -win.set_default_size(400, 300) -win.set_title("Embedding in GTK") - -f = Figure(figsize=(5, 4), dpi=100) -a = f.add_subplot(111) -t = np.arange(0.0, 3.0, 0.01) -s = np.sin(2*np.pi*t) -a.plot(t, s) - -canvas = FigureCanvas(f) # a gtk.DrawingArea -win.add(canvas) - -win.show_all() -gtk.main() diff --git a/examples/user_interfaces/embedding_in_qt_sgskip.py b/examples/user_interfaces/embedding_in_qt_sgskip.py index 24b906ed7277..54059c62147b 100644 --- a/examples/user_interfaces/embedding_in_qt_sgskip.py +++ b/examples/user_interfaces/embedding_in_qt_sgskip.py @@ -26,7 +26,7 @@ class ApplicationWindow(QtWidgets.QMainWindow): def __init__(self): - super(ApplicationWindow, self).__init__() + super().__init__() self._main = QtWidgets.QWidget() self.setCentralWidget(self._main) layout = QtWidgets.QVBoxLayout(self._main) diff --git a/examples/user_interfaces/embedding_in_tk_canvas_sgskip.py b/examples/user_interfaces/embedding_in_tk_canvas_sgskip.py deleted file mode 100644 index 41380b758cd6..000000000000 --- a/examples/user_interfaces/embedding_in_tk_canvas_sgskip.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -====================== -Embedding in Tk Canvas -====================== - -Embedding plots in a Tk Canvas. -""" -import matplotlib as mpl -import numpy as np -import sys -if sys.version_info[0] < 3: - import Tkinter as tk -else: - import tkinter as tk -import matplotlib.backends.tkagg as tkagg -from matplotlib.backends.backend_agg import FigureCanvasAgg - - -def draw_figure(canvas, figure, loc=(0, 0)): - """ Draw a matplotlib figure onto a Tk canvas - - loc: location of top-left corner of figure on canvas in pixels. - Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py - """ - figure_canvas_agg = FigureCanvasAgg(figure) - figure_canvas_agg.draw() - figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds - figure_w, figure_h = int(figure_w), int(figure_h) - photo = tk.PhotoImage(master=canvas, width=figure_w, height=figure_h) - - # Position: convert from top-left anchor to center anchor - canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo) - - # Unfortunately, there's no accessor for the pointer to the native renderer - tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2) - - # Return a handle which contains a reference to the photo object - # which must be kept live or else the picture disappears - return photo - -# Create a canvas -w, h = 300, 200 -window = tk.Tk() -window.title("A figure in a canvas") -canvas = tk.Canvas(window, width=w, height=h) -canvas.pack() - -# Generate some example data -X = np.linspace(0, 2 * np.pi, 50) -Y = np.sin(X) - -# Create the figure we desire to add to an existing canvas -fig = mpl.figure.Figure(figsize=(2, 1)) -ax = fig.add_axes([0, 0, 1, 1]) -ax.plot(X, Y) - -# Keep this handle alive, or else figure will disappear -fig_x, fig_y = 100, 100 -fig_photo = draw_figure(canvas, fig, loc=(fig_x, fig_y)) -fig_w, fig_h = fig_photo.width(), fig_photo.height() - -# Add more elements to the canvas, potentially on top of the figure -canvas.create_line(200, 50, fig_x + fig_w / 2, fig_y + fig_h / 2) -canvas.create_text(200, 50, text="Zero-crossing", anchor="s") - -# Let Tk take over -tk.mainloop() diff --git a/examples/user_interfaces/embedding_in_tk_sgskip.py b/examples/user_interfaces/embedding_in_tk_sgskip.py index f79390d30990..7d32c6a7cffb 100644 --- a/examples/user_interfaces/embedding_in_tk_sgskip.py +++ b/examples/user_interfaces/embedding_in_tk_sgskip.py @@ -5,19 +5,18 @@ """ -from six.moves import tkinter as Tk +import tkinter from matplotlib.backends.backend_tkagg import ( - FigureCanvasTkAgg, NavigationToolbar2TkAgg) + FigureCanvasTkAgg, NavigationToolbar2Tk) # Implement the default Matplotlib key bindings. from matplotlib.backend_bases import key_press_handler from matplotlib.figure import Figure -from six.moves import tkinter as Tk import numpy as np -root = Tk.Tk() +root = tkinter.Tk() root.wm_title("Embedding in Tk") fig = Figure(figsize=(5, 4), dpi=100) @@ -26,11 +25,11 @@ canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea. canvas.draw() -canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) +canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1) -toolbar = NavigationToolbar2TkAgg(canvas, root) +toolbar = NavigationToolbar2Tk(canvas, root) toolbar.update() -canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) +canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1) def on_key_press(event): @@ -47,9 +46,9 @@ def _quit(): # Fatal Python Error: PyEval_RestoreThread: NULL tstate -button = Tk.Button(master=root, text="Quit", command=_quit) -button.pack(side=Tk.BOTTOM) +button = tkinter.Button(master=root, text="Quit", command=_quit) +button.pack(side=tkinter.BOTTOM) -Tk.mainloop() +tkinter.mainloop() # If you put root.destroy() here, it will cause an error if the window is # closed with the window manager. diff --git a/examples/user_interfaces/embedding_in_wx3_sgskip.py b/examples/user_interfaces/embedding_in_wx3_sgskip.py index e27c3bb2e633..fcb69f0a910b 100644 --- a/examples/user_interfaces/embedding_in_wx3_sgskip.py +++ b/examples/user_interfaces/embedding_in_wx3_sgskip.py @@ -21,12 +21,6 @@ Thanks to matplotlib and wx teams for creating such great software! """ -from __future__ import print_function - -import sys -import time -import os -import gc import matplotlib import matplotlib.cm as cm import matplotlib.cbook as cbook diff --git a/examples/user_interfaces/embedding_in_wx4_sgskip.py b/examples/user_interfaces/embedding_in_wx4_sgskip.py index a6b81f97681d..c984e02b06da 100644 --- a/examples/user_interfaces/embedding_in_wx4_sgskip.py +++ b/examples/user_interfaces/embedding_in_wx4_sgskip.py @@ -27,15 +27,9 @@ def __init__(self, canvas, cankill): # for simplicity I'm going to reuse a bitmap from wx, you'll # probably want to add your own. - if 'phoenix' in wx.PlatformInfo: - self.AddTool(self.ON_CUSTOM, 'Click me', - _load_bitmap('back.png'), - 'Activate custom contol') - self.Bind(wx.EVT_TOOL, self._on_custom, id=self.ON_CUSTOM) - else: - self.AddSimpleTool(self.ON_CUSTOM, _load_bitmap('back.png'), - 'Click me', 'Activate custom contol') - self.Bind(wx.EVT_TOOL, self._on_custom, id=self.ON_CUSTOM) + self.AddTool(self.ON_CUSTOM, 'Click me', _load_bitmap('back.png'), + 'Activate custom contol') + self.Bind(wx.EVT_TOOL, self._on_custom, id=self.ON_CUSTOM) def _on_custom(self, evt): # add some text to the axes in a random location in axes (0,1) diff --git a/examples/user_interfaces/embedding_in_wx5_sgskip.py b/examples/user_interfaces/embedding_in_wx5_sgskip.py index 61261cd1297d..c726a988fd89 100644 --- a/examples/user_interfaces/embedding_in_wx5_sgskip.py +++ b/examples/user_interfaces/embedding_in_wx5_sgskip.py @@ -6,13 +6,9 @@ """ import wx +import wx.lib.agw.aui as aui import wx.lib.mixins.inspection as wit -if 'phoenix' in wx.PlatformInfo: - import wx.lib.agw.aui as aui -else: - import wx.aui as aui - import matplotlib as mpl from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar diff --git a/examples/user_interfaces/embedding_webagg_sgskip.py b/examples/user_interfaces/embedding_webagg_sgskip.py index a5d296ae029b..9dd164c53147 100644 --- a/examples/user_interfaces/embedding_webagg_sgskip.py +++ b/examples/user_interfaces/embedding_webagg_sgskip.py @@ -214,10 +214,9 @@ def send_binary(self, blob): def __init__(self, figure): self.figure = figure - self.manager = new_figure_manager_given_figure( - id(figure), figure) + self.manager = new_figure_manager_given_figure(id(figure), figure) - super(MyApplication, self).__init__([ + super().__init__([ # Static files for the CSS and JS (r'/_static/(.*)', tornado.web.StaticFileHandler, diff --git a/examples/user_interfaces/fourier_demo_wx_sgskip.py b/examples/user_interfaces/fourier_demo_wx_sgskip.py index 2a943f253a82..b00cd01d6982 100644 --- a/examples/user_interfaces/fourier_demo_wx_sgskip.py +++ b/examples/user_interfaces/fourier_demo_wx_sgskip.py @@ -180,8 +180,7 @@ def createPlots(self): # This method creates the subplots, waveforms and labels. # Later, when the waveforms or sliders are dragged, only the # waveform data will be updated (not here, but below in setKnob). - if not hasattr(self, 'subplot1'): - self.subplot1, self.subplot2 = self.figure.subplots(2) + self.subplot1, self.subplot2 = self.figure.subplots(2) x1, y1, x2, y2 = self.compute(self.f0.value, self.A.value) color = (1., 0., 0.) self.lines += self.subplot1.plot(x1, y1, color=color, linewidth=2) diff --git a/examples/user_interfaces/gtk_spreadsheet_sgskip.py b/examples/user_interfaces/gtk_spreadsheet_sgskip.py index 41d4aca37418..476022db1c44 100644 --- a/examples/user_interfaces/gtk_spreadsheet_sgskip.py +++ b/examples/user_interfaces/gtk_spreadsheet_sgskip.py @@ -8,55 +8,54 @@ data """ -import pygtk -pygtk.require('2.0') -import gtk -from gtk import gdk +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk -import matplotlib -matplotlib.use('GTKAgg') # or 'GTK' -from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas +from matplotlib.backends.backend_gtk3agg import FigureCanvas +# from matplotlib.backends.backend_gtk3cairo import FigureCanvas from numpy.random import random from matplotlib.figure import Figure -class DataManager(gtk.Window): +class DataManager(Gtk.Window): numRows, numCols = 20, 10 data = random((numRows, numCols)) def __init__(self): - gtk.Window.__init__(self) + Gtk.Window.__init__(self) self.set_default_size(600, 600) - self.connect('destroy', lambda win: gtk.main_quit()) + self.connect('destroy', lambda win: Gtk.main_quit()) self.set_title('GtkListStore demo') self.set_border_width(8) - vbox = gtk.VBox(False, 8) + vbox = Gtk.VBox(False, 8) self.add(vbox) - label = gtk.Label('Double click a row to plot the data') + label = Gtk.Label('Double click a row to plot the data') - vbox.pack_start(label, False, False) + vbox.pack_start(label, False, False, 0) - sw = gtk.ScrolledWindow() - sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - sw.set_policy(gtk.POLICY_NEVER, - gtk.POLICY_AUTOMATIC) - vbox.pack_start(sw, True, True) + sw = Gtk.ScrolledWindow() + sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + sw.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + vbox.pack_start(sw, True, True, 0) model = self.create_model() - self.treeview = gtk.TreeView(model) + self.treeview = Gtk.TreeView(model) self.treeview.set_rules_hint(True) # matplotlib stuff fig = Figure(figsize=(6, 4)) - self.canvas = FigureCanvas(fig) # a gtk.DrawingArea - vbox.pack_start(self.canvas, True, True) + self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea + vbox.pack_start(self.canvas, True, True, 0) ax = fig.add_subplot(111) self.line, = ax.plot(self.data[0, :], 'go') # plot the first row @@ -65,9 +64,9 @@ def __init__(self): self.add_columns() - self.add_events(gdk.BUTTON_PRESS_MASK | - gdk.KEY_PRESS_MASK | - gdk.KEY_RELEASE_MASK) + self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.KEY_PRESS_MASK | + Gdk.EventMask.KEY_RELEASE_MASK) def plot_row(self, treeview, path, view_column): ind, = path # get the index into data @@ -77,18 +76,18 @@ def plot_row(self, treeview, path, view_column): def add_columns(self): for i in range(self.numCols): - column = gtk.TreeViewColumn('%d' % i, gtk.CellRendererText(), text=i) + column = Gtk.TreeViewColumn(str(i), Gtk.CellRendererText(), text=i) self.treeview.append_column(column) def create_model(self): types = [float]*self.numCols - store = gtk.ListStore(*types) + store = Gtk.ListStore(*types) for row in self.data: - store.append(row) + store.append(tuple(row)) return store manager = DataManager() manager.show_all() -gtk.main() +Gtk.main() diff --git a/examples/user_interfaces/histogram_demo_canvasagg_sgskip.py b/examples/user_interfaces/histogram_demo_canvasagg_sgskip.py deleted file mode 100644 index 1757a1245b5f..000000000000 --- a/examples/user_interfaces/histogram_demo_canvasagg_sgskip.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -======================== -Histogram Demo CanvasAgg -======================== - -This is an example that shows you how to work directly with the agg -figure canvas to create a figure using the pythonic API. - -In this example, the contents of the agg canvas are extracted to a -string, which can in turn be passed off to PIL or put in a numeric -array - - -""" -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.figure import Figure -import numpy as np - -fig = Figure(figsize=(5, 4), dpi=100) -ax = fig.add_subplot(111) - -canvas = FigureCanvasAgg(fig) - -mu, sigma = 100, 15 -x = mu + sigma * np.random.randn(10000) - -# the histogram of the data -n, bins, patches = ax.hist(x, 50, density=True) - -# add a 'best fit' line -y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * - np.exp(-0.5 * (1 / sigma * (bins - mu))**2)) -line, = ax.plot(bins, y, 'r--') -line.set_linewidth(1) - -ax.set_xlabel('Smarts') -ax.set_ylabel('Probability') -ax.set_title(r'$\mathrm{Histogram of IQ: }\mu=100, \sigma=15$') - -ax.set_xlim((40, 160)) -ax.set_ylim((0, 0.03)) - -canvas.draw() - -s = canvas.tostring_rgb() # save this and convert to bitmap as needed - -# Get the figure dimensions for creating bitmaps or NumPy arrays, -# etc. -l, b, w, h = fig.bbox.bounds -w, h = int(w), int(h) - -if 0: - # Convert to a NumPy array - X = np.fromstring(s, np.uint8).reshape((h, w, 3)) - -if 0: - # pass off to PIL - from PIL import Image - im = Image.fromstring("RGB", (w, h), s) - im.show() diff --git a/examples/user_interfaces/lineprops_dialog_gtk_sgskip.py b/examples/user_interfaces/lineprops_dialog_gtk_sgskip.py deleted file mode 100644 index 584fa6d49e80..000000000000 --- a/examples/user_interfaces/lineprops_dialog_gtk_sgskip.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -==================== -Lineprops Dialog GTK -==================== - -""" -import matplotlib -matplotlib.use('GTKAgg') -from matplotlib.backends.backend_gtk import DialogLineprops - -import numpy as np -import matplotlib.pyplot as plt - - -def f(t): - s1 = np.cos(2*np.pi*t) - e1 = np.exp(-t) - return np.multiply(s1, e1) - -t1 = np.arange(0.0, 5.0, 0.1) -t2 = np.arange(0.0, 5.0, 0.02) -t3 = np.arange(0.0, 2.0, 0.01) - -fig, ax = plt.subplots() -l1, = ax.plot(t1, f(t1), 'bo', label='line 1') -l2, = ax.plot(t2, f(t2), 'k--', label='line 2') - -dlg = DialogLineprops([l1, l2]) -dlg.show() -plt.show() diff --git a/examples/user_interfaces/mathtext_wx_sgskip.py b/examples/user_interfaces/mathtext_wx_sgskip.py index b06162b2f0f3..53a861986c23 100644 --- a/examples/user_interfaces/mathtext_wx_sgskip.py +++ b/examples/user_interfaces/mathtext_wx_sgskip.py @@ -10,7 +10,7 @@ import matplotlib matplotlib.use("WxAgg") from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas -from matplotlib.backends.backend_wx import NavigationToolbar2Wx, wxc +from matplotlib.backends.backend_wx import NavigationToolbar2Wx from matplotlib.figure import Figure import numpy as np @@ -27,7 +27,7 @@ def mathtext_to_wxbitmap(s): ftimage, depth = mathtext_parser.parse(s, 150) - return wxc.BitmapFromBuffer( + return wx.Bitmap.FromBufferRGBA( ftimage.get_width(), ftimage.get_height(), ftimage.as_rgba_str()) ############################################################ @@ -43,7 +43,6 @@ def mathtext_to_wxbitmap(s): class CanvasFrame(wx.Frame): def __init__(self, parent, title): wx.Frame.__init__(self, parent, -1, title, size=(550, 350)) - self.SetBackgroundColour(wxc.NamedColour("WHITE")) self.figure = Figure() self.axes = self.figure.add_subplot(111) @@ -61,8 +60,9 @@ def __init__(self, parent, title): # File Menu menu = wx.Menu() - menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") + m_exit = menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") menuBar.Append(menu, "&File") + self.Bind(wx.EVT_MENU, self.OnClose, m_exit) if IS_GTK or IS_WIN: # Equation Menu @@ -71,7 +71,7 @@ def __init__(self, parent, title): bm = mathtext_to_wxbitmap(mt) item = wx.MenuItem(menu, 1000 + i, " ") item.SetBitmap(bm) - menu.AppendItem(item) + menu.Append(item) self.Bind(wx.EVT_MENU, self.OnChangePlot, item) menuBar.Append(menu, "&Functions") @@ -113,6 +113,9 @@ def change_plot(self, plot_number): self.axes.plot(t, s) self.canvas.draw() + def OnClose(self, event): + self.Destroy() + class MyApp(wx.App): def OnInit(self): diff --git a/examples/user_interfaces/mpl_with_glade.glade b/examples/user_interfaces/mpl_with_glade.glade deleted file mode 100644 index 96e3278b490e..000000000000 --- a/examples/user_interfaces/mpl_with_glade.glade +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - True - window1 - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_NORMAL - GDK_GRAVITY_NORTH_WEST - True - - - - - - - 4 - True - False - 2 - - - - True - - - - True - _File - True - - - - - - - True - gtk-new - True - - - - - - - True - gtk-open - True - - - - - - - True - gtk-save - True - - - - - - - True - gtk-save-as - True - - - - - - - True - - - - - - True - gtk-quit - True - - - - - - - - - - - True - _Edit - True - - - - - - - True - gtk-cut - True - - - - - - - True - gtk-copy - True - - - - - - - True - gtk-paste - True - - - - - - - True - gtk-delete - True - - - - - - - - - - - True - _View - True - - - - - - - - - - - True - _Help - True - - - - - - - True - _About - True - - - - - - - - - - 0 - False - False - - - - - - - - - - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-dialog-info - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - Click Me! - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - - - - 0 - False - False - - - - - - - diff --git a/examples/user_interfaces/mpl_with_glade_316.glade b/examples/user_interfaces/mpl_with_glade3.glade similarity index 100% rename from examples/user_interfaces/mpl_with_glade_316.glade rename to examples/user_interfaces/mpl_with_glade3.glade diff --git a/examples/user_interfaces/mpl_with_glade_316_sgskip.py b/examples/user_interfaces/mpl_with_glade3_sgskip.py similarity index 75% rename from examples/user_interfaces/mpl_with_glade_316_sgskip.py rename to examples/user_interfaces/mpl_with_glade3_sgskip.py index b5cb5a6637fb..3329bc342da9 100644 --- a/examples/user_interfaces/mpl_with_glade_316_sgskip.py +++ b/examples/user_interfaces/mpl_with_glade3_sgskip.py @@ -1,14 +1,17 @@ """ -========================= -Matplotlib With Glade 316 -========================= +======================= +Matplotlib With Glade 3 +======================= """ +import os + +import gi +gi.require_version('Gtk', '3.0') from gi.repository import Gtk from matplotlib.figure import Figure -from matplotlib.axes import Subplot from matplotlib.backends.backend_gtk3agg import ( FigureCanvasGTK3Agg as FigureCanvas) import numpy as np @@ -21,7 +24,9 @@ def on_window1_destroy(self, widget): def main(): builder = Gtk.Builder() - builder.add_objects_from_file("mpl_with_glade_316.glade", ("window1", "")) + builder.add_objects_from_file(os.path.join(os.path.dirname(__file__), + "mpl_with_glade3.glade"), + ("window1", "")) builder.connect_signals(Window1Signals()) window = builder.get_object("window1") sw = builder.get_object("scrolledwindow1") diff --git a/examples/user_interfaces/mpl_with_glade_sgskip.py b/examples/user_interfaces/mpl_with_glade_sgskip.py deleted file mode 100644 index ab2652b1365d..000000000000 --- a/examples/user_interfaces/mpl_with_glade_sgskip.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -===================== -Matplotlib With Glade -===================== - -""" -from __future__ import print_function -import matplotlib -matplotlib.use('GTK') - -from matplotlib.figure import Figure -from matplotlib.axes import Subplot -from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas -from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar -from matplotlib.widgets import SpanSelector - -import numpy as np -import gtk -import gtk.glade - - -def simple_msg(msg, parent=None, title=None): - dialog = gtk.MessageDialog( - parent=None, - type=gtk.MESSAGE_INFO, - buttons=gtk.BUTTONS_OK, - message_format=msg) - if parent is not None: - dialog.set_transient_for(parent) - if title is not None: - dialog.set_title(title) - dialog.show() - dialog.run() - dialog.destroy() - return None - - -class GladeHandlers(object): - def on_buttonClickMe_clicked(event): - simple_msg('Nothing to say, really', - parent=widgets['windowMain'], - title='Thanks!') - - -class WidgetsWrapper(object): - def __init__(self): - self.widgets = gtk.glade.XML('mpl_with_glade.glade') - self.widgets.signal_autoconnect(GladeHandlers.__dict__) - - self['windowMain'].connect('destroy', lambda x: gtk.main_quit()) - self['windowMain'].move(10, 10) - self.figure = Figure(figsize=(8, 6), dpi=72) - self.axis = self.figure.add_subplot(111) - - t = np.arange(0.0, 3.0, 0.01) - s = np.sin(2*np.pi*t) - self.axis.plot(t, s) - self.axis.set_xlabel('time (s)') - self.axis.set_ylabel('voltage') - - self.canvas = FigureCanvas(self.figure) # a gtk.DrawingArea - self.canvas.show() - self.canvas.set_size_request(600, 400) - self.canvas.set_events( - gtk.gdk.BUTTON_PRESS_MASK | - gtk.gdk.KEY_PRESS_MASK | - gtk.gdk.KEY_RELEASE_MASK - ) - self.canvas.set_flags(gtk.HAS_FOCUS | gtk.CAN_FOCUS) - self.canvas.grab_focus() - - def keypress(widget, event): - print('key press') - - def buttonpress(widget, event): - print('button press') - - self.canvas.connect('key_press_event', keypress) - self.canvas.connect('button_press_event', buttonpress) - - def onselect(xmin, xmax): - print(xmin, xmax) - - span = SpanSelector(self.axis, onselect, 'horizontal', useblit=False, - rectprops=dict(alpha=0.5, facecolor='red')) - - self['vboxMain'].pack_start(self.canvas, True, True) - self['vboxMain'].show() - - # below is optional if you want the navigation toolbar - self.navToolbar = NavigationToolbar(self.canvas, self['windowMain']) - self.navToolbar.lastDir = '/var/tmp/' - self['vboxMain'].pack_start(self.navToolbar) - self.navToolbar.show() - - sep = gtk.HSeparator() - sep.show() - self['vboxMain'].pack_start(sep, True, True) - - self['vboxMain'].reorder_child(self['buttonClickMe'], -1) - - def __getitem__(self, key): - return self.widgets.get_widget(key) - -widgets = WidgetsWrapper() -gtk.main() diff --git a/examples/user_interfaces/pylab_with_gtk_sgskip.py b/examples/user_interfaces/pylab_with_gtk_sgskip.py index 75c623801745..093105f1bd46 100644 --- a/examples/user_interfaces/pylab_with_gtk_sgskip.py +++ b/examples/user_interfaces/pylab_with_gtk_sgskip.py @@ -1,14 +1,13 @@ """ -============== -Pylab With GTK -============== +=============== +Pyplot With GTK +=============== -An example of how to use pylab to manage your figure windows, but +An example of how to use pyplot to manage your figure windows, but modify the GUI by accessing the underlying gtk widgets """ -from __future__ import print_function import matplotlib -matplotlib.use('GTKAgg') +matplotlib.use('GTK3Agg') # or 'GTK3Cairo' import matplotlib.pyplot as plt @@ -23,9 +22,11 @@ toolbar = manager.toolbar # now let's add a button to the toolbar -import gtk -next = 8 # where to insert this in the mpl toolbar -button = gtk.Button('Click me') +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +pos = 8 # where to insert this in the mpl toolbar +button = Gtk.Button('Click me') button.show() @@ -33,22 +34,20 @@ def clicked(button): print('hi mom') button.connect('clicked', clicked) -toolitem = gtk.ToolItem() +toolitem = Gtk.ToolItem() toolitem.show() -toolitem.set_tooltip( - toolbar.tooltips, - 'Click me for fun and profit') +toolitem.set_tooltip_text('Click me for fun and profit') toolitem.add(button) -toolbar.insert(toolitem, next) -next += 1 +toolbar.insert(toolitem, pos) +pos += 1 # now let's add a widget to the vbox -label = gtk.Label() +label = Gtk.Label() label.set_markup('Drag mouse over axes for position') label.show() vbox = manager.vbox -vbox.pack_start(label, False, False) +vbox.pack_start(label, False, False, 0) vbox.reorder_child(manager.toolbar, -1) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 247997f6e2e2..f1fd7c6146bc 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -13,15 +13,8 @@ Using `matplotlib.backend_managers.ToolManager` """ - -from __future__ import print_function -import matplotlib -# Change to the desired backend -matplotlib.use('GTK3Cairo') -# matplotlib.use('TkAgg') -# matplotlib.use('QT5Agg') -matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt +plt.rcParams['toolbar'] = 'toolmanager' from matplotlib.backend_tools import ToolBase, ToolToggleBase @@ -57,9 +50,9 @@ class GroupHideTool(ToolToggleBase): description = 'Show by gid' default_toggled = True - def __init__(self, *args, **kwargs): - self.gid = kwargs.pop('gid') - ToolToggleBase.__init__(self, *args, **kwargs) + def __init__(self, *args, gid, **kwargs): + self.gid = gid + super().__init__(*args, **kwargs) def enable(self, *args): self.set_lines_visibility(True) diff --git a/examples/user_interfaces/wxcursor_demo_sgskip.py b/examples/user_interfaces/wxcursor_demo_sgskip.py index d1b7650cc2f6..e74cf3e11f6e 100644 --- a/examples/user_interfaces/wxcursor_demo_sgskip.py +++ b/examples/user_interfaces/wxcursor_demo_sgskip.py @@ -8,7 +8,7 @@ from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas -from matplotlib.backends.backend_wx import NavigationToolbar2Wx, wxc +from matplotlib.backends.backend_wx import NavigationToolbar2Wx from matplotlib.figure import Figure import numpy as np @@ -17,10 +17,7 @@ class CanvasFrame(wx.Frame): def __init__(self, ): - wx.Frame.__init__(self, None, -1, - 'CanvasFrame', size=(550, 350)) - - self.SetBackgroundColour(wxc.NamedColour("WHITE")) + wx.Frame.__init__(self, None, -1, 'CanvasFrame', size=(550, 350)) self.figure = Figure() self.axes = self.figure.add_subplot(111) @@ -33,7 +30,8 @@ def __init__(self, ): self.figure_canvas = FigureCanvas(self, -1, self.figure) # Note that event is a MplEvent - self.figure_canvas.mpl_connect('motion_notify_event', self.UpdateStatusBar) + self.figure_canvas.mpl_connect( + 'motion_notify_event', self.UpdateStatusBar) self.figure_canvas.Bind(wx.EVT_ENTER_WINDOW, self.ChangeCursor) self.sizer = wx.BoxSizer(wx.VERTICAL) @@ -49,14 +47,12 @@ def __init__(self, ): self.toolbar.Show() def ChangeCursor(self, event): - self.figure_canvas.SetCursor(wxc.StockCursor(wx.CURSOR_BULLSEYE)) + self.figure_canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE)) def UpdateStatusBar(self, event): if event.inaxes: - x, y = event.xdata, event.ydata - self.statusBar.SetStatusText(("x= " + str(x) + - " y=" + str(y)), - 0) + self.statusBar.SetStatusText( + "x={} y={}".format(event.xdata, event.ydata)) class App(wx.App): diff --git a/examples/userdemo/anchored_box01.py b/examples/userdemo/anchored_box01.py index 3cadfec6a558..00f75c7f5518 100644 --- a/examples/userdemo/anchored_box01.py +++ b/examples/userdemo/anchored_box01.py @@ -11,7 +11,7 @@ fig, ax = plt.subplots(figsize=(3, 3)) at = AnchoredText("Figure 1a", - prop=dict(size=15), frameon=True, loc=2) + prop=dict(size=15), frameon=True, loc='upper left') at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") ax.add_artist(at) diff --git a/examples/userdemo/anchored_box02.py b/examples/userdemo/anchored_box02.py index 8aa172aaa8b8..59db0a4180a8 100644 --- a/examples/userdemo/anchored_box02.py +++ b/examples/userdemo/anchored_box02.py @@ -12,7 +12,7 @@ fig, ax = plt.subplots(figsize=(3, 3)) ada = AnchoredDrawingArea(40, 20, 0, 0, - loc=1, pad=0., frameon=False) + loc='upper right', pad=0., frameon=False) p1 = Circle((10, 10), 10) ada.drawing_area.add_artist(p1) p2 = Circle((30, 10), 5, fc="r") diff --git a/examples/userdemo/anchored_box03.py b/examples/userdemo/anchored_box03.py index 0979a84f8cab..ba673d8471a5 100644 --- a/examples/userdemo/anchored_box03.py +++ b/examples/userdemo/anchored_box03.py @@ -11,7 +11,7 @@ fig, ax = plt.subplots(figsize=(3, 3)) -box = AnchoredAuxTransformBox(ax.transData, loc=2) +box = AnchoredAuxTransformBox(ax.transData, loc='upper left') el = Ellipse((0, 0), width=0.1, height=0.4, angle=30) # in data coordinates! box.drawing_area.add_artist(el) diff --git a/examples/userdemo/anchored_box04.py b/examples/userdemo/anchored_box04.py index d934c6764150..d641e7a18ac4 100644 --- a/examples/userdemo/anchored_box04.py +++ b/examples/userdemo/anchored_box04.py @@ -26,7 +26,7 @@ align="center", pad=0, sep=5) -anchored_box = AnchoredOffsetbox(loc=3, +anchored_box = AnchoredOffsetbox(loc='lower left', child=box, pad=0., frameon=True, bbox_to_anchor=(0., 1.02), diff --git a/examples/userdemo/connectionstyle_demo.py b/examples/userdemo/connectionstyle_demo.py index 4c8abe22e4a5..1ea2bf5fe8fd 100644 --- a/examples/userdemo/connectionstyle_demo.py +++ b/examples/userdemo/connectionstyle_demo.py @@ -6,7 +6,6 @@ """ import matplotlib.pyplot as plt -import matplotlib.patches as mpatches fig, axs = plt.subplots(3, 5, figsize=(8, 4.8)) diff --git a/examples/userdemo/custom_boxstyle02.py b/examples/userdemo/custom_boxstyle02.py index f80705f3dfaa..5b2ef39d7a7b 100644 --- a/examples/userdemo/custom_boxstyle02.py +++ b/examples/userdemo/custom_boxstyle02.py @@ -26,7 +26,7 @@ def __init__(self, pad=0.3): """ self.pad = pad - super(MyStyle, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): """ diff --git a/examples/userdemo/demo_gridspec02.py b/examples/userdemo/demo_gridspec02.py deleted file mode 100644 index 15d75b2c642c..000000000000 --- a/examples/userdemo/demo_gridspec02.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -=============== -Demo Gridspec02 -=============== - -""" -import matplotlib.pyplot as plt -from matplotlib.gridspec import GridSpec - - -def make_ticklabels_invisible(fig): - for i, ax in enumerate(fig.axes): - ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center") - ax.tick_params(labelbottom=False, labelleft=False) - - -fig = plt.figure() - -gs = GridSpec(3, 3) -ax1 = plt.subplot(gs[0, :]) -# identical to ax1 = plt.subplot(gs.new_subplotspec((0, 0), colspan=3)) -ax2 = plt.subplot(gs[1, :-1]) -ax3 = plt.subplot(gs[1:, -1]) -ax4 = plt.subplot(gs[-1, 0]) -ax5 = plt.subplot(gs[-1, -2]) - -fig.suptitle("GridSpec") -make_ticklabels_invisible(fig) - -plt.show() diff --git a/examples/userdemo/pgf_fonts_sgskip.py b/examples/userdemo/pgf_fonts.py similarity index 78% rename from examples/userdemo/pgf_fonts_sgskip.py rename to examples/userdemo/pgf_fonts.py index 0528b8ef88d0..463d5c7e6887 100644 --- a/examples/userdemo/pgf_fonts_sgskip.py +++ b/examples/userdemo/pgf_fonts.py @@ -4,25 +4,21 @@ ========= """ -# -*- coding: utf-8 -*- -import matplotlib as mpl -mpl.use("pgf") -pgf_with_rc_fonts = { +import matplotlib.pyplot as plt +plt.rcParams.update({ "font.family": "serif", "font.serif": [], # use latex default serif font "font.sans-serif": ["DejaVu Sans"], # use a specific sans-serif font -} -mpl.rcParams.update(pgf_with_rc_fonts) +}) -import matplotlib.pyplot as plt plt.figure(figsize=(4.5, 2.5)) plt.plot(range(5)) plt.text(0.5, 3., "serif") plt.text(0.5, 2., "monospace", family="monospace") plt.text(2.5, 2., "sans-serif", family="sans-serif") plt.text(2.5, 1., "comic sans", family="Comic Sans MS") -plt.xlabel(u"µ is not $\\mu$") +plt.xlabel("µ is not $\\mu$") plt.tight_layout(.5) plt.savefig("pgf_fonts.pdf") diff --git a/examples/userdemo/pgf_preamble_sgskip.py b/examples/userdemo/pgf_preamble_sgskip.py index 46dd45bb1d40..eccdefa0d6e1 100644 --- a/examples/userdemo/pgf_preamble_sgskip.py +++ b/examples/userdemo/pgf_preamble_sgskip.py @@ -4,15 +4,11 @@ ============ """ -# -*- coding: utf-8 -*- -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six import matplotlib as mpl mpl.use("pgf") -pgf_with_custom_preamble = { +import matplotlib.pyplot as plt +plt.rcParams.update({ "font.family": "serif", # use serif/main font for text elements "text.usetex": True, # use inline math for ticks "pgf.rcfonts": False, # don't setup fonts from rc parameters @@ -23,10 +19,8 @@ r"\setmathfont{xits-math.otf}", r"\setmainfont{DejaVu Serif}", # serif font via preamble ] -} -mpl.rcParams.update(pgf_with_custom_preamble) +}) -import matplotlib.pyplot as plt plt.figure(figsize=(4.5, 2.5)) plt.plot(range(5)) plt.xlabel("unicode text: я, ψ, €, ü, \\unitfrac[10]{°}{µm}") diff --git a/examples/userdemo/pgf_texsystem_sgskip.py b/examples/userdemo/pgf_texsystem.py similarity index 77% rename from examples/userdemo/pgf_texsystem_sgskip.py rename to examples/userdemo/pgf_texsystem.py index c4914d1736cf..d3e535183539 100644 --- a/examples/userdemo/pgf_texsystem_sgskip.py +++ b/examples/userdemo/pgf_texsystem.py @@ -4,27 +4,23 @@ ============= """ -# -*- coding: utf-8 -*- -import matplotlib as mpl -mpl.use("pgf") -pgf_with_pdflatex = { +import matplotlib.pyplot as plt +plt.rcParams.update({ "pgf.texsystem": "pdflatex", "pgf.preamble": [ r"\usepackage[utf8x]{inputenc}", r"\usepackage[T1]{fontenc}", r"\usepackage{cmbright}", ] -} -mpl.rcParams.update(pgf_with_pdflatex) +}) -import matplotlib.pyplot as plt plt.figure(figsize=(4.5, 2.5)) plt.plot(range(5)) plt.text(0.5, 3., "serif", family="serif") plt.text(0.5, 2., "monospace", family="monospace") plt.text(2.5, 2., "sans-serif", family="sans-serif") -plt.xlabel(u"µ is not $\\mu$") +plt.xlabel(r"µ is not $\mu$") plt.tight_layout(.5) plt.savefig("pgf_texsystem.pdf") diff --git a/examples/userdemo/simple_legend01.py b/examples/userdemo/simple_legend01.py index 32338a692d77..9e178af9be5b 100644 --- a/examples/userdemo/simple_legend01.py +++ b/examples/userdemo/simple_legend01.py @@ -12,13 +12,13 @@ plt.plot([3, 2, 1], label="test2") # Place a legend above this subplot, expanding itself to # fully use the given bounding box. -plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, +plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', ncol=2, mode="expand", borderaxespad=0.) plt.subplot(223) plt.plot([1, 2, 3], label="test1") plt.plot([3, 2, 1], label="test2") # Place a legend to the right of this smaller subplot. -plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) +plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.) plt.show() diff --git a/examples/userdemo/simple_legend02.py b/examples/userdemo/simple_legend02.py index 4072c9bbf278..2f9be1172572 100644 --- a/examples/userdemo/simple_legend02.py +++ b/examples/userdemo/simple_legend02.py @@ -12,12 +12,12 @@ line2, = ax.plot([3, 2, 1], label="Line 2", linewidth=4) # Create a legend for the first line. -first_legend = ax.legend(handles=[line1], loc=1) +first_legend = ax.legend(handles=[line1], loc='upper right') # Add the legend manually to the current Axes. ax.add_artist(first_legend) # Create another legend for the second line. -ax.legend(handles=[line2], loc=4) +ax.legend(handles=[line2], loc='lower right') plt.show() diff --git a/examples/widgets/cursor.py b/examples/widgets/cursor.py index 5648563d6e35..6fa20ca21177 100644 --- a/examples/widgets/cursor.py +++ b/examples/widgets/cursor.py @@ -20,7 +20,7 @@ ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) -# set useblit = True on gtkagg for enhanced performance +# Set useblit=True on most backends for enhanced performance. cursor = Cursor(ax, useblit=True, color='red', linewidth=2) plt.show() diff --git a/examples/widgets/lasso_selector_demo_sgskip.py b/examples/widgets/lasso_selector_demo_sgskip.py index 9bace4319c51..ac6c7325199f 100644 --- a/examples/widgets/lasso_selector_demo_sgskip.py +++ b/examples/widgets/lasso_selector_demo_sgskip.py @@ -10,7 +10,6 @@ on the graph, hold, and drag it around the points you need to select. """ -from __future__ import print_function import numpy as np diff --git a/examples/widgets/menu.py b/examples/widgets/menu.py index 6458041222ae..326e28fd81a1 100644 --- a/examples/widgets/menu.py +++ b/examples/widgets/menu.py @@ -4,9 +4,7 @@ ==== """ -from __future__ import division, print_function import numpy as np -import matplotlib import matplotlib.colors as colors import matplotlib.patches as patches import matplotlib.mathtext as mathtext @@ -91,7 +89,6 @@ def set_extent(self, x, y, w, h): self.label.ox = x + self.padx self.label.oy = y - self.depth + self.pady/2. - self.rect._update_patch_transform() self.hover = False def draw(self, renderer): diff --git a/examples/widgets/rectangle_selector.py b/examples/widgets/rectangle_selector.py index 56eb208639ce..cbdaf8026197 100644 --- a/examples/widgets/rectangle_selector.py +++ b/examples/widgets/rectangle_selector.py @@ -10,7 +10,6 @@ method 'self.ignore()' it is checked whether the button from eventpress and eventrelease are the same. """ -from __future__ import print_function from matplotlib.widgets import RectangleSelector import numpy as np import matplotlib.pyplot as plt diff --git a/examples/widgets/span_selector.py b/examples/widgets/span_selector.py index 854defc87a0f..e3516b0ef7de 100644 --- a/examples/widgets/span_selector.py +++ b/examples/widgets/span_selector.py @@ -38,7 +38,7 @@ def onselect(xmin, xmax): ax2.set_ylim(thisy.min(), thisy.max()) fig.canvas.draw() -# set useblit True on gtkagg for enhanced performance +# Set useblit=True on most backends for enhanced performance. span = SpanSelector(ax1, onselect, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red')) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index c5accc3c301f..f7b37ee3b612 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -28,7 +28,7 @@ Modules include: :mod:`matplotlib.axes` - defines the :class:`~matplotlib.axes.Axes` class. Most pylab + defines the :class:`~matplotlib.axes.Axes` class. Most pyplot commands are wrappers for :class:`~matplotlib.axes.Axes` methods. The axes module is the highest level of OO access to the library. @@ -90,7 +90,7 @@ a function for setting the matplotlib backend. If used, this function must be called immediately after importing matplotlib for the first time. In particular, it must be called - **before** importing pylab (if pylab is imported). + **before** importing pyplot (if pyplot is imported). matplotlib was initially written by John D. Hunter (1968-2012) and is now developed and maintained by a host of others. @@ -99,39 +99,52 @@ to MATLAB®, a registered trademark of The MathWorks, Inc. """ -from __future__ import absolute_import, division, print_function +# NOTE: This file must remain Python 2 compatible for the foreseeable future, +# to ensure that we error out properly for existing editable installs. -import six +import sys +if sys.version_info < (3, 5): # noqa: E402 + raise ImportError(""" +Matplotlib 3.0+ does not support Python 2.x, 3.0, 3.1, 3.2, 3.3, or 3.4. +Beginning with Matplotlib 3.0, Python 3.5 and above is required. + +See Matplotlib `INSTALL.rst` file for more information: + + https://github.com/matplotlib/matplotlib/blob/master/INSTALL.rst + +""") import atexit -from collections import MutableMapping +from collections.abc import MutableMapping import contextlib import distutils.version import functools import io +import importlib import inspect -import itertools +from inspect import Parameter import locale import logging import os +from pathlib import Path +import pprint import re import shutil import stat -import sys +import subprocess import tempfile +import urllib.request import warnings # cbook must import matplotlib only within function # definitions, so it is safe to import from it here. -from . import cbook +from . import cbook, rcsetup from matplotlib.cbook import ( - _backports, mplDeprecation, dedent, get_label, sanitize_sequence) -from matplotlib.compat import subprocess + MatplotlibDeprecationWarning, dedent, get_label, sanitize_sequence) +from matplotlib.cbook import mplDeprecation # deprecated from matplotlib.rcsetup import defaultParams, validate_backend, cycler import numpy -from six.moves.urllib.request import urlopen -from six.moves import reload_module as reload # Get the version from the _version.py versioneer file. For a git checkout, # this is computed based on the number of commits since the last tag. @@ -141,7 +154,7 @@ _log = logging.getLogger(__name__) -__version__numpy__ = str('1.7.1') # minimum required numpy version +__version__numpy__ = '1.10.0' # minimum required numpy version __bibtex__ = r"""@Article{Hunter:2007, Author = {Hunter, J. D.}, @@ -159,23 +172,17 @@ }""" -_python27 = (sys.version_info.major == 2 and sys.version_info.minor >= 7) -_python34 = (sys.version_info.major == 3 and sys.version_info.minor >= 4) -if not (_python27 or _python34): - raise ImportError("Matplotlib requires Python 2.7 or 3.4 or later") - -if _python27: - _log.addHandler(logging.NullHandler()) - - def compare_versions(a, b): "return True if a is greater than or equal to b" + if isinstance(a, bytes): + cbook.warn_deprecated( + "3.0", "compare_version arguments should be strs.") + a = a.decode('ascii') + if isinstance(b, bytes): + cbook.warn_deprecated( + "3.0", "compare_version arguments should be strs.") + b = b.decode('ascii') if a: - if six.PY3: - if isinstance(a, bytes): - a = a.decode('ascii') - if isinstance(b, bytes): - b = b.decode('ascii') a = distutils.version.LooseVersion(a) b = distutils.version.LooseVersion(b) return a >= b @@ -189,11 +196,6 @@ def compare_versions(a, b): raise ImportError("Matplotlib requires dateutil") -if not compare_versions(six.__version__, '1.10'): - raise ImportError( - "Matplotlib requires six>=1.10; you have %s" % six.__version__) - - try: import pyparsing except ImportError: @@ -212,16 +214,9 @@ def compare_versions(a, b): if not hasattr(sys, 'argv'): # for modpython - sys.argv = [str('modpython')] + sys.argv = ['modpython'] -def _is_writable_dir(p): - """ - p is a string pointing to a putative writable dir -- return True p - is such a string, else False - """ - return os.access(p, os.W_OK) and os.path.isdir(p) - _verbose_msg = """\ matplotlib.verbose is deprecated; Command line argument --verbose-LEVEL is deprecated. @@ -392,33 +387,40 @@ def ge(self, level): verbose = Verbose() -def _wrap(fmt, func, level=logging.DEBUG, always=True): +def _logged_cached(fmt, func=None): """ - return a callable function that wraps func and reports its - output through logger + Decorator that logs a function's return value, and memoizes that value. - if always is True, the report will occur on every function - call; otherwise only on the first time the function is called - """ - assert callable(func) + After :: - def wrapper(*args, **kwargs): - ret = func(*args, **kwargs) + @_logged_cached(fmt) + def func(): ... - if (always or not wrapper._spoke): - _log.log(level, fmt % ret) - spoke = True - if not wrapper._spoke: - wrapper._spoke = spoke + the first call to *func* will log its return value at the DEBUG level using + %-format string *fmt*, and memoize it; later calls to *func* will directly + return that value. + """ + if func is None: # Return the actual decorator. + return functools.partial(_logged_cached, fmt) + + called = False + ret = None + + @functools.wraps(func) + def wrapper(): + nonlocal called, ret + if not called: + ret = func() + called = True + _log.debug(fmt, ret) return ret - wrapper._spoke = False - wrapper.__doc__ = func.__doc__ + return wrapper def checkdep_dvipng(): try: - s = subprocess.Popen([str('dvipng'), '-version'], + s = subprocess.Popen(['dvipng', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = s.communicate() @@ -439,7 +441,7 @@ def checkdep_ghostscript(): for gs_exec in gs_execs: try: s = subprocess.Popen( - [str(gs_exec), '--version'], stdout=subprocess.PIPE, + [gs_exec, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = s.communicate() if s.returncode == 0: @@ -453,26 +455,9 @@ def checkdep_ghostscript(): checkdep_ghostscript.version = None -# Deprecated, as it is unneeded and some distributions (e.g. MiKTeX 2.9.6350) -# do not actually report the TeX version. -@cbook.deprecated("2.1") -def checkdep_tex(): - try: - s = subprocess.Popen([str('tex'), '-version'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = s.communicate() - line = stdout.decode('ascii').split('\n')[0] - pattern = r'3\.1\d+' - match = re.search(pattern, line) - v = match.group(0) - return v - except (IndexError, ValueError, AttributeError, OSError): - return None - - def checkdep_pdftops(): try: - s = subprocess.Popen([str('pdftops'), '-v'], stdout=subprocess.PIPE, + s = subprocess.Popen(['pdftops', '-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = s.communicate() lines = stderr.decode('ascii').split('\n') @@ -487,7 +472,7 @@ def checkdep_pdftops(): def checkdep_inkscape(): if checkdep_inkscape.version is None: try: - s = subprocess.Popen([str('inkscape'), '-V'], + s = subprocess.Popen(['inkscape', '-V'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = s.communicate() @@ -503,23 +488,6 @@ def checkdep_inkscape(): checkdep_inkscape.version = None -@cbook.deprecated("2.1") -def checkdep_xmllint(): - try: - s = subprocess.Popen([str('xmllint'), '--version'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = s.communicate() - lines = stderr.decode('ascii').split('\n') - for line in lines: - if 'version' in line: - v = line.split()[-1] - break - return v - except (IndexError, ValueError, UnboundLocalError, OSError): - return None - - def checkdep_ps_distiller(s): if not s: return False @@ -562,7 +530,7 @@ def checkdep_usetex(s): dvipng_req = '1.6' flag = True - if _backports.which("tex") is None: + if shutil.which("tex") is None: flag = False warnings.warn('matplotlibrc text.usetex option can not be used unless ' 'TeX is installed on your system') @@ -584,30 +552,22 @@ def checkdep_usetex(s): return flag -def _get_home(): - """Find user's home directory if possible. - Otherwise, returns None. +@_logged_cached('$HOME=%s') +def get_home(): + """ + Return the user's home directory. - :see: - http://mail.python.org/pipermail/python-list/2005-February/325395.html + If the user's home directory cannot be found, return None. """ - if six.PY2 and sys.platform == 'win32': - path = os.path.expanduser(b"~").decode(sys.getfilesystemencoding()) - else: - path = os.path.expanduser("~") - if os.path.isdir(path): - return path - for evar in ('HOME', 'USERPROFILE', 'TMP'): - path = os.environ.get(evar) - if path is not None and os.path.isdir(path): - return path - return None + try: + return str(Path.home()) + except Exception: + return None def _create_tmp_config_dir(): """ - If the config directory can not be created, create a temporary - directory. + If the config directory can not be created, create a temporary directory. """ configdir = os.environ['MPLCONFIGDIR'] = ( tempfile.mkdtemp(prefix='matplotlib-')) @@ -615,21 +575,16 @@ def _create_tmp_config_dir(): return configdir -get_home = _wrap('$HOME=%s', _get_home, always=False) - - def _get_xdg_config_dir(): """ Returns the XDG configuration directory, according to the `XDG base directory spec `_. """ - path = os.environ.get('XDG_CONFIG_HOME') - if path is None: - path = get_home() - if path is not None: - path = os.path.join(path, '.config') - return path + return (os.environ.get('XDG_CONFIG_HOME') + or (str(Path(get_home(), ".config")) + if get_home() + else None)) def _get_xdg_cache_dir(): @@ -638,64 +593,46 @@ def _get_xdg_cache_dir(): base directory spec `_. """ - path = os.environ.get('XDG_CACHE_HOME') - if path is None: - path = get_home() - if path is not None: - path = os.path.join(path, '.cache') - return path + return (os.environ.get('XDG_CACHE_HOME') + or (str(Path(get_home(), ".cache")) + if get_home() + else None)) def _get_config_or_cache_dir(xdg_base): - from matplotlib.cbook import mkdirs - configdir = os.environ.get('MPLCONFIGDIR') - if configdir is not None: - configdir = os.path.abspath(configdir) - if not os.path.exists(configdir): - mkdirs(configdir) - - if not _is_writable_dir(configdir): - return _create_tmp_config_dir() - return configdir - - p = None - h = get_home() - if h is not None: - p = os.path.join(h, '.matplotlib') - if sys.platform.startswith(('linux', 'freebsd')): - p = None - if xdg_base is not None: - p = os.path.join(xdg_base, 'matplotlib') - - if p is not None: - if os.path.exists(p): - if _is_writable_dir(p): - return p + if configdir: + configdir = Path(configdir).resolve() + elif sys.platform.startswith(('linux', 'freebsd')) and xdg_base: + configdir = Path(xdg_base, "matplotlib") + elif get_home(): + configdir = Path(get_home(), ".matplotlib") + else: + configdir = None + + if configdir: + try: + configdir.mkdir(parents=True, exist_ok=True) + except OSError: + pass else: - try: - mkdirs(p) - except OSError: - pass - else: - return p + if os.access(str(configdir), os.W_OK) and configdir.is_dir(): + return str(configdir) return _create_tmp_config_dir() -def _get_configdir(): +@_logged_cached('CONFIGDIR=%s') +def get_configdir(): """ Return the string representing the configuration directory. The directory is chosen as follows: 1. If the MPLCONFIGDIR environment variable is supplied, choose that. - 2a. On Linux, follow the XDG specification and look first in `$XDG_CONFIG_HOME`, if defined, or `$HOME/.config`. - 2b. On other platforms, choose `$HOME/.matplotlib`. - 3. If the chosen directory exists and is writable, use that as the configuration directory. 4. If possible, create a temporary directory, and use it as the @@ -704,10 +641,9 @@ def _get_configdir(): """ return _get_config_or_cache_dir(_get_xdg_config_dir()) -get_configdir = _wrap('CONFIGDIR=%s', _get_configdir, always=False) - -def _get_cachedir(): +@_logged_cached('CACHEDIR=%s') +def get_cachedir(): """ Return the location of the cache directory. @@ -716,15 +652,6 @@ def _get_cachedir(): """ return _get_config_or_cache_dir(_get_xdg_cache_dir()) -get_cachedir = _wrap('CACHEDIR=%s', _get_cachedir, always=False) - - -def _decode_filesystem_path(path): - if not isinstance(path, str): - return path.decode(sys.getfilesystemencoding()) - else: - return path - def _get_data_path(): 'get the path to matplotlib data' @@ -736,61 +663,40 @@ def _get_data_path(): 'directory') return path - _file = _decode_filesystem_path(__file__) - path = os.sep.join([os.path.dirname(_file), 'mpl-data']) - if os.path.isdir(path): - return path - - # setuptools' namespace_packages may highjack this init file - # so need to try something known to be in matplotlib, not basemap - import matplotlib.afm - _file = _decode_filesystem_path(matplotlib.afm.__file__) - path = os.sep.join([os.path.dirname(_file), 'mpl-data']) - if os.path.isdir(path): - return path - - # py2exe zips pure python, so still need special check - if getattr(sys, 'frozen', None): - exe_path = os.path.dirname(_decode_filesystem_path(sys.executable)) - path = os.path.join(exe_path, 'mpl-data') - if os.path.isdir(path): - return path - - # Try again assuming we need to step up one more directory - path = os.path.join(os.path.split(exe_path)[0], 'mpl-data') - if os.path.isdir(path): - return path - - # Try again assuming sys.path[0] is a dir not a exe - path = os.path.join(sys.path[0], 'mpl-data') - if os.path.isdir(path): - return path + def get_candidate_paths(): + yield Path(__file__).with_name('mpl-data') + # setuptools' namespace_packages may highjack this init file + # so need to try something known to be in Matplotlib, not basemap. + import matplotlib.afm + yield Path(matplotlib.afm.__file__).with_name('mpl-data') + # py2exe zips pure python, so still need special check. + if getattr(sys, 'frozen', None): + yield Path(sys.executable).with_name('mpl-data') + # Try again assuming we need to step up one more directory. + yield Path(sys.executable).parent.with_name('mpl-data') + # Try again assuming sys.path[0] is a dir not a exe. + yield Path(sys.path[0]) / 'mpl-data' + + for path in get_candidate_paths(): + if path.is_dir(): + return str(path) raise RuntimeError('Could not find the matplotlib data files') -def _get_data_path_cached(): +@_logged_cached('matplotlib data path: %s') +def get_data_path(): if defaultParams['datapath'][0] is None: defaultParams['datapath'][0] = _get_data_path() return defaultParams['datapath'][0] -get_data_path = _wrap('matplotlib data path %s', _get_data_path_cached, - always=False) - def get_py2exe_datafiles(): - datapath = get_data_path() - _, tail = os.path.split(datapath) + data_path = Path(get_data_path()) d = {} - for root, _, files in os.walk(datapath): - # Need to explicitly remove cocoa_agg files or py2exe complains - # NOTE I don't know why, but do as previous version - if 'Matplotlib.nib' in files: - files.remove('Matplotlib.nib') - files = [os.path.join(root, filename) for filename in files] - root = root.replace(tail, 'mpl-data') - root = root[root.index('mpl-data'):] - d[root] = files + for path in filter(Path.is_file, data_path.glob("**/*")): + (d.setdefault(str(path.parent.relative_to(data_path.parent)), []) + .append(str(path))) return list(d.items()) @@ -826,7 +732,7 @@ def matplotlib_fname(): """ def gen_candidates(): - yield os.path.join(six.moves.getcwd(), 'matplotlibrc') + yield os.path.join(os.getcwd(), 'matplotlibrc') try: matplotlibrc = os.environ['MATPLOTLIBRC'] except KeyError: @@ -834,7 +740,7 @@ def gen_candidates(): else: yield matplotlibrc yield os.path.join(matplotlibrc, 'matplotlibrc') - yield os.path.join(_get_configdir(), 'matplotlibrc') + yield os.path.join(get_configdir(), 'matplotlibrc') yield os.path.join(get_data_path(), 'matplotlibrc') for fname in gen_candidates(): @@ -847,23 +753,31 @@ def gen_candidates(): return fname -# names of keys to deprecate -# the values are a tuple of (new_name, f_old_2_new, f_new_2_old) -# the inverse function may be `None` +# rcParams deprecated and automatically mapped to another key. +# Values are tuples of (version, new_name, f_old2new, f_new2old). _deprecated_map = {} -_deprecated_ignore_map = {'nbagg.transparent': 'figure.facecolor'} +# rcParams deprecated; some can manually be mapped to another key. +# Values are tuples of (version, new_name_or_None). +_deprecated_ignore_map = { + 'text.dvipnghack': ('2.1', None), + 'nbagg.transparent': ('2.2', 'figure.facecolor'), + 'plugins.directory': ('2.2', None), + 'pgf.debug': ('3.0', None), +} -_obsolete_set = {'plugins.directory', 'text.dvipnghack'} +# rcParams deprecated; can use None to suppress warnings; remain actually +# listed in the rcParams (not included in _all_deprecated). +# Values are typles of (version,) +_deprecated_remain_as_none = { + 'axes.hold': ('2.1',), + 'backend.qt4': ('2.2',), + 'backend.qt5': ('2.2',), + 'text.latex.unicode': ('3.0',), +} -# The following may use a value of None to suppress the warning. -# do NOT include in _all_deprecated -_deprecated_set = {'axes.hold', - 'backend.qt4', - 'backend.qt5'} -_all_deprecated = set(itertools.chain( - _deprecated_ignore_map, _deprecated_map, _obsolete_set)) +_all_deprecated = {*_deprecated_map, *_deprecated_ignore_map} class RcParams(MutableMapping, dict): @@ -875,19 +789,38 @@ class RcParams(MutableMapping, dict): :mod:`matplotlib.rcsetup` """ - validate = dict((key, converter) for key, (default, converter) in - six.iteritems(defaultParams) - if key not in _all_deprecated) - msg_depr = "%s is deprecated and replaced with %s; please use the latter." - msg_depr_set = ("%s is deprecated. Please remove it from your " - "matplotlibrc and/or style files.") - msg_depr_ignore = "%s is deprecated and ignored. Use %s instead." - msg_obsolete = ("%s is obsolete. Please remove it from your matplotlibrc " - "and/or style files.") - msg_backend_obsolete = ("The {} rcParam was deprecated in version 2.2. In" - " order to force the use of a specific Qt binding," - " either import that binding first, or set the " - "QT_API environment variable.") + validate = {key: converter + for key, (default, converter) in defaultParams.items() + if key not in _all_deprecated} + + @property + @cbook.deprecated("3.0") + def msg_depr(self): + return "%s is deprecated and replaced with %s; please use the latter." + + @property + @cbook.deprecated("3.0") + def msg_depr_ignore(self): + return "%s is deprecated and ignored. Use %s instead." + + @property + @cbook.deprecated("3.0") + def msg_depr_set(self): + return ("%s is deprecated. Please remove it from your matplotlibrc " + "and/or style files.") + + @property + @cbook.deprecated("3.0") + def msg_obsolete(self): + return ("%s is obsolete. Please remove it from your matplotlibrc " + "and/or style files.") + + @property + @cbook.deprecated("3.0") + def msg_backend_obsolete(self): + return ("The {} rcParam was deprecated in version 2.2. In order to " + "force the use of a specific Qt binding, either import that " + "binding first, or set the QT_API environment variable.") # validate values on the way in def __init__(self, *args, **kwargs): @@ -896,27 +829,34 @@ def __init__(self, *args, **kwargs): def __setitem__(self, key, val): try: if key in _deprecated_map: - alt_key, alt_val, inverse_alt = _deprecated_map[key] - warnings.warn(self.msg_depr % (key, alt_key), - mplDeprecation) + version, alt_key, alt_val, inverse_alt = _deprecated_map[key] + cbook.warn_deprecated( + version, key, obj_type="rcparam", alternative=alt_key) key = alt_key val = alt_val(val) - elif key in _deprecated_set and val is not None: + elif key in _deprecated_remain_as_none and val is not None: + version, = _deprecated_remain_as_none[key] + addendum = '' if key.startswith('backend'): - warnings.warn(self.msg_backend_obsolete.format(key), - mplDeprecation) - else: - warnings.warn(self.msg_depr_set % key, - mplDeprecation) + addendum = ( + "In order to force the use of a specific Qt binding, " + "either import that binding first, or set the QT_API " + "environment variable.") + cbook.warn_deprecated( + "2.2", name=key, obj_type="rcparam", addendum=addendum) elif key in _deprecated_ignore_map: - alt = _deprecated_ignore_map[key] - warnings.warn(self.msg_depr_ignore % (key, alt), - mplDeprecation) - return - elif key in _obsolete_set: - warnings.warn(self.msg_obsolete % (key, ), - mplDeprecation) + version, alt_key = _deprecated_ignore_map[key] + cbook.warn_deprecated( + version, name=key, obj_type="rcparam", alternative=alt_key) return + elif key == 'examples.directory': + cbook.warn_deprecated( + "3.0", "{} is deprecated; in the future, examples will be " + "found relative to the 'datapath' directory.".format(key)) + elif key == 'backend': + if val is rcsetup._auto_backend_sentinel: + if 'backend' in self: + return try: cval = self.validate[key](val) except ValueError as ve: @@ -928,49 +868,45 @@ def __setitem__(self, key, val): 'list of valid parameters.' % (key,)) def __getitem__(self, key): - inverse_alt = None if key in _deprecated_map: - alt_key, alt_val, inverse_alt = _deprecated_map[key] - warnings.warn(self.msg_depr % (key, alt_key), - mplDeprecation) - key = alt_key + version, alt_key, alt_val, inverse_alt = _deprecated_map[key] + cbook.warn_deprecated( + version, key, obj_type="rcparam", alternative=alt_key) + return inverse_alt(dict.__getitem__(self, alt_key)) elif key in _deprecated_ignore_map: - alt = _deprecated_ignore_map[key] - warnings.warn(self.msg_depr_ignore % (key, alt), - mplDeprecation) - key = alt - - elif key in _obsolete_set: - warnings.warn(self.msg_obsolete % (key, ), - mplDeprecation) - return None - - val = dict.__getitem__(self, key) - if inverse_alt is not None: - return inverse_alt(val) - else: - return val + version, alt_key = _deprecated_ignore_map[key] + cbook.warn_deprecated( + version, key, obj_type="rcparam", alternative=alt_key) + return dict.__getitem__(self, alt_key) if alt_key else None + + elif key == 'examples.directory': + cbook.warn_deprecated( + "3.0", "{} is deprecated; in the future, examples will be " + "found relative to the 'datapath' directory.".format(key)) + + elif key == "backend": + val = dict.__getitem__(self, key) + if val is rcsetup._auto_backend_sentinel: + from matplotlib import pyplot as plt + plt.switch_backend(rcsetup._auto_backend_sentinel) + + return dict.__getitem__(self, key) def __repr__(self): - import pprint class_name = self.__class__.__name__ indent = len(class_name) + 1 repr_split = pprint.pformat(dict(self), indent=1, width=80 - indent).split('\n') repr_indented = ('\n' + ' ' * indent).join(repr_split) - return '{0}({1})'.format(class_name, repr_indented) + return '{}({})'.format(class_name, repr_indented) def __str__(self): - return '\n'.join('{0}: {1}'.format(k, v) - for k, v in sorted(self.items())) + return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) def __iter__(self): - """ - Yield sorted list of keys. - """ - for k in sorted(dict.__iter__(self)): - yield k + """Yield sorted list of keys.""" + yield from sorted(dict.__iter__(self)) def find_all(self, pattern): """ @@ -998,7 +934,7 @@ def rc_params(fail_on_error=False): # this should never happen, default in mpl-data should always be found message = 'could not find rc file; returning defaults' ret = RcParams([(key, default) for key, (default, _) in - six.iteritems(defaultParams) + defaultParams.items() if key not in _all_deprecated]) warnings.warn(message) return ret @@ -1014,24 +950,17 @@ def is_url(filename): return URL_REGEX.match(filename) is not None -def _url_lines(f): - # Compatibility for urlopen in python 3, which yields bytes. - for line in f: - yield line.decode('utf8') - - @contextlib.contextmanager def _open_file_or_url(fname): if is_url(fname): - f = urlopen(fname) - yield _url_lines(f) - f.close() + with urllib.request.urlopen(fname) as f: + yield (line.decode('utf-8') for line in f) else: fname = os.path.expanduser(fname) encoding = locale.getpreferredencoding(do_setlocale=False) if encoding is None: encoding = "utf-8" - with io.open(fname, encoding=encoding) as f: + with open(fname, encoding=encoding) as f: yield f @@ -1088,7 +1017,7 @@ def _rc_params_in_file(fname, fail_on_error=False): warnings.warn('Bad val "%s" on %s\n\t%s' % (val, error_details, msg)) - for key, (val, line, cnt) in six.iteritems(rc_temp): + for key, (val, line, cnt) in rc_temp.items(): if key in defaultParams: if fail_on_error: config[key] = val # try to convert to proper type or raise @@ -1100,10 +1029,10 @@ def _rc_params_in_file(fname, fail_on_error=False): warnings.warn('Bad val "%s" on %s\n\t%s' % (val, error_details, msg)) elif key in _deprecated_ignore_map: - warnings.warn('%s is deprecated. Update your matplotlibrc to use ' - '%s instead.' % (key, _deprecated_ignore_map[key]), - mplDeprecation) - + version, alt_key = _deprecated_ignore_map[key] + cbook.warn_deprecated( + version, key, alternative=alt_key, + addendum="Please update your matplotlibrc.") else: print(""" Bad key "%s" on line %d in @@ -1135,9 +1064,9 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): if not use_default_template: return config_from_file - iter_params = six.iteritems(defaultParams) + iter_params = defaultParams.items() with warnings.catch_warnings(): - warnings.simplefilter("ignore", mplDeprecation) + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) config = RcParams([(key, default) for key, (default, _) in iter_params if key not in _all_deprecated]) config.update(config_from_file) @@ -1161,7 +1090,8 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # this is the instance used by the matplotlib classes rcParams = rc_params() -if rcParams['examples.directory']: +# Don't trigger deprecation warning when just fetching. +if dict.__getitem__(rcParams, 'examples.directory'): # paths that are intended to be relative to matplotlib_fname() # are allowed for the examples.directory parameter. # However, we will need to fully qualify the path because @@ -1175,12 +1105,12 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): _fullpath = os.path.join(_basedir, rcParams['examples.directory']) rcParams['examples.directory'] = _fullpath -rcParamsOrig = rcParams.copy() with warnings.catch_warnings(): - warnings.simplefilter("ignore", mplDeprecation) + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + rcParamsOrig = RcParams(rcParams.copy()) rcParamsDefault = RcParams([(key, default) for key, (default, converter) in - six.iteritems(defaultParams) + defaultParams.items() if key not in _all_deprecated]) rcParams['ps.usedistiller'] = checkdep_ps_distiller( @@ -1194,7 +1124,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): def rc(group, **kwargs): """ - Set the current rc params. Group is the grouping for the rc, e.g., + Set the current rc params. *group* is the grouping for the rc, e.g., for ``lines.linewidth`` the group is ``lines``, for ``axes.facecolor``, the group is ``axes``, and so on. Group may also be a list or tuple of group names, e.g., (*xtick*, *ytick*). @@ -1252,10 +1182,10 @@ def rc(group, **kwargs): 'aa': 'antialiased', } - if isinstance(group, six.string_types): + if isinstance(group, str): group = (group,) for g in group: - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): name = aliases.get(k) or k key = '%s.%s' % (g, name) try: @@ -1266,7 +1196,11 @@ def rc(group, **kwargs): def rcdefaults(): - """Restore the rc params from Matplotlib's internal defaults. + """ + Restore the rc params from Matplotlib's internal default style. + + Style-blacklisted rc params (defined in + `matplotlib.style.core.STYLE_BLACKLIST`) are not updated. See Also -------- @@ -1276,24 +1210,50 @@ def rcdefaults(): Use a specific style file. Call ``style.use('default')`` to restore the default style. """ - rcParams.clear() - rcParams.update(rcParamsDefault) + # Deprecation warnings were already handled when creating rcParamsDefault, + # no need to reemit them here. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", mplDeprecation) + from .style.core import STYLE_BLACKLIST + rcParams.clear() + rcParams.update({k: v for k, v in rcParamsDefault.items() + if k not in STYLE_BLACKLIST}) def rc_file_defaults(): - """Restore the rc params from the original rc file loaded by Matplotlib. """ - rcParams.update(rcParamsOrig) + Restore the rc params from the original rc file loaded by Matplotlib. + + Style-blacklisted rc params (defined in + `matplotlib.style.core.STYLE_BLACKLIST`) are not updated. + """ + # Deprecation warnings were already handled when creating rcParamsOrig, no + # need to reemit them here. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", mplDeprecation) + from .style.core import STYLE_BLACKLIST + rcParams.update({k: rcParamsOrig[k] for k in rcParamsOrig + if k not in STYLE_BLACKLIST}) def rc_file(fname): """ Update rc params from file. + + Style-blacklisted rc params (defined in + `matplotlib.style.core.STYLE_BLACKLIST`) are not updated. """ - rcParams.update(rc_params_from_file(fname)) + # Deprecation warnings were already handled in rc_params_from_file, no need + # to reemit them here. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", mplDeprecation) + from .style.core import STYLE_BLACKLIST + rc_from_file = rc_params_from_file(fname) + rcParams.update({k: rc_from_file[k] for k in rc_from_file + if k not in STYLE_BLACKLIST}) -class rc_context(object): +class rc_context: """ Return a context manager for managing rc settings. @@ -1340,92 +1300,86 @@ def __init__(self, rc=None, fname=None): if rc: rcParams.update(rc) except Exception: - # If anything goes wrong, revert to the original rcs. - dict.update(rcParams, self._orig) + self.__fallback() raise + def __fallback(self): + # If anything goes wrong, revert to the original rcs. + updated_backend = self._orig['backend'] + dict.update(rcParams, self._orig) + # except for the backend. If the context block triggered resloving + # the auto backend resolution keep that value around + if self._orig['backend'] is rcsetup._auto_backend_sentinel: + rcParams['backend'] = updated_backend + def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): - # No need to revalidate the original values. - dict.update(rcParams, self._orig) - - -_use_error_msg = """ -This call to matplotlib.use() has no effect because the backend has already -been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot, -or matplotlib.backends is imported for the first time. - -The backend was *originally* set to {backend!r} by the following code: -{tb} -""" + self.__fallback() def use(arg, warn=True, force=False): """ Set the matplotlib backend to one of the known backends. - The argument is case-insensitive. *warn* specifies whether a - warning should be issued if a backend has already been set up. - *force* is an **experimental** flag that tells matplotlib to - attempt to initialize a new backend by reloading the backend - module. + To find out which backend is currently set, see + :func:`matplotlib.get_backend`. - .. note:: - This function must be called *before* importing pyplot for - the first time; or, if you are not using pyplot, it must be called - before importing matplotlib.backends. If warn is True, a warning - is issued if you try and call this after pylab or pyplot have been - loaded. In certain black magic use cases, e.g. - :func:`pyplot.switch_backend`, we are doing the reloading necessary to - make the backend switch work (in some cases, e.g., pure image - backends) so one can set warn=False to suppress the warnings. + Parameters + ---------- + arg : str + The backend to switch to. This can either be one of the + 'standard' backend names or a string of the form + ``module://my.module.name``. This value is case-insensitive. + + warn : bool, optional + If True, warn if this is called after pyplot has been imported + and a backend is set up. + + defaults to True + + force : bool, optional + If True, attempt to switch the backend. This defaults to + False. - To find out which backend is currently set, see - :func:`matplotlib.get_backend`. """ - # Lets determine the proper backend name first - if arg.startswith('module://'): - name = arg - else: - # Lowercase only non-module backend names (modules are case-sensitive) - arg = arg.lower() - name = validate_backend(arg) - - # Check if we've already set up a backend - if 'matplotlib.backends' in sys.modules: - # Warn only if called with a different name - if (rcParams['backend'] != name) and warn: - import matplotlib.backends + name = validate_backend(arg) + + # if setting back to the same thing, do nothing + if (dict.__getitem__(rcParams, 'backend') == name): + pass + + # Check if we have already imported pyplot and triggered + # backend selection, do a bit more work + elif 'matplotlib.pyplot' in sys.modules: + # If we are here then the requested is different than the current. + # If we are going to force the switch, never warn, else, if warn + # is True, then direct users to `plt.switch_backend` + if (not force) and warn: warnings.warn( - _use_error_msg.format( - backend=rcParams['backend'], - tb=matplotlib.backends._backend_loading_tb), + ("matplotlib.pyplot as already been imported, " + "this call will have no effect."), stacklevel=2) - # Unless we've been told to force it, just return - if not force: - return - need_reload = True + # if we are going to force switching the backend, pull in + # `switch_backend` from pyplot. This will only happen if + # pyplot is already imported. + if force: + from matplotlib.pyplot import switch_backend + switch_backend(name) + # Finally if pyplot is not imported update both rcParams and + # rcDefaults so restoring the defaults later with rcdefaults + # won't change the backend. This is a bit of overkill as 'backend' + # is already in style.core.STYLE_BLACKLIST, but better to be safe. else: - need_reload = False - - # Store the backend name - rcParams['backend'] = name + rcParams['backend'] = rcParamsDefault['backend'] = name - # If needed we reload here because a lot of setup code is triggered on - # module import. See backends/__init__.py for more detail. - if need_reload: - reload(sys.modules['matplotlib.backends']) - -try: - use(os.environ['MPLBACKEND']) -except KeyError: - pass +if os.environ.get('MPLBACKEND'): + rcParams['backend'] = os.environ.get('MPLBACKEND') def get_backend(): @@ -1464,19 +1418,15 @@ def tk_window_focus(): def _init_tests(): - try: + # CPython's faulthandler since v3.6 handles exceptions on Windows + # https://bugs.python.org/issue23848 but until v3.6.4 it was printing + # non-fatal exceptions https://bugs.python.org/issue30557 + import platform + if not (sys.platform == 'win32' and + (3, 6) < sys.version_info < (3, 6, 4) and + platform.python_implementation() == 'CPython'): import faulthandler - except ImportError: - pass - else: - # CPython's faulthandler since v3.6 handles exceptions on Windows - # https://bugs.python.org/issue23848 but until v3.6.4 it was - # printing non-fatal exceptions https://bugs.python.org/issue30557 - import platform - if not (sys.platform == 'win32' and - (3, 6) < sys.version_info < (3, 6, 4) and - platform.python_implementation() == 'CPython'): - faulthandler.enable() + faulthandler.enable() # The version of FreeType to install locally for running the # tests. This must match the value in `setupext.py` @@ -1500,12 +1450,8 @@ def _init_tests(): try: import pytest - try: - from unittest import mock - except ImportError: - import mock except ImportError: - print("matplotlib.test requires pytest and mock to run.") + print("matplotlib.test requires pytest to run.") raise @@ -1565,8 +1511,8 @@ def _replacer(data, key): converts input data to a sequence as needed. """ # if key isn't a string don't bother - if not isinstance(key, six.string_types): - return (key) + if not isinstance(key, str): + return key # try to use __getitem__ try: return sanitize_sequence(data[key]) @@ -1583,6 +1529,9 @@ def _replacer(data, key): following arguments are replaced by **data[]**: {replaced} + + Objects passed as **data** must support item access (``data[]``) and + membership test (`` in data``). """ @@ -1662,52 +1611,24 @@ def foo(ax, *args, **kwargs) replace_names = set(replace_names) def param(func): - new_sig = None - # signature is since 3.3 and wrapped since 3.2, but we support 3.4+. - python_has_signature = python_has_wrapped = six.PY3 - - # if in a legacy version of python and IPython is already imported - # try to use their back-ported signature - if not python_has_signature and 'IPython' in sys.modules: - try: - import IPython.utils.signatures - signature = IPython.utils.signatures.signature - Parameter = IPython.utils.signatures.Parameter - except ImportError: - pass + sig = inspect.signature(func) + _has_varargs = False + _has_varkwargs = False + _arg_names = [] + params = list(sig.parameters.values()) + for p in params: + if p.kind is Parameter.VAR_POSITIONAL: + _has_varargs = True + elif p.kind is Parameter.VAR_KEYWORD: + _has_varkwargs = True else: - python_has_signature = True + _arg_names.append(p.name) + data_param = Parameter('data', Parameter.KEYWORD_ONLY, default=None) + if _has_varkwargs: + params.insert(-1, data_param) else: - if python_has_signature: - signature = inspect.signature - Parameter = inspect.Parameter - - if not python_has_signature: - arg_spec = inspect.getargspec(func) - _arg_names = arg_spec.args - _has_varargs = arg_spec.varargs is not None - _has_varkwargs = arg_spec.keywords is not None - else: - sig = signature(func) - _has_varargs = False - _has_varkwargs = False - _arg_names = [] - params = list(sig.parameters.values()) - for p in params: - if p.kind is Parameter.VAR_POSITIONAL: - _has_varargs = True - elif p.kind is Parameter.VAR_KEYWORD: - _has_varkwargs = True - else: - _arg_names.append(p.name) - data_param = Parameter('data', - Parameter.KEYWORD_ONLY, - default=None) - if _has_varkwargs: - params.insert(-1, data_param) - else: - params.append(data_param) - new_sig = sig.replace(parameters=params) + params.append(data_param) + new_sig = sig.replace(parameters=params) # Import-time check: do we have enough information to replace *args? arg_names_at_runtime = False # there can't be any positional arguments behind *args and no @@ -1761,7 +1682,7 @@ def param(func): label_namer_pos = 9999 # bigger than all "possible" argument lists if (label_namer and # we actually want a label here ... arg_names and # and we can determine a label in *args ... - (label_namer in arg_names)): # and it is in *args + label_namer in arg_names): # and it is in *args label_namer_pos = arg_names.index(label_namer) if "label" in arg_names: label_pos = arg_names.index("label") @@ -1784,7 +1705,7 @@ def param(func): pass @functools.wraps(func) - def inner(ax, *args, **kwargs): + def inner(ax, *args, data=None, **kwargs): # this is needed because we want to change these values if # arg_names_at_runtime==True, but python does not allow assigning # to a variable in a outer scope. So use some new local ones and @@ -1795,8 +1716,6 @@ def inner(ax, *args, **kwargs): label = None - data = kwargs.pop('data', None) - if data is None: # data validation args = tuple(sanitize_sequence(a) for a in args) else: @@ -1818,7 +1737,7 @@ def inner(ax, *args, **kwargs): else: label = kwargs.get(label_namer, None) # ensure a string, as label can't be anything else - if not isinstance(label, six.string_types): + if not isinstance(label, str): label = None if (replace_names is None) or (replace_all_args is True): @@ -1837,22 +1756,21 @@ def inner(ax, *args, **kwargs): if replace_names is None: # replace all kwargs ... - kwargs = dict((k, _replacer(data, v)) - for k, v in six.iteritems(kwargs)) + kwargs = {k: _replacer(data, v) for k, v in kwargs.items()} else: # ... or only if a kwarg of that name is in replace_names - kwargs = dict((k, _replacer(data, v) - if k in replace_names else v) - for k, v in six.iteritems(kwargs)) + kwargs = { + k: _replacer(data, v) if k in replace_names else v + for k, v in kwargs.items()} # replace the label if this func "wants" a label arg and the user # didn't set one. Note: if the user puts in "label=None", it does # *NOT* get replaced! user_supplied_label = ( - (len(args) >= _label_pos) or # label is included in args - ('label' in kwargs) # ... or in kwargs + len(args) >= _label_pos or # label is included in args + 'label' in kwargs # ... or in kwargs ) - if (label_namer and not user_supplied_label): + if label_namer and not user_supplied_label: if _label_namer_pos < len(args): kwargs['label'] = get_label(args[_label_namer_pos], label) elif label_namer in kwargs: @@ -1868,10 +1786,7 @@ def inner(ax, *args, **kwargs): inner.__doc__ = _add_data_doc(inner.__doc__, replace_names, replace_all_args) - if not python_has_wrapped: - inner.__wrapped__ = func - if new_sig is not None: - inner.__signature__ = new_sig + inner.__signature__ = new_sig return inner return param diff --git a/lib/matplotlib/_cm.py b/lib/matplotlib/_cm.py index a32229cb63b9..203f7fa104f0 100644 --- a/lib/matplotlib/_cm.py +++ b/lib/matplotlib/_cm.py @@ -6,9 +6,6 @@ with the purpose and type of your colormap if you add data for one here. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np _binary_data = { @@ -44,17 +41,17 @@ 'blue': ((0., 0., 0.), (1.0, 0.4975, 0.4975))} -_flag_data = { - 'red': lambda x: 0.75 * np.sin((x * 31.5 + 0.25) * np.pi) + 0.5, - 'green': lambda x: np.sin(x * 31.5 * np.pi), - 'blue': lambda x: 0.75 * np.sin((x * 31.5 - 0.25) * np.pi) + 0.5, -} -_prism_data = { - 'red': lambda x: 0.75 * np.sin((x * 20.9 + 0.25) * np.pi) + 0.67, - 'green': lambda x: 0.75 * np.sin((x * 20.9 - 0.25) * np.pi) + 0.33, - 'blue': lambda x: -1.1 * np.sin((x * 20.9) * np.pi), -} +def _flag_red(x): return 0.75 * np.sin((x * 31.5 + 0.25) * np.pi) + 0.5 +def _flag_green(x): return np.sin(x * 31.5 * np.pi) +def _flag_blue(x): return 0.75 * np.sin((x * 31.5 - 0.25) * np.pi) + 0.5 +def _prism_red(x): return 0.75 * np.sin((x * 20.9 + 0.25) * np.pi) + 0.67 +def _prism_green(x): return 0.75 * np.sin((x * 20.9 - 0.25) * np.pi) + 0.33 +def _prism_blue(x): return -1.1 * np.sin((x * 20.9) * np.pi) + + +_flag_data = {'red': _flag_red, 'green': _flag_green, 'blue': _flag_blue} +_prism_data = {'red': _prism_red, 'green': _prism_green, 'blue': _prism_blue} def cubehelix(gamma=1.0, s=0.5, r=-1.5, h=1.0): diff --git a/lib/matplotlib/_cm_listed.py b/lib/matplotlib/_cm_listed.py index c4a4e13e4d78..cd70b28197b0 100644 --- a/lib/matplotlib/_cm_listed.py +++ b/lib/matplotlib/_cm_listed.py @@ -1285,12 +1285,530 @@ [0.995503, 0.903866, 0.212370], [0.995737, 0.909344, 0.217772]] +_twilight_data = [ + [0.88575015840754434, 0.85000924943067835, 0.8879736506427196], + [0.88378520195539056, 0.85072940540310626, 0.88723222096949894], + [0.88172231059285788, 0.85127594077653468, 0.88638056925514819], + [0.8795410528270573, 0.85165675407495722, 0.8854143767924102], + [0.87724880858965482, 0.85187028338870274, 0.88434120381311432], + [0.87485347508575972, 0.85191526123023187, 0.88316926967613829], + [0.87233134085124076, 0.85180165478080894, 0.88189704355001619], + [0.86970474853509816, 0.85152403004797894, 0.88053883390003362], + [0.86696015505333579, 0.8510896085314068, 0.87909766977173343], + [0.86408985081463996, 0.85050391167507788, 0.87757925784892632], + [0.86110245436899846, 0.84976754857001258, 0.87599242923439569], + [0.85798259245670372, 0.84888934810281835, 0.87434038553446281], + [0.85472593189256985, 0.84787488124672816, 0.8726282980930582], + [0.85133714570857189, 0.84672735796116472, 0.87086081657350445], + [0.84780710702577922, 0.8454546229209523, 0.86904036783694438], + [0.8441261828674842, 0.84406482711037389, 0.86716973322690072], + [0.84030420805957784, 0.8425605950855084, 0.865250882410458], + [0.83634031809191178, 0.84094796518951942, 0.86328528001070159], + [0.83222705712934408, 0.83923490627754482, 0.86127563500427884], + [0.82796894316013536, 0.83742600751395202, 0.85922399451306786], + [0.82357429680252847, 0.83552487764795436, 0.85713191328514948], + [0.81904654677937527, 0.8335364929949034, 0.85500206287010105], + [0.81438982121143089, 0.83146558694197847, 0.85283759062147024], + [0.8095999819094809, 0.82931896673505456, 0.85064441601050367], + [0.80469164429814577, 0.82709838780560663, 0.84842449296974021], + [0.79967075421267997, 0.82480781812080928, 0.84618210029578533], + [0.79454305089231114, 0.82245116226304615, 0.84392184786827984], + [0.78931445564608915, 0.82003213188702007, 0.8416486380471222], + [0.78399101042764918, 0.81755426400533426, 0.83936747464036732], + [0.77857892008227592, 0.81502089378742548, 0.8370834463093898], + [0.77308416590170936, 0.81243524735466011, 0.83480172950579679], + [0.76751108504417864, 0.8098007598713145, 0.83252816638059668], + [0.76186907937980286, 0.80711949387647486, 0.830266486168872], + [0.75616443584381976, 0.80439408733477935, 0.82802138994719998], + [0.75040346765406696, 0.80162699008965321, 0.82579737851082424], + [0.74459247771890169, 0.79882047719583249, 0.82359867586156521], + [0.73873771700494939, 0.79597665735031009, 0.82142922780433014], + [0.73284543645523459, 0.79309746468844067, 0.81929263384230377], + [0.72692177512829703, 0.7901846863592763, 0.81719217466726379], + [0.72097280665536778, 0.78723995923452639, 0.81513073920879264], + [0.71500403076252128, 0.78426487091581187, 0.81311116559949914], + [0.70902078134539304, 0.78126088716070907, 0.81113591855117928], + [0.7030297722540817, 0.77822904973358131, 0.80920618848056969], + [0.6970365443886174, 0.77517050008066057, 0.80732335380063447], + [0.69104641009309098, 0.77208629460678091, 0.80548841690679074], + [0.68506446154395928, 0.7689774029354699, 0.80370206267176914], + [0.67909554499882152, 0.76584472131395898, 0.8019646617300199], + [0.67314422559426212, 0.76268908733890484, 0.80027628545809526], + [0.66721479803752815, 0.7595112803730375, 0.79863674654537764], + [0.6613112930078745, 0.75631202708719025, 0.7970456043491897], + [0.65543692326454717, 0.75309208756768431, 0.79550271129031047], + [0.64959573004253479, 0.74985201221941766, 0.79400674021499107], + [0.6437910831099849, 0.7465923800833657, 0.79255653201306053], + [0.63802586828545982, 0.74331376714033193, 0.79115100459573173], + [0.6323027138710603, 0.74001672160131404, 0.78978892762640429], + [0.62662402022604591, 0.73670175403699445, 0.78846901316334561], + [0.62099193064817548, 0.73336934798923203, 0.78718994624696581], + [0.61540846411770478, 0.73001995232739691, 0.78595022706750484], + [0.60987543176093062, 0.72665398759758293, 0.78474835732694714], + [0.60439434200274855, 0.7232718614323369, 0.78358295593535587], + [0.5989665814482068, 0.71987394892246725, 0.78245259899346642], + [0.59359335696837223, 0.7164606049658685, 0.78135588237640097], + [0.58827579780555495, 0.71303214646458135, 0.78029141405636515], + [0.58301487036932409, 0.70958887676997473, 0.77925781820476592], + [0.5778116438998202, 0.70613106157153982, 0.77825345121025524], + [0.5726668948158774, 0.7026589535425779, 0.77727702680911992], + [0.56758117853861967, 0.69917279302646274, 0.77632748534275298], + [0.56255515357219343, 0.69567278381629649, 0.77540359142309845], + [0.55758940419605174, 0.69215911458254054, 0.7745041337932782], + [0.55268450589347129, 0.68863194515166382, 0.7736279426902245], + [0.54784098153018634, 0.68509142218509878, 0.77277386473440868], + [0.54305932424018233, 0.68153767253065878, 0.77194079697835083], + [0.53834015575176275, 0.67797081129095405, 0.77112734439057717], + [0.53368389147728401, 0.67439093705212727, 0.7703325054879735], + [0.529090861832473, 0.67079812302806219, 0.76955552292313134], + [0.52456151470593582, 0.66719242996142225, 0.76879541714230948], + [0.52009627392235558, 0.66357391434030388, 0.76805119403344102], + [0.5156955988596057, 0.65994260812897998, 0.76732191489596169], + [0.51135992541601927, 0.65629853981831865, 0.76660663780645333], + [0.50708969576451657, 0.65264172403146448, 0.76590445660835849], + [0.5028853540415561, 0.64897216734095264, 0.76521446718174913], + [0.49874733661356069, 0.6452898684900934, 0.76453578734180083], + [0.4946761847863938, 0.64159484119504429, 0.76386719002130909], + [0.49067224938561221, 0.63788704858847078, 0.76320812763163837], + [0.4867359599430568, 0.63416646251100506, 0.76255780085924041], + [0.4828677867260272, 0.6304330455306234, 0.76191537149895305], + [0.47906816236197386, 0.62668676251860134, 0.76128000375662419], + [0.47533752394906287, 0.62292757283835809, 0.76065085571817748], + [0.47167629518877091, 0.61915543242884641, 0.76002709227883047], + [0.46808490970531597, 0.61537028695790286, 0.75940789891092741], + [0.46456376716303932, 0.61157208822864151, 0.75879242623025811], + [0.46111326647023881, 0.607760777169989, 0.75817986436807139], + [0.45773377230160567, 0.60393630046586455, 0.75756936901859162], + [0.45442563977552913, 0.60009859503858665, 0.75696013660606487], + [0.45118918687617743, 0.59624762051353541, 0.75635120643246645], + [0.44802470933589172, 0.59238331452146575, 0.75574176474107924], + [0.44493246854215379, 0.5885055998308617, 0.7551311041857901], + [0.44191271766696399, 0.58461441100175571, 0.75451838884410671], + [0.43896563958048396, 0.58070969241098491, 0.75390276208285945], + [0.43609138958356369, 0.57679137998186081, 0.7532834105961016], + [0.43329008867358393, 0.57285941625606673, 0.75265946532566674], + [0.43056179073057571, 0.56891374572457176, 0.75203008099312696], + [0.42790652284925834, 0.5649543060909209, 0.75139443521914839], + [0.42532423665011354, 0.56098104959950301, 0.75075164989005116], + [0.42281485675772662, 0.55699392126996583, 0.75010086988227642], + [0.42037822361396326, 0.55299287158108168, 0.7494412559451894], + [0.41801414079233629, 0.54897785421888889, 0.74877193167001121], + [0.4157223260454232, 0.54494882715350401, 0.74809204459000522], + [0.41350245743314729, 0.54090574771098476, 0.74740073297543086], + [0.41135414697304568, 0.53684857765005933, 0.74669712855065784], + [0.4092768899914751, 0.53277730177130322, 0.74598030635707824], + [0.40727018694219069, 0.52869188011057411, 0.74524942637581271], + [0.40533343789303178, 0.52459228174983119, 0.74450365836708132], + [0.40346600333905397, 0.52047847653840029, 0.74374215223567086], + [0.40166714010896104, 0.51635044969688759, 0.7429640345324835], + [0.39993606933454834, 0.51220818143218516, 0.74216844571317986], + [0.3982719152586337, 0.50805166539276136, 0.74135450918099721], + [0.39667374905665609, 0.50388089053847973, 0.74052138580516735], + [0.39514058808207631, 0.49969585326377758, 0.73966820211715711], + [0.39367135736822567, 0.49549655777451179, 0.738794102296364], + [0.39226494876209317, 0.49128300332899261, 0.73789824784475078], + [0.39092017571994903, 0.48705520251223039, 0.73697977133881254], + [0.38963580160340855, 0.48281316715123496, 0.73603782546932739], + [0.38841053300842432, 0.47855691131792805, 0.73507157641157261], + [0.38724301459330251, 0.47428645933635388, 0.73408016787854391], + [0.38613184178892102, 0.4700018340988123, 0.7330627749243106], + [0.38507556793651387, 0.46570306719930193, 0.73201854033690505], + [0.38407269378943537, 0.46139018782416635, 0.73094665432902683], + [0.38312168084402748, 0.45706323581407199, 0.72984626791353258], + [0.38222094988570376, 0.45272225034283325, 0.72871656144003782], + [0.38136887930454161, 0.44836727669277859, 0.72755671317141346], + [0.38056380696565623, 0.44399837208633719, 0.72636587045135315], + [0.37980403744848751, 0.43961558821222629, 0.72514323778761092], + [0.37908789283110761, 0.43521897612544935, 0.72388798691323131], + [0.378413635091359, 0.43080859411413064, 0.72259931993061044], + [0.37777949753513729, 0.4263845142616835, 0.72127639993530235], + [0.37718371844251231, 0.42194680223454828, 0.71991841524475775], + [0.37662448930806297, 0.41749553747893614, 0.71852454736176108], + [0.37610001286385814, 0.41303079952477062, 0.71709396919920232], + [0.37560846919442398, 0.40855267638072096, 0.71562585091587549], + [0.37514802505380473, 0.4040612609993941, 0.7141193695725726], + [0.37471686019302231, 0.3995566498711684, 0.71257368516500463], + [0.37431313199312338, 0.39503894828283309, 0.71098796522377461], + [0.37393499330475782, 0.39050827529375831, 0.70936134293478448], + [0.3735806215098284, 0.38596474386057539, 0.70769297607310577], + [0.37324816143326384, 0.38140848555753937, 0.70598200974806036], + [0.37293578646665032, 0.37683963835219841, 0.70422755780589941], + [0.37264166757849604, 0.37225835004836849, 0.7024287314570723], + [0.37236397858465387, 0.36766477862108266, 0.70058463496520773], + [0.37210089702443822, 0.36305909736982378, 0.69869434615073722], + [0.3718506155898596, 0.35844148285875221, 0.69675695810256544], + [0.37161133234400479, 0.3538121372967869, 0.69477149919380887], + [0.37138124223736607, 0.34917126878479027, 0.69273703471928827], + [0.37115856636209105, 0.34451911410230168, 0.69065253586464992], + [0.37094151551337329, 0.33985591488818123, 0.68851703379505125], + [0.37072833279422668, 0.33518193808489577, 0.68632948169606767], + [0.37051738634484427, 0.33049741244307851, 0.68408888788857214], + [0.37030682071842685, 0.32580269697872455, 0.68179411684486679], + [0.37009487130772695, 0.3210981375964933, 0.67944405399056851], + [0.36987980329025361, 0.31638410101153364, 0.67703755438090574], + [0.36965987626565955, 0.31166098762951971, 0.67457344743419545], + [0.36943334591276228, 0.30692923551862339, 0.67205052849120617], + [0.36919847837592484, 0.30218932176507068, 0.66946754331614522], + [0.36895355306596778, 0.29744175492366276, 0.66682322089824264], + [0.36869682231895268, 0.29268709856150099, 0.66411625298236909], + [0.36842655638020444, 0.28792596437778462, 0.66134526910944602], + [0.36814101479899719, 0.28315901221182987, 0.65850888806972308], + [0.36783843696531082, 0.27838697181297761, 0.65560566838453704], + [0.36751707094367697, 0.27361063317090978, 0.65263411711618635], + [0.36717513650699446, 0.26883085667326956, 0.64959272297892245], + [0.36681085540107988, 0.26404857724525643, 0.64647991652908243], + [0.36642243251550632, 0.25926481158628106, 0.64329409140765537], + [0.36600853966739794, 0.25448043878086224, 0.64003361803368586], + [0.36556698373538982, 0.24969683475296395, 0.63669675187488584], + [0.36509579845886808, 0.24491536803550484, 0.63328173520055586], + [0.36459308890125008, 0.24013747024823828, 0.62978680155026101], + [0.36405693022088509, 0.23536470386204195, 0.62621013451953023], + [0.36348537610385145, 0.23059876218396419, 0.62254988622392882], + [0.36287643560041027, 0.22584149293287031, 0.61880417410823019], + [0.36222809558295926, 0.22109488427338303, 0.61497112346096128], + [0.36153829010998356, 0.21636111429594002, 0.61104880679640927], + [0.36080493826624654, 0.21164251793458128, 0.60703532172064711], + [0.36002681809096376, 0.20694122817889948, 0.60292845431916875], + [0.35920088560930186, 0.20226037920758122, 0.5987265295935138], + [0.35832489966617809, 0.197602942459778, 0.59442768517501066], + [0.35739663292915563, 0.19297208197842461, 0.59003011251063131], + [0.35641381143126327, 0.18837119869242164, 0.5855320765920552], + [0.35537415306906722, 0.18380392577704466, 0.58093191431832802], + [0.35427534960663759, 0.17927413271618647, 0.57622809660668717], + [0.35311574421123737, 0.17478570377561287, 0.57141871523555288], + [0.35189248608873791, 0.17034320478524959, 0.56650284911216653], + [0.35060304441931012, 0.16595129984720861, 0.56147964703993225], + [0.34924513554955644, 0.16161477763045118, 0.55634837474163779], + [0.34781653238777782, 0.15733863511152979, 0.55110853452703257], + [0.34631507175793091, 0.15312802296627787, 0.5457599924248665], + [0.34473901574536375, 0.14898820589826409, 0.54030245920406539], + [0.34308600291572294, 0.14492465359918028, 0.53473704282067103], + [0.34135411074506483, 0.1409427920655632, 0.52906500940336754], + [0.33954168752669694, 0.13704801896718169, 0.52328797535085236], + [0.33764732090671112, 0.13324562282438077, 0.51740807573979475], + [0.33566978565015315, 0.12954074251271822, 0.51142807215168951], + [0.33360804901486002, 0.12593818301005921, 0.50535164796654897], + [0.33146154891145124, 0.12244245263391232, 0.49918274588431072], + [0.32923005203231409, 0.11905764321981127, 0.49292595612342666], + [0.3269137124539796, 0.1157873496841953, 0.48658646495697461], + [0.32451307931207785, 0.11263459791730848, 0.48017007211645196], + [0.32202882276069322, 0.10960114111258401, 0.47368494725726878], + [0.31946262395497965, 0.10668879882392659, 0.46713728801395243], + [0.31681648089023501, 0.10389861387653518, 0.46053414662739794], + [0.31409278414755532, 0.10123077676403242, 0.45388335612058467], + [0.31129434479712365, 0.098684771934052201, 0.44719313715161618], + [0.30842444457210105, 0.096259385340577736, 0.44047194882050544], + [0.30548675819945936, 0.093952764840823738, 0.43372849999361113], + [0.30248536364574252, 0.091761187397303601, 0.42697404043749887], + [0.29942483960214772, 0.089682253716750038, 0.42021619665853854], + [0.29631000388905288, 0.087713250960463951, 0.41346259134143476], + [0.29314593096985248, 0.085850656889620708, 0.40672178082365834], + [0.28993792445176608, 0.08409078829085731, 0.40000214725256295], + [0.28669151388283165, 0.082429873848480689, 0.39331182532243375], + [0.28341239797185225, 0.080864153365499375, 0.38665868550105914], + [0.28010638576975472, 0.079389994802261526, 0.38005028528138707], + [0.27677939615815589, 0.078003941033788216, 0.37349382846504675], + [0.27343739342450812, 0.076702800237496066, 0.36699616136347685], + [0.27008637749114051, 0.075483675584275545, 0.36056376228111864], + [0.26673233211995284, 0.074344018028546205, 0.35420276066240958], + [0.26338121807151404, 0.073281657939897077, 0.34791888996380105], + [0.26003895187439957, 0.072294781043362205, 0.3417175669546984], + [0.25671191651083902, 0.071380106242082242, 0.33560648984600089], + [0.25340685873736807, 0.070533582926851829, 0.3295945757321303], + [0.25012845306199383, 0.069758206429106989, 0.32368100685760637], + [0.24688226237958999, 0.069053639449204451, 0.31786993834254956], + [0.24367372557466271, 0.068419855150922693, 0.31216524050888372], + [0.24050813332295939, 0.067857103814855602, 0.30657054493678321], + [0.23739062429054825, 0.067365888050555517, 0.30108922184065873], + [0.23433055727563878, 0.066935599661639394, 0.29574009929867601], + [0.23132955273021344, 0.066576186939090592, 0.29051361067988485], + [0.2283917709422868, 0.06628997924139618, 0.28541074411068496], + [0.22552164337737857, 0.066078173119395595, 0.28043398847505197], + [0.22272706739121817, 0.065933790675651943, 0.27559714652053702], + [0.22001251100779617, 0.065857918918907604, 0.27090279994325861], + [0.21737845072382705, 0.065859661233562045, 0.26634209349669508], + [0.21482843531473683, 0.065940385613778491, 0.26191675992376573], + [0.21237411048541005, 0.066085024661758446, 0.25765165093569542], + [0.21001214221188125, 0.066308573918947178, 0.2535289048041211], + [0.2077442377448806, 0.06661453200418091, 0.24954644291943817], + [0.20558051999470117, 0.066990462397868739, 0.24572497420147632], + [0.20352007949514977, 0.067444179612424215, 0.24205576625191821], + [0.20156133764129841, 0.067983271026200248, 0.23852974228695395], + [0.19971571438603364, 0.068592710553704722, 0.23517094067076993], + [0.19794834061899208, 0.069314066071660657, 0.23194647381302336], + [0.1960826032659409, 0.070321227242423623, 0.22874673279569585], + [0.19410351363791453, 0.071608304856891569, 0.22558727307410353], + [0.19199449184606268, 0.073182830649273306, 0.22243385243433622], + [0.18975853639094634, 0.075019861862143766, 0.2193005075652994], + [0.18739228342697645, 0.077102096899588329, 0.21618875376309582], + [0.18488035509396164, 0.079425730279723883, 0.21307651648984993], + [0.18774482037046955, 0.077251588468039312, 0.21387448578597812], + [0.19049578401722037, 0.075311278416787641, 0.2146562337112265], + [0.1931548636579131, 0.073606819040117955, 0.21542362939081539], + [0.19571853588267552, 0.072157781039602742, 0.21617499187076789], + [0.19819343656336558, 0.070974625252738788, 0.21690975060032436], + [0.20058760685133747, 0.070064576149984209, 0.21762721310371608], + [0.20290365333558247, 0.069435248580458964, 0.21833167885096033], + [0.20531725273301316, 0.068919592266397572, 0.21911516689288835], + [0.20785704662965598, 0.068484398797025281, 0.22000133917653536], + [0.21052882914958676, 0.06812195249816172, 0.22098759107715404], + [0.2133313859647627, 0.067830148426026665, 0.22207043213024291], + [0.21625279838647882, 0.067616330270516389, 0.22324568672294431], + [0.21930503925136402, 0.067465786362940039, 0.22451023616807558], + [0.22247308588973624, 0.067388214053092838, 0.22585960379408354], + [0.2257539681670791, 0.067382132300147474, 0.22728984778098055], + [0.22915620278592841, 0.067434730871152565, 0.22879681433956656], + [0.23266299920501882, 0.067557104388479783, 0.23037617493752832], + [0.23627495835774248, 0.06774359820987802, 0.23202360805926608], + [0.23999586188690308, 0.067985029964779953, 0.23373434258507808], + [0.24381149720247919, 0.068289851529011875, 0.23550427698321885], + [0.24772092990501099, 0.068653337909486523, 0.2373288009471749], + [0.25172899728289466, 0.069064630826035506, 0.23920260612763083], + [0.25582135547481771, 0.06953231029187984, 0.24112190491594204], + [0.25999463887892144, 0.070053855603861875, 0.24308218808684579], + [0.26425512207060942, 0.070616595622995437, 0.24507758869355967], + [0.26859095948172862, 0.071226716277922458, 0.24710443563450618], + [0.27299701518897301, 0.071883555446163511, 0.24915847093232929], + [0.27747150809142801, 0.072582969899254779, 0.25123493995942769], + [0.28201746297366942, 0.073315693214040967, 0.25332800295084507], + [0.28662309235899847, 0.074088460826808866, 0.25543478673717029], + [0.29128515387578635, 0.074899049847466703, 0.25755101595750435], + [0.2960004726065818, 0.075745336000958424, 0.25967245030364566], + [0.30077276812918691, 0.076617824336164764, 0.26179294097819672], + [0.30559226007249934, 0.077521963107537312, 0.26391006692119662], + [0.31045520848595526, 0.078456871676182177, 0.2660200572779356], + [0.31535870009205808, 0.079420997315243186, 0.26811904076941961], + [0.32029986557994061, 0.080412994737554838, 0.27020322893039511], + [0.32527888860401261, 0.081428390076546092, 0.27226772884656186], + [0.33029174471181438, 0.08246763389003825, 0.27430929404579435], + [0.33533353224455448, 0.083532434119003962, 0.27632534356790039], + [0.34040164359597463, 0.084622236191702671, 0.27831254595259397], + [0.34549355713871799, 0.085736654965126335, 0.28026769921081435], + [0.35060678246032478, 0.08687555176033529, 0.28218770540182386], + [0.35573889947341125, 0.088038974350243354, 0.2840695897279818], + [0.36088752387578377, 0.089227194362745205, 0.28591050458531014], + [0.36605031412464006, 0.090440685427697898, 0.2877077458811747], + [0.37122508431309342, 0.091679997480262732, 0.28945865397633169], + [0.3764103053221462, 0.092945198093777909, 0.29116024157313919], + [0.38160247377467543, 0.094238731263712183, 0.29281107506269488], + [0.38679939079544168, 0.09556181960083443, 0.29440901248173756], + [0.39199887556812907, 0.09691583650296684, 0.29595212005509081], + [0.39719876876325577, 0.098302320968278623, 0.29743856476285779], + [0.40239692379737496, 0.099722930314950553, 0.29886674369733968], + [0.40759120392688708, 0.10117945586419633, 0.30023519507728602], + [0.41277985630360303, 0.1026734006932461, 0.30154226437468967], + [0.41796105205173684, 0.10420644885760968, 0.30278652039631843], + [0.42313214269556043, 0.10578120994917611, 0.3039675809469457], + [0.42829101315789753, 0.1073997763055258, 0.30508479060294547], + [0.4334355841041439, 0.1090642347484701, 0.30613767928289148], + [0.43856378187931538, 0.11077667828375456, 0.30712600062348083], + [0.44367358645071275, 0.11253912421257944, 0.30804973095465449], + [0.44876299173174822, 0.11435355574622549, 0.30890905921943196], + [0.45383005086999889, 0.11622183788331528, 0.30970441249844921], + [0.45887288947308297, 0.11814571137706886, 0.31043636979038808], + [0.46389102840284874, 0.12012561256850712, 0.31110343446582983], + [0.46888111384598413, 0.12216445576414045, 0.31170911458932665], + [0.473841437035254, 0.12426354237989065, 0.31225470169927194], + [0.47877034239726296, 0.12642401401409453, 0.31274172735821959], + [0.48366628618847957, 0.12864679022013889, 0.31317188565991266], + [0.48852847371852987, 0.13093210934893723, 0.31354553695453014], + [0.49335504375145617, 0.13328091630401023, 0.31386561956734976], + [0.49814435462074153, 0.13569380302451714, 0.314135190862664], + [0.50289524974970612, 0.13817086581280427, 0.31435662153833671], + [0.50760681181053691, 0.14071192654913128, 0.31453200120082569], + [0.51227835105321762, 0.14331656120063752, 0.3146630922831542], + [0.51690848800544464, 0.14598463068714407, 0.31475407592280041], + [0.52149652863229956, 0.14871544765633712, 0.31480767954534428], + [0.52604189625477482, 0.15150818660835483, 0.31482653406646727], + [0.53054420489856446, 0.15436183633886777, 0.31481299789187128], + [0.5350027976174474, 0.15727540775107324, 0.31477085207396532], + [0.53941736649199057, 0.16024769309971934, 0.31470295028655965], + [0.54378771313608565, 0.16327738551419116, 0.31461204226295625], + [0.54811370033467621, 0.1663630904279047, 0.31450102990914708], + [0.55239521572711914, 0.16950338809328983, 0.31437291554615371], + [0.55663229034969341, 0.17269677158182117, 0.31423043195101424], + [0.56082499039117173, 0.17594170887918095, 0.31407639883970623], + [0.56497343529017696, 0.17923664950367169, 0.3139136046337036], + [0.56907784784011428, 0.18258004462335425, 0.31374440956796529], + [0.57313845754107873, 0.18597036007065024, 0.31357126868520002], + [0.57715550812992045, 0.18940601489760422, 0.31339704333572083], + [0.58112932761586555, 0.19288548904692518, 0.31322399394183942], + [0.58506024396466882, 0.19640737049066315, 0.31305401163732732], + [0.58894861935544707, 0.19997020971775276, 0.31288922211590126], + [0.59279480536520257, 0.20357251410079796, 0.31273234839304942], + [0.59659918109122367, 0.207212956082026, 0.31258523031121233], + [0.60036213010411577, 0.21089030138947745, 0.31244934410414688], + [0.60408401696732739, 0.21460331490206347, 0.31232652641170694], + [0.60776523994818654, 0.21835070166659282, 0.31221903291870201], + [0.6114062072731884, 0.22213124697023234, 0.31212881396435238], + [0.61500723236391375, 0.22594402043981826, 0.31205680685765741], + [0.61856865258877192, 0.22978799249179921, 0.31200463838728931], + [0.62209079821082613, 0.2336621873300741, 0.31197383273627388], + [0.62557416500434959, 0.23756535071152696, 0.31196698314912269], + [0.62901892016985872, 0.24149689191922535, 0.31198447195645718], + [0.63242534854210275, 0.24545598775548677, 0.31202765974624452], + [0.6357937104834237, 0.24944185818822678, 0.31209793953300591], + [0.6391243387840212, 0.25345365461983138, 0.31219689612063978], + [0.642417577481186, 0.257490519876798, 0.31232631707560987], + [0.64567349382645434, 0.26155203161615281, 0.31248673753935263], + [0.64889230169458245, 0.26563755336209077, 0.31267941819570189], + [0.65207417290277303, 0.26974650525236699, 0.31290560605819168], + [0.65521932609327127, 0.27387826652410152, 0.3131666792687211], + [0.6583280801134499, 0.27803210957665631, 0.3134643447952643], + [0.66140037532601781, 0.28220778870555907, 0.31379912926498488], + [0.66443632469878844, 0.28640483614256179, 0.31417223403606975], + [0.66743603766369131, 0.29062280081258873, 0.31458483752056837], + [0.67039959547676198, 0.29486126309253047, 0.31503813956872212], + [0.67332725564817331, 0.29911962764489264, 0.31553372323982209], + [0.67621897924409746, 0.30339762792450425, 0.3160724937230589], + [0.67907474028157344, 0.30769497879760166, 0.31665545668946665], + [0.68189457150944521, 0.31201133280550686, 0.31728380489244951], + [0.68467850942494535, 0.31634634821222207, 0.31795870784057567], + [0.68742656435169625, 0.32069970535138104, 0.31868137622277692], + [0.6901389321505248, 0.32507091815606004, 0.31945332332898302], + [0.69281544846764931, 0.32945984647042675, 0.3202754315314667], + [0.69545608346891119, 0.33386622163232865, 0.32114884306985791], + [0.6980608153581771, 0.33828976326048621, 0.32207478855218091], + [0.70062962477242097, 0.34273019305341756, 0.32305449047765694], + [0.70316249458814151, 0.34718723719597999, 0.32408913679491225], + [0.70565951122610093, 0.35166052978120937, 0.32518014084085567], + [0.70812059568420482, 0.35614985523380299, 0.32632861885644465], + [0.7105456546582587, 0.36065500290840113, 0.32753574162788762], + [0.71293466839773467, 0.36517570519856757, 0.3288027427038317], + [0.71528760614847287, 0.36971170225223449, 0.3301308728723546], + [0.71760444908133847, 0.37426272710686193, 0.33152138620958932], + [0.71988521490549851, 0.37882848839337313, 0.33297555200245399], + [0.7221299918421461, 0.38340864508963057, 0.33449469983585844], + [0.72433865647781592, 0.38800301593162145, 0.33607995965691828], + [0.72651122900227549, 0.3926113126792577, 0.3377325942005665], + [0.72864773856716547, 0.39723324476747235, 0.33945384341064017], + [0.73074820754845171, 0.401868526884681, 0.3412449533046818], + [0.73281270506268747, 0.4065168468778026, 0.34310715173410822], + [0.73484133598564938, 0.41117787004519513, 0.34504169470809071], + [0.73683422173585866, 0.41585125850290111, 0.34704978520758401], + [0.73879140024599266, 0.42053672992315327, 0.34913260148542435], + [0.74071301619506091, 0.4252339389526239, 0.35129130890802607], + [0.7425992159973317, 0.42994254036133867, 0.35352709245374592], + [0.74445018676570673, 0.43466217184617112, 0.35584108091122535], + [0.74626615789163442, 0.43939245044973502, 0.35823439142300639], + [0.74804739275559562, 0.44413297780351974, 0.36070813602540136], + [0.74979420547170472, 0.44888333481548809, 0.36326337558360278], + [0.75150685045891663, 0.45364314496866825, 0.36590112443835765], + [0.75318566369046569, 0.45841199172949604, 0.36862236642234769], + [0.75483105066959544, 0.46318942799460555, 0.3714280448394211], + [0.75644341577140706, 0.46797501437948458, 0.37431909037543515], + [0.75802325538455839, 0.4727682731566229, 0.37729635531096678], + [0.75957111105340058, 0.47756871222057079, 0.380360657784311], + [0.7610876378057071, 0.48237579130289127, 0.38351275723852291], + [0.76257333554052609, 0.48718906673415824, 0.38675335037837993], + [0.76402885609288662, 0.49200802533379656, 0.39008308392311997], + [0.76545492593330511, 0.49683212909727231, 0.39350254000115381], + [0.76685228950643891, 0.5016608471009063, 0.39701221751773474], + [0.76822176599735303, 0.50649362371287909, 0.40061257089416885], + [0.7695642334401418, 0.5113298901696085, 0.40430398069682483], + [0.77088091962302474, 0.51616892643469103, 0.40808667584648967], + [0.77217257229605551, 0.5210102658711383, 0.41196089987122869], + [0.77344021829889886, 0.52585332093451564, 0.41592679539764366], + [0.77468494746063199, 0.53069749384776732, 0.41998440356963762], + [0.77590790730685699, 0.53554217882461186, 0.42413367909988375], + [0.7771103295521099, 0.54038674910561235, 0.42837450371258479], + [0.77829345807633121, 0.54523059488426595, 0.432706647838971], + [0.77945862731506643, 0.55007308413977274, 0.43712979856444761], + [0.78060774749483774, 0.55491335744890613, 0.44164332426364639], + [0.78174180478981836, 0.55975098052594863, 0.44624687186865436], + [0.78286225264440912, 0.56458533111166875, 0.45093985823706345], + [0.78397060836414478, 0.56941578326710418, 0.45572154742892063], + [0.78506845019606841, 0.5742417003617839, 0.46059116206904965], + [0.78615737132332963, 0.5790624629815756, 0.46554778281918402], + [0.78723904108188347, 0.58387743744557208, 0.47059039582133383], + [0.78831514045623963, 0.58868600173562435, 0.47571791879076081], + [0.78938737766251943, 0.5934875421745599, 0.48092913815357724], + [0.79045776847727878, 0.59828134277062461, 0.48622257801969754], + [0.79152832843475607, 0.60306670593147205, 0.49159667021646397], + [0.79260034304237448, 0.60784322087037024, 0.49705020621532009], + [0.79367559698664958, 0.61261029334072192, 0.50258161291269432], + [0.79475585972654039, 0.61736734400220705, 0.50818921213102985], + [0.79584292379583765, 0.62211378808451145, 0.51387124091909786], + [0.79693854719951607, 0.62684905679296699, 0.5196258425240281], + [0.79804447815136637, 0.63157258225089552, 0.52545108144834785], + [0.7991624518501963, 0.63628379372029187, 0.53134495942561433], + [0.80029415389753977, 0.64098213306749863, 0.53730535185141037], + [0.80144124292560048, 0.64566703459218766, 0.5433300863249918], + [0.80260531146112946, 0.65033793748103852, 0.54941691584603647], + [0.80378792531077625, 0.65499426549472628, 0.55556350867083815], + [0.80499054790810298, 0.65963545027564163, 0.56176745110546977], + [0.80621460526927058, 0.66426089585282289, 0.56802629178649788], + [0.8074614045096935, 0.6688700095398864, 0.57433746373459582], + [0.80873219170089694, 0.67346216702194517, 0.58069834805576737], + [0.81002809466520687, 0.67803672673971815, 0.58710626908082753], + [0.81135014011763329, 0.68259301546243389, 0.59355848909050757], + [0.81269922039881493, 0.68713033714618876, 0.60005214820435104], + [0.81407611046993344, 0.69164794791482131, 0.6065843782630862], + [0.81548146627279483, 0.69614505508308089, 0.61315221209322646], + [0.81691575775055891, 0.70062083014783982, 0.61975260637257923], + [0.81837931164498223, 0.70507438189635097, 0.62638245478933297], + [0.81987230650455289, 0.70950474978787481, 0.63303857040067113], + [0.8213947205565636, 0.7139109141951604, 0.63971766697672761], + [0.82294635110428427, 0.71829177331290062, 0.6464164243818421], + [0.8245268129450285, 0.72264614312088882, 0.65313137915422603], + [0.82613549710580259, 0.72697275518238258, 0.65985900156216504], + [0.8277716072353446, 0.73127023324078089, 0.66659570204682972], + [0.82943407816481474, 0.7355371221572935, 0.67333772009301907], + [0.83112163529096306, 0.73977184647638616, 0.68008125203631464], + [0.83283277185777982, 0.74397271817459876, 0.68682235874648545], + [0.8345656905566583, 0.7481379479992134, 0.69355697649863846], + [0.83631898844737929, 0.75226548952875261, 0.70027999028864962], + [0.83809123476131964, 0.75635314860808633, 0.70698561390212977], + [0.83987839884120874, 0.76039907199779677, 0.71367147811129228], + [0.84167750766845151, 0.76440101200982946, 0.72033299387284622], + [0.84348529222933699, 0.76835660399870176, 0.72696536998972039], + [0.84529810731955113, 0.77226338601044719, 0.73356368240541492], + [0.84711195507965098, 0.77611880236047159, 0.74012275762807056], + [0.84892245563117641, 0.77992021407650147, 0.74663719293664366], + [0.85072697023178789, 0.78366457342383888, 0.7530974636118285], + [0.85251907207708444, 0.78734936133548439, 0.7594994148789691], + [0.85429219611470464, 0.79097196777091994, 0.76583801477914104], + [0.85604022314725403, 0.79452963601550608, 0.77210610037674143], + [0.85775662943504905, 0.79801963142713928, 0.77829571667247499], + [0.8594346370300241, 0.8014392309950078, 0.78439788751383921], + [0.86107117027565516, 0.80478517909812231, 0.79039529663736285], + [0.86265601051127572, 0.80805523804261525, 0.796282666437655], + [0.86418343723941027, 0.81124644224653542, 0.80204612696863953], + [0.86564934325605325, 0.81435544067514909, 0.80766972324164554], + [0.86705314907048503, 0.81737804041911244, 0.81313419626911398], + [0.86839954695818633, 0.82030875512181523, 0.81841638963128993], + [0.86969131502613806, 0.82314158859569164, 0.82350476683173168], + [0.87093846717297507, 0.82586857889438514, 0.82838497261149613], + [0.87215331978454325, 0.82848052823709672, 0.8330486712880828], + [0.87335171360916275, 0.83096715251272624, 0.83748851001197089], + [0.87453793320260187, 0.83331972948645461, 0.84171925358069011], + [0.87571458709961403, 0.8355302318472394, 0.84575537519027078], + [0.87687848451614692, 0.83759238071186537, 0.84961373549150254], + [0.87802298436649007, 0.83950165618540074, 0.85330645352458923], + [0.87913244240792765, 0.84125554884475906, 0.85685572291039636], + [0.88019293315695812, 0.84285224824778615, 0.86027399927156634], + [0.88119169871341951, 0.84429066717717349, 0.86356595168669881], + [0.88211542489401606, 0.84557007254559347, 0.86673765046233331], + [0.88295168595448525, 0.84668970275699273, 0.86979617048190971], + [0.88369127145898041, 0.84764891761519268, 0.87274147101441557], + [0.88432713054113543, 0.84844741572055415, 0.87556785228242973], + [0.88485138159908572, 0.84908426422893801, 0.87828235285372469], + [0.88525897972630474, 0.84955892810989209, 0.88088414794024839], + [0.88554714811952384, 0.84987174283631584, 0.88336206121170946], + [0.88571155122845646, 0.85002186115856315, 0.88572538990087124]] + +_twilight_shifted_data = (_twilight_data[len(_twilight_data)//2:] + + _twilight_data[:len(_twilight_data)//2]) +_twilight_shifted_data.reverse() + cmaps = {} for (name, data) in (('magma', _magma_data), ('inferno', _inferno_data), ('plasma', _plasma_data), ('viridis', _viridis_data), - ('cividis', _cividis_data)): + ('cividis', _cividis_data), + ('twilight', _twilight_data), + ('twilight_shifted', _twilight_shifted_data)): cmaps[name] = ListedColormap(data, name=name) # generate reversed colormap diff --git a/lib/matplotlib/_color_data.py b/lib/matplotlib/_color_data.py index 774e251d72bd..973f4a2f2435 100644 --- a/lib/matplotlib/_color_data.py +++ b/lib/matplotlib/_color_data.py @@ -1,7 +1,4 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) from collections import OrderedDict -import six BASE_COLORS = { diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index b8325f5d6368..7f740b9980a5 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -45,9 +45,6 @@ # Todo: AnchoredOffsetbox connected to gridspecs or axes. This would # be more general way to add extra-axes annotations. -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np import logging import warnings @@ -59,37 +56,27 @@ _log = logging.getLogger(__name__) -def get_axall_tightbbox(ax, renderer): - ''' - Get the tight_bbox of the axis ax, and any dependent decorations, like - a `Legend` instance. - ''' - - # main bbox of the axis.... - bbox = ax.get_tightbbox(renderer=renderer) - # now add the possibility of the legend... - for child in ax.get_children(): - if isinstance(child, Legend): - bboxn = child._legend_box.get_window_extent(renderer) - bbox = transforms.Bbox.union([bbox, bboxn]) - # add other children here.... - return bbox +def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax): + return (colnumCmin <= colnum0min <= colnumCmax + or colnumCmin <= colnum0max <= colnumCmax) -def in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax): - if colnum0min >= colnumCmin and colnum0min <= colnumCmax: - return True - if colnum0max >= colnumCmin and colnum0max <= colnumCmax: - return True - return False +def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax): + return (rownumCmin <= rownum0min <= rownumCmax + or rownumCmin <= rownum0max <= rownumCmax) -def in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax): - if rownum0min >= rownumCmin and rownum0min <= rownumCmax: - return True - if rownum0max >= rownumCmin and rownum0max <= rownumCmax: - return True - return False +def _axes_all_finite_sized(fig): + """ + helper function to make sure all axes in the + figure have a finite width and height. If not, return False + """ + for ax in fig.axes: + if ax._layoutbox is not None: + newpos = ax._poslayoutbox.get_rect() + if newpos[2] <= 0 or newpos[3] <= 0: + return False + return True ###################################################### @@ -161,7 +148,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, invTransFig = fig.transFigure.inverted().transform_bbox # list of unique gridspecs that contain child axes: - gss = set([]) + gss = set() for ax in fig.axes: if hasattr(ax, 'get_subplotspec'): gs = ax.get_subplotspec().get_gridspec() @@ -172,280 +159,312 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, 'Possibly did not call parent GridSpec with the figure= ' 'keyword') - # check for unoccupied gridspec slots and make ghost axes for these - # slots... Do for each gs separately. This is a pretty big kludge - # but shoudn't have too much ill effect. The worst is that - # someone querrying the figure will wonder why there are more - # axes than they thought. if fig._layoutbox.constrained_layout_called < 1: for gs in gss: - nrows, ncols = gs.get_geometry() - hassubplotspec = np.zeros(nrows * ncols, dtype=bool) - axs = [] + # fill in any empty gridspec slots w/ ghost axes... + _make_ghost_gridspec_slots(fig, gs) + + for nnn in range(2): + # do the algrithm twice. This has to be done because decorators + # change size after the first re-position (i.e. x/yticklabels get + # larger/smaller). This second reposition tends to be much milder, + # so doing twice makes things work OK. + for ax in fig.axes: + _log.debug(ax._layoutbox) + if ax._layoutbox is not None: + # make margins for each layout box based on the size of + # the decorators. + _make_layout_margins(ax, renderer, h_pad, w_pad) + + # do layout for suptitle. + if fig._suptitle is not None and fig._suptitle._layoutbox is not None: + sup = fig._suptitle + bbox = invTransFig(sup.get_window_extent(renderer=renderer)) + height = bbox.y1 - bbox.y0 + sup._layoutbox.edit_height(height+h_pad) + + # OK, the above lines up ax._poslayoutbox with ax._layoutbox + # now we need to + # 1) arrange the subplotspecs. We do it at this level because + # the subplotspecs are meant to contain other dependent axes + # like colorbars or legends. + # 2) line up the right and left side of the ax._poslayoutbox + # that have the same subplotspec maxes. + + if fig._layoutbox.constrained_layout_called < 1: + # arrange the subplotspecs... This is all done relative to each + # other. Some subplotspecs conatain axes, and others contain + # gridspecs the ones that contain gridspecs are a set proportion + # of their parent gridspec. The ones that contain axes are + # not so constrained. + figlb = fig._layoutbox + for child in figlb.children: + if child._is_gridspec_layoutbox(): + # This routine makes all the subplot spec containers + # have the correct arrangement. It just stacks the + # subplot layoutboxes in the correct order... + _arrange_subplotspecs(child, hspace=hspace, wspace=wspace) + + for gs in gss: + _align_spines(fig, gs) + + fig._layoutbox.constrained_layout_called += 1 + fig._layoutbox.update_variables() + + # check if any axes collapsed to zero. If not, don't change positions: + if _axes_all_finite_sized(fig): + # Now set the position of the axes... for ax in fig.axes: - if (hasattr(ax, 'get_subplotspec') - and ax._layoutbox is not None - and ax.get_subplotspec().get_gridspec() == gs): - axs += [ax] - for ax in axs: - ss0 = ax.get_subplotspec() - if ss0.num2 is None: - ss0.num2 = ss0.num1 - hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True - for nn, hss in enumerate(hassubplotspec): - if not hss: - # this gridspec slot doesn't have an axis so we - # make a "ghost". - ax = fig.add_subplot(gs[nn]) - ax.set_frame_on(False) - ax.set_xticks([]) - ax.set_yticks([]) - ax.set_facecolor((1, 0, 0, 0)) - - # for each axes, make a margin between the *pos* layoutbox and the - # *axes* layoutbox be a minimum size that can accomodate the - # decorations on the axis. - for ax in fig.axes: - _log.debug(ax._layoutbox) - if ax._layoutbox is not None: - pos = ax.get_position(original=True) - tightbbox = get_axall_tightbbox(ax, renderer) - bbox = invTransFig(tightbbox) - # use stored h_pad if it exists - h_padt = ax._poslayoutbox.h_pad - if h_padt is None: - h_padt = h_pad - w_padt = ax._poslayoutbox.w_pad - if w_padt is None: - w_padt = w_pad - ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + - pos.x0 + w_padt) - ax._poslayoutbox.edit_right_margin_min(bbox.x1 - - pos.x1 + w_padt) - ax._poslayoutbox.edit_bottom_margin_min( - -bbox.y0 + pos.y0 + h_padt) - ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt) - _log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad)) - _log.debug('right %f', (bbox.x1 - pos.x1 + w_pad)) - _log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt)) - # Sometimes its possible for the solver to collapse - # rather than expand axes, so they all have zero height - # or width. This stops that... It *should* have been - # taken into account w/ pref_width... - if fig._layoutbox.constrained_layout_called < 1: - ax._poslayoutbox.constrain_height_min(20, strength='weak') - ax._poslayoutbox.constrain_width_min(20, strength='weak') - ax._layoutbox.constrain_height_min(20, strength='weak') - ax._layoutbox.constrain_width_min(20, strength='weak') - ax._poslayoutbox.constrain_top_margin(0, strength='weak') - ax._poslayoutbox.constrain_bottom_margin(0, - strength='weak') - ax._poslayoutbox.constrain_right_margin(0, strength='weak') - ax._poslayoutbox.constrain_left_margin(0, strength='weak') - - # do layout for suptitle. - if fig._suptitle is not None and fig._suptitle._layoutbox is not None: - sup = fig._suptitle - bbox = invTransFig(sup.get_window_extent(renderer=renderer)) - height = bbox.y1 - bbox.y0 - sup._layoutbox.edit_height(height+h_pad) - - # OK, the above lines up ax._poslayoutbox with ax._layoutbox - # now we need to - # 1) arrange the subplotspecs. We do it at this level because - # the subplotspecs are meant to contain other dependent axes - # like colorbars or legends. - # 2) line up the right and left side of the ax._poslayoutbox - # that have the same subplotspec maxes. + if ax._layoutbox is not None: + newpos = ax._poslayoutbox.get_rect() + # Now set the new position. + # ax.set_position will zero out the layout for + # this axis, allowing users to hard-code the position, + # so this does the same w/o zeroing layout. + ax._set_position(newpos, which='original') + else: + warnings.warn('constrained_layout not applied. At least ' + 'one axes collapsed to zero width or height.') - if fig._layoutbox.constrained_layout_called < 1: - # arrange the subplotspecs... This is all done relative to each - # other. Some subplotspecs conatain axes, and others contain gridspecs - # the ones that contain gridspecs are a set proportion of their - # parent gridspec. The ones that contain axes are not so constrained. - figlb = fig._layoutbox - for child in figlb.children: - if child._is_gridspec_layoutbox(): - # farm the gridspec layout out. - # - # This routine makes all the subplot spec containers - # have the correct arrangement. It just stacks the - # subplot layoutboxes in the correct order... - _arange_subplotspecs(child, hspace=hspace, wspace=wspace) - - # - Align right/left and bottom/top spines of appropriate subplots. - # - Compare size of subplotspec including height and width ratios - # and make sure that the axes spines are at least as large - # as they should be. - for gs in gss: - # for each gridspec... - nrows, ncols = gs.get_geometry() - width_ratios = gs.get_width_ratios() - height_ratios = gs.get_height_ratios() - if width_ratios is None: - width_ratios = np.ones(ncols) - if height_ratios is None: - height_ratios = np.ones(nrows) - - # get axes in this gridspec.... - axs = [] - for ax in fig.axes: - if (hasattr(ax, 'get_subplotspec') - and ax._layoutbox is not None): - if ax.get_subplotspec().get_gridspec() == gs: - axs += [ax] - rownummin = np.zeros(len(axs), dtype=np.int8) - rownummax = np.zeros(len(axs), dtype=np.int8) - colnummin = np.zeros(len(axs), dtype=np.int8) - colnummax = np.zeros(len(axs), dtype=np.int8) - width = np.zeros(len(axs)) - height = np.zeros(len(axs)) - - for n, ax in enumerate(axs): - ss0 = ax.get_subplotspec() - if ss0.num2 is None: - ss0.num2 = ss0.num1 - rownummin[n], colnummin[n] = divmod(ss0.num1, ncols) - rownummax[n], colnummax[n] = divmod(ss0.num2, ncols) - width[n] = np.sum( - width_ratios[colnummin[n]:(colnummax[n] + 1)]) - height[n] = np.sum( - height_ratios[rownummin[n]:(rownummax[n] + 1)]) - - for nn, ax in enumerate(axs[:-1]): - ss0 = ax.get_subplotspec() - - # now compare ax to all the axs: - # - # If the subplotspecs have the same colnumXmax, then line - # up their right sides. If they have the same min, then - # line up their left sides (and vertical equivalents). - rownum0min, colnum0min = rownummin[nn], colnummin[nn] - rownum0max, colnum0max = rownummax[nn], colnummax[nn] - width0, height0 = width[nn], height[nn] - alignleft = False - alignright = False - alignbot = False - aligntop = False - alignheight = False - alignwidth = False - for mm in range(nn+1, len(axs)): - axc = axs[mm] - rownumCmin, colnumCmin = rownummin[mm], colnummin[mm] - rownumCmax, colnumCmax = rownummax[mm], colnummax[mm] - widthC, heightC = width[mm], height[mm] - # Horizontally align axes spines if they have the - # same min or max: - if not alignleft and colnum0min == colnumCmin: - # we want the _poslayoutboxes to line up on left - # side of the axes spines... - layoutbox.align([ax._poslayoutbox, - axc._poslayoutbox], - 'left') - alignleft = True - - if not alignright and colnum0max == colnumCmax: - # line up right sides of _poslayoutbox - layoutbox.align([ax._poslayoutbox, - axc._poslayoutbox], - 'right') - alignright = True - # Vertically align axes spines if they have the - # same min or max: - if not aligntop and rownum0min == rownumCmin: - # line up top of _poslayoutbox - _log.debug('rownum0min == rownumCmin') - layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], - 'top') - aligntop = True - - if not alignbot and rownum0max == rownumCmax: - # line up bottom of _poslayoutbox - _log.debug('rownum0max == rownumCmax') - layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], - 'bottom') - alignbot = True - ########### - # Now we make the widths and heights of position boxes - # similar. (i.e the spine locations) - # This allows vertically stacked subplots to have - # different sizes if they occupy different amounts - # of the gridspec: i.e. - # gs = gridspec.GridSpec(3,1) - # ax1 = gs[0,:] - # ax2 = gs[1:,:] - # then drows0 = 1, and drowsC = 2, and ax2 - # should be at least twice as large as ax1. - # But it can be more than twice as large because - # it needs less room for the labeling. - # - # For height, this only needs to be done if the - # subplots share a column. For width if they - # share a row. - - drowsC = (rownumCmax - rownumCmin + 1) - drows0 = (rownum0max - rownum0min + 1) - dcolsC = (colnumCmax - colnumCmin + 1) - dcols0 = (colnum0max - colnum0min + 1) - - if not alignheight and drows0 == drowsC: - ax._poslayoutbox.constrain_height( - axc._poslayoutbox.height * height0 / heightC) - alignheight = True - elif in_same_column(colnum0min, colnum0max, - colnumCmin, colnumCmax): - if height0 > heightC: - ax._poslayoutbox.constrain_height_min( - axc._poslayoutbox.height * height0 / heightC) - # these constraints stop the smaller axes from - # being allowed to go to zero height... - axc._poslayoutbox.constrain_height_min( - ax._poslayoutbox.height * heightC / - (height0*1.8)) - elif height0 < heightC: - axc._poslayoutbox.constrain_height_min( - ax._poslayoutbox.height * heightC / height0) - ax._poslayoutbox.constrain_height_min( - ax._poslayoutbox.height * height0 / - (heightC*1.8)) - # widths... - if not alignwidth and dcols0 == dcolsC: - ax._poslayoutbox.constrain_width( - axc._poslayoutbox.width * width0 / widthC) - alignwidth = True - elif in_same_row(rownum0min, rownum0max, - rownumCmin, rownumCmax): - if width0 > widthC: - ax._poslayoutbox.constrain_width_min( - axc._poslayoutbox.width * width0 / widthC) - axc._poslayoutbox.constrain_width_min( - ax._poslayoutbox.width * widthC / - (width0*1.8)) - elif width0 < widthC: - axc._poslayoutbox.constrain_width_min( - ax._poslayoutbox.width * widthC / width0) - ax._poslayoutbox.constrain_width_min( - axc._poslayoutbox.width * width0 / - (widthC*1.8)) - - fig._layoutbox.constrained_layout_called += 1 - fig._layoutbox.update_variables() - # Now set the position of the axes... +def _make_ghost_gridspec_slots(fig, gs): + """ + Check for unoccupied gridspec slots and make ghost axes for these + slots... Do for each gs separately. This is a pretty big kludge + but shoudn't have too much ill effect. The worst is that + someone querrying the figure will wonder why there are more + axes than they thought. + """ + nrows, ncols = gs.get_geometry() + hassubplotspec = np.zeros(nrows * ncols, dtype=bool) + axs = [] for ax in fig.axes: - if ax._layoutbox is not None: - newpos = ax._poslayoutbox.get_rect() - _log.debug('newpos %r', newpos) - # Now set the new position. - # ax.set_position will zero out the layout for - # this axis, allowing users to hard-code the position, - # so this does the same w/o zeroing layout. - ax._set_position(newpos, which='original') - + if (hasattr(ax, 'get_subplotspec') + and ax._layoutbox is not None + and ax.get_subplotspec().get_gridspec() == gs): + axs += [ax] + for ax in axs: + ss0 = ax.get_subplotspec() + if ss0.num2 is None: + ss0.num2 = ss0.num1 + hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True + for nn, hss in enumerate(hassubplotspec): + if not hss: + # this gridspec slot doesn't have an axis so we + # make a "ghost". + ax = fig.add_subplot(gs[nn]) + ax.set_frame_on(False) + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_facecolor((1, 0, 0, 0)) + + +def _make_layout_margins(ax, renderer, h_pad, w_pad): + """ + For each axes, make a margin between the *pos* layoutbox and the + *axes* layoutbox be a minimum size that can accommodate the + decorations on the axis. + """ + fig = ax.figure + invTransFig = fig.transFigure.inverted().transform_bbox -def _arange_subplotspecs(gs, hspace=0, wspace=0): + pos = ax.get_position(original=True) + tightbbox = ax.get_tightbbox(renderer=renderer) + bbox = invTransFig(tightbbox) + # use stored h_pad if it exists + h_padt = ax._poslayoutbox.h_pad + if h_padt is None: + h_padt = h_pad + w_padt = ax._poslayoutbox.w_pad + if w_padt is None: + w_padt = w_pad + ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + + pos.x0 + w_padt) + ax._poslayoutbox.edit_right_margin_min(bbox.x1 - + pos.x1 + w_padt) + ax._poslayoutbox.edit_bottom_margin_min( + -bbox.y0 + pos.y0 + h_padt) + ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt) + _log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad)) + _log.debug('right %f', (bbox.x1 - pos.x1 + w_pad)) + _log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt)) + # Sometimes its possible for the solver to collapse + # rather than expand axes, so they all have zero height + # or width. This stops that... It *should* have been + # taken into account w/ pref_width... + if fig._layoutbox.constrained_layout_called < 1: + ax._poslayoutbox.constrain_height_min(20, strength='weak') + ax._poslayoutbox.constrain_width_min(20, strength='weak') + ax._layoutbox.constrain_height_min(20, strength='weak') + ax._layoutbox.constrain_width_min(20, strength='weak') + ax._poslayoutbox.constrain_top_margin(0, strength='weak') + ax._poslayoutbox.constrain_bottom_margin(0, + strength='weak') + ax._poslayoutbox.constrain_right_margin(0, strength='weak') + ax._poslayoutbox.constrain_left_margin(0, strength='weak') + + +def _align_spines(fig, gs): + """ + - Align right/left and bottom/top spines of appropriate subplots. + - Compare size of subplotspec including height and width ratios + and make sure that the axes spines are at least as large + as they should be. + """ + # for each gridspec... + nrows, ncols = gs.get_geometry() + width_ratios = gs.get_width_ratios() + height_ratios = gs.get_height_ratios() + if width_ratios is None: + width_ratios = np.ones(ncols) + if height_ratios is None: + height_ratios = np.ones(nrows) + + # get axes in this gridspec.... + axs = [] + for ax in fig.axes: + if (hasattr(ax, 'get_subplotspec') + and ax._layoutbox is not None): + if ax.get_subplotspec().get_gridspec() == gs: + axs += [ax] + rownummin = np.zeros(len(axs), dtype=np.int8) + rownummax = np.zeros(len(axs), dtype=np.int8) + colnummin = np.zeros(len(axs), dtype=np.int8) + colnummax = np.zeros(len(axs), dtype=np.int8) + width = np.zeros(len(axs)) + height = np.zeros(len(axs)) + + for n, ax in enumerate(axs): + ss0 = ax.get_subplotspec() + if ss0.num2 is None: + ss0.num2 = ss0.num1 + rownummin[n], colnummin[n] = divmod(ss0.num1, ncols) + rownummax[n], colnummax[n] = divmod(ss0.num2, ncols) + width[n] = np.sum( + width_ratios[colnummin[n]:(colnummax[n] + 1)]) + height[n] = np.sum( + height_ratios[rownummin[n]:(rownummax[n] + 1)]) + + for nn, ax in enumerate(axs[:-1]): + ss0 = ax.get_subplotspec() + + # now compare ax to all the axs: + # + # If the subplotspecs have the same colnumXmax, then line + # up their right sides. If they have the same min, then + # line up their left sides (and vertical equivalents). + rownum0min, colnum0min = rownummin[nn], colnummin[nn] + rownum0max, colnum0max = rownummax[nn], colnummax[nn] + width0, height0 = width[nn], height[nn] + alignleft = False + alignright = False + alignbot = False + aligntop = False + alignheight = False + alignwidth = False + for mm in range(nn+1, len(axs)): + axc = axs[mm] + rownumCmin, colnumCmin = rownummin[mm], colnummin[mm] + rownumCmax, colnumCmax = rownummax[mm], colnummax[mm] + widthC, heightC = width[mm], height[mm] + # Horizontally align axes spines if they have the + # same min or max: + if not alignleft and colnum0min == colnumCmin: + # we want the _poslayoutboxes to line up on left + # side of the axes spines... + layoutbox.align([ax._poslayoutbox, + axc._poslayoutbox], + 'left') + alignleft = True + + if not alignright and colnum0max == colnumCmax: + # line up right sides of _poslayoutbox + layoutbox.align([ax._poslayoutbox, + axc._poslayoutbox], + 'right') + alignright = True + # Vertically align axes spines if they have the + # same min or max: + if not aligntop and rownum0min == rownumCmin: + # line up top of _poslayoutbox + _log.debug('rownum0min == rownumCmin') + layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], + 'top') + aligntop = True + + if not alignbot and rownum0max == rownumCmax: + # line up bottom of _poslayoutbox + _log.debug('rownum0max == rownumCmax') + layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], + 'bottom') + alignbot = True + ########### + # Now we make the widths and heights of position boxes + # similar. (i.e the spine locations) + # This allows vertically stacked subplots to have + # different sizes if they occupy different amounts + # of the gridspec: i.e. + # gs = gridspec.GridSpec(3,1) + # ax1 = gs[0,:] + # ax2 = gs[1:,:] + # then drows0 = 1, and drowsC = 2, and ax2 + # should be at least twice as large as ax1. + # But it can be more than twice as large because + # it needs less room for the labeling. + # + # For height, this only needs to be done if the + # subplots share a column. For width if they + # share a row. + + drowsC = (rownumCmax - rownumCmin + 1) + drows0 = (rownum0max - rownum0min + 1) + dcolsC = (colnumCmax - colnumCmin + 1) + dcols0 = (colnum0max - colnum0min + 1) + + if not alignheight and drows0 == drowsC: + ax._poslayoutbox.constrain_height( + axc._poslayoutbox.height * height0 / heightC) + alignheight = True + elif _in_same_column(colnum0min, colnum0max, + colnumCmin, colnumCmax): + if height0 > heightC: + ax._poslayoutbox.constrain_height_min( + axc._poslayoutbox.height * height0 / heightC) + # these constraints stop the smaller axes from + # being allowed to go to zero height... + axc._poslayoutbox.constrain_height_min( + ax._poslayoutbox.height * heightC / + (height0*1.8)) + elif height0 < heightC: + axc._poslayoutbox.constrain_height_min( + ax._poslayoutbox.height * heightC / height0) + ax._poslayoutbox.constrain_height_min( + ax._poslayoutbox.height * height0 / + (heightC*1.8)) + # widths... + if not alignwidth and dcols0 == dcolsC: + ax._poslayoutbox.constrain_width( + axc._poslayoutbox.width * width0 / widthC) + alignwidth = True + elif _in_same_row(rownum0min, rownum0max, + rownumCmin, rownumCmax): + if width0 > widthC: + ax._poslayoutbox.constrain_width_min( + axc._poslayoutbox.width * width0 / widthC) + axc._poslayoutbox.constrain_width_min( + ax._poslayoutbox.width * widthC / + (width0*1.8)) + elif width0 < widthC: + axc._poslayoutbox.constrain_width_min( + ax._poslayoutbox.width * widthC / width0) + ax._poslayoutbox.constrain_width_min( + axc._poslayoutbox.width * width0 / + (widthC*1.8)) + + +def _arrange_subplotspecs(gs, hspace=0, wspace=0): """ - arange the subplotspec children of this gridspec, and then recursively + arrange the subplotspec children of this gridspec, and then recursively do the same of any gridspec children of those gridspecs... """ sschildren = [] @@ -454,7 +473,7 @@ def _arange_subplotspecs(gs, hspace=0, wspace=0): for child2 in child.children: # check for gridspec children... if child2._is_gridspec_layoutbox(): - _arange_subplotspecs(child2, hspace=hspace, wspace=wspace) + _arrange_subplotspecs(child2, hspace=hspace, wspace=wspace) sschildren += [child] # now arrange the subplots... for child0 in sschildren: diff --git a/lib/matplotlib/_layoutbox.py b/lib/matplotlib/_layoutbox.py index cb6f0315805d..2eb04436dcf8 100644 --- a/lib/matplotlib/_layoutbox.py +++ b/lib/matplotlib/_layoutbox.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Conventions: @@ -16,9 +15,6 @@ """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import itertools import kiwisolver as kiwi import logging diff --git a/lib/matplotlib/_mathtext_data.py b/lib/matplotlib/_mathtext_data.py index d042d25892d6..baefe1b7eb72 100644 --- a/lib/matplotlib/_mathtext_data.py +++ b/lib/matplotlib/_mathtext_data.py @@ -1,10 +1,6 @@ """ font data tables for truetype and afm computer modern fonts """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six latex_to_bakoma = { '\\__sqrt__' : ('cmex10', 0x70), @@ -285,7 +281,7 @@ r'\rho' : ('psyr', 114), r'\sigma' : ('psyr', 115), r'\tau' : ('psyr', 116), - '\\upsilon' : ('psyr', 117), + r'\upsilon' : ('psyr', 117), r'\varpi' : ('psyr', 118), r'\omega' : ('psyr', 119), r'\xi' : ('psyr', 120), @@ -300,7 +296,7 @@ r'\spadesuit' : ('psyr', 170), r'\leftrightarrow' : ('psyr', 171), r'\leftarrow' : ('psyr', 172), - '\\uparrow' : ('psyr', 173), + r'\uparrow' : ('psyr', 173), r'\rightarrow' : ('psyr', 174), r'\downarrow' : ('psyr', 175), r'\pm' : ('psyr', 176), @@ -339,12 +335,12 @@ r'\surd' : ('psyr', 214), r'\__sqrt__' : ('psyr', 214), r'\cdot' : ('psyr', 215), - '\\urcorner' : ('psyr', 216), + r'\urcorner' : ('psyr', 216), r'\vee' : ('psyr', 217), r'\wedge' : ('psyr', 218), r'\Leftrightarrow' : ('psyr', 219), r'\Leftarrow' : ('psyr', 220), - '\\Uparrow' : ('psyr', 221), + r'\Uparrow' : ('psyr', 221), r'\Rightarrow' : ('psyr', 222), r'\Downarrow' : ('psyr', 223), r'\Diamond' : ('psyr', 224), @@ -366,7 +362,7 @@ r'\slash' : ('psyr', 0o57), r'\Lamda' : ('psyr', 0o114), r'\neg' : ('psyr', 0o330), - '\\Upsilon' : ('psyr', 0o241), + r'\Upsilon' : ('psyr', 0o241), r'\rightbrace' : ('psyr', 0o175), r'\rfloor' : ('psyr', 0o373), r'\lambda' : ('psyr', 0o154), @@ -1752,7 +1748,7 @@ 'uni044B' : 1099 } -uni2type1 = dict(((v,k) for k,v in six.iteritems(type12uni))) +uni2type1 = {v: k for k, v in type12uni.items()} tex2uni = { 'widehat' : 0x0302, diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index a1d37f21e202..ee1854ccef5c 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -1,10 +1,6 @@ """ Manage figures for pyplot interface. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six import atexit import gc @@ -63,7 +59,7 @@ def destroy(cls, num): @classmethod def destroy_fig(cls, fig): "*fig* is a Figure instance" - num = next((manager.num for manager in six.itervalues(cls.figs) + num = next((manager.num for manager in cls.figs.values() if manager.canvas.figure == fig), None) if num is not None: cls.destroy(num) diff --git a/lib/matplotlib/afm.py b/lib/matplotlib/afm.py index 1b5f4d5f6a0d..df8fe8010867 100644 --- a/lib/matplotlib/afm.py +++ b/lib/matplotlib/afm.py @@ -32,19 +32,18 @@ >>> afm.get_bbox_char('!') [130, -9, 238, 676] +As in the Adobe Font Metrics File Format Specification, all dimensions +are given in units of 1/1000 of the scale factor (point size) of the font +being used. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import map - -import sys +from collections import namedtuple import re +import sys + from ._mathtext_data import uni2type1 +from matplotlib.cbook import deprecated -# Convert string the a python type # some afm files have floats where we are expecting ints -- there is # probably a better way to handle this (support floats, round rather @@ -52,7 +51,6 @@ # this change to _to_int should at least prevent mpl from crashing on # these JDH (2009-11-06) - def _to_int(x): return int(float(x)) @@ -162,8 +160,8 @@ def _parse_header(fh): try: d[key] = headerConverters[key](val) except ValueError: - print('Value error parsing header in AFM:', - key, val, file=sys.stderr) + print('Value error parsing header in AFM:', key, val, + file=sys.stderr) continue except KeyError: print('Found an unknown keyword in AFM header (was %r)' % key, @@ -174,16 +172,42 @@ def _parse_header(fh): raise RuntimeError('Bad parse') +CharMetrics = namedtuple('CharMetrics', 'width, name, bbox') +CharMetrics.__doc__ = """ + Represents the character metrics of a single character. + + Notes + ----- + The fields do currently only describe a subset of character metrics + information defined in the AFM standard. + """ +CharMetrics.width.__doc__ = """The character width (WX).""" +CharMetrics.name.__doc__ = """The character name (N).""" +CharMetrics.bbox.__doc__ = """ + The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*).""" + + def _parse_char_metrics(fh): """ - Return a character metric dictionary. Keys are the ASCII num of - the character, values are a (*wx*, *name*, *bbox*) tuple, where - *wx* is the character width, *name* is the postscript language - name, and *bbox* is a (*llx*, *lly*, *urx*, *ury*) tuple. + Parse the given filehandle for character metrics information and return + the information as dicts. + It is assumed that the file cursor is on the line behind + 'StartCharMetrics'. + + Returns + ------- + ascii_d : dict + A mapping "ASCII num of the character" to `.CharMetrics`. + name_d : dict + A mapping "character name" to `.CharMetrics`. + + Notes + ----- This function is incomplete per the standard, but thus far parses all the sample afm files tried. """ + required_keys = {'C', 'WX', 'N', 'B'} ascii_d = {} name_d = {} @@ -196,21 +220,22 @@ def _parse_char_metrics(fh): # Split the metric line into a dictionary, keyed by metric identifiers vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s) # There may be other metrics present, but only these are needed - if not {'C', 'WX', 'N', 'B'}.issubset(vals): + if not required_keys.issubset(vals): raise RuntimeError('Bad char metrics line: %s' % line) num = _to_int(vals['C']) wx = _to_float(vals['WX']) name = vals['N'] bbox = _to_list_of_floats(vals['B']) bbox = list(map(int, bbox)) + metrics = CharMetrics(wx, name, bbox) # Workaround: If the character name is 'Euro', give it the # corresponding character code, according to WinAnsiEncoding (see PDF # Reference). if name == 'Euro': num = 128 if num != -1: - ascii_d[num] = (wx, name, bbox) - name_d[name] = (wx, bbox) + ascii_d[num] = metrics + name_d[name] = metrics raise RuntimeError('Bad parse') @@ -246,55 +271,80 @@ def _parse_kern_pairs(fh): raise RuntimeError('Bad kern pairs parse') +CompositePart = namedtuple('CompositePart', 'name, dx, dy') +CompositePart.__doc__ = """ + Represents the information on a composite element of a composite char.""" +CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'.""" +CompositePart.dx.__doc__ = """x-displacement of the part from the origin.""" +CompositePart.dy.__doc__ = """y-displacement of the part from the origin.""" + + def _parse_composites(fh): """ - Return a composites dictionary. Keys are the names of the - composites. Values are a num parts list of composite information, - with each element being a (*name*, *dx*, *dy*) tuple. Thus a - composites line reading: + Parse the given filehandle for composites information return them as a + dict. + + It is assumed that the file cursor is on the line behind 'StartComposites'. + + Returns + ------- + composites : dict + A dict mapping composite character names to a parts list. The parts + list is a list of `.CompositePart` entries describing the parts of + the composite. + + Example + ------- + A composite definition line:: CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ; will be represented as:: - d['Aacute'] = [ ('A', 0, 0), ('acute', 160, 170) ] + composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0), + CompositePart(name='acute', dx=160, dy=170)] """ - d = {} + composites = {} for line in fh: line = line.rstrip() if not line: continue if line.startswith(b'EndComposites'): - return d + return composites vals = line.split(b';') cc = vals[0].split() name, numParts = cc[1], _to_int(cc[2]) pccParts = [] for s in vals[1:-1]: pcc = s.split() - name, dx, dy = pcc[1], _to_float(pcc[2]), _to_float(pcc[3]) - pccParts.append((name, dx, dy)) - d[name] = pccParts + part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3])) + pccParts.append(part) + composites[name] = pccParts raise RuntimeError('Bad composites parse') def _parse_optional(fh): """ - Parse the optional fields for kern pair data and composites - - return value is a (*kernDict*, *compositeDict*) which are the - return values from :func:`_parse_kern_pairs`, and - :func:`_parse_composites` if the data exists, or empty dicts - otherwise + Parse the optional fields for kern pair data and composites. + + Returns + ------- + kern_data : dict + A dict containing kerning information. May be empty. + See `._parse_kern_pairs`. + composites : dict + A dict containing composite information. May be empty. + See `._parse_composites`. """ optional = { b'StartKernData': _parse_kern_pairs, b'StartComposites': _parse_composites, } - d = {b'StartKernData': {}, b'StartComposites': {}} + d = {b'StartKernData': {}, + b'StartComposites': {}} for line in fh: line = line.rstrip() if not line: @@ -304,47 +354,53 @@ def _parse_optional(fh): if key in optional: d[key] = optional[key](fh) - l = (d[b'StartKernData'], d[b'StartComposites']) - return l + return d[b'StartKernData'], d[b'StartComposites'] +@deprecated("3.0", "Use the class AFM instead.") def parse_afm(fh): + return _parse_afm(fh) + + +def _parse_afm(fh): """ - Parse the Adobe Font Metics file in file handle *fh*. Return value - is a (*dhead*, *dcmetrics_ascii*, *dmetrics_name*, *dkernpairs*, - *dcomposite*) tuple where - *dhead* is a :func:`_parse_header` dict, - *dcmetrics_ascii* and *dcmetrics_name* are the two resulting dicts - from :func:`_parse_char_metrics`, - *dkernpairs* is a :func:`_parse_kern_pairs` dict (possibly {}) and - *dcomposite* is a :func:`_parse_composites` dict (possibly {}) + Parse the Adobe Font Metrics file in file handle *fh*. + + Returns + ------- + header : dict + A header dict. See :func:`_parse_header`. + cmetrics_by_ascii : dict + From :func:`_parse_char_metrics`. + cmetrics_by_name : dict + From :func:`_parse_char_metrics`. + kernpairs : dict + From :func:`_parse_kern_pairs`. + composites : dict + From :func:`_parse_composites` + """ _sanity_check(fh) - dhead = _parse_header(fh) - dcmetrics_ascii, dcmetrics_name = _parse_char_metrics(fh) - doptional = _parse_optional(fh) - return dhead, dcmetrics_ascii, dcmetrics_name, doptional[0], doptional[1] + header = _parse_header(fh) + cmetrics_by_ascii, cmetrics_by_name = _parse_char_metrics(fh) + kernpairs, composites = _parse_optional(fh) + return header, cmetrics_by_ascii, cmetrics_by_name, kernpairs, composites class AFM(object): def __init__(self, fh): - """ - Parse the AFM file in file object *fh* - """ - (dhead, dcmetrics_ascii, dcmetrics_name, dkernpairs, dcomposite) = \ - parse_afm(fh) - self._header = dhead - self._kern = dkernpairs - self._metrics = dcmetrics_ascii - self._metrics_by_name = dcmetrics_name - self._composite = dcomposite + """Parse the AFM file in file object *fh*.""" + (self._header, + self._metrics, + self._metrics_by_name, + self._kern, + self._composite) = _parse_afm(fh) def get_bbox_char(self, c, isord=False): if not isord: c = ord(c) - wx, name, bbox = self._metrics[c] - return bbox + return self._metrics[c].bbox def string_width_height(self, s): """ @@ -353,7 +409,7 @@ def string_width_height(self, s): """ if not len(s): return 0, 0 - totalw = 0 + total_width = 0 namelast = None miny = 1e9 maxy = 0 @@ -361,119 +417,77 @@ def string_width_height(self, s): if c == '\n': continue wx, name, bbox = self._metrics[ord(c)] + + total_width += wx + self._kern.get((namelast, name), 0) l, b, w, h = bbox + miny = min(miny, b) + maxy = max(maxy, b + h) - # find the width with kerning - try: - kp = self._kern[(namelast, name)] - except KeyError: - kp = 0 - totalw += wx + kp - - # find the max y - thismax = b + h - if thismax > maxy: - maxy = thismax - - # find the min y - thismin = b - if thismin < miny: - miny = thismin namelast = name - return totalw, maxy - miny + return total_width, maxy - miny def get_str_bbox_and_descent(self, s): - """ - Return the string bounding box - """ + """Return the string bounding box and the maximal descent.""" if not len(s): - return 0, 0, 0, 0 - totalw = 0 + return 0, 0, 0, 0, 0 + total_width = 0 namelast = None miny = 1e9 maxy = 0 left = 0 - if not isinstance(s, six.text_type): + if not isinstance(s, str): s = _to_str(s) for c in s: if c == '\n': continue name = uni2type1.get(ord(c), 'question') try: - wx, bbox = self._metrics_by_name[name] + wx, _, bbox = self._metrics_by_name[name] except KeyError: name = 'question' - wx, bbox = self._metrics_by_name[name] + wx, _, bbox = self._metrics_by_name[name] + total_width += wx + self._kern.get((namelast, name), 0) l, b, w, h = bbox - if l < left: - left = l - # find the width with kerning - try: - kp = self._kern[(namelast, name)] - except KeyError: - kp = 0 - totalw += wx + kp - - # find the max y - thismax = b + h - if thismax > maxy: - maxy = thismax - - # find the min y - thismin = b - if thismin < miny: - miny = thismin + left = min(left, l) + miny = min(miny, b) + maxy = max(maxy, b + h) + namelast = name - return left, miny, totalw, maxy - miny, -miny + return left, miny, total_width, maxy - miny, -miny def get_str_bbox(self, s): - """ - Return the string bounding box - """ + """Return the string bounding box.""" return self.get_str_bbox_and_descent(s)[:4] def get_name_char(self, c, isord=False): - """ - Get the name of the character, i.e., ';' is 'semicolon' - """ + """Get the name of the character, i.e., ';' is 'semicolon'.""" if not isord: c = ord(c) - wx, name, bbox = self._metrics[c] - return name + return self._metrics[c].name def get_width_char(self, c, isord=False): """ - Get the width of the character from the character metric WX - field + Get the width of the character from the character metric WX field. """ if not isord: c = ord(c) - wx, name, bbox = self._metrics[c] - return wx + return self._metrics[c].width def get_width_from_char_name(self, name): - """ - Get the width of the character from a type1 character name - """ - wx, bbox = self._metrics_by_name[name] - return wx + """Get the width of the character from a type1 character name.""" + return self._metrics_by_name[name].width def get_height_char(self, c, isord=False): - """ - Get the height of character *c* from the bounding box. This - is the ink height (space is 0) - """ + """Get the bounding box (ink) height of character *c* (space is 0).""" if not isord: c = ord(c) - wx, name, bbox = self._metrics[c] - return bbox[-1] + return self._metrics[c].bbox[-1] def get_kern_dist(self, c1, c2): """ - Return the kerning pair distance (possibly 0) for chars *c1* - and *c2* + Return the kerning pair distance (possibly 0) for chars *c1* and *c2*. """ name1, name2 = self.get_name_char(c1), self.get_name_char(c2) return self.get_kern_dist_from_name(name1, name2) @@ -481,23 +495,23 @@ def get_kern_dist(self, c1, c2): def get_kern_dist_from_name(self, name1, name2): """ Return the kerning pair distance (possibly 0) for chars - *name1* and *name2* + *name1* and *name2*. """ return self._kern.get((name1, name2), 0) def get_fontname(self): - "Return the font name, e.g., 'Times-Roman'" + """Return the font name, e.g., 'Times-Roman'.""" return self._header[b'FontName'] def get_fullname(self): - "Return the font full name, e.g., 'Times-Roman'" + """Return the font full name, e.g., 'Times-Roman'.""" name = self._header.get(b'FullName') if name is None: # use FontName as a substitute name = self._header[b'FontName'] return name def get_familyname(self): - "Return the font family name, e.g., 'Times'" + """Return the font family name, e.g., 'Times'.""" name = self._header.get(b'FamilyName') if name is not None: return name @@ -510,26 +524,27 @@ def get_familyname(self): @property def family_name(self): + """The font family name, e.g., 'Times'.""" return self.get_familyname() def get_weight(self): - "Return the font weight, e.g., 'Bold' or 'Roman'" + """Return the font weight, e.g., 'Bold' or 'Roman'.""" return self._header[b'Weight'] def get_angle(self): - "Return the fontangle as float" + """Return the fontangle as float.""" return self._header[b'ItalicAngle'] def get_capheight(self): - "Return the cap height as float" + """Return the cap height as float.""" return self._header[b'CapHeight'] def get_xheight(self): - "Return the xheight as float" + """Return the xheight as float.""" return self._header[b'XHeight'] def get_underline_thickness(self): - "Return the underline thickness as float" + """Return the underline thickness as float.""" return self._header[b'UnderlineThickness'] def get_horizontal_stem_width(self): diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index e2e6f51e706f..2a85d23bb724 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -1,6 +1,4 @@ # TODO: -# * Loop Delay is broken on GTKAgg. This is because source_remove() is not -# working as we want. PyGTK bug? # * Documentation -- this will need a new section of the User's Guide. # Both for Animations and just timers. # - Also need to update http://www.scipy.org/Cookbook/Matplotlib/Animations @@ -17,35 +15,28 @@ # * Movies # * Can blit be enabled for movies? # * Need to consider event sources to allow clicking through multiple figures -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange, zip import abc +import base64 import contextlib from io import BytesIO import itertools import logging import os +from pathlib import Path import platform +import shutil +import subprocess import sys -import tempfile +from tempfile import TemporaryDirectory import uuid import numpy as np from matplotlib._animation_data import (DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE) -from matplotlib.compat import subprocess from matplotlib import cbook, rcParams, rcParamsDefault, rc_context -if six.PY2: - from base64 import encodestring as encodebytes -else: - from base64 import encodebytes - _log = logging.getLogger(__name__) @@ -176,7 +167,7 @@ def __getitem__(self, name): writers = MovieWriterRegistry() -class AbstractMovieWriter(six.with_metaclass(abc.ABCMeta)): +class AbstractMovieWriter(abc.ABC): ''' Abstract base class for writing movies. Fundamentally, what a MovieWriter does is provide is a way to grab frames by calling grab_frame(). @@ -385,8 +376,7 @@ def grab_frame(self, **savefig_kwargs): dpi=self.dpi, **savefig_kwargs) except (RuntimeError, IOError) as e: out, err = self._proc.communicate() - _log.info('MovieWriter -- Error ' - 'running proc:\n%s\n%s' % (out, err)) + _log.info('MovieWriter -- Error running proc:\n%s\n%s', out, err) raise IOError('Error saving animation to file (cause: {0}) ' 'Stdout: {1} StdError: {2}. It may help to re-run ' 'with logging level set to ' @@ -419,27 +409,9 @@ def bin_path(cls): @classmethod def isAvailable(cls): ''' - Check to see if a MovieWriter subclass is actually available by - running the commandline tool. + Check to see if a MovieWriter subclass is actually available. ''' - bin_path = cls.bin_path() - if not bin_path: - return False - try: - p = subprocess.Popen( - bin_path, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - creationflags=subprocess_creation_flags) - return cls._handle_subprocess(p) - except OSError: - return False - - @classmethod - def _handle_subprocess(cls, process): - process.communicate() - return True + return shutil.which(cls.bin_path()) is not None class FileMovieWriter(MovieWriter): @@ -539,8 +511,7 @@ def grab_frame(self, **savefig_kwargs): except RuntimeError: out, err = self._proc.communicate() - _log.info('MovieWriter -- Error ' - 'running proc:\n%s\n%s' % (out, err)) + _log.info('MovieWriter -- Error running proc:\n%s\n%s', out, err) raise def finish(self): @@ -586,7 +557,7 @@ def isAvailable(cls): def __init__(self, *args, **kwargs): if kwargs.get("extra_args") is None: kwargs["extra_args"] = () - super(PillowWriter, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def setup(self, fig, outfile, dpi=None): self._frames = [] @@ -639,19 +610,20 @@ def output_args(self): args.extend(['-b', '%dk' % self.bitrate]) if self.extra_args: args.extend(self.extra_args) - for k, v in six.iteritems(self.metadata): + for k, v in self.metadata.items(): args.extend(['-metadata', '%s=%s' % (k, v)]) return args + ['-y', self.outfile] @classmethod - def _handle_subprocess(cls, process): - _, err = process.communicate() - # Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use - # NOTE : when removed, remove the same method in AVConvBase. - if 'Libav' in err.decode(): - return False - return True + def isAvailable(cls): + return ( + super().isAvailable() + # Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use. + # NOTE: when removed, remove the same method in AVConvBase. + and b'LibAv' not in subprocess.run( + [cls.bin_path()], creationflags=subprocess_creation_flags, + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE).stderr) # Combine FFMpeg options with pipe-based writing @@ -671,7 +643,7 @@ def _args(self): # Logging is quieted because subprocess.PIPE has limited buffer size. # If you have a lot of frames in your animation and set logging to # DEBUG, you will have a buffer overrun. - if (_log.getEffectiveLevel() > logging.DEBUG): + if _log.getEffectiveLevel() > logging.DEBUG: args += ['-loglevel', 'quiet'] args += ['-i', 'pipe:'] + self.output_args return args @@ -697,8 +669,7 @@ def _args(self): '-vframes', str(self._frame_counter)] + self.output_args -# Base class of avconv information. AVConv has identical arguments to -# FFMpeg +# Base class of avconv information. AVConv has identical arguments to FFMpeg. class AVConvBase(FFMpegBase): '''Mixin class for avconv output. @@ -710,9 +681,7 @@ class AVConvBase(FFMpegBase): args_key = 'animation.avconv_args' # NOTE : should be removed when the same method is removed in FFMpegBase. - @classmethod - def _handle_subprocess(cls, process): - return MovieWriter._handle_subprocess(process) + isAvailable = classmethod(MovieWriter.isAvailable.__func__) # Combine AVConv options with pipe-based writing @@ -758,18 +727,25 @@ def output_args(self): def _init_from_registry(cls): if sys.platform != 'win32' or rcParams[cls.exec_key] != 'convert': return - from six.moves import winreg + import winreg for flag in (0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY): try: hkey = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, - 'Software\\Imagemagick\\Current', + r'Software\Imagemagick\Current', 0, winreg.KEY_QUERY_VALUE | flag) binpath = winreg.QueryValueEx(hkey, 'BinPath')[0] winreg.CloseKey(hkey) - binpath += '\\convert.exe' break except Exception: binpath = '' + if binpath: + for exe in ('convert.exe', 'magick.exe'): + path = os.path.join(binpath, exe) + if os.path.exists(path): + binpath = path + break + else: + binpath = '' rcParams[cls.exec_key] = rcParamsDefault[cls.exec_key] = binpath @classmethod @@ -783,9 +759,7 @@ def isAvailable(cls): bin_path = cls.bin_path() if bin_path == "convert": cls._init_from_registry() - return super(ImageMagickBase, cls).isAvailable() - -ImageMagickBase._init_from_registry() + return super().isAvailable() # Note: the base classes need to be in that order to get @@ -875,17 +849,17 @@ def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, self.default_mode = 'loop' _log.warning("unrecognized default_mode: using 'loop'") - self._saved_frames = [] - self._total_bytes = 0 - self._hit_limit = False - super(HTMLWriter, self).__init__(fps, codec, bitrate, - extra_args, metadata) + super().__init__(fps, codec, bitrate, extra_args, metadata) def setup(self, fig, outfile, dpi, frame_dir=None): root, ext = os.path.splitext(outfile) if ext not in ['.html', '.htm']: raise ValueError("outfile must be *.htm or *.html") + self._saved_frames = [] + self._total_bytes = 0 + self._hit_limit = False + if not self.embed_frames: if frame_dir is None: frame_dir = root + '_frames' @@ -895,19 +869,17 @@ def setup(self, fig, outfile, dpi, frame_dir=None): else: frame_prefix = None - super(HTMLWriter, self).setup(fig, outfile, dpi, - frame_prefix, clear_temp=False) + super().setup(fig, outfile, dpi, frame_prefix, clear_temp=False) def grab_frame(self, **savefig_kwargs): if self.embed_frames: # Just stop processing if we hit the limit if self._hit_limit: return - suffix = '.' + self.frame_format f = BytesIO() self.fig.savefig(f, format=self.frame_format, dpi=self.dpi, **savefig_kwargs) - imgdata64 = encodebytes(f.getvalue()).decode('ascii') + imgdata64 = base64.encodebytes(f.getvalue()).decode('ascii') self._total_bytes += len(imgdata64) if self._total_bytes >= self._bytes_limit: _log.warning( @@ -920,7 +892,7 @@ def grab_frame(self, **savefig_kwargs): else: self._saved_frames.append(imgdata64) else: - return super(HTMLWriter, self).grab_frame(**savefig_kwargs) + return super().grab_frame(**savefig_kwargs) def _run(self): # make a duck-typed subprocess stand in @@ -937,11 +909,12 @@ def communicate(self): if self.embed_frames: fill_frames = _embedded_frames(self._saved_frames, self.frame_format) + Nframes = len(self._saved_frames) else: # temp names is filled by FileMovieWriter fill_frames = _included_frames(self._temp_names, self.frame_format) - + Nframes = len(self._temp_names) mode_dict = dict(once_checked='', loop_checked='', reflect_checked='') @@ -952,7 +925,7 @@ def communicate(self): with open(self.outfile, 'w') as of: of.write(JS_INCLUDE) of.write(DISPLAY_TEMPLATE.format(id=uuid.uuid4().hex, - Nframes=len(self._temp_names), + Nframes=Nframes, fill_frames=fill_frames, interval=interval, **mode_dict)) @@ -1108,9 +1081,9 @@ class to use, such as 'ffmpeg'. If ``None``, defaults to # to use if writer is None: writer = rcParams['animation.writer'] - elif (not isinstance(writer, six.string_types) and - any(arg is not None - for arg in (fps, codec, bitrate, extra_args, metadata))): + elif (not isinstance(writer, str) and + any(arg is not None + for arg in (fps, codec, bitrate, extra_args, metadata))): raise RuntimeError('Passing in values for arguments ' 'fps, codec, bitrate, extra_args, or metadata ' 'is not supported when writer is an existing ' @@ -1153,13 +1126,14 @@ class to use, such as 'ffmpeg'. If ``None``, defaults to # If we have the name of a writer, instantiate an instance of the # registered class. - if isinstance(writer, six.string_types): + if isinstance(writer, str): if writer in writers.avail: writer = writers[writer](fps, codec, bitrate, extra_args=extra_args, metadata=metadata) else: - _log.warning("MovieWriter %s unavailable.", writer) + _log.warning("MovieWriter {} unavailable. Trying to use {} " + "instead.".format(writer, writers.list()[0])) try: writer = writers[writers.list()[0]](fps, codec, bitrate, @@ -1283,7 +1257,7 @@ def _blit_clear(self, artists, bg_cache): # Get a list of the axes that need clearing from the artists that # have been drawn. Grab the appropriate saved background from the # cache and restore. - axes = set(a.axes for a in artists) + axes = {a.axes for a in artists} for a in axes: if a in bg_cache: a.figure.canvas.restore_region(bg_cache[a]) @@ -1340,35 +1314,31 @@ def to_html5_video(self, embed_limit=None): # Convert from MB to bytes embed_limit *= 1024 * 1024 - # First write the video to a tempfile. Set delete to False - # so we can re-open to read binary data. - with tempfile.NamedTemporaryFile(suffix='.m4v', - delete=False) as f: + # Can't open a NamedTemporaryFile twice on Windows, so use a + # TemporaryDirectory instead. + with TemporaryDirectory() as tmpdir: + path = Path(tmpdir, "temp.m4v") # We create a writer manually so that we can get the # appropriate size for the tag Writer = writers[rcParams['animation.writer']] writer = Writer(codec='h264', bitrate=rcParams['animation.bitrate'], fps=1000. / self._interval) - self.save(f.name, writer=writer) - - # Now open and base64 encode - with open(f.name, 'rb') as video: - vid64 = encodebytes(video.read()) - vid_len = len(vid64) - if vid_len >= embed_limit: - _log.warning( - "Animation movie is %s bytes, exceeding the limit of " - "%s. If you're sure you want a large animation " - "embedded, set the animation.embed_limit rc parameter " - "to a larger value (in MB).", vid_len, embed_limit) - else: - self._base64_video = vid64.decode('ascii') - self._video_size = 'width="{}" height="{}"'.format( - *writer.frame_size) - - # Now we can remove - os.remove(f.name) + self.save(str(path), writer=writer) + # Now open and base64 encode. + vid64 = base64.encodebytes(path.read_bytes()) + + vid_len = len(vid64) + if vid_len >= embed_limit: + _log.warning( + "Animation movie is %s bytes, exceeding the limit of %s. " + "If you're sure you want a large animation embedded, set " + "the animation.embed_limit rc parameter to a larger value " + "(in MB).", vid_len, embed_limit) + else: + self._base64_video = vid64.decode('ascii') + self._video_size = 'width="{}" height="{}"'.format( + *writer.frame_size) # If we exceeded the size, this attribute won't exist if hasattr(self, '_base64_video'): @@ -1396,25 +1366,18 @@ def to_jshtml(self, fps=None, embed_frames=True, default_mode=None): if default_mode is None: default_mode = 'loop' if self.repeat else 'once' - if hasattr(self, "_html_representation"): - return self._html_representation - else: - # Can't open a second time while opened on windows. So we avoid - # deleting when closed, and delete manually later. - with tempfile.NamedTemporaryFile(suffix='.html', - delete=False) as f: - self.save(f.name, writer=HTMLWriter(fps=fps, - embed_frames=embed_frames, - default_mode=default_mode)) - # Re-open and get content - with open(f.name) as fobj: - html = fobj.read() - - # Now we can delete - os.remove(f.name) - - self._html_representation = html - return html + if not hasattr(self, "_html_representation"): + # Can't open a NamedTemporaryFile twice on Windows, so use a + # TemporaryDirectory instead. + with TemporaryDirectory() as tmpdir: + path = Path(tmpdir, "temp.html") + writer = HTMLWriter(fps=fps, + embed_frames=embed_frames, + default_mode=default_mode) + self.save(str(path), writer=writer) + self._html_representation = path.read_text() + + return self._html_representation def _repr_html_(self): '''IPython display hook for rendering.''' @@ -1653,8 +1616,10 @@ def init_func() -> iterable_of_artists: of frames is completed. Defaults to ``True``. blit : bool, optional - Controls whether blitting is used to optimize drawing. Defaults - to ``False``. + Controls whether blitting is used to optimize drawing. Note: when using + blitting any animated artists will be drawn according to their zorder. + However, they will be drawn on top of any previous artists, regardless + of their zorder. Defaults to ``False``. ''' def __init__(self, fig, func, frames=None, init_func=None, fargs=None, @@ -1683,7 +1648,7 @@ def __init__(self, fig, func, frames=None, init_func=None, fargs=None, if hasattr(frames, '__len__'): self.save_count = len(frames) else: - self._iter_gen = lambda: iter(xrange(frames)) + self._iter_gen = lambda: iter(range(frames)) self.save_count = frames if self.save_count is None: @@ -1774,5 +1739,8 @@ def _draw_frame(self, framedata): if self._drawn_artists is None: raise RuntimeError('The animation function must return a ' 'sequence of Artist objects.') + self._drawn_artists = sorted(self._drawn_artists, + key=lambda x: x.get_zorder()) + for a in self._drawn_artists: a.set_animated(self._blit) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 8dc1034bc707..d68334a0c31f 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from collections import OrderedDict, namedtuple from functools import wraps import inspect @@ -119,12 +114,12 @@ def __init__(self): self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] self._sticky_edges = _XYPair([], []) + self._in_layout = True def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load # (by the axes) if the artist lives on an axes. - d['_remove_method'] = None d['stale_callback'] = None return d @@ -154,7 +149,7 @@ def remove(self): _ax_flag = False if hasattr(self, 'axes') and self.axes: # remove from the mouse hit list - self.axes.mouseover_set.discard(self) + self.axes._mouseover_set.discard(self) # mark the axes as stale self.axes.stale = True # decouple the artist from the axes @@ -257,6 +252,33 @@ def get_window_extent(self, renderer): """ return Bbox([[0, 0], [0, 0]]) + def get_tightbbox(self, renderer): + """ + Like `Artist.get_window_extent`, but includes any clipping. + + Parameters + ---------- + renderer : `.RendererBase` instance + renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + Returns + ------- + bbox : `.BboxBase` + containing the bounding box (in figure pixel co-ordinates). + """ + + bbox = self.get_window_extent(renderer) + if self.get_clip_on(): + clip_box = self.get_clip_box() + if clip_box is not None: + bbox = Bbox.intersection(bbox, clip_box) + clip_path = self.get_clip_path() + if clip_path is not None and bbox is not None: + clip_path = clip_path.get_fully_transformed_path() + bbox = Bbox.intersection(bbox, clip_path.get_extents()) + return bbox + def add_callback(self, func): """ Adds a callback function that will be called whenever one of @@ -290,7 +312,7 @@ def pchanged(self): Fire an event when property changed, calling all of the registered callbacks. """ - for oid, func in six.iteritems(self._propobservers): + for oid, func in self._propobservers.items(): func(self) def is_transform_set(self): @@ -307,7 +329,6 @@ def set_transform(self, t): Parameters ---------- t : `.Transform` - .. ACCEPTS: `.Transform` """ self._transform = t self._transformSet = True @@ -379,7 +400,6 @@ def set_contains(self, picker): Parameters ---------- picker : callable - .. ACCEPTS: a callable function """ self._contains = picker @@ -459,7 +479,6 @@ def set_picker(self, picker): Parameters ---------- picker : None or bool or float or callable - .. ACCEPTS: [None | bool | float | callable] """ self._picker = picker @@ -483,7 +502,6 @@ def set_url(self, url): Parameters ---------- url : str - .. ACCEPTS: a url string """ self._url = url @@ -498,7 +516,6 @@ def set_gid(self, gid): Parameters ---------- gid : str - .. ACCEPTS: an id string """ self._gid = gid @@ -536,7 +553,6 @@ def set_snap(self, snap): Parameters ---------- snap : bool or None - .. ACCEPTS: bool or None """ self._snap = snap self.stale = True @@ -549,17 +565,17 @@ def get_sketch_params(self): ------- sketch_params : tuple or `None` - A 3-tuple with the following elements: + A 3-tuple with the following elements: - * `scale`: The amplitude of the wiggle perpendicular to the - source line. + * `scale`: The amplitude of the wiggle perpendicular to the + source line. - * `length`: The length of the wiggle along the line. + * `length`: The length of the wiggle along the line. - * `randomness`: The scale factor by which the length is - shrunken or expanded. + * `randomness`: The scale factor by which the length is + shrunken or expanded. - May return `None` if no sketch parameters were set. + May return `None` if no sketch parameters were set. """ return self._sketch @@ -597,7 +613,6 @@ def set_path_effects(self, path_effects): Parameters ---------- path_effects : `.AbstractPathEffect` - .. ACCEPTS: `.AbstractPathEffect` """ self._path_effects = path_effects self.stale = True @@ -616,7 +631,6 @@ def set_figure(self, fig): Parameters ---------- fig : `.Figure` - .. ACCEPTS: a `.Figure` instance """ # if this is a no-op just return if self.figure is fig: @@ -641,7 +655,6 @@ def set_clip_box(self, clipbox): Parameters ---------- clipbox : `.Bbox` - .. ACCEPTS: a `.Bbox` instance """ self.clipbox = clipbox self.pchanged() @@ -716,6 +729,17 @@ def get_animated(self): "Return the artist's animated state" return self._animated + def get_in_layout(self): + """ + Return boolean flag, ``True`` if artist is included in layout + calculations. + + E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, + `.Figure.tight_layout()`, and + ``fig.savefig(fname, bbox_inches='tight')``. + """ + return self._in_layout + def get_clip_on(self): 'Return whether artist uses clipping' return self._clipon @@ -748,7 +772,6 @@ def set_clip_on(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ self._clipon = b # This may result in the callbacks being hit twice, but ensures they @@ -779,7 +802,6 @@ def set_rasterized(self, rasterized): Parameters ---------- rasterized : bool or None - .. ACCEPTS: bool or None """ if rasterized and not hasattr(self.draw, "_supports_rasterization"): warnings.warn("Rasterization of '%s' will be ignored" % self) @@ -813,13 +835,11 @@ def draw(self, renderer, *args, **kwargs): def set_alpha(self, alpha): """ - Set the alpha value used for blending - not supported on - all backends. + Set the alpha value used for blending - not supported on all backends. Parameters ---------- alpha : float - .. ACCEPTS: float (0.0 transparent through 1.0 opaque) """ self._alpha = alpha self.pchanged() @@ -832,7 +852,6 @@ def set_visible(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ self._visible = b self.pchanged() @@ -845,12 +864,24 @@ def set_animated(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ if self._animated != b: self._animated = b self.pchanged() + def set_in_layout(self, in_layout): + """ + Set if artist is to be included in layout calculations, + E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, + `.Figure.tight_layout()`, and + ``fig.savefig(fname, bbox_inches='tight')``. + + Parameters + ---------- + in_layout : bool + """ + self._in_layout = in_layout + def update(self, props): """ Update this artist's properties from the dictionary *prop*. @@ -881,13 +912,8 @@ def _update_property(self, k, v): raise AttributeError('Unknown property %s' % k) return func(v) - store = self.eventson - self.eventson = False - try: - ret = [_update_property(self, k, v) - for k, v in props.items()] - finally: - self.eventson = store + with cbook._setattr_cm(self, eventson=False): + ret = [_update_property(self, k, v) for k, v in props.items()] if len(ret): self.pchanged() @@ -905,13 +931,10 @@ def set_label(self, s): Parameters ---------- s : object - *s* will be converted to a string by calling `str` (`unicode` on - Py2). - - .. ACCEPTS: object + *s* will be converted to a string by calling `str`. """ if s is not None: - self._label = six.text_type(s) + self._label = str(s) else: self._label = None self.pchanged() @@ -929,7 +952,6 @@ def set_zorder(self, level): Parameters ---------- level : float - .. ACCEPTS: float """ if level is None: level = self.__class__.zorder @@ -1043,8 +1065,9 @@ def format_cursor_data(self, data): data[0] except (TypeError, IndexError): data = [data] - return ', '.join('{:0.3g}'.format(item) for item in data if - isinstance(item, (np.floating, np.integer, int, float))) + data_str = ', '.join('{:0.3g}'.format(item) for item in data if + isinstance(item, (np.floating, np.integer, int, float))) + return "[" + data_str + "]" @property def mouseover(self): @@ -1057,17 +1080,17 @@ def mouseover(self, val): ax = self.axes if ax: if val: - ax.mouseover_set.add(self) + ax._mouseover_set.add(self) else: - ax.mouseover_set.discard(self) + ax._mouseover_set.discard(self) class ArtistInspector(object): """ - A helper class to inspect an :class:`~matplotlib.artist.Artist` - and return information about it's settable properties and their - current values. + A helper class to inspect an `~matplotlib.artist.Artist` and return + information about its settable properties and their current values. """ + def __init__(self, o): r""" Initialize the artist inspector with an `Artist` or an iterable of @@ -1090,15 +1113,14 @@ def __init__(self, o): def get_aliases(self): """ - Get a dict mapping *fullname* -> *alias* for each *alias* in - the :class:`~matplotlib.artist.ArtistInspector`. + Get a dict mapping property fullnames to sets of aliases for each alias + in the :class:`~matplotlib.artist.ArtistInspector`. e.g., for lines:: - {'markerfacecolor': 'mfc', - 'linewidth' : 'lw', + {'markerfacecolor': {'mfc'}, + 'linewidth' : {'lw'}, } - """ names = [name for name in dir(self.o) if name.startswith(('set_', 'get_')) @@ -1108,9 +1130,9 @@ def get_aliases(self): func = getattr(self.o, name) if not self.is_alias(func): continue - docstring = func.__doc__ - fullname = docstring[10:] - aliases.setdefault(fullname[4:], {})[name[4:]] = None + propname = re.search("`({}.*)`".format(name[:4]), # get_.*/set_.* + func.__doc__).group(1) + aliases.setdefault(propname, set()).add(name[4:]) return aliases _get_valid_values_regex = re.compile( @@ -1144,6 +1166,14 @@ def get_valid_values(self, attr): match = self._get_valid_values_regex.search(docstring) if match is not None: return re.sub("\n *", " ", match.group(1)) + + # Much faster than list(inspect.signature(func).parameters)[1], + # although barely relevant wrt. matplotlib's total import time. + param_name = func.__code__.co_varnames[1] + match = re.search("(?m)^ *{} : (.+)".format(param_name), docstring) + if match: + return match.group(1) + return 'unknown' def _get_setters_and_targets(self): @@ -1159,10 +1189,7 @@ def _get_setters_and_targets(self): func = getattr(self.o, name) if not callable(func): continue - if six.PY2: - nargs = len(inspect.getargspec(func)[0]) - else: - nargs = len(inspect.getfullargspec(func)[0]) + nargs = len(inspect.getfullargspec(func).args) if nargs < 2 or self.is_alias(func): continue source_class = self.o.__module__ + "." + self.o.__name__ @@ -1170,9 +1197,23 @@ def _get_setters_and_targets(self): if name in cls.__dict__: source_class = cls.__module__ + "." + cls.__name__ break + source_class = self._replace_path(source_class) setters.append((name[4:], source_class + "." + name)) return setters + def _replace_path(self, source_class): + """ + Changes the full path to the public API path that is used + in sphinx. This is needed for links to work. + """ + + replace_dict = {'_base._AxesBase': 'Axes', + '_axes.Axes': 'Axes'} + + for key, value in replace_dict.items(): + source_class = source_class.replace(key, value) + return source_class + def get_setters(self): """ Get the attribute strings with setters for object. e.g., for a line, @@ -1332,7 +1373,7 @@ def pprint_getters(self): """ lines = [] - for name, val in sorted(six.iteritems(self.properties())): + for name, val in sorted(self.properties().items()): if getattr(val, 'shape', ()) != () and len(val) > 6: s = str(val[:6]) + '...' else: @@ -1462,21 +1503,37 @@ def setp(obj, *args, **kwargs): raise ValueError('The set args must be string, value pairs') # put args into ordereddict to maintain order - funcvals = OrderedDict() - for i in range(0, len(args) - 1, 2): - funcvals[args[i]] = args[i + 1] - - ret = [o.update(funcvals) for o in objs] - ret.extend([o.set(**kwargs) for o in objs]) - return [x for x in cbook.flatten(ret)] - - -def kwdoc(a): + funcvals = OrderedDict((k, v) for k, v in zip(args[::2], args[1::2])) + ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs] + return list(cbook.flatten(ret)) + + +def kwdoc(artist): + r""" + Inspect an `~matplotlib.artist.Artist` class and return + information about its settable properties and their current values. + + It use the class `.ArtistInspector`. + + Parameters + ---------- + artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s + + Returns + ------- + string + Returns a string with a list or rst table with the settable properties + of the *artist*. The formating depends on the value of + :rc:`docstring.hardcopy`. False result in a list that is intended for + easy reading as a docstring and True result in a rst table intended + for rendering the documentation with sphinx. + """ hardcopy = matplotlib.rcParams['docstring.hardcopy'] if hardcopy: - return '\n'.join(ArtistInspector(a).pprint_setters_rest( + return '\n'.join(ArtistInspector(artist).pprint_setters_rest( leadingspace=4)) else: - return '\n'.join(ArtistInspector(a).pprint_setters(leadingspace=2)) + return '\n'.join(ArtistInspector(artist).pprint_setters( + leadingspace=2)) docstring.interpd.update(Artist=kwdoc(Artist)) diff --git a/lib/matplotlib/axes/__init__.py b/lib/matplotlib/axes/__init__.py index 82c543891941..4dd998c0d43d 100644 --- a/lib/matplotlib/axes/__init__.py +++ b/lib/matplotlib/axes/__init__.py @@ -1,5 +1,2 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - from ._subplots import * from ._axes import * diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 210d02e58ccf..bb749e1c9e85 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1,13 +1,8 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange, zip, zip_longest - import functools import itertools import logging import math +from numbers import Number import warnings import numpy as np @@ -39,8 +34,8 @@ import matplotlib.transforms as mtransforms import matplotlib.tri as mtri from matplotlib.cbook import ( - _backports, mplDeprecation, warn_deprecated, - STEP_LOOKUP_MAP, iterable, safe_first_element) + MatplotlibDeprecationWarning, warn_deprecated, STEP_LOOKUP_MAP, iterable, + safe_first_element) from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer from matplotlib.axes._base import _AxesBase, _process_plot_format @@ -48,16 +43,17 @@ rcParams = matplotlib.rcParams -_alias_map = {'color': ['c'], - 'linewidth': ['lw'], - 'linestyle': ['ls'], - 'facecolor': ['fc'], - 'edgecolor': ['ec'], - 'markerfacecolor': ['mfc'], - 'markeredgecolor': ['mec'], - 'markeredgewidth': ['mew'], - 'markersize': ['ms'], - } + +def _has_item(data, name): + """Return whether *data* can be item-accessed with *name*. + + This supports data with a dict-like interface (`in` checks item + availability) and with numpy.arrays. + """ + try: + return data.dtype.names is not None and name in data.dtype.names + except AttributeError: # not a numpy array + return name in data def _plot_args_replacer(args, data): @@ -65,9 +61,7 @@ def _plot_args_replacer(args, data): return ["y"] elif len(args) == 2: # this can be two cases: x,y or y,c - if not args[1] in data: - # this is not in data, so just assume that it is something which - # will not get replaced (color spec or array like). + if not _has_item(data, args[1]): return ["y", "c"] # it's data, but could be a color code like 'ro' or 'b--' # -> warn the user in that case... @@ -76,11 +70,11 @@ def _plot_args_replacer(args, data): except ValueError: pass else: - warnings.warn( + cbook._warn_external( "Second argument {!r} is ambiguous: could be a color spec but " "is in data; using as data. Either rename the entry in data " "or use three arguments to plot.".format(args[1]), - RuntimeWarning, stacklevel=3) + RuntimeWarning) return ["x", "y"] elif len(args) == 3: return ["x", "y", "c"] @@ -90,9 +84,37 @@ def _plot_args_replacer(args, data): "multiple plotting calls instead.") +def _make_inset_locator(bounds, trans, parent): + """ + Helper function to locate inset axes, used in + `.Axes.inset_axes`. + + A locator gets used in `Axes.set_aspect` to override the default + locations... It is a function that takes an axes object and + a renderer and tells `set_aspect` where it is to be placed. + + Here *rect* is a rectangle [l, b, w, h] that specifies the + location for the axes in the transform given by *trans* on the + *parent*. + """ + _bounds = mtransforms.Bbox.from_bounds(*bounds) + _trans = trans + _parent = parent + + def inset_locator(ax, renderer): + bbox = _bounds + bb = mtransforms.TransformedBbox(bbox, _trans) + tr = _parent.figure.transFigure.inverted() + bb = mtransforms.TransformedBbox(bb, tr) + return bb + + return inset_locator + + # The axes module contains all the wrappers to plotting functions. # All the other methods should go in the _AxesBase class. + class Axes(_AxesBase): """ The :class:`Axes` contains most of the figure elements: @@ -285,7 +307,7 @@ def get_legend_handles_labels(self, legend_handler_map=None): @docstring.dedent_interpd def legend(self, *args, **kwargs): """ - Places a legend on the axes. + Place a legend on the axes. Call signatures:: @@ -364,172 +386,7 @@ def legend(self, *args, **kwargs): Other Parameters ---------------- - loc : int or string or pair of floats, default: 'upper right' - The location of the legend. Possible codes are: - - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= - - - Alternatively can be a 2-tuple giving ``x, y`` of the lower-left - corner of the legend in axes coordinates (in which case - ``bbox_to_anchor`` will be ignored). - - bbox_to_anchor : `.BboxBase` or pair of floats - Specify any arbitrary location for the legend in `bbox_transform` - coordinates (default Axes coordinates). - - For example, to put the legend's upper right hand corner in the - center of the axes the following keywords can be used:: - - loc='upper right', bbox_to_anchor=(0.5, 0.5) - - ncol : integer - The number of columns that the legend has. Default is 1. - - prop : None or :class:`matplotlib.font_manager.FontProperties` or dict - The font properties of the legend. If None (default), the current - :data:`matplotlib.rcParams` will be used. - - fontsize : int or float or {'xx-small', 'x-small', 'small', 'medium', \ -'large', 'x-large', 'xx-large'} - Controls the font size of the legend. If the value is numeric the - size will be the absolute font size in points. String values are - relative to the current default font size. This argument is only - used if `prop` is not specified. - - numpoints : None or int - The number of marker points in the legend when creating a legend - entry for a `.Line2D` (line). - Default is ``None``, which will take the value from - :rc:`legend.numpoints`. - - scatterpoints : None or int - The number of marker points in the legend when creating - a legend entry for a `.PathCollection` (scatter plot). - Default is ``None``, which will take the value from - :rc:`legend.scatterpoints`. - - scatteryoffsets : iterable of floats - The vertical offset (relative to the font size) for the markers - created for a scatter plot legend entry. 0.0 is at the base the - legend text, and 1.0 is at the top. To draw all markers at the - same height, set to ``[0.5]``. Default is ``[0.375, 0.5, 0.3125]``. - - markerscale : None or int or float - The relative size of legend markers compared with the originally - drawn ones. - Default is ``None``, which will take the value from - :rc:`legend.markerscale`. - - markerfirst : bool - If *True*, legend marker is placed to the left of the legend label. - If *False*, legend marker is placed to the right of the legend - label. - Default is *True*. - - frameon : None or bool - Control whether the legend should be drawn on a patch - (frame). - Default is ``None``, which will take the value from - :rc:`legend.frameon`. - - fancybox : None or bool - Control whether round edges should be enabled around the - :class:`~matplotlib.patches.FancyBboxPatch` which makes up the - legend's background. - Default is ``None``, which will take the value from - :rc:`legend.fancybox`. - - shadow : None or bool - Control whether to draw a shadow behind the legend. - Default is ``None``, which will take the value from - :rc:`legend.shadow`. - - framealpha : None or float - Control the alpha transparency of the legend's background. - Default is ``None``, which will take the value from - :rc:`legend.framealpha`. If shadow is activated and - *framealpha* is ``None``, the default value is ignored. - - facecolor : None or "inherit" or a color spec - Control the legend's background color. - Default is ``None``, which will take the value from - :rc:`legend.facecolor`. If ``"inherit"``, it will take - :rc:`axes.facecolor`. - - edgecolor : None or "inherit" or a color spec - Control the legend's background patch edge color. - Default is ``None``, which will take the value from - :rc:`legend.edgecolor` If ``"inherit"``, it will take - :rc:`axes.edgecolor`. - - mode : {"expand", None} - If `mode` is set to ``"expand"`` the legend will be horizontally - expanded to fill the axes area (or `bbox_to_anchor` if defines - the legend's size). - - bbox_transform : None or :class:`matplotlib.transforms.Transform` - The transform for the bounding box (`bbox_to_anchor`). For a value - of ``None`` (default) the Axes' - :data:`~matplotlib.axes.Axes.transAxes` transform will be used. - - title : str or None - The legend's title. Default is no title (``None``). - - borderpad : float or None - The fractional whitespace inside the legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderpad`. - - labelspacing : float or None - The vertical space between the legend entries. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.labelspacing`. - - handlelength : float or None - The length of the legend handles. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handlelength`. - - handletextpad : float or None - The pad between the legend handle and text. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handletextpad`. - - borderaxespad : float or None - The pad between the axes and legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderaxespad`. - - columnspacing : float or None - The spacing between columns. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.columnspacing`. - - handler_map : dict or None - The custom dictionary mapping instances or types to a legend - handler. This `handler_map` updates the default handler map - found at :func:`matplotlib.legend.Legend.get_legend_handler_map`. + %(_legend_kw_doc)s Returns ------- @@ -545,7 +402,7 @@ def legend(self, *args, **kwargs): Examples -------- - .. plot:: gallery/api/legend.py + .. plot:: gallery/text_labels_and_annotations/legend.py """ handles, labels, extra_args, kwargs = mlegend._parse_legend_args( @@ -555,9 +412,233 @@ def legend(self, *args, **kwargs): if len(extra_args): raise TypeError('legend only accepts two non-keyword arguments') self.legend_ = mlegend.Legend(self, handles, labels, **kwargs) - self.legend_._remove_method = lambda h: setattr(self, 'legend_', None) + self.legend_._remove_method = self._remove_legend return self.legend_ + def _remove_legend(self, legend): + self.legend_ = None + + def inset_axes(self, bounds, *, transform=None, zorder=5, + **kwargs): + """ + Add a child inset axes to this existing axes. + + Warnings + -------- + + This method is experimental as of 3.0, and the API may change. + + Parameters + ---------- + + bounds : [x0, y0, width, height] + Lower-left corner of inset axes, and its width and height. + + transform : `.Transform` + Defaults to `ax.transAxes`, i.e. the units of *rect* are in + axes-relative coordinates. + + zorder : number + Defaults to 5 (same as `.Axes.legend`). Adjust higher or lower + to change whether it is above or below data plotted on the + parent axes. + + **kwargs + + Other *kwargs* are passed on to the `axes.Axes` child axes. + + Returns + ------- + + Axes + The created `.axes.Axes` instance. + + Examples + -------- + + This example makes two inset axes, the first is in axes-relative + coordinates, and the second in data-coordinates:: + + fig, ax = plt.suplots() + ax.plot(range(10)) + axin1 = ax.inset_axes([0.8, 0.1, 0.15, 0.15]) + axin2 = ax.inset_axes( + [5, 7, 2.3, 2.3], transform=ax.transData) + + """ + if transform is None: + transform = self.transAxes + label = kwargs.pop('label', 'inset_axes') + + # This puts the rectangle into figure-relative coordinates. + inset_locator = _make_inset_locator(bounds, transform, self) + bb = inset_locator(None, None) + + inset_ax = Axes(self.figure, bb.bounds, zorder=zorder, + label=label, **kwargs) + + # this locator lets the axes move if in data coordinates. + # it gets called in `ax.apply_aspect() (of all places) + inset_ax.set_axes_locator(inset_locator) + + self.add_child_axes(inset_ax) + + return inset_ax + + def indicate_inset(self, bounds, inset_ax=None, *, transform=None, + facecolor='none', edgecolor='0.5', alpha=0.5, + zorder=4.99, **kwargs): + """ + Add an inset indicator to the axes. This is a rectangle on the plot + at the position indicated by *bounds* that optionally has lines that + connect the rectangle to an inset axes + (`.Axes.inset_axes`). + + Warnings + -------- + + This method is experimental as of 3.0, and the API may change. + + + Parameters + ---------- + + bounds : [x0, y0, width, height] + Lower-left corner of rectangle to be marked, and its width + and height. + + inset_ax : `.Axes` + An optional inset axes to draw connecting lines to. Two lines are + drawn connecting the indicator box to the inset axes on corners + chosen so as to not overlap with the indicator box. + + transform : `.Transform` + Transform for the rectangle co-ordinates. Defaults to + `ax.transAxes`, i.e. the units of *rect* are in axes-relative + coordinates. + + facecolor : Matplotlib color + Facecolor of the rectangle (default 'none'). + + edgecolor : Matplotlib color + Color of the rectangle and color of the connecting lines. Default + is '0.5'. + + alpha : number + Transparency of the rectangle and connector lines. Default is 0.5. + + zorder : number + Drawing order of the rectangle and connector lines. Default is 4.99 + (just below the default level of inset axes). + + **kwargs + Other *kwargs* are passed on to the rectangle patch. + + Returns + ------- + + rectangle_patch: `.Patches.Rectangle` + Rectangle artist. + + connector_lines: 4-tuple of `.Patches.ConnectionPatch` + One for each of four connector lines. Two are set with visibility + to *False*, but the user can set the visibility to True if the + automatic choice is not deemed correct. + + """ + + # to make the axes connectors work, we need to apply the aspect to + # the parent axes. + self.apply_aspect() + + if transform is None: + transform = self.transData + label = kwargs.pop('label', 'indicate_inset') + + xy = (bounds[0], bounds[1]) + rectpatch = mpatches.Rectangle(xy, bounds[2], bounds[3], + facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, + zorder=zorder, label=label, transform=transform, **kwargs) + self.add_patch(rectpatch) + + if inset_ax is not None: + # want to connect the indicator to the rect.... + + pos = inset_ax.get_position() # this is in fig-fraction. + coordsA = 'axes fraction' + connects = [] + xr = [bounds[0], bounds[0]+bounds[2]] + yr = [bounds[1], bounds[1]+bounds[3]] + for xc in range(2): + for yc in range(2): + xyA = (xc, yc) + xyB = (xr[xc], yr[yc]) + connects += [mpatches.ConnectionPatch(xyA, xyB, + 'axes fraction', 'data', + axesA=inset_ax, axesB=self, arrowstyle="-", + zorder=zorder, edgecolor=edgecolor, alpha=alpha)] + self.add_patch(connects[-1]) + # decide which two of the lines to keep visible.... + pos = inset_ax.get_position() + bboxins = pos.transformed(self.figure.transFigure) + rectbbox = mtransforms.Bbox.from_bounds( + *bounds).transformed(transform) + if rectbbox.x0 < bboxins.x0: + sig = 1 + else: + sig = -1 + if sig*rectbbox.y0 < sig*bboxins.y0: + connects[0].set_visible(False) + connects[3].set_visible(False) + else: + connects[1].set_visible(False) + connects[2].set_visible(False) + + return rectpatch, connects + + def indicate_inset_zoom(self, inset_ax, **kwargs): + """ + Add an inset indicator rectangle to the axes based on the axis + limits for an *inset_ax* and draw connectors between *inset_ax* + and the rectangle. + + Warnings + -------- + + This method is experimental as of 3.0, and the API may change. + + Parameters + ---------- + + inset_ax : `.Axes` + Inset axes to draw connecting lines to. Two lines are + drawn connecting the indicator box to the inset axes on corners + chosen so as to not overlap with the indicator box. + + **kwargs + Other *kwargs* are passed on to `.Axes.inset_rectangle` + + Returns + ------- + + rectangle_patch: `.Patches.Rectangle` + Rectangle artist. + + connector_lines: 4-tuple of `.Patches.ConnectionPatch` + One for each of four connector lines. Two are set with visibility + to *False*, but the user can set the visibility to True if the + automatic choice is not deemed correct. + + """ + + xlim = inset_ax.get_xlim() + ylim = inset_ax.get_ylim() + rect = [xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]] + rectpatch, connects = self.indicate_inset( + rect, inset_ax, **kwargs) + + return rectpatch, connects + def text(self, x, y, s, fontdict=None, withdash=False, **kwargs): """ Add text to the axes. @@ -643,8 +724,8 @@ def text(self, x, y, s, fontdict=None, withdash=False, **kwargs): return t @docstring.dedent_interpd - def annotate(self, *args, **kwargs): - a = mtext.Annotation(*args, **kwargs) + def annotate(self, text, xy, *args, **kwargs): + a = mtext.Annotation(text, xy, *args, **kwargs) a.set_transform(mtransforms.IdentityTransform()) if 'clip_on' in kwargs: a.set_clip_path(self.patch) @@ -857,7 +938,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): spans the yrange, regardless of the ylim settings, even if you change them, e.g., with the :meth:`set_ylim` command. That is, the vertical extent is in axes coords: 0=bottom, 0.5=middle, - 1.0=top but the y location is in data coordinates. + 1.0=top but the x location is in data coordinates. Parameters ---------- @@ -931,7 +1012,7 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', colors : array_like of colors, optional, default: 'k' - linestyles : ['solid' | 'dashed' | 'dashdot' | 'dotted'], optional + linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional label : string, optional, default: '' @@ -1009,7 +1090,7 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', colors : array_like of colors, optional, default: 'k' - linestyles : ['solid' | 'dashed' | 'dashdot' | 'dotted'], optional + linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional label : string, optional, default: '' @@ -1289,7 +1370,7 @@ def eventplot(self, positions, orientation='horizontal', lineoffsets=1, positional_parameter_names=_plot_args_replacer, label_namer=None) @docstring.dedent_interpd - def plot(self, *args, **kwargs): + def plot(self, *args, scalex=True, scaley=True, **kwargs): """ Plot y versus x as lines and/or markers. @@ -1310,12 +1391,12 @@ def plot(self, *args, **kwargs): >>> plot(y, 'r+') # ditto, but with red plusses You can use `.Line2D` properties as keyword arguments for more - control on the appearance. Line properties and *fmt* can be mixed. + control on the appearance. Line properties and *fmt* can be mixed. The following two calls yield identical results: >>> plot(x, y, 'go--', linewidth=2, markersize=12) >>> plot(x, y, color='green', marker='o', linestyle='dashed', - linewidth=2, markersize=12) + ... linewidth=2, markersize=12) When conflicting with *fmt*, keyword arguments take precedence. @@ -1516,14 +1597,9 @@ def plot(self, *args, **kwargs): 'k^:' # black triangle_up markers connected by a dotted line """ - scalex = kwargs.pop('scalex', True) - scaley = kwargs.pop('scaley', True) - - if not self._hold: - self.cla() lines = [] - kwargs = cbook.normalize_kwargs(kwargs, _alias_map) + kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D._alias_map) for line in self._get_lines(*args, **kwargs): self.add_line(line) @@ -1598,10 +1674,6 @@ def plot_date(self, x, y, fmt='o', tz=None, xdate=True, ydate=False, `.AutoDateFormatter` (if the tick formatter is not already set to a `.DateFormatter` instance). """ - - if not self._hold: - self.cla() - if xdate: self.xaxis_date(tz) if ydate: @@ -1657,9 +1729,6 @@ def loglog(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - if not self._hold: - self.cla() - dx = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] if k in kwargs} dy = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] @@ -1668,11 +1737,7 @@ def loglog(self, *args, **kwargs): self.set_xscale('log', **dx) self.set_yscale('log', **dy) - b = self._hold - self._hold = True # we've already processed the hold l = self.plot(*args, **kwargs) - self._hold = b # restore the hold - return l # @_preprocess_data() # let 'plot' do the unpacking.. @@ -1717,16 +1782,11 @@ def semilogx(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - if not self._hold: - self.cla() d = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx'] if k in kwargs} self.set_xscale('log', **d) - b = self._hold - self._hold = True # we've already processed the hold l = self.plot(*args, **kwargs) - self._hold = b # restore the hold return l # @_preprocess_data() # let 'plot' do the unpacking.. @@ -1771,15 +1831,10 @@ def semilogy(self, *args, **kwargs): **kwargs All parameters supported by `.plot`. """ - if not self._hold: - self.cla() d = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy'] - if k in kwargs} + if k in kwargs} self.set_yscale('log', **d) - b = self._hold - self._hold = True # we've already processed the hold l = self.plot(*args, **kwargs) - self._hold = b # restore the hold return l @@ -1793,8 +1848,6 @@ def acorr(self, x, **kwargs): x : sequence of scalar - hold : bool, optional, *deprecated*, default: True - detrend : callable, optional, default: `mlab.detrend_none` *x* is detrended by the *detrend* callable. Default is no normalization. @@ -1806,13 +1859,13 @@ def acorr(self, x, **kwargs): If ``True``, `Axes.vlines` is used to plot the vertical lines from the origin to the acorr. Otherwise, `Axes.plot` is used. - maxlags : integer, optional, default: 10 + maxlags : int, optional, default: 10 Number of lags to show. If ``None``, will return all ``2 * len(x) - 1`` lags. Returns ------- - lags : array (lenth ``2*maxlags+1``) + lags : array (length ``2*maxlags+1``) lag vector. c : array (length ``2*maxlags+1``) auto correlation vector. @@ -1827,18 +1880,16 @@ def acorr(self, x, **kwargs): Other Parameters ---------------- - linestyle : `~matplotlib.lines.Line2D` prop, optional, default: None + linestyle : `.Line2D` property, optional, default: None Only used if usevlines is ``False``. - marker : string, optional, default: 'o' + marker : str, optional, default: 'o' Notes ----- The cross correlation is performed with :func:`numpy.correlate` with ``mode = 2``. """ - if "hold" in kwargs: - warnings.warn("the 'hold' kwarg is deprecated", mplDeprecation) return self.xcorr(x, x, **kwargs) @_preprocess_data(replace_names=["x", "y"], label_namer="y") @@ -1847,7 +1898,9 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, r""" Plot the cross correlation between *x* and *y*. - The correlation with lag k is defined as sum_n x[n+k] * conj(y[n]). + The correlation with lag k is defined as + :math:`\sum_n x[n+k] \cdot y^*[n]`, where :math:`y^*` is the complex + conjugate of :math:`y`. Parameters ---------- @@ -1855,8 +1908,6 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, y : sequence of scalars of length n - hold : bool, optional, *deprecated*, default: True - detrend : callable, optional, default: `mlab.detrend_none` *x* is detrended by the *detrend* callable. Default is no normalization. @@ -1874,7 +1925,7 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, Returns ------- - lags : array (lenth ``2*maxlags+1``) + lags : array (length ``2*maxlags+1``) lag vector. c : array (length ``2*maxlags+1``) auto correlation vector. @@ -1889,7 +1940,7 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, Other Parameters ---------------- - linestyle : `~matplotlib.lines.Line2D` property, optional + linestyle : `.Line2D` property, optional Only used if usevlines is ``False``. marker : string, optional @@ -1900,9 +1951,6 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, The cross correlation is performed with :func:`numpy.correlate` with ``mode = 2``. """ - if "hold" in kwargs: - warnings.warn("the 'hold' kwarg is deprecated", mplDeprecation) - Nx = len(x) if Nx != len(y): raise ValueError('x and y must be equal length') @@ -1940,7 +1988,7 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, #### Specialized plotting @_preprocess_data(replace_names=["x", "y"], label_namer="y") - def step(self, x, y, *args, **kwargs): + def step(self, x, y, *args, where='pre', **kwargs): """ Make a step plot. @@ -2001,12 +2049,10 @@ def step(self, x, y, *args, **kwargs): ----- .. [notes section required to get data note injection right] """ - where = kwargs.pop('where', 'pre') if where not in ('pre', 'post', 'mid'): raise ValueError("'where' argument to step must be " "'pre', 'post' or 'mid'") - usr_linestyle = kwargs.pop('linestyle', '') - kwargs['linestyle'] = 'steps-' + where + usr_linestyle + kwargs['linestyle'] = 'steps-' + where + kwargs.get('linestyle', '') return self.plot(x, y, *args, **kwargs) @@ -2020,17 +2066,12 @@ def step(self, x, y, *args, **kwargs): replace_all_args=True ) @docstring.dedent_interpd - def bar(self, *args, **kwargs): + def bar(self, x, height, width=0.8, bottom=None, *, align="center", + **kwargs): r""" Make a bar plot. - Call signatures:: - - bar(x, height, *, align='center', **kwargs) - bar(x, height, width, *, align='center', **kwargs) - bar(x, height, width, bottom, *, align='center', **kwargs) - - The bars are positioned at *x* with the given *align* ment. Their + The bars are positioned at *x* with the given *align*\ment. Their dimensions are given by *width* and *height*. The vertical baseline is *bottom* (default 0). @@ -2038,7 +2079,6 @@ def bar(self, *args, **kwargs): applying to all bars, or it may be a sequence of length N providing a separate value for each bar. - Parameters ---------- x : sequence of scalars @@ -2136,58 +2176,23 @@ def bar(self, *args, **kwargs): %(Rectangle)s """ - kwargs = cbook.normalize_kwargs(kwargs, mpatches._patch_alias_map) - # this is using the lambdas to do the arg/kwarg unpacking rather - # than trying to re-implement all of that logic our selves. - matchers = [ - (lambda x, height, width=0.8, bottom=None, **kwargs: - (False, x, height, width, bottom, kwargs)), - (lambda left, height, width=0.8, bottom=None, **kwargs: - (True, left, height, width, bottom, kwargs)), - ] - exps = [] - for matcher in matchers: - try: - dp, x, height, width, y, kwargs = matcher(*args, **kwargs) - except TypeError as e: - # This can only come from a no-match as there is - # no other logic in the matchers. - exps.append(e) - else: - break - else: - raise exps[0] - # if we matched the second-case, then the user passed in - # left=val as a kwarg which we want to deprecate - if dp: - warnings.warn( - "The *left* kwarg to `bar` is deprecated use *x* instead. " - "Support for *left* will be removed in Matplotlib 3.0", - mplDeprecation, stacklevel=2) - if not self._hold: - self.cla() + kwargs = cbook.normalize_kwargs(kwargs, mpatches.Patch._alias_map) color = kwargs.pop('color', None) if color is None: color = self._get_patches_for_fill.get_next_color() edgecolor = kwargs.pop('edgecolor', None) linewidth = kwargs.pop('linewidth', None) - # Because xerr and yerr will be passed to errorbar, - # most dimension checking and processing will be left - # to the errorbar method. + # Because xerr and yerr will be passed to errorbar, most dimension + # checking and processing will be left to the errorbar method. xerr = kwargs.pop('xerr', None) yerr = kwargs.pop('yerr', None) - error_kw = kwargs.pop('error_kw', dict()) + error_kw = kwargs.pop('error_kw', {}) ecolor = kwargs.pop('ecolor', 'k') capsize = kwargs.pop('capsize', rcParams["errorbar.capsize"]) error_kw.setdefault('ecolor', ecolor) error_kw.setdefault('capsize', capsize) - if rcParams['_internal.classic_mode']: - align = kwargs.pop('align', 'edge') - else: - align = kwargs.pop('align', 'center') - orientation = kwargs.pop('orientation', 'vertical') log = kwargs.pop('log', False) label = kwargs.pop('label', '') @@ -2196,8 +2201,9 @@ def bar(self, *args, **kwargs): adjust_ylim = False adjust_xlim = False + y = bottom # Matches barh call signature. if orientation == 'vertical': - if y is None: + if bottom is None: if self.get_yscale() == 'log': adjust_ylim = True y = 0 @@ -2248,14 +2254,14 @@ def bar(self, *args, **kwargs): linewidth = itertools.cycle(np.atleast_1d(linewidth)) color = itertools.chain(itertools.cycle(mcolors.to_rgba_array(color)), # Fallback if color == "none". - itertools.repeat([0, 0, 0, 0])) + itertools.repeat('none')) if edgecolor is None: edgecolor = itertools.repeat(None) else: edgecolor = itertools.chain( itertools.cycle(mcolors.to_rgba_array(edgecolor)), # Fallback if edgecolor == "none". - itertools.repeat([0, 0, 0, 0])) + itertools.repeat('none')) # We will now resolve the alignment and really have # left, bottom, width, height vectors @@ -2291,9 +2297,6 @@ def bar(self, *args, **kwargs): self.add_patch(r) patches.append(r) - holdstate = self._hold - self._hold = True # ensure hold is on before plotting errorbars - if xerr is not None or yerr is not None: if orientation == 'vertical': # using list comps rather than arrays to preserve unit info @@ -2313,8 +2316,6 @@ def bar(self, *args, **kwargs): else: errorbar = None - self._hold = holdstate # restore previous hold state - if adjust_xlim: xmin, xmax = self.dataLim.intervalx xmin = min(w for w in width if w > 0) @@ -2336,24 +2337,19 @@ def bar(self, *args, **kwargs): self.add_container(bar_container) if tick_labels is not None: - tick_labels = _backports.broadcast_to(tick_labels, len(patches)) + tick_labels = np.broadcast_to(tick_labels, len(patches)) tick_label_axis.set_ticks(tick_label_position) tick_label_axis.set_ticklabels(tick_labels) return bar_container @docstring.dedent_interpd - def barh(self, *args, **kwargs): + def barh(self, y, width, height=0.8, left=None, *, align="center", + **kwargs): r""" Make a horizontal bar plot. - Call signatures:: - - bar(y, width, *, align='center', **kwargs) - bar(y, width, height, *, align='center', **kwargs) - bar(y, width, height, left, *, align='center', **kwargs) - - The bars are positioned at *y* with the given *align*. Their + The bars are positioned at *y* with the given *align*\ment. Their dimensions are given by *width* and *height*. The horizontal baseline is *left* (default 0). @@ -2361,7 +2357,6 @@ def barh(self, *args, **kwargs): applying to all bars, or it may be a sequence of length N providing a separate value for each bar. - Parameters ---------- y : scalar or array-like @@ -2456,35 +2451,9 @@ def barh(self, *args, **kwargs): %(Rectangle)s """ - # this is using the lambdas to do the arg/kwarg unpacking rather - # than trying to re-implement all of that logic our selves. - matchers = [ - (lambda y, width, height=0.8, left=None, **kwargs: - (False, y, width, height, left, kwargs)), - (lambda bottom, width, height=0.8, left=None, **kwargs: - (True, bottom, width, height, left, kwargs)), - ] - excs = [] - for matcher in matchers: - try: - dp, y, width, height, left, kwargs = matcher(*args, **kwargs) - except TypeError as e: - # This can only come from a no-match as there is - # no other logic in the matchers. - excs.append(e) - else: - break - else: - raise excs[0] - - if dp: - warnings.warn( - "The *bottom* kwarg to `barh` is deprecated use *y* instead. " - "Support for *bottom* will be removed in Matplotlib 3.0", - mplDeprecation, stacklevel=2) kwargs.setdefault('orientation', 'horizontal') - patches = self.bar(x=left, height=height, width=width, - bottom=y, **kwargs) + patches = self.bar(x=left, height=height, width=width, bottom=y, + align=align, **kwargs) return patches @_preprocess_data(label_namer=None) @@ -2559,7 +2528,8 @@ def broken_barh(self, xranges, yrange, **kwargs): return col @_preprocess_data(replace_all_args=True, label_namer=None) - def stem(self, *args, **kwargs): + def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, + bottom=0, label=None): """ Create a stem plot. @@ -2619,15 +2589,6 @@ def stem(self, *args, **kwargs): The label to use for the stems in legends. - Other Parameters - ---------------- - **kwargs - No other parameters are supported. They are currently ignored - silently for backward compatibility. This behavior is deprecated. - Future versions will not accept any other parameters and will - raise a TypeError instead. - - Returns ------- container : :class:`~matplotlib.container.StemContainer` @@ -2644,46 +2605,20 @@ def stem(self, *args, **kwargs): which inspired this method. """ + if not 1 <= len(args) <= 5: + raise TypeError('stem expected between 1 and 5 positional ' + 'arguments, got {}'.format(args)) - # kwargs handling - # We would like to have a signature with explicit kewords: - # stem(*args, linefmt=None, markerfmt=None, basefmt=None, - # bottom=0, label=None) - # Unfortunately, this is not supported in Python 2.x. There, *args - # can only exist after keyword arguments. - linefmt = kwargs.pop('linefmt', None) - markerfmt = kwargs.pop('markerfmt', None) - basefmt = kwargs.pop('basefmt', None) - bottom = kwargs.pop('bottom', None) - if bottom is None: - bottom = 0 - label = kwargs.pop('label', None) - if kwargs: - warn_deprecated(since='2.2', - message="stem() got an unexpected keyword " - "argument '%s'. This will raise a " - "TypeError in future versions." % ( - next(k for k in kwargs), ) - ) - - remember_hold = self._hold - if not self._hold: - self.cla() - self._hold = True - - # Assume there's at least one data array y = np.asarray(args[0]) args = args[1:] # Try a second one - try: - second = np.asarray(args[0], dtype=float) - x, y = y, second + if not args: + x = np.arange(len(y)) + else: + x = y + y = np.asarray(args[0], dtype=float) args = args[1:] - except (IndexError, ValueError): - # The second array doesn't make sense, or it doesn't exist - second = np.arange(len(y)) - x = second # defaults for formats if linefmt is None: @@ -2746,8 +2681,6 @@ def stem(self, *args, **kwargs): color=basecolor, linestyle=basestyle, marker=basemarker, label="_nolegend_") - self._hold = remember_hold - stem_container = StemContainer((markerline, stemlines, baseline), label=label) self.add_container(stem_container) @@ -2852,7 +2785,10 @@ def pie(self, x, explode=None, labels=None, colors=None, ----- The pie chart will probably look best if the figure and axes are square, or the Axes aspect is equal. + This method sets the aspect ratio of the axis to "equal". + The axes aspect ratio can be controlled with `Axes.set_aspect`. """ + self.set_aspect('equal') x = np.array(x, np.float32) sx = x.sum() @@ -2930,20 +2866,20 @@ def get_next_color(): if rotatelabels: label_alignment_v = yt > 0 and 'bottom' or 'top' label_rotation = np.rad2deg(thetam) + (0 if xt > 0 else 180) - - t = self.text(xt, yt, label, - size=rcParams['xtick.labelsize'], - horizontalalignment=label_alignment_h, + props = dict(horizontalalignment=label_alignment_h, verticalalignment=label_alignment_v, rotation=label_rotation, - **textprops) + size=rcParams['xtick.labelsize']) + props.update(textprops) + + t = self.text(xt, yt, label, **props) texts.append(t) if autopct is not None: xt = x + pctdistance * radius * math.cos(thetam) yt = y + pctdistance * radius * math.sin(thetam) - if isinstance(autopct, six.string_types): + if isinstance(autopct, str): s = autopct % (100. * frac) elif callable(autopct): s = autopct(100. * frac) @@ -2951,10 +2887,10 @@ def get_next_color(): raise TypeError( 'autopct must be callable or a format string') - t = self.text(xt, yt, s, - horizontalalignment='center', - verticalalignment='center', - **textprops) + props = dict(horizontalalignment='center', + verticalalignment='center') + props.update(textprops) + t = self.text(xt, yt, s, **props) autotexts.append(t) @@ -3089,7 +3025,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, .. [Notes section required for data comment. See #10189.] """ - kwargs = cbook.normalize_kwargs(kwargs, _alias_map) + kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D._alias_map) # anything that comes in as 'None', drop so the default thing # happens down stream kwargs = {k: v for k, v in kwargs.items() if v is not None} @@ -3100,10 +3036,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, 'errorevery has to be a strictly positive integer') self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) - if not self._hold: - self.cla() - holdstate = self._hold - self._hold = True plot_line = (fmt.lower() != 'none') label = kwargs.pop("label", None) @@ -3112,8 +3044,9 @@ def errorbar(self, x, y, yerr=None, xerr=None, fmt_style_kwargs = {} else: fmt_style_kwargs = {k: v for k, v in - zip(('linestyle', 'marker', 'color'), - _process_plot_format(fmt)) if v is not None} + zip(('linestyle', 'marker', 'color'), + _process_plot_format(fmt)) + if v is not None} if fmt == 'none': # Remove alpha=0 color that _process_plot_format returns fmt_style_kwargs.pop('color') @@ -3149,12 +3082,12 @@ def errorbar(self, x, y, yerr=None, xerr=None, yerr = [yerr] * len(y) # make the style dict for the 'normal' plot line - plot_line_style = dict(base_style) - plot_line_style.update(**kwargs) - if barsabove: - plot_line_style['zorder'] = kwargs['zorder'] - .1 - else: - plot_line_style['zorder'] = kwargs['zorder'] + .1 + plot_line_style = { + **base_style, + **kwargs, + 'zorder': (kwargs['zorder'] - .1 if barsabove else + kwargs['zorder'] + .1), + } # make the style dict for the line collections (the bars) eb_lines_style = dict(base_style) @@ -3204,16 +3137,10 @@ def errorbar(self, x, y, yerr=None, xerr=None, caplines = [] # arrays fine here, they are booleans and hence not units - def _bool_asarray_helper(d, expected): - if not iterable(d): - return np.asarray([d] * expected, bool) - else: - return np.asarray(d, bool) - - lolims = _bool_asarray_helper(lolims, len(x)) - uplims = _bool_asarray_helper(uplims, len(x)) - xlolims = _bool_asarray_helper(xlolims, len(x)) - xuplims = _bool_asarray_helper(xuplims, len(x)) + lolims = np.broadcast_to(lolims, len(x)).astype(bool) + uplims = np.broadcast_to(uplims, len(x)).astype(bool) + xlolims = np.broadcast_to(xlolims, len(x)).astype(bool) + xuplims = np.broadcast_to(xuplims, len(x)).astype(bool) everymask = np.arange(len(x)) % errorevery == 0 @@ -3245,9 +3172,9 @@ def extract_err(err, data): else: if iterable(a) and iterable(b): # using list comps rather than arrays to preserve units - low = [thisx - thiserr for (thisx, thiserr) + low = [thisx - thiserr for thisx, thiserr in cbook.safezip(data, a)] - high = [thisx + thiserr for (thisx, thiserr) + high = [thisx + thiserr for thisx, thiserr in cbook.safezip(data, b)] return low, high # Check if xerr is scalar or symmetric. Asymmetric is handled @@ -3256,13 +3183,13 @@ def extract_err(err, data): # special case for empty lists if len(err) > 1: fe = safe_first_element(err) - if (len(err) != len(data) or np.size(fe) > 1): + if len(err) != len(data) or np.size(fe) > 1: raise ValueError("err must be [ scalar | N, Nx1 " "or 2xN array-like ]") # using list comps rather than arrays to preserve units - low = [thisx - thiserr for (thisx, thiserr) + low = [thisx - thiserr for thisx, thiserr in cbook.safezip(data, err)] - high = [thisx + thiserr for (thisx, thiserr) + high = [thisx + thiserr for thisx, thiserr in cbook.safezip(data, err)] return low, high @@ -3367,8 +3294,6 @@ def extract_err(err, data): self.add_line(l) self.autoscale_view() - self._hold = holdstate - errorbar_container = ErrorbarContainer((data_line, tuple(caplines), tuple(barcols)), has_xerr=(xerr is not None), @@ -3485,16 +3410,15 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, labels : sequence, optional Labels for each dataset. Length must be compatible with - dimensions of ``x``. + dimensions of ``x``. manage_xticks : bool, optional (True) If the function should adjust the xlim and xtick locations. autorange : bool, optional (False) - When `True` and the data are distributed such that the 25th and + When `True` and the data are distributed such that the 25th and 75th percentiles are equal, ``whis`` is set to ``'range'`` such - that the whisker ends are at the minimum and maximum of the - data. + that the whisker ends are at the minimum and maximum of the data. meanline : bool, optional (False) If `True` (and ``showmeans`` is `True`), will try to render @@ -3560,8 +3484,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, """ - # If defined in matplotlibrc, apply the value from rc file - # Overridden if argument is passed + # Missing arguments default to rcParams. if whis is None: whis = rcParams['boxplot.whiskers'] if bootstrap is None: @@ -3957,7 +3880,7 @@ def dopatch(xs, ys, **kwargs): else: def doplot(*args, **kwargs): shuffled = [] - for i in xrange(0, len(args), 2): + for i in range(0, len(args), 2): shuffled.extend([args[i + 1], args[i]]) return self.plot(*shuffled, **kwargs) @@ -3971,7 +3894,7 @@ def dopatch(xs, ys, **kwargs): "values must have same the length") # check position if positions is None: - positions = list(xrange(1, N + 1)) + positions = list(range(1, N + 1)) elif len(positions) != N: raise ValueError(datashape_message.format("positions")) @@ -3983,10 +3906,6 @@ def dopatch(xs, ys, **kwargs): elif len(widths) != N: raise ValueError(datashape_message.format("widths")) - # check and save the `hold` state of the current axes - if not self._hold: - self.cla() - holdStatus = self._hold for pos, width, stats in zip(positions, widths, bxpstats): # try to find a new label datalabels.append(stats.get('label', pos)) @@ -4087,9 +4006,6 @@ def dopatch(xs, ys, **kwargs): setticks(positions) setlabels(datalabels) - # reset hold status - self._hold = holdStatus - return dict(whiskers=whiskers, caps=caps, boxes=boxes, medians=medians, fliers=fliers, means=means) @@ -4125,7 +4041,9 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, Note that *c* should not be a single numeric RGB or RGBA sequence because that is indistinguishable from an array of values to be colormapped. If you want to specify the same RGB or RGBA value for - all points, use a 2-D array with a single row. + all points, use a 2-D array with a single row. Otherwise, value- + matching will have precedence in case of a size matching with *x* + and *y*. marker : `~matplotlib.markers.MarkerStyle`, optional, default: 'o' The marker style. *marker* can be either an instance of the class @@ -4156,11 +4074,6 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, is 'face'. You may want to change this as well. If *None*, defaults to rcParams ``lines.linewidth``. - verts : sequence of (x, y), optional - If *marker* is *None*, these vertices will be used to construct - the marker. The center of the marker is located at (0, 0) in - normalized units. The overall marker is rescaled by *s*. - edgecolors : color or sequence of color, optional, default: 'face' The edge color of the marker. Possible values: @@ -4200,12 +4113,7 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, size matches the size of *x* and *y*. """ - - if not self._hold: - self.cla() - # Process **kwargs to handle aliases, conflicts with explicit kwargs: - facecolors = None edgecolors = kwargs.pop('edgecolor', edgecolors) fc = kwargs.pop('facecolors', None) @@ -4219,15 +4127,15 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, except ValueError: raise ValueError("'color' kwarg must be an mpl color" " spec or sequence of color specs.\n" - "For a sequence of values to be" - " color-mapped, use the 'c' kwarg instead.") + "For a sequence of values to be color-mapped," + " use the 'c' argument instead.") if edgecolors is None: edgecolors = co if facecolors is None: facecolors = co if c is not None: - raise ValueError("Supply a 'c' kwarg or a 'color' kwarg" - " but not both; they differ but" + raise ValueError("Supply a 'c' argument or a 'color'" + " kwarg but not both; they differ but" " their functionalities overlap.") if c is None: if facecolors is not None: @@ -4268,29 +4176,60 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, # c is an array for mapping. The potential ambiguity # with a sequence of 3 or 4 numbers is resolved in # favor of mapping, not rgb or rgba. + + # Convenience vars to track shape mismatch *and* conversion failures. + valid_shape = True # will be put to the test! + n_elem = -1 # used only for (some) exceptions + if c_none or co is not None: c_array = None else: - try: + try: # First, does 'c' look suitable for value-mapping? c_array = np.asanyarray(c, dtype=float) + n_elem = c_array.shape[0] if c_array.shape in xy_shape: c = np.ma.ravel(c_array) else: + if c_array.shape in ((3,), (4,)): + _log.warning( + "'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 " + "the same RGB or RGBA value for all points.") # Wrong size; it must not be intended for mapping. + valid_shape = False c_array = None except ValueError: # Failed to make a floating-point array; c must be color specs. c_array = None if c_array is None: - try: - # must be acceptable as PathCollection facecolors + try: # Then is 'c' acceptable as PathCollection facecolors? colors = mcolors.to_rgba_array(c) + n_elem = colors.shape[0] + if colors.shape[0] not in (0, 1, x.size, y.size): + # NB: remember that a single color is also acceptable. + # Besides *colors* will be an empty array if c == 'none'. + valid_shape = False + raise ValueError except ValueError: - # c not acceptable as PathCollection facecolor - raise ValueError("c of shape {} not acceptable as a color " - "sequence for x with size {}, y with size {}" - .format(c.shape, x.size, y.size)) + if not valid_shape: # but at least one conversion succeeded. + raise ValueError( + "'c' argument has {nc} elements, which is not " + "acceptable for use with 'x' with size {xs}, " + "'y' with size {ys}." + .format(nc=n_elem, xs=x.size, ys=y.size) + ) + # Both the mapping *and* the RGBA conversion failed: pretty + # severe failure => one may appreciate a verbose feedback. + raise ValueError( + "'c' argument must either be valid as mpl color(s) " + "or as numbers to be mapped to colors. " + "Here c = {}." # <- beware, could be long depending on c. + .format(c) + ) else: colors = None # use cmap, norm after collection is created @@ -4303,9 +4242,11 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, scales = s # Renamed for readability below. # to be API compatible - if marker is None and verts is not None: - marker = (verts, 0) - verts = None + if verts is not None: + cbook.warn_deprecated("3.0", name="'verts'", obj_type="kwarg", + alternative="'marker'") + if marker is None: + marker = verts # load default marker from rcParams if marker is None: @@ -4382,11 +4323,10 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, of the observations at (x[i],y[i]). If *C* is specified, it specifies values at the coordinate - (x[i],y[i]). These values are accumulated for each hexagonal + (x[i], y[i]). These values are accumulated for each hexagonal bin and then reduced according to *reduce_C_function*, which - defaults to numpy's mean function (np.mean). (If *C* is - specified, it must also be a 1-D sequence of the same length - as *x* and *y*.) + defaults to `numpy.mean`. (If *C* is specified, it must also + be a 1-D sequence of the same length as *x* and *y*.) Parameters ---------- @@ -4402,7 +4342,7 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, tuple with two elements specifying the number of hexagons in the *x*-direction and the *y*-direction. - bins : {'log'} or int or sequence, optional, default is *None* + bins : 'log' or int or sequence, optional, default is *None* If *None*, no binning is applied; the color of each hexagon directly corresponds to its count value. @@ -4478,11 +4418,9 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, Returns ------- - object - a :class:`~matplotlib.collections.PolyCollection` instance; use - :meth:`~matplotlib.collections.PolyCollection.get_array` on - this :class:`~matplotlib.collections.PolyCollection` to get - the counts in each hexagon. + polycollection + A `.PolyCollection` instance; use `.PolyCollection.get_array` on + this to get the counts in each hexagon. If *marginals* is *True*, horizontal bar and vertical bar (both PolyCollections) will be attached @@ -4496,10 +4434,6 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, %(Collection)s """ - - if not self._hold: - self.cla() - self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) x, y, C = cbook.delete_masked_points(x, y, C) @@ -4592,15 +4526,15 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, # create accumulation arrays lattice1 = np.empty((nx1, ny1), dtype=object) - for i in xrange(nx1): - for j in xrange(ny1): + for i in range(nx1): + for j in range(ny1): lattice1[i, j] = [] lattice2 = np.empty((nx2, ny2), dtype=object) - for i in xrange(nx2): - for j in xrange(ny2): + for i in range(nx2): + for j in range(ny2): lattice2[i, j] = [] - for i in xrange(len(x)): + for i in range(len(x)): if bdist[i]: if 0 <= ix1[i] < nx1 and 0 <= iy1[i] < ny1: lattice1[ix1[i], iy1[i]].append(C[i]) @@ -4608,15 +4542,15 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, if 0 <= ix2[i] < nx2 and 0 <= iy2[i] < ny2: lattice2[ix2[i], iy2[i]].append(C[i]) - for i in xrange(nx1): - for j in xrange(ny1): + for i in range(nx1): + for j in range(ny1): vals = lattice1[i, j] if len(vals) > mincnt: lattice1[i, j] = reduce_C_function(vals) else: lattice1[i, j] = np.nan - for i in xrange(nx2): - for j in xrange(ny2): + for i in range(nx2): + for j in range(ny2): vals = lattice2[i, j] if len(vals) > mincnt: lattice2[i, j] = reduce_C_function(vals) @@ -4674,9 +4608,23 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, offset_position="data" ) + # Check for valid norm + if norm is not None and not isinstance(norm, mcolors.Normalize): + msg = "'norm' must be an instance of 'mcolors.Normalize'" + raise ValueError(msg) + + # Set normalizer if bins is 'log' + if bins == 'log': + if norm is not None: + warnings.warn("Only one of 'bins' and 'norm' arguments can be " + "supplied, ignoring bins={}".format(bins)) + else: + norm = mcolors.LogNorm() + bins = None + if isinstance(norm, mcolors.LogNorm): if (accum == 0).any(): - # make sure we have not zeros + # make sure we have no zeros accum += 1 # autoscale the norm with curren accum values if it hasn't @@ -4685,10 +4633,7 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, if norm.vmin is None and norm.vmax is None: norm.autoscale(accum) - # Transform accum if needed - if bins == 'log': - accum = np.log10(accum + 1) - elif bins is not None: + if bins is not None: if not iterable(bins): minimum, maximum = min(accum), max(accum) bins -= 1 # one less edge than bins @@ -4696,9 +4641,6 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, bins = np.sort(bins) accum = bins.searchsorted(accum) - if norm is not None and not isinstance(norm, mcolors.Normalize): - raise ValueError( - "'norm' must be an instance of 'mcolors.Normalize'") collection.set_array(accum) collection.set_cmap(cmap) collection.set_norm(norm) @@ -4859,8 +4801,8 @@ def arrow(self, x, y, dx, dy, **kwargs): self.add_artist(a) return a - def quiverkey(self, *args, **kw): - qk = mquiver.QuiverKey(*args, **kw) + def quiverkey(self, Q, X, Y, U, label, **kw): + qk = mquiver.QuiverKey(Q, X, Y, U, label, **kw) self.add_artist(qk) return qk quiverkey.__doc__ = mquiver.QuiverKey.quiverkey_doc @@ -4878,9 +4820,6 @@ def _quiver_units(self, args, kw): # args can by a combination if X, Y, U, V, C and all should be replaced @_preprocess_data(replace_all_args=True, label_namer=None) def quiver(self, *args, **kw): - if not self._hold: - self.cla() - # Make sure units are handled for x and y values args = self._quiver_units(args, kw) @@ -4904,8 +4843,6 @@ def streamplot(self, x, y, u, v, density=1, linewidth=None, color=None, minlength=0.1, transform=None, zorder=None, start_points=None, maxlength=4.0, integration_direction='both'): - if not self._hold: - self.cla() stream_container = mstream.streamplot( self, x, y, u, v, density=density, @@ -4931,9 +4868,6 @@ def barbs(self, *args, **kw): """ %(barbs_doc)s """ - if not self._hold: - self.cla() - # Make sure units are handled for x and y values args = self._quiver_units(args, kw) @@ -4952,7 +4886,7 @@ def fill(self, *args, **kwargs): ---------- args : sequence of x, y, [color] Each polygon is defined by the lists of *x* and *y* positions of - its nodes, optionally followed by by a *color* specifier. See + its nodes, optionally followed by a *color* specifier. See :mod:`matplotlib.colors` for supported color specifiers. The standard color cycle is used for polygons without a color specifier. @@ -4980,10 +4914,8 @@ def fill(self, *args, **kwargs): Use :meth:`fill_between` if you would like to fill the region between two curves. """ - if not self._hold: - self.cla() - - kwargs = cbook.normalize_kwargs(kwargs, _alias_map) + # For compatibility(!), get aliases from Line2D rather than Patch. + kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D._alias_map) patches = [] for poly in self._get_patches_for_fill(*args, **kwargs): @@ -5037,11 +4969,11 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, By default, the nodes of the polygon defining the filled region will only be placed at the positions in the *x* array. Such a polygon cannot describe the above semantics close to the - intersection. The x-sections containing the intersecion are + intersection. The x-sections containing the intersection are simply clipped. Setting *interpolate* to *True* will calculate the actual - interscection point and extend the filled region up to this point. + intersection point and extend the filled region up to this point. step : {'pre', 'post', 'mid'}, optional Define *step* if the filling should be a step function, @@ -5079,12 +5011,11 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, """ if not rcParams['_internal.classic_mode']: - color_aliases = mcoll._color_aliases - kwargs = cbook.normalize_kwargs(kwargs, color_aliases) - - if not any(c in kwargs for c in ('color', 'facecolors')): - fc = self._get_patches_for_fill.get_next_color() - kwargs['facecolors'] = fc + kwargs = cbook.normalize_kwargs( + kwargs, mcoll.Collection._alias_map) + if not any(c in kwargs for c in ('color', 'facecolor')): + kwargs['facecolor'] = \ + self._get_patches_for_fill.get_next_color() # Handle united data, such as dates self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs) @@ -5263,12 +5194,12 @@ def fill_betweenx(self, y, x1, x2=0, where=None, """ if not rcParams['_internal.classic_mode']: - color_aliases = mcoll._color_aliases - kwargs = cbook.normalize_kwargs(kwargs, color_aliases) + kwargs = cbook.normalize_kwargs( + kwargs, mcoll.Collection._alias_map) + if not any(c in kwargs for c in ('color', 'facecolor')): + kwargs['facecolor'] = \ + self._get_patches_for_fill.get_next_color() - if not any(c in kwargs for c in ('color', 'facecolors')): - fc = self._get_patches_for_fill.get_next_color() - kwargs['facecolors'] = fc # Handle united data, such as dates self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs) self._process_unit_info(xdata=x2) @@ -5365,95 +5296,137 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, origin=None, extent=None, shape=None, filternorm=1, filterrad=4.0, imlim=None, resample=None, url=None, **kwargs): """ - Display an image on the axes. + Display an image, i.e. data on a 2D regular raster. Parameters ---------- - X : array_like, shape (n, m) or (n, m, 3) or (n, m, 4) - Display the image in `X` to current axes. `X` may be an - array or a PIL image. If `X` is an array, it - can have the following shapes and types: + X : array-like or PIL image + The image data. Supported array shapes are: - - MxN -- values to be mapped (float or int) - - MxNx3 -- RGB (float or uint8) - - MxNx4 -- RGBA (float or uint8) + - (M, N): an image with scalar data. The data is visualized + using a colormap. + - (M, N, 3): an image with RGB values (float or uint8). + - (M, N, 4): an image with RGBA values (float or uint8), i.e. + including transparency. - MxN arrays are mapped to colors based on the `norm` (mapping - scalar to scalar) and the `cmap` (mapping the normed scalar to - a color). + The first two dimensions (M, N) define the rows and columns of + the image. - Elements of RGB and RGBA arrays represent pixels of an MxN image. - All values should be in the range [0 .. 1] for floats or + The RGB(A) values should be in the range [0 .. 1] for floats or [0 .. 255] for integers. Out-of-range values will be clipped to these bounds. - cmap : `~matplotlib.colors.Colormap`, optional, default: None - If None, default to rc `image.cmap` value. `cmap` is ignored - if `X` is 3-D, directly specifying RGB(A) values. + cmap : str or `~matplotlib.colors.Colormap`, optional + A Colormap instance or registered colormap name. The colormap + maps scalar data to colors. It is ignored for RGB(A) data. + Defaults to :rc:`image.cmap`. - aspect : ['auto' | 'equal' | scalar], optional, default: None - If 'auto', changes the image aspect ratio to match that of the - axes. + aspect : {'equal', 'auto'} or float, optional + Controls the aspect ratio of the axes. The aspect is of particular + relevance for images since it may distort the image, i.e. pixel + will not be square. - If 'equal', and `extent` is None, changes the axes aspect ratio to - match that of the image. If `extent` is not `None`, the axes - aspect ratio is changed to match that of the extent. + This parameter is a shortcut for explicitly calling + `.Axes.set_aspect`. See there for further details. - If None, default to rc ``image.aspect`` value. + - 'equal': Ensures an aspect ratio of 1. Pixels will be square + (unless pixel sizes are explicitly made non-square in data + coordinates using *extent*). + - 'auto': The axes is kept fixed and the aspect is adjusted so + that the data fit in the axes. In general, this will result in + non-square pixels. - interpolation : string, optional, default: None - Acceptable values are 'none', 'nearest', 'bilinear', 'bicubic', + If not given, use :rc:`image.aspect` (default: 'equal'). + + interpolation : str, optional + The interpolation method used. If *None* + :rc:`image.interpolation` is used, which defaults to 'nearest'. + + Supported values are 'none', 'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', - 'lanczos' + 'lanczos'. - If `interpolation` is None, default to rc `image.interpolation`. - See also the `filternorm` and `filterrad` parameters. - If `interpolation` is 'none', then no interpolation is performed + If *interpolation* is 'none', then no interpolation is performed on the Agg, ps and pdf backends. Other backends will fall back to 'nearest'. - norm : `~matplotlib.colors.Normalize`, optional, default: None - A `~matplotlib.colors.Normalize` instance is used to scale - a 2-D float `X` input to the (0, 1) range for input to the - `cmap`. If `norm` is None, use the default func:`normalize`. - If `norm` is an instance of `~matplotlib.colors.NoNorm`, - `X` must be an array of integers that index directly into - the lookup table of the `cmap`. + See + :doc:`/gallery/images_contours_and_fields/interpolation_methods` + for an overview of the supported interpolation methods. - vmin, vmax : scalar, optional, default: None - `vmin` and `vmax` are used in conjunction with norm to normalize - luminance data. Note if you pass a `norm` instance, your - settings for `vmin` and `vmax` will be ignored. + Some interpolation methods require an additional radius parameter, + which can be set by *filterrad*. Additionally, the antigrain image + resize filter is controlled by the parameter *filternorm*. - alpha : scalar, optional, default: None + norm : `~matplotlib.colors.Normalize`, optional + If scalar data are used, the Normalize instance scales the + data values to the canonical colormap range [0,1] for mapping + to colors. By default, the data range is mapped to the + colorbar range using linear scaling. This parameter is ignored for + RGB(A) data. + + vmin, vmax : scalar, optional + When using scalar data and no explicit *norm*, *vmin* and *vmax* + define the data range that the colormap covers. By default, + the colormap covers the complete value range of the supplied + data. *vmin*, *vmax* are ignored if the *norm* parameter is used. + + alpha : scalar, optional The alpha blending value, between 0 (transparent) and 1 (opaque). - The ``alpha`` argument is ignored for RGBA input data. + This parameter is ignored for RGBA input data. - origin : ['upper' | 'lower'], optional, default: None + origin : {'upper', 'lower'}, optional Place the [0,0] index of the array in the upper left or lower left - corner of the axes. If None, default to rc `image.origin`. + corner of the axes. The convention 'upper' is typically used for + matrices and images. + If not given, :rc:`image.origin` is used, defaulting to 'upper'. + + Note that the vertical axes points upward for 'lower' + but downward for 'upper'. - extent : scalars (left, right, bottom, top), optional, default: None - The location, in data-coordinates, of the lower-left and - upper-right corners. If `None`, the image is positioned such that - the pixel centers fall on zero-based (row, column) indices. + extent : scalars (left, right, bottom, top), optional + The bounding box in data coordinates that the image will fill. + The image is stretched individually along x and y to fill the box. + + The default extent is determined by the following conditions. + Pixels have unit size in data coordinates. Their centers are on + integer coordinates, and their center coordinates range from 0 to + columns-1 horizontally and from 0 to rows-1 vertically. + + Note that the direction of the vertical axis and thus the default + values for top and bottom depend on *origin*: + + - For ``origin == 'upper'`` the default is + ``(-0.5, numcols-0.5, numrows-0.5, -0.5)``. + - For ``origin == 'lower'`` the default is + ``(-0.5, numcols-0.5, -0.5, numrows-0.5)``. + + See the example :doc:`/tutorials/intermediate/imshow_extent` for a + more detailed description. shape : scalars (columns, rows), optional, default: None - For raw buffer images + For raw buffer images. - filternorm : scalar, optional, default: 1 - A parameter for the antigrain image resize filter. From the - antigrain documentation, if `filternorm` = 1, the filter + filternorm : bool, optional, default: True + A parameter for the antigrain image resize filter (see the + antigrain documentation). If *filternorm* is set, the filter normalizes integer values and corrects the rounding errors. It doesn't do anything with the source floating point values, it corrects only integers according to the rule of 1.0 which means that any sum of pixel weights must be equal to 1.0. So, the filter function must produce a graph of the proper shape. - filterrad : scalar, optional, default: 4.0 + filterrad : float > 0, optional, default: 4.0 The filter radius for filters that have a radius parameter, i.e. - when interpolation is one of: 'sinc', 'lanczos' or 'blackman' + when interpolation is one of: 'sinc', 'lanczos' or 'blackman'. + + resample : bool, optional + When *True*, use a full resampling method. When *False*, only + resample when the output image is larger than the input image. + + url : str, optional + Set the url of the created `.AxesImage`. See `.Artist.set_url`. Returns ------- @@ -5461,7 +5434,9 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, Other Parameters ---------------- - **kwargs : `~matplotlib.artist.Artist` properties. + **kwargs : `~matplotlib.artist.Artist` properties + These parameters are passed on to the constructor of the + `.AxesImage` artist. See also -------- @@ -5473,7 +5448,7 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, coordinates. In other words: the origin will coincide with the center of pixel (0, 0). - Two typical representations are used for RGB images with an alpha + There are two common representations for RGB images with an alpha channel: - Straight (unassociated) alpha: R, G, and B channels represent the @@ -5484,10 +5459,6 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, `~matplotlib.pyplot.imshow` expects RGB images adopting the straight (unassociated) alpha representation. """ - - if not self._hold: - self.cla() - if norm is not None and not isinstance(norm, mcolors.Normalize): raise ValueError( "'norm' must be an instance of 'mcolors.Normalize'") @@ -5503,8 +5474,6 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, if im.get_clip_path() is None: # image does not already have clipping set, clip to axes patch im.set_clip_path(self.patch) - #if norm is None and shape is None: - # im.set_clim(vmin, vmax) if vmin is not None or vmax is not None: im.set_clim(vmin, vmax) else: @@ -5519,20 +5488,14 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, return im @staticmethod - def _pcolorargs(funcname, *args, **kw): - # This takes one kwarg, allmatch. - # If allmatch is True, then the incoming X, Y, C must - # have matching dimensions, taking into account that - # X and Y can be 1-D rather than 2-D. This perfect - # match is required for Gouroud shading. For flat - # shading, X and Y specify boundaries, so we need - # one more boundary than color in each direction. - # For convenience, and consistent with Matlab, we - # discard the last row and/or column of C if necessary - # to meet this condition. This is done if allmatch - # is False. - - allmatch = kw.pop("allmatch", False) + def _pcolorargs(funcname, *args, allmatch=False): + # If allmatch is True, then the incoming X, Y, C must have matching + # dimensions, taking into account that X and Y can be 1-D rather than + # 2-D. This perfect match is required for Gouroud shading. For flat + # shading, X and Y specify boundaries, so we need one more boundary + # than color in each direction. For convenience, and consistent with + # Matlab, we discard the last row and/or column of C if necessary to + # meet this condition. This is done if allmatch is False. if len(args) == 1: C = np.asanyarray(args[0]) @@ -5579,7 +5542,7 @@ def _pcolorargs(funcname, *args, **kw): 'Incompatible X, Y inputs to %s; see help(%s)' % ( funcname, funcname)) if allmatch: - if not (Nx == numCols and Ny == numRows): + if (Nx, Ny) != (numCols, numRows): raise TypeError('Dimensions of C %s are incompatible with' ' X (%d) and/or Y (%d); see help(%s)' % ( C.shape, Nx, Ny, funcname)) @@ -5594,7 +5557,8 @@ def _pcolorargs(funcname, *args, **kw): @_preprocess_data(label_namer=None) @docstring.dedent_interpd - def pcolor(self, *args, **kwargs): + def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, + vmax=None, **kwargs): r""" Create a pseudocolor plot with a non-regular rectangular grid. @@ -5607,7 +5571,7 @@ def pcolor(self, *args, **kwargs): .. hint:: ``pcolor()`` can be very slow for large arrays. In most - cases you should use the the similar but much faster + cases you should use the similar but much faster `~.Axes.pcolormesh` instead. See there for a discussion of the differences. @@ -5729,16 +5693,6 @@ def pcolor(self, *args, **kwargs): Note: This behavior is different from MATLAB's ``pcolor()``, which always discards the last row and column of *C*. """ - - if not self._hold: - self.cla() - - alpha = kwargs.pop('alpha', None) - norm = kwargs.pop('norm', None) - cmap = kwargs.pop('cmap', None) - vmin = kwargs.pop('vmin', None) - vmax = kwargs.pop('vmax', None) - X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False) Ny, Nx = X.shape @@ -5758,26 +5712,20 @@ def pcolor(self, *args, **kwargs): # don't plot if C or any of the surrounding vertices are masked. mask = ma.getmaskarray(C) + xymask - newaxis = np.newaxis compress = np.compress ravelmask = (mask == 0).ravel() - X1 = compress(ravelmask, ma.filled(X[0:-1, 0:-1]).ravel()) - Y1 = compress(ravelmask, ma.filled(Y[0:-1, 0:-1]).ravel()) - X2 = compress(ravelmask, ma.filled(X[1:, 0:-1]).ravel()) - Y2 = compress(ravelmask, ma.filled(Y[1:, 0:-1]).ravel()) + X1 = compress(ravelmask, ma.filled(X[:-1, :-1]).ravel()) + Y1 = compress(ravelmask, ma.filled(Y[:-1, :-1]).ravel()) + X2 = compress(ravelmask, ma.filled(X[1:, :-1]).ravel()) + Y2 = compress(ravelmask, ma.filled(Y[1:, :-1]).ravel()) X3 = compress(ravelmask, ma.filled(X[1:, 1:]).ravel()) Y3 = compress(ravelmask, ma.filled(Y[1:, 1:]).ravel()) - X4 = compress(ravelmask, ma.filled(X[0:-1, 1:]).ravel()) - Y4 = compress(ravelmask, ma.filled(Y[0:-1, 1:]).ravel()) + X4 = compress(ravelmask, ma.filled(X[:-1, 1:]).ravel()) + Y4 = compress(ravelmask, ma.filled(Y[:-1, 1:]).ravel()) npoly = len(X1) - xy = np.concatenate((X1[:, newaxis], Y1[:, newaxis], - X2[:, newaxis], Y2[:, newaxis], - X3[:, newaxis], Y3[:, newaxis], - X4[:, newaxis], Y4[:, newaxis], - X1[:, newaxis], Y1[:, newaxis]), - axis=1) + xy = np.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4, X1, Y1], axis=-1) verts = xy.reshape((npoly, 5, 2)) C = compress(ravelmask, ma.filled(C[0:Ny - 1, 0:Nx - 1]).ravel()) @@ -5797,8 +5745,7 @@ def pcolor(self, *args, **kwargs): # makes artifacts that are often disturbing. if 'antialiased' in kwargs: kwargs['antialiaseds'] = kwargs.pop('antialiased') - if 'antialiaseds' not in kwargs and ( - isinstance(ec, six.string_types) and ec.lower() == "none"): + if 'antialiaseds' not in kwargs and cbook._str_lower_equal(ec, "none"): kwargs['antialiaseds'] = False kwargs.setdefault('snap', False) @@ -5847,7 +5794,8 @@ def pcolor(self, *args, **kwargs): @_preprocess_data(label_namer=None) @docstring.dedent_interpd - def pcolormesh(self, *args, **kwargs): + def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, + vmax=None, shading='flat', antialiased=False, **kwargs): """ Create a pseudocolor plot with a non-regular rectangular grid. @@ -6004,16 +5952,7 @@ def pcolormesh(self, *args, **kwargs): `~.Axes.pcolormesh`, which is not available with `~.Axes.pcolor`. """ - if not self._hold: - self.cla() - - alpha = kwargs.pop('alpha', None) - norm = kwargs.pop('norm', None) - cmap = kwargs.pop('cmap', None) - vmin = kwargs.pop('vmin', None) - vmax = kwargs.pop('vmax', None) - shading = kwargs.pop('shading', 'flat').lower() - antialiased = kwargs.pop('antialiased', False) + shading = shading.lower() kwargs.setdefault('edgecolors', 'None') allmatch = (shading == 'gouraud') @@ -6068,7 +6007,8 @@ def pcolormesh(self, *args, **kwargs): @_preprocess_data(label_namer=None) @docstring.dedent_interpd - def pcolorfast(self, *args, **kwargs): + def pcolorfast(self, *args, alpha=None, norm=None, cmap=None, vmin=None, + vmax=None, **kwargs): """ Create a pseudocolor plot with a non-regular rectangular grid. @@ -6083,7 +6023,7 @@ def pcolorfast(self, *args, **kwargs): It's designed to provide the fastest pcolor-type plotting with the Agg backend. To achieve this, it uses different algorithms internally depending on the complexity of the input grid (regular rectangular, - non-regular rectangular or arbitrary quadrilateral). + non-regular rectangular or arbitrary quadrilateral). .. warning:: @@ -6168,15 +6108,6 @@ def pcolorfast(self, *args, **kwargs): .. [notes section required to get data note injection right] """ - - if not self._hold: - self.cla() - - alpha = kwargs.pop('alpha', None) - norm = kwargs.pop('norm', None) - cmap = kwargs.pop('cmap', None) - vmin = kwargs.pop('vmin', None) - vmax = kwargs.pop('vmax', None) if norm is not None and not isinstance(norm, mcolors.Normalize): raise ValueError( "'norm' must be an instance of 'mcolors.Normalize'") @@ -6274,8 +6205,6 @@ def pcolorfast(self, *args, **kwargs): @_preprocess_data() def contour(self, *args, **kwargs): - if not self._hold: - self.cla() kwargs['filled'] = False contours = mcontour.QuadContourSet(self, *args, **kwargs) self.autoscale_view() @@ -6284,8 +6213,6 @@ def contour(self, *args, **kwargs): @_preprocess_data() def contourf(self, *args, **kwargs): - if not self._hold: - self.cla() kwargs['filled'] = True contours = mcontour.QuadContourSet(self, *args, **kwargs) self.autoscale_view() @@ -6350,11 +6277,11 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, ---------- x : (n,) array or sequence of (n,) arrays Input values, this takes either a single array or a sequence of - arrays which are not required to be of the same length + arrays which are not required to be of the same length. - bins : integer or sequence or 'auto', optional + bins : int or sequence or str, optional If an integer is given, ``bins + 1`` bin edges are calculated and - returned, consistent with :func:`numpy.histogram`. + returned, consistent with `numpy.histogram`. If `bins` is a sequence, gives bin edges, including left edge of first bin and right edge of last bin. In this case, `bins` is @@ -6371,9 +6298,12 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Unequally spaced bins are supported if *bins* is a sequence. - If Numpy 1.11 is installed, may also be ``'auto'``. + With Numpy 1.11 or newer, you can alternatively provide a string + describing a binning strategy, such as 'auto', 'sturges', 'fd', + 'doane', 'scott', 'rice', 'sturges' or 'sqrt', see + `numpy.histogram`. - Default is taken from the rcParam ``hist.bins``. + The default is taken from :rc:`hist.bins`. range : tuple or None, optional The lower and upper range of the bins. Lower and upper outliers @@ -6386,7 +6316,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``None`` - density : boolean, optional + density : bool, optional If ``True``, the first element of the return tuple will be the counts normalized to form a probability density, i.e., the area (or integral) under the histogram will sum to 1. @@ -6410,7 +6340,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``None`` - cumulative : boolean, optional + cumulative : bool, optional If ``True``, then a histogram is computed where each bin gives the counts in that bin plus all bins for smaller values. The last bin gives the total number of datapoints. If *normed* or *density* @@ -6470,7 +6400,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``None`` - log : boolean, optional + log : bool, optional If ``True``, the histogram axis will be set to a log scale. If *log* is ``True`` and *x* is a 1D array, empty bins will be filtered out and only the non-empty ``(n, bins, patches)`` @@ -6484,14 +6414,14 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``None`` - label : string or None, optional + label : str or None, optional String, or sequence of strings to match multiple datasets. Bar charts yield multiple patches per dataset, but only the first gets the label, so that the legend command will work as expected. default is ``None`` - stacked : boolean, optional + stacked : bool, optional If ``True``, multiple data are stacked on top of each other If ``False`` multiple data are arranged side by side if histtype is 'bar' or on top of each other if histtype is 'step' @@ -6536,10 +6466,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, """ # Avoid shadowing the builtin. bin_range = range - del range - - if not self._hold: - self.cla() + from builtins import range if np.isscalar(x): x = [x] @@ -6568,8 +6495,8 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, "Please only use 'density', since 'normed'" "is deprecated.") if normed is not None: - warnings.warn("The 'normed' kwarg is deprecated, and has been " - "replaced by the 'density' kwarg.") + cbook.warn_deprecated("2.1", name="'normed'", obj_type="kwarg", + alternative="'density'", removal="3.1") # basic input validation input_empty = np.size(x) == 0 @@ -6606,11 +6533,14 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, 'weights should have the same shape as x') if color is None: - color = [self._get_lines.get_next_color() for i in xrange(nx)] + color = [self._get_lines.get_next_color() for i in range(nx)] else: color = mcolors.to_rgba_array(color) if len(color) != nx: - raise ValueError("color kwarg must have one color per dataset") + error_message = ( + "color kwarg must have one color per data set. %d data " + "sets and %d colors were provided" % (len(color), nx)) + raise ValueError(error_message) # If bins are not specified either explicitly or via range, # we need to figure out the range required for all datasets, @@ -6620,8 +6550,8 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, xmax = -np.inf for xi in x: if len(xi) > 0: - xmin = min(xmin, xi.min()) - xmax = max(xmax, xi.max()) + xmin = min(xmin, np.nanmin(xi)) + xmax = max(xmax, np.nanmax(xi)) bin_range = (xmin, xmax) density = bool(density) or bool(normed) if density and not stacked: @@ -6633,7 +6563,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, tops = [] mlast = None # Loop through datasets - for i in xrange(nx): + for i in range(nx): # this will automatically overwrite bins, # so that each histogram uses the same bins m, bins = np.histogram(x[i], bins, weights=w[i], **hist_kwargs) @@ -6653,7 +6583,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, m[:] = (m / db) / tops[-1].sum() if cumulative: slc = slice(None) - if cbook.is_numlike(cumulative) and cumulative < 0: + if isinstance(cumulative, Number) and cumulative < 0: slc = slice(None, None, -1) if density: @@ -6816,12 +6746,12 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, if label is None: labels = [None] - elif isinstance(label, six.string_types): + elif isinstance(label, str): labels = [label] else: - labels = [six.text_type(lab) for lab in label] + labels = [str(lab) for lab in label] - for patch, lbl in zip_longest(patches, labels, fillvalue=None): + for patch, lbl in itertools.zip_longest(patches, labels): if patch: p = patch[0] p.update(kwargs) @@ -6845,10 +6775,10 @@ def hist2d(self, x, y, bins=10, range=None, normed=False, weights=None, Parameters ---------- - x, y: array_like, shape (n, ) + x, y : array_like, shape (n, ) Input values - bins: [None | int | [int, int] | array_like | [array, array]] + bins : None or int or [int, int] or array_like or [array, array] The bin specification: @@ -6872,7 +6802,7 @@ def hist2d(self, x, y, bins=10, range=None, normed=False, weights=None, xmax], [ymin, ymax]]``. All values outside of this range will be considered outliers and not tallied in the histogram. - normed : boolean, optional, default: False + normed : bool, optional, default: False Normalize histogram. weights : array_like, shape (n, ), optional, default: None @@ -6898,7 +6828,7 @@ def hist2d(self, x, y, bins=10, range=None, normed=False, weights=None, The bin edges along the x axis. yedges : 1D array The bin edges along the y axis. - image : AxesImage + image : `~.matplotlib.collections.QuadMesh` Other Parameters ---------------- @@ -6939,7 +6869,7 @@ def hist2d(self, x, y, bins=10, range=None, normed=False, weights=None, if cmax is not None: h[h > cmax] = None - pc = self.pcolorfast(xedges, yedges, h.T, **kwargs) + pc = self.pcolormesh(xedges, yedges, h.T, **kwargs) self.set_xlim(xedges[0], xedges[-1]) self.set_ylim(yedges[0], yedges[-1]) @@ -6978,11 +6908,11 @@ def psd(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, %(PSD)s - noverlap : integer + noverlap : int The number of points of overlap between segments. The default value is 0 (no overlap). - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -6996,10 +6926,10 @@ def psd(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, ------- Pxx : 1-D array The values for the power spectrum `P_{xx}` before scaling - (real valued) + (real valued). freqs : 1-D array - The frequencies corresponding to the elements in *Pxx* + The frequencies corresponding to the elements in *Pxx*. line : a :class:`~matplotlib.lines.Line2D` instance The line created by this function. @@ -7037,9 +6967,6 @@ def psd(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John Wiley & Sons (1986) """ - if not self._hold: - self.cla() - if Fc is None: Fc = 0 @@ -7100,17 +7027,17 @@ def csd(self, x, y, NFFT=None, Fs=None, Fc=None, detrend=None, Parameters ---------- x, y : 1-D arrays or sequences - Arrays or sequences containing the data + Arrays or sequences containing the data. %(Spectral)s %(PSD)s - noverlap : integer + noverlap : int The number of points of overlap between segments. The default value is 0 (no overlap). - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7124,10 +7051,10 @@ def csd(self, x, y, NFFT=None, Fs=None, Fc=None, detrend=None, ------- Pxy : 1-D array The values for the cross spectrum `P_{xy}` before scaling - (complex valued) + (complex valued). freqs : 1-D array - The frequencies corresponding to the elements in *Pxy* + The frequencies corresponding to the elements in *Pxy*. line : a :class:`~matplotlib.lines.Line2D` instance The line created by this function. @@ -7157,9 +7084,6 @@ def csd(self, x, y, NFFT=None, Fs=None, Fc=None, detrend=None, Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John Wiley & Sons (1986) """ - if not self._hold: - self.cla() - if Fc is None: Fc = 0 @@ -7206,18 +7130,18 @@ def magnitude_spectrum(self, x, Fs=None, Fc=None, window=None, Parameters ---------- x : 1-D array or sequence - Array or sequence containing the data + Array or sequence containing the data. %(Spectral)s %(Single_Spectrum)s - scale : [ 'default' | 'linear' | 'dB' ] + scale : {'default', 'linear', 'dB'} The scaling of the values in the *spec*. 'linear' is no scaling. 'dB' returns the values in dB scale, i.e., the dB amplitude (20 * log10). 'default' is 'linear'. - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7226,13 +7150,13 @@ def magnitude_spectrum(self, x, Fs=None, Fc=None, window=None, Returns ------- spectrum : 1-D array - The values for the magnitude spectrum before scaling (real valued) + The values for the magnitude spectrum before scaling (real valued). freqs : 1-D array - The frequencies corresponding to the elements in *spectrum* + The frequencies corresponding to the elements in *spectrum*. line : a :class:`~matplotlib.lines.Line2D` instance - The line created by this function + The line created by this function. Other Parameters ---------------- @@ -7264,9 +7188,6 @@ def magnitude_spectrum(self, x, Fs=None, Fc=None, window=None, .. [Notes section required for data comment. See #10189.] """ - if not self._hold: - self.cla() - if Fc is None: Fc = 0 @@ -7311,13 +7232,13 @@ def angle_spectrum(self, x, Fs=None, Fc=None, window=None, Parameters ---------- x : 1-D array or sequence - Array or sequence containing the data + Array or sequence containing the data. %(Spectral)s %(Single_Spectrum)s - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7326,13 +7247,13 @@ def angle_spectrum(self, x, Fs=None, Fc=None, window=None, Returns ------- spectrum : 1-D array - The values for the angle spectrum in radians (real valued) + The values for the angle spectrum in radians (real valued). freqs : 1-D array - The frequencies corresponding to the elements in *spectrum* + The frequencies corresponding to the elements in *spectrum*. line : a :class:`~matplotlib.lines.Line2D` instance - The line created by this function + The line created by this function. Other Parameters ---------------- @@ -7361,9 +7282,6 @@ def angle_spectrum(self, x, Fs=None, Fc=None, window=None, .. [Notes section required for data comment. See #10189.] """ - if not self._hold: - self.cla() - if Fc is None: Fc = 0 @@ -7402,7 +7320,7 @@ def phase_spectrum(self, x, Fs=None, Fc=None, window=None, %(Single_Spectrum)s - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7411,13 +7329,13 @@ def phase_spectrum(self, x, Fs=None, Fc=None, window=None, Returns ------- spectrum : 1-D array - The values for the phase spectrum in radians (real valued) + The values for the phase spectrum in radians (real valued). freqs : 1-D array - The frequencies corresponding to the elements in *spectrum* + The frequencies corresponding to the elements in *spectrum*. line : a :class:`~matplotlib.lines.Line2D` instance - The line created by this function + The line created by this function. Other Parameters ---------------- @@ -7445,9 +7363,6 @@ def phase_spectrum(self, x, Fs=None, Fc=None, window=None, .. [Notes section required for data comment. See #10189.] """ - if not self._hold: - self.cla() - if Fc is None: Fc = 0 @@ -7482,11 +7397,11 @@ def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, %(PSD)s - noverlap : integer + noverlap : int The number of points of overlap between blocks. The default value is 0 (no overlap). - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7495,10 +7410,11 @@ def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, Returns ------- - The return value is a tuple (*Cxy*, *f*), where *f* are the - frequencies of the coherence vector. + Cxy : 1-D array + The coherence vector. - kwargs are applied to the lines. + freqs : 1-D array + The frequencies for the elements in *Cxy*. Other Parameters ---------------- @@ -7513,8 +7429,6 @@ def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John Wiley & Sons (1986) """ - if not self._hold: - self.cla() cxy, freqs = mlab.cohere(x=x, y=y, NFFT=NFFT, Fs=Fs, detrend=detrend, window=window, noverlap=noverlap, scale_by_freq=scale_by_freq) @@ -7561,18 +7475,18 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, %(PSD)s - mode : [ 'default' | 'psd' | 'magnitude' | 'angle' | 'phase' ] + mode : {'default', 'psd', 'magnitude', 'angle', 'phase'} What sort of spectrum to use. Default is 'psd', which takes the power spectral density. 'complex' returns the complex-valued frequency spectrum. 'magnitude' returns the magnitude spectrum. 'angle' returns the phase spectrum without unwrapping. 'phase' returns the phase spectrum with unwrapping. - noverlap : integer + noverlap : int The number of points of overlap between blocks. The default value is 128. - scale : [ 'default' | 'linear' | 'dB' ] + scale : {'default', 'linear', 'dB'} The scaling of the values in the *spec*. 'linear' is no scaling. 'dB' returns the values in dB scale. When *mode* is 'psd', this is dB power (10 * log10). Otherwise this is dB amplitude @@ -7580,7 +7494,7 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, 'magnitude' and 'linear' otherwise. This must be 'linear' if *mode* is 'angle' or 'phase'. - Fc : integer + Fc : int The center frequency of *x* (defaults to 0), which offsets the x extents of the plot to reflect the frequency range used when a signal is acquired and then filtered and downsampled to @@ -7590,7 +7504,7 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, A :class:`matplotlib.colors.Colormap` instance; if *None*, use default determined by rc - xextent : [None | (xmin, xmax)] + xextent : *None* or (xmin, xmax) The image extent along the x-axis. The default sets *xmin* to the left border of the first bin (*spectrum* column) and *xmax* to the right border of the last bin. Note that for *noverlap>0* the width @@ -7598,7 +7512,7 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, **kwargs : Additional kwargs are passed on to imshow which makes the - specgram image + specgram image. Returns ------- @@ -7639,9 +7553,6 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, The parameters *detrend* and *scale_by_freq* do only apply when *mode* is set to 'psd'. """ - if not self._hold: - self.cla() - if NFFT is None: NFFT = 256 # same default as in mlab.specgram() if Fc is None: @@ -7692,64 +7603,88 @@ def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None, return spec, freqs, t, im + @docstring.dedent_interpd def spy(self, Z, precision=0, marker=None, markersize=None, aspect='equal', origin="upper", **kwargs): """ - Plot the sparsity pattern on a 2-D array. + Plot the sparsity pattern of a 2D array. + + This visualizes the non-zero values of the array. + + Two plotting styles are available: image and marker. Both + are available for full arrays, but only the marker style + works for `scipy.sparse.spmatrix` instances. + + **Image style** + + If *marker* and *markersize* are *None*, `~.Axes.imshow` is used. Any + extra remaining kwargs are passed to this method. - ``spy(Z)`` plots the sparsity pattern of the 2-D array *Z*. + **Marker style** + + If *Z* is a `scipy.sparse.spmatrix` or *marker* or *markersize* are + *None*, a `~matplotlib.lines.Line2D` object will be returned with + the value of marker determining the marker type, and any + remaining kwargs passed to `~.Axes.plot`. Parameters ---------- - - Z : sparse array (n, m) + Z : array-like (M, N) The array to be plotted. - precision : float, optional, default: 0 - If *precision* is 0, any non-zero value will be plotted; else, + precision : float or 'present', optional, default: 0 + If *precision* is 0, any non-zero value will be plotted. Otherwise, values of :math:`|Z| > precision` will be plotted. - For :class:`scipy.sparse.spmatrix` instances, there is a special - case: if *precision* is 'present', any value present in the array + For :class:`scipy.sparse.spmatrix` instances, you can also + pass 'present'. In this case any value present in the array will be plotted, even if it is identically zero. - origin : ["upper", "lower"], optional, default: "upper" + origin : {'upper', 'lower'}, optional Place the [0,0] index of the array in the upper left or lower left - corner of the axes. + corner of the axes. The convention 'upper' is typically used for + matrices and images. + If not given, :rc:`image.origin` is used, defaulting to 'upper'. - aspect : ['auto' | 'equal' | scalar], optional, default: "equal" - If 'equal', and `extent` is None, changes the axes aspect ratio to - match that of the image. If `extent` is not `None`, the axes - aspect ratio is changed to match that of the extent. + aspect : {'equal', 'auto', None} or float, optional + Controls the aspect ratio of the axes. The aspect is of particular + relevance for images since it may distort the image, i.e. pixel + will not be square. + This parameter is a shortcut for explicitly calling + `.Axes.set_aspect`. See there for further details. - If 'auto', changes the image aspect ratio to match that of the - axes. + - 'equal': Ensures an aspect ratio of 1. Pixels will be square. + - 'auto': The axes is kept fixed and the aspect is adjusted so + that the data fit in the axes. In general, this will result in + non-square pixels. + - *None*: Use :rc:`image.aspect` (default: 'equal'). - If None, default to rc ``image.aspect`` value. + Default: 'equal' - Two plotting styles are available: image or marker. Both - are available for full arrays, but only the marker style - works for :class:`scipy.sparse.spmatrix` instances. + Returns + ------- + ret : `~matplotlib.image.AxesImage` or `.Line2D` + The return type depends on the plotting style (see above). - If *marker* and *markersize* are *None*, an image will be - returned and any remaining kwargs are passed to - :func:`~matplotlib.pyplot.imshow`; else, a - :class:`~matplotlib.lines.Line2D` object will be returned with - the value of marker determining the marker type, and any - remaining kwargs passed to the - :meth:`~matplotlib.axes.Axes.plot` method. + Other Parameters + ---------------- + **kwargs + The supported additional parameters depend on the plotting style. - If *marker* and *markersize* are *None*, useful kwargs include: + For the image style, you can pass the following additional + parameters of `~.Axes.imshow`: - * *cmap* - * *alpha* + - *cmap* + - *alpha* + - *url* + - any `.Artist` properties (passed on to the `.AxesImage`) - See also - -------- - imshow : for image options. - plot : for plotting options + For the marker style, you can pass any `.Line2D` property except + for *linestyle*: + + %(Line2D)s """ if marker is None and markersize is None and hasattr(Z, 'tocoo'): marker = 's' @@ -7786,8 +7721,8 @@ def spy(self, Z, precision=0, marker=None, markersize=None, marker=marker, markersize=markersize, **kwargs) self.add_line(marks) nr, nc = Z.shape - self.set_xlim(xmin=-0.5, xmax=nc - 0.5) - self.set_ylim(ymin=nr - 0.5, ymax=-0.5) + self.set_xlim(-0.5, nc - 0.5) + self.set_ylim(nr - 0.5, -0.5) self.set_aspect(aspect) ret = marks self.title.set_y(1.05) @@ -7810,7 +7745,7 @@ def matshow(self, Z, **kwargs): Parameters ---------- - Z : array-like(N, M) + Z : array-like(M, N) The matrix to be displayed. Returns @@ -7841,8 +7776,8 @@ def matshow(self, Z, **kwargs): nr, nc = Z.shape kw = {'origin': 'upper', 'interpolation': 'nearest', - 'aspect': 'equal'} # (already the imshow default) - kw.update(kwargs) + 'aspect': 'equal', # (already the imshow default) + **kwargs} im = self.imshow(Z, **kw) self.title.set_y(1.05) self.xaxis.tick_top() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 5265f1127015..01d6add25184 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1,23 +1,19 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - from collections import OrderedDict - -import six -from six.moves import xrange - import itertools -import warnings +import logging import math +from numbers import Real from operator import attrgetter +import types +import warnings import numpy as np import matplotlib -from matplotlib import cbook -from matplotlib.cbook import (_check_1d, _string_to_bool, iterable, - index_of, get_label) +from matplotlib import cbook, rcParams +from matplotlib.cbook import ( + _OrderedSet, _check_1d, _string_to_bool, iterable, index_of, get_label) from matplotlib import docstring import matplotlib.colors as mcolors import matplotlib.lines as mlines @@ -35,17 +31,9 @@ from matplotlib.artist import allow_rasterization from matplotlib.legend import Legend -from matplotlib.rcsetup import cycler -from matplotlib.rcsetup import validate_axisbelow - -rcParams = matplotlib.rcParams - -is_string_like = cbook.is_string_like -is_sequence_of_strings = cbook.is_sequence_of_strings +from matplotlib.rcsetup import cycler, validate_axisbelow -_hold_msg = """axes.hold is deprecated. - See the API Changes document (http://matplotlib.org/api/api_changes.html) - for more details.""" +_log = logging.getLogger(__name__) def _process_plot_format(fmt): @@ -62,7 +50,7 @@ def _process_plot_format(fmt): .. seealso:: :func:`~matplotlib.Line2D.lineStyles` and - :func:`~matplotlib.pyplot.colors` + :attr:`~matplotlib.colors.cnames` for all possible styles and color format string. """ @@ -158,7 +146,7 @@ def __init__(self, axes, command='plot'): self.set_prop_cycle() def __getstate__(self): - # note: it is not possible to pickle a itertools.cycle instance + # note: it is not possible to pickle a generator (and thus a cycler). return {'axes': self.axes, 'command': self.command} def __setstate__(self, state): @@ -166,6 +154,7 @@ def __setstate__(self, state): self.set_prop_cycle() def set_prop_cycle(self, *args, **kwargs): + # Can't do `args == (None,)` as that crashes cycler. if not (args or kwargs) or (len(args) == 1 and args[0] is None): prop_cycler = rcParams['axes.prop_cycle'] else: @@ -293,8 +282,7 @@ def _setdefaults(self, defaults, *kwargs): kw[k] = defaults[k] def _makeline(self, x, y, kw, kwargs): - kw = kw.copy() # Don't modify the original kw. - kw.update(kwargs) + kw = {**kw, **kwargs} # Don't modify the original kw. default_dict = self._getdefaults(None, kw) self._setdefaults(default_dict, kw) seg = mlines.Line2D(x, y, **kw) @@ -315,7 +303,7 @@ def _makefill(self, x, y, kw, kwargs): ignores = {'marker', 'markersize', 'markeredgecolor', 'markerfacecolor', 'markeredgewidth'} # Also ignore anything provided by *kwargs*. - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if v is not None: ignores.add(k) @@ -341,8 +329,7 @@ def _makefill(self, x, y, kw, kwargs): # modify the kwargs dictionary. self._setdefaults(default_dict, kwargs) - seg = mpatches.Polygon(np.hstack((x[:, np.newaxis], - y[:, np.newaxis])), + seg = mpatches.Polygon(np.column_stack((x, y)), facecolor=facecolor, fill=kwargs.get('fill', True), closed=kw['closed']) @@ -351,7 +338,7 @@ def _makefill(self, x, y, kw, kwargs): def _plot_args(self, tup, kwargs): ret = [] - if len(tup) > 1 and isinstance(tup[-1], six.string_types): + if len(tup) > 1 and isinstance(tup[-1], str): linestyle, marker, color = _process_plot_format(tup[-1]) tup = tup[:-1] elif len(tup) == 3: @@ -363,7 +350,7 @@ def _plot_args(self, tup, kwargs): # to one element array of None which causes problems # downstream. if any(v is None for v in tup): - raise ValueError("x and y must not be None") + raise ValueError("x, y, and format string must not be None") kw = {} for k, v in zip(('linestyle', 'marker', 'color'), @@ -392,7 +379,7 @@ def _plot_args(self, tup, kwargs): if ncx > 1 and ncy > 1 and ncx != ncy: cbook.warn_deprecated("2.2", "cycling among columns of inputs " "with non-matching shapes is deprecated.") - for j in xrange(max(ncx, ncy)): + for j in range(max(ncx, ncy)): seg = func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) ret.append(seg) return ret @@ -400,11 +387,10 @@ def _plot_args(self, tup, kwargs): def _grab_next_args(self, *args, **kwargs): while args: this, args = args[:2], args[2:] - if args and isinstance(args[0], six.string_types): + if args and isinstance(args[0], str): this += args[0], args = args[1:] - for seg in self._plot_args(this, kwargs): - yield seg + yield from self._plot_args(this, kwargs) class _AxesBase(martist.Artist): @@ -420,8 +406,6 @@ def __str__(self): return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format( type(self).__name__, self._position.bounds) - @docstring.Substitution(scale=' | '.join( - [repr(x) for x in mscale.get_scale_names()])) def __init__(self, fig, rect, facecolor=None, # defaults to rc axes.facecolor frameon=True, @@ -433,52 +417,32 @@ def __init__(self, fig, rect, **kwargs ): """ - Build an `~axes.Axes` instance in - `~matplotlib.figure.Figure` *fig* with - *rect=[left, bottom, width, height]* in - `~matplotlib.figure.Figure` coordinates + Build an axes in a figure. - Optional keyword arguments: + Parameters + ---------- + fig : `~matplotlib.figure.Figure` + The axes is build in the `.Figure` *fig*. + + rect : [left, bottom, width, height] + The axes is build in the rectangle *rect*. *rect* is in + `.Figure` coordinates. + + sharex, sharey : `~.axes.Axes`, optional + The x or y `~.matplotlib.axis` is shared with the x or + y axis in the input `~.axes.Axes`. - ================ ========================================= - Keyword Description - ================ ========================================= - *adjustable* [ 'box' | 'datalim' ] - *alpha* float: the alpha transparency (can be None) - *anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', - 'NW', 'W' ] - *aspect* [ 'auto' | 'equal' | aspect_ratio ] - *autoscale_on* bool; whether to autoscale the *viewlim* - *axisbelow* [ bool | 'line' ] draw the grids - and ticks below or above most other artists, - or below lines but above patches - *cursor_props* a (*float*, *color*) tuple - *figure* a :class:`~matplotlib.figure.Figure` - instance - *frame_on* bool; whether to draw the axes frame - *label* the axes label - *navigate* bool - *navigate_mode* [ 'PAN' | 'ZOOM' | None ] the navigation - toolbar button status - *position* [left, bottom, width, height] in - class:`~matplotlib.figure.Figure` coords - *sharex* an class:`~matplotlib.axes.Axes` instance - to share the x-axis with - *sharey* an class:`~matplotlib.axes.Axes` instance - to share the y-axis with - *title* the title string - *visible* bool, whether the axes is visible - *xlabel* the xlabel - *xlim* (*xmin*, *xmax*) view limits - *xscale* [%(scale)s] - *xticklabels* sequence of strings - *xticks* sequence of floats - *ylabel* the ylabel strings - *ylim* (*ymin*, *ymax*) view limits - *yscale* [%(scale)s] - *yticklabels* sequence of strings - *yticks* sequence of floats - ================ ========================================= + frameon : bool, optional + True means that the axes frame is visible. + + **kwargs + Other optional keyword arguments: + %(Axes)s + + Returns + ------- + axes : `~.axes.Axes` + The new `~.axes.Axes` object. """ martist.Artist.__init__(self) @@ -489,7 +453,6 @@ def __init__(self, fig, rect, if self._position.width < 0 or self._position.height < 0: raise ValueError('Width and height specified must be non-negative') self._originalPosition = self._position.frozen() - # self.set_axes(self) self.axes = self self._aspect = 'auto' self._adjustable = 'box' @@ -513,14 +476,9 @@ def __init__(self, fig, rect, facecolor = rcParams['axes.facecolor'] self._facecolor = facecolor self._frameon = frameon - self._axisbelow = rcParams['axes.axisbelow'] + self.set_axisbelow(rcParams['axes.axisbelow']) self._rasterization_zorder = None - - self._hold = rcParams['axes.hold'] - if self._hold is None: - self._hold = True - self._connected = {} # a dict from events to (id, func) self.cla() @@ -537,8 +495,7 @@ def __init__(self, fig, rect, if yscale: self.set_yscale(yscale) - if len(kwargs): - self.update(kwargs) + self.update(kwargs) if self.xaxis is not None: self._xcid = self.xaxis.callbacks.connect( @@ -584,24 +541,26 @@ def __init__(self, fig, rect, def __getstate__(self): # The renderer should be re-created by the figure, and then cached at # that point. - state = super(_AxesBase, self).__getstate__() - state['_cachedRenderer'] = None - state.pop('_layoutbox') - state.pop('_poslayoutbox') - + state = super().__getstate__() + for key in ['_cachedRenderer', '_layoutbox', '_poslayoutbox']: + state[key] = None + # Prune the sharing & twinning info to only contain the current group. + for grouper_name in [ + '_shared_x_axes', '_shared_y_axes', '_twinned_axes']: + grouper = getattr(self, grouper_name) + state[grouper_name] = (grouper.get_siblings(self) + if self in grouper else None) return state def __setstate__(self, state): + # Merge the grouping info back into the global groupers. + for grouper_name in [ + '_shared_x_axes', '_shared_y_axes', '_twinned_axes']: + siblings = state.pop(grouper_name) + if siblings: + getattr(self, grouper_name).join(*siblings) self.__dict__ = state - # put the _remove_method back on all artists contained within the axes - for container_name in ['lines', 'collections', 'tables', 'patches', - 'texts', 'images']: - container = getattr(self, container_name) - for artist in container: - artist._remove_method = container.remove self._stale = True - self._layoutbox = None - self._poslayoutbox = None def get_window_extent(self, *args, **kwargs): """ @@ -609,8 +568,12 @@ def get_window_extent(self, *args, **kwargs): *kwargs* are empty """ bbox = self.bbox - x_pad = self.xaxis.get_tick_padding() - y_pad = self.yaxis.get_tick_padding() + x_pad = 0 + if self.axison and self.xaxis.get_visible(): + x_pad = self.xaxis.get_tick_padding() + y_pad = 0 + if self.axison and self.yaxis.get_visible(): + y_pad = self.yaxis.get_tick_padding() return mtransforms.Bbox([[bbox.x0 - x_pad, bbox.y0 - y_pad], [bbox.x1 + x_pad, bbox.y1 + y_pad]]) @@ -628,8 +591,6 @@ def set_figure(self, fig): """ Set the `.Figure` for this `.Axes`. - .. ACCEPTS: `.Figure` - Parameters ---------- fig : `.Figure` @@ -734,7 +695,7 @@ def get_xaxis_text1_transform(self, pad_points): labels_align = matplotlib.rcParams["xtick.alignment"] return (self.get_xaxis_transform(which='tick1') + - mtransforms.ScaledTranslation(0, -1 * pad_points / 72.0, + mtransforms.ScaledTranslation(0, -1 * pad_points / 72, self.figure.dpi_scale_trans), "top", labels_align) @@ -761,7 +722,7 @@ def get_xaxis_text2_transform(self, pad_points): """ labels_align = matplotlib.rcParams["xtick.alignment"] return (self.get_xaxis_transform(which='tick2') + - mtransforms.ScaledTranslation(0, pad_points / 72.0, + mtransforms.ScaledTranslation(0, pad_points / 72, self.figure.dpi_scale_trans), "bottom", labels_align) @@ -813,7 +774,7 @@ def get_yaxis_text1_transform(self, pad_points): """ labels_align = matplotlib.rcParams["ytick.alignment"] return (self.get_yaxis_transform(which='tick1') + - mtransforms.ScaledTranslation(-1 * pad_points / 72.0, 0, + mtransforms.ScaledTranslation(-1 * pad_points / 72, 0, self.figure.dpi_scale_trans), labels_align, "right") @@ -841,7 +802,7 @@ def get_yaxis_text2_transform(self, pad_points): labels_align = matplotlib.rcParams["ytick.alignment"] return (self.get_yaxis_transform(which='tick2') + - mtransforms.ScaledTranslation(pad_points / 72.0, 0, + mtransforms.ScaledTranslation(pad_points / 72, 0, self.figure.dpi_scale_trans), labels_align, "left") @@ -849,12 +810,11 @@ def _update_transScale(self): self.transScale.set( mtransforms.blended_transform_factory( self.xaxis.get_transform(), self.yaxis.get_transform())) - if hasattr(self, "lines"): - for line in self.lines: - try: - line._transformed_path.invalidate() - except AttributeError: - pass + for line in getattr(self, "lines", []): # Not set during init. + try: + line._transformed_path.invalidate() + except AttributeError: + pass def get_position(self, original=False): """ @@ -875,6 +835,7 @@ def get_position(self, original=False): if original: return self._originalPosition.frozen() else: + self.apply_aspect() return self._position.frozen() def set_position(self, pos, which='both'): @@ -892,7 +853,7 @@ def set_position(self, pos, which='both'): pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox` The new position of the in `.Figure` coordinates. - which : ['both' | 'active' | 'original'], optional + which : {'both', 'active', 'original'}, optional Determines which position variables to change. """ @@ -933,14 +894,9 @@ def set_axes_locator(self, locator): """ Set the axes locator. - .. ACCEPTS: a callable object which takes an axes instance and - renderer and returns a bbox. - Parameters ---------- - locator : callable - A locator function, which takes an axes and a renderer and returns - a bbox. + locator : Callable[[Axes, Renderer], Bbox] """ self._axes_locator = locator self.stale = True @@ -959,7 +915,7 @@ def _set_artist_props(self, a): a.axes = self if a.mouseover: - self.mouseover_set.add(a) + self._mouseover_set.add(a) def _gen_axes_patch(self): """ @@ -991,11 +947,8 @@ def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): Intended to be overridden by new projection types. """ - return OrderedDict([ - ('left', mspines.Spine.linear_spine(self, 'left')), - ('right', mspines.Spine.linear_spine(self, 'right')), - ('bottom', mspines.Spine.linear_spine(self, 'bottom')), - ('top', mspines.Spine.linear_spine(self, 'top'))]) + return OrderedDict((side, mspines.Spine.linear_spine(self, side)) + for side in ['left', 'right', 'bottom', 'top']) def cla(self): """Clear the current axes.""" @@ -1013,7 +966,7 @@ def cla(self): self.xaxis.cla() self.yaxis.cla() - for name, spine in six.iteritems(self.spines): + for name, spine in self.spines.items(): spine.cla() self.ignore_existing_data_limits = True @@ -1025,7 +978,8 @@ def cla(self): self.xaxis.major = self._sharex.xaxis.major self.xaxis.minor = self._sharex.xaxis.minor x0, x1 = self._sharex.get_xlim() - self.set_xlim(x0, x1, emit=False, auto=None) + self.set_xlim(x0, x1, emit=False, + auto=self._sharex.get_autoscalex_on()) self.xaxis._scale = mscale.scale_factory( self._sharex.xaxis.get_scale(), self.xaxis) else: @@ -1039,7 +993,8 @@ def cla(self): self.yaxis.major = self._sharey.yaxis.major self.yaxis.minor = self._sharey.yaxis.minor y0, y1 = self._sharey.get_ylim() - self.set_ylim(y0, y1, emit=False, auto=None) + self.set_ylim(y0, y1, emit=False, + auto=self._sharey.get_autoscaley_on()) self.yaxis._scale = mscale.scale_factory( self._sharey.yaxis.get_scale(), self.yaxis) else: @@ -1049,14 +1004,16 @@ def cla(self): except TypeError: pass # update the minor locator for x and y axis based on rcParams - if (rcParams['xtick.minor.visible']): + if rcParams['xtick.minor.visible']: self.xaxis.set_minor_locator(mticker.AutoMinorLocator()) - if (rcParams['ytick.minor.visible']): + if rcParams['ytick.minor.visible']: self.yaxis.set_minor_locator(mticker.AutoMinorLocator()) - self._autoscaleXon = True - self._autoscaleYon = True + if self._sharex is None: + self._autoscaleXon = True + if self._sharey is None: + self._autoscaleYon = True self._xmargin = rcParams['axes.xmargin'] self._ymargin = rcParams['axes.ymargin'] self._tight = None @@ -1073,7 +1030,8 @@ def cla(self): self.tables = [] self.artists = [] self.images = [] - self.mouseover_set = set() + self._mouseover_set = _OrderedSet() + self.child_axes = [] self._current_image = None # strictly for pyplot via _sci, _gci self.legend_ = None self.collections = [] # collection.Collection instances @@ -1107,6 +1065,8 @@ def cla(self): # refactor this out so it can be called in ax.set_title if # pad argument used... self._set_title_offset_trans(title_offset_points) + # determine if the title position has been set manually: + self._autotitlepos = None for _title in (self.title, self._left_title, self._right_title): self._set_artist_props(_title) @@ -1139,9 +1099,9 @@ def cla(self): self.stale = True @property - @cbook.deprecated("2.1", alternative="Axes.patch") - def axesPatch(self): - return self.patch + @cbook.deprecated("3.0") + def mouseover_set(self): + return frozenset(self._mouseover_set) def clear(self): """Clear the axes.""" @@ -1153,9 +1113,8 @@ def get_facecolor(self): get_fc = get_facecolor def set_facecolor(self, color): - """Set the Axes facecolor. - - .. ACCEPTS: color + """ + Set the Axes facecolor. Parameters ---------- @@ -1172,7 +1131,7 @@ def _set_title_offset_trans(self, title_offset_points): or from set_title kwarg ``pad``. """ self.titleOffsetTrans = mtransforms.ScaledTranslation( - 0.0, title_offset_points / 72.0, + 0.0, title_offset_points / 72, self.figure.dpi_scale_trans) for _title in (self.title, self._left_title, self._right_title): _title.set_transform(self.transAxes + self.titleOffsetTrans) @@ -1244,6 +1203,7 @@ def set_prop_cycle(self, *args, **kwargs): if args and kwargs: raise TypeError("Cannot supply both positional and keyword " "arguments to this method.") + # Can't do `args == (None,)` as that crashes cycler. if len(args) == 1 and args[0] is None: prop_cycle = None else: @@ -1251,67 +1211,6 @@ def set_prop_cycle(self, *args, **kwargs): self._get_lines.set_prop_cycle(prop_cycle) self._get_patches_for_fill.set_prop_cycle(prop_cycle) - @cbook.deprecated('1.5', alternative='`.set_prop_cycle`') - def set_color_cycle(self, clist): - """ - Set the color cycle for any future plot commands on this Axes. - - Parameters - ---------- - clist - A list of mpl color specifiers. - """ - if clist is None: - # Calling set_color_cycle() or set_prop_cycle() with None - # effectively resets the cycle, but you can't do - # set_prop_cycle('color', None). So we are special-casing this. - self.set_prop_cycle(None) - else: - self.set_prop_cycle('color', clist) - - @cbook.deprecated("2.0") - def ishold(self): - """return the HOLD status of the axes - - The `hold` mechanism is deprecated and will be removed in - v3.0. - """ - - return self._hold - - @cbook.deprecated("2.0", message=_hold_msg) - def hold(self, b=None): - """ - Set the hold state. - - The ``hold`` mechanism is deprecated and will be removed in - v3.0. The behavior will remain consistent with the - long-time default value of True. - - If *hold* is *None* (default), toggle the *hold* state. Else - set the *hold* state to boolean value *b*. - - Examples:: - - # toggle hold - hold() - - # turn hold on - hold(True) - - # turn hold off - hold(False) - - When hold is *True*, subsequent plot commands will be added to - the current axes. When hold is *False*, the current axes and - figure will be cleared on the next plot command - - """ - if b is None: - self._hold = not self._hold - else: - self._hold = b - def get_aspect(self): return self._aspect @@ -1321,7 +1220,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): Parameters ---------- - aspect : ['auto' | 'equal'] or num + aspect : {'auto', 'equal'} or num Possible values: ======== ================================================ @@ -1334,7 +1233,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): aspect='equal'. ======== ================================================ - adjustable : None or ['box' | 'datalim'], optional + 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. @@ -1368,8 +1267,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): matplotlib.axes.Axes.set_anchor defining the position in case of extra space. """ - if not (isinstance(aspect, six.string_types) - and aspect in ('equal', 'auto')): + if not (isinstance(aspect, str) and aspect in ('equal', 'auto')): aspect = float(aspect) # raise ValueError if necessary if share: axes = set(self._shared_x_axes.get_siblings(self) @@ -1396,7 +1294,7 @@ def set_adjustable(self, adjustable, share=False): Parameters ---------- - adjustable : ['box' | 'datalim'] + adjustable : {'box', 'datalim'} If 'box', change the physical dimensions of the Axes. If 'datalim', change the ``x`` or ``y`` data limits. @@ -1404,8 +1302,6 @@ def set_adjustable(self, adjustable, share=False): If ``True``, apply the settings to all shared Axes. Default is ``False``. - .. ACCEPTS: [ 'box' | 'datalim'] - See Also -------- matplotlib.axes.Axes.set_aspect @@ -1424,8 +1320,8 @@ def set_adjustable(self, adjustable, share=False): and independently on each Axes as it is drawn. """ if adjustable == 'box-forced': - warnings.warn("The 'box-forced' keyword argument is deprecated" - " since 2.2.", cbook.mplDeprecation) + cbook.warn_deprecated( + "2.2", "box-forced", obj_type="keyword argument") if adjustable not in ('box', 'datalim', 'box-forced'): raise ValueError("argument must be 'box', or 'datalim'") if share: @@ -1459,11 +1355,9 @@ def set_anchor(self, anchor, share=False): anchor defines where the drawing area will be located within the available space. - .. ACCEPTS: [ 'C' | 'SW' | 'S' | 'SE' | 'E' | 'NE' | 'N' | 'NW' | 'W' ] - Parameters ---------- - anchor : str or 2-tuple of floats + anchor : 2-tuple of floats or {'C', 'SW', 'S', 'SE', ...} The anchor position may be either: - a sequence (*cx*, *cy*). *cx* and *cy* may range from 0 @@ -1576,7 +1470,7 @@ def apply_aspect(self, position=None): if aspect != "auto": warnings.warn( 'aspect is not supported for Axes with xscale=%s, ' - 'yscale=%s' % (xscale, yscale)) + 'yscale=%s' % (xscale, yscale), stacklevel=2) aspect = "auto" else: # some custom projections have their own scales. pass @@ -1688,9 +1582,10 @@ def apply_aspect(self, position=None): self.set_xbound((x0, x1)) def axis(self, *v, **kwargs): - """Set axis properties. + """ + Convenience method to get or set some axis properties. - Valid signatures:: + Call signatures:: xmin, xmax, ymin, ymax = axis() xmin, xmax, ymin, ymax = axis(list_arg) @@ -1699,49 +1594,54 @@ def axis(self, *v, **kwargs): Parameters ---------- - v : list of float or {'on', 'off', 'equal', 'tight', 'scaled',\ - 'normal', 'auto', 'image', 'square'} - Optional positional argument - - Axis data limits set from a list; or a command relating to axes: - - ========== ================================================ - Value Description - ========== ================================================ - 'on' Toggle axis lines and labels on - 'off' Toggle axis lines and labels off - 'equal' Equal scaling by changing limits - 'scaled' Equal scaling by changing box dimensions - 'tight' Limits set such that all data is shown - 'auto' Automatic scaling, fill rectangle with data - 'normal' Same as 'auto'; deprecated - 'image' 'scaled' with axis limits equal to data limits - 'square' Square plot; similar to 'scaled', but initially\ - forcing xmax-xmin = ymax-ymin - ========== ================================================ + v : List[float] or one of the strings listed below. + Optional positional-only argument + + If a list, set the axis data limits. If a string: + + ======== ========================================================== + Value Description + ======== ========================================================== + 'on' Turn on axis lines and labels. + 'off' Turn off axis lines and labels. + 'equal' Set equal scaling (i.e., make circles circular) by + changing axis limits. + 'scaled' Set equal scaling (i.e., make circles circular) by + changing dimensions of the plot box. + 'tight' Set limits just large enough to show all data. + 'auto' Automatic scaling (fill plot box with data). + 'normal' Same as 'auto'; deprecated. + 'image' 'scaled' with axis limits equal to data limits. + 'square' Square plot; similar to 'scaled', but initially forcing + ``xmax-xmin = ymax-ymin``. + ======== ========================================================== emit : bool, optional - Passed to set_{x,y}lim functions, if observers - are notified of axis limit change + Passed to set_{x,y}lim functions, if observers are notified of axis + limit change. xmin, ymin, xmax, ymax : float, optional - The axis limits to be set + The axis limits to be set. Returns ------- xmin, xmax, ymin, ymax : float - The axis limits + The axis limits. + See also + -------- + matplotlib.axes.Axes.set_xlim + matplotlib.axes.Axes.set_ylim """ - if len(v) == 0 and len(kwargs) == 0: + if len(v) == len(kwargs) == 0: xmin, xmax = self.get_xlim() ymin, ymax = self.get_ylim() return xmin, xmax, ymin, ymax emit = kwargs.get('emit', True) - if len(v) == 1 and isinstance(v[0], six.string_types): + if len(v) == 1 and isinstance(v[0], str): s = v[0].lower() if s == 'on': self.set_axis_on() @@ -1852,18 +1752,18 @@ def get_yticklines(self): # Adding and tracking artists def _sci(self, im): - """ - helper for :func:`~matplotlib.pyplot.sci`; - do not use elsewhere. + """Set the current image. + + This image will be the target of colormap functions like + `~.pyplot.viridis`, and other functions such as `~.pyplot.clim`. The + current image is an attribute of the current axes. """ if isinstance(im, matplotlib.contour.ContourSet): if im.collections[0] not in self.collections: - raise ValueError( - "ContourSet must be in current Axes") + raise ValueError("ContourSet must be in current Axes") elif im not in self.images and im not in self.collections: - raise ValueError( - "Argument must be an image, collection, or ContourSet in " - "this Axes") + raise ValueError("Argument must be an image, collection, or " + "ContourSet in this Axes") self._current_image = im def _gci(self): @@ -1895,16 +1795,41 @@ def add_artist(self, a): to manually update the dataLim if the artist is to be included in autoscaling. + If no ``transform`` has been specified when creating the artist (e.g. + ``artist.get_transform() == None``) then the transform is set to + ``ax.transData``. + Returns the artist. """ a.axes = self self.artists.append(a) + a._remove_method = self.artists.remove self._set_artist_props(a) a.set_clip_path(self.patch) - a._remove_method = lambda h: self.artists.remove(h) self.stale = True return a + def add_child_axes(self, ax): + """ + Add a :class:`~matplotlib.axes.Axesbase` instance + as a child to the axes. + + Returns the added axes. + + This is the lowlevel version. See `.axes.Axes.inset_axes` + """ + + # normally axes have themselves as the axes, but these need to have + # their parent... + # Need to bypass the getter... + ax._axes = self + ax.stale_callback = martist._stale_axes_callback + + self.child_axes.append(ax) + ax._remove_method = self.child_axes.remove + self.stale = True + return ax + def add_collection(self, collection, autolim=True): """ Add a :class:`~matplotlib.collections.Collection` instance @@ -1916,6 +1841,7 @@ def add_collection(self, collection, autolim=True): if not label: collection.set_label('_collection%d' % len(self.collections)) self.collections.append(collection) + collection._remove_method = self.collections.remove self._set_artist_props(collection) if collection.get_clip_path() is None: @@ -1924,7 +1850,6 @@ def add_collection(self, collection, autolim=True): if autolim: self.update_datalim(collection.get_datalim(self.transData)) - collection._remove_method = lambda h: self.collections.remove(h) self.stale = True return collection @@ -1938,7 +1863,7 @@ def add_image(self, image): if not image.get_label(): image.set_label('_image%d' % len(self.images)) self.images.append(image) - image._remove_method = lambda h: self.images.remove(h) + image._remove_method = self.images.remove self.stale = True return image @@ -1961,7 +1886,7 @@ def add_line(self, line): if not line.get_label(): line.set_label('_line%d' % len(self.lines)) self.lines.append(line) - line._remove_method = lambda h: self.lines.remove(h) + line._remove_method = self.lines.remove self.stale = True return line @@ -1971,7 +1896,7 @@ def _add_text(self, txt): """ self._set_artist_props(txt) self.texts.append(txt) - txt._remove_method = lambda h: self.texts.remove(h) + txt._remove_method = self.texts.remove self.stale = True return txt @@ -2034,7 +1959,7 @@ def add_patch(self, p): p.set_clip_path(self.patch) self._update_patch_limits(p) self.patches.append(p) - p._remove_method = lambda h: self.patches.remove(h) + p._remove_method = self.patches.remove return p def _update_patch_limits(self, patch): @@ -2080,7 +2005,7 @@ def add_table(self, tab): self._set_artist_props(tab) self.tables.append(tab) tab.set_clip_path(self.patch) - tab._remove_method = lambda h: self.tables.remove(h) + tab._remove_method = self.tables.remove return tab def add_container(self, container): @@ -2094,7 +2019,7 @@ def add_container(self, container): if not label: container.set_label('_container%d' % len(self.containers)) self.containers.append(container) - container.set_remove_method(lambda h: self.containers.remove(h)) + container._remove_method = self.containers.remove return container def _on_units_changed(self, scalex=False, scaley=False): @@ -2158,40 +2083,33 @@ def update_datalim_bounds(self, bounds): def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): """Look for unit *kwargs* and update the axis instances as necessary""" - if self.xaxis is None or self.yaxis is None: - return - - if xdata is not None: - # we only need to update if there is nothing set yet. - if not self.xaxis.have_units(): - self.xaxis.update_units(xdata) - - if ydata is not None: - # we only need to update if there is nothing set yet. - if not self.yaxis.have_units(): - self.yaxis.update_units(ydata) - - # process kwargs 2nd since these will override default units - if kwargs is not None: - xunits = kwargs.pop('xunits', self.xaxis.units) - if self.name == 'polar': - xunits = kwargs.pop('thetaunits', xunits) - if xunits != self.xaxis.units: - self.xaxis.set_units(xunits) - # If the units being set imply a different converter, - # we need to update. - if xdata is not None: - self.xaxis.update_units(xdata) - - yunits = kwargs.pop('yunits', self.yaxis.units) - if self.name == 'polar': - yunits = kwargs.pop('runits', yunits) - if yunits != self.yaxis.units: - self.yaxis.set_units(yunits) - # If the units being set imply a different converter, - # we need to update. - if ydata is not None: - self.yaxis.update_units(ydata) + def _process_single_axis(data, axis, unit_name, kwargs): + # Return if there's no axis set + if axis is None: + return kwargs + + if data is not None: + # We only need to update if there is nothing set yet. + if not axis.have_units(): + axis.update_units(data) + + # Check for units in the kwargs, and if present update axis + if kwargs is not None: + units = kwargs.pop(unit_name, axis.units) + if self.name == 'polar': + polar_units = {'xunits': 'thetaunits', 'yunits': 'runits'} + units = kwargs.pop(polar_units[unit_name], units) + + if units != axis.units: + axis.set_units(units) + # If the units being set imply a different converter, + # we need to update. + if data is not None: + axis.update_units(data) + return kwargs + + kwargs = _process_single_axis(xdata, self.xaxis, 'xunits', kwargs) + kwargs = _process_single_axis(ydata, self.yaxis, 'yunits', kwargs) return kwargs def in_axes(self, mouseevent): @@ -2223,8 +2141,6 @@ def set_autoscale_on(self, b): """ Set whether autoscaling is applied on plot commands - .. ACCEPTS: bool - Parameters ---------- b : bool @@ -2236,8 +2152,6 @@ def set_autoscalex_on(self, b): """ Set whether autoscaling for the x-axis is applied on plot commands - .. ACCEPTS: bool - Parameters ---------- b : bool @@ -2248,8 +2162,6 @@ def set_autoscaley_on(self, b): """ Set whether autoscaling for the y-axis is applied on plot commands - .. ACCEPTS: bool - Parameters ---------- b : bool @@ -2291,8 +2203,6 @@ def set_xmargin(self, m): I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in a range [0.2, 1.8]. - .. ACCEPTS: float greater than -0.5 - Parameters ---------- m : float greater than -0.5 @@ -2315,8 +2225,6 @@ def set_ymargin(self, m): I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in a range [0.2, 1.8]. - .. ACCEPTS: float greater than -0.5 - Parameters ---------- m : float greater than -0.5 @@ -2326,65 +2234,85 @@ def set_ymargin(self, m): self._ymargin = m self.stale = True - def margins(self, *args, **kw): + def margins(self, *margins, x=None, y=None, tight=True): """ Set or retrieve autoscaling margins. - signatures:: - - margins() - - returns xmargin, ymargin - - :: - - margins(margin) - - margins(xmargin, ymargin) - - margins(x=xmargin, y=ymargin) - - margins(..., tight=False) - - All three forms above set the xmargin and ymargin parameters. - All keyword parameters are optional. A single argument - specifies both xmargin and ymargin. The padding added to the end of - each interval is *margin* times the data interval. The *margin* must - be a float in the range [0, 1]. - - The *tight* parameter is passed to :meth:`autoscale_view` - , which is executed after a margin is changed; the default here is - *True*, on the assumption that when margins are specified, no - additional padding to match tick marks is usually desired. Setting - *tight* to *None* will preserve the previous setting. + The padding added to each limit of the axes is the *margin* + times the data interval. All input parameters must be floats + within the range [0, 1]. Passing both positional and keyword + arguments is invalid and will raise a TypeError. If no + arguments (positional or otherwise) are provided, the current + margins will remain in place and simply be returned. Specifying any margin changes only the autoscaling; for example, if *xmargin* is not None, then *xmargin* times the X data interval will be added to each end of that interval before it is used in autoscaling. - """ - if not args and not kw: + Parameters + ---------- + args : float, optional + If a single positional argument is provided, it specifies + both margins of the x-axis and y-axis limits. If two + positional arguments are provided, they will be interpreted + as *xmargin*, *ymargin*. If setting the margin on a single + axis is desired, use the keyword arguments described below. + + x, y : float, optional + Specific margin values for the x-axis and y-axis, + respectively. These cannot be used with positional + arguments, but can be used individually to alter on e.g., + only the y-axis. + + tight : bool, default is True + The *tight* parameter is passed to :meth:`autoscale_view`, + which is executed after a margin is changed; the default + here is *True*, on the assumption that when margins are + specified, no additional padding to match tick marks is + usually desired. Set *tight* to *None* will preserve + the previous setting. + + + Returns + ------- + xmargin, ymargin : float + + Notes + ----- + If a previously used Axes method such as :meth:`pcolor` has set + :attr:`use_sticky_edges` to `True`, only the limits not set by + the "sticky artists" will be modified. To force all of the + margins to be set, set :attr:`use_sticky_edges` to `False` + before calling :meth:`margins`. + + """ + + if margins and x is not None and y is not None: + raise TypeError('Cannot pass both positional and keyword ' + 'arguments for x and/or y.') + elif len(margins) == 1: + x = y = margins[0] + elif len(margins) == 2: + x, y = margins + elif margins: + raise TypeError('Must pass a single positional argument for all ' + 'margins, or one for each margin (x, y).') + + if x is None and y is None: + if tight is not True: + warnings.warn('ignoring tight=%r in get mode' % (tight,), + stacklevel=2) return self._xmargin, self._ymargin - tight = kw.pop('tight', True) - mx = kw.pop('x', None) - my = kw.pop('y', None) - if len(args) == 1: - mx = my = args[0] - elif len(args) == 2: - mx, my = args - elif len(args) > 2: - raise ValueError("more than two arguments were supplied") - if mx is not None: - self.set_xmargin(mx) - if my is not None: - self.set_ymargin(my) - - scalex = (mx is not None) - scaley = (my is not None) + if x is not None: + self.set_xmargin(x) + if y is not None: + self.set_ymargin(y) - self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley) + self.autoscale_view( + tight=tight, scalex=(x is not None), scaley=(y is not None) + ) def set_rasterization_zorder(self, z): """ @@ -2393,8 +2321,6 @@ def set_rasterization_zorder(self, z): z : float or None zorder below which artists are rasterized. ``None`` means that artists do not get rasterized based on zorder. - - .. ACCEPTS: float or None """ self._rasterization_zorder = z self.stale = True @@ -2418,7 +2344,7 @@ def autoscale(self, enable=True, axis='both', tight=None): True (default) turns autoscaling on, False turns it off. None leaves the autoscaling state unchanged. - axis : ['both' | 'x' | 'y'], optional + axis : {'both', 'x', 'y'}, optional which axis to operate on; default is 'both' tight: bool or None, optional @@ -2529,7 +2455,13 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, do_upper_margin = not np.any(np.isclose(x1, stickies)) x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos) x0t, x1t = transform.transform([x0, x1]) - delta = (x1t - x0t) * margin + + if (np.isfinite(x1t) and np.isfinite(x0t)): + delta = (x1t - x0t) * margin + else: + # If at least one bound isn't finite, set margin to zero + delta = 0 + if do_lower_margin: x0t -= delta if do_upper_margin: @@ -2551,6 +2483,50 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, def _get_axis_list(self): return (self.xaxis, self.yaxis) + def _update_title_position(self, renderer): + """ + Update the title position based on the bounding box enclosing + all the ticklabels and x-axis spine and xlabel... + """ + _log.debug('update_title_pos') + + if self._autotitlepos is not None and not self._autotitlepos: + _log.debug('title position was updated manually, not adjusting') + return + + titles = (self.title, self._left_title, self._right_title) + + if self._autotitlepos is None: + for title in titles: + x, y = title.get_position() + if not np.isclose(y, 1.0): + self._autotitlepos = False + _log.debug('not adjusting title pos because title was' + ' already placed manually: %f', y) + return + self._autotitlepos = True + + for title in titles: + x, y0 = title.get_position() + y = 1.0 + # need to check all our twins too... + axs = self._twinned_axes.get_siblings(self) + + for ax in axs: + try: + if (ax.xaxis.get_label_position() == 'top' + or ax.xaxis.get_ticks_position() == 'top'): + bb = ax.xaxis.get_tightbbox(renderer) + top = bb.ymax + # we don't need to pad because the padding is already + # in __init__: titleOffsetTrans + yn = self.transAxes.inverted().transform((0., top))[1] + y = max(y, yn) + except AttributeError: + pass + + title.set_position((x, y)) + # Drawing @allow_rasterization @@ -2564,6 +2540,7 @@ def draw(self, renderer=None, inframe=False): if not self.get_visible(): return renderer.open_group('axes') + # prevent triggering call backs during the draw process self._stale = True locator = self.get_axes_locator() @@ -2581,21 +2558,12 @@ def draw(self, renderer=None, inframe=False): # frame in the foreground. Do this before drawing the axis # objects so that the spine has the opportunity to update them. if not (self.axison and self._frameon): - for spine in six.itervalues(self.spines): + for spine in self.spines.values(): artists.remove(spine) - if self.axison and not inframe: - if self._axisbelow is True: - self.xaxis.set_zorder(0.5) - self.yaxis.set_zorder(0.5) - elif self._axisbelow is False: - self.xaxis.set_zorder(2.5) - self.yaxis.set_zorder(2.5) - else: - # 'line': above patches, below lines - self.xaxis.set_zorder(1.5) - self.yaxis.set_zorder(1.5) - else: + self._update_title_position(renderer) + + if not self.axison or inframe: for _axis in self._get_axis_list(): artists.remove(_axis) @@ -2612,6 +2580,7 @@ def draw(self, renderer=None, inframe=False): # rasterize artists with negative zorder # if the minimum zorder is negative, start rasterization rasterization_zorder = self._rasterization_zorder + if (rasterization_zorder is not None and artists and artists[0].zorder < rasterization_zorder): renderer.start_rasterizing() @@ -2666,17 +2635,13 @@ def get_renderer_cache(self): # Axes rectangle characteristics def get_frame_on(self): - """ - Get whether the axes rectangle patch is drawn. - """ + """Get whether the axes rectangle patch is drawn.""" return self._frameon def set_frame_on(self, b): """ Set whether the axes rectangle patch is drawn. - .. ACCEPTS: bool - Parameters ---------- b : bool @@ -2692,42 +2657,60 @@ def get_axisbelow(self): def set_axisbelow(self, b): """ - Set whether axis ticks and gridlines are above or below most artists. - - .. ACCEPTS: [ bool | 'line' ] + Set the zorder for the axes ticks and gridlines. Parameters ---------- b : bool or 'line' + ``True`` corresponds to a zorder of 0.5, ``False`` to a zorder of + 2.5, and ``"line"`` to a zorder of 1.5. + """ - self._axisbelow = validate_axisbelow(b) + self._axisbelow = axisbelow = validate_axisbelow(b) + if axisbelow is True: + zorder = 0.5 + elif axisbelow is False: + zorder = 2.5 + elif axisbelow == "line": + zorder = 1.5 + else: + raise ValueError("Unexpected axisbelow value") + for axis in self._get_axis_list(): + axis.set_zorder(zorder) self.stale = True @docstring.dedent_interpd def grid(self, b=None, which='major', axis='both', **kwargs): """ - Turn the axes grids on or off. + Configure the grid lines. - Set the axes grids on or off; *b* is a boolean. + Parameters + ---------- + b : bool or None + Whether to show the grid lines. If any *kwargs* are supplied, + it is assumed you want the grid on and *b* will be set to True. - If *b* is *None* and ``len(kwargs)==0``, toggle the grid state. If - *kwargs* are supplied, it is assumed that you want a grid and *b* - is thus set to *True*. + If *b* is *None* and there are no *kwargs*, this toggles the + visibility of the lines. - *which* can be 'major' (default), 'minor', or 'both' to control - whether major tick grids, minor tick grids, or both are affected. + which : {'major', 'minor', 'both'} + The grid lines to apply the changes on. - *axis* can be 'both' (default), 'x', or 'y' to control which - set of gridlines are drawn. + axis : {'both', 'x', 'y'} + The axis to apply the changes on. - *kwargs* are used to set the grid line properties, e.g.,:: + **kwargs : `.Line2D` properties + Define the line properties of the grid, e.g.:: - ax.grid(color='r', linestyle='-', linewidth=2) + grid(color='r', linestyle='-', linewidth=2) - Valid :class:`~matplotlib.lines.Line2D` kwargs are + Valid *kwargs* are - %(Line2D)s + %(Line2D)s + Notes + ----- + The grid will be drawn according to the axes' zorder and not its own. """ if len(kwargs): b = True @@ -2739,7 +2722,8 @@ def grid(self, b=None, which='major', axis='both', **kwargs): if axis == 'y' or axis == 'both': self.yaxis.grid(b, which=which, **kwargs) - def ticklabel_format(self, **kwargs): + def ticklabel_format(self, *, axis='both', style='', scilimits=None, + useOffset=None, useLocale=None, useMathText=None): """ Change the `~matplotlib.ticker.ScalarFormatter` used by default for linear axes. @@ -2749,6 +2733,7 @@ def ticklabel_format(self, **kwargs): ============== ========================================= Keyword Description ============== ========================================= + *axis* [ 'x' | 'y' | 'both' ] *style* [ 'sci' (or 'scientific') | 'plain' ] plain turns off scientific notation *scilimits* (m, n), pair of integers; if *style* @@ -2756,12 +2741,13 @@ def ticklabel_format(self, **kwargs): be used for numbers outside the range 10`m`:sup: to 10`n`:sup:. Use (0,0) to include all numbers. + Use (m,m) where m <> 0 to fix the order + of magnitude to 10`m`:sup:. *useOffset* [ bool | offset ]; if True, the offset will be calculated as needed; if False, no offset will be used; if a numeric offset is specified, it will be used. - *axis* [ 'x' | 'y' | 'both' ] *useLocale* If True, format the number according to the current locale. This affects things such as the character used for the @@ -2780,12 +2766,8 @@ def ticklabel_format(self, **kwargs): :exc:`AttributeError` will be raised. """ - style = kwargs.pop('style', '').lower() - scilimits = kwargs.pop('scilimits', None) - useOffset = kwargs.pop('useOffset', None) - useLocale = kwargs.pop('useLocale', None) - useMathText = kwargs.pop('useMathText', None) - axis = kwargs.pop('axis', 'both').lower() + style = style.lower() + axis = axis.lower() if scilimits is not None: try: m, n = scilimits @@ -2796,8 +2778,6 @@ def ticklabel_format(self, **kwargs): sb = True elif style == 'plain': sb = False - elif style == 'comma': - raise NotImplementedError("comma style remains to be added") elif style == '': sb = None else: @@ -2838,7 +2818,7 @@ def locator_params(self, axis='both', tight=None, **kwargs): Parameters ---------- - axis : ['both' | 'x' | 'y'], optional + axis : {'both', 'x', 'y'}, optional The axis on which to operate. tight : bool or None, optional @@ -3062,13 +3042,13 @@ def _validate_converted_limits(self, limit, convert): """ if limit is not None: converted_limit = convert(limit) - if (isinstance(converted_limit, float) and - (not np.isreal(converted_limit) or - not np.isfinite(converted_limit))): + if (isinstance(converted_limit, Real) + and not np.isfinite(converted_limit)): raise ValueError("Axis limits cannot be NaN or Inf") return converted_limit - def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): + def set_xlim(self, left=None, right=None, emit=True, auto=False, + *, xmin=None, xmax=None): """ Set the data limits for the x-axis @@ -3079,6 +3059,9 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): left : scalar, optional The left xlim (default: None, which leaves the left limit unchanged). + The left and right xlims may be passed as the tuple + (`left`, `right`) as the first positional argument (or as + the `left` keyword argument). right : scalar, optional The right xlim (default: None, which leaves the right limit @@ -3091,10 +3074,11 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): Whether to turn on autoscaling of the x-axis. True turns on, False turns off (default action), None leaves unchanged. - xlimits : tuple, optional - The left and right xlims may be passed as the tuple - (`left`, `right`) as the first positional argument (or as - the `left` keyword argument). + xmin, xmax : scalar, optional + These arguments are deprecated and will be removed in a future + version. They are equivalent to left and right respectively, + and it is an error to pass both `xmin` and `left` or + `xmax` and `right`. Returns ------- @@ -3125,15 +3109,20 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): >>> set_xlim(5000, 0) """ - if 'xmin' in kw: - left = kw.pop('xmin') - if 'xmax' in kw: - right = kw.pop('xmax') - if kw: - raise ValueError("unrecognized kwargs: %s" % list(kw)) - if right is None and iterable(left): left, right = left + if xmin is not None: + cbook.warn_deprecated('3.0', name='`xmin`', + alternative='`left`', obj_type='argument') + if left is not None: + raise TypeError('Cannot pass both `xmin` and `left`') + left = xmin + if xmax is not None: + cbook.warn_deprecated('3.0', name='`xmax`', + alternative='`right`', obj_type='argument') + if right is not None: + raise TypeError('Cannot pass both `xmax` and `right`') + right = xmax self._process_unit_info(xdata=(left, right)) left = self._validate_converted_limits(left, self.convert_xunits) @@ -3149,13 +3138,23 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): warnings.warn( ('Attempting to set identical left==right results\n' 'in singular transformations; automatically expanding.\n' - 'left=%s, right=%s') % (left, right)) + 'left=%s, right=%s') % (left, right), stacklevel=2) left, right = mtransforms.nonsingular(left, right, increasing=False) - if self.get_xscale() == 'log' and (left <= 0.0 or right <= 0.0): - warnings.warn( - 'Attempted to set non-positive xlimits for log-scale axis; ' - 'invalid limits will be ignored.') + if self.get_xscale() == 'log': + if left <= 0: + warnings.warn( + 'Attempted to set non-positive left xlim on a ' + 'log-scaled axis.\n' + 'Invalid limit will be ignored.', stacklevel=2) + left = old_left + if right <= 0: + warnings.warn( + 'Attempted to set non-positive right xlim on a ' + 'log-scaled axis.\n' + 'Invalid limit will be ignored.', stacklevel=2) + right = old_right + left, right = self.xaxis.limit_range_for_scale(left, right) self.viewLim.intervalx = (left, right) @@ -3184,11 +3183,9 @@ def set_xscale(self, value, **kwargs): """ Set the x-axis scale. - .. ACCEPTS: [ 'linear' | 'log' | 'symlog' | 'logit' | ... ] - Parameters ---------- - value : {"linear", "log", "symlog", "logit"} + value : {"linear", "log", "symlog", "logit", ...} scaling strategy to apply Notes @@ -3222,8 +3219,6 @@ def set_xticks(self, ticks, minor=False): """ Set the x ticks with list of *ticks* - .. ACCEPTS: list of tick locations. - Parameters ---------- ticks : list @@ -3290,11 +3285,9 @@ def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs): """ Set the x-tick labels with list of string labels. - .. ACCEPTS: list of string labels - Parameters ---------- - labels : list of str + labels : List[str] List of string labels. fontdict : dict, optional @@ -3388,7 +3381,8 @@ def get_ylim(self): """ return tuple(self.viewLim.intervaly) - def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): + def set_ylim(self, bottom=None, top=None, emit=True, auto=False, + *, ymin=None, ymax=None): """ Set the data limits for the y-axis @@ -3399,6 +3393,9 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): bottom : scalar, optional The bottom ylim (default: None, which leaves the bottom limit unchanged). + The bottom and top ylims may be passed as the tuple + (`bottom`, `top`) as the first positional argument (or as + the `bottom` keyword argument). top : scalar, optional The top ylim (default: None, which leaves the top limit @@ -3411,10 +3408,11 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): Whether to turn on autoscaling of the y-axis. True turns on, False turns off (default action), None leaves unchanged. - ylimits : tuple, optional - The bottom and top yxlims may be passed as the tuple - (`bottom`, `top`) as the first positional argument (or as - the `bottom` keyword argument). + ymin, ymax : scalar, optional + These arguments are deprecated and will be removed in a future + version. They are equivalent to bottom and top respectively, + and it is an error to pass both `xmin` and `bottom` or + `xmax` and `top`. Returns ------- @@ -3444,15 +3442,20 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): >>> set_ylim(5000, 0) """ - if 'ymin' in kw: - bottom = kw.pop('ymin') - if 'ymax' in kw: - top = kw.pop('ymax') - if kw: - raise ValueError("unrecognized kwargs: %s" % list(kw)) - if top is None and iterable(bottom): bottom, top = bottom + if ymin is not None: + cbook.warn_deprecated('3.0', name='`ymin`', + alternative='`bottom`', obj_type='argument') + if bottom is not None: + raise TypeError('Cannot pass both `ymin` and `bottom`') + bottom = ymin + if ymax is not None: + cbook.warn_deprecated('3.0', name='`ymax`', + alternative='`top`', obj_type='argument') + if top is not None: + raise TypeError('Cannot pass both `ymax` and `top`') + top = ymax bottom = self._validate_converted_limits(bottom, self.convert_yunits) top = self._validate_converted_limits(top, self.convert_yunits) @@ -3468,14 +3471,23 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): warnings.warn( ('Attempting to set identical bottom==top results\n' 'in singular transformations; automatically expanding.\n' - 'bottom=%s, top=%s') % (bottom, top)) + 'bottom=%s, top=%s') % (bottom, top), stacklevel=2) bottom, top = mtransforms.nonsingular(bottom, top, increasing=False) - if self.get_yscale() == 'log' and (bottom <= 0.0 or top <= 0.0): - warnings.warn( - 'Attempted to set non-positive ylimits for log-scale axis; ' - 'invalid limits will be ignored.') + if self.get_yscale() == 'log': + if bottom <= 0: + warnings.warn( + 'Attempted to set non-positive bottom ylim on a ' + 'log-scaled axis.\n' + 'Invalid limit will be ignored.', stacklevel=2) + bottom = old_bottom + if top <= 0: + warnings.warn( + 'Attempted to set non-positive top ylim on a ' + 'log-scaled axis.\n' + 'Invalid limit will be ignored.', stacklevel=2) + top = old_top bottom, top = self.yaxis.limit_range_for_scale(bottom, top) self.viewLim.intervaly = (bottom, top) @@ -3504,11 +3516,9 @@ def set_yscale(self, value, **kwargs): """ Set the y-axis scale. - .. ACCEPTS: [ 'linear' | 'log' | 'symlog' | 'logit' | ... ] - Parameters ---------- - value : {"linear", "log", "symlog", "logit"} + value : {"linear", "log", "symlog", "logit", ...} scaling strategy to apply Notes @@ -3541,11 +3551,9 @@ def set_yticks(self, ticks, minor=False): """ Set the y ticks with list of *ticks* - .. ACCEPTS: list of tick locations. - Parameters ---------- - ticks : sequence + ticks : list List of y-axis tick locations minor : bool, optional @@ -3608,11 +3616,9 @@ def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs): """ Set the y-tick labels with list of strings labels. - .. ACCEPTS: list of string labels - Parameters ---------- - labels : list of str + labels : List[str] list of string labels fontdict : dict, optional @@ -3703,7 +3709,12 @@ def format_coord(self, x, y): return 'x=%s y=%s' % (xs, ys) def minorticks_on(self): - 'Add autoscaling minor ticks to the axes.' + """ + Display minor ticks on the axes. + + Displaying minor ticks may reduce performance; you may turn them off + using `minorticks_off()` if drawing speed is a problem. + """ for ax in (self.xaxis, self.yaxis): scale = ax.get_scale() if scale == 'log': @@ -3745,8 +3756,6 @@ def set_navigate(self, b): """ Set whether the axes responds to navigation toolbar commands - .. ACCEPTS: bool - Parameters ---------- b : bool @@ -3877,7 +3886,7 @@ def _set_view_from_bbox(self, bbox, direction='in', # should be len 3 or 4 but nothing else warnings.warn( "Warning in _set_view_from_bbox: bounding box is not a tuple " - "of length 3 or 4. Ignoring the view change.") + "of length 3 or 4. Ignoring the view change.", stacklevel=2) return # Just grab bounding box @@ -3982,7 +3991,7 @@ def start_pan(self, x, y, button): Intended to be overridden by new projection types. """ - self._pan_start = cbook.Bunch( + self._pan_start = types.SimpleNamespace( lim=self.viewLim.frozen(), trans=self.transData.frozen(), trans_inverse=self.transData.inverted().frozen(), @@ -4045,7 +4054,7 @@ def format_deltas(key, dx, dy): p = self._pan_start dx = x - p.x dy = y - p.y - if dx == 0 and dy == 0: + if dx == dy == 0: return if button == 1: dx, dy = format_deltas(key, dx, dy) @@ -4064,8 +4073,10 @@ def format_deltas(key, dx, dy): result = (mtransforms.Bbox(newpoints) .transformed(p.trans_inverse)) except OverflowError: - warnings.warn('Overflow while panning') + warnings.warn('Overflow while panning', stacklevel=2) return + else: + return valid = np.isfinite(result.transformed(p.trans)) points = result.get_points().astype(object) @@ -4074,38 +4085,6 @@ def format_deltas(key, dx, dy): self.set_xlim(points[:, 0]) self.set_ylim(points[:, 1]) - @cbook.deprecated("2.1") - def get_cursor_props(self): - """ - Return the cursor propertiess as a (*linewidth*, *color*) - tuple, where *linewidth* is a float and *color* is an RGBA - tuple - """ - return self._cursorProps - - @cbook.deprecated("2.1") - def set_cursor_props(self, *args): - """Set the cursor property as - - Call signature :: - - ax.set_cursor_props(linewidth, color) - - or:: - - ax.set_cursor_props((linewidth, color)) - - ACCEPTS: a (*float*, *color*) tuple - """ - if len(args) == 1: - lw, c = args[0] - elif len(args) == 2: - lw, c = args - else: - raise ValueError('args must be a (linewidth, color) tuple') - c = mcolors.to_rgba(c) - self._cursorProps = lw, c - def get_children(self): """return a list of child artists""" children = [] @@ -4114,7 +4093,7 @@ def get_children(self): children.extend(self.lines) children.extend(self.texts) children.extend(self.artists) - children.extend(six.itervalues(self.spines)) + children.extend(self.spines.values()) children.append(self.xaxis) children.append(self.yaxis) children.append(self.title) @@ -4122,9 +4101,12 @@ def get_children(self): children.append(self._right_title) children.extend(self.tables) children.extend(self.images) + children.extend(self.child_axes) + if self.legend_ is not None: children.append(self.legend_) children.append(self.patch) + return children def contains(self, mouseevent): @@ -4159,19 +4141,47 @@ def pick(self, *args): martist.Artist.pick(self, args[0]) def get_default_bbox_extra_artists(self): + """ + Return a default list of artists that are used for the bounding box + calculation. + + Artists are excluded either by not being visible or + ``artist.set_in_layout(False)``. + """ return [artist for artist in self.get_children() - if artist.get_visible()] + if (artist.get_visible() and artist.get_in_layout())] - def get_tightbbox(self, renderer, call_axes_locator=True): + def get_tightbbox(self, renderer, call_axes_locator=True, + bbox_extra_artists=None): """ - Return the tight bounding box of the axes. - The dimension of the Bbox in canvas coordinate. + Return the tight bounding box of the axes, including axis and their + decorators (xlabel, title, etc). + + Artists that have ``artist.set_in_layout(False)`` are not included + in the bbox. + + Parameters + ---------- + renderer : `.RendererBase` instance + renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + bbox_extra_artists : list of `.Artist` or ``None`` + List of artists to include in the tight bounding box. If + ``None`` (default), then all artist children of the axes are + included in the tight bounding box. + + call_axes_locator : boolean (default ``True``) + If *call_axes_locator* is ``False``, it does not call the + ``_axes_locator`` attribute, which is necessary to get the correct + bounding box. ``call_axes_locator=False`` can be used if the + caller is only interested in the relative size of the tightbbox + compared to the axes bbox. - If *call_axes_locator* is *False*, it does not call the - _axes_locator attribute, which is necessary to get the correct - bounding box. ``call_axes_locator==False`` can be used if the - caller is only intereted in the relative size of the tightbbox - compared to the axes bbox. + Returns + ------- + bbox : `.BboxBase` + bounding box in figure pixel coordinates. """ bb = [] @@ -4186,6 +4196,11 @@ def get_tightbbox(self, renderer, call_axes_locator=True): else: self.apply_aspect() + bb_xaxis = self.xaxis.get_tightbbox(renderer) + if bb_xaxis: + bb.append(bb_xaxis) + + self._update_title_position(renderer) bb.append(self.get_window_extent(renderer)) if self.title.get_visible(): @@ -4195,19 +4210,18 @@ def get_tightbbox(self, renderer, call_axes_locator=True): if self._right_title.get_visible(): bb.append(self._right_title.get_window_extent(renderer)) - bb_xaxis = self.xaxis.get_tightbbox(renderer) - if bb_xaxis: - bb.append(bb_xaxis) - bb_yaxis = self.yaxis.get_tightbbox(renderer) if bb_yaxis: bb.append(bb_yaxis) - for child in self.get_children(): - if isinstance(child, OffsetBox) and child.get_visible(): - bb.append(child.get_window_extent(renderer)) - elif isinstance(child, Legend) and child.get_visible(): - bb.append(child._legend_box.get_window_extent(renderer)) + bbox_artists = bbox_extra_artists + if bbox_artists is None: + bbox_artists = self.get_default_bbox_extra_artists() + + for a in bbox_artists: + bbox = a.get_tightbbox(renderer) + if bbox is not None and (bbox.width != 0 or bbox.height != 0): + bb.append(bbox) _bbox = mtransforms.Bbox.union( [b for b in bb if b.width != 0 or b.height != 0]) diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 4c93ed996a16..c229ec9d3514 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -1,19 +1,12 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import map +import functools +import warnings -from matplotlib.gridspec import GridSpec, SubplotSpec from matplotlib import docstring import matplotlib.artist as martist from matplotlib.axes._axes import Axes - +from matplotlib.gridspec import GridSpec, SubplotSpec import matplotlib._layoutbox as layoutbox -import warnings -from matplotlib.cbook import mplDeprecation - class SubplotBase(object): """ @@ -94,18 +87,13 @@ def __init__(self, fig, *args, **kwargs): pos=True, subplot=True, artist=self) def __reduce__(self): - # get the first axes class which does not - # inherit from a subplotbase - - def not_subplotbase(c): - return issubclass(c, Axes) and not issubclass(c, SubplotBase) - - axes_class = [c for c in self.__class__.mro() - if not_subplotbase(c)][0] - r = [_PicklableSubplotClassConstructor(), - (axes_class,), - self.__getstate__()] - return tuple(r) + # get the first axes class which does not inherit from a subplotbase + axes_class = next( + c for c in type(self).__mro__ + if issubclass(c, Axes) and not issubclass(c, SubplotBase)) + return (_picklable_subplot_class_constructor, + (axes_class,), + self.__getstate__()) def get_geometry(self): """get the subplot geometry, e.g., 2,2,3""" @@ -128,6 +116,10 @@ def set_subplotspec(self, subplotspec): """set the SubplotSpec instance associated with the subplot""" self._subplotspec = subplotspec + def get_gridspec(self): + """get the GridSpec instance associated with the subplot""" + return self._subplotspec.get_gridspec() + def update_params(self): """update the subplot position from fig.subplotpars""" @@ -195,73 +187,56 @@ def _make_twin_axes(self, *kl, **kwargs): self._twinned_axes.join(self, ax2) return ax2 + +# this here to support cartopy which was using a private part of the +# API to register their Axes subclasses. + +# In 3.1 this should be changed to a dict subclass that warns on use +# In 3.3 to a dict subclass that raises a useful exception on use +# In 3.4 should be removed + +# The slow timeline is to give cartopy enough time to get several +# release out before we break them. _subplot_classes = {} +@functools.lru_cache(None) def subplot_class_factory(axes_class=None): - # This makes a new class that inherits from SubplotBase and the - # given axes_class (which is assumed to be a subclass of Axes). - # This is perhaps a little bit roundabout to make a new class on - # the fly like this, but it means that a new Subplot class does - # not have to be created for every type of Axes. + """ + This makes a new class that inherits from `.SubplotBase` and the + given axes_class (which is assumed to be a subclass of `.axes.Axes`). + This is perhaps a little bit roundabout to make a new class on + the fly like this, but it means that a new Subplot class does + not have to be created for every type of Axes. + """ if axes_class is None: axes_class = Axes + try: + # Avoid creating two different instances of GeoAxesSubplot... + # Only a temporary backcompat fix. This should be removed in + # 3.4 + return next(cls for cls in SubplotBase.__subclasses__() + if cls.__bases__ == (SubplotBase, axes_class)) + except StopIteration: + return type("%sSubplot" % axes_class.__name__, + (SubplotBase, axes_class), + {'_axes_class': axes_class}) - new_class = _subplot_classes.get(axes_class) - if new_class is None: - new_class = type(str("%sSubplot") % (axes_class.__name__), - (SubplotBase, axes_class), - {'_axes_class': axes_class}) - _subplot_classes[axes_class] = new_class - - return new_class # This is provided for backward compatibility Subplot = subplot_class_factory() -class _PicklableSubplotClassConstructor(object): +def _picklable_subplot_class_constructor(axes_class): """ - This stub class exists to return the appropriate subplot - class when __call__-ed with an axes class. This is purely to - allow Pickling of Axes and Subplots. + This stub class exists to return the appropriate subplot class when called + with an axes class. This is purely to allow pickling of Axes and Subplots. """ - def __call__(self, axes_class): - # create a dummy object instance - subplot_instance = _PicklableSubplotClassConstructor() - subplot_class = subplot_class_factory(axes_class) - # update the class to the desired subplot class - subplot_instance.__class__ = subplot_class - return subplot_instance + subplot_class = subplot_class_factory(axes_class) + return subplot_class.__new__(subplot_class) docstring.interpd.update(Axes=martist.kwdoc(Axes)) -docstring.interpd.update(Subplot=martist.kwdoc(Axes)) +docstring.dedent_interpd(Axes.__init__) -""" -# this is some discarded code I was using to find the minimum positive -# data point for some log scaling fixes. I realized there was a -# cleaner way to do it, but am keeping this around as an example for -# how to get the data out of the axes. Might want to make something -# like this a method one day, or better yet make get_verts an Artist -# method - - minx, maxx = self.get_xlim() - if minx<=0 or maxx<=0: - # find the min pos value in the data - xs = [] - for line in self.lines: - xs.extend(line.get_xdata(orig=False)) - for patch in self.patches: - xs.extend([x for x,y in patch.get_verts()]) - for collection in self.collections: - xs.extend([x for x,y in collection.get_verts()]) - posx = [x for x in xs if x>0] - if len(posx): - - minx = min(posx) - maxx = max(posx) - # warning, probably breaks inverted axis - self.set_xlim((0.1*minx, maxx)) - -""" +docstring.interpd.update(Subplot=martist.kwdoc(Axes)) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 48c31ae6c0cc..06a3925dd35a 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1,12 +1,12 @@ """ Classes for the ticks and x and y axis """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import datetime import logging +import warnings + +import numpy as np from matplotlib import rcParams import matplotlib.artist as artist @@ -21,8 +21,6 @@ import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms import matplotlib.units as munits -import numpy as np -import warnings _log = logging.getLogger(__name__) @@ -32,7 +30,7 @@ # allows all Line2D kwargs. _line_AI = artist.ArtistInspector(mlines.Line2D) _line_param_names = _line_AI.get_setters() -_line_param_aliases = [list(d.keys())[0] for d in _line_AI.aliasd.values()] +_line_param_aliases = [list(d)[0] for d in _line_AI.aliasd.values()] _gridline_param_names = ['grid_' + name for name in _line_param_names + _line_param_aliases] @@ -190,7 +188,7 @@ def __init__(self, axes, loc, label, self.update_position(loc) def _set_labelrotation(self, labelrotation): - if isinstance(labelrotation, six.string_types): + if isinstance(labelrotation, str): mode = labelrotation angle = 0 elif isinstance(labelrotation, (tuple, list)): @@ -236,7 +234,7 @@ def set_clip_path(self, clippath, transform=None): set_clip_path.__doc__ = artist.Artist.set_clip_path.__doc__ def get_pad_pixels(self): - return self.figure.dpi * self._base_pad / 72.0 + return self.figure.dpi * self._base_pad / 72 def contains(self, mouseevent): """ @@ -253,7 +251,9 @@ def set_pad(self, val): """ Set the tick label pad in points - ACCEPTS: float + Parameters + ---------- + val : float """ self._apply_params(pad=val) self.stale = True @@ -310,9 +310,11 @@ def draw(self, renderer): def set_label1(self, s): """ - Set the text of ticklabel + Set the label1 text. - ACCEPTS: str + Parameters + ---------- + s : str """ self.label1.set_text(s) self.stale = True @@ -321,9 +323,11 @@ def set_label1(self, s): def set_label2(self, s): """ - Set the text of ticklabel2 + Set the label2 text. - ACCEPTS: str + Parameters + ---------- + s : str """ self.label2.set_text(s) self.stale = True @@ -336,12 +340,10 @@ def get_view_interval(self): raise NotImplementedError('Derived must override') def _apply_params(self, **kw): - switchkw = ['gridOn', 'tick1On', 'tick2On', 'label1On', 'label2On'] - switches = [k for k in kw if k in switchkw] - for k in switches: - setattr(self, k, kw.pop(k)) - newmarker = [k for k in kw if k in ['size', 'width', 'pad', 'tickdir']] - if newmarker: + for name in ['gridOn', 'tick1On', 'tick2On', 'label1On', 'label2On']: + if name in kw: + setattr(self, name, kw.pop(name)) + if any(k in kw for k in ['size', 'width', 'pad', 'tickdir']): self._size = kw.pop('size', self._size) # Width could be handled outside this block, but it is # convenient to leave it here. @@ -360,39 +362,33 @@ def _apply_params(self, **kw): self.label1.set_transform(trans) trans = self._get_text2_transform()[0] self.label2.set_transform(trans) - tick_kw = {k: v for k, v in six.iteritems(kw) - if k in ['color', 'zorder']} - if tick_kw: - self.tick1line.set(**tick_kw) - self.tick2line.set(**tick_kw) - for k, v in six.iteritems(tick_kw): - setattr(self, '_' + k, v) + tick_kw = {k: v for k, v in kw.items() if k in ['color', 'zorder']} + self.tick1line.set(**tick_kw) + self.tick2line.set(**tick_kw) + for k, v in tick_kw.items(): + setattr(self, '_' + k, v) if 'labelrotation' in kw: self._set_labelrotation(kw.pop('labelrotation')) self.label1.set(rotation=self._labelrotation[1]) self.label2.set(rotation=self._labelrotation[1]) - label_list = [k for k in six.iteritems(kw) - if k[0] in ['labelsize', 'labelcolor']] - if label_list: - label_kw = {k[5:]: v for k, v in label_list} - self.label1.set(**label_kw) - self.label2.set(**label_kw) - for k, v in six.iteritems(label_kw): - # for labelsize the text objects covert str ('small') - # -> points. grab the integer from the `Text` object - # instead of saving the string representation - v = getattr(self.label1, 'get_' + k)() - setattr(self, '_label' + k, v) - - grid_list = [k for k in six.iteritems(kw) - if k[0] in _gridline_param_names] - if grid_list: - grid_kw = {k[5:]: v for k, v in grid_list} - self.gridline.set(**grid_kw) - for k, v in six.iteritems(grid_kw): - setattr(self, '_grid_' + k, v) + label_kw = {k[5:]: v for k, v in kw.items() + if k in ['labelsize', 'labelcolor']} + self.label1.set(**label_kw) + self.label2.set(**label_kw) + for k, v in label_kw.items(): + # for labelsize the text objects covert str ('small') + # -> points. grab the integer from the `Text` object + # instead of saving the string representation + v = getattr(self.label1, 'get_' + k)() + setattr(self, '_label' + k, v) + + grid_kw = {k[5:]: v for k, v in kw.items() + if k in _gridline_param_names} + self.gridline.set(**grid_kw) + for k, v in grid_kw.items(): + setattr(self, '_grid_' + k, v) def update_position(self, loc): 'Set the location of tick in data coords with scalar *loc*' @@ -985,8 +981,7 @@ def iter_ticks(self): (minorTicks, minorLocs, minorLabels)] for group in major_minor: - for tick in zip(*group): - yield tick + yield from zip(*group) def get_ticklabel_extents(self, renderer): """ @@ -1156,8 +1151,10 @@ def get_tightbbox(self, renderer): bb = [] for a in [self.label, self.offsetText]: - if a.get_visible(): - bb.append(a.get_window_extent(renderer)) + bbox = a.get_window_extent(renderer) + if (np.isfinite(bbox.width) and np.isfinite(bbox.height) and + a.get_visible()): + bb.append(bbox) bb.extend(ticklabelBoxes) bb.extend(ticklabelBoxes2) @@ -1175,9 +1172,7 @@ def get_tick_padding(self): values.append(self.majorTicks[0].get_tick_padding()) if len(self.minorTicks): values.append(self.minorTicks[0].get_tick_padding()) - if len(values): - return max(values) - return 0.0 + return max(values, default=0) @allow_rasterization def draw(self, renderer, *args, **kwargs): @@ -1422,16 +1417,25 @@ def get_minor_ticks(self, numticks=None): def grid(self, b=None, which='major', **kwargs): """ - Set the axis grid on or off; b is a boolean. Use *which* = - 'major' | 'minor' | 'both' to set the grid for major or minor ticks. + Configure the grid lines. + + Parameters + ---------- + b : bool or None + Whether to show the grid lines. If any *kwargs* are supplied, + it is assumed you want the grid on and *b* will be set to True. - If *b* is *None* and len(kwargs)==0, toggle the grid state. If - *kwargs* are supplied, it is assumed you want the grid on and *b* - will be set to True. + If *b* is *None* and there are no *kwargs*, this toggles the + visibility of the lines. - *kwargs* are used to set the line properties of the grids, e.g., + which : {'major', 'minor', 'both'} + The grid lines to apply the changes on. + + **kwargs : `.Line2D` properties + Define the line properties of the grid, e.g.:: + + grid(color='r', linestyle='-', linewidth=2) - xax.grid(color='r', linestyle='-', linewidth=2) """ if len(kwargs): b = True @@ -1551,7 +1555,8 @@ def get_units(self): return self.units def set_label_text(self, label, fontdict=None, **kwargs): - """ Sets the text value of the axis label + """ + Set the text value of the axis label. ACCEPTS: A string value for the label """ @@ -1565,10 +1570,15 @@ def set_label_text(self, label, fontdict=None, **kwargs): def set_major_formatter(self, formatter): """ - Set the formatter of the major ticker + Set the formatter of the major ticker. - ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance + Parameters + ---------- + formatter : ~matplotlib.ticker.Formatter """ + if not isinstance(formatter, mticker.Formatter): + raise TypeError("formatter argument should be instance of " + "matplotlib.ticker.Formatter") self.isDefault_majfmt = False self.major.formatter = formatter formatter.set_axis(self) @@ -1576,10 +1586,15 @@ def set_major_formatter(self, formatter): def set_minor_formatter(self, formatter): """ - Set the formatter of the minor ticker + Set the formatter of the minor ticker. - ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance + Parameters + ---------- + formatter : ~matplotlib.ticker.Formatter """ + if not isinstance(formatter, mticker.Formatter): + raise TypeError("formatter argument should be instance of " + "matplotlib.ticker.Formatter") self.isDefault_minfmt = False self.minor.formatter = formatter formatter.set_axis(self) @@ -1587,10 +1602,15 @@ def set_minor_formatter(self, formatter): def set_major_locator(self, locator): """ - Set the locator of the major ticker + Set the locator of the major ticker. - ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance + Parameters + ---------- + locator : ~matplotlib.ticker.Locator """ + if not isinstance(locator, mticker.Locator): + raise TypeError("formatter argument should be instance of " + "matplotlib.ticker.Locator") self.isDefault_majloc = False self.major.locator = locator locator.set_axis(self) @@ -1598,10 +1618,15 @@ def set_major_locator(self, locator): def set_minor_locator(self, locator): """ - Set the locator of the minor ticker + Set the locator of the minor ticker. - ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance + Parameters + ---------- + locator : ~matplotlib.ticker.Locator """ + if not isinstance(locator, mticker.Locator): + raise TypeError("formatter argument should be instance of " + "matplotlib.ticker.Locator") self.isDefault_minloc = False self.minor.locator = locator locator.set_axis(self) @@ -1609,13 +1634,15 @@ def set_minor_locator(self, locator): def set_pickradius(self, pickradius): """ - Set the depth of the axis used by the picker + Set the depth of the axis used by the picker. - ACCEPTS: a distance in points + Parameters + ---------- + pickradius : float """ self.pickradius = pickradius - def set_ticklabels(self, ticklabels, *args, **kwargs): + def set_ticklabels(self, ticklabels, *args, minor=False, **kwargs): """ Set the text values of the tick labels. Return a list of Text instances. Use *kwarg* *minor=True* to select minor ticks. @@ -1644,7 +1671,6 @@ def set_ticklabels(self, ticklabels, *args, **kwargs): # replace the ticklabels list with the processed one ticklabels = get_labels - minor = kwargs.pop('minor', False) if minor: self.set_minor_formatter(mticker.FixedFormatter(ticklabels)) ticks = self.get_minor_ticks() @@ -1726,14 +1752,12 @@ def axis_date(self, tz=None): *tz* is a :class:`tzinfo` instance or a timezone string. This timezone is used to create date labels. """ - # By providing a sample datetime instance with the desired - # timezone, the registered converter can be selected, - # and the "units" attribute, which is the timezone, can - # be set. - import datetime - if isinstance(tz, six.string_types): - import pytz - tz = pytz.timezone(tz) + # By providing a sample datetime instance with the desired timezone, + # the registered converter can be selected, and the "units" attribute, + # which is the timezone, can be set. + if isinstance(tz, str): + import dateutil.tz + tz = dateutil.tz.gettz(tz) self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz)) def get_tick_space(self): @@ -1753,7 +1777,9 @@ def set_label_position(self, position): """ Set the label position (top or bottom) - ACCEPTS: [ 'top' | 'bottom' ] + Parameters + ---------- + position : {'top', 'bottom'} """ raise NotImplementedError() @@ -1779,9 +1805,9 @@ def contains(self, mouseevent): return False, {} l, b = self.axes.transAxes.transform_point((0, 0)) r, t = self.axes.transAxes.transform_point((1, 1)) - inaxis = xaxes >= 0 and xaxes <= 1 and ( - (y < b and y > b - self.pickradius) or - (y > t and y < t + self.pickradius)) + inaxis = 0 <= xaxes <= 1 and ( + b - self.pickradius < y < b or + t < y < t + self.pickradius) return inaxis, {} def _get_tick(self, major): @@ -1863,7 +1889,9 @@ def set_label_position(self, position): """ Set the label position (top or bottom) - ACCEPTS: [ 'top' | 'bottom' ] + Parameters + ---------- + position : {'top', 'bottom'} """ if position == 'top': self.label.set_verticalalignment('baseline') @@ -1918,7 +1946,7 @@ def _update_label_position(self, renderer): bottom = bbox.y0 self.label.set_position( - (x, bottom - self.labelpad * self.figure.dpi / 72.0) + (x, bottom - self.labelpad * self.figure.dpi / 72) ) else: @@ -1933,7 +1961,7 @@ def _update_label_position(self, renderer): top = bbox.y1 self.label.set_position( - (x, top + self.labelpad * self.figure.dpi / 72.0) + (x, top + self.labelpad * self.figure.dpi / 72) ) def _update_offset_text_position(self, bboxes, bboxes2): @@ -1948,7 +1976,7 @@ def _update_offset_text_position(self, bboxes, bboxes2): bbox = mtransforms.Bbox.union(bboxes) bottom = bbox.y0 self.offsetText.set_position( - (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72.0) + (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72) ) def get_text_heights(self, renderer): @@ -1982,7 +2010,9 @@ def set_ticks_position(self, position): can be used if you don't want any ticks. 'none' and 'both' affect only the ticks, not the labels. - ACCEPTS: [ 'top' | 'bottom' | 'both' | 'default' | 'none' ] + Parameters + ---------- + position : {'top', 'bottom', 'both', 'default', 'none'} """ if position == 'top': self.set_tick_params(which='both', top=True, labeltop=True, @@ -2120,7 +2150,7 @@ def set_default_intervals(self): def get_tick_space(self): ends = self.axes.transAxes.transform([[0, 0], [1, 0]]) - length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72.0 + length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72 tick = self._get_tick(True) # There is a heuristic here that the aspect ratio of tick text # is no more than 3:1 @@ -2151,9 +2181,9 @@ def contains(self, mouseevent): return False, {} l, b = self.axes.transAxes.transform_point((0, 0)) r, t = self.axes.transAxes.transform_point((1, 1)) - inaxis = yaxes >= 0 and yaxes <= 1 and ( - (x < l and x > l - self.pickradius) or - (x > r and x < r + self.pickradius)) + inaxis = 0 <= yaxes <= 1 and ( + l - self.pickradius < x < l or + r < x < r + self.pickradius) return inaxis, {} def _get_tick(self, major): @@ -2230,7 +2260,9 @@ def set_label_position(self, position): """ Set the label position (left or right) - ACCEPTS: [ 'left' | 'right' ] + Parameters + ---------- + position : {'left', 'right'} """ self.label.set_rotation_mode('anchor') self.label.set_horizontalalignment('center') @@ -2286,7 +2318,7 @@ def _update_label_position(self, renderer): bbox = mtransforms.Bbox.union(bboxes + [spinebbox]) left = bbox.x0 self.label.set_position( - (left - self.labelpad * self.figure.dpi / 72.0, y) + (left - self.labelpad * self.figure.dpi / 72, y) ) else: @@ -2301,7 +2333,7 @@ def _update_label_position(self, renderer): right = bbox.x1 self.label.set_position( - (right + self.labelpad * self.figure.dpi / 72.0, y) + (right + self.labelpad * self.figure.dpi / 72, y) ) def _update_offset_text_position(self, bboxes, bboxes2): @@ -2312,12 +2344,14 @@ def _update_offset_text_position(self, bboxes, bboxes2): x, y = self.offsetText.get_position() top = self.axes.bbox.ymax self.offsetText.set_position( - (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72.0) + (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72) ) def set_offset_position(self, position): """ - .. ACCEPTS: [ 'left' | 'right' ] + Parameters + ---------- + position : {'left', 'right'} """ x, y = self.offsetText.get_position() if position == 'left': @@ -2358,7 +2392,9 @@ def set_ticks_position(self, position): can be used if you don't want any ticks. 'none' and 'both' affect only the ticks, not the labels. - ACCEPTS: [ 'left' | 'right' | 'both' | 'default' | 'none' ] + Parameters + ---------- + position : {'left', 'right', 'both', 'default', 'none'} """ if position == 'right': self.set_tick_params(which='both', right=True, labelright=True, @@ -2499,7 +2535,7 @@ def set_default_intervals(self): def get_tick_space(self): ends = self.axes.transAxes.transform([[0, 0], [0, 1]]) - length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72.0 + length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72 tick = self._get_tick(True) # Having a spacing of at least 2 just looks good. size = tick.label1.get_size() * 2.0 diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 136f567ebcd7..9810178ee38b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -32,14 +32,7 @@ The base class for the messaging area. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange - from contextlib import contextmanager -from functools import partial import importlib import io import os @@ -59,7 +52,12 @@ try: from PIL import Image - _has_pil = True + from PIL import PILLOW_VERSION + from distutils.version import LooseVersion + if LooseVersion(PILLOW_VERSION) >= "3.4": + _has_pil = True + else: + _has_pil = False del Image except ImportError: _has_pil = False @@ -120,126 +118,12 @@ def get_registered_canvas_class(format): if format not in _default_backends: return None backend_class = _default_backends[format] - if isinstance(backend_class, six.string_types): + if isinstance(backend_class, str): backend_class = importlib.import_module(backend_class).FigureCanvas _default_backends[format] = backend_class return backend_class -class _Backend(object): - # A backend can be defined by using the following pattern: - # - # @_Backend.export - # class FooBackend(_Backend): - # # override the attributes and methods documented below. - - # The following attributes and methods must be overridden by subclasses. - - # The `FigureCanvas` and `FigureManager` classes must be defined. - FigureCanvas = None - FigureManager = None - - # The following methods must be left as None for non-interactive backends. - # For interactive backends, `trigger_manager_draw` should be a function - # taking a manager as argument and triggering a canvas draw, and `mainloop` - # should be a function taking no argument and starting the backend main - # loop. - trigger_manager_draw = None - mainloop = None - - # The following methods will be automatically defined and exported, but - # can be overridden. - - @classmethod - def new_figure_manager(cls, num, *args, **kwargs): - """Create a new figure manager instance. - """ - # This import needs to happen here due to circular imports. - from matplotlib.figure import Figure - fig_cls = kwargs.pop('FigureClass', Figure) - fig = fig_cls(*args, **kwargs) - return cls.new_figure_manager_given_figure(num, fig) - - @classmethod - def new_figure_manager_given_figure(cls, num, figure): - """Create a new figure manager instance for the given figure. - """ - canvas = cls.FigureCanvas(figure) - manager = cls.FigureManager(canvas, num) - return manager - - @classmethod - def draw_if_interactive(cls): - if cls.trigger_manager_draw is not None and is_interactive(): - manager = Gcf.get_active() - if manager: - cls.trigger_manager_draw(manager) - - @classmethod - def show(cls, block=None): - """Show all figures. - - `show` blocks by calling `mainloop` if *block* is ``True``, or if it - is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in - `interactive` mode. - """ - if cls.mainloop is None: - return - managers = Gcf.get_all_fig_managers() - if not managers: - return - for manager in managers: - manager.show() - if block is None: - # Hack: Are we in IPython's pylab mode? - from matplotlib import pyplot - try: - # IPython versions >= 0.10 tack the _needmain attribute onto - # pyplot.show, and always set it to False, when in %pylab mode. - ipython_pylab = not pyplot.show._needmain - except AttributeError: - ipython_pylab = False - block = not ipython_pylab and not is_interactive() - # TODO: The above is a hack to get the WebAgg backend working with - # ipython's `%pylab` mode until proper integration is implemented. - if get_backend() == "WebAgg": - block = True - if block: - cls.mainloop() - - # This method is the one actually exporting the required methods. - - @staticmethod - def export(cls): - for name in ["FigureCanvas", - "FigureManager", - "new_figure_manager", - "new_figure_manager_given_figure", - "draw_if_interactive", - "show"]: - setattr(sys.modules[cls.__module__], name, getattr(cls, name)) - - # For back-compatibility, generate a shim `Show` class. - - class Show(ShowBase): - def mainloop(self): - return cls.mainloop() - - setattr(sys.modules[cls.__module__], "Show", Show) - return cls - - -class ShowBase(_Backend): - """ - Simple base class to generate a show() callable in backends. - - Subclass must override mainloop() method. - """ - - def __call__(self, block=None): - return self.show(block=block) - - class RendererBase(object): """An abstract base class to handle drawing/rendering operations. @@ -440,7 +324,7 @@ def _iter_collection_raw_paths(self, master_transform, paths, return transform = transforms.IdentityTransform() - for i in xrange(N): + for i in range(N): path = paths[i % Npaths] if Ntransforms: transform = Affine2D(all_transforms[i % Ntransforms]) @@ -457,7 +341,7 @@ def _iter_collection_uses_per_path(self, paths, all_transforms, is not the same for every path. """ Npaths = len(paths) - if Npaths == 0 or (len(facecolors) == 0 and len(edgecolors) == 0): + if Npaths == 0 or len(facecolors) == len(edgecolors) == 0: return 0 Npath_ids = max(Npaths, len(all_transforms)) N = max(Npath_ids, len(offsets)) @@ -518,7 +402,7 @@ def _iter_collection(self, gc, master_transform, all_transforms, gc0.set_linewidth(0.0) xo, yo = 0, 0 - for i in xrange(N): + for i in range(N): path_id = path_ids[i % Npaths] if Noffsets: xo, yo = toffsets[i % Noffsets] @@ -955,14 +839,6 @@ def get_joinstyle(self): """ return self._joinstyle - @cbook.deprecated("2.1") - def get_linestyle(self): - """ - Return the linestyle: one of ('solid', 'dashed', 'dashdot', - 'dotted'). - """ - return self._linestyle - def get_linewidth(self): """ Return the line width in points as a scalar @@ -1106,17 +982,6 @@ def set_linewidth(self, w): """ self._linewidth = float(w) - @cbook.deprecated("2.1") - def set_linestyle(self, style): - """ - Set the linestyle to be one of ('solid', 'dashed', 'dashdot', - 'dotted'). These are defined in the rcParams - `lines.dashed_pattern`, `lines.dashdot_pattern` and - `lines.dotted_pattern`. One may also specify customized dash - styles by providing a tuple of (offset, dash pairs). - """ - self._linestyle = style - def set_url(self, url): """ Sets the url for links in compatible backends @@ -1189,17 +1054,17 @@ def get_sketch_params(self): ------- sketch_params : tuple or `None` - A 3-tuple with the following elements: + A 3-tuple with the following elements: - * `scale`: The amplitude of the wiggle perpendicular to the - source line. + * `scale`: The amplitude of the wiggle perpendicular to the + source line. - * `length`: The length of the wiggle along the line. + * `length`: The length of the wiggle along the line. - * `randomness`: The scale factor by which the length is - shrunken or expanded. + * `randomness`: The scale factor by which the length is + shrunken or expanded. - May return `None` if no sketch parameters were set. + May return `None` if no sketch parameters were set. """ return self._sketch @@ -1407,14 +1272,6 @@ def __init__(self, name, canvas, guiEvent=None): self.guiEvent = guiEvent -@cbook.deprecated("2.1") -class IdleEvent(Event): - """ - An event triggered by the GUI backend when it is idle -- useful - for passive animation - """ - - class DrawEvent(Event): """ An event triggered by a draw operation on the canvas @@ -1475,7 +1332,7 @@ def __init__(self, name, canvas, guiEvent=None): class LocationEvent(Event): """ - An event that has a screen location + An event that has a screen location. The following additional attributes are defined and shown with their default values. @@ -1499,28 +1356,25 @@ class LocationEvent(Event): ydata : scalar y coord of mouse in data coords - """ - x = None # x position - pixels from left of canvas - y = None # y position - pixels from right of canvas - inaxes = None # the Axes instance if mouse us over axes - xdata = None # x coord of mouse in data coords - ydata = None # y coord of mouse in data coords - # the last event that was triggered before this one - lastevent = None + lastevent = None # the last event that was triggered before this one def __init__(self, name, canvas, x, y, guiEvent=None): """ *x*, *y* in figure coords, 0,0 = bottom, left """ Event.__init__(self, name, canvas, guiEvent=guiEvent) - self.x = x - self.y = y + # x position - pixels from left of canvas + self.x = int(x) if x is not None else x + # y position - pixels from right of canvas + self.y = int(y) if y is not None else y + self.inaxes = None # the Axes instance if mouse us over axes + self.xdata = None # x coord of mouse in data coords + self.ydata = None # y coord of mouse in data coords if x is None or y is None: # cannot check if event was in axes if no x,y info - self.inaxes = None self._update_enter_leave() return @@ -1537,13 +1391,10 @@ def __init__(self, name, canvas, x, y, guiEvent=None): trans = self.inaxes.transData.inverted() xdata, ydata = trans.transform_point((x, y)) except ValueError: - self.xdata = None - self.ydata = None + pass else: self.xdata = xdata self.ydata = ydata - else: - self.inaxes = None self._update_enter_leave() @@ -1586,18 +1437,21 @@ class MouseEvent(LocationEvent): Attributes ---------- - button : None, scalar, or str - button pressed None, 1, 2, 3, 'up', 'down' (up and down are used - for scroll events). Note that in the nbagg backend, both the - middle and right clicks return 3 since right clicking will bring - up the context menu in some browsers. + button : {None, 1, 2, 3, 'up', 'down'} + The button pressed. 'up' and 'down' are used for scroll events. + Note that in the nbagg backend, both the middle and right clicks + return 3 since right clicking will bring up the context menu in + some browsers. - key : None, or str - the key depressed when the mouse event triggered (see - :class:`KeyEvent`) + key : None or str + The key pressed when the mouse event triggered, e.g. 'shift'. + See `KeyEvent`. step : scalar - number of scroll steps (positive for 'up', negative for 'down') + The Number of scroll steps (positive for 'up', negative for 'down'). + + dblclick : bool + *True* if the event is a double-click. Examples -------- @@ -1607,16 +1461,7 @@ def on_press(event): print('you pressed', event.button, event.xdata, event.ydata) cid = fig.canvas.mpl_connect('button_press_event', on_press) - """ - x = None # x position - pixels from left of canvas - y = None # y position - pixels from right of canvas - button = None # button pressed None, 1, 2, 3 - dblclick = None # whether or not the event is the result of a double click - inaxes = None # the Axes instance if mouse us over axes - xdata = None # x coord of mouse in data coords - ydata = None # y coord of mouse in data coords - step = None # scroll steps for scroll events def __init__(self, name, canvas, x, y, button=None, key=None, step=0, dblclick=False, guiEvent=None): @@ -2011,17 +1856,16 @@ def enter_notify_event(self, guiEvent=None, xy=None): if xy is not None: x, y = xy self._lastx, self._lasty = x, y + else: + x = None + y = None + cbook.warn_deprecated('3.0', 'enter_notify_event expects a ' + 'location but ' + 'your backend did not pass one.') - event = Event('figure_enter_event', self, guiEvent) + event = LocationEvent('figure_enter_event', self, x, y, guiEvent) self.callbacks.process('figure_enter_event', event) - @cbook.deprecated("2.1") - def idle_event(self, guiEvent=None): - """Called when GUI is idle.""" - s = 'idle_event' - event = IdleEvent(s, self, guiEvent=guiEvent) - self.callbacks.process(s, event) - def grab_mouse(self, ax): """ Set the child axes which are currently grabbing the mouse events. @@ -2079,7 +1923,7 @@ def get_supported_filetypes_grouped(cls): Experts Group', and the values are a list of filename extensions used for that filetype, such as ['jpg', 'jpeg'].""" groupings = {} - for ext, name in six.iteritems(cls.filetypes): + for ext, name in cls.filetypes.items(): groupings.setdefault(name, []).append(ext) groupings[name].sort() return groupings @@ -2091,9 +1935,8 @@ def _get_output_canvas(self, fmt): If necessary, this function will switch to a registered backend that supports the format. """ - method_name = 'print_%s' % fmt # Return the current canvas if it supports the requested format. - if hasattr(self, method_name): + if hasattr(self, 'print_{}'.format(fmt)): return self # Return a default canvas for the requested format, if it exists. canvas_class = get_registered_canvas_class(fmt) @@ -2105,7 +1948,8 @@ def _get_output_canvas(self, fmt): .format(fmt, ", ".join(sorted(self.get_supported_filetypes())))) def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, - orientation='portrait', format=None, **kwargs): + orientation='portrait', format=None, + *, bbox_inches=None, **kwargs): """ Render the figure to hardcopy. Set the figure patch face and edge colors. This is useful because some of the GUIs have a gray figure @@ -2146,26 +1990,15 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, tight bbox is calculated. """ - self._is_saving = True - # Remove the figure manager, if any, to avoid resizing the GUI widget. - # Having *no* manager and a *None* manager are currently different (see - # Figure.show); should probably be normalized to None at some point. - _no_manager = object() - if hasattr(self, 'manager'): - manager = self.manager - del self.manager - else: - manager = _no_manager - if format is None: # get format from filename, or from backend's default filetype if isinstance(filename, getattr(os, "PathLike", ())): filename = os.fspath(filename) - if isinstance(filename, six.string_types): + if isinstance(filename, str): format = os.path.splitext(filename)[1][1:] if format is None or format == '': format = self.get_default_filetype() - if isinstance(filename, six.string_types): + if isinstance(filename, str): filename = filename.rstrip('.') + '.' + format format = format.lower() @@ -2175,104 +2008,79 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, if dpi is None: dpi = rcParams['savefig.dpi'] - if dpi == 'figure': dpi = getattr(self.figure, '_original_dpi', self.figure.dpi) - if facecolor is None: - facecolor = rcParams['savefig.facecolor'] - if edgecolor is None: - edgecolor = rcParams['savefig.edgecolor'] - - origDPI = self.figure.dpi - origfacecolor = self.figure.get_facecolor() - origedgecolor = self.figure.get_edgecolor() - - self.figure.dpi = dpi - self.figure.set_facecolor(facecolor) - self.figure.set_edgecolor(edgecolor) - - bbox_inches = kwargs.pop("bbox_inches", None) - if bbox_inches is None: - bbox_inches = rcParams['savefig.bbox'] - - if bbox_inches: - # call adjust_bbox to save only the given area - if bbox_inches == "tight": - # When bbox_inches == "tight", it saves the figure twice. The - # first save command (to a BytesIO) is just to estimate the - # bounding box of the figure. + # Remove the figure manager, if any, to avoid resizing the GUI widget. + # Some code (e.g. Figure.show) differentiates between having *no* + # manager and a *None* manager, which should be fixed at some point, + # but this should be fine. + with cbook._setattr_cm(self, _is_saving=True, manager=None), \ + cbook._setattr_cm(self.figure, dpi=dpi): + + if facecolor is None: + facecolor = rcParams['savefig.facecolor'] + if edgecolor is None: + edgecolor = rcParams['savefig.edgecolor'] + + origfacecolor = self.figure.get_facecolor() + origedgecolor = self.figure.get_edgecolor() + + self.figure.dpi = dpi + self.figure.set_facecolor(facecolor) + self.figure.set_edgecolor(edgecolor) + + if bbox_inches is None: + bbox_inches = rcParams['savefig.bbox'] + + if bbox_inches: + # call adjust_bbox to save only the given area + if bbox_inches == "tight": + # When bbox_inches == "tight", it saves the figure twice. + # The first save command (to a BytesIO) is just to estimate + # the bounding box of the figure. + result = print_method( + io.BytesIO(), + dpi=dpi, + facecolor=facecolor, + edgecolor=edgecolor, + orientation=orientation, + dryrun=True, + **kwargs) + renderer = self.figure._cachedRenderer + bbox_artists = kwargs.pop("bbox_extra_artists", None) + bbox_inches = self.figure.get_tightbbox(renderer, + bbox_extra_artists=bbox_artists) + pad = kwargs.pop("pad_inches", None) + if pad is None: + pad = rcParams['savefig.pad_inches'] + + bbox_inches = bbox_inches.padded(pad) + + restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, + canvas.fixed_dpi) + + _bbox_inches_restore = (bbox_inches, restore_bbox) + else: + _bbox_inches_restore = None + + try: result = print_method( - io.BytesIO(), + filename, dpi=dpi, facecolor=facecolor, edgecolor=edgecolor, orientation=orientation, - dryrun=True, + bbox_inches_restore=_bbox_inches_restore, **kwargs) - renderer = self.figure._cachedRenderer - bbox_inches = self.figure.get_tightbbox(renderer) - - bbox_artists = kwargs.pop("bbox_extra_artists", None) - if bbox_artists is None: - bbox_artists = self.figure.get_default_bbox_extra_artists() - - bbox_filtered = [] - for a in bbox_artists: - bbox = a.get_window_extent(renderer) - if a.get_clip_on(): - clip_box = a.get_clip_box() - if clip_box is not None: - bbox = Bbox.intersection(bbox, clip_box) - clip_path = a.get_clip_path() - if clip_path is not None and bbox is not None: - clip_path = clip_path.get_fully_transformed_path() - bbox = Bbox.intersection(bbox, - clip_path.get_extents()) - if bbox is not None and (bbox.width != 0 or - bbox.height != 0): - bbox_filtered.append(bbox) - - if bbox_filtered: - _bbox = Bbox.union(bbox_filtered) - trans = Affine2D().scale(1.0 / self.figure.dpi) - bbox_extra = TransformedBbox(_bbox, trans) - bbox_inches = Bbox.union([bbox_inches, bbox_extra]) - - pad = kwargs.pop("pad_inches", None) - if pad is None: - pad = rcParams['savefig.pad_inches'] - - bbox_inches = bbox_inches.padded(pad) - - restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, - canvas.fixed_dpi) - - _bbox_inches_restore = (bbox_inches, restore_bbox) - else: - _bbox_inches_restore = None + finally: + if bbox_inches and restore_bbox: + restore_bbox() - try: - result = print_method( - filename, - dpi=dpi, - facecolor=facecolor, - edgecolor=edgecolor, - orientation=orientation, - bbox_inches_restore=_bbox_inches_restore, - **kwargs) - finally: - if bbox_inches and restore_bbox: - restore_bbox() - - self.figure.dpi = origDPI - self.figure.set_facecolor(origfacecolor) - self.figure.set_edgecolor(origedgecolor) - self.figure.set_canvas(self) - if manager is not _no_manager: - self.manager = manager - self._is_saving = False - return result + self.figure.set_facecolor(origfacecolor) + self.figure.set_edgecolor(origedgecolor) + self.figure.set_canvas(self) + return result @classmethod def get_default_filetype(cls): @@ -2308,17 +2116,6 @@ def get_default_filename(self): default_basename = default_basename.replace(' ', '_') default_filetype = self.get_default_filetype() default_filename = default_basename + '.' + default_filetype - - save_dir = os.path.expanduser(rcParams['savefig.directory']) - - # ensure non-existing filename in save dir - i = 1 - while os.path.isfile(os.path.join(save_dir, default_filename)): - # attach numerical count to basename - default_filename = ( - '{}-{}.{}'.format(default_basename, i, default_filetype)) - i += 1 - return default_filename def switch_backends(self, FigureCanvasClass): @@ -2378,13 +2175,7 @@ def on_press(event): print('you pressed', event.button, event.xdata, event.ydata) cid = canvas.mpl_connect('button_press_event', on_press) - """ - if s == 'idle_event': - cbook.warn_deprecated(1.5, - "idle_event is only implemented for the wx backend, and will " - "be removed in matplotlib 2.1. Use the animations module " - "instead.") return self.callbacks.connect(s, func) @@ -2469,11 +2260,6 @@ def stop_event_loop(self): """ self._looping = False - start_event_loop_default = cbook.deprecated( - "2.1", name="start_event_loop_default")(start_event_loop) - stop_event_loop_default = cbook.deprecated( - "2.1", name="stop_event_loop_default")(stop_event_loop) - def key_press_handler(event, canvas, toolbar=None): """ @@ -2495,7 +2281,7 @@ def key_press_handler(event, canvas, toolbar=None): if event.key is None: return - # Load key-mappings from your matplotlibrc file. + # Load key-mappings from rcParams. fullscreen_keys = rcParams['keymap.fullscreen'] home_keys = rcParams['keymap.home'] back_keys = rcParams['keymap.back'] @@ -2630,13 +2416,13 @@ def _get_uniform_gridstate(ticks): # keys in list 'all' enables all axes (default key 'a'), # otherwise if key is a number only enable this particular axes # if it was the axes, where the event was raised - if not (event.key in all_keys): + if event.key not in all_keys: n = int(event.key) - 1 for i, a in enumerate(canvas.figure.get_axes()): # consider axes, in which the event was raised # FIXME: Why only this axes? - if event.x is not None and event.y is not None \ - and a.in_axes(event): + if (event.x is not None and event.y is not None + and a.in_axes(event)): if event.key in all_keys: a.set_navigate(True) else: @@ -2677,6 +2463,15 @@ def __init__(self, canvas, num): 'key_press_event', self.key_press) + self.toolmanager = None + self.toolbar = None + + @self.canvas.figure.add_axobserver + def notify_axes_change(fig): + # Called whenever the current axes is changed. + if self.toolmanager is None and self.toolbar is not None: + self.toolbar.update() + def show(self): """ For GUI backends, show the figure window and redraw. @@ -2774,7 +2569,7 @@ class NavigationToolbar2(object): # ) toolitems = ( ('Home', 'Reset original view', 'home', 'home'), - ('Back', 'Back to previous view', 'back', 'back'), + ('Back', 'Back to previous view', 'back', 'back'), ('Forward', 'Forward to next view', 'forward', 'forward'), (None, None, None, None), ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'), @@ -2817,10 +2612,6 @@ def back(self, *args): self.set_history_buttons() self._update_view() - @cbook.deprecated("2.1", alternative="canvas.draw_idle") - def dynamic_update(self): - self.canvas.draw_idle() - def draw_rubberband(self, event, x0, y0, x1, y1): """Draw a rectangle rubberband to indicate zoom limits. @@ -2889,7 +2680,7 @@ def mouse_move(self, event): except (ValueError, OverflowError): pass else: - artists = [a for a in event.inaxes.mouseover_set + artists = [a for a in event.inaxes._mouseover_set if a.contains(event) and a.get_visible()] if artists: @@ -2897,7 +2688,9 @@ def mouse_move(self, event): if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: - s += ' [%s]' % a.format_cursor_data(data) + data_str = a.format_cursor_data(data) + if data_str is not None: + s = s + ' ' + data_str if len(self.mode): self.set_message('%s, %s' % (self.mode, s)) @@ -3381,3 +3174,127 @@ def set_message(self, s): Message text """ pass + + +class _Backend(object): + # A backend can be defined by using the following pattern: + # + # @_Backend.export + # class FooBackend(_Backend): + # # override the attributes and methods documented below. + + # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an + # interactive framework is required, or None otherwise. + required_interactive_framework = None + + # `backend_version` may be overridden by the subclass. + backend_version = "unknown" + + # The `FigureCanvas` class must be defined. + FigureCanvas = None + + # For interactive backends, the `FigureManager` class must be overridden. + FigureManager = FigureManagerBase + + # The following methods must be left as None for non-interactive backends. + # For interactive backends, `trigger_manager_draw` should be a function + # taking a manager as argument and triggering a canvas draw, and `mainloop` + # should be a function taking no argument and starting the backend main + # loop. + trigger_manager_draw = None + mainloop = None + + # The following methods will be automatically defined and exported, but + # can be overridden. + + @classmethod + def new_figure_manager(cls, num, *args, **kwargs): + """Create a new figure manager instance. + """ + # This import needs to happen here due to circular imports. + from matplotlib.figure import Figure + fig_cls = kwargs.pop('FigureClass', Figure) + fig = fig_cls(*args, **kwargs) + return cls.new_figure_manager_given_figure(num, fig) + + @classmethod + def new_figure_manager_given_figure(cls, num, figure): + """Create a new figure manager instance for the given figure. + """ + canvas = cls.FigureCanvas(figure) + manager = cls.FigureManager(canvas, num) + return manager + + @classmethod + def draw_if_interactive(cls): + if cls.trigger_manager_draw is not None and is_interactive(): + manager = Gcf.get_active() + if manager: + cls.trigger_manager_draw(manager) + + @classmethod + def show(cls, block=None): + """Show all figures. + + `show` blocks by calling `mainloop` if *block* is ``True``, or if it + is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in + `interactive` mode. + """ + managers = Gcf.get_all_fig_managers() + if not managers: + return + for manager in managers: + # Emits a warning if the backend is non-interactive. + manager.canvas.figure.show() + if cls.mainloop is None: + return + if block is None: + # Hack: Are we in IPython's pylab mode? + from matplotlib import pyplot + try: + # IPython versions >= 0.10 tack the _needmain attribute onto + # pyplot.show, and always set it to False, when in %pylab mode. + ipython_pylab = not pyplot.show._needmain + except AttributeError: + ipython_pylab = False + block = not ipython_pylab and not is_interactive() + # TODO: The above is a hack to get the WebAgg backend working with + # ipython's `%pylab` mode until proper integration is implemented. + if get_backend() == "WebAgg": + block = True + if block: + cls.mainloop() + + # This method is the one actually exporting the required methods. + + @staticmethod + def export(cls): + for name in ["required_interactive_framework", + "backend_version", + "FigureCanvas", + "FigureManager", + "new_figure_manager", + "new_figure_manager_given_figure", + "draw_if_interactive", + "show"]: + setattr(sys.modules[cls.__module__], name, getattr(cls, name)) + + # For back-compatibility, generate a shim `Show` class. + + class Show(ShowBase): + def mainloop(self): + return cls.mainloop() + + setattr(sys.modules[cls.__module__], "Show", Show) + return cls + + +class ShowBase(_Backend): + """ + Simple base class to generate a show() callable in backends. + + Subclass must override mainloop() method. + """ + + def __call__(self, block=None): + return self.show(block=block) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index ab9a503fab88..d1e88df41127 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -4,9 +4,6 @@ toolbar clicks, ..) and the actions in response to the user inputs. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six import warnings import matplotlib.cbook as cbook @@ -25,7 +22,7 @@ def __init__(self, name, sender, tool, data=None): class ToolTriggerEvent(ToolEvent): - """Event to inform that a tool has been triggered""" + """Event to inform that a tool has been triggered""" def __init__(self, name, sender, tool, canvasevent=None, data=None): ToolEvent.__init__(self, name, sender, tool, data) self.canvasevent = canvasevent @@ -45,7 +42,7 @@ def __init__(self, name, sender, message): class ToolManager(object): """ - Helper class that groups all the user interactions for a Figure + Helper class that groups all the user interactions for a Figure. Attributes ---------- @@ -93,12 +90,12 @@ def figure(self, figure): def set_figure(self, figure, update_tools=True): """ - Sets the figure to interact with the tools + Bind the given figure to the tools. Parameters - ========== - figure: `Figure` - update_tools: bool + ---------- + figure : `.Figure` + update_tools : bool Force tools to update figure """ if self._key_press_handler_id: @@ -179,7 +176,7 @@ def get_tool_keymap(self, name): list : list of keys associated with the Tool """ - keys = [k for k, i in six.iteritems(self._keys) if i == name] + keys = [k for k, i in self._keys.items() if i == name] return keys def _remove_keys(self, name): @@ -342,7 +339,7 @@ def _handle_toggle(self, tool, sender, canvasevent, data): def _get_cls_to_instantiate(self, callback_class): # Find the class that corresponds to the tool - if isinstance(callback_class, six.string_types): + if isinstance(callback_class, str): # FIXME: make more complete searching structure if callback_class in globals(): callback_class = globals()[callback_class] diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 6639763e417d..a913dbaf6dc7 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -11,20 +11,21 @@ `matplotlib.backend_managers.ToolManager` """ +import re +import time +import warnings +from weakref import WeakKeyDictionary + +import numpy as np from matplotlib import rcParams from matplotlib._pylab_helpers import Gcf import matplotlib.cbook as cbook -from weakref import WeakKeyDictionary -import six -import time -import warnings -import numpy as np class Cursors(object): """Simple namespace for cursor reference""" - HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) + HAND, POINTER, SELECT_REGION, MOVE, WAIT = range(5) cursors = Cursors() # Views positions tool @@ -336,7 +337,7 @@ def send_message(self, event): except (ValueError, OverflowError): pass else: - artists = [a for a in event.inaxes.mouseover_set + artists = [a for a in event.inaxes._mouseover_set if a.contains(event) and a.get_visible()] if artists: @@ -344,7 +345,9 @@ def send_message(self, event): if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: - s += ' [%s]' % a.format_cursor_data(data) + data_str = a.format_cursor_data(data) + if data_str is not None: + s = s + ' ' + data_str message = s self.toolmanager.message_event(message, self) @@ -401,7 +404,7 @@ def trigger(self, sender, event, data=None): class ToolEnableAllNavigation(ToolBase): """Tool to enable all axes for toolmanager interaction""" - description = 'Enables all axes toolmanager' + description = 'Enable all axes toolmanager' default_keymap = rcParams['keymap.all_axes'] def trigger(self, sender, event, data=None): @@ -417,7 +420,7 @@ def trigger(self, sender, event, data=None): class ToolEnableNavigation(ToolBase): """Tool to enable a specific axes for toolmanager interaction""" - description = 'Enables one axes toolmanager' + description = 'Enable one axes toolmanager' default_keymap = (1, 2, 3, 4, 5, 6, 7, 8, 9) def trigger(self, sender, event, data=None): @@ -468,7 +471,7 @@ def _get_uniform_grid_state(ticks): class ToolGrid(_ToolGridBase): """Tool to toggle the major grids of the figure""" - description = 'Toogle major grids' + description = 'Toggle major grids' default_keymap = rcParams['keymap.grid'] def _get_next_grid_states(self, ax): @@ -489,7 +492,7 @@ def _get_next_grid_states(self, ax): class ToolMinorGrid(_ToolGridBase): """Tool to toggle the major and minor grids of the figure""" - description = 'Toogle major and minor grids' + description = 'Toggle major and minor grids' default_keymap = rcParams['keymap.grid_minor'] def _get_next_grid_states(self, ax): @@ -509,7 +512,7 @@ def _get_next_grid_states(self, ax): class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" - description = 'Toogle Fullscreen mode' + description = 'Toggle fullscreen mode' default_keymap = rcParams['keymap.fullscreen'] def enable(self, event): @@ -539,7 +542,7 @@ def disable(self, event): class ToolYScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the Y axis""" - description = 'Toogle Scale Y axis' + description = 'Toggle scale Y axis' default_keymap = rcParams['keymap.yscale'] def set_scale(self, ax, scale): @@ -549,7 +552,7 @@ def set_scale(self, ax, scale): class ToolXScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the X axis""" - description = 'Toogle Scale X axis' + description = 'Toggle scale X axis' default_keymap = rcParams['keymap.xscale'] def set_scale(self, ax, scale): @@ -1018,6 +1021,59 @@ def _mouse_move(self, event): self.toolmanager.canvas.draw_idle() +class ToolHelpBase(ToolBase): + description = 'Print tool list, shortcuts and description' + default_keymap = rcParams['keymap.help'] + image = 'help.png' + + @staticmethod + def format_shortcut(key_sequence): + """ + Converts a shortcut string from the notation used in rc config to the + standard notation for displaying shortcuts, e.g. 'ctrl+a' -> 'Ctrl+A'. + """ + return (key_sequence if len(key_sequence) == 1 else + re.sub(r"\+[A-Z]", r"+Shift\g<0>", key_sequence).title()) + + def _format_tool_keymap(self, name): + keymaps = self.toolmanager.get_tool_keymap(name) + return ", ".join(self.format_shortcut(keymap) for keymap in keymaps) + + def _get_help_entries(self): + entries = [] + for name, tool in sorted(self.toolmanager.tools.items()): + if not tool.description: + continue + entries.append((name, self._format_tool_keymap(name), + tool.description)) + return entries + + def _get_help_text(self): + entries = self._get_help_entries() + entries = ["{}: {}\n\t{}".format(*entry) for entry in entries] + return "\n".join(entries) + + def _get_help_html(self): + fmt = "{}{}{}" + rows = [fmt.format( + "Action", "Shortcuts", "Description")] + rows += [fmt.format(*row) for row in self._get_help_entries()] + return ("" + "" + rows[0] + "" + "".join(rows[1:]) + "
    ") + + +class ToolCopyToClipboardBase(ToolBase): + """Tool to copy the figure to the clipboard""" + + description = 'Copy the canvas figure to clipboard' + default_keymap = rcParams['keymap.copy'] + + def trigger(self, *args, **kwargs): + message = "Copy tool is not available" + self.toolmanager.message_event(message, self) + + default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward, 'zoom': ToolZoom, 'pan': ToolPan, 'subplots': 'ToolConfigureSubplots', @@ -1035,12 +1091,14 @@ def _mouse_move(self, event): _views_positions: ToolViewsPositions, 'cursor': 'ToolSetCursor', 'rubberband': 'ToolRubberband', + 'help': 'ToolHelp', + 'copy': 'ToolCopyToClipboard', } """Default tools""" default_toolbar_tools = [['navigation', ['home', 'back', 'forward']], ['zoompan', ['pan', 'zoom', 'subplots']], - ['io', ['save']]] + ['io', ['save', 'help']]] """Default tools in the toolbar""" @@ -1057,7 +1115,7 @@ def add_tools_to_manager(toolmanager, tools=default_tools): info. """ - for name, tool in six.iteritems(tools): + for name, tool in tools.items(): toolmanager.add_tool(name, tool) diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index ac7b6301e3e7..01e230df6804 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -1,28 +1,72 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import importlib +import logging +import os +import sys +import traceback import matplotlib -import inspect -import traceback -import warnings -import logging +from matplotlib import cbook +from matplotlib.backend_bases import _Backend _log = logging.getLogger(__name__) -backend = matplotlib.get_backend() -_backend_loading_tb = "".join( - line for line in traceback.format_stack() - # Filter out line noise from importlib line. - if not line.startswith(' File "', ''): - warnings.warn(""" -Your currently selected backend, '%s' does not support show(). -Please select a GUI backend in your matplotlibrc file ('%s') -or with matplotlib.use()""" % - (name, matplotlib.matplotlib_fname())) - - def do_nothing(*args, **kwargs): - pass - - backend_version = getattr(backend_mod, 'backend_version', 'unknown') - - show = getattr(backend_mod, 'show', do_nothing_show) - - draw_if_interactive = getattr(backend_mod, 'draw_if_interactive', - do_nothing) - - _log.debug('backend %s version %s', name, backend_version) - - # need to keep a global reference to the backend for compatibility - # reasons. See https://github.com/matplotlib/matplotlib/issues/6092 + backend_name = (name[9:] if name.startswith("module://") + else "matplotlib.backends.backend_{}".format(name.lower())) + backend_mod = importlib.import_module(backend_name) + # Create a local Backend class whose body corresponds to the contents of + # the backend module. This allows the Backend class to fill in the missing + # methods through inheritance. + Backend = type("Backend", (_Backend,), vars(backend_mod)) + + # Need to keep a global reference to the backend for compatibility reasons. + # See https://github.com/matplotlib/matplotlib/issues/6092 global backend backend = name - return backend_mod, new_figure_manager, draw_if_interactive, show + + _log.debug('backend %s version %s', name, Backend.backend_version) + return (backend_mod, + Backend.new_figure_manager, + Backend.draw_if_interactive, + Backend.show) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index da404b6e1bc7..e361baecb3df 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -1,21 +1,17 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import tkinter as Tk - +import math import logging import os.path import sys +import tkinter as Tk +from tkinter.simpledialog import SimpleDialog +from contextlib import contextmanager -# Paint image to Tk photo blitter extension -import matplotlib.backends.tkagg as tkagg +import numpy as np -from matplotlib.backends.backend_agg import FigureCanvasAgg -import matplotlib.backends.windowing as windowing +from . import _tkagg import matplotlib -from matplotlib import backend_tools, cbook, rcParams +from matplotlib import backend_tools, rcParams from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, StatusbarBase, TimerBase, ToolContainerBase, cursors) @@ -24,6 +20,22 @@ from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool +try: + from matplotlib._windowing import GetForegroundWindow, SetForegroundWindow +except ImportError: + @contextmanager + def _restore_foreground_window_at_end(): + yield +else: + @contextmanager + def _restore_foreground_window_at_end(): + foreground = GetForegroundWindow() + try: + yield + finally: + if rcParams['tk.window_focus']: + SetForegroundWindow(foreground) + _log = logging.getLogger(__name__) @@ -44,13 +56,39 @@ def raise_msg_to_str(msg): """msg is a return arg from a raise. Join with new lines""" - if not isinstance(msg, six.string_types): + if not isinstance(msg, str): msg = '\n'.join(map(str, msg)) return msg + def error_msg_tkpaint(msg, parent=None): - from six.moves import tkinter_messagebox as tkMessageBox - tkMessageBox.showerror("matplotlib", msg) + import tkinter.messagebox + tkinter.messagebox.showerror("matplotlib", msg) + + +def blit(photoimage, aggimage, offsets, bbox=None): + """ + Blit *aggimage* to *photoimage*. + + *offsets* is a tuple describing how to fill the ``offset`` field of the + ``Tk_PhotoImageBlock`` struct: it should be (0, 1, 2, 3) for RGBA8888 data, + (2, 1, 0, 3) for little-endian ARBG32 (i.e. GBRA8888) data and (1, 2, 3, 0) + for big-endian ARGB32 (i.e. ARGB8888) data. + + If *bbox* is passed, it defines the region that gets blitted. + """ + data = np.asarray(aggimage) + height, width = data.shape[:2] + dataptr = (height, width, data.ctypes.data) + if bbox is not None: + (x1, y1), (x2, y2) = bbox.__array__() + bboxptr = (math.floor(x1), math.ceil(x2), + math.floor(y1), math.ceil(y2)) + else: + photoimage.blank() + bboxptr = (0, width, 0, height) + _tkagg.blit( + photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, bboxptr) class TimerTk(TimerBase): @@ -178,6 +216,8 @@ def __init__(self, figure, master=None, resize_callback=None): self._tkcanvas.bind("", self.resize) self._tkcanvas.bind("", self.key_press) self._tkcanvas.bind("", self.motion_notify_event) + self._tkcanvas.bind("", self.enter_notify_event) + self._tkcanvas.bind("", self.leave_notify_event) self._tkcanvas.bind("", self.key_release) for name in "", "", "": self._tkcanvas.bind(name, self.button_press_event) @@ -294,10 +334,6 @@ def _update_pointer_position(self, guiEvent=None): else: self.leave_notify_event(guiEvent) - show = cbook.deprecated("2.2", name="FigureCanvasTk.show", - alternative="FigureCanvasTk.draw")( - lambda self: self.draw()) - def draw_idle(self): 'update drawing area only if idle' if self._idle is False: @@ -326,6 +362,11 @@ def motion_notify_event(self, event): y = self.figure.bbox.height - event.y FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) + def enter_notify_event(self, event): + x = event.x + # flipy so y=0 is bottom of canvas + y = self.figure.bbox.height - event.y + FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y)) def button_press_event(self, event, dblclick=False): x = event.x @@ -333,11 +374,10 @@ def button_press_event(self, event, dblclick=False): y = self.figure.bbox.height - event.y num = getattr(event, 'num', None) - if sys.platform=='darwin': - # 2 and 3 were reversed on the OSX platform I - # tested under tkagg - if num==2: num=3 - elif num==3: num=2 + if sys.platform == 'darwin': + # 2 and 3 were reversed on the OSX platform I tested under tkagg. + if num == 2: num = 3 + elif num == 3: num = 2 FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick, guiEvent=event) @@ -351,11 +391,10 @@ def button_release_event(self, event): num = getattr(event, 'num', None) - if sys.platform=='darwin': - # 2 and 3 were reversed on the OSX platform I - # tested under tkagg - if num==2: num=3 - elif num==3: num=2 + if sys.platform == 'darwin': + # 2 and 3 were reversed on the OSX platform I tested under tkagg. + if num == 2: num = 3 + elif num == 3: num = 2 FigureCanvasBase.button_release_event(self, x, y, num, guiEvent=event) @@ -384,8 +423,8 @@ def _get_key(self, event): val = event.keysym_num if val in self.keyvald: key = self.keyvald[val] - elif val == 0 and sys.platform == 'darwin' and \ - event.keycode in self._keycode_lookup: + elif (val == 0 and sys.platform == 'darwin' + and event.keycode in self._keycode_lookup): key = self._keycode_lookup[event.keycode] elif val < 256: key = chr(val) @@ -489,14 +528,6 @@ def __init__(self, canvas, num, window): self._shown = False - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolmanager is not None: - pass - elif self.toolbar is not None: - self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) - def _get_toolbar(self): if matplotlib.rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2Tk(self.canvas, self.window) @@ -513,22 +544,8 @@ def _get_toolmanager(self): toolmanager = None return toolmanager - def resize(self, width, height=None): - # before 09-12-22, the resize method takes a single *event* - # parameter. On the other hand, the resize method of other - # FigureManager class takes *width* and *height* parameter, - # which is used to change the size of the window. For the - # Figure.set_size_inches with forward=True work with Tk - # backend, I changed the function signature but tried to keep - # it backward compatible. -JJL - - # when a single parameter is given, consider it as a event - if height is None: - cbook.warn_deprecated("2.2", "FigureManagerTkAgg.resize now takes " - "width and height as separate arguments") - width = width.width - else: - self.canvas._tkcanvas.master.geometry("%dx%d" % (width, height)) + def resize(self, width, height): + self.canvas._tkcanvas.master.geometry("%dx%d" % (width, height)) if self.toolbar is not None: self.toolbar.configure(width=width) @@ -538,19 +555,19 @@ def show(self): this function doesn't segfault but causes the PyEval_RestoreThread: NULL state bug on win32 """ - _focus = windowing.FocusManager() - if not self._shown: - def destroy(*args): - self.window = None - Gcf.destroy(self._num) - self.canvas._tkcanvas.bind("", destroy) - self.window.deiconify() - else: - self.canvas.draw_idle() - # Raise the new window. - self.canvas.manager.window.attributes('-topmost', 1) - self.canvas.manager.window.attributes('-topmost', 0) - self._shown = True + with _restore_foreground_window_at_end(): + if not self._shown: + def destroy(*args): + self.window = None + Gcf.destroy(self._num) + self.canvas._tkcanvas.bind("", destroy) + self.window.deiconify() + else: + self.canvas.draw_idle() + # Raise the new window. + self.canvas.manager.window.attributes('-topmost', 1) + self.canvas.manager.window.attributes('-topmost', 0) + self._shown = True def destroy(self, *args): if self.window is not None: @@ -574,70 +591,6 @@ def full_screen_toggle(self): self.window.attributes('-fullscreen', not is_fullscreen) -@cbook.deprecated("2.2") -class AxisMenu(object): - def __init__(self, master, naxes): - self._master = master - self._naxes = naxes - self._mbar = Tk.Frame(master=master, relief=Tk.RAISED, borderwidth=2) - self._mbar.pack(side=Tk.LEFT) - self._mbutton = Tk.Menubutton( - master=self._mbar, text="Axes", underline=0) - self._mbutton.pack(side=Tk.LEFT, padx="2m") - self._mbutton.menu = Tk.Menu(self._mbutton) - self._mbutton.menu.add_command( - label="Select All", command=self.select_all) - self._mbutton.menu.add_command( - label="Invert All", command=self.invert_all) - self._axis_var = [] - self._checkbutton = [] - for i in range(naxes): - self._axis_var.append(Tk.IntVar()) - self._axis_var[i].set(1) - self._checkbutton.append(self._mbutton.menu.add_checkbutton( - label = "Axis %d" % (i+1), - variable=self._axis_var[i], - command=self.set_active)) - self._mbutton.menu.invoke(self._mbutton.menu.index("Select All")) - self._mbutton['menu'] = self._mbutton.menu - self._mbar.tk_menuBar(self._mbutton) - self.set_active() - - def adjust(self, naxes): - if self._naxes < naxes: - for i in range(self._naxes, naxes): - self._axis_var.append(Tk.IntVar()) - self._axis_var[i].set(1) - self._checkbutton.append( self._mbutton.menu.add_checkbutton( - label = "Axis %d" % (i+1), - variable=self._axis_var[i], - command=self.set_active)) - elif self._naxes > naxes: - for i in range(self._naxes-1, naxes-1, -1): - del self._axis_var[i] - self._mbutton.menu.forget(self._checkbutton[i]) - del self._checkbutton[i] - self._naxes = naxes - self.set_active() - - def get_indices(self): - a = [i for i in range(len(self._axis_var)) if self._axis_var[i].get()] - return a - - def set_active(self): - self._master.set_active(self.get_indices()) - - def invert_all(self): - for a in self._axis_var: - a.set(not a.get()) - self.set_active() - - def select_all(self): - for a in self._axis_var: - a.set(1) - self.set_active() - - class NavigationToolbar2Tk(NavigationToolbar2, Tk.Frame): """ Attributes @@ -733,7 +686,7 @@ def configure_subplots(self): window.grab_set() def save_figure(self, *args): - from six.moves import tkinter_tkfiledialog, tkinter_messagebox + import tkinter.filedialog, tkinter.messagebox filetypes = self.canvas.get_supported_filetypes().copy() default_filetype = self.canvas.get_default_filetype() @@ -741,7 +694,7 @@ def save_figure(self, *args): # so we just have to put it first default_filetype_name = filetypes.pop(default_filetype) sorted_filetypes = ([(default_filetype, default_filetype_name)] - + sorted(six.iteritems(filetypes))) + + sorted(filetypes.items())) tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes] # adding a default extension seems to break the @@ -752,7 +705,7 @@ def save_figure(self, *args): defaultextension = '' initialdir = os.path.expanduser(rcParams['savefig.directory']) initialfile = self.canvas.get_default_filename() - fname = tkinter_tkfiledialog.asksaveasfilename( + fname = tkinter.filedialog.asksaveasfilename( master=self.window, title='Save the figure', filetypes=tk_filetypes, @@ -766,21 +719,21 @@ def save_figure(self, *args): # Save dir for next time, unless empty str (i.e., use cwd). if initialdir != "": rcParams['savefig.directory'] = ( - os.path.dirname(six.text_type(fname))) + os.path.dirname(str(fname))) try: # This method will handle the delegation to the correct type self.canvas.figure.savefig(fname) except Exception as e: - tkinter_messagebox.showerror("Error saving file", str(e)) + tkinter.messagebox.showerror("Error saving file", str(e)) def set_active(self, ind): self._ind = ind self._active = [self._axes[i] for i in self._ind] def update(self): - _focus = windowing.FocusManager() self._axes = self.canvas.figure.axes - NavigationToolbar2.update(self) + with _restore_foreground_window_at_end(): + NavigationToolbar2.update(self) class ToolTip(object): @@ -953,7 +906,7 @@ def set_message(self, s): class SaveFigureTk(backend_tools.SaveFigureBase): def trigger(self, *args): - from six.moves import tkinter_tkfiledialog, tkinter_messagebox + import tkinter.filedialog, tkinter.messagebox filetypes = self.figure.canvas.get_supported_filetypes().copy() default_filetype = self.figure.canvas.get_default_filetype() @@ -961,7 +914,7 @@ def trigger(self, *args): # so we just have to put it first default_filetype_name = filetypes.pop(default_filetype) sorted_filetypes = ([(default_filetype, default_filetype_name)] - + sorted(six.iteritems(filetypes))) + + sorted(filetypes.items())) tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes] # adding a default extension seems to break the @@ -972,7 +925,7 @@ def trigger(self, *args): defaultextension = '' initialdir = os.path.expanduser(rcParams['savefig.directory']) initialfile = self.figure.canvas.get_default_filename() - fname = tkinter_tkfiledialog.asksaveasfilename( + fname = tkinter.filedialog.asksaveasfilename( master=self.figure.canvas.manager.window, title='Save the figure', filetypes=tk_filetypes, @@ -989,13 +942,12 @@ def trigger(self, *args): rcParams['savefig.directory'] = initialdir else: # save dir for next time - rcParams['savefig.directory'] = os.path.dirname( - six.text_type(fname)) + rcParams['savefig.directory'] = os.path.dirname(str(fname)) try: # This method will handle the delegation to the correct type self.figure.savefig(fname) except Exception as e: - tkinter_messagebox.showerror("Error saving file", str(e)) + tkinter.messagebox.showerror("Error saving file", str(e)) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): @@ -1026,15 +978,25 @@ def destroy(self, *args, **kwargs): self.window = None +class HelpTk(backend_tools.ToolHelpBase): + def trigger(self, *args): + dialog = SimpleDialog( + self.figure.canvas._tkcanvas, self._get_help_text(), ["OK"]) + dialog.done = lambda num: dialog.frame.master.withdraw() + + backend_tools.ToolSaveFigure = SaveFigureTk backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk backend_tools.ToolSetCursor = SetCursorTk backend_tools.ToolRubberband = RubberbandTk +backend_tools.ToolHelp = HelpTk +backend_tools.ToolCopyToClipboard = backend_tools.ToolCopyToClipboardBase Toolbar = ToolbarTk @_Backend.export class _BackendTk(_Backend): + required_interactive_framework = "tk" FigureManager = FigureManagerTk @classmethod @@ -1042,29 +1004,29 @@ def new_figure_manager_given_figure(cls, num, figure): """ Create a new figure manager instance for the given figure. """ - _focus = windowing.FocusManager() - window = Tk.Tk(className="matplotlib") - window.withdraw() - - # Put a mpl icon on the window rather than the default tk icon. - # Tkinter doesn't allow colour icons on linux systems, but tk>=8.5 has - # a iconphoto command which we call directly. Source: - # http://mail.python.org/pipermail/tkinter-discuss/2006-November/000954.html - icon_fname = os.path.join( - rcParams['datapath'], 'images', 'matplotlib.ppm') - icon_img = Tk.PhotoImage(file=icon_fname) - try: - window.tk.call('wm', 'iconphoto', window._w, icon_img) - except Exception as exc: - # log the failure (due e.g. to Tk version), but carry on - _log.info('Could not load matplotlib icon: %s', exc) - - canvas = cls.FigureCanvas(figure, master=window) - manager = cls.FigureManager(canvas, num, window) - if matplotlib.is_interactive(): - manager.show() - canvas.draw_idle() - return manager + with _restore_foreground_window_at_end(): + window = Tk.Tk(className="matplotlib") + window.withdraw() + + # Put a mpl icon on the window rather than the default tk icon. + # Tkinter doesn't allow colour icons on linux systems, but tk>=8.5 + # has a iconphoto command which we call directly. Source: + # http://mail.python.org/pipermail/tkinter-discuss/2006-November/000954.html + icon_fname = os.path.join( + rcParams['datapath'], 'images', 'matplotlib.ppm') + icon_img = Tk.PhotoImage(file=icon_fname, master=window) + try: + window.iconphoto(False, icon_img) + except Exception as exc: + # log the failure (due e.g. to Tk version), but carry on + _log.info('Could not load matplotlib icon: %s', exc) + + canvas = cls.FigureCanvas(figure, master=window) + manager = cls.FigureManager(canvas, num, window) + if matplotlib.is_interactive(): + manager.show() + canvas.draw_idle() + return manager @staticmethod def trigger_manager_draw(manager): diff --git a/lib/matplotlib/backends/_gtk3_compat.py b/lib/matplotlib/backends/_gtk3_compat.py index 825fa2341c80..e0ac33c8d343 100644 --- a/lib/matplotlib/backends/_gtk3_compat.py +++ b/lib/matplotlib/backends/_gtk3_compat.py @@ -11,15 +11,9 @@ Thus, to force usage of PGI when both bindings are installed, import it first. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import importlib import sys - if "gi" in sys.modules: import gi elif "pgi" in sys.modules: @@ -31,9 +25,21 @@ try: import pgi as gi except ImportError: - raise ImportError("The Gtk3 backend requires PyGObject or pgi") - - + raise ImportError("The GTK3 backends require PyGObject or pgi") + +from .backend_cairo import cairo # noqa +# The following combinations are allowed: +# gi + pycairo +# gi + cairocffi +# pgi + cairocffi +# (pgi doesn't work with pycairo) +# We always try to import cairocffi first so if a check below fails it means +# that cairocffi was unavailable to start with. +if gi.__name__ == "pgi" and cairo.__name__ == "cairo": + raise ImportError("pgi and pycairo are not compatible") + +if gi.__name__ == "pgi" and gi.version_info < (0, 0, 11, 2): + raise ImportError("The GTK3 backends are incompatible with pgi<0.0.11.2") gi.require_version("Gtk", "3.0") globals().update( {name: diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index aff6cddf492d..a5708a1746c5 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -19,24 +19,16 @@ * integrate screen dpi w/ ppi and text """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - try: import threading except ImportError: import dummy_threading as threading - import numpy as np from collections import OrderedDict from math import radians, cos, sin from matplotlib import cbook, rcParams, __version__ from matplotlib.backend_bases import ( - _Backend, FigureCanvasBase, FigureManagerBase, RendererBase, cursors) -from matplotlib.cbook import maxdict -from matplotlib.figure import Figure + _Backend, FigureCanvasBase, FigureManagerBase, RendererBase) from matplotlib.font_manager import findfont, get_font from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, LOAD_DEFAULT, LOAD_NO_AUTOHINT) @@ -48,11 +40,10 @@ from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg from matplotlib import _png -try: +from matplotlib.backend_bases import _has_pil + +if _has_pil: from PIL import Image - _has_pil = True -except ImportError: - _has_pil = False backend_version = 'v2.2' @@ -74,11 +65,6 @@ class RendererAgg(RendererBase): context instance that controls the colors/styles """ - @property - @cbook.deprecated("2.2") - def debug(self): - return 1 - # we want to cache the fonts at the class level so that when # multiple figures are created we can reuse them. This helps with # a bug on windows where the creation of too many figures leads to @@ -114,25 +100,13 @@ def __getstate__(self): def __setstate__(self, state): self.__init__(state['width'], state['height'], state['dpi']) - def _get_hinting_flag(self): - if rcParams['text.hinting']: - return LOAD_FORCE_AUTOHINT - else: - return LOAD_NO_HINTING - - # for filtering to work with rasterization, methods needs to be wrapped. - # maybe there is better way to do it. - def draw_markers(self, *kl, **kw): - return self._renderer.draw_markers(*kl, **kw) - - def draw_path_collection(self, *kl, **kw): - return self._renderer.draw_path_collection(*kl, **kw) - def _update_methods(self): - self.draw_quad_mesh = self._renderer.draw_quad_mesh self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles self.draw_image = self._renderer.draw_image + self.draw_markers = self._renderer.draw_markers + self.draw_path_collection = self._renderer.draw_path_collection + self.draw_quad_mesh = self._renderer.draw_quad_mesh self.copy_from_bbox = self._renderer.copy_from_bbox self.get_content_extents = self._renderer.get_content_extents @@ -177,7 +151,6 @@ def draw_path(self, gc, path, transform, rgbFace=None): raise OverflowError("Exceeded cell block limit (set " "'agg.path.chunksize' rcparam)") - def draw_mathtext(self, gc, x, y, s, prop, angle): """ Draw the math text using matplotlib.mathtext @@ -274,7 +247,7 @@ def get_canvas_width_height(self): def _get_agg_font(self, prop): """ - Get the font for text instance t, cacheing for efficiency + Get the font for text instance t, caching for efficiency """ fname = findfont(prop) font = get_font(fname) @@ -290,7 +263,7 @@ def points_to_pixels(self, points): convert point measures to pixes using dpi and the pixels per inch of the display """ - return points*self.dpi/72.0 + return points * self.dpi / 72 def tostring_rgb(self): return self._renderer.tostring_rgb() @@ -378,14 +351,9 @@ def post_processing(image, dpi): post_processing is plotted (using draw_image) on it. """ - # WARNING: For agg_filter to work, the renderer's method need to - # overridden in the class. See draw_markers and draw_path_collections. - width, height = int(self.width), int(self.height) - buffer, bounds = self.tostring_rgba_minimized() - - l, b, w, h = bounds + buffer, (l, b, w, h) = self.tostring_rgba_minimized() self._renderer = self._filter_renderers.pop() self._update_methods() @@ -398,8 +366,7 @@ def post_processing(image, dpi): if img.dtype.kind == 'f': img = np.asarray(img * 255., np.uint8) img = img[::-1] - self._renderer.draw_image( - gc, l + ox, height - b - h + oy, img) + self._renderer.draw_image(gc, l + ox, height - b - h + oy, img) class FigureCanvasAgg(FigureCanvasBase): @@ -424,7 +391,7 @@ def restore_region(self, region, bbox=None, xy=None): def draw(self): """ - Draw the figure using the renderer + Draw the figure using the renderer. """ self.renderer = self.get_renderer(cleared=True) # acquire a lock on the shared font cache @@ -432,15 +399,11 @@ def draw(self): toolbar = self.toolbar try: - # if toolbar: - # toolbar.set_cursor(cursors.WAIT) self.figure.draw(self.renderer) # A GUI class may be need to update a window using this draw, so # don't forget to call the superclass. - super(FigureCanvasAgg, self).draw() + super().draw() finally: - # if toolbar: - # toolbar.set_cursor(toolbar._lastCursor) RendererAgg.lock.release() def get_renderer(self, cleared=False): @@ -458,7 +421,7 @@ def get_renderer(self, cleared=False): return self.renderer def tostring_rgb(self): - '''Get the image as an RGB byte string + '''Get the image as an RGB byte string. `draw` must be called at least once before this function will work and to update the renderer for any subsequent changes to the Figure. @@ -483,7 +446,7 @@ def tostring_argb(self): return self.renderer.tostring_argb() def buffer_rgba(self): - '''Get the image as an RGBA byte string + '''Get the image as an RGBA byte string. `draw` must be called at least once before this function will work and to update the renderer for any subsequent changes to the Figure. @@ -497,66 +460,93 @@ def buffer_rgba(self): def print_raw(self, filename_or_obj, *args, **kwargs): FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - if isinstance(filename_or_obj, six.string_types): - fileobj = open(filename_or_obj, 'wb') - close = True - else: - fileobj = filename_or_obj - close = False - try: - fileobj.write(renderer._renderer.buffer_rgba()) - finally: - if close: - fileobj.close() - renderer.dpi = original_dpi + with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ + cbook.open_file_cm(filename_or_obj, "wb") as fh: + fh.write(renderer._renderer.buffer_rgba()) print_rgba = print_raw def print_png(self, filename_or_obj, *args, **kwargs): + """ + Write the figure to a PNG file. + + Parameters + ---------- + filename_or_obj : str or PathLike or file-like object + The file to write to. + + metadata : dict, optional + Metadata in the PNG file as key-value pairs of bytes or latin-1 + encodable strings. + According to the PNG specification, keys must be shorter than 79 + chars. + + The `PNG specification`_ defines some common keywords that may be + used as appropriate: + + - Title: Short (one line) title or caption for image. + - Author: Name of image's creator. + - Description: Description of image (possibly long). + - Copyright: Copyright notice. + - Creation Time: Time of original image creation + (usually RFC 1123 format). + - Software: Software used to create the image. + - Disclaimer: Legal disclaimer. + - Warning: Warning of nature of content. + - Source: Device used to create the image. + - Comment: Miscellaneous comment; + conversion from other image format. + + Other keywords may be invented for other purposes. + + If 'Software' is not given, an autogenerated value for matplotlib + will be used. + + For more details see the `PNG specification`_. + + .. _PNG specification: \ + https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords + + """ FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - version_str = 'matplotlib version ' + __version__ + \ - ', http://matplotlib.org/' + version_str = ( + 'matplotlib version ' + __version__ + ', http://matplotlib.org/') metadata = OrderedDict({'Software': version_str}) user_metadata = kwargs.pop("metadata", None) if user_metadata is not None: metadata.update(user_metadata) - try: - with cbook.open_file_cm(filename_or_obj, "wb") as fh: - _png.write_png(renderer._renderer, fh, - self.figure.dpi, metadata=metadata) - finally: - renderer.dpi = original_dpi + with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ + cbook.open_file_cm(filename_or_obj, "wb") as fh: + _png.write_png(renderer._renderer, fh, + self.figure.dpi, metadata=metadata) def print_to_buffer(self): FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - try: - result = (renderer._renderer.buffer_rgba(), - (int(renderer.width), int(renderer.height))) - finally: - renderer.dpi = original_dpi - return result + with cbook._setattr_cm(renderer, dpi=self.figure.dpi): + return (renderer._renderer.buffer_rgba(), + (int(renderer.width), int(renderer.height))) if _has_pil: # add JPEG support - def print_jpg(self, filename_or_obj, *args, **kwargs): + def print_jpg(self, filename_or_obj, *args, dryrun=False, **kwargs): """ + Write the figure to a JPEG file. + + Parameters + ---------- + filename_or_obj : str or PathLike or file-like object + The file to write to. + Other Parameters ---------------- quality : int - The image quality, on a scale from 1 (worst) to - 95 (best). The default is 95, if not given in the - matplotlibrc file in the savefig.jpeg_quality parameter. - Values above 95 should be avoided; 100 completely - disables the JPEG quantization stage. + The image quality, on a scale from 1 (worst) to 100 (best). + The default is :rc:`savefig.jpeg_quality`. Values above + 95 should be avoided; 100 completely disables the JPEG + quantization stage. optimize : bool If present, indicates that the encoder should @@ -568,13 +558,13 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): should be stored as a progressive JPEG file. """ buf, size = self.print_to_buffer() - if kwargs.pop("dryrun", False): + if dryrun: return # The image is "pasted" onto a white background image to safely # handle any transparency image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) rgba = mcolors.to_rgba(rcParams['savefig.facecolor']) - color = tuple([int(x * 255.0) for x in rgba[:3]]) + color = tuple([int(x * 255) for x in rgba[:3]]) background = Image.new('RGB', size, color) background.paste(image, image) options = {k: kwargs[k] @@ -589,14 +579,13 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): print_jpeg = print_jpg # add TIFF support - def print_tif(self, filename_or_obj, *args, **kwargs): + def print_tif(self, filename_or_obj, *args, dryrun=False, **kwargs): buf, size = self.print_to_buffer() - if kwargs.pop("dryrun", False): + if dryrun: return image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) dpi = (self.figure.dpi, self.figure.dpi) - return image.save(filename_or_obj, format='tiff', - dpi=dpi) + return image.save(filename_or_obj, format='tiff', dpi=dpi) print_tiff = print_tif diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index c870ba60a55b..80103fb50498 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -3,15 +3,10 @@ ============================== :Author: Steve Chaplin and others -This backend depends on `cairo `_, and either on -cairocffi, or (Python 2 only) on pycairo. +This backend depends on cairocffi or pycairo. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +import copy import gzip import sys import warnings @@ -29,24 +24,41 @@ raise ImportError("cairo backend requires that cairocffi or pycairo " "is installed") else: - HAS_CAIRO_CFFI = False -else: - HAS_CAIRO_CFFI = True + if cairo.version_info < (1, 11, 0): + # Introduced create_for_data for Py3. + raise ImportError( + "cairo {} is installed; cairo>=1.11.0 is required" + .format(cairo.version)) -if cairo.version_info < (1, 4, 0): - raise ImportError("cairo {} is installed; " - "cairo>=1.4.0 is required".format(cairo.version)) backend_version = cairo.version +from .. import cbook from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) +from matplotlib.font_manager import ttfFontProperty from matplotlib.mathtext import MathTextParser from matplotlib.path import Path from matplotlib.transforms import Affine2D -from matplotlib.font_manager import ttfFontProperty +if cairo.__name__ == "cairocffi": + # Convert a pycairo context to a cairocffi one. + def _to_context(ctx): + if not isinstance(ctx, cairo.Context): + ctx = cairo.Context._from_pointer( + cairo.ffi.cast( + 'cairo_t **', + id(ctx) + object.__basicsize__)[0], + incref=True) + return ctx +else: + # Pass-through a pycairo context. + def _to_context(ctx): + return ctx + + +@cbook.deprecated("3.0") class ArrayWrapper: """Thin wrapper around numpy ndarray to expose the interface expected by cairocffi. Basically replicates the @@ -62,6 +74,94 @@ def buffer_info(self): return (self.__data, self.__size) +# Mapping from Matplotlib Path codes to cairo path codes. +_MPL_TO_CAIRO_PATH_TYPE = np.zeros(80, dtype=int) # CLOSEPOLY = 79. +_MPL_TO_CAIRO_PATH_TYPE[Path.MOVETO] = cairo.PATH_MOVE_TO +_MPL_TO_CAIRO_PATH_TYPE[Path.LINETO] = cairo.PATH_LINE_TO +_MPL_TO_CAIRO_PATH_TYPE[Path.CURVE4] = cairo.PATH_CURVE_TO +_MPL_TO_CAIRO_PATH_TYPE[Path.CLOSEPOLY] = cairo.PATH_CLOSE_PATH +# Sizes in cairo_path_data_t of each cairo path element. +_CAIRO_PATH_TYPE_SIZES = np.zeros(4, dtype=int) +_CAIRO_PATH_TYPE_SIZES[cairo.PATH_MOVE_TO] = 2 +_CAIRO_PATH_TYPE_SIZES[cairo.PATH_LINE_TO] = 2 +_CAIRO_PATH_TYPE_SIZES[cairo.PATH_CURVE_TO] = 4 +_CAIRO_PATH_TYPE_SIZES[cairo.PATH_CLOSE_PATH] = 1 + + +def _append_paths_slow(ctx, paths, transforms, clip=None): + for path, transform in zip(paths, transforms): + for points, code in path.iter_segments(transform, clip=clip): + if code == Path.MOVETO: + ctx.move_to(*points) + elif code == Path.CLOSEPOLY: + ctx.close_path() + elif code == Path.LINETO: + ctx.line_to(*points) + elif code == Path.CURVE3: + cur = ctx.get_current_point() + ctx.curve_to( + *np.concatenate([cur / 3 + points[:2] * 2 / 3, + points[:2] * 2 / 3 + points[-2:] / 3])) + elif code == Path.CURVE4: + ctx.curve_to(*points) + + +def _append_paths_fast(ctx, paths, transforms, clip=None): + # We directly convert to the internal representation used by cairo, for + # which ABI compatibility is guaranteed. The layout for each item is + # --CODE(4)-- -LENGTH(4)- ---------PAD(8)--------- + # ----------X(8)---------- ----------Y(8)---------- + # with the size in bytes in parentheses, and (X, Y) repeated as many times + # as there are points for the current code. + ffi = cairo.ffi + + # Convert curves to segment, so that 1. we don't have to handle + # variable-sized CURVE-n codes, and 2. we don't have to implement degree + # elevation for quadratic Beziers. + cleaneds = [path.cleaned(transform=transform, clip=clip, curves=False) + for path, transform in zip(paths, transforms)] + vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds]) + codes = np.concatenate([cleaned.codes for cleaned in cleaneds]) + + # Remove unused vertices and convert to cairo codes. Note that unlike + # cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after + # CLOSE_PATH, so our resulting buffer may be smaller. + vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)] + codes = codes[codes != Path.STOP] + codes = _MPL_TO_CAIRO_PATH_TYPE[codes] + + # Where are the headers of each cairo portions? + cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes] + cairo_type_positions = np.insert(np.cumsum(cairo_type_sizes), 0, 0) + cairo_num_data = cairo_type_positions[-1] + cairo_type_positions = cairo_type_positions[:-1] + + # Fill the buffer. + buf = np.empty(cairo_num_data * 16, np.uint8) + as_int = np.frombuffer(buf.data, np.int32) + as_int[::4][cairo_type_positions] = codes + as_int[1::4][cairo_type_positions] = cairo_type_sizes + as_float = np.frombuffer(buf.data, np.float64) + mask = np.ones_like(as_float, bool) + mask[::2][cairo_type_positions] = mask[1::2][cairo_type_positions] = False + as_float[mask] = vertices.ravel() + + # Construct the cairo_path_t, and pass it to the context. + ptr = ffi.new("cairo_path_t *") + ptr.status = cairo.STATUS_SUCCESS + ptr.data = ffi.cast("cairo_path_data_t *", ffi.from_buffer(buf)) + ptr.num_data = cairo_num_data + cairo.cairo.cairo_append_path(ctx._pointer, ptr) + + +_append_paths = (_append_paths_fast if cairo.__name__ == "cairocffi" + else _append_paths_slow) + + +def _append_path(ctx, path, transform, clip=None): + return _append_paths(ctx, [path], [transform], clip) + + class RendererCairo(RendererBase): fontweights = { 100 : cairo.FONT_WEIGHT_NORMAL, @@ -122,37 +222,20 @@ def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides): ctx.stroke() @staticmethod + @cbook.deprecated("3.0") def convert_path(ctx, path, transform, clip=None): - for points, code in path.iter_segments(transform, clip=clip): - if code == Path.MOVETO: - ctx.move_to(*points) - elif code == Path.CLOSEPOLY: - ctx.close_path() - elif code == Path.LINETO: - ctx.line_to(*points) - elif code == Path.CURVE3: - ctx.curve_to(points[0], points[1], - points[0], points[1], - points[2], points[3]) - elif code == Path.CURVE4: - ctx.curve_to(*points) + _append_path(ctx, path, transform, clip) def draw_path(self, gc, path, transform, rgbFace=None): ctx = gc.ctx - - # We'll clip the path to the actual rendering extents - # if the path isn't filled. - if rgbFace is None and gc.get_hatch() is None: - clip = ctx.clip_extents() - else: - clip = None - + # Clip the path to the actual rendering extents if it isn't filled. + clip = (ctx.clip_extents() + if rgbFace is None and gc.get_hatch() is None + else None) transform = (transform - + Affine2D().scale(1.0, -1.0).translate(0, self.height)) - + + Affine2D().scale(1, -1).translate(0, self.height)) ctx.new_path() - self.convert_path(ctx, path, transform, clip) - + _append_path(ctx, path, transform, clip) self._fill_and_stroke( ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) @@ -162,8 +245,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, ctx.new_path() # Create the path for the marker; it needs to be flipped here already! - self.convert_path( - ctx, marker_path, marker_trans + Affine2D().scale(1.0, -1.0)) + _append_path(ctx, marker_path, marker_trans + Affine2D().scale(1, -1)) marker_path = ctx.copy_path_flat() # Figure out whether the path has a fill @@ -176,7 +258,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, filled = True transform = (transform - + Affine2D().scale(1.0, -1.0).translate(0, self.height)) + + Affine2D().scale(1, -1).translate(0, self.height)) ctx.new_path() for i, (vertices, codes) in enumerate( @@ -204,36 +286,68 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, self._fill_and_stroke( ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) + def draw_path_collection( + self, gc, master_transform, paths, all_transforms, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): + + path_ids = [] + for path, transform in self._iter_collection_raw_paths( + master_transform, paths, all_transforms): + path_ids.append((path, Affine2D(transform))) + + reuse_key = None + grouped_draw = [] + + def _draw_paths(): + if not grouped_draw: + return + gc_vars, rgb_fc = reuse_key + gc = copy.copy(gc0) + # We actually need to call the setters to reset the internal state. + vars(gc).update(gc_vars) + for k, v in gc_vars.items(): + if k == "_linestyle": # Deprecated, no effect. + continue + try: + getattr(gc, "set" + k)(v) + except (AttributeError, TypeError) as e: + pass + gc.ctx.new_path() + paths, transforms = zip(*grouped_draw) + grouped_draw.clear() + _append_paths(gc.ctx, paths, transforms) + self._fill_and_stroke( + gc.ctx, rgb_fc, gc.get_alpha(), gc.get_forced_alpha()) + + for xo, yo, path_id, gc0, rgb_fc in self._iter_collection( + gc, master_transform, all_transforms, path_ids, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): + path, transform = path_id + transform = (Affine2D(transform.get_matrix()) + .translate(xo, yo - self.height).scale(1, -1)) + # rgb_fc could be a ndarray, for which equality is elementwise. + new_key = vars(gc0), tuple(rgb_fc) if rgb_fc is not None else None + if new_key == reuse_key: + grouped_draw.append((path, transform)) + else: + _draw_paths() + grouped_draw.append((path, transform)) + reuse_key = new_key + _draw_paths() + def draw_image(self, gc, x, y, im): - # bbox - not currently used - if sys.byteorder == 'little': - im = im[:, :, (2, 1, 0, 3)] - else: - im = im[:, :, (3, 0, 1, 2)] - if HAS_CAIRO_CFFI: - # cairocffi tries to use the buffer_info from array.array - # that we replicate in ArrayWrapper and alternatively falls back - # on ctypes to get a pointer to the numpy array. This works - # correctly on a numpy array in python3 but not 2.7. We replicate - # the array.array functionality here to get cross version support. - imbuffer = ArrayWrapper(im.flatten()) - else: - # pycairo uses PyObject_AsWriteBuffer to get a pointer to the - # numpy array; this works correctly on a regular numpy array but - # not on a py2 memoryview. - imbuffer = im.flatten() + im = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(im[::-1]) surface = cairo.ImageSurface.create_for_data( - imbuffer, cairo.FORMAT_ARGB32, - im.shape[1], im.shape[0], im.shape[1]*4) + im.ravel().data, cairo.FORMAT_ARGB32, + im.shape[1], im.shape[0], im.shape[1] * 4) ctx = gc.ctx y = self.height - y - im.shape[0] ctx.save() ctx.set_source_surface(surface, float(x), float(y)) - if gc.get_alpha() != 1.0: - ctx.paint_with_alpha(gc.get_alpha()) - else: - ctx.paint() + ctx.paint() ctx.restore() def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): @@ -257,13 +371,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): ctx.rotate(np.deg2rad(-angle)) ctx.set_font_size(size) - if HAS_CAIRO_CFFI: - if not isinstance(s, six.text_type): - s = six.text_type(s) - else: - if six.PY2 and isinstance(s, six.text_type): - s = s.encode("utf-8") - ctx.show_text(s) ctx.restore() @@ -282,17 +389,13 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): ctx.move_to(ox, oy) fontProp = ttfFontProperty(font) - ctx.save() ctx.select_font_face(fontProp.name, self.fontangles[fontProp.style], self.fontweights[fontProp.weight]) size = fontsize * self.dpi / 72.0 ctx.set_font_size(size) - if not six.PY3 and isinstance(s, six.text_type): - s = s.encode("utf-8") ctx.show_text(s) - ctx.restore() for ox, oy, w, h in rects: ctx.new_path() @@ -398,12 +501,12 @@ def set_clip_path(self, path): ctx.new_path() affine = (affine + Affine2D().scale(1, -1).translate(0, self.renderer.height)) - RendererCairo.convert_path(ctx, tpath, affine) + _append_path(ctx, tpath, affine) ctx.clip() def set_dashes(self, offset, dashes): self._dashes = offset, dashes - if dashes == None: + if dashes is None: self.ctx.set_dash([], 0) # switch dashes off else: self.ctx.set_dash( @@ -436,15 +539,24 @@ class FigureCanvasCairo(FigureCanvasBase): supports_blit = False def print_png(self, fobj, *args, **kwargs): + self._get_printed_image_surface().write_to_png(fobj) + + def print_rgba(self, fobj, *args, **kwargs): width, height = self.get_width_height() + buf = self._get_printed_image_surface().get_data() + fobj.write(cbook._premultiplied_argb32_to_unmultiplied_rgba8888( + np.asarray(buf).reshape((width, height, 4)))) + print_raw = print_rgba + + def _get_printed_image_surface(self): + width, height = self.get_width_height() renderer = RendererCairo(self.figure.dpi) renderer.set_width_height(width, height) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) renderer.set_ctx_from_surface(surface) - self.figure.draw(renderer) - surface.write_to_png(fobj) + return surface def print_pdf(self, fobj, *args, **kwargs): return self._save(fobj, 'pdf', *args, **kwargs) @@ -486,13 +598,13 @@ def _save(self, fo, fmt, **kwargs): raise RuntimeError('cairo has not been compiled with SVG ' 'support enabled') if fmt == 'svgz': - if isinstance(fo, six.string_types): + if isinstance(fo, str): fo = gzip.GzipFile(fo, 'wb') else: fo = gzip.GzipFile(None, 'wb', fileobj=fo) surface = cairo.SVGSurface(fo, width_in_points, height_in_points) else: - warnings.warn("unknown format: %s" % fmt) + warnings.warn("unknown format: %s" % fmt, stacklevel=2) return # surface.set_dpi() can be used diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py deleted file mode 100644 index 7d18922fc370..000000000000 --- a/lib/matplotlib/backends/backend_gdk.py +++ /dev/null @@ -1,438 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -import warnings - -import gobject -import gtk; gdk = gtk.gdk -import pango -pygtk_version_required = (2,2,0) -if gtk.pygtk_version < pygtk_version_required: - raise ImportError ("PyGTK %d.%d.%d is installed\n" - "PyGTK %d.%d.%d or later is required" - % (gtk.pygtk_version + pygtk_version_required)) -del pygtk_version_required - -import numpy as np - -import matplotlib -from matplotlib import rcParams -from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import ( - _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - RendererBase) -from matplotlib.cbook import warn_deprecated -from matplotlib.mathtext import MathTextParser -from matplotlib.transforms import Affine2D -from matplotlib.backends._backend_gdk import pixbuf_get_pixels_array - -backend_version = "%d.%d.%d" % gtk.pygtk_version - -# Image formats that this backend supports - for FileChooser and print_figure() -IMAGE_FORMAT = sorted(['bmp', 'eps', 'jpg', 'png', 'ps', 'svg']) # 'raw', 'rgb' -IMAGE_FORMAT_DEFAULT = 'png' - - -class RendererGDK(RendererBase): - fontweights = { - 100 : pango.WEIGHT_ULTRALIGHT, - 200 : pango.WEIGHT_LIGHT, - 300 : pango.WEIGHT_LIGHT, - 400 : pango.WEIGHT_NORMAL, - 500 : pango.WEIGHT_NORMAL, - 600 : pango.WEIGHT_BOLD, - 700 : pango.WEIGHT_BOLD, - 800 : pango.WEIGHT_HEAVY, - 900 : pango.WEIGHT_ULTRABOLD, - 'ultralight' : pango.WEIGHT_ULTRALIGHT, - 'light' : pango.WEIGHT_LIGHT, - 'normal' : pango.WEIGHT_NORMAL, - 'medium' : pango.WEIGHT_NORMAL, - 'semibold' : pango.WEIGHT_BOLD, - 'bold' : pango.WEIGHT_BOLD, - 'heavy' : pango.WEIGHT_HEAVY, - 'ultrabold' : pango.WEIGHT_ULTRABOLD, - 'black' : pango.WEIGHT_ULTRABOLD, - } - - # cache for efficiency, these must be at class, not instance level - layoutd = {} # a map from text prop tups to pango layouts - rotated = {} # a map from text prop tups to rotated text pixbufs - - def __init__(self, gtkDA, dpi): - # widget gtkDA is used for: - # '.create_pango_layout(s)' - # cmap line below) - self.gtkDA = gtkDA - self.dpi = dpi - self._cmap = gtkDA.get_colormap() - self.mathtext_parser = MathTextParser("Agg") - - def set_pixmap (self, pixmap): - self.gdkDrawable = pixmap - - def set_width_height (self, width, height): - """w,h is the figure w,h not the pixmap w,h - """ - self.width, self.height = width, height - - def draw_path(self, gc, path, transform, rgbFace=None): - transform = transform + Affine2D(). \ - scale(1.0, -1.0).translate(0, self.height) - polygons = path.to_polygons(transform, self.width, self.height) - for polygon in polygons: - # draw_polygon won't take an arbitrary sequence -- it must be a list - # of tuples - polygon = [(int(np.round(x)), int(np.round(y))) for x, y in polygon] - if rgbFace is not None: - saveColor = gc.gdkGC.foreground - gc.gdkGC.foreground = gc.rgb_to_gdk_color(rgbFace) - self.gdkDrawable.draw_polygon(gc.gdkGC, True, polygon) - gc.gdkGC.foreground = saveColor - if gc.gdkGC.line_width > 0: - self.gdkDrawable.draw_lines(gc.gdkGC, polygon) - - def draw_image(self, gc, x, y, im): - bbox = gc.get_clip_rectangle() - - if bbox != None: - l,b,w,h = bbox.bounds - #rectangle = (int(l), self.height-int(b+h), - # int(w), int(h)) - # set clip rect? - - rows, cols = im.shape[:2] - - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, - has_alpha=True, bits_per_sample=8, - width=cols, height=rows) - - array = pixbuf_get_pixels_array(pixbuf) - array[:, :, :] = im[::-1] - - gc = self.new_gc() - - - y = self.height-y-rows - - try: # new in 2.2 - # can use None instead of gc.gdkGC, if don't need clipping - self.gdkDrawable.draw_pixbuf (gc.gdkGC, pixbuf, 0, 0, - int(x), int(y), cols, rows, - gdk.RGB_DITHER_NONE, 0, 0) - except AttributeError: - # deprecated in 2.2 - pixbuf.render_to_drawable(self.gdkDrawable, gc.gdkGC, 0, 0, - int(x), int(y), cols, rows, - gdk.RGB_DITHER_NONE, 0, 0) - - def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): - x, y = int(x), int(y) - - if x < 0 or y < 0: # window has shrunk and text is off the edge - return - - if angle not in (0,90): - warnings.warn('backend_gdk: unable to draw text at angles ' + - 'other than 0 or 90') - elif ismath: - self._draw_mathtext(gc, x, y, s, prop, angle) - - elif angle==90: - self._draw_rotated_text(gc, x, y, s, prop, angle) - - else: - layout, inkRect, logicalRect = self._get_pango_layout(s, prop) - l, b, w, h = inkRect - if (x + w > self.width or y + h > self.height): - return - - self.gdkDrawable.draw_layout(gc.gdkGC, x, y-h-b, layout) - - def _draw_mathtext(self, gc, x, y, s, prop, angle): - ox, oy, width, height, descent, font_image, used_characters = \ - self.mathtext_parser.parse(s, self.dpi, prop) - - if angle == 90: - width, height = height, width - x -= width - y -= height - - imw = font_image.get_width() - imh = font_image.get_height() - - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=True, - bits_per_sample=8, width=imw, height=imh) - - array = pixbuf_get_pixels_array(pixbuf) - - rgb = gc.get_rgb() - array[:,:,0] = int(rgb[0]*255) - array[:,:,1] = int(rgb[1]*255) - array[:,:,2] = int(rgb[2]*255) - array[:,:,3] = ( - np.fromstring(font_image.as_str(), np.uint8).reshape((imh, imw))) - - # can use None instead of gc.gdkGC, if don't need clipping - self.gdkDrawable.draw_pixbuf(gc.gdkGC, pixbuf, 0, 0, - int(x), int(y), imw, imh, - gdk.RGB_DITHER_NONE, 0, 0) - - def _draw_rotated_text(self, gc, x, y, s, prop, angle): - """ - Draw the text rotated 90 degrees, other angles are not supported - """ - # this function (and its called functions) is a bottleneck - # Pango 1.6 supports rotated text, but pygtk 2.4.0 does not yet have - # wrapper functions - # GTK+ 2.6 pixbufs support rotation - - gdrawable = self.gdkDrawable - ggc = gc.gdkGC - - layout, inkRect, logicalRect = self._get_pango_layout(s, prop) - l, b, w, h = inkRect - x = int(x-h) - y = int(y-w) - - if (x < 0 or y < 0 or # window has shrunk and text is off the edge - x + w > self.width or y + h > self.height): - return - - key = (x,y,s,angle,hash(prop)) - imageVert = self.rotated.get(key) - if imageVert != None: - gdrawable.draw_image(ggc, imageVert, 0, 0, x, y, h, w) - return - - imageBack = gdrawable.get_image(x, y, w, h) - imageVert = gdrawable.get_image(x, y, h, w) - imageFlip = gtk.gdk.Image(type=gdk.IMAGE_FASTEST, - visual=gdrawable.get_visual(), - width=w, height=h) - if imageFlip == None or imageBack == None or imageVert == None: - warnings.warn("Could not renderer vertical text") - return - imageFlip.set_colormap(self._cmap) - for i in range(w): - for j in range(h): - imageFlip.put_pixel(i, j, imageVert.get_pixel(j,w-i-1) ) - - gdrawable.draw_image(ggc, imageFlip, 0, 0, x, y, w, h) - gdrawable.draw_layout(ggc, x, y-b, layout) - - imageIn = gdrawable.get_image(x, y, w, h) - for i in range(w): - for j in range(h): - imageVert.put_pixel(j, i, imageIn.get_pixel(w-i-1,j) ) - - gdrawable.draw_image(ggc, imageBack, 0, 0, x, y, w, h) - gdrawable.draw_image(ggc, imageVert, 0, 0, x, y, h, w) - self.rotated[key] = imageVert - - def _get_pango_layout(self, s, prop): - """ - Create a pango layout instance for Text 's' with properties 'prop'. - Return - pango layout (from cache if already exists) - - Note that pango assumes a logical DPI of 96 - Ref: pango/fonts.c/pango_font_description_set_size() manual page - """ - # problem? - cache gets bigger and bigger, is never cleared out - # two (not one) layouts are created for every text item s (then they - # are cached) - why? - - key = self.dpi, s, hash(prop) - value = self.layoutd.get(key) - if value != None: - return value - - size = prop.get_size_in_points() * self.dpi / 96.0 - size = np.round(size) - - font_str = '%s, %s %i' % (prop.get_name(), prop.get_style(), size,) - font = pango.FontDescription(font_str) - - # later - add fontweight to font_str - font.set_weight(self.fontweights[prop.get_weight()]) - - layout = self.gtkDA.create_pango_layout(s) - layout.set_font_description(font) - inkRect, logicalRect = layout.get_pixel_extents() - - self.layoutd[key] = layout, inkRect, logicalRect - return layout, inkRect, logicalRect - - def flipy(self): - return True - - def get_canvas_width_height(self): - return self.width, self.height - - def get_text_width_height_descent(self, s, prop, ismath): - if ismath: - ox, oy, width, height, descent, font_image, used_characters = \ - self.mathtext_parser.parse(s, self.dpi, prop) - return width, height, descent - - layout, inkRect, logicalRect = self._get_pango_layout(s, prop) - l, b, w, h = inkRect - ll, lb, lw, lh = logicalRect - - return w, h + 1, h - lh - - def new_gc(self): - return GraphicsContextGDK(renderer=self) - - def points_to_pixels(self, points): - return points/72.0 * self.dpi - - -class GraphicsContextGDK(GraphicsContextBase): - # a cache shared by all class instances - _cached = {} # map: rgb color -> gdk.Color - - _joind = { - 'bevel' : gdk.JOIN_BEVEL, - 'miter' : gdk.JOIN_MITER, - 'round' : gdk.JOIN_ROUND, - } - - _capd = { - 'butt' : gdk.CAP_BUTT, - 'projecting' : gdk.CAP_PROJECTING, - 'round' : gdk.CAP_ROUND, - } - - - def __init__(self, renderer): - GraphicsContextBase.__init__(self) - self.renderer = renderer - self.gdkGC = gtk.gdk.GC(renderer.gdkDrawable) - self._cmap = renderer._cmap - - - def rgb_to_gdk_color(self, rgb): - """ - rgb - an RGB tuple (three 0.0-1.0 values) - return an allocated gtk.gdk.Color - """ - try: - return self._cached[tuple(rgb)] - except KeyError: - color = self._cached[tuple(rgb)] = \ - self._cmap.alloc_color( - int(rgb[0]*65535),int(rgb[1]*65535),int(rgb[2]*65535)) - return color - - - #def set_antialiased(self, b): - # anti-aliasing is not supported by GDK - - def set_capstyle(self, cs): - GraphicsContextBase.set_capstyle(self, cs) - self.gdkGC.cap_style = self._capd[self._capstyle] - - - def set_clip_rectangle(self, rectangle): - GraphicsContextBase.set_clip_rectangle(self, rectangle) - if rectangle is None: - return - l,b,w,h = rectangle.bounds - rectangle = (int(l), self.renderer.height-int(b+h)+1, - int(w), int(h)) - #rectangle = (int(l), self.renderer.height-int(b+h), - # int(w+1), int(h+2)) - self.gdkGC.set_clip_rectangle(rectangle) - - def set_dashes(self, dash_offset, dash_list): - GraphicsContextBase.set_dashes(self, dash_offset, dash_list) - - if dash_list == None: - self.gdkGC.line_style = gdk.LINE_SOLID - else: - pixels = self.renderer.points_to_pixels(np.asarray(dash_list)) - dl = [max(1, int(np.round(val))) for val in pixels] - self.gdkGC.set_dashes(dash_offset, dl) - self.gdkGC.line_style = gdk.LINE_ON_OFF_DASH - - - def set_foreground(self, fg, isRGBA=False): - GraphicsContextBase.set_foreground(self, fg, isRGBA) - self.gdkGC.foreground = self.rgb_to_gdk_color(self.get_rgb()) - - - def set_joinstyle(self, js): - GraphicsContextBase.set_joinstyle(self, js) - self.gdkGC.join_style = self._joind[self._joinstyle] - - - def set_linewidth(self, w): - GraphicsContextBase.set_linewidth(self, w) - if w == 0: - self.gdkGC.line_width = 0 - else: - pixels = self.renderer.points_to_pixels(w) - self.gdkGC.line_width = max(1, int(np.round(pixels))) - - -class FigureCanvasGDK (FigureCanvasBase): - def __init__(self, figure): - FigureCanvasBase.__init__(self, figure) - if self.__class__ == matplotlib.backends.backend_gdk.FigureCanvasGDK: - warn_deprecated('2.0', message="The GDK backend is " - "deprecated. It is untested, known to be " - "broken and will be removed in Matplotlib 3.0. " - "Use the Agg backend instead. " - "See Matplotlib usage FAQ for" - " more info on backends.", - alternative="Agg") - self._renderer_init() - - def _renderer_init(self): - self._renderer = RendererGDK (gtk.DrawingArea(), self.figure.dpi) - - def _render_figure(self, pixmap, width, height): - self._renderer.set_pixmap (pixmap) - self._renderer.set_width_height (width, height) - self.figure.draw (self._renderer) - - filetypes = FigureCanvasBase.filetypes.copy() - filetypes['jpg'] = 'JPEG' - filetypes['jpeg'] = 'JPEG' - - def print_jpeg(self, filename, *args, **kwargs): - return self._print_image(filename, 'jpeg') - print_jpg = print_jpeg - - def print_png(self, filename, *args, **kwargs): - return self._print_image(filename, 'png') - - def _print_image(self, filename, format, *args, **kwargs): - width, height = self.get_width_height() - pixmap = gtk.gdk.Pixmap (None, width, height, depth=24) - self._render_figure(pixmap, width, height) - - # jpg colors don't match the display very well, png colors match - # better - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, - width, height) - pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), - 0, 0, 0, 0, width, height) - - # set the default quality, if we are writing a JPEG. - # http://www.pygtk.org/docs/pygtk/class-gdkpixbuf.html#method-gdkpixbuf--save - options = {k: kwargs[k] for k in ['quality'] if k in kwargs} - if format in ['jpg', 'jpeg']: - options.setdefault('quality', rcParams['savefig.jpeg_quality']) - options['quality'] = str(options['quality']) - - pixbuf.save(filename, format, options=options) - - -@_Backend.export -class _BackendGDK(_Backend): - FigureCanvas = FigureCanvasGDK - FigureManager = FigureManagerBase diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py deleted file mode 100644 index a4ae7cc28b75..000000000000 --- a/lib/matplotlib/backends/backend_gtk.py +++ /dev/null @@ -1,1037 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -import logging -import os -import sys -import warnings - -if six.PY3: - warnings.warn( - "The gtk* backends have not been tested with Python 3.x", - ImportWarning) - -try: - import gobject - import gtk; gdk = gtk.gdk - import pango -except ImportError: - raise ImportError("Gtk* backend requires pygtk to be installed.") - -pygtk_version_required = (2,4,0) -if gtk.pygtk_version < pygtk_version_required: - raise ImportError ("PyGTK %d.%d.%d is installed\n" - "PyGTK %d.%d.%d or later is required" - % (gtk.pygtk_version + pygtk_version_required)) -del pygtk_version_required - -_new_tooltip_api = (gtk.pygtk_version[1] >= 12) - -import matplotlib -from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import ( - _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, - TimerBase, cursors) - -from matplotlib.backends.backend_gdk import RendererGDK, FigureCanvasGDK -from matplotlib.cbook import is_writable_file_like, warn_deprecated -from matplotlib.figure import Figure -from matplotlib.widgets import SubplotTool - -from matplotlib import ( - cbook, colors as mcolors, lines, markers, rcParams) - -_log = logging.getLogger(__name__) - -backend_version = "%d.%d.%d" % gtk.pygtk_version - -# the true dots per inch on the screen; should be display dependent -# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi -PIXELS_PER_INCH = 96 - -# Hide the benign warning that it can't stat a file that doesn't -warnings.filterwarnings('ignore', '.*Unable to retrieve the file info for.*', gtk.Warning) - -cursord = { - cursors.MOVE : gdk.Cursor(gdk.FLEUR), - cursors.HAND : gdk.Cursor(gdk.HAND2), - cursors.POINTER : gdk.Cursor(gdk.LEFT_PTR), - cursors.SELECT_REGION : gdk.Cursor(gdk.TCROSS), - cursors.WAIT : gdk.Cursor(gdk.WATCH), - } - -# ref gtk+/gtk/gtkwidget.h -def GTK_WIDGET_DRAWABLE(w): - flags = w.flags(); - return flags & gtk.VISIBLE != 0 and flags & gtk.MAPPED != 0 - - -class TimerGTK(TimerBase): - ''' - Subclass of :class:`backend_bases.TimerBase` using GTK for timer events. - - Attributes - ---------- - interval : int - The time between timer events in milliseconds. Default is 1000 ms. - single_shot : bool - Boolean flag indicating whether this timer should operate as single - shot (run once and then stop). Defaults to False. - callbacks : list - Stores list of (func, args) tuples that will be called upon timer - events. This list can be manipulated directly, or the functions - `add_callback` and `remove_callback` can be used. - - ''' - def _timer_start(self): - # Need to stop it, otherwise we potentially leak a timer id that will - # never be stopped. - self._timer_stop() - self._timer = gobject.timeout_add(self._interval, self._on_timer) - - def _timer_stop(self): - if self._timer is not None: - gobject.source_remove(self._timer) - self._timer = None - - def _timer_set_interval(self): - # Only stop and restart it if the timer has already been started - if self._timer is not None: - self._timer_stop() - self._timer_start() - - def _on_timer(self): - TimerBase._on_timer(self) - - # Gtk timeout_add() requires that the callback returns True if it - # is to be called again. - if len(self.callbacks) > 0 and not self._single: - return True - else: - self._timer = None - return False - - -class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase): - keyvald = {65507 : 'control', - 65505 : 'shift', - 65513 : 'alt', - 65508 : 'control', - 65506 : 'shift', - 65514 : 'alt', - 65361 : 'left', - 65362 : 'up', - 65363 : 'right', - 65364 : 'down', - 65307 : 'escape', - 65470 : 'f1', - 65471 : 'f2', - 65472 : 'f3', - 65473 : 'f4', - 65474 : 'f5', - 65475 : 'f6', - 65476 : 'f7', - 65477 : 'f8', - 65478 : 'f9', - 65479 : 'f10', - 65480 : 'f11', - 65481 : 'f12', - 65300 : 'scroll_lock', - 65299 : 'break', - 65288 : 'backspace', - 65293 : 'enter', - 65379 : 'insert', - 65535 : 'delete', - 65360 : 'home', - 65367 : 'end', - 65365 : 'pageup', - 65366 : 'pagedown', - 65438 : '0', - 65436 : '1', - 65433 : '2', - 65435 : '3', - 65430 : '4', - 65437 : '5', - 65432 : '6', - 65429 : '7', - 65431 : '8', - 65434 : '9', - 65451 : '+', - 65453 : '-', - 65450 : '*', - 65455 : '/', - 65439 : 'dec', - 65421 : 'enter', - 65511 : 'super', - 65512 : 'super', - 65406 : 'alt', - 65289 : 'tab', - } - - # Setting this as a static constant prevents - # this resulting expression from leaking - event_mask = (gdk.BUTTON_PRESS_MASK | - gdk.BUTTON_RELEASE_MASK | - gdk.EXPOSURE_MASK | - gdk.KEY_PRESS_MASK | - gdk.KEY_RELEASE_MASK | - gdk.ENTER_NOTIFY_MASK | - gdk.LEAVE_NOTIFY_MASK | - gdk.POINTER_MOTION_MASK | - gdk.POINTER_MOTION_HINT_MASK) - - def __init__(self, figure): - if self.__class__ == matplotlib.backends.backend_gtk.FigureCanvasGTK: - warn_deprecated('2.0', message="The GTK backend is " - "deprecated. It is untested, known to be " - "broken and will be removed in Matplotlib 3.0. " - "Use the GTKAgg backend instead. " - "See Matplotlib usage FAQ for" - " more info on backends.", - alternative="GTKAgg") - FigureCanvasBase.__init__(self, figure) - gtk.DrawingArea.__init__(self) - - self._idle_draw_id = 0 - self._need_redraw = True - self._pixmap_width = -1 - self._pixmap_height = -1 - self._lastCursor = None - - self.connect('scroll_event', self.scroll_event) - self.connect('button_press_event', self.button_press_event) - self.connect('button_release_event', self.button_release_event) - self.connect('configure_event', self.configure_event) - self.connect('expose_event', self.expose_event) - self.connect('key_press_event', self.key_press_event) - self.connect('key_release_event', self.key_release_event) - self.connect('motion_notify_event', self.motion_notify_event) - self.connect('leave_notify_event', self.leave_notify_event) - self.connect('enter_notify_event', self.enter_notify_event) - - self.set_events(self.__class__.event_mask) - - self.set_double_buffered(False) - self.set_flags(gtk.CAN_FOCUS) - self._renderer_init() - - self.last_downclick = {} - - def destroy(self): - #gtk.DrawingArea.destroy(self) - self.close_event() - if self._idle_draw_id != 0: - gobject.source_remove(self._idle_draw_id) - - def scroll_event(self, widget, event): - x = event.x - # flipy so y=0 is bottom of canvas - y = self.allocation.height - event.y - if event.direction==gdk.SCROLL_UP: - step = 1 - else: - step = -1 - FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event) - return False # finish event propagation? - - def button_press_event(self, widget, event): - x = event.x - # flipy so y=0 is bottom of canvas - y = self.allocation.height - event.y - dblclick = (event.type == gdk._2BUTTON_PRESS) - if not dblclick: - # GTK is the only backend that generates a DOWN-UP-DOWN-DBLCLICK-UP event - # sequence for a double click. All other backends have a DOWN-UP-DBLCLICK-UP - # sequence. In order to provide consistency to matplotlib users, we will - # eat the extra DOWN event in the case that we detect it is part of a double - # click. - # first, get the double click time in milliseconds. - current_time = event.get_time() - last_time = self.last_downclick.get(event.button,0) - dblclick_time = gtk.settings_get_for_screen(gdk.screen_get_default()).get_property('gtk-double-click-time') - delta_time = current_time-last_time - if delta_time < dblclick_time: - del self.last_downclick[event.button] # we do not want to eat more than one event. - return False # eat. - self.last_downclick[event.button] = current_time - FigureCanvasBase.button_press_event(self, x, y, event.button, dblclick=dblclick, guiEvent=event) - return False # finish event propagation? - - def button_release_event(self, widget, event): - x = event.x - # flipy so y=0 is bottom of canvas - y = self.allocation.height - event.y - FigureCanvasBase.button_release_event(self, x, y, event.button, guiEvent=event) - return False # finish event propagation? - - def key_press_event(self, widget, event): - key = self._get_key(event) - FigureCanvasBase.key_press_event(self, key, guiEvent=event) - return True # stop event propagation - - def key_release_event(self, widget, event): - key = self._get_key(event) - FigureCanvasBase.key_release_event(self, key, guiEvent=event) - return True # stop event propagation - - def motion_notify_event(self, widget, event): - if event.is_hint: - x, y, state = event.window.get_pointer() - else: - x, y, state = event.x, event.y, event.state - - # flipy so y=0 is bottom of canvas - y = self.allocation.height - y - FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) - return False # finish event propagation? - - def leave_notify_event(self, widget, event): - FigureCanvasBase.leave_notify_event(self, event) - - def enter_notify_event(self, widget, event): - x, y, state = event.window.get_pointer() - FigureCanvasBase.enter_notify_event(self, event, xy=(x, y)) - - def _get_key(self, event): - if event.keyval in self.keyvald: - key = self.keyvald[event.keyval] - elif event.keyval < 256: - key = chr(event.keyval) - else: - key = None - - for key_mask, prefix in ( - [gdk.MOD4_MASK, 'super'], - [gdk.MOD1_MASK, 'alt'], - [gdk.CONTROL_MASK, 'ctrl'], ): - if event.state & key_mask: - key = '{0}+{1}'.format(prefix, key) - - return key - - def configure_event(self, widget, event): - if widget.window is None: - return - w, h = event.width, event.height - if w < 3 or h < 3: - return # empty fig - - # resize the figure (in inches) - dpi = self.figure.dpi - self.figure.set_size_inches(w/dpi, h/dpi, forward=False) - self._need_redraw = True - - return False # finish event propagation? - - def draw(self): - # Note: FigureCanvasBase.draw() is inconveniently named as it clashes - # with the deprecated gtk.Widget.draw() - - self._need_redraw = True - if GTK_WIDGET_DRAWABLE(self): - self.queue_draw() - # do a synchronous draw (its less efficient than an async draw, - # but is required if/when animation is used) - self.window.process_updates (False) - - def draw_idle(self): - if self._idle_draw_id != 0: - return - def idle_draw(*args): - try: - self.draw() - finally: - self._idle_draw_id = 0 - return False - self._idle_draw_id = gobject.idle_add(idle_draw) - - - def _renderer_init(self): - """Override by GTK backends to select a different renderer - Renderer should provide the methods: - set_pixmap () - set_width_height () - that are used by - _render_figure() / _pixmap_prepare() - """ - self._renderer = RendererGDK (self, self.figure.dpi) - - - def _pixmap_prepare(self, width, height): - """ - Make sure _._pixmap is at least width, height, - create new pixmap if necessary - """ - create_pixmap = False - if width > self._pixmap_width: - # increase the pixmap in 10%+ (rather than 1 pixel) steps - self._pixmap_width = max (int (self._pixmap_width * 1.1), - width) - create_pixmap = True - - if height > self._pixmap_height: - self._pixmap_height = max (int (self._pixmap_height * 1.1), - height) - create_pixmap = True - - if create_pixmap: - self._pixmap = gdk.Pixmap (self.window, self._pixmap_width, - self._pixmap_height) - self._renderer.set_pixmap (self._pixmap) - - - def _render_figure(self, pixmap, width, height): - """used by GTK and GTKcairo. GTKAgg overrides - """ - self._renderer.set_width_height (width, height) - self.figure.draw (self._renderer) - - - def expose_event(self, widget, event): - """Expose_event for all GTK backends. Should not be overridden. - """ - toolbar = self.toolbar - # if toolbar: - # toolbar.set_cursor(cursors.WAIT) - if GTK_WIDGET_DRAWABLE(self): - if self._need_redraw: - x, y, w, h = self.allocation - self._pixmap_prepare (w, h) - self._render_figure(self._pixmap, w, h) - self._need_redraw = False - x, y, w, h = event.area - self.window.draw_drawable (self.style.fg_gc[self.state], - self._pixmap, x, y, x, y, w, h) - # if toolbar: - # toolbar.set_cursor(toolbar._lastCursor) - return False # finish event propagation? - - filetypes = FigureCanvasBase.filetypes.copy() - filetypes['jpg'] = 'JPEG' - filetypes['jpeg'] = 'JPEG' - filetypes['png'] = 'Portable Network Graphics' - - def print_jpeg(self, filename, *args, **kwargs): - return self._print_image(filename, 'jpeg') - print_jpg = print_jpeg - - def print_png(self, filename, *args, **kwargs): - return self._print_image(filename, 'png') - - def _print_image(self, filename, format, *args, **kwargs): - if self.flags() & gtk.REALIZED == 0: - # for self.window(for pixmap) and has a side effect of altering - # figure width,height (via configure-event?) - gtk.DrawingArea.realize(self) - - width, height = self.get_width_height() - pixmap = gdk.Pixmap (self.window, width, height) - self._renderer.set_pixmap (pixmap) - self._render_figure(pixmap, width, height) - - # jpg colors don't match the display very well, png colors match - # better - pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, 0, 8, width, height) - pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), - 0, 0, 0, 0, width, height) - - # set the default quality, if we are writing a JPEG. - # http://www.pygtk.org/docs/pygtk/class-gdkpixbuf.html#method-gdkpixbuf--save - options = {k: kwargs[k] for k in ['quality'] if k in kwargs} - if format in ['jpg', 'jpeg']: - options.setdefault('quality', rcParams['savefig.jpeg_quality']) - options['quality'] = str(options['quality']) - - if isinstance(filename, six.string_types): - try: - pixbuf.save(filename, format, options=options) - except gobject.GError as exc: - error_msg_gtk('Save figure failure:\n%s' % (exc,), parent=self) - elif is_writable_file_like(filename): - if hasattr(pixbuf, 'save_to_callback'): - def save_callback(buf, data=None): - data.write(buf) - try: - pixbuf.save_to_callback(save_callback, format, user_data=filename, options=options) - except gobject.GError as exc: - error_msg_gtk('Save figure failure:\n%s' % (exc,), parent=self) - else: - raise ValueError("Saving to a Python file-like object is only supported by PyGTK >= 2.8") - else: - raise ValueError("filename must be a path or a file-like object") - - def new_timer(self, *args, **kwargs): - """ - Creates a new backend-specific subclass of :class:`backend_bases.Timer`. - This is useful for getting periodic events through the backend's native - event loop. Implemented only for backends with GUIs. - - Other Parameters - ---------------- - interval : scalar - Timer interval in milliseconds - callbacks : list - Sequence of (func, args, kwargs) where ``func(*args, **kwargs)`` - will be executed by the timer every *interval*. - """ - return TimerGTK(*args, **kwargs) - - def flush_events(self): - gtk.gdk.threads_enter() - while gtk.events_pending(): - gtk.main_iteration(True) - gtk.gdk.flush() - gtk.gdk.threads_leave() - - -class FigureManagerGTK(FigureManagerBase): - """ - Attributes - ---------- - canvas : `FigureCanvas` - The FigureCanvas instance - num : int or str - The Figure number - toolbar : gtk.Toolbar - The gtk.Toolbar (gtk only) - vbox : gtk.VBox - The gtk.VBox containing the canvas and toolbar (gtk only) - window : gtk.Window - The gtk.Window (gtk only) - - """ - def __init__(self, canvas, num): - FigureManagerBase.__init__(self, canvas, num) - - self.window = gtk.Window() - self.window.set_wmclass("matplotlib", "Matplotlib") - self.set_window_title("Figure %d" % num) - if window_icon: - try: - self.window.set_icon_from_file(window_icon) - except: - # some versions of gtk throw a glib.GError but not - # all, so I am not sure how to catch it. I am unhappy - # diong a blanket catch here, but an not sure what a - # better way is - JDH - _log.info('Could not load matplotlib ' - 'icon: %s', sys.exc_info()[1]) - - self.vbox = gtk.VBox() - self.window.add(self.vbox) - self.vbox.show() - - self.canvas.show() - - self.vbox.pack_start(self.canvas, True, True) - - self.toolbar = self._get_toolbar(canvas) - - # calculate size for window - w = int (self.canvas.figure.bbox.width) - h = int (self.canvas.figure.bbox.height) - - if self.toolbar is not None: - self.toolbar.show() - self.vbox.pack_end(self.toolbar, False, False) - - tb_w, tb_h = self.toolbar.size_request() - h += tb_h - self.window.set_default_size (w, h) - - def destroy(*args): - Gcf.destroy(num) - self.window.connect("destroy", destroy) - self.window.connect("delete_event", destroy) - if matplotlib.is_interactive(): - self.window.show() - self.canvas.draw_idle() - - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolbar is not None: self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) - - self.canvas.grab_focus() - - def destroy(self, *args): - if hasattr(self, 'toolbar') and self.toolbar is not None: - self.toolbar.destroy() - if hasattr(self, 'vbox'): - self.vbox.destroy() - if hasattr(self, 'window'): - self.window.destroy() - if hasattr(self, 'canvas'): - self.canvas.destroy() - self.__dict__.clear() #Is this needed? Other backends don't have it. - - if Gcf.get_num_fig_managers()==0 and \ - not matplotlib.is_interactive() and \ - gtk.main_level() >= 1: - gtk.main_quit() - - def show(self): - # show the figure window - self.window.show() - # raise the window above others and release the "above lock" - self.window.set_keep_above(True) - self.window.set_keep_above(False) - - def full_screen_toggle(self): - self._full_screen_flag = not self._full_screen_flag - if self._full_screen_flag: - self.window.fullscreen() - else: - self.window.unfullscreen() - _full_screen_flag = False - - - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2GTK (canvas, self.window) - else: - toolbar = None - return toolbar - - def get_window_title(self): - return self.window.get_title() - - def set_window_title(self, title): - self.window.set_title(title) - - def resize(self, width, height): - 'set the canvas size in pixels' - #_, _, cw, ch = self.canvas.allocation - #_, _, ww, wh = self.window.allocation - #self.window.resize (width-cw+ww, height-ch+wh) - self.window.resize(width, height) - - -class NavigationToolbar2GTK(NavigationToolbar2, gtk.Toolbar): - def __init__(self, canvas, window): - self.win = window - gtk.Toolbar.__init__(self) - NavigationToolbar2.__init__(self, canvas) - - def set_message(self, s): - self.message.set_label(s) - - def set_cursor(self, cursor): - self.canvas.window.set_cursor(cursord[cursor]) - gtk.main_iteration() - - def release(self, event): - try: del self._pixmapBack - except AttributeError: pass - - def draw_rubberband(self, event, x0, y0, x1, y1): - 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' - drawable = self.canvas.window - if drawable is None: - return - - gc = drawable.new_gc() - - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - - w = abs(x1 - x0) - h = abs(y1 - y0) - - rect = [int(val)for val in (min(x0,x1), min(y0, y1), w, h)] - try: - lastrect, pixmapBack = self._pixmapBack - except AttributeError: - #snap image back - if event.inaxes is None: - return - - ax = event.inaxes - l,b,w,h = [int(val) for val in ax.bbox.bounds] - b = int(height)-(b+h) - axrect = l,b,w,h - self._pixmapBack = axrect, gtk.gdk.Pixmap(drawable, w, h) - self._pixmapBack[1].draw_drawable(gc, drawable, l, b, 0, 0, w, h) - else: - drawable.draw_drawable(gc, pixmapBack, 0, 0, *lastrect) - drawable.draw_rectangle(gc, False, *rect) - - - def _init_toolbar(self): - self.set_style(gtk.TOOLBAR_ICONS) - self._init_toolbar2_4() - - - def _init_toolbar2_4(self): - basedir = os.path.join(rcParams['datapath'],'images') - if not _new_tooltip_api: - self.tooltips = gtk.Tooltips() - - for text, tooltip_text, image_file, callback in self.toolitems: - if text is None: - self.insert( gtk.SeparatorToolItem(), -1 ) - continue - fname = os.path.join(basedir, image_file + '.png') - image = gtk.Image() - image.set_from_file(fname) - tbutton = gtk.ToolButton(image, text) - self.insert(tbutton, -1) - tbutton.connect('clicked', getattr(self, callback)) - if _new_tooltip_api: - tbutton.set_tooltip_text(tooltip_text) - else: - tbutton.set_tooltip(self.tooltips, tooltip_text, 'Private') - - toolitem = gtk.SeparatorToolItem() - self.insert(toolitem, -1) - # set_draw() not making separator invisible, - # bug #143692 fixed Jun 06 2004, will be in GTK+ 2.6 - toolitem.set_draw(False) - toolitem.set_expand(True) - - toolitem = gtk.ToolItem() - self.insert(toolitem, -1) - self.message = gtk.Label() - toolitem.add(self.message) - - self.show_all() - - def get_filechooser(self): - fc = FileChooserDialog( - title='Save the figure', - parent=self.win, - path=os.path.expanduser(rcParams['savefig.directory']), - filetypes=self.canvas.get_supported_filetypes(), - default_filetype=self.canvas.get_default_filetype()) - fc.set_current_name(self.canvas.get_default_filename()) - return fc - - def save_figure(self, *args): - chooser = self.get_filechooser() - fname, format = chooser.get_filename_from_user() - chooser.destroy() - if fname: - startpath = os.path.expanduser(rcParams['savefig.directory']) - # Save dir for next time, unless empty str (i.e., use cwd). - if startpath != "": - rcParams['savefig.directory'] = ( - os.path.dirname(six.text_type(fname))) - try: - self.canvas.figure.savefig(fname, format=format) - except Exception as e: - error_msg_gtk(str(e), parent=self) - - def configure_subplots(self, button): - toolfig = Figure(figsize=(6,3)) - canvas = self._get_canvas(toolfig) - toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self.canvas.figure, toolfig) - - w = int(toolfig.bbox.width) - h = int(toolfig.bbox.height) - - window = gtk.Window() - if window_icon: - try: - window.set_icon_from_file(window_icon) - except: - # we presumably already logged a message on the - # failure of the main plot, don't keep reporting - pass - window.set_title("Subplot Configuration Tool") - window.set_default_size(w, h) - vbox = gtk.VBox() - window.add(vbox) - vbox.show() - - canvas.show() - vbox.pack_start(canvas, True, True) - window.show() - - def _get_canvas(self, fig): - return FigureCanvasGTK(fig) - - -class FileChooserDialog(gtk.FileChooserDialog): - """GTK+ 2.4 file selector which presents the user with a menu - of supported image formats - """ - def __init__ (self, - title = 'Save file', - parent = None, - action = gtk.FILE_CHOOSER_ACTION_SAVE, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK), - path = None, - filetypes = [], - default_filetype = None - ): - super(FileChooserDialog, self).__init__(title, parent, action, buttons) - super(FileChooserDialog, self).set_do_overwrite_confirmation(True) - self.set_default_response(gtk.RESPONSE_OK) - - if not path: - path = os.getcwd() + os.sep - - # create an extra widget to list supported image formats - self.set_current_folder (path) - self.set_current_name ('image.' + default_filetype) - - hbox = gtk.HBox(spacing=10) - hbox.pack_start(gtk.Label ("File Format:"), expand=False) - - liststore = gtk.ListStore(gobject.TYPE_STRING) - cbox = gtk.ComboBox(liststore) - cell = gtk.CellRendererText() - cbox.pack_start(cell, True) - cbox.add_attribute(cell, 'text', 0) - hbox.pack_start(cbox) - - self.filetypes = filetypes - self.sorted_filetypes = sorted(six.iteritems(filetypes)) - default = 0 - for i, (ext, name) in enumerate(self.sorted_filetypes): - cbox.append_text("%s (*.%s)" % (name, ext)) - if ext == default_filetype: - default = i - cbox.set_active(default) - self.ext = default_filetype - - def cb_cbox_changed (cbox, data=None): - """File extension changed""" - head, filename = os.path.split(self.get_filename()) - root, ext = os.path.splitext(filename) - ext = ext[1:] - new_ext = self.sorted_filetypes[cbox.get_active()][0] - self.ext = new_ext - - if ext in self.filetypes: - filename = root + '.' + new_ext - elif ext == '': - filename = filename.rstrip('.') + '.' + new_ext - - self.set_current_name(filename) - cbox.connect("changed", cb_cbox_changed) - - hbox.show_all() - self.set_extra_widget(hbox) - - def get_filename_from_user (self): - while True: - filename = None - if self.run() != int(gtk.RESPONSE_OK): - break - filename = self.get_filename() - break - - return filename, self.ext - - -class DialogLineprops(object): - """ - A GUI dialog for controlling lineprops - """ - signals = ( - 'on_combobox_lineprops_changed', - 'on_combobox_linestyle_changed', - 'on_combobox_marker_changed', - 'on_colorbutton_linestyle_color_set', - 'on_colorbutton_markerface_color_set', - 'on_dialog_lineprops_okbutton_clicked', - 'on_dialog_lineprops_cancelbutton_clicked', - ) - - linestyles = [ls for ls in lines.Line2D.lineStyles if ls.strip()] - linestyled = {s: i for i, s in enumerate(linestyles)} - - markers = [m for m in markers.MarkerStyle.markers - if isinstance(m, six.string_types)] - markerd = {s: i for i, s in enumerate(markers)} - - def __init__(self, lines): - import gtk.glade - - datadir = matplotlib.get_data_path() - gladefile = os.path.join(datadir, 'lineprops.glade') - if not os.path.exists(gladefile): - raise IOError( - 'Could not find gladefile lineprops.glade in %s' % datadir) - - self._inited = False - self._updateson = True # suppress updates when setting widgets manually - self.wtree = gtk.glade.XML(gladefile, 'dialog_lineprops') - self.wtree.signal_autoconnect( - {s: getattr(self, s) for s in self.signals}) - - self.dlg = self.wtree.get_widget('dialog_lineprops') - - self.lines = lines - - cbox = self.wtree.get_widget('combobox_lineprops') - cbox.set_active(0) - self.cbox_lineprops = cbox - - cbox = self.wtree.get_widget('combobox_linestyles') - for ls in self.linestyles: - cbox.append_text(ls) - cbox.set_active(0) - self.cbox_linestyles = cbox - - cbox = self.wtree.get_widget('combobox_markers') - for m in self.markers: - cbox.append_text(m) - cbox.set_active(0) - self.cbox_markers = cbox - self._lastcnt = 0 - self._inited = True - - def show(self): - 'populate the combo box' - self._updateson = False - # flush the old - cbox = self.cbox_lineprops - for i in range(self._lastcnt-1,-1,-1): - cbox.remove_text(i) - - # add the new - for line in self.lines: - cbox.append_text(line.get_label()) - cbox.set_active(0) - - self._updateson = True - self._lastcnt = len(self.lines) - self.dlg.show() - - def get_active_line(self): - 'get the active line' - ind = self.cbox_lineprops.get_active() - line = self.lines[ind] - return line - - def get_active_linestyle(self): - 'get the active lineinestyle' - ind = self.cbox_linestyles.get_active() - ls = self.linestyles[ind] - return ls - - def get_active_marker(self): - 'get the active lineinestyle' - ind = self.cbox_markers.get_active() - m = self.markers[ind] - return m - - def _update(self): - 'update the active line props from the widgets' - if not self._inited or not self._updateson: return - line = self.get_active_line() - ls = self.get_active_linestyle() - marker = self.get_active_marker() - line.set_linestyle(ls) - line.set_marker(marker) - - button = self.wtree.get_widget('colorbutton_linestyle') - color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_color((r,g,b)) - - button = self.wtree.get_widget('colorbutton_markerface') - color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_markerfacecolor((r,g,b)) - - line.figure.canvas.draw() - - def on_combobox_lineprops_changed(self, item): - 'update the widgets from the active line' - if not self._inited: return - self._updateson = False - line = self.get_active_line() - - ls = line.get_linestyle() - if ls is None: ls = 'None' - self.cbox_linestyles.set_active(self.linestyled[ls]) - - marker = line.get_marker() - if marker is None: marker = 'None' - self.cbox_markers.set_active(self.markerd[marker]) - - rgba = mcolors.to_rgba(line.get_color()) - color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]]) - button = self.wtree.get_widget('colorbutton_linestyle') - button.set_color(color) - - rgba = mcolors.to_rgba(line.get_markerfacecolor()) - color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]]) - button = self.wtree.get_widget('colorbutton_markerface') - button.set_color(color) - self._updateson = True - - def on_combobox_linestyle_changed(self, item): - self._update() - - def on_combobox_marker_changed(self, item): - self._update() - - def on_colorbutton_linestyle_color_set(self, button): - self._update() - - def on_colorbutton_markerface_color_set(self, button): - 'called colorbutton marker clicked' - self._update() - - def on_dialog_lineprops_okbutton_clicked(self, button): - self._update() - self.dlg.hide() - - def on_dialog_lineprops_cancelbutton_clicked(self, button): - self.dlg.hide() - -# set icon used when windows are minimized -# Unfortunately, the SVG renderer (rsvg) leaks memory under earlier -# versions of pygtk, so we have to use a PNG file instead. -try: - if gtk.pygtk_version < (2, 8, 0) or sys.platform == 'win32': - icon_filename = 'matplotlib.png' - else: - icon_filename = 'matplotlib.svg' - window_icon = os.path.join(rcParams['datapath'], 'images', icon_filename) -except: - window_icon = None - _log.info('Could not load matplotlib icon: %s', sys.exc_info()[1]) - -def error_msg_gtk(msg, parent=None): - if parent is not None: # find the toplevel gtk.Window - parent = parent.get_toplevel() - if parent.flags() & gtk.TOPLEVEL == 0: - parent = None - - if not isinstance(msg, six.string_types): - msg = ','.join(map(str, msg)) - - dialog = gtk.MessageDialog( - parent = parent, - type = gtk.MESSAGE_ERROR, - buttons = gtk.BUTTONS_OK, - message_format = msg) - dialog.run() - dialog.destroy() - - -@_Backend.export -class _BackendGTK(_Backend): - FigureCanvas = FigureCanvasGTK - FigureManager = FigureManagerGTK - - @staticmethod - def trigger_manager_draw(manager): - manager.canvas.draw_idle() - - @staticmethod - def mainloop(): - if gtk.main_level() == 0: - gtk.main() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 359b8fd88488..41a72c1cbdf4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -1,14 +1,9 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import logging import os import sys import matplotlib -from matplotlib import backend_tools, rcParams +from matplotlib import backend_tools, cbook, rcParams from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, @@ -28,13 +23,18 @@ # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi PIXELS_PER_INCH = 96 -cursord = { - cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR), - cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2), - cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR), - cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS), - cursors.WAIT : Gdk.Cursor.new(Gdk.CursorType.WATCH), +try: + cursord = { + cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR), + cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2), + cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR), + cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS), + cursors.WAIT : Gdk.Cursor.new(Gdk.CursorType.WATCH), } +except TypeError as exc: + # Happens when running headless. Convert to ImportError to cooperate with + # backend switching. + raise ImportError(exc) class TimerGTK3(TimerBase): @@ -76,7 +76,7 @@ def _on_timer(self): # Gtk timeout_add() requires that the callback returns True if it # is to be called again. - if len(self.callbacks) > 0 and not self._single: + if self.callbacks and not self._single: return True else: self._timer = None @@ -84,55 +84,55 @@ def _on_timer(self): class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): - keyvald = {65507 : 'control', - 65505 : 'shift', - 65513 : 'alt', - 65508 : 'control', - 65506 : 'shift', - 65514 : 'alt', - 65361 : 'left', - 65362 : 'up', - 65363 : 'right', - 65364 : 'down', - 65307 : 'escape', - 65470 : 'f1', - 65471 : 'f2', - 65472 : 'f3', - 65473 : 'f4', - 65474 : 'f5', - 65475 : 'f6', - 65476 : 'f7', - 65477 : 'f8', - 65478 : 'f9', - 65479 : 'f10', - 65480 : 'f11', - 65481 : 'f12', - 65300 : 'scroll_lock', - 65299 : 'break', - 65288 : 'backspace', - 65293 : 'enter', - 65379 : 'insert', - 65535 : 'delete', - 65360 : 'home', - 65367 : 'end', - 65365 : 'pageup', - 65366 : 'pagedown', - 65438 : '0', - 65436 : '1', - 65433 : '2', - 65435 : '3', - 65430 : '4', - 65437 : '5', - 65432 : '6', - 65429 : '7', - 65431 : '8', - 65434 : '9', - 65451 : '+', - 65453 : '-', - 65450 : '*', - 65455 : '/', - 65439 : 'dec', - 65421 : 'enter', + keyvald = {65507: 'control', + 65505: 'shift', + 65513: 'alt', + 65508: 'control', + 65506: 'shift', + 65514: 'alt', + 65361: 'left', + 65362: 'up', + 65363: 'right', + 65364: 'down', + 65307: 'escape', + 65470: 'f1', + 65471: 'f2', + 65472: 'f3', + 65473: 'f4', + 65474: 'f5', + 65475: 'f6', + 65476: 'f7', + 65477: 'f8', + 65478: 'f9', + 65479: 'f10', + 65480: 'f11', + 65481: 'f12', + 65300: 'scroll_lock', + 65299: 'break', + 65288: 'backspace', + 65293: 'enter', + 65379: 'insert', + 65535: 'delete', + 65360: 'home', + 65367: 'end', + 65365: 'pageup', + 65366: 'pagedown', + 65438: '0', + 65436: '1', + 65433: '2', + 65435: '3', + 65430: '4', + 65437: '5', + 65432: '6', + 65429: '7', + 65431: '8', + 65434: '9', + 65451: '+', + 65453: '-', + 65450: '*', + 65455: '/', + 65439: 'dec', + 65421: 'enter', } # Setting this as a static constant prevents @@ -230,7 +230,10 @@ def leave_notify_event(self, widget, event): FigureCanvasBase.leave_notify_event(self, event) def enter_notify_event(self, widget, event): - FigureCanvasBase.enter_notify_event(self, event) + x = event.x + # flipy so y=0 is bottom of canvas + y = self.get_allocation().height - event.y + FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y)) def size_allocate(self, widget, allocation): dpival = self.figure.dpi @@ -267,7 +270,7 @@ def configure_event(self, widget, event): return # empty fig # resize the figure (in inches) dpi = self.figure.dpi - self.figure.set_size_inches(w/dpi, h/dpi, forward=False) + self.figure.set_size_inches(w / dpi, h / dpi, forward=False) return False # finish event propagation? def on_draw_event(self, widget, ctx): @@ -279,7 +282,7 @@ def draw(self): self.queue_draw() # do a synchronous draw (its less efficient than an async draw, # but is required if/when animation is used) - self.get_property("window").process_updates (False) + self.get_property("window").process_updates(False) def draw_idle(self): if self._idle_draw_id != 0: @@ -325,11 +328,11 @@ class FigureManagerGTK3(FigureManagerBase): num : int or str The Figure number toolbar : Gtk.Toolbar - The Gtk.Toolbar (gtk only) + The Gtk.Toolbar vbox : Gtk.VBox - The Gtk.VBox containing the canvas and toolbar (gtk only) + The Gtk.VBox containing the canvas and toolbar window : Gtk.Window - The Gtk.Window (gtk only) + The Gtk.Window """ def __init__(self, canvas, num): @@ -340,14 +343,10 @@ def __init__(self, canvas, num): self.set_window_title("Figure %d" % num) try: self.window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: - # some versions of gtk throw a glib.GError but not - # all, so I am not sure how to catch it. I am unhappy - # doing a blanket catch here, but am not sure what a - # better way is - JDH + except Exception: + # Some versions of gtk throw a glib.GError but not all, so I am not + # sure how to catch it. I am unhappy doing a blanket catch here, + # but am not sure what a better way is - JDH _log.info('Could not load matplotlib icon: %s', sys.exc_info()[1]) self.vbox = Gtk.Box() @@ -359,8 +358,8 @@ def __init__(self, canvas, num): self.vbox.pack_start(self.canvas, True, True, 0) # calculate size for window - w = int (self.canvas.figure.bbox.width) - h = int (self.canvas.figure.bbox.height) + w = int(self.canvas.figure.bbox.width) + h = int(self.canvas.figure.bbox.height) self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar() @@ -384,7 +383,7 @@ def add_widget(child, expand, fill, padding): self.toolbar.show() h += add_widget(self.toolbar, False, False, 0) - self.window.set_default_size (w, h) + self.window.set_default_size(w, h) def destroy(*args): Gcf.destroy(num) @@ -394,14 +393,6 @@ def destroy(*args): self.window.show() self.canvas.draw_idle() - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolmanager is not None: - pass - elif self.toolbar is not None: - self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) - self.canvas.grab_focus() def destroy(self, *args): @@ -421,7 +412,7 @@ def show(self): self.window.show() self.window.present() - def full_screen_toggle (self): + def full_screen_toggle(self): self._full_screen_flag = not self._full_screen_flag if self._full_screen_flag: self.window.fullscreen() @@ -476,10 +467,6 @@ def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) Gtk.main_iteration() - def release(self, event): - try: del self._pixmapBack - except AttributeError: pass - def draw_rubberband(self, event, x0, y0, x1, y1): 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' self.ctx = self.canvas.get_property("window").cairo_create() @@ -493,7 +480,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): y0 = height - y0 w = abs(x1 - x0) h = abs(y1 - y0) - rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] self.ctx.new_path() self.ctx.set_line_width(0.5) @@ -503,11 +490,11 @@ def draw_rubberband(self, event, x0, y0, x1, y1): def _init_toolbar(self): self.set_style(Gtk.ToolbarStyle.ICONS) - basedir = os.path.join(rcParams['datapath'],'images') + basedir = os.path.join(rcParams['datapath'], 'images') for text, tooltip_text, image_file, callback in self.toolitems: if text is None: - self.insert( Gtk.SeparatorToolItem(), -1 ) + self.insert(Gtk.SeparatorToolItem(), -1) continue fname = os.path.join(basedir, image_file + '.png') image = Gtk.Image() @@ -549,15 +536,14 @@ def save_figure(self, *args): startpath = os.path.expanduser(rcParams['savefig.directory']) # Save dir for next time, unless empty str (i.e., use cwd). if startpath != "": - rcParams['savefig.directory'] = ( - os.path.dirname(six.text_type(fname))) + rcParams['savefig.directory'] = os.path.dirname(fname) try: self.canvas.figure.savefig(fname, format=format) except Exception as e: error_msg_gtk(str(e), parent=self) def configure_subplots(self, button): - toolfig = Figure(figsize=(6,3)) + toolfig = Figure(figsize=(6, 3)) canvas = self._get_canvas(toolfig) toolfig.subplots_adjust(top=0.9) tool = SubplotTool(self.canvas.figure, toolfig) @@ -568,10 +554,7 @@ def configure_subplots(self, button): window = Gtk.Window() try: window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: + except Exception: # we presumably already logged a message on the # failure of the main plot, don't keep reporting pass @@ -594,31 +577,32 @@ class FileChooserDialog(Gtk.FileChooserDialog): """GTK+ file selector which remembers the last file/directory selected and presents the user with a menu of supported image formats """ - def __init__ (self, - title = 'Save file', - parent = None, - action = Gtk.FileChooserAction.SAVE, - buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_SAVE, Gtk.ResponseType.OK), - path = None, - filetypes = [], - default_filetype = None - ): - super (FileChooserDialog, self).__init__ (title, parent, action, - buttons) - self.set_default_response (Gtk.ResponseType.OK) - - if not path: path = os.getcwd() + os.sep + def __init__(self, + title = 'Save file', + parent = None, + action = Gtk.FileChooserAction.SAVE, + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, Gtk.ResponseType.OK), + path = None, + filetypes = [], + default_filetype = None + ): + super().__init__(title, parent, action, buttons) + self.set_default_response(Gtk.ResponseType.OK) + self.set_do_overwrite_confirmation(True) + + if not path: + path = os.getcwd() # create an extra widget to list supported image formats - self.set_current_folder (path) - self.set_current_name ('image.' + default_filetype) + self.set_current_folder(path) + self.set_current_name('image.' + default_filetype) hbox = Gtk.Box(spacing=10) hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0) liststore = Gtk.ListStore(GObject.TYPE_STRING) - cbox = Gtk.ComboBox() #liststore) + cbox = Gtk.ComboBox() cbox.set_model(liststore) cell = Gtk.CellRendererText() cbox.pack_start(cell, True) @@ -626,21 +610,21 @@ def __init__ (self, hbox.pack_start(cbox, False, False, 0) self.filetypes = filetypes - self.sorted_filetypes = sorted(six.iteritems(filetypes)) + sorted_filetypes = sorted(filetypes.items()) default = 0 - for i, (ext, name) in enumerate(self.sorted_filetypes): + for i, (ext, name) in enumerate(sorted_filetypes): liststore.append(["%s (*.%s)" % (name, ext)]) if ext == default_filetype: default = i cbox.set_active(default) self.ext = default_filetype - def cb_cbox_changed (cbox, data=None): + def cb_cbox_changed(cbox, data=None): """File extension changed""" head, filename = os.path.split(self.get_filename()) root, ext = os.path.splitext(filename) ext = ext[1:] - new_ext = self.sorted_filetypes[cbox.get_active()][0] + new_ext = sorted_filetypes[cbox.get_active()][0] self.ext = new_ext if ext in self.filetypes: @@ -648,21 +632,21 @@ def cb_cbox_changed (cbox, data=None): elif ext == '': filename = filename.rstrip('.') + '.' + new_ext - self.set_current_name (filename) - cbox.connect ("changed", cb_cbox_changed) + self.set_current_name(filename) + cbox.connect("changed", cb_cbox_changed) hbox.show_all() self.set_extra_widget(hbox) - def get_filename_from_user (self): - while True: - filename = None - if self.run() != int(Gtk.ResponseType.OK): - break - filename = self.get_filename() - break + @cbook.deprecated("3.0", alternative="sorted(self.filetypes.items())") + def sorted_filetypes(self): + return sorted(self.filetypes.items()) - return filename, self.ext + def get_filename_from_user(self): + if self.run() == int(Gtk.ResponseType.OK): + return self.get_filename(), self.ext + else: + return None, self.ext class RubberbandGTK3(backend_tools.RubberbandBase): @@ -695,6 +679,7 @@ def draw_rubberband(self, x0, y0, x1, y1): class ToolbarGTK3(ToolContainerBase, Gtk.Box): _icon_extension = '.png' + def __init__(self, toolmanager): ToolContainerBase.__init__(self, toolmanager) Gtk.Box.__init__(self) @@ -804,8 +789,7 @@ def trigger(self, *args, **kwargs): rcParams['savefig.directory'] = startpath else: # save dir for next time - rcParams['savefig.directory'] = os.path.dirname( - six.text_type(fname)) + rcParams['savefig.directory'] = os.path.dirname(fname) try: self.figure.canvas.print_figure(fname, format=format_) except Exception as e: @@ -829,10 +813,7 @@ def init_window(self): try: self.window.window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: + except Exception: # we presumably already logged a message on the # failure of the main plot, don't keep reporting pass @@ -870,6 +851,92 @@ def trigger(self, sender, event, data=None): self.window.present() +class HelpGTK3(backend_tools.ToolHelpBase): + def _normalize_shortcut(self, key): + """ + Convert Matplotlib key presses to GTK+ accelerator identifiers. + + Related to `FigureCanvasGTK3._get_key`. + """ + special = { + 'backspace': 'BackSpace', + 'pagedown': 'Page_Down', + 'pageup': 'Page_Up', + 'scroll_lock': 'Scroll_Lock', + } + + parts = key.split('+') + mods = ['<' + mod + '>' for mod in parts[:-1]] + key = parts[-1] + + if key in special: + key = special[key] + elif len(key) > 1: + key = key.capitalize() + elif key.isupper(): + mods += [''] + + return ''.join(mods) + key + + def _show_shortcuts_window(self): + section = Gtk.ShortcutsSection() + + for name, tool in sorted(self.toolmanager.tools.items()): + if not tool.description: + continue + + # Putting everything in a separate group allows GTK to + # automatically split them into separate columns/pages, which is + # useful because we have lots of shortcuts, some with many keys + # that are very wide. + group = Gtk.ShortcutsGroup() + section.add(group) + # A hack to remove the title since we have no group naming. + group.forall(lambda widget, data: widget.set_visible(False), None) + + shortcut = Gtk.ShortcutsShortcut( + accelerator=' '.join( + self._normalize_shortcut(key) + for key in self.toolmanager.get_tool_keymap(name) + # Will never be sent: + if 'cmd+' not in key), + title=tool.name, + subtitle=tool.description) + group.add(shortcut) + + window = Gtk.ShortcutsWindow( + title='Help', + modal=True, + transient_for=self._figure.canvas.get_toplevel()) + section.show() # Must be done explicitly before add! + window.add(section) + + window.show_all() + + def _show_shortcuts_dialog(self): + dialog = Gtk.MessageDialog( + self._figure.canvas.get_toplevel(), + 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, self._get_help_text(), + title="Help") + dialog.run() + dialog.destroy() + + def trigger(self, *args): + if Gtk.check_version(3, 20, 0) is None: + self._show_shortcuts_window() + else: + self._show_shortcuts_dialog() + + +class ToolCopyToClipboardGTK3(backend_tools.ToolCopyToClipboardBase): + def trigger(self, *args, **kwargs): + clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + window = self.canvas.get_window() + x, y, width, height = window.get_geometry() + pb = Gdk.pixbuf_get_from_window(window, x, y, width, height) + clipboard.set_image(pb) + + # Define the file to use as the GTk icon if sys.platform == 'win32': icon_filename = 'matplotlib.png' @@ -885,7 +952,7 @@ def error_msg_gtk(msg, parent=None): if not parent.is_toplevel(): parent = None - if not isinstance(msg, six.string_types): + if not isinstance(msg, str): msg = ','.join(map(str, msg)) dialog = Gtk.MessageDialog( @@ -901,12 +968,15 @@ def error_msg_gtk(msg, parent=None): backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 backend_tools.ToolSetCursor = SetCursorGTK3 backend_tools.ToolRubberband = RubberbandGTK3 +backend_tools.ToolHelp = HelpGTK3 +backend_tools.ToolCopyToClipboard = ToolCopyToClipboardGTK3 Toolbar = ToolbarGTK3 @_Backend.export class _BackendGTK3(_Backend): + required_interactive_framework = "gtk3" FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 53c625b8a50f..34c1d113d3a6 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -1,21 +1,14 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import sys import numpy as np -import warnings -from . import backend_agg, backend_gtk3 -from .backend_cairo import cairo, HAS_CAIRO_CFFI -from .backend_gtk3 import _BackendGTK3 +from .. import cbook +from . import backend_agg, backend_cairo, backend_gtk3 +from ._gtk3_compat import gi +from .backend_cairo import cairo +from .backend_gtk3 import Gtk, _BackendGTK3 from matplotlib import transforms -if six.PY3 and not HAS_CAIRO_CFFI: - warnings.warn( - "The Gtk3Agg backend is known to not work on Python 3.x with pycairo. " - "Try installing cairocffi.") - class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3, backend_agg.FigureCanvasAgg): @@ -30,38 +23,33 @@ def _render_figure(self, width, height): backend_agg.FigureCanvasAgg.draw(self) def on_draw_event(self, widget, ctx): - """ GtkDrawable draw event, like expose_event in GTK 2.X + """GtkDrawable draw event, like expose_event in GTK 2.X. """ allocation = self.get_allocation() w, h = allocation.width, allocation.height if not len(self._bbox_queue): self._render_figure(w, h) + Gtk.render_background( + self.get_style_context(), ctx, + allocation.x, allocation.y, + allocation.width, allocation.height) bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] else: bbox_queue = self._bbox_queue - if HAS_CAIRO_CFFI and not isinstance(ctx, cairo.Context): - ctx = cairo.Context._from_pointer( - cairo.ffi.cast('cairo_t **', - id(ctx) + object.__basicsize__)[0], - incref=True) + ctx = backend_cairo._to_context(ctx) for bbox in bbox_queue: - area = self.copy_from_bbox(bbox) - buf = np.fromstring(area.to_string_argb(), dtype='uint8') - x = int(bbox.x0) y = h - int(bbox.y1) width = int(bbox.x1) - int(bbox.x0) height = int(bbox.y1) - int(bbox.y0) - if HAS_CAIRO_CFFI: - image = cairo.ImageSurface.create_for_data( - buf.data, cairo.FORMAT_ARGB32, width, height) - else: - image = cairo.ImageSurface.create_for_data( - buf, cairo.FORMAT_ARGB32, width, height) + buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32( + np.asarray(self.copy_from_bbox(bbox))) + image = cairo.ImageSurface.create_for_data( + buf.ravel().data, cairo.FORMAT_ARGB32, width, height) ctx.set_source_surface(image, x, y) ctx.paint() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 2591b112d2c9..1d7416826e09 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -1,24 +1,12 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from . import backend_cairo, backend_gtk3 -from .backend_cairo import cairo, HAS_CAIRO_CFFI -from .backend_gtk3 import _BackendGTK3 +from ._gtk3_compat import gi +from .backend_gtk3 import Gtk, _BackendGTK3 from matplotlib.backend_bases import cursors class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): - if HAS_CAIRO_CFFI and not isinstance(ctx, cairo.Context): - ctx = cairo.Context._from_pointer( - cairo.ffi.cast( - 'cairo_t **', - id(ctx) + object.__basicsize__)[0], - incref=True) - - self.gc.ctx = ctx + self.gc.ctx = backend_cairo._to_context(ctx) class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3, @@ -39,6 +27,9 @@ def on_draw_event(self, widget, ctx): # toolbar.set_cursor(cursors.WAIT) self._renderer.set_context(ctx) allocation = self.get_allocation() + Gtk.render_background( + self.get_style_context(), ctx, + allocation.x, allocation.y, allocation.width, allocation.height) self._render_figure(allocation.width, allocation.height) # if toolbar: # toolbar.set_cursor(toolbar._lastCursor) diff --git a/lib/matplotlib/backends/backend_gtkagg.py b/lib/matplotlib/backends/backend_gtkagg.py deleted file mode 100644 index 14240647ccb7..000000000000 --- a/lib/matplotlib/backends/backend_gtkagg.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Render to gtk from agg -""" -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -import matplotlib -from matplotlib.cbook import warn_deprecated -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.backends.backend_gtk import ( - gtk, _BackendGTK, FigureCanvasGTK, FigureManagerGTK, NavigationToolbar2GTK, - backend_version, error_msg_gtk, PIXELS_PER_INCH) -from matplotlib.backends._gtkagg import agg_to_gtk_drawable - - -class NavigationToolbar2GTKAgg(NavigationToolbar2GTK): - def _get_canvas(self, fig): - return FigureCanvasGTKAgg(fig) - - -class FigureManagerGTKAgg(FigureManagerGTK): - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if matplotlib.rcParams['toolbar']=='toolbar2': - toolbar = NavigationToolbar2GTKAgg (canvas, self.window) - else: - toolbar = None - return toolbar - - -class FigureCanvasGTKAgg(FigureCanvasGTK, FigureCanvasAgg): - filetypes = FigureCanvasGTK.filetypes.copy() - filetypes.update(FigureCanvasAgg.filetypes) - - def __init__(self, *args, **kwargs): - warn_deprecated('2.2', - message=('The GTKAgg backend is deprecated. It is ' - 'untested and will be removed in Matplotlib ' - '3.0. Use the GTK3Agg backend instead. See ' - 'Matplotlib usage FAQ for more info on ' - 'backends.'), - alternative='GTK3Agg') - super(FigureCanvasGTKAgg, self).__init__(*args, **kwargs) - - def configure_event(self, widget, event=None): - - if widget.window is None: - return - try: - del self.renderer - except AttributeError: - pass - w,h = widget.window.get_size() - if w==1 or h==1: return # empty fig - - # compute desired figure size in inches - dpival = self.figure.dpi - winch = w/dpival - hinch = h/dpival - self.figure.set_size_inches(winch, hinch, forward=False) - self._need_redraw = True - self.resize_event() - return True - - def _render_figure(self, pixmap, width, height): - FigureCanvasAgg.draw(self) - - buf = self.buffer_rgba() - ren = self.get_renderer() - w = int(ren.width) - h = int(ren.height) - - pixbuf = gtk.gdk.pixbuf_new_from_data( - buf, gtk.gdk.COLORSPACE_RGB, True, 8, w, h, w*4) - pixmap.draw_pixbuf(pixmap.new_gc(), pixbuf, 0, 0, 0, 0, w, h, - gtk.gdk.RGB_DITHER_NONE, 0, 0) - - def blit(self, bbox=None): - agg_to_gtk_drawable(self._pixmap, self.renderer._renderer, bbox) - x, y, w, h = self.allocation - self.window.draw_drawable(self.style.fg_gc[self.state], self._pixmap, - 0, 0, 0, 0, w, h) - - def print_png(self, filename, *args, **kwargs): - # Do this so we can save the resolution of figure in the PNG file - agg = self.switch_backends(FigureCanvasAgg) - return agg.print_png(filename, *args, **kwargs) - - -@_BackendGTK.export -class _BackendGTKAgg(_BackendGTK): - FigureCanvas = FigureCanvasGTKAgg - FigureManager = FigureManagerGTKAgg diff --git a/lib/matplotlib/backends/backend_gtkcairo.py b/lib/matplotlib/backends/backend_gtkcairo.py deleted file mode 100644 index 87e6debae796..000000000000 --- a/lib/matplotlib/backends/backend_gtkcairo.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -GTK+ Matplotlib interface using cairo (not GDK) drawing operations. -Author: Steve Chaplin -""" -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -import gtk -if gtk.pygtk_version < (2, 7, 0): - import cairo.gtk - -from matplotlib import cbook -from matplotlib.backends import backend_cairo -from matplotlib.backends.backend_gtk import * -from matplotlib.backends.backend_gtk import _BackendGTK - -backend_version = ('PyGTK(%d.%d.%d) ' % gtk.pygtk_version - + 'Pycairo(%s)' % backend_cairo.backend_version) - - -class RendererGTKCairo (backend_cairo.RendererCairo): - if gtk.pygtk_version >= (2,7,0): - def set_pixmap (self, pixmap): - self.gc.ctx = pixmap.cairo_create() - else: - def set_pixmap (self, pixmap): - self.gc.ctx = cairo.gtk.gdk_cairo_create (pixmap) - - -class FigureCanvasGTKCairo(backend_cairo.FigureCanvasCairo, FigureCanvasGTK): - filetypes = FigureCanvasGTK.filetypes.copy() - filetypes.update(backend_cairo.FigureCanvasCairo.filetypes) - - def __init__(self, *args, **kwargs): - warn_deprecated('2.2', - message=('The GTKCairo backend is deprecated. It is ' - 'untested and will be removed in Matplotlib ' - '3.0. Use the GTK3Cairo backend instead. See ' - 'Matplotlib usage FAQ for more info on ' - 'backends.'), - alternative='GTK3Cairo') - super(FigureCanvasGTKCairo, self).__init__(*args, **kwargs) - - def _renderer_init(self): - """Override to use cairo (rather than GDK) renderer""" - self._renderer = RendererGTKCairo(self.figure.dpi) - - -# This class has been unused for a while at least. -@cbook.deprecated("2.1") -class FigureManagerGTKCairo(FigureManagerGTK): - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if matplotlib.rcParams['toolbar']=='toolbar2': - toolbar = NavigationToolbar2GTKCairo (canvas, self.window) - else: - toolbar = None - return toolbar - - -# This class has been unused for a while at least. -@cbook.deprecated("2.1") -class NavigationToolbar2Cairo(NavigationToolbar2GTK): - def _get_canvas(self, fig): - return FigureCanvasGTKCairo(fig) - - -@_BackendGTK.export -class _BackendGTKCairo(_BackendGTK): - FigureCanvas = FigureCanvasGTKCairo - FigureManager = FigureManagerGTK diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 4ab5d0c90772..ab7601f63bcd 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -1,6 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import os from matplotlib._pylab_helpers import Gcf @@ -91,7 +88,7 @@ def draw(self): def draw_idle(self, *args, **kwargs): self.invalidate() - def blit(self, bbox): + def blit(self, bbox=None): self.invalidate() def resize(self, width, height): @@ -129,18 +126,13 @@ def __init__(self, canvas, num): FigureManagerBase.__init__(self, canvas, num) title = "Figure %d" % num _macosx.FigureManager.__init__(self, canvas, title) - if rcParams['toolbar']=='toolbar2': + if rcParams['toolbar'] == 'toolbar2': self.toolbar = NavigationToolbar2Mac(canvas) else: self.toolbar = None if self.toolbar is not None: self.toolbar.update() - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolbar != None: self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) - if matplotlib.is_interactive(): self.show() self.canvas.draw_idle() @@ -193,6 +185,7 @@ def set_message(self, message): @_Backend.export class _BackendMac(_Backend): + required_interactive_framework = "macosx" FigureCanvas = FigureCanvasMac FigureManager = FigureManagerMac diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 8e475bd13c95..7bfc51bceb2e 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -1,10 +1,5 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import numpy as np -import six - from matplotlib.backends.backend_agg import RendererAgg from matplotlib.tight_bbox import process_figure_for_rasterizing diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 429fb1e7ccee..26ff6a2eafc4 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -3,12 +3,11 @@ # lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify # that changes made maintain expected behaviour. -import six - from base64 import b64encode import io import json import os +import pathlib import uuid from IPython.display import display, Javascript, HTML @@ -112,12 +111,10 @@ def get_javascript(cls, stream=None): output = io.StringIO() else: output = stream - super(FigureManagerNbAgg, cls).get_javascript(stream=output) - with io.open(os.path.join( - os.path.dirname(__file__), - "web_backend", 'js', - "nbagg_mpl.js"), encoding='utf8') as fd: - output.write(fd.read()) + super().get_javascript(stream=output) + output.write((pathlib.Path(__file__).parent + / "web_backend/js/nbagg_mpl.js") + .read_text(encoding="utf-8")) if stream is None: return output.getvalue() @@ -135,15 +132,15 @@ def destroy(self): def clearup_closed(self): """Clear up any closed Comms.""" - self.web_sockets = set([socket for socket in self.web_sockets - if socket.is_open()]) + self.web_sockets = {socket for socket in self.web_sockets + if socket.is_open()} if len(self.web_sockets) == 0: self.canvas.close_event() def remove_comm(self, comm_id): - self.web_sockets = set([socket for socket in self.web_sockets - if not socket.comm.comm_id == comm_id]) + self.web_sockets = {socket for socket in self.web_sockets + if not socket.comm.comm_id == comm_id} class FigureCanvasNbAgg(FigureCanvasWebAggCore): @@ -204,9 +201,7 @@ def send_json(self, content): def send_binary(self, blob): # The comm is ascii, so we always send the image in base64 # encoded data URL form. - data = b64encode(blob) - if six.PY3: - data = data.decode('ascii') + data = b64encode(blob).decode('ascii') data_uri = "data:image/png;base64,{0}".format(data) self.comm.send({'data': data_uri}) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 4f248fde9a7e..a840f9742f75 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1,14 +1,7 @@ -# -*- coding: utf-8 -*- - """ A PDF matplotlib backend Author: Jouni K Seppänen """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six import unichr import codecs import collections @@ -22,6 +15,7 @@ import struct import sys import time +import types import warnings import zlib @@ -33,7 +27,7 @@ _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer -from matplotlib.cbook import (Bunch, get_realpath_and_stat, +from matplotlib.cbook import (get_realpath_and_stat, is_writable_file_like, maxdict) from matplotlib.figure import Figure from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font @@ -151,7 +145,7 @@ def pdfRepr(obj): elif isinstance(obj, (float, np.floating)): if not np.isfinite(obj): raise ValueError("Can only output finite numbers in PDF") - r = ("%.10f" % obj).encode('ascii') + r = b"%.10f" % obj return r.rstrip(b'0').rstrip(b'.') # Booleans. Needs to be tested before integers since @@ -160,11 +154,11 @@ def pdfRepr(obj): return [b'false', b'true'][obj] # Integers are written as such. - elif isinstance(obj, (six.integer_types, np.integer)): - return ("%d" % obj).encode('ascii') + elif isinstance(obj, (int, np.integer)): + return b"%d" % obj # Unicode strings are encoded in UTF-16BE with byte-order mark. - elif isinstance(obj, six.text_type): + elif isinstance(obj, str): try: # But maybe it's really ASCII? s = obj.encode('ASCII') @@ -242,11 +236,11 @@ def __repr__(self): return "" % self.id def pdfRepr(self): - return ("%d 0 R" % self.id).encode('ascii') + return b"%d 0 R" % self.id def write(self, contents, file): write = file.write - write(("%d 0 obj\n" % self.id).encode('ascii')) + write(b"%d 0 obj\n" % self.id) write(pdfRepr(contents)) write(b"\nendobj\n") @@ -269,7 +263,7 @@ def __repr__(self): return "" % self.name def __str__(self): - return '/' + six.text_type(self.name) + return '/' + str(self.name) def __eq__(self, other): return isinstance(other, Name) and self.name == other.name @@ -325,7 +319,8 @@ def pdfRepr(self): grestore=b'Q', textpos=b'Td', selectfont=b'Tf', textmatrix=b'Tm', show=b'Tj', showkern=b'TJ', setlinewidth=b'w', clip=b'W', shading=b'sh') -Op = Bunch(**{name: Operator(value) for name, value in six.iteritems(_pdfops)}) +Op = types.SimpleNamespace(**{name: Operator(value) + for name, value in _pdfops.items()}) def _paint_path(fill, stroke): @@ -383,7 +378,7 @@ def __init__(self, id, len, file, extra=None, png=None): def _writeHeader(self): write = self.file.write - write(("%d 0 obj\n" % self.id).encode('ascii')) + write(b"%d 0 obj\n" % self.id) dict = self.extra dict['Length'] = self.len if rcParams['pdf.compression']: @@ -576,14 +571,14 @@ def finalize(self): self.writeFonts() self.writeObject( self.alphaStateObject, - {val[0]: val[1] for val in six.itervalues(self.alphaStates)}) + {val[0]: val[1] for val in self.alphaStates.values()}) self.writeHatches() self.writeGouraudTriangles() xobjects = { - name: ob for image, name, ob in six.itervalues(self._images)} - for tup in six.itervalues(self.markers): + name: ob for image, name, ob in self._images.values()} + for tup in self.markers.values(): xobjects[tup[0]] = tup[1] - for name, value in six.iteritems(self.multi_byte_charprocs): + for name, value in self.multi_byte_charprocs.items(): xobjects[name] = value for name, path, trans, ob, join, cap, padding, filled, stroked \ in self.paths: @@ -639,7 +634,7 @@ def fontName(self, fontprop): as the filename of the font. """ - if isinstance(fontprop, six.string_types): + if isinstance(fontprop, str): filename = fontprop elif rcParams['pdf.use14corefonts']: filename = findfont( @@ -660,14 +655,11 @@ def fontName(self, fontprop): return Fx @property + @cbook.deprecated("3.0") def texFontMap(self): # lazy-load texFontMap, it takes a while to parse # and usetex is a relatively rare use case - if self._texFontMap is None: - self._texFontMap = dviread.PsfontsMap( - dviread.find_tex_file('pdftex.map')) - - return self._texFontMap + return dviread.PsfontsMap(dviread.find_tex_file('pdftex.map')) def dviFontName(self, dvifont): """ @@ -680,7 +672,8 @@ def dviFontName(self, dvifont): if dvi_info is not None: return dvi_info.pdfname - psfont = self.texFontMap[dvifont.texname] + tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map')) + psfont = tex_font_map[dvifont.texname] if psfont.filename is None: raise ValueError( "No usable font file found for {} (TeX: {}); " @@ -690,7 +683,7 @@ def dviFontName(self, dvifont): pdfname = Name('F%d' % self.nextFont) self.nextFont += 1 _log.debug('Assigning font %s = %s (dvi)', pdfname, dvifont.texname) - self.dviFontInfo[dvifont.texname] = Bunch( + self.dviFontInfo[dvifont.texname] = types.SimpleNamespace( dvifont=dvifont, pdfname=pdfname, fontfile=psfont.filename, @@ -863,7 +856,7 @@ def _get_xobject_symbol_name(self, filename, symbol_name): os.path.splitext(os.path.basename(filename))[0], symbol_name) - _identityToUnicodeCMap = """/CIDInit /ProcSet findresource begin + _identityToUnicodeCMap = b"""/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo @@ -977,7 +970,7 @@ def get_char_width(charcode): # Make the charprocs array (using ttconv to generate the # actual outlines) rawcharprocs = ttconv.get_pdf_charprocs( - filename.encode(sys.getfilesystemencoding()), glyph_ids) + os.fsencode(filename), glyph_ids) charprocs = {} for charname in sorted(rawcharprocs): stream = rawcharprocs[charname] @@ -1078,7 +1071,7 @@ def embedTTFType42(font, characters, descriptor): flags=LOAD_NO_SCALE | LOAD_NO_HINTING) widths.append((ccode, cvt(glyph.horiAdvance))) if ccode < 65536: - cid_to_gid_map[ccode] = unichr(gind) + cid_to_gid_map[ccode] = chr(gind) max_ccode = max(ccode, max_ccode) widths.sort() cid_to_gid_map = cid_to_gid_map[:max_ccode + 1] @@ -1101,18 +1094,17 @@ def embedTTFType42(font, characters, descriptor): unicode_bfrange = [] for start, end in unicode_groups: unicode_bfrange.append( - "<%04x> <%04x> [%s]" % + b"<%04x> <%04x> [%s]" % (start, end, - " ".join(["<%04x>" % x for x in range(start, end+1)]))) + b" ".join(b"<%04x>" % x for x in range(start, end+1)))) unicode_cmap = (self._identityToUnicodeCMap % - (len(unicode_groups), - "\n".join(unicode_bfrange))).encode('ascii') + (len(unicode_groups), b"\n".join(unicode_bfrange))) # CIDToGIDMap stream cid_to_gid_map = "".join(cid_to_gid_map).encode("utf-16be") self.beginStream(cidToGidMapObject.id, None, - {'Length': len(cid_to_gid_map)}) + {'Length': len(cid_to_gid_map)}) self.currentstream.write(cid_to_gid_map) self.endStream() @@ -1135,15 +1127,7 @@ def embedTTFType42(font, characters, descriptor): # Beginning of main embedTTF function... - # You are lost in a maze of TrueType tables, all different... - sfnt = font.get_sfnt() - try: - ps_name = sfnt[1, 0, 0, 6].decode('mac_roman') # Macintosh scheme - except KeyError: - # Microsoft scheme: - ps_name = sfnt[3, 1, 0x0409, 6].decode('utf-16be') - # (see freetype/ttnameid.h) - ps_name = ps_name.encode('ascii', 'replace') + ps_name = font.postscript_name.encode('ascii', 'replace') ps_name = Name(ps_name) pclt = font.get_sfnt_table('pclt') or {'capHeight': 0, 'xHeight': 0} post = font.get_sfnt_table('post') or {'italicAngle': (0, 0)} @@ -1232,7 +1216,7 @@ def hatchPattern(self, hatch_style): def writeHatches(self): hatchDict = dict() sidelen = 72.0 - for hatch_style, name in six.iteritems(self.hatchPatterns): + for hatch_style, name in self.hatchPatterns.items(): ob = self.reserveObject('hatch pattern') hatchDict[name] = ob res = {'Procsets': @@ -1299,9 +1283,9 @@ def writeGouraudTriangles(self): streamarr = np.empty( (shape[0] * shape[1],), - dtype=[(str('flags'), str('u1')), - (str('points'), str('>u4'), (2,)), - (str('colors'), str('u1'), (3,))]) + dtype=[('flags', 'u1'), + ('points', '>u4', (2,)), + ('colors', 'u1', (3,))]) streamarr['flags'] = 0 streamarr['points'] = (flat_points - points_min) * factor streamarr['colors'] = flat_colors[:, :3] * 255.0 @@ -1410,7 +1394,7 @@ def _writeImg(self, data, height, width, grayscale, id, smask=None): self.endStream() def writeImages(self): - for img, name, ob in six.itervalues(self._images): + for img, name, ob in self._images.values(): height, width, data, adata = self._unpack(img) if adata is not None: smaskObject = self.reserveObject("smask") @@ -1451,7 +1435,7 @@ def markerObject(self, path, trans, fill, stroke, lw, joinstyle, def writeMarkers(self): for ((pathops, fill, stroke, joinstyle, capstyle), - (name, ob, bbox, lw)) in six.iteritems(self.markers): + (name, ob, bbox, lw)) in self.markers.items(): bbox = bbox.padded(lw * 0.5) self.beginStream( ob.id, None, @@ -1534,7 +1518,7 @@ def writeXref(self): """Write out the xref table.""" self.startxref = self.fh.tell() - self.tell_base - self.write(("xref\n0 %d\n" % self.nextObject).encode('ascii')) + self.write(b"xref\n0 %d\n" % self.nextObject) i = 0 borken = False for offset, generation, name in self.xrefTable: @@ -1543,12 +1527,9 @@ def writeXref(self): file=sys.stderr) borken = True else: - if name == 'the zero object': - key = "f" - else: - key = "n" - text = "%010d %05d %s \n" % (offset, generation, key) - self.write(text.encode('ascii')) + key = b"f" if name == 'the zero object' else b"n" + text = b"%010d %05d %b \n" % (offset, generation, key) + self.write(text) i += 1 if borken: raise AssertionError('Indirect object does not exist') @@ -1557,7 +1538,7 @@ def writeInfoDict(self): """Write out the info dictionary, checking it for good form""" def is_string_like(x): - return isinstance(x, six.string_types) + return isinstance(x, str) def is_date(x): return isinstance(x, datetime) @@ -1576,10 +1557,11 @@ def is_date(x): 'Trapped': check_trapped} for k in self.infoDict: if k not in keywords: - warnings.warn('Unknown infodict keyword: %s' % k) + warnings.warn('Unknown infodict keyword: %s' % k, stacklevel=2) else: if not keywords[k](self.infoDict[k]): - warnings.warn('Bad value for infodict keyword %s' % k) + warnings.warn('Bad value for infodict keyword %s' % k, + stacklevel=2) self.infoObject = self.reserveObject('info') self.writeObject(self.infoObject, self.infoDict) @@ -1593,8 +1575,7 @@ def writeTrailer(self): 'Root': self.rootObject, 'Info': self.infoObject})) # Could add 'ID' - self.write(("\nstartxref\n%d\n%%%%EOF\n" % - self.startxref).encode('ascii')) + self.write(b"\nstartxref\n%d\n%%%%EOF\n" % self.startxref) class RendererPdf(RendererBase): @@ -1643,17 +1624,17 @@ def check_gc(self, gc, fillcolor=None): def track_characters(self, font, s): """Keeps track of which characters are required from each font.""" - if isinstance(font, six.string_types): + if isinstance(font, str): fname = font else: fname = font.fname realpath, stat_key = get_realpath_and_stat(fname) used_characters = self.file.used_characters.setdefault( stat_key, (realpath, set())) - used_characters[1].update([ord(x) for x in s]) + used_characters[1].update(map(ord, s)) def merge_used_characters(self, other): - for stat_key, (realpath, charset) in six.iteritems(other): + for stat_key, (realpath, charset) in other.items(): used_characters = self.file.used_characters.setdefault( stat_key, (realpath, set())) used_characters[1].update(charset) @@ -1807,8 +1788,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, simplify=False): if len(vertices): x, y = vertices[-2:] - if (x < 0 or y < 0 or - x > self.file.width * 72 or y > self.file.height * 72): + if not (0 <= x <= self.file.width * 72 + and 0 <= y <= self.file.height * 72): continue dx, dy = x - lastx, y - lasty output(1, 0, 0, 1, dx, dy, Op.concat_matrix, @@ -1881,7 +1862,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): self.file.output(self.file.fontName(fontname), fontsize, Op.selectfont) prev_font = fontname, fontsize - self.file.output(self.encode_string(unichr(num), fonttype), + self.file.output(self.encode_string(chr(num), fonttype), Op.show) self.file.output(Op.end_text) @@ -1935,10 +1916,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None): pdfname = self.file.dviFontName(dvifont) seq += [['font', pdfname, dvifont.size]] oldfont = dvifont - # We need to convert the glyph numbers to bytes, and the easiest - # way to do this on both Python 2 and 3 is .encode('latin-1') - seq += [['text', x1, y1, - [six.unichr(glyph).encode('latin-1')], x1+width]] + seq += [['text', x1, y1, [bytes([glyph])], x1+width]] # Find consecutive text strings with constant y coordinate and # combine into a sequence of strings and kerns, or just one @@ -2046,7 +2024,7 @@ def check_simple_method(s): if fonttype == 3 and not isinstance(s, bytes) and len(s) != 0: # Break the string into chunks where each chunk is either # a string of chars <= 255, or a single character > 255. - s = six.text_type(s) + s = str(s) for c in s: if ord(c) <= 255: char_type = 1 @@ -2294,7 +2272,7 @@ def rgb_cmd(self, rgb): if rgb[0] == rgb[1] == rgb[2]: return [rgb[0], Op.setgray_stroke] else: - return list(rgb[:3]) + [Op.setrgb_stroke] + return [*rgb[:3], Op.setrgb_stroke] def fillcolor_cmd(self, rgb): if rgb is None or rcParams['pdf.inheritcolor']: @@ -2302,7 +2280,7 @@ def fillcolor_cmd(self, rgb): elif rgb[0] == rgb[1] == rgb[2]: return [rgb[0], Op.setgray_nonstroke] else: - return list(rgb[:3]) + [Op.setrgb_nonstroke] + return [*rgb[:3], Op.setrgb_nonstroke] def push(self): parent = GraphicsContextPdf(self.file) @@ -2568,21 +2546,22 @@ def draw(self): def get_default_filetype(self): return 'pdf' - def print_pdf(self, filename, **kwargs): - image_dpi = kwargs.get('dpi', 72) # dpi to use for images + def print_pdf(self, filename, *, + dpi=72, # dpi to use for images + bbox_inches_restore=None, metadata=None, + **kwargs): self.figure.set_dpi(72) # there are 72 pdf points to an inch width, height = self.figure.get_size_inches() if isinstance(filename, PdfPages): file = filename._file else: - file = PdfFile(filename, metadata=kwargs.pop("metadata", None)) + file = PdfFile(filename, metadata=metadata) try: file.newPage(width, height) - _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) renderer = MixedModeRenderer( - self.figure, width, height, image_dpi, - RendererPdf(file, image_dpi, height, width), - bbox_inches_restore=_bbox_inches_restore) + self.figure, width, height, dpi, + RendererPdf(file, dpi, height, width), + bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) renderer.finalize() if not isinstance(filename, PdfPages): @@ -2594,11 +2573,9 @@ def print_pdf(self, filename, **kwargs): file.close() -class FigureManagerPdf(FigureManagerBase): - pass +FigureManagerPdf = FigureManagerBase @_Backend.export class _BackendPdf(_Backend): FigureCanvas = FigureCanvasPdf - FigureManager = FigureManagerPdf diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index cec6358452d7..d26cd94547b5 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -1,54 +1,36 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import atexit import codecs import errno +import logging import math import os +import pathlib import re import shutil +import subprocess import sys import tempfile import warnings import weakref import matplotlib as mpl -from matplotlib import _png, rcParams +from matplotlib import _png, cbook, font_manager as fm, __version__, rcParams from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.cbook import is_writable_file_like -from matplotlib.compat import subprocess -from matplotlib.compat.subprocess import check_output from matplotlib.path import Path +from matplotlib.figure import Figure +from matplotlib._pylab_helpers import Gcf + +_log = logging.getLogger(__name__) ############################################################################### -# create a list of system fonts, all of these should work with xe/lua-latex -system_fonts = [] -if sys.platform.startswith('win'): - from matplotlib import font_manager - for f in font_manager.win32InstalledFonts(): - try: - system_fonts.append(font_manager.get_font(str(f)).family_name) - except: - pass # unknown error, skip this font -else: - # assuming fontconfig is installed and the command 'fc-list' exists - try: - # list scalable (non-bitmap) fonts - fc_list = check_output([str('fc-list'), ':outline,scalable', 'family']) - fc_list = fc_list.decode('utf8') - system_fonts = [f.split(',')[0] for f in fc_list.splitlines()] - system_fonts = list(set(system_fonts)) - except: - warnings.warn('error getting fonts from fc-list', UserWarning) +@cbook.deprecated("3.0") def get_texcommand(): """Get chosen TeX system from rc.""" texsystem_options = ["xelatex", "lualatex", "pdflatex"] @@ -59,23 +41,20 @@ def get_texcommand(): def get_fontspec(): """Build fontspec preamble from rc.""" latex_fontspec = [] - texcommand = get_texcommand() + texcommand = rcParams["pgf.texsystem"] if texcommand != "pdflatex": latex_fontspec.append("\\usepackage{fontspec}") if texcommand != "pdflatex" and rcParams["pgf.rcfonts"]: - # try to find fonts from rc parameters - families = ["serif", "sans-serif", "monospace"] - fontspecs = [r"\setmainfont{%s}", r"\setsansfont{%s}", - r"\setmonofont{%s}"] - for family, fontspec in zip(families, fontspecs): - matches = [f for f in rcParams["font." + family] - if f in system_fonts] - if matches: - latex_fontspec.append(fontspec % matches[0]) - else: - pass # no fonts found, fallback to LaTeX defaule + families = ["serif", "sans\\-serif", "monospace"] + commands = ["setmainfont", "setsansfont", "setmonofont"] + for family, command in zip(families, commands): + # 1) Forward slashes also work on Windows, so don't mess with + # backslashes. 2) The dirname needs to include a separator. + path = pathlib.Path(fm.findfont(family)) + latex_fontspec.append(r"\%s{%s}[Path=%s]" % ( + command, path.name, path.parent.as_posix() + "/")) return "\n".join(latex_fontspec) @@ -145,7 +124,8 @@ def _font_properties_str(prop): family = prop.get_family()[0] if family in families: commands.append(families[family]) - elif family in system_fonts and get_texcommand() != "pdflatex": + elif (any(font.name == family for font in fm.fontManager.ttflist) + and rcParams["pgf.texsystem"] != "pdflatex"): commands.append(r"\setmainfont{%s}\rmfamily" % family) else: pass # print warning? @@ -173,9 +153,9 @@ def make_pdf_to_png_converter(): tools_available = [] # check for pdftocairo try: - check_output([str("pdftocairo"), "-v"], stderr=subprocess.STDOUT) + subprocess.check_output(["pdftocairo", "-v"], stderr=subprocess.STDOUT) tools_available.append("pdftocairo") - except: + except OSError: pass # check for ghostscript gs, ver = mpl.checkdep_ghostscript() @@ -185,19 +165,19 @@ def make_pdf_to_png_converter(): # pick converter if "pdftocairo" in tools_available: def cairo_convert(pdffile, pngfile, dpi): - cmd = [str("pdftocairo"), "-singlefile", "-png", "-r", "%d" % dpi, + cmd = ["pdftocairo", "-singlefile", "-png", "-r", "%d" % dpi, pdffile, os.path.splitext(pngfile)[0]] - check_output(cmd, stderr=subprocess.STDOUT) + subprocess.check_output(cmd, stderr=subprocess.STDOUT) return cairo_convert elif "gs" in tools_available: def gs_convert(pdffile, pngfile, dpi): - cmd = [str(gs), + cmd = [gs, '-dQUIET', '-dSAFER', '-dBATCH', '-dNOPAUSE', '-dNOPROMPT', '-dUseCIEColor', '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-dDOINTERPOLATE', '-sDEVICE=png16m', '-sOutputFile=%s' % pngfile, '-r%d' % dpi, pdffile] - check_output(cmd, stderr=subprocess.STDOUT) + subprocess.check_output(cmd, stderr=subprocess.STDOUT) return gs_convert else: raise RuntimeError("No suitable pdf to png renderer found.") @@ -205,34 +185,32 @@ def gs_convert(pdffile, pngfile, dpi): class LatexError(Exception): def __init__(self, message, latex_output=""): - Exception.__init__(self, message) + super().__init__(message) self.latex_output = latex_output -class LatexManagerFactory(object): +class LatexManagerFactory: previous_instance = None @staticmethod def get_latex_manager(): - texcommand = get_texcommand() + texcommand = rcParams["pgf.texsystem"] latex_header = LatexManager._build_latex_header() prev = LatexManagerFactory.previous_instance # Check if the previous instance of LatexManager can be reused. if (prev and prev.latex_header == latex_header and prev.texcommand == texcommand): - if rcParams["pgf.debug"]: - print("reusing LatexManager") + _log.debug("reusing LatexManager") return prev else: - if rcParams["pgf.debug"]: - print("creating LatexManager") + _log.debug("creating LatexManager") new_inst = LatexManager() LatexManagerFactory.previous_instance = new_inst return new_inst -class LatexManager(object): +class LatexManager: """ The LatexManager opens an instance of the LaTeX application for determining the metrics of text elements. The LaTeX environment can be @@ -285,29 +263,26 @@ def __init__(self): # store references for __del__ self._os_path = os.path self._shutil = shutil - self._debug = rcParams["pgf.debug"] # create a tmp directory for running latex, remember to cleanup self.tmpdir = tempfile.mkdtemp(prefix="mpl_pgf_lm_") LatexManager._unclean_instances.add(self) # test the LaTeX setup to ensure a clean startup of the subprocess - self.texcommand = get_texcommand() + self.texcommand = rcParams["pgf.texsystem"] self.latex_header = LatexManager._build_latex_header() latex_end = "\n\\makeatletter\n\\@@end\n" try: - latex = subprocess.Popen([str(self.texcommand), "-halt-on-error"], + latex = subprocess.Popen([self.texcommand, "-halt-on-error"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=self.tmpdir) - except OSError as e: - if e.errno == errno.ENOENT: - raise RuntimeError( - "Latex command not found. Install %r or change " - "pgf.texsystem to the desired command." % self.texcommand) - else: - raise RuntimeError( - "Error starting process %r" % self.texcommand) + except FileNotFoundError: + raise RuntimeError( + "Latex command not found. Install %r or change " + "pgf.texsystem to the desired command." % self.texcommand) + except OSError: + raise RuntimeError("Error starting process %r" % self.texcommand) test_input = self.latex_header + latex_end stdout, stderr = latex.communicate(test_input.encode("utf-8")) if latex.returncode != 0: @@ -315,7 +290,7 @@ def __init__(self): "or error in preamble:\n%s" % stdout) # open LaTeX process for real work - latex = subprocess.Popen([str(self.texcommand), "-halt-on-error"], + latex = subprocess.Popen([self.texcommand, "-halt-on-error"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=self.tmpdir) self.latex = latex @@ -336,17 +311,16 @@ def _cleanup(self): self.latex.communicate() self.latex_stdin_utf8.close() self.latex.stdout.close() - except: + except Exception: pass try: self._shutil.rmtree(self.tmpdir) LatexManager._unclean_instances.discard(self) - except: + except Exception: sys.stderr.write("error deleting tmp directory %s\n" % self.tmpdir) def __del__(self): - if self._debug: - print("deleting LatexManager") + _log.debug("deleting LatexManager") self._cleanup() def get_width_height_descent(self, text, prop): @@ -429,7 +403,7 @@ def __init__(self, figure, fh, dummy=False): if not hasattr(fh, 'name') or not os.path.exists(fh.name): warnings.warn("streamed pgf-code does not support raster " "graphics, consider using the pgf-to-pdf option", - UserWarning) + UserWarning, stacklevel=2) self.__dict__["draw_image"] = lambda *args, **kwargs: None def draw_markers(self, gc, marker_path, marker_trans, path, trans, @@ -699,11 +673,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): writeln(self.fh, r"\pgfsetfillopacity{%f}" % alpha) writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % alpha) rgb = tuple(gc.get_rgb())[:3] - if rgb != (0, 0, 0): - writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % rgb) - writeln(self.fh, r"\pgfsetstrokecolor{textcolor}") - writeln(self.fh, r"\pgfsetfillcolor{textcolor}") - s = r"\color{textcolor}" + s + writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % rgb) + writeln(self.fh, r"\pgfsetstrokecolor{textcolor}") + writeln(self.fh, r"\pgfsetfillcolor{textcolor}") + s = r"\color{textcolor}" + s f = 1.0 / self.figure.dpi text_args = [] @@ -713,7 +686,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): mtext.get_va() != "center_baseline"): # if text anchoring can be supported, get the original coordinates # and add alignment information - x, y = mtext.get_transform().transform_point(mtext.get_position()) + pos = mtext.get_unitless_position() + x, y = mtext.get_transform().transform_point(pos) text_args.append("x=%fin" % (x * f)) text_args.append("y=%fin" % (y * f)) @@ -766,7 +740,7 @@ class GraphicsContextPgf(GraphicsContextBase): ######################################################################## -class TmpDirCleaner(object): +class TmpDirCleaner: remaining_tmpdirs = set() @staticmethod @@ -776,10 +750,10 @@ def add(tmpdir): @staticmethod def cleanup_remaining_tmpdirs(): for tmpdir in TmpDirCleaner.remaining_tmpdirs: - try: - shutil.rmtree(tmpdir) - except: - sys.stderr.write("error deleting tmp directory %s\n" % tmpdir) + shutil.rmtree( + tmpdir, + onerror=lambda *args: print("error deleting tmp directory %s" + % tmpdir, file=sys.stderr)) class FigureCanvasPgf(FigureCanvasBase): @@ -790,8 +764,9 @@ class FigureCanvasPgf(FigureCanvasBase): def get_default_filetype(self): return 'pdf' - def _print_pgf_to_fh(self, fh, *args, **kwargs): - if kwargs.get("dryrun", False): + def _print_pgf_to_fh(self, fh, *args, + dryrun=False, bbox_inches_restore=None, **kwargs): + if dryrun: renderer = RendererPgf(self.figure, None, dummy=True) self.figure.draw(renderer) return @@ -837,10 +812,9 @@ def _print_pgf_to_fh(self, fh, *args, **kwargs): r"\pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{%fin}{%fin}}" % (w, h)) writeln(fh, r"\pgfusepath{use as bounding box, clip}") - _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) renderer = MixedModeRenderer(self.figure, w, h, dpi, RendererPgf(self.figure, fh), - bbox_inches_restore=_bbox_inches_restore) + bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) # end the pgfpicture environment @@ -858,8 +832,8 @@ def print_pgf(self, fname_or_fh, *args, **kwargs): return # figure out where the pgf is to be written to - if isinstance(fname_or_fh, six.string_types): - with codecs.open(fname_or_fh, "w", encoding="utf-8") as fh: + if isinstance(fname_or_fh, str): + with open(fname_or_fh, "w", encoding="utf-8") as fh: self._print_pgf_to_fh(fh, *args, **kwargs) elif is_writable_file_like(fname_or_fh): fh = codecs.getwriter("utf-8")(fname_or_fh) @@ -893,14 +867,14 @@ def _print_pdf_to_fh(self, fh, *args, **kwargs): \\centering \\input{figure.pgf} \\end{document}""" % (w, h, latex_preamble, latex_fontspec) - with codecs.open(fname_tex, "w", "utf-8") as fh_tex: - fh_tex.write(latexcode) + pathlib.Path(fname_tex).write_text(latexcode, encoding="utf-8") - texcommand = get_texcommand() - cmdargs = [str(texcommand), "-interaction=nonstopmode", + texcommand = rcParams["pgf.texsystem"] + cmdargs = [texcommand, "-interaction=nonstopmode", "-halt-on-error", "figure.tex"] try: - check_output(cmdargs, stderr=subprocess.STDOUT, cwd=tmpdir) + subprocess.check_output( + cmdargs, stderr=subprocess.STDOUT, cwd=tmpdir) except subprocess.CalledProcessError as e: raise RuntimeError( "%s was not able to process your file.\n\nFull log:\n%s" @@ -924,7 +898,7 @@ def print_pdf(self, fname_or_fh, *args, **kwargs): return # figure out where the pdf is to be written to - if isinstance(fname_or_fh, six.string_types): + if isinstance(fname_or_fh, str): with open(fname_or_fh, "wb") as fh: self._print_pdf_to_fh(fh, *args, **kwargs) elif is_writable_file_like(fname_or_fh): @@ -960,7 +934,7 @@ def print_png(self, fname_or_fh, *args, **kwargs): self._print_pgf_to_fh(None, *args, **kwargs) return - if isinstance(fname_or_fh, six.string_types): + if isinstance(fname_or_fh, str): with open(fname_or_fh, "wb") as fh: self._print_png_to_fh(fh, *args, **kwargs) elif is_writable_file_like(fname_or_fh): @@ -973,8 +947,7 @@ def get_renderer(self): class FigureManagerPgf(FigureManagerBase): - def __init__(self, *args): - FigureManagerBase.__init__(self, *args) + pass @_Backend.export @@ -987,4 +960,217 @@ def _cleanup_all(): LatexManager._cleanup_remaining_instances() TmpDirCleaner.cleanup_remaining_tmpdirs() + atexit.register(_cleanup_all) + + +class PdfPages: + """ + A multi-page PDF file using the pgf backend + + Examples + -------- + + >>> import matplotlib.pyplot as plt + >>> # Initialize: + >>> with PdfPages('foo.pdf') as pdf: + ... # As many times as you like, create a figure fig and save it: + ... fig = plt.figure() + ... pdf.savefig(fig) + ... # When no figure is specified the current figure is saved + ... pdf.savefig() + """ + __slots__ = ( + '_outputfile', + 'keep_empty', + '_tmpdir', + '_basename', + '_fname_tex', + '_fname_pdf', + '_n_figures', + '_file', + 'metadata', + ) + + def __init__(self, filename, *, keep_empty=True, metadata=None): + """ + Create a new PdfPages object. + + Parameters + ---------- + + filename : str + Plots using :meth:`PdfPages.savefig` will be written to a file at + this location. Any older file with the same name is overwritten. + keep_empty : bool, optional + If set to False, then empty pdf files will be deleted automatically + when closed. + metadata : dictionary, optional + Information dictionary object (see PDF reference section 10.2.1 + 'Document Information Dictionary'), e.g.: + `{'Creator': 'My software', 'Author': 'Me', + 'Title': 'Awesome fig'}` + + The standard keys are `'Title'`, `'Author'`, `'Subject'`, + `'Keywords'`, `'Producer'`, `'Creator'` and `'Trapped'`. + Values have been predefined for `'Creator'` and `'Producer'`. + They can be removed by setting them to the empty string. + """ + self._outputfile = filename + self._n_figures = 0 + self.keep_empty = keep_empty + self.metadata = metadata or {} + + # create temporary directory for compiling the figure + self._tmpdir = tempfile.mkdtemp(prefix="mpl_pgf_pdfpages_") + self._basename = 'pdf_pages' + self._fname_tex = os.path.join(self._tmpdir, self._basename + ".tex") + self._fname_pdf = os.path.join(self._tmpdir, self._basename + ".pdf") + self._file = open(self._fname_tex, 'wb') + + def _write_header(self, width_inches, height_inches): + supported_keys = { + 'title', 'author', 'subject', 'keywords', 'creator', + 'producer', 'trapped' + } + infoDict = { + 'creator': 'matplotlib %s, https://matplotlib.org' % __version__, + 'producer': 'matplotlib pgf backend %s' % __version__, + } + metadata = {k.lower(): v for k, v in self.metadata.items()} + infoDict.update(metadata) + hyperref_options = '' + for k, v in infoDict.items(): + if k not in supported_keys: + raise ValueError( + 'Not a supported pdf metadata field: "{}"'.format(k) + ) + hyperref_options += 'pdf' + k + '={' + str(v) + '},' + + latex_preamble = get_preamble() + latex_fontspec = get_fontspec() + latex_header = r"""\PassOptionsToPackage{{ + {metadata} +}}{{hyperref}} +\RequirePackage{{hyperref}} +\documentclass[12pt]{{minimal}} +\usepackage[ + paperwidth={width}in, + paperheight={height}in, + margin=0in +]{{geometry}} +{preamble} +{fontspec} +\usepackage{{pgf}} +\setlength{{\parindent}}{{0pt}} + +\begin{{document}}%% +""".format( + width=width_inches, + height=height_inches, + preamble=latex_preamble, + fontspec=latex_fontspec, + metadata=hyperref_options, + ) + self._file.write(latex_header.encode('utf-8')) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def close(self): + """ + Finalize this object, running LaTeX in a temporary directory + and moving the final pdf file to `filename`. + """ + self._file.write(rb'\end{document}\n') + self._file.close() + + if self._n_figures > 0: + try: + self._run_latex() + finally: + try: + shutil.rmtree(self._tmpdir) + except: + TmpDirCleaner.add(self._tmpdir) + elif self.keep_empty: + open(self._outputfile, 'wb').close() + + def _run_latex(self): + texcommand = rcParams["pgf.texsystem"] + cmdargs = [ + texcommand, + "-interaction=nonstopmode", + "-halt-on-error", + os.path.basename(self._fname_tex), + ] + try: + subprocess.check_output( + cmdargs, stderr=subprocess.STDOUT, cwd=self._tmpdir + ) + except subprocess.CalledProcessError as e: + raise RuntimeError( + "%s was not able to process your file.\n\nFull log:\n%s" + % (texcommand, e.output.decode('utf-8'))) + + # copy file contents to target + shutil.copyfile(self._fname_pdf, self._outputfile) + + def savefig(self, figure=None, **kwargs): + """ + Saves a :class:`~matplotlib.figure.Figure` to this file as a new page. + + Any other keyword arguments are passed to + :meth:`~matplotlib.figure.Figure.savefig`. + + Parameters + ---------- + + figure : :class:`~matplotlib.figure.Figure` or int, optional + Specifies what figure is saved to file. If not specified, the + active figure is saved. If a :class:`~matplotlib.figure.Figure` + instance is provided, this figure is saved. If an int is specified, + the figure instance to save is looked up by number. + """ + if not isinstance(figure, Figure): + if figure is None: + manager = Gcf.get_active() + else: + manager = Gcf.get_fig_manager(figure) + if manager is None: + raise ValueError("No figure {}".format(figure)) + figure = manager.canvas.figure + + try: + orig_canvas = figure.canvas + figure.canvas = FigureCanvasPgf(figure) + + width, height = figure.get_size_inches() + if self._n_figures == 0: + self._write_header(width, height) + else: + # \pdfpagewidth and \pdfpageheight exist on pdftex, xetex, and + # luatex<0.85; they were renamed to \pagewidth and \pageheight + # on luatex>=0.85. + self._file.write( + br'\newpage' + br'\ifdefined\pdfpagewidth\pdfpagewidth' + br'\else\pagewidth\fi=%ain' + br'\ifdefined\pdfpageheight\pdfpageheight' + br'\else\pageheight\fi=%ain' + b'%%\n' % (width, height) + ) + + figure.savefig(self._file, format="pgf", **kwargs) + self._n_figures += 1 + finally: + figure.canvas = orig_canvas + + def get_pagecount(self): + """ + Returns the current number of pages in the multipage pdf file. + """ + return self._n_figures diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 5e475101d3b7..14f549b331e2 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1,28 +1,30 @@ """ -A PostScript backend, which can produce both PostScript .ps and .eps +A PostScript backend, which can produce both PostScript .ps and .eps. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import StringIO - -import glob, os, shutil, sys, time, datetime -import io +import binascii +import datetime +import glob +from io import StringIO, TextIOWrapper import logging +import os +import pathlib +import re +import shutil +import subprocess +import sys +from tempfile import TemporaryDirectory +import time + +import numpy as np -from tempfile import mkstemp from matplotlib import cbook, __version__, rcParams, checkdep_ghostscript from matplotlib.afm import AFM from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) - from matplotlib.cbook import (get_realpath_and_stat, is_writable_file_like, maxdict, file_requires_unicode) -from matplotlib.compat.subprocess import subprocess - from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING from matplotlib.ttconv import convert_ttf_to_ps @@ -31,14 +33,8 @@ from matplotlib.path import Path from matplotlib import _path from matplotlib.transforms import Affine2D - from matplotlib.backends.backend_mixed import MixedModeRenderer - -import numpy as np -import binascii -import re - _log = logging.getLogger(__name__) backend_version = 'Level II' @@ -78,13 +74,10 @@ def gs_version(self): except KeyError: pass - from matplotlib.compat.subprocess import Popen, PIPE - s = Popen([self.gs_exe, "--version"], stdout=PIPE) + s = subprocess.Popen( + [self.gs_exe, "--version"], stdout=subprocess.PIPE) pipe, stderr = s.communicate() - if six.PY3: - ver = pipe.decode('ascii') - else: - ver = pipe + ver = pipe.decode('ascii') try: gs_version = tuple(map(int, ver.strip().split("."))) except ValueError: @@ -129,17 +122,16 @@ def supports_ps2write(self): 'b10': (1.26,1.76)} def _get_papertype(w, h): - keys = list(six.iterkeys(papersize)) - keys.sort() - keys.reverse() - for key in keys: - if key.startswith('l'): continue - pw, ph = papersize[key] - if (w < pw) and (h < ph): return key + for key, (pw, ph) in sorted(papersize.items(), reverse=True): + if key.startswith('l'): + continue + if w < pw and h < ph: + return key return 'a0' def _num_to_str(val): - if isinstance(val, six.string_types): return val + if isinstance(val, str): + return val ival = int(val) if val == ival: return str(ival) @@ -170,17 +162,13 @@ def _move_path_to_path_or_stream(src, dst): If *dst* is a path, the metadata of *src* are *not* copied. """ if is_writable_file_like(dst): - fh = (io.open(src, 'r', encoding='latin-1') + fh = (open(src, 'r', encoding='latin-1') if file_requires_unicode(dst) - else io.open(src, 'rb')) + else open(src, 'rb')) with fh: shutil.copyfileobj(fh, dst) else: - # Py3: shutil.move(src, dst, copy_function=shutil.copyfile) - open(dst, 'w').close() - mode = os.stat(dst).st_mode - shutil.move(src, dst) - os.chmod(dst, mode) + shutil.move(src, dst, copy_function=shutil.copyfile) class RendererPS(RendererBase): @@ -231,10 +219,10 @@ def track_characters(self, font, s): realpath, stat_key = get_realpath_and_stat(font.fname) used_characters = self.used_characters.setdefault( stat_key, (realpath, set())) - used_characters[1].update([ord(x) for x in s]) + used_characters[1].update(map(ord, s)) def merge_used_characters(self, other): - for stat_key, (realpath, charset) in six.iteritems(other): + for stat_key, (realpath, charset) in other.items(): used_characters = self.used_characters.setdefault( stat_key, (realpath, set())) used_characters[1].update(charset) @@ -278,15 +266,16 @@ def set_linedash(self, offset, seq, store=1): self.linedash = (offset, seq) def set_font(self, fontname, fontsize, store=1): - if rcParams['ps.useafm']: return - if (fontname,fontsize) != (self.fontname,self.fontsize): + if rcParams['ps.useafm']: + return + if (fontname, fontsize) != (self.fontname,self.fontsize): out = ("/%s findfont\n" "%1.3f scalefont\n" "setfont\n" % (fontname, fontsize)) - self._pswriter.write(out) - if store: self.fontname = fontname - if store: self.fontsize = fontsize + if store: + self.fontname = fontname + self.fontsize = fontsize def create_hatch(self, hatch): sidelen = 72 @@ -380,7 +369,7 @@ def _get_font_afm(self, prop): "Helvetica", fontext='afm', directory=self._afm_font_dir) font = self.afmfontd.get(fname) if font is None: - with io.open(fname, 'rb') as fh: + with open(fname, 'rb') as fh: font = AFM(fh) self.afmfontd[fname] = font self.afmfontd[key] = font @@ -719,12 +708,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): self.track_characters(font, s) self.set_color(*gc.get_rgb()) - sfnt = font.get_sfnt() - try: - ps_name = sfnt[1, 0, 0, 6].decode('mac_roman') - except KeyError: - ps_name = sfnt[3, 1, 0x0409, 6].decode('utf-16be') - ps_name = ps_name.encode('ascii', 'replace').decode('ascii') + ps_name = (font.postscript_name + .encode('ascii', 'replace').decode('ascii')) self.set_font(ps_name, prop.get_size_in_points()) lastgind = None @@ -935,8 +920,12 @@ def print_ps(self, outfile, *args, **kwargs): def print_eps(self, outfile, *args, **kwargs): return self._print_ps(outfile, 'eps', *args, **kwargs) - def _print_ps(self, outfile, format, *args, **kwargs): - papertype = kwargs.pop("papertype", rcParams['ps.papersize']) + def _print_ps(self, outfile, format, *args, + papertype=None, dpi=72, facecolor='w', edgecolor='w', + orientation='portrait', + **kwargs): + if papertype is None: + papertype = rcParams['ps.papersize'] papertype = papertype.lower() if papertype == 'auto': pass @@ -944,28 +933,27 @@ def _print_ps(self, outfile, format, *args, **kwargs): raise RuntimeError('%s is not a valid papertype. Use one of %s' % (papertype, ', '.join(papersize))) - orientation = kwargs.pop("orientation", "portrait").lower() + orientation = orientation.lower() if orientation == 'landscape': isLandscape = True elif orientation == 'portrait': isLandscape = False else: raise RuntimeError('Orientation must be "portrait" or "landscape"') self.figure.set_dpi(72) # Override the dpi kwarg - imagedpi = kwargs.pop("dpi", 72) - facecolor = kwargs.pop("facecolor", "w") - edgecolor = kwargs.pop("edgecolor", "w") if rcParams['text.usetex']: - self._print_figure_tex(outfile, format, imagedpi, facecolor, edgecolor, + self._print_figure_tex(outfile, format, dpi, facecolor, edgecolor, orientation, isLandscape, papertype, **kwargs) else: - self._print_figure(outfile, format, imagedpi, facecolor, edgecolor, + self._print_figure(outfile, format, dpi, facecolor, edgecolor, orientation, isLandscape, papertype, **kwargs) - def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w', - orientation='portrait', isLandscape=False, papertype=None, - metadata=None, **kwargs): + def _print_figure( + self, outfile, format, dpi=72, facecolor='w', edgecolor='w', + orientation='portrait', isLandscape=False, papertype=None, + metadata=None, *, + dryrun=False, bbox_inches_restore=None, **kwargs): """ Render the figure to hardcopy. Set the figure patch face and edge colors. This is useful because some of the GUIs have a @@ -983,9 +971,9 @@ def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w', the key 'Creator' is used. """ isEPSF = format == 'eps' - if isinstance(outfile, - (six.string_types, getattr(os, "PathLike", ()),)): + if isinstance(outfile, (str, getattr(os, "PathLike", ()),)): outfile = title = getattr(os, "fspath", lambda obj: obj)(outfile) + title = title.encode("latin-1", "replace").decode() passed_in_file_object = False elif is_writable_file_like(outfile): title = None @@ -1035,8 +1023,6 @@ def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w', self.figure.set_facecolor(facecolor) self.figure.set_edgecolor(edgecolor) - - dryrun = kwargs.get("dryrun", False) if dryrun: class NullWriter(object): def write(self, *kl, **kwargs): @@ -1044,16 +1030,14 @@ def write(self, *kl, **kwargs): self._pswriter = NullWriter() else: - self._pswriter = io.StringIO() - + self._pswriter = StringIO() # mixed mode rendering - _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) ps_renderer = self._renderer_class(width, height, self._pswriter, imagedpi=dpi) renderer = MixedModeRenderer(self.figure, width, height, dpi, ps_renderer, - bbox_inches_restore=_bbox_inches_restore) + bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) @@ -1077,7 +1061,7 @@ def print_figure_impl(fh): else: print("%!PS-Adobe-3.0", file=fh) if title: - print("%%Title: "+title, file=fh) + print("%%Title: " + title, file=fh) print("%%Creator: " + creator_str, file=fh) # get source date from SOURCE_DATE_EPOCH, if set # See https://reproducible-builds.org/specs/source-date-epoch/ @@ -1087,7 +1071,7 @@ def print_figure_impl(fh): int(source_date_epoch)).strftime("%a %b %d %H:%M:%S %Y") else: source_date = time.ctime() - print("%%CreationDate: "+source_date, file=fh) + print("%%CreationDate: " + source_date, file=fh) print("%%Orientation: " + orientation, file=fh) if not isEPSF: print("%%DocumentPaperSizes: "+papertype, file=fh) @@ -1107,8 +1091,8 @@ def print_figure_impl(fh): for l in d.split('\n'): print(l.strip(), file=fh) if not rcParams['ps.useafm']: - for font_filename, chars in six.itervalues( - ps_renderer.used_characters): + for font_filename, chars in \ + ps_renderer.used_characters.values(): if len(chars): font = get_font(font_filename) glyph_ids = [] @@ -1134,10 +1118,8 @@ def print_figure_impl(fh): "time; consider using the Cairo backend") else: fh.flush() - convert_ttf_to_ps( - font_filename.encode( - sys.getfilesystemencoding()), - fh, fonttype, glyph_ids) + convert_ttf_to_ps(os.fsencode(font_filename), + fh, fonttype, glyph_ids) print("end", file=fh) print("%%EndProlog", file=fh) @@ -1153,7 +1135,7 @@ def print_figure_impl(fh): # write the figure content = self._pswriter.getvalue() - if not isinstance(content, six.text_type): + if not isinstance(content, str): content = content.decode('ascii') print(content, file=fh) @@ -1167,31 +1149,25 @@ def print_figure_impl(fh): if rcParams['ps.usedistiller']: # We are going to use an external program to process the output. # Write to a temporary file. - fd, tmpfile = mkstemp() - try: - with io.open(fd, 'w', encoding='latin-1') as fh: + with TemporaryDirectory() as tmpdir: + tmpfile = os.path.join(tmpdir, "tmp.ps") + with open(tmpfile, 'w', encoding='latin-1') as fh: print_figure_impl(fh) if rcParams['ps.usedistiller'] == 'ghostscript': gs_distill(tmpfile, isEPSF, ptype=papertype, bbox=bbox) elif rcParams['ps.usedistiller'] == 'xpdf': xpdf_distill(tmpfile, isEPSF, ptype=papertype, bbox=bbox) - _move_path_to_path_or_stream(tmpfile, outfile) - finally: - if os.path.isfile(tmpfile): - os.unlink(tmpfile) else: # Write directly to outfile. if passed_in_file_object: requires_unicode = file_requires_unicode(outfile) - if (not requires_unicode and - (six.PY3 or not isinstance(outfile, StringIO))): - fh = io.TextIOWrapper(outfile, encoding="latin-1") - - # Prevent the io.TextIOWrapper from closing the - # underlying file + if not requires_unicode: + fh = TextIOWrapper(outfile, encoding="latin-1") + # Prevent the TextIOWrapper from closing the underlying + # file. def do_nothing(): pass fh.close = do_nothing @@ -1200,12 +1176,13 @@ def do_nothing(): print_figure_impl(fh) else: - with io.open(outfile, 'w', encoding='latin-1') as fh: + with open(outfile, 'w', encoding='latin-1') as fh: print_figure_impl(fh) - def _print_figure_tex(self, outfile, format, dpi, facecolor, edgecolor, - orientation, isLandscape, papertype, metadata=None, - **kwargs): + def _print_figure_tex( + self, outfile, format, dpi, facecolor, edgecolor, + orientation, isLandscape, papertype, metadata=None, *, + dryrun=False, bbox_inches_restore=None, **kwargs): """ If text.usetex is True in rc, a temporary pair of tex/eps files are created to allow tex to manage the text layout via the PSFrags @@ -1215,7 +1192,7 @@ def _print_figure_tex(self, outfile, format, dpi, facecolor, edgecolor, the key 'Creator' is used. """ isEPSF = format == 'eps' - if isinstance(outfile, six.string_types): + if isinstance(outfile, str): title = outfile elif is_writable_file_like(outfile): title = None @@ -1240,7 +1217,6 @@ def _print_figure_tex(self, outfile, format, dpi, facecolor, edgecolor, self.figure.set_facecolor(facecolor) self.figure.set_edgecolor(edgecolor) - dryrun = kwargs.get("dryrun", False) if dryrun: class NullWriter(object): def write(self, *kl, **kwargs): @@ -1248,15 +1224,14 @@ def write(self, *kl, **kwargs): self._pswriter = NullWriter() else: - self._pswriter = io.StringIO() + self._pswriter = StringIO() # mixed mode rendering - _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) ps_renderer = self._renderer_class(width, height, self._pswriter, imagedpi=dpi) renderer = MixedModeRenderer(self.figure, width, height, dpi, ps_renderer, - bbox_inches_restore=_bbox_inches_restore) + bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) @@ -1275,9 +1250,9 @@ def write(self, *kl, **kwargs): # write to a temp file, we'll move it to outfile when done - fd, tmpfile = mkstemp() - try: - with io.open(fd, 'w', encoding='latin-1') as fh: + with TemporaryDirectory() as tmpdir: + tmpfile = os.path.join(tmpdir, "tmp.ps") + with open(tmpfile, 'w', encoding='latin-1') as fh: # write the Encapsulated PostScript headers print("%!PS-Adobe-3.0 EPSF-3.0", file=fh) if title: @@ -1364,9 +1339,6 @@ def write(self, *kl, **kwargs): rotated=psfrag_rotated) _move_path_to_path_or_stream(tmpfile, outfile) - finally: - if os.path.isfile(tmpfile): - os.unlink(tmpfile) def convert_psfrags(tmpfile, psfrags, font_preamble, custom_preamble, @@ -1417,24 +1389,19 @@ def convert_psfrags(tmpfile, psfrags, font_preamble, custom_preamble, paperWidth, paperHeight, '\n'.join(psfrags), angle, os.path.split(epsfile)[-1]) - with io.open(latexfile, 'wb') as latexh: - if rcParams['text.latex.unicode']: - latexh.write(s.encode('utf8')) - else: - try: - latexh.write(s.encode('ascii')) - except UnicodeEncodeError: - _log.info("You are using unicode and latex, but have " - "not enabled the matplotlib 'text.latex.unicode' " - "rcParam.") - raise + try: + pathlib.Path(latexfile).write_text( + s, encoding='utf-8' if rcParams['text.latex.unicode'] else 'ascii') + except UnicodeEncodeError: + _log.info("You are using unicode and latex, but have not enabled the " + "Matplotlib 'text.latex.unicode' rcParam.") + raise # Replace \\ for / so latex does not think there is a function call latexfile = latexfile.replace("\\", "/") # Replace ~ so Latex does not think it is line break latexfile = latexfile.replace("~", "\\string~") - command = [str("latex"), "-interaction=nonstopmode", - '"%s"' % latexfile] + command = ["latex", "-interaction=nonstopmode", '"%s"' % latexfile] _log.debug('%s', command) try: report = subprocess.check_output(command, cwd=tmpdir, @@ -1448,7 +1415,7 @@ def convert_psfrags(tmpfile, psfrags, font_preamble, custom_preamble, exc.output.decode("utf-8")))) _log.debug(report) - command = [str('dvips'), '-q', '-R0', '-o', os.path.basename(psfile), + command = ['dvips', '-q', '-R0', '-o', os.path.basename(psfile), os.path.basename(dvifile)] _log.debug(command) try: @@ -1472,7 +1439,7 @@ def convert_psfrags(tmpfile, psfrags, font_preamble, custom_preamble, # the generated ps file is in landscape and return this # information. The return value is used in pstoeps step to recover # the correct bounding box. 2010-06-05 JJL - with io.open(tmpfile) as fh: + with open(tmpfile) as fh: if "Landscape" in fh.read(1000): psfrag_rotated = True else: @@ -1549,7 +1516,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): # Pass options as `-foo#bar` instead of `-foo=bar` to keep Windows happy # (https://www.ghostscript.com/doc/9.22/Use.htm#MS_Windows). - command = [str("ps2pdf"), + command = ["ps2pdf", "-dAutoFilterColorImages#false", "-dAutoFilterGrayImages#false", "-dAutoRotatePages#false", @@ -1568,7 +1535,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): '\n\n' % exc.output.decode("utf-8"))) _log.debug(report) - command = [str("pdftops"), "-paper", "match", "-level2", pdffile, psfile] + command = ["pdftops", "-paper", "match", "-level2", pdffile, psfile] _log.debug(command) try: report = subprocess.check_output(command, stderr=subprocess.STDOUT) @@ -1596,7 +1563,7 @@ def get_bbox_header(lbrt, rotated=False): l, b, r, t = lbrt if rotated: - rotate = "%.2f %.2f translate\n90 rotate" % (l+r, 0) + rotate = "%.2f %.2f translate\n90 rotate" % (l+r, 0) else: rotate = "" bbox_info = '%%%%BoundingBox: %d %d %d %d' % (l, b, np.ceil(r), np.ceil(t)) @@ -1607,6 +1574,7 @@ def get_bbox_header(lbrt, rotated=False): # get_bbox is deprecated. I don't see any reason to use ghostscript to # find the bounding box, as the required bounding box is alread known. +@cbook.deprecated("3.0") def get_bbox(tmpfile, bbox): """ Use ghostscript's bbox device to find the center of the bounding box. @@ -1665,7 +1633,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): bbox_info, rotate = None, None epsfile = tmpfile + '.eps' - with io.open(epsfile, 'wb') as epsh, io.open(tmpfile, 'rb') as tmph: + with open(epsfile, 'wb') as epsh, open(tmpfile, 'rb') as tmph: write = epsh.write # Modify the header: for line in tmph: @@ -1712,8 +1680,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): shutil.move(epsfile, tmpfile) -class FigureManagerPS(FigureManagerBase): - pass +FigureManagerPS = FigureManagerBase # The following Python dictionary psDefs contains the entries for the @@ -1759,4 +1726,3 @@ class FigureManagerPS(FigureManagerBase): @_Backend.export class _BackendPS(_Backend): FigureCanvas = FigureCanvasPS - FigureManager = FigureManagerPS diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 92463a6573a9..f51066f7d57d 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from .backend_qt5 import ( backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, SHIFT, MODIFIER_KEYS, cursord, _create_qApp, _BackendQT5, TimerQT, MainWindow, FigureManagerQT, @@ -12,4 +7,4 @@ @_BackendQT5.export class _BackendQT4(_BackendQT5): - pass + required_interactive_framework = "qt4" diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index 7e90a09bf35e..65785bbd32bb 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -1,10 +1,6 @@ """ Render to qt from agg """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six from .backend_qt5agg import ( _BackendQT5Agg, FigureCanvasQTAgg, FigureManagerQT, NavigationToolbar2QT) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index f70bf6f1dc5f..0df0f764dc1e 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -1,17 +1,13 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six - import functools import os import re import signal import sys -from six import unichr import traceback import matplotlib +from matplotlib import backend_tools, cbook from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, @@ -20,7 +16,6 @@ from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool from matplotlib.figure import Figure from matplotlib.backend_managers import ToolManager -from matplotlib import backend_tools from .qt_compat import ( QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API) @@ -168,13 +163,10 @@ def cooperative_qwidget_init(self, *args, **kwargs): next_coop_init.__init__(self, *args, **kwargs) @functools.wraps(__init__) - def wrapper(self, **kwargs): - try: - QtWidgets.QWidget.__init__ = cooperative_qwidget_init - __init__(self, **kwargs) - finally: - # Restore __init__ - QtWidgets.QWidget.__init__ = qwidget_init + def wrapper(self, *args, **kwargs): + with cbook._setattr_cm(QtWidgets.QWidget, + __init__=cooperative_qwidget_init): + __init__(self, *args, **kwargs) return wrapper @@ -232,7 +224,7 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase): @_allow_super_init def __init__(self, figure): _create_qApp() - super(FigureCanvasQT, self).__init__(figure=figure) + super().__init__(figure=figure) self.figure = figure # We don't want to scale up the figure DPI more than once. @@ -249,6 +241,7 @@ def __init__(self, figure): self._dpi_ratio_prev = None self._draw_pending = False + self._erase_before_paint = False self._is_drawing = False self._draw_rect_callback = lambda painter: None @@ -299,7 +292,12 @@ def get_width_height(self): return int(w / self._dpi_ratio), int(h / self._dpi_ratio) def enterEvent(self, event): - FigureCanvasBase.enter_notify_event(self, guiEvent=event) + try: + x, y = self.mouseEventCoords(event.pos()) + except AttributeError: + # the event from PyQt4 does not include the position + x = y = None + FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y)) def leaveEvent(self, event): QtWidgets.QApplication.restoreOverrideCursor() @@ -380,6 +378,8 @@ def keyReleaseEvent(self, event): FigureCanvasBase.key_release_event(self, key, guiEvent=event) @property + @cbook.deprecated("3.0", "Manually check `event.guiEvent.isAutoRepeat()` " + "in the event handler.") def keyAutoRepeat(self): """ If True, enable auto-repeat for key events. @@ -387,6 +387,8 @@ def keyAutoRepeat(self): return self._keyautorepeat @keyAutoRepeat.setter + @cbook.deprecated("3.0", "Manually check `event.guiEvent.isAutoRepeat()` " + "in the event handler.") def keyAutoRepeat(self, val): self._keyautorepeat = bool(val) @@ -439,7 +441,7 @@ def _get_key(self, event): if event_key > MAX_UNICODE: return None - key = unichr(event_key) + key = chr(event_key) # qt delivers capitalized letters. fix capitalization # note that capslock is ignored if 'shift' in mods: @@ -491,11 +493,9 @@ def draw(self): # that uses the result of the draw() to update plot elements. if self._is_drawing: return - self._is_drawing = True - try: - super(FigureCanvasQT, self).draw() - finally: - self._is_drawing = False + with cbook._setattr_cm(self, _is_drawing=True): + super().draw() + self._erase_before_paint = True self.update() def draw_idle(self): @@ -611,8 +611,7 @@ def __init__(self, canvas, num): # requested size: cs = canvas.sizeHint() sbs = self.window.statusBar().sizeHint() - self._status_and_tool_height = tbs_height + sbs.height() - height = cs.height() + self._status_and_tool_height + height = cs.height() + tbs_height + sbs.height() self.window.resize(cs.width(), height) self.window.setCentralWidget(self.canvas) @@ -621,11 +620,6 @@ def __init__(self, canvas, num): self.window.show() self.canvas.draw_idle() - def notify_axes_change(fig): - # This will be called whenever the current axes is changed - if self.toolbar is not None: - self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) self.window.raise_() def full_screen_toggle(self): @@ -665,8 +659,11 @@ def _get_toolmanager(self): return toolmanager def resize(self, width, height): - 'set the canvas size in pixels' - self.window.resize(width, height + self._status_and_tool_height) + # these are Qt methods so they return sizes in 'virtual' pixels + # so we do not need to worry about dpi scaling here. + extra_width = self.window.width() - self.canvas.width() + extra_height = self.window.height() - self.canvas.height() + self.window.resize(width+extra_width, height+extra_height) def show(self): self.window.show() @@ -685,7 +682,7 @@ def destroy(self, *args): self.window.close() def get_window_title(self): - return six.text_type(self.window.windowTitle()) + return self.window.windowTitle() def set_window_title(self, title): self.window.setWindowTitle(title) @@ -762,7 +759,7 @@ def _init_toolbar(self): # the actual sizeHint, so override it instead in order to make the # aesthetic adjustments noted above. def sizeHint(self): - size = super(NavigationToolbar2QT, self).sizeHint() + size = super().sizeHint() size.setHeight(max(48, size.height())) return size @@ -786,7 +783,7 @@ def edit_parameters(self): item, ok = QtWidgets.QInputDialog.getItem( self.parent, 'Customize', 'Select axes:', titles, 0, False) if ok: - axes = allaxes[titles.index(six.text_type(item))] + axes = allaxes[titles.index(item)] else: return @@ -798,11 +795,11 @@ def _update_buttons_checked(self): self._actions['zoom'].setChecked(self._active == 'ZOOM') def pan(self, *args): - super(NavigationToolbar2QT, self).pan(*args) + super().pan(*args) self._update_buttons_checked() def zoom(self, *args): - super(NavigationToolbar2QT, self).zoom(*args) + super().zoom(*args) self._update_buttons_checked() def set_message(self, s): @@ -832,7 +829,7 @@ def configure_subplots(self): def save_figure(self, *args): filetypes = self.canvas.get_supported_filetypes_grouped() - sorted_filetypes = sorted(six.iteritems(filetypes)) + sorted_filetypes = sorted(filetypes.items()) default_filetype = self.canvas.get_default_filetype() startpath = os.path.expanduser( @@ -855,12 +852,12 @@ def save_figure(self, *args): # Save dir for next time, unless empty str (i.e., use cwd). if startpath != "": matplotlib.rcParams['savefig.directory'] = ( - os.path.dirname(six.text_type(fname))) + os.path.dirname(fname)) try: - self.canvas.figure.savefig(six.text_type(fname)) + self.canvas.figure.savefig(fname) except Exception as e: QtWidgets.QMessageBox.critical( - self, "Error saving file", six.text_type(e), + self, "Error saving file", str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) @@ -938,7 +935,6 @@ def __init__(self, toolmanager, parent): QtWidgets.QToolBar.__init__(self, parent) self._toolitems = {} self._groups = {} - self._last = None @property def _icon_extension(self): @@ -963,7 +959,6 @@ def handler(): else: button.clicked.connect(handler) - self._last = button self._toolitems.setdefault(name, []) self._add_to_group(group, name, button, position) self._toolitems[name].append((button, handler)) @@ -1021,7 +1016,7 @@ def trigger(self, *args): class SaveFigureQt(backend_tools.SaveFigureBase): def trigger(self, *args): filetypes = self.canvas.get_supported_filetypes_grouped() - sorted_filetypes = sorted(six.iteritems(filetypes)) + sorted_filetypes = sorted(filetypes.items()) default_filetype = self.canvas.get_default_filetype() startpath = os.path.expanduser( @@ -1045,12 +1040,12 @@ def trigger(self, *args): # Save dir for next time, unless empty str (i.e., use cwd). if startpath != "": matplotlib.rcParams['savefig.directory'] = ( - os.path.dirname(six.text_type(fname))) + os.path.dirname(fname)) try: - self.canvas.figure.savefig(six.text_type(fname)) + self.canvas.figure.savefig(fname) except Exception as e: QtWidgets.QMessageBox.critical( - self, "Error saving file", six.text_type(e), + self, "Error saving file", str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) @@ -1071,20 +1066,35 @@ def remove_rubberband(self): self.canvas.drawRectangle(None) +class HelpQt(backend_tools.ToolHelpBase): + def trigger(self, *args): + QtWidgets.QMessageBox.information(None, "Help", self._get_help_html()) + + +class ToolCopyToClipboardQT(backend_tools.ToolCopyToClipboardBase): + def trigger(self, *args, **kwargs): + pixmap = self.canvas.grab() + qApp.clipboard().setPixmap(pixmap) + + backend_tools.ToolSaveFigure = SaveFigureQt backend_tools.ToolConfigureSubplots = ConfigureSubplotsQt backend_tools.ToolSetCursor = SetCursorQt backend_tools.ToolRubberband = RubberbandQt +backend_tools.ToolHelp = HelpQt +backend_tools.ToolCopyToClipboard = ToolCopyToClipboardQT +@cbook.deprecated("3.0") def error_msg_qt(msg, parent=None): - if not isinstance(msg, six.string_types): + if not isinstance(msg, str): msg = ','.join(map(str, msg)) QtWidgets.QMessageBox.warning(None, "Matplotlib", msg, QtGui.QMessageBox.Ok) +@cbook.deprecated("3.0") def exception_handler(type, value, tb): """Handle uncaught exceptions It does not catch SystemExit @@ -1096,7 +1106,7 @@ def exception_handler(type, value, tb): if hasattr(value, 'strerror') and value.strerror is not None: msg += value.strerror else: - msg += six.text_type(value) + msg += str(value) if len(msg): error_msg_qt(msg) @@ -1104,6 +1114,7 @@ def exception_handler(type, value, tb): @_Backend.export class _BackendQT5(_Backend): + required_interactive_framework = "qt5" FigureCanvas = FigureCanvasQT FigureManager = FigureManagerQT diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index f0268299bad4..ae37afe0bdbb 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -1,16 +1,12 @@ """ Render to qt from agg """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six import ctypes -from matplotlib import cbook from matplotlib.transforms import Bbox +from .. import cbook from .backend_agg import FigureCanvasAgg from .backend_qt5 import ( QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT, @@ -21,15 +17,10 @@ class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT): def __init__(self, figure): - super(FigureCanvasQTAgg, self).__init__(figure=figure) - self._bbox_queue = [] - - @property - @cbook.deprecated("2.1") - def blitbox(self): - return self._bbox_queue + # Must pass 'figure' as kwarg to Qt base class. + super().__init__(figure=figure) - def paintEvent(self, e): + def paintEvent(self, event): """Copy the image from the Agg canvas to the qt.drawable. In Qt, all drawing should be done inside of here when a widget is @@ -47,29 +38,34 @@ def paintEvent(self, e): painter = QtGui.QPainter(self) - if self._bbox_queue: - bbox_queue = self._bbox_queue - else: + if self._erase_before_paint: painter.eraseRect(self.rect()) - bbox_queue = [ - Bbox([[0, 0], [self.renderer.width, self.renderer.height]])] - self._bbox_queue = [] - for bbox in bbox_queue: - l, b, r, t = map(int, bbox.extents) - w = r - l - h = t - b - reg = self.copy_from_bbox(bbox) - buf = reg.to_string_argb() - qimage = QtGui.QImage(buf, w, h, QtGui.QImage.Format_ARGB32) - # Adjust the buf reference count to work around a memory leak bug - # in QImage under PySide on Python 3. - if QT_API == 'PySide' and six.PY3: - ctypes.c_long.from_address(id(buf)).value = 1 - if hasattr(qimage, 'setDevicePixelRatio'): - # Not available on Qt4 or some older Qt5. - qimage.setDevicePixelRatio(self._dpi_ratio) - origin = QtCore.QPoint(l, self.renderer.height - t) - painter.drawImage(origin / self._dpi_ratio, qimage) + self._erase_before_paint = False + + rect = event.rect() + left = rect.left() + top = rect.top() + width = rect.width() + height = rect.height() + # See documentation of QRect: bottom() and right() are off by 1, so use + # left() + width() and top() + height(). + bbox = Bbox( + [[left, self.renderer.height - (top + height * self._dpi_ratio)], + [left + width * self._dpi_ratio, self.renderer.height - top]]) + reg = self.copy_from_bbox(bbox) + buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32( + memoryview(reg)) + qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0], + QtGui.QImage.Format_ARGB32_Premultiplied) + if hasattr(qimage, 'setDevicePixelRatio'): + # Not available on Qt4 or some older Qt5. + qimage.setDevicePixelRatio(self._dpi_ratio) + origin = QtCore.QPoint(left, top) + painter.drawImage(origin / self._dpi_ratio, qimage) + # Adjust the buf reference count to work around a memory + # leak bug in QImage under PySide on Python 3. + if QT_API in ('PySide', 'PySide2'): + ctypes.c_long.from_address(id(buf)).value = 1 self._draw_rect_callback(painter) @@ -83,23 +79,16 @@ def blit(self, bbox=None): if bbox is None and self.figure: bbox = self.figure.bbox - self._bbox_queue.append(bbox) - # repaint uses logical pixels, not physical pixels like the renderer. l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds] t = b + h self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h) def print_figure(self, *args, **kwargs): - super(FigureCanvasQTAgg, self).print_figure(*args, **kwargs) + super().print_figure(*args, **kwargs) self.draw() -@cbook.deprecated("2.2") -class FigureCanvasQTAggBase(FigureCanvasQTAgg): - pass - - @_BackendQT5.export class _BackendQT5Agg(_BackendQT5): FigureCanvas = FigureCanvasQTAgg diff --git a/lib/matplotlib/backends/backend_qt5cairo.py b/lib/matplotlib/backends/backend_qt5cairo.py index 1108707c3a0d..bea4f6069c33 100644 --- a/lib/matplotlib/backends/backend_qt5cairo.py +++ b/lib/matplotlib/backends/backend_qt5cairo.py @@ -1,5 +1,4 @@ - -import six +import ctypes from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT @@ -8,14 +7,14 @@ class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo): def __init__(self, figure): - super(FigureCanvasQTCairo, self).__init__(figure=figure) + super().__init__(figure=figure) self._renderer = RendererCairo(self.figure.dpi) self._renderer.set_width_height(-1, -1) # Invalid values. def draw(self): if hasattr(self._renderer.gc, "ctx"): self.figure.draw(self._renderer) - super(FigureCanvasQTCairo, self).draw() + super().draw() def paintEvent(self, event): self._update_dpi() @@ -32,13 +31,15 @@ def paintEvent(self, event): QtGui.QImage.Format_ARGB32_Premultiplied) # Adjust the buf reference count to work around a memory leak bug in # QImage under PySide on Python 3. - if QT_API == 'PySide' and six.PY3: - import ctypes + if QT_API == 'PySide': ctypes.c_long.from_address(id(buf)).value = 1 if hasattr(qimage, 'setDevicePixelRatio'): # Not available on Qt4 or some older Qt5. qimage.setDevicePixelRatio(dpi_ratio) painter = QtGui.QPainter(self) + if self._erase_before_paint: + painter.eraseRect(self.rect()) + self._erase_before_paint = False painter.drawImage(0, 0, qimage) self._draw_rect_callback(painter) painter.end() diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index b38c6850dad0..514ee3d161f5 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -1,14 +1,6 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - from collections import OrderedDict -import six -from six import unichr -from six.moves import xrange - import base64 -import codecs import gzip import hashlib import io @@ -22,7 +14,6 @@ from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer -from matplotlib.cbook import is_writable_file_like, maxdict from matplotlib.colors import rgb2hex from matplotlib.font_manager import findfont, get_font from matplotlib.ft2font import LOAD_NO_HINTING @@ -145,15 +136,11 @@ def start(self, tag, attrib={}, **extra): self.__tags.append(tag) self.__write(self.__indentation[:len(self.__tags) - 1]) self.__write("<%s" % tag) - if attrib or extra: - attrib = attrib.copy() - attrib.update(extra) - attrib = sorted(six.iteritems(attrib)) - for k, v in attrib: - if not v == '': - k = escape_cdata(k) - v = escape_attrib(v) - self.__write(" %s=\"%s\"" % (k, v)) + for k, v in sorted({**attrib, **extra}.items()): + if not v == '': + k = escape_cdata(k) + v = escape_attrib(v) + self.__write(" %s=\"%s\"" % (k, v)) self.__open = 1 return len(self.__tags)-1 @@ -233,15 +220,12 @@ def generate_transform(transform_list=[]): if len(transform_list): output = io.StringIO() for type, value in transform_list: - if type == 'scale' and (value == (1.0,) or value == (1.0, 1.0)): - continue - if type == 'translate' and value == (0.0, 0.0): - continue - if type == 'rotate' and value == (0.0,): + if (type == 'scale' and (value == (1,) or value == (1, 1)) + or type == 'translate' and value == (0, 0) + or type == 'rotate' and value == (0,)): continue if type == 'matrix' and isinstance(value, Affine2DBase): value = value.to_values() - output.write('%s(%s)' % ( type, ' '.join(short_float_fmt(x) for x in value))) return output.getvalue() @@ -250,7 +234,7 @@ def generate_transform(transform_list=[]): def generate_css(attrib={}): if attrib: output = io.StringIO() - attrib = sorted(six.iteritems(attrib)) + attrib = sorted(attrib.items()) for k, v in attrib: k = escape_attrib(k) v = escape_attrib(v) @@ -260,9 +244,6 @@ def generate_css(attrib={}): _capstyle_d = {'projecting' : 'square', 'butt' : 'butt', 'round': 'round',} class RendererSVG(RendererBase): - FONT_SCALE = 100.0 - fontd = maxdict(50) - def __init__(self, width, height, svgwriter, basename=None, image_dpi=72): self.width = width self.height = height @@ -319,17 +300,12 @@ def _write_default_style(self): writer.end('defs') def _make_id(self, type, content): - content = str(content) - if rcParams['svg.hashsalt'] is None: + salt = rcParams['svg.hashsalt'] + if salt is None: salt = str(uuid.uuid4()) - else: - salt = rcParams['svg.hashsalt'] - if six.PY3: - content = content.encode('utf8') - salt = salt.encode('utf8') m = hashlib.md5() - m.update(salt) - m.update(content) + m.update(salt.encode('utf8')) + m.update(str(content).encode('utf8')) return '%s%s' % (type, m.hexdigest()[:10]) def _make_flip_transform(self, transform): @@ -370,13 +346,13 @@ def _write_hatches(self): HATCH_SIZE = 72 writer = self.writer writer.start('defs') - for ((path, face, stroke), oid) in six.itervalues(self._hatchd): + for (path, face, stroke), oid in self._hatchd.values(): writer.start( 'pattern', id=oid, patternUnits="userSpaceOnUse", - x="0", y="0", width=six.text_type(HATCH_SIZE), - height=six.text_type(HATCH_SIZE)) + x="0", y="0", width=str(HATCH_SIZE), + height=str(HATCH_SIZE)) path_data = self._convert_path( path, Affine2D().scale(HATCH_SIZE).scale(1.0, -1.0).translate(0, HATCH_SIZE), @@ -387,8 +363,8 @@ def _write_hatches(self): fill = rgb2hex(face) writer.element( 'rect', - x="0", y="0", width=six.text_type(HATCH_SIZE+1), - height=six.text_type(HATCH_SIZE+1), + x="0", y="0", width=str(HATCH_SIZE+1), + height=str(HATCH_SIZE+1), fill=fill) writer.element( 'path', @@ -396,7 +372,7 @@ def _write_hatches(self): style=generate_css({ 'fill': rgb2hex(stroke), 'stroke': rgb2hex(stroke), - 'stroke-width': six.text_type(rcParams['hatch.linewidth']), + 'stroke-width': str(rcParams['hatch.linewidth']), 'stroke-linecap': 'butt', 'stroke-linejoin': 'miter' }) @@ -481,7 +457,7 @@ def _write_clips(self): return writer = self.writer writer.start('defs') - for clip, oid in six.itervalues(self._clipd): + for clip, oid in self._clipd.values(): writer.start('clipPath', id=oid) if len(clip) == 2: clippath, clippath_trans = clip @@ -504,7 +480,7 @@ def _write_svgfonts(self): writer = self.writer writer.start('defs') - for font_fname, chars in six.iteritems(self._fonts): + for font_fname, chars in self._fonts.items(): font = get_font(font_fname) font.set_size(72, 72) sfnt = font.get_sfnt() @@ -528,7 +504,7 @@ def _write_svgfonts(self): d=path_data, attrib={ # 'glyph-name': name, - 'unicode': unichr(char), + 'unicode': chr(char), 'horiz-adv-x': short_float_fmt(glyph.linearHoriAdvance / 65536.0)}) writer.end('font') @@ -598,7 +574,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) style = self._get_style_dict(gc, rgbFace) dictkey = (path_data, generate_css(style)) oid = self._markers.get(dictkey) - style = generate_css({k: v for k, v in six.iteritems(style) + style = generate_css({k: v for k, v in style.items() if k.startswith('stroke')}) if oid is None: @@ -710,6 +686,16 @@ def draw_gouraud_triangle(self, gc, points, colors, trans): operator='arithmetic', k2="1", k3="1") writer.end('filter') + # feColorMatrix filter to correct opacity + writer.start( + 'filter', + id='colorMat') + writer.element( + 'feColorMatrix', + attrib={'type': 'matrix'}, + values='1 0 0 0 0 \n0 1 0 0 0 \n0 0 1 0 0' + + ' \n1 1 1 1 0 \n0 0 0 0 1 ') + writer.end('filter') avg_color = np.sum(colors[:, :], axis=0) / 3.0 # Just skip fully-transparent triangles @@ -743,41 +729,64 @@ def draw_gouraud_triangle(self, gc, points, colors, trans): writer.start( 'linearGradient', id="GR%x_%d" % (self._n_gradients, i), + gradientUnits="userSpaceOnUse", x1=short_float_fmt(x1), y1=short_float_fmt(y1), x2=short_float_fmt(xb), y2=short_float_fmt(yb)) writer.element( 'stop', - offset='0', - style=generate_css({'stop-color': rgb2hex(c), + offset='1', + style=generate_css({'stop-color': rgb2hex(avg_color), 'stop-opacity': short_float_fmt(c[-1])})) writer.element( 'stop', - offset='1', + offset='0', style=generate_css({'stop-color': rgb2hex(c), 'stop-opacity': "0"})) + writer.end('linearGradient') - writer.element( - 'polygon', - id='GT%x' % self._n_gradients, - points=" ".join([short_float_fmt(x) - for x in (x1, y1, x2, y2, x3, y3)])) writer.end('defs') - avg_color = np.sum(colors[:, :], axis=0) / 3.0 - href = '#GT%x' % self._n_gradients + # triangle formation using "path" + dpath = "M " + short_float_fmt(x1)+',' + short_float_fmt(y1) + dpath += " L " + short_float_fmt(x2) + ',' + short_float_fmt(y2) + dpath += " " + short_float_fmt(x3) + ',' + short_float_fmt(y3) + " Z" + writer.element( - 'use', - attrib={'xlink:href': href, + 'path', + attrib={'d': dpath, 'fill': rgb2hex(avg_color), - 'fill-opacity': short_float_fmt(avg_color[-1])}) - for i in range(3): - writer.element( - 'use', - attrib={'xlink:href': href, - 'fill': 'url(#GR%x_%d)' % (self._n_gradients, i), - 'fill-opacity': '1', - 'filter': 'url(#colorAdd)'}) + 'fill-opacity': '1', + 'shape-rendering': "crispEdges"}) + + writer.start( + 'g', + attrib={'stroke': "none", + 'stroke-width': "0", + 'shape-rendering': "crispEdges", + 'filter': "url(#colorMat)"}) + + writer.element( + 'path', + attrib={'d': dpath, + 'fill': 'url(#GR%x_0)' % self._n_gradients, + 'shape-rendering': "crispEdges"}) + + writer.element( + 'path', + attrib={'d': dpath, + 'fill': 'url(#GR%x_1)' % self._n_gradients, + 'filter': 'url(#colorAdd)', + 'shape-rendering': "crispEdges"}) + + writer.element( + 'path', + attrib={'d': dpath, + 'fill': 'url(#GR%x_2)' % self._n_gradients, + 'filter': 'url(#colorAdd)', + 'shape-rendering': "crispEdges"}) + + writer.end('g') self._n_gradients += 1 @@ -909,8 +918,10 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None): style = {} if color != '#000000': style['fill'] = color - if gc.get_alpha() != 1.0: - style['opacity'] = short_float_fmt(gc.get_alpha()) + + alpha = gc.get_alpha() if gc.get_forced_alpha() else gc.get_rgb()[3] + if alpha != 1: + style['opacity'] = short_float_fmt(alpha) if not ismath: font = text2path._get_font(prop) @@ -920,7 +931,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None): if glyph_map_new: writer.start('defs') - for char_id, glyph_path in six.iteritems(glyph_map_new): + for char_id, glyph_path in glyph_map_new.items(): path = Path(*glyph_path) path_data = self._convert_path(path, simplify=False) writer.element('path', id=char_id, d=path_data) @@ -963,7 +974,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None): # used. if glyph_map_new: writer.start('defs') - for char_id, glyph_path in six.iteritems(glyph_map_new): + for char_id, glyph_path in glyph_map_new.items(): char_id = self._adjust_char_id(char_id) # Some characters are blank if not len(glyph_path[0]): @@ -1010,8 +1021,10 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): style = {} if color != '#000000': style['fill'] = color - if gc.get_alpha() != 1.0: - style['opacity'] = short_float_fmt(gc.get_alpha()) + + alpha = gc.get_alpha() if gc.get_forced_alpha() else gc.get_rgb()[3] + if alpha != 1: + style['opacity'] = short_float_fmt(alpha) if not ismath: font = self._get_font(prop) @@ -1025,9 +1038,9 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): attrib = {} # Must add "px" to workaround a Firefox bug style['font-size'] = short_float_fmt(fontsize) + 'px' - style['font-family'] = six.text_type(fontfamily) + style['font-family'] = str(fontfamily) style['font-style'] = prop.get_style().lower() - style['font-weight'] = six.text_type(prop.get_weight()).lower() + style['font-weight'] = str(prop.get_weight()).lower() attrib['style'] = generate_css(style) if mtext and (angle == 0 or mtext.get_rotation_mode() == "anchor"): @@ -1085,15 +1098,13 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): ('translate', (x, y)), ('rotate', (-angle,))]) - # Apply attributes to 'g', not 'text', because we likely - # have some rectangles as well with the same style and - # transformation + # Apply attributes to 'g', not 'text', because we likely have some + # rectangles as well with the same style and transformation. writer.start('g', attrib=attrib) writer.start('text') - # Sort the characters by font, and output one tspan for - # each + # Sort the characters by font, and output one tspan for each. spans = OrderedDict() for font, fontsize, thetext, new_x, new_y, metrics in svg_glyphs: style = generate_css({ @@ -1110,20 +1121,20 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): fontset = self._fonts.setdefault(font.fname, set()) fontset.add(thetext) - for style, chars in six.iteritems(spans): + for style, chars in spans.items(): chars.sort() same_y = True if len(chars) > 1: last_y = chars[0][1] - for i in xrange(1, len(chars)): + for i in range(1, len(chars)): if chars[i][1] != last_y: same_y = False break if same_y: - ys = six.text_type(chars[0][1]) + ys = str(chars[0][1]) else: - ys = ' '.join(six.text_type(c[1]) for c in chars) + ys = ' '.join(str(c[1]) for c in chars) attrib = { 'style': style, @@ -1133,7 +1144,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): writer.element( 'tspan', - ''.join(unichr(c[2]) for c in chars), + ''.join(chr(c[2]) for c in chars), attrib=attrib) writer.end('text') @@ -1195,16 +1206,13 @@ def print_svg(self, filename, *args, **kwargs): with cbook.open_file_cm(filename, "w", encoding="utf-8") as fh: filename = getattr(fh, 'name', '') - if not isinstance(filename, six.string_types): + if not isinstance(filename, str): filename = '' if cbook.file_requires_unicode(fh): detach = False else: - if six.PY3: - fh = io.TextIOWrapper(fh, 'utf-8') - else: - fh = codecs.getwriter('utf-8')(fh) + fh = io.TextIOWrapper(fh, 'utf-8') detach = True result = self._print_svg(filename, fh, **kwargs) @@ -1212,11 +1220,7 @@ def print_svg(self, filename, *args, **kwargs): # Detach underlying stream from wrapper so that it remains open in # the caller. if detach: - if six.PY3: - fh.detach() - else: - fh.reset() - fh.stream = io.BytesIO() + fh.detach() return result @@ -1225,17 +1229,16 @@ def print_svgz(self, filename, *args, **kwargs): gzip.GzipFile(mode='w', fileobj=fh) as gzipwriter: return self.print_svg(gzipwriter) - def _print_svg(self, filename, fh, **kwargs): - image_dpi = kwargs.pop("dpi", 72) + def _print_svg( + self, filename, fh, *, dpi=72, bbox_inches_restore=None, **kwargs): self.figure.set_dpi(72.0) width, height = self.figure.get_size_inches() w, h = width * 72, height * 72 - _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) renderer = MixedModeRenderer( - self.figure, width, height, image_dpi, - RendererSVG(w, h, fh, filename, image_dpi), - bbox_inches_restore=_bbox_inches_restore) + self.figure, width, height, dpi, + RendererSVG(w, h, fh, filename, dpi), + bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) renderer.finalize() @@ -1243,19 +1246,18 @@ def _print_svg(self, filename, fh, **kwargs): def get_default_filetype(self): return 'svg' -class FigureManagerSVG(FigureManagerBase): - pass + +FigureManagerSVG = FigureManagerBase svgProlog = """\ - + """ @_Backend.export class _BackendSVG(_Backend): FigureCanvas = FigureCanvasSVG - FigureManager = FigureManagerSVG diff --git a/lib/matplotlib/backends/backend_template.py b/lib/matplotlib/backends/backend_template.py index 524ca73285c6..a4ae747816f0 100644 --- a/lib/matplotlib/backends/backend_template.py +++ b/lib/matplotlib/backends/backend_template.py @@ -21,9 +21,9 @@ import matplotlib matplotlib.use('xxx') - from pylab import * - plot([1,2,3]) - show() + import matplotlib.pyplot as plt + plt.plot([1,2,3]) + plt.show() matplotlib also supports external backends, so you can place you can use any module in your PYTHONPATH with the syntax:: @@ -62,11 +62,6 @@ """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) @@ -140,17 +135,17 @@ def points_to_pixels(self, points): class GraphicsContextTemplate(GraphicsContextBase): """ - The graphics context provides the color, line styles, etc... See the gtk + The graphics context provides the color, line styles, etc... See the cairo and postscript backends for examples of mapping the graphics context attributes (cap styles, join styles, line widths, colors) to a particular - backend. In GTK this is done by wrapping a gtk.gdk.GC object and + backend. In cairo this is done by wrapping a cairo.Context object and forwarding the appropriate calls to it using a dictionary mapping styles to gdk constants. In Postscript, all the work is done by the renderer, mapping line styles to postscript calls. If it's more appropriate to do the mapping at the renderer level (as in the postscript backend), you don't need to override any of the GC methods. - If it's more appropriate to wrap an instance (as in the GTK backend) and + If it's more appropriate to wrap an instance (as in the cairo backend) and do the mapping here, you'll need to override several of the setter methods. @@ -158,8 +153,6 @@ class GraphicsContextTemplate(GraphicsContextBase): interval, e.g., (0.5, 0.0, 1.0). You may need to map this to colors appropriate for your backend. """ - pass - ######################################################################## @@ -171,36 +164,33 @@ class GraphicsContextTemplate(GraphicsContextBase): def draw_if_interactive(): """ - For image backends - is not required + For image backends - is not required. For GUI backends - this should be overridden if drawing should be done in - interactive python mode + interactive python mode. """ def show(block=None): """ - For image backends - is not required - For GUI backends - show() is usually the last line of a pylab script and - tells the backend that it is time to draw. In interactive mode, this may - be a do nothing func. See the GTK backend for an example of how to handle - interactive versus batch mode + For image backends - is not required. + For GUI backends - show() is usually the last line of a pyplot script and + tells the backend that it is time to draw. In interactive mode, this + should do nothing. """ for manager in Gcf.get_all_fig_managers(): # do something to display the GUI pass -def new_figure_manager(num, *args, **kwargs): +def new_figure_manager(num, *args, FigureClass=Figure, **kwargs): """ Create a new figure manager instance """ - # May be implemented via the `_new_figure_manager_template` helper. # If a main-level app must be created, this (and # new_figure_manager_given_figure) is the usual place to do it -- see # backend_wx, backend_wxagg and backend_tkagg for examples. Not all GUIs - # require explicit instantiation of a main-level app (egg backend_gtk, - # backend_gtkagg) for pylab. - FigureClass = kwargs.pop('FigureClass', Figure) + # require explicit instantiation of a main-level app (e.g., backend_gtk3) + # for pylab. thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) @@ -209,7 +199,6 @@ def new_figure_manager_given_figure(num, figure): """ Create a new figure manager instance for the given figure. """ - # May be implemented via the `_new_figure_manager_template` helper. canvas = FigureCanvasTemplate(figure) manager = FigureManagerTemplate(canvas, num) return manager @@ -218,19 +207,18 @@ def new_figure_manager_given_figure(num, figure): class FigureCanvasTemplate(FigureCanvasBase): """ The canvas the figure renders into. Calls the draw and print fig - methods, creates the renderers, etc... + methods, creates the renderers, etc. - Note GUI templates will want to connect events for button presses, + Note: GUI templates will want to connect events for button presses, mouse movements and key presses to functions that call the base class methods button_press_event, button_release_event, - motion_notify_event, key_press_event, and key_release_event. See, - e.g., backend_gtk.py, backend_wx.py and backend_tkagg.py + motion_notify_event, key_press_event, and key_release_event. See the + implementations of the interactive backends for examples. Attributes ---------- figure : `matplotlib.figure.Figure` A high-level Figure instance - """ def draw(self): diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 9511326e4a5a..74d4e4178dd6 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -1,7 +1,4 @@ -from __future__ import absolute_import, division, print_function - -from .. import cbook -from . import tkagg # Paint image to Tk photo blitter extension. +from . import _backend_tk from .backend_agg import FigureCanvasAgg from ._backend_tk import ( _BackendTk, FigureCanvasTk, FigureManagerTk, NavigationToolbar2Tk) @@ -10,25 +7,15 @@ class FigureCanvasTkAgg(FigureCanvasAgg, FigureCanvasTk): def draw(self): super(FigureCanvasTkAgg, self).draw() - tkagg.blit(self._tkphoto, self.renderer._renderer, colormode=2) + _backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3)) self._master.update_idletasks() def blit(self, bbox=None): - tkagg.blit( - self._tkphoto, self.renderer._renderer, bbox=bbox, colormode=2) + _backend_tk.blit( + self._tkphoto, self.renderer._renderer, (0, 1, 2, 3), bbox=bbox) self._master.update_idletasks() -@cbook.deprecated("2.2") -class FigureManagerTkAgg(FigureManagerTk): - pass - - -@cbook.deprecated("2.2") -class NavigationToolbar2TkAgg(NavigationToolbar2Tk): - pass - - @_BackendTk.export class _BackendTkAgg(_BackendTk): FigureCanvas = FigureCanvasTkAgg diff --git a/lib/matplotlib/backends/backend_tkcairo.py b/lib/matplotlib/backends/backend_tkcairo.py index c4edfb97ed1a..56505193014d 100644 --- a/lib/matplotlib/backends/backend_tkcairo.py +++ b/lib/matplotlib/backends/backend_tkcairo.py @@ -1,10 +1,8 @@ -from __future__ import absolute_import, division, print_function - import sys import numpy as np -from . import tkagg # Paint image to Tk photo blitter extension. +from . import _backend_tk from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo from ._backend_tk import _BackendTk, FigureCanvasTk @@ -22,13 +20,9 @@ def draw(self): self._renderer.set_width_height(width, height) self.figure.draw(self._renderer) buf = np.reshape(surface.get_data(), (height, width, 4)) - # Convert from ARGB32 to RGBA8888. Using .take() instead of directly - # indexing ensures C-contiguity of the result, which is needed by - # tkagg. - buf = buf.take( - [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], - axis=2) - tkagg.blit(self._tkphoto, buf, colormode=2) + _backend_tk.blit( + self._tkphoto, buf, + (2, 1, 0, 3) if sys.byteorder == "little" else (1, 2, 3, 0)) self._master.update_idletasks() diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index c917a162ab19..18a995b97619 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -1,8 +1,6 @@ """ Displays Agg images in the browser, with interactivity """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) # The WebAgg backend is divided into two modules: # @@ -13,12 +11,12 @@ # - `backend_webagg.py` contains a concrete implementation of a basic # application, implemented with tornado. -import six - from contextlib import contextmanager import errno +from io import BytesIO import json import os +from pathlib import Path import random import sys import signal @@ -45,12 +43,14 @@ class ServerThread(threading.Thread): def run(self): tornado.ioloop.IOLoop.instance().start() + webagg_server_thread = ServerThread() class FigureCanvasWebAgg(core.FigureCanvasWebAggCore): def show(self): # show the figure window + global show # placates pyflakes: created by @_Backend.export below show() def new_timer(self, *args, **kwargs): @@ -63,20 +63,14 @@ class WebAggApplication(tornado.web.Application): class FavIcon(tornado.web.RequestHandler): def get(self): - image_path = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - 'mpl-data', 'images') - self.set_header('Content-Type', 'image/png') - with open(os.path.join(image_path, - 'matplotlib.png'), 'rb') as fd: - self.write(fd.read()) + image_path = Path(rcParams["datapath"], "images", "matplotlib.png") + self.write(image_path.read_bytes()) class SingleFigurePage(tornado.web.RequestHandler): - def __init__(self, application, request, **kwargs): - self.url_prefix = kwargs.pop('url_prefix', '') - tornado.web.RequestHandler.__init__(self, application, - request, **kwargs) + def __init__(self, application, request, *, url_prefix='', **kwargs): + self.url_prefix = url_prefix + super().__init__(application, request, **kwargs) def get(self, fignum): fignum = int(fignum) @@ -93,10 +87,9 @@ def get(self, fignum): canvas=manager.canvas) class AllFiguresPage(tornado.web.RequestHandler): - def __init__(self, application, request, **kwargs): - self.url_prefix = kwargs.pop('url_prefix', '') - tornado.web.RequestHandler.__init__(self, application, - request, **kwargs) + def __init__(self, application, request, *, url_prefix='', **kwargs): + self.url_prefix = url_prefix + super().__init__(application, request, **kwargs) def get(self): ws_uri = 'ws://{req.host}{prefix}/'.format(req=self.request, @@ -135,7 +128,7 @@ def get(self, fignum, fmt): self.set_header('Content-Type', mimetypes.get(fmt, 'binary')) - buff = six.BytesIO() + buff = BytesIO() manager.canvas.figure.savefig(buff, format=fmt) self.write(buff.getvalue()) @@ -183,7 +176,7 @@ def __init__(self, url_prefix=''): assert url_prefix[0] == '/' and url_prefix[-1] != '/', \ 'url_prefix must start with a "/" and not end with one.' - super(WebAggApplication, self).__init__( + super().__init__( [ # Static files for the CSS and JS (url_prefix + r'/_static/(.*)', @@ -237,8 +230,6 @@ def random_ports(port, n): for i in range(n - 5): yield port + random.randint(-2 * n, 2 * n) - success = None - if address is None: cls.address = rcParams['webagg.address'] else: @@ -252,10 +243,8 @@ def random_ports(port, n): raise else: cls.port = port - success = True break - - if not success: + else: raise SystemExit( "The webagg server could not be started because an available " "port could not be found") @@ -308,13 +297,9 @@ def ipython_inline_display(figure): if not webagg_server_thread.is_alive(): webagg_server_thread.start() - with open(os.path.join( - core.FigureManagerWebAgg.get_static_file_path(), - 'ipython_inline_figure.html')) as fd: - tpl = fd.read() - fignum = figure.number - + tpl = Path(core.FigureManagerWebAgg.get_static_file_path(), + "ipython_inline_figure.html").read_text() t = tornado.template.Template(tpl) return t.generate( prefix=WebAggApplication.url_prefix, diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index e75014b1e632..b30f7bd416d6 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -10,15 +10,11 @@ # - `backend_webagg.py` contains a concrete implementation of a basic # application, implemented with tornado. -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import datetime -import io +from io import StringIO import json import os +from pathlib import Path import warnings import numpy as np @@ -26,8 +22,7 @@ from matplotlib.backends import backend_agg from matplotlib.backend_bases import _Backend -from matplotlib import backend_bases -from matplotlib import _png +from matplotlib import backend_bases, _png # http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes @@ -94,21 +89,21 @@ def _handle_key(key): code = int(key[key.index('k') + 1:]) value = chr(code) # letter keys - if code >= 65 and code <= 90: + if 65 <= code <= 90: if 'shift+' in key: key = key.replace('shift+', '') else: value = value.lower() # number keys - elif code >= 48 and code <= 57: + elif 48 <= code <= 57: if 'shift+' in key: value = ')!@#$%^&*('[int(value)] key = key.replace('shift+', '') # function keys - elif code >= 112 and code <= 123: + elif 112 <= code <= 123: value = 'f%s' % (code - 111) # number pad keys - elif code >= 96 and code <= 105: + elif 96 <= code <= 105: value = '%s' % (code - 96) # keys with shift alternatives elif code in _SHIFT_LUT and 'shift+' in key: @@ -150,17 +145,11 @@ def show(self): show() def draw(self): - renderer = self.get_renderer(cleared=True) - self._png_is_old = True - - backend_agg.RendererAgg.lock.acquire() try: - self.figure.draw(renderer) + super().draw() finally: - backend_agg.RendererAgg.lock.release() - # Swap the frames - self.manager.refresh_all() + self.manager.refresh_all() # Swap the frames. def draw_idle(self): self.send_event("draw") @@ -253,7 +242,7 @@ def handle_event(self, event): def handle_unknown_event(self, event): warnings.warn('Unhandled message type {0}. {1}'.format( - event['type'], event)) + event['type'], event), stacklevel=2) def handle_ack(self, event): # Network latency tends to decrease if traffic is flowing @@ -460,15 +449,12 @@ def refresh_all(self): @classmethod def get_javascript(cls, stream=None): if stream is None: - output = io.StringIO() + output = StringIO() else: output = stream - with io.open(os.path.join( - os.path.dirname(__file__), - "web_backend", "js", - "mpl.js"), encoding='utf8') as fd: - output.write(fd.read()) + output.write((Path(__file__).parent / "web_backend/js/mpl.js") + .read_text(encoding="utf-8")) toolitems = [] for name, tooltip, image, method in cls.ToolbarCls.toolitems: @@ -499,8 +485,7 @@ def get_static_file_path(cls): return os.path.join(os.path.dirname(__file__), 'web_backend') def _send_event(self, event_type, **kwargs): - payload = {'type': event_type} - payload.update(kwargs) + payload = {'type': event_type, **kwargs} for s in self.web_sockets: s.send_json(payload) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 2ecc1a4b3b45..6f836db94a1f 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1,36 +1,23 @@ """ - A wxPython backend for matplotlib, based (very heavily) on - backend_template.py and backend_gtk.py +A wxPython backend for matplotlib. - Author: Jeremy O'Donoghue (jeremy@o-donoghue.com) - - Derived from original copyright work by John Hunter - (jdhunter@ace.bsd.uchicago.edu) - - Copyright (C) Jeremy O'Donoghue & John Hunter, 2003-4 - - License: This work is licensed under a PSF compatible license. A copy - should be included with this source code. +Originally contributed by Jeremy O'Donoghue (jeremy@o-donoghue.com) and John +Hunter (jdhunter@ace.bsd.uchicago.edu). +Copyright (C) Jeremy O'Donoghue & John Hunter, 2003-4. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange -import six -import sys -import os import os.path import math -import weakref +import sys import warnings +import weakref import matplotlib from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - NavigationToolbar2, RendererBase, TimerBase, cursors) + NavigationToolbar2, RendererBase, TimerBase, cursors, ToolContainerBase, + StatusbarBase) from matplotlib.backend_bases import _has_pil from matplotlib._pylab_helpers import Gcf @@ -39,9 +26,9 @@ from matplotlib.path import Path from matplotlib.transforms import Affine2D from matplotlib.widgets import SubplotTool +from matplotlib.backend_managers import ToolManager from matplotlib import cbook, rcParams, backend_tools -from . import wx_compat as wxc import wx # Debugging settings here... @@ -110,7 +97,7 @@ def error_msg_wx(msg, parent=None): def raise_msg_to_str(msg): """msg is a return arg from a raise. Join with new lines.""" - if not isinstance(msg, six.string_types): + if not isinstance(msg, str): msg = '\n'.join(map(str, msg)) return msg @@ -133,19 +120,15 @@ class TimerWx(TimerBase): ''' - def __init__(self, parent, *args, **kwargs): + def __init__(self, *args, **kwargs): + if args and isinstance(args[0], wx.EvtHandler): + cbook.warn_deprecated( + "3.0", "Passing a wx.EvtHandler as first argument to the " + "TimerWx constructor is deprecated since %(version)s.") + args = args[1:] TimerBase.__init__(self, *args, **kwargs) - - # Create a new timer and connect the timer event to our handler. - # For WX, the events have to use a widget for binding. - self.parent = parent - self._timer = wx.Timer(self.parent, wx.NewId()) - self.parent.Bind(wx.EVT_TIMER, self._on_timer, self._timer) - - # Unbinding causes Wx to stop for some reason. Disabling for now. -# def __del__(self): -# TimerBase.__del__(self) -# self.parent.Bind(wx.EVT_TIMER, None, self._timer) + self._timer = wx.Timer() + self._timer.Notify = self._on_timer def _timer_start(self): self._timer.Start(self._interval, self._single) @@ -159,9 +142,6 @@ def _timer_set_interval(self): def _timer_set_single_shot(self): self._timer.Start() - def _on_timer(self, *args): - TimerBase._on_timer(self) - class RendererWx(RendererBase): """ @@ -175,14 +155,45 @@ class RendererWx(RendererBase): # describes the colour and weight of any lines drawn, and a wxBrush # which describes the fill colour of any closed polygon. - fontweights = wxc.fontweights - fontangles = wxc.fontangles + # Font styles, families and weight. + fontweights = { + 100: wx.FONTWEIGHT_LIGHT, + 200: wx.FONTWEIGHT_LIGHT, + 300: wx.FONTWEIGHT_LIGHT, + 400: wx.FONTWEIGHT_NORMAL, + 500: wx.FONTWEIGHT_NORMAL, + 600: wx.FONTWEIGHT_NORMAL, + 700: wx.FONTWEIGHT_BOLD, + 800: wx.FONTWEIGHT_BOLD, + 900: wx.FONTWEIGHT_BOLD, + 'ultralight': wx.FONTWEIGHT_LIGHT, + 'light': wx.FONTWEIGHT_LIGHT, + 'normal': wx.FONTWEIGHT_NORMAL, + 'medium': wx.FONTWEIGHT_NORMAL, + 'semibold': wx.FONTWEIGHT_NORMAL, + 'bold': wx.FONTWEIGHT_BOLD, + 'heavy': wx.FONTWEIGHT_BOLD, + 'ultrabold': wx.FONTWEIGHT_BOLD, + 'black': wx.FONTWEIGHT_BOLD, + } + fontangles = { + 'italic': wx.FONTSTYLE_ITALIC, + 'normal': wx.FONTSTYLE_NORMAL, + 'oblique': wx.FONTSTYLE_SLANT, + } - # wxPython allows for portable font styles, choosing them appropriately - # for the target platform. Map some standard font names to the portable - # styles + # wxPython allows for portable font styles, choosing them appropriately for + # the target platform. Map some standard font names to the portable styles. # QUESTION: Is it be wise to agree standard fontnames across all backends? - fontnames = wxc.fontnames + fontnames = { + 'Sans': wx.FONTFAMILY_SWISS, + 'Roman': wx.FONTFAMILY_ROMAN, + 'Script': wx.FONTFAMILY_SCRIPT, + 'Decorative': wx.FONTFAMILY_DECORATIVE, + 'Modern': wx.FONTFAMILY_MODERN, + 'Courier': wx.FONTFAMILY_MODERN, + 'courier': wx.FONTFAMILY_MODERN, + } def __init__(self, bitmap, dpi): """ @@ -287,7 +298,7 @@ def draw_image(self, gc, x, y, im): w = self.width h = self.height rows, cols = im.shape[:2] - bitmap = wxc.BitmapFromBuffer(cols, rows, im.tostring()) + bitmap = wx.Bitmap.FromBufferRGBA(cols, rows, im.tostring()) gc = self.get_gc() gc.select() gc.gfx_ctx.DrawBitmap(bitmap, int(l), int(self.height - b), @@ -471,7 +482,7 @@ def set_linewidth(self, w): w = float(w) DEBUG_MSG("set_linewidth()", 1, self) self.select() - if w > 0 and w < 1: + if 0 < w < 1: w = 1 GraphicsContextBase.set_linewidth(self, w) lw = int(self.renderer.points_to_pixels(self._linewidth)) @@ -503,27 +514,6 @@ def set_joinstyle(self, js): self.gfx_ctx.SetPen(self._pen) self.unselect() - @cbook.deprecated("2.1") - def set_linestyle(self, ls): - """ - Set the line style to be one of - """ - DEBUG_MSG("set_linestyle()", 1, self) - self.select() - GraphicsContextBase.set_linestyle(self, ls) - try: - self._style = wxc.dashd_wx[ls] - except KeyError: - self._style = wx.LONG_DASH # Style not used elsewhere... - - # On MS Windows platform, only line width of 1 allowed for dash lines - if wx.Platform == '__WXMSW__': - self.set_linewidth(1) - - self._pen.SetStyle(self._style) - self.gfx_ctx.SetPen(self._pen) - self.unselect() - def get_wxcolour(self, color): """return a wx.Colour from RGB format""" DEBUG_MSG("get_wx_color()", 1, self) @@ -629,32 +619,13 @@ def __init__(self, parent, id, figure): # Set preferred window size hint - helps the sizer (if one is # connected) l, b, w, h = figure.bbox.bounds - w = int(math.ceil(w)) - h = int(math.ceil(h)) + w = math.ceil(w) + h = math.ceil(h) wx.Panel.__init__(self, parent, id, size=wx.Size(w, h)) - def do_nothing(*args, **kwargs): - warnings.warn( - "could not find a setinitialsize function for backend_wx; " - "please report your wxpython version=%s " - "to the matplotlib developers list" % - wxc.backend_version) - pass - - # try to find the set size func across wx versions - try: - getattr(self, 'SetInitialSize') - except AttributeError: - self.SetInitialSize = getattr(self, 'SetBestFittingSize', - do_nothing) - - if not hasattr(self, 'IsShownOnScreen'): - self.IsShownOnScreen = getattr(self, 'IsVisible', - lambda *args: True) - # Create the drawing bitmap - self.bitmap = wxc.EmptyBitmap(w, h) + self.bitmap = wx.Bitmap(w, h) DEBUG_MSG("__init__() - bitmap w:%d h:%d" % (w, h), 2, self) # TODO: Add support for 'point' inspection and plot navigation. self._isDrawn = False @@ -684,10 +655,10 @@ def do_nothing(*args, **kwargs): self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # Reduce flicker. self.SetBackgroundColour(wx.WHITE) - self.macros = {} # dict from wx id to seq of macros - - def Destroy(self, *args, **kwargs): - wx.Panel.Destroy(self, *args, **kwargs) + @property + @cbook.deprecated("3.0") + def macros(self): + return {} def Copy_to_Clipboard(self, event=None): "copy bitmap of canvas to system clipboard" @@ -728,7 +699,7 @@ def new_timer(self, *args, **kwargs): will be executed by the timer every *interval*. """ - return TimerWx(self, *args, **kwargs) + return TimerWx(*args, **kwargs) def flush_events(self): wx.Yield() @@ -756,7 +727,7 @@ def start_event_loop(self, timeout=0): self.Bind(wx.EVT_TIMER, self.stop_event_loop, id=id) # Event loop handler for start/stop event loop - self._event_loop = wxc.EventLoop() + self._event_loop = wx.GUIEventLoop() self._event_loop.Run() timer.Stop() @@ -813,19 +784,20 @@ def gui_repaint(self, drawDC=None, origin='WX'): else: drawDC.DrawBitmap(self.bitmap, 0, 0) - filetypes = FigureCanvasBase.filetypes.copy() - filetypes['bmp'] = 'Windows bitmap' - filetypes['jpeg'] = 'JPEG' - filetypes['jpg'] = 'JPEG' - filetypes['pcx'] = 'PCX' - filetypes['png'] = 'Portable Network Graphics' - filetypes['tif'] = 'Tagged Image Format File' - filetypes['tiff'] = 'Tagged Image Format File' - filetypes['xpm'] = 'X pixmap' + filetypes = { + **FigureCanvasBase.filetypes, + 'bmp': 'Windows bitmap', + 'jpeg': 'JPEG', + 'jpg': 'JPEG', + 'pcx': 'PCX', + 'png': 'Portable Network Graphics', + 'tif': 'Tagged Image Format File', + 'tiff': 'Tagged Image Format File', + 'xpm': 'X pixmap', + } def print_figure(self, filename, *args, **kwargs): - super(_FigureCanvasWxBase, self).print_figure( - filename, *args, **kwargs) + super().print_figure(filename, *args, **kwargs) # Restore the current view; this is needed because the artist contains # methods rely on particular attributes of the rendered figure for # determining things like bounding boxes. @@ -869,7 +841,7 @@ def _onSize(self, evt): return self._width, self._height = size # Create a new, correctly sized bitmap - self.bitmap = wxc.EmptyBitmap(self._width, self._height) + self.bitmap = wx.Bitmap(self._width, self._height) self._isDrawn = False @@ -1056,7 +1028,10 @@ def _onLeave(self, evt): def _onEnter(self, evt): """Mouse has entered the window.""" - FigureCanvasBase.enter_notify_event(self, guiEvent=evt) + x = evt.GetX() + y = self.figure.bbox.height - evt.GetY() + evt.Skip() + FigureCanvasBase.enter_notify_event(self, guiEvent=evt, xy=(x, y)) class FigureCanvasWx(_FigureCanvasWxBase): @@ -1101,10 +1076,10 @@ def _print_image(self, filename, filetype, *args, **kwargs): origBitmap = self.bitmap l, b, width, height = self.figure.bbox.bounds - width = int(math.ceil(width)) - height = int(math.ceil(height)) + width = math.ceil(width) + height = math.ceil(height) - self.bitmap = wxc.EmptyBitmap(width, height) + self.bitmap = wx.Bitmap(width, height) renderer = RendererWx(self.bitmap, self.figure.dpi) @@ -1126,7 +1101,7 @@ 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, six.string_types): + if isinstance(filename, str): if not image.SaveFile(filename, filetype): DEBUG_MSG('print_figure() file save error', 4, self) raise RuntimeError( @@ -1176,9 +1151,8 @@ def __init__(self, num, fig): # Frame will be sized later by the Fit method DEBUG_MSG("__init__()", 1, self) self.num = num + _set_frame_icon(self) - statbar = StatusBarWx(self) - self.SetStatusBar(statbar) self.canvas = self.get_canvas(fig) self.canvas.SetInitialSize(wx.Size(fig.bbox.width, fig.bbox.height)) self.canvas.SetFocus() @@ -1187,18 +1161,25 @@ def __init__(self, num, fig): # By adding toolbar in sizer, we are able to put it at the bottom # of the frame - so appearance is closer to GTK version - self.toolbar = self._get_toolbar(statbar) + self.toolmanager = self._get_toolmanager() + if self.toolmanager: + self.statusbar = StatusbarWx(self, self.toolmanager) + else: + self.statusbar = StatusBarWx(self) + self.SetStatusBar(self.statusbar) + self.toolbar = self._get_toolbar(self.statusbar) + + if self.toolmanager: + backend_tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + backend_tools.add_tools_to_container(self.toolbar) if self.toolbar is not None: self.toolbar.Realize() # On Windows platform, default window size is incorrect, so set # toolbar width to figure width. - if wxc.is_phoenix: - tw, th = self.toolbar.GetSize() - fw, fh = self.canvas.GetSize() - else: - tw, th = self.toolbar.GetSizeTuple() - fw, fh = self.canvas.GetSizeTuple() + tw, th = self.toolbar.GetSize() + fw, fh = self.canvas.GetSize() # By adding toolbar in sizer, we are able to put it at the bottom # of the frame - so appearance is closer to GTK version. self.toolbar.SetSize(wx.Size(fw, th)) @@ -1225,10 +1206,19 @@ def _get_toolbar(self, statbar): if rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2Wx(self.canvas) toolbar.set_status_bar(statbar) + elif matplotlib.rcParams['toolbar'] == 'toolmanager': + toolbar = ToolbarWx(self.toolmanager, self) else: toolbar = None return toolbar + def _get_toolmanager(self): + if matplotlib.rcParams['toolbar'] == 'toolmanager': + toolmanager = ToolManager(self.canvas.figure) + else: + toolmanager = None + return toolmanager + def get_canvas(self, fig): return FigureCanvasWx(self, -1, fig) @@ -1285,14 +1275,8 @@ def __init__(self, canvas, num, frame): self.frame = frame self.window = frame - self.tb = frame.GetToolBar() - self.toolbar = self.tb # consistent with other backends - - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.tb is not None: - self.tb.update() - self.canvas.figure.add_axobserver(notify_axes_change) + self.toolmanager = getattr(frame, "toolmanager", None) + self.toolbar = frame.GetToolBar() def show(self): self.frame.Show() @@ -1356,6 +1340,20 @@ def _load_bitmap(filename): return bmp +def _set_frame_icon(frame): + # set frame icon + bundle = wx.IconBundle() + for image in ('matplotlib.png', 'matplotlib_large.png'): + image = os.path.join(matplotlib.rcParams['datapath'], 'images', image) + if not os.path.exists(image): + continue + icon = wx.Icon(_load_bitmap(image)) + if not icon.IsOk(): + return + bundle.AddIcon(icon) + frame.SetIcons(bundle) + + class MenuButtonWx(wx.Button): """ wxPython does not permit a menu to be incorporated directly into a toolbar. @@ -1389,12 +1387,8 @@ def Destroy(self): def _onMenuButton(self, evt): """Handle menu button pressed.""" - if wxc.is_phoenix: - x, y = self.GetPosition() - w, h = self.GetSize() - else: - x, y = self.GetPositionTuple() - w, h = self.GetSizeTuple() + x, y = self.GetPosition() + w, h = self.GetSize() self.PopupMenuXY(self._menu, x, y + h - 4) # When menu returned, indicate selection in button evt.Skip() @@ -1449,7 +1443,7 @@ def updateAxes(self, maxAxis): for menuId in self._axisId[maxAxis:]: self._menu.Delete(menuId) self._axisId = self._axisId[:maxAxis] - self._toolbar.set_active(list(xrange(maxAxis))) + self._toolbar.set_active(list(range(maxAxis))) def getActiveAxes(self): """Return a list of the selected axes.""" @@ -1477,6 +1471,7 @@ def updateButtonText(self, lst): @cbook.deprecated("2.2") class SubplotToolWX(wx.Frame): def __init__(self, targetfig): + global FigureManager # placates pyflakes: created by @_Backend.export wx.Frame.__init__(self, None, -1, "Configure subplots") toolfig = Figure((6, 3)) @@ -1521,11 +1516,15 @@ def _init_toolbar(self): if text is None: self.AddSeparator() continue - self.wx_ids[text] = wx.NewId() - wxc._AddTool(self, self.wx_ids, text, - _load_bitmap(image_file + '.png'), - tooltip_text) - + self.wx_ids[text] = ( + self.AddTool( + -1, + bitmap=_load_bitmap(image_file + ".png"), + bmpDisabled=wx.NullBitmap, + label=text, shortHelp=text, longHelp=tooltip_text, + kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"] + else wx.ITEM_NORMAL)) + .Id) self.Bind(wx.EVT_TOOL, getattr(self, callback), id=self.wx_ids[text]) @@ -1540,7 +1539,9 @@ def pan(self, *args): NavigationToolbar2.pan(self, *args) def configure_subplots(self, evt): + global FigureManager # placates pyflakes: created by @_Backend.export frame = wx.Frame(None, -1, "Configure subplots") + _set_frame_icon(frame) toolfig = Figure((6, 3)) canvas = self.get_canvas(frame, toolfig) @@ -1581,7 +1582,7 @@ def save_figure(self, *args): warnings.warn( 'extension %s did not match the selected ' 'image type %s; going with %s' % - (ext, format, ext), stacklevel=0) + (ext, format, ext), stacklevel=2) format = ext try: self.canvas.figure.savefig( @@ -1590,18 +1591,10 @@ def save_figure(self, *args): error_msg_wx(str(e)) def set_cursor(self, cursor): - cursor = wxc.Cursor(cursord[cursor]) + cursor = wx.Cursor(cursord[cursor]) self.canvas.SetCursor(cursor) self.canvas.Update() - @cbook.deprecated("2.1", alternative="canvas.draw_idle") - def dynamic_update(self): - d = self._idle - self._idle = False - if d: - self.canvas.draw() - self._idle = True - def press(self, event): if self._active == 'ZOOM': if not self.retinaFix: @@ -1674,17 +1667,14 @@ def draw_rubberband(self, event, x0, y0, x1, y1): rubberBandColor = '#C0C0FF' # or load from config? # Set a pen for the border - color = wxc.NamedColour(rubberBandColor) + color = wx.Colour(rubberBandColor) dc.SetPen(wx.Pen(color, 1)) # use the same color, plus alpha for the brush r, g, b, a = color.Get(True) color.Set(r, g, b, 0x60) dc.SetBrush(wx.Brush(color)) - if wxc.is_phoenix: - dc.DrawRectangle(rect) - else: - dc.DrawRectangleRect(rect) + dc.DrawRectangle(rect) def set_status_bar(self, statbar): self.statbar = statbar @@ -1712,7 +1702,7 @@ class StatusBarWx(wx.StatusBar): convenience. """ - def __init__(self, parent): + def __init__(self, parent, *args, **kwargs): wx.StatusBar.__init__(self, parent, -1) self.SetFieldsCount(2) self.SetStatusText("None", 1) @@ -1727,9 +1717,107 @@ def set_function(self, string): # tools for matplotlib.backend_managers.ToolManager: -# for now only SaveFigure, SetCursor and Rubberband are implemented -# once a ToolbarWx is implemented, also FigureManagerWx needs to be -# modified, similar to pull request #9934 + +class ToolbarWx(ToolContainerBase, wx.ToolBar): + def __init__(self, toolmanager, parent, style=wx.TB_HORIZONTAL): + ToolContainerBase.__init__(self, toolmanager) + wx.ToolBar.__init__(self, parent, -1, style=style) + self._toolitems = {} + self._groups = {} + self._last = None + + def add_toolitem( + self, name, group, position, image_file, description, toggle): + + before, group = self._add_to_group(group, name, position) + idx = self.GetToolPos(before.Id) + if image_file: + bmp = _load_bitmap(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 "") + else: + size = (self.GetTextExtent(name)[0]+10, -1) + if toggle: + control = wx.ToggleButton(self, -1, name, size=size) + else: + control = wx.Button(self, -1, name, size=size) + tool = self.InsertControl(idx, control, label=name) + self.Realize() + + def handler(event): + self.trigger_tool(name) + + if image_file: + self.Bind(wx.EVT_TOOL, handler, tool) + else: + control.Bind(wx.EVT_LEFT_DOWN, handler) + + self._last = tool + self._toolitems.setdefault(name, []) + group.insert(position, tool) + self._toolitems[name].append((tool, handler)) + + def _add_to_group(self, group, name, position): + gr = self._groups.get(group, []) + if not gr: + sep = self.AddSeparator() + gr.append(sep) + before = gr[position] + self._groups[group] = gr + return before, gr + + def toggle_toolitem(self, name, toggled): + if name not in self._toolitems: + return + for tool, handler in self._toolitems[name]: + if not tool.IsControl(): + self.ToggleTool(tool.Id, toggled) + else: + tool.GetControl().SetValue(toggled) + self.Refresh() + + def remove_toolitem(self, name): + for tool, handler in self._toolitems[name]: + self.DeleteTool(tool.Id) + del self._toolitems[name] + + +class StatusbarWx(StatusbarBase, wx.StatusBar): + """for use with ToolManager""" + def __init__(self, parent, *args, **kwargs): + StatusbarBase.__init__(self, *args, **kwargs) + wx.StatusBar.__init__(self, parent, -1) + self.SetFieldsCount(1) + self.SetStatusText("") + + def set_message(self, s): + self.SetStatusText(s) + + +class ConfigureSubplotsWx(backend_tools.ConfigureSubplotsBase): + def trigger(self, *args): + self.configure_subplots() + + def configure_subplots(self): + frame = wx.Frame(None, -1, "Configure subplots") + _set_frame_icon(frame) + + toolfig = Figure((6, 3)) + canvas = self.get_canvas(frame, toolfig) + + # Now put all into a sizer + sizer = wx.BoxSizer(wx.VERTICAL) + # This way of adding to sizer allows resizing + sizer.Add(canvas, 1, wx.LEFT | wx.TOP | wx.GROW) + frame.SetSizer(sizer) + frame.Fit() + tool = SubplotTool(self.canvas.figure, toolfig) + frame.Show() + + def get_canvas(self, frame, fig): + return type(self.canvas)(frame, -1, fig) + class SaveFigureWx(backend_tools.SaveFigureBase): def trigger(self, *args): @@ -1758,7 +1846,7 @@ def trigger(self, *args): warnings.warn( 'extension %s did not match the selected ' 'image type %s; going with %s' % - (ext, format, ext), stacklevel=0) + (ext, format, ext), stacklevel=2) format = ext if default_dir != "": matplotlib.rcParams['savefig.directory'] = dirname @@ -1771,7 +1859,7 @@ def trigger(self, *args): class SetCursorWx(backend_tools.SetCursorBase): def set_cursor(self, cursor): - cursor = wxc.Cursor(cursord[cursor]) + cursor = wx.Cursor(cursord[cursor]) self.canvas.SetCursor(cursor) self.canvas.Update() @@ -1809,17 +1897,14 @@ def draw_rubberband(self, x0, y0, x1, y1): rubberBandColor = '#C0C0FF' # or load from config? # Set a pen for the border - color = wxc.NamedColour(rubberBandColor) + color = wx.Colour(rubberBandColor) dc.SetPen(wx.Pen(color, 1)) # use the same color, plus alpha for the brush r, g, b, a = color.Get(True) color.Set(r, g, b, 0x60) dc.SetBrush(wx.Brush(color)) - if wxc.is_phoenix: - dc.DrawRectangle(rect) - else: - dc.DrawRectangleRect(rect) + dc.DrawRectangle(rect) def remove_rubberband(self): if self.wxoverlay is None: @@ -1850,10 +1935,7 @@ def draw_rubberband(self, x0, y0, x1, y1): dc.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID)) dc.SetBrush(wx.TRANSPARENT_BRUSH) self._rect = (x0, self.canvas._height-y0, x1-x0, -y1+y0) - if wxc.is_phoenix: - dc.DrawRectangle(self._rect) - else: - dc.DrawRectangleRect(self._rect) + dc.DrawRectangle(self._rect) def remove_rubberband(self, dc=None): if not self._rect: @@ -1869,9 +1951,75 @@ def remove_rubberband(self, dc=None): self._rect = None +class _HelpDialog(wx.Dialog): + _instance = None # a reference to an open dialog singleton + headers = [("Action", "Shortcuts", "Description")] + widths = [100, 140, 300] + + def __init__(self, parent, help_entries): + wx.Dialog.__init__(self, parent, title="Help", + style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + + sizer = wx.BoxSizer(wx.VERTICAL) + grid_sizer = wx.FlexGridSizer(0, 3, 8, 6) + # create and add the entries + bold = self.GetFont().MakeBold() + for r, row in enumerate(self.headers + help_entries): + for (col, width) in zip(row, self.widths): + label = wx.StaticText(self, label=col) + if r == 0: + label.SetFont(bold) + label.Wrap(width) + grid_sizer.Add(label, 0, 0, 0) + # finalize layout, create button + sizer.Add(grid_sizer, 0, wx.ALL, 6) + OK = wx.Button(self, wx.ID_OK) + sizer.Add(OK, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 8) + self.SetSizer(sizer) + sizer.Fit(self) + self.Layout() + self.Bind(wx.EVT_CLOSE, self.OnClose) + OK.Bind(wx.EVT_BUTTON, self.OnClose) + + def OnClose(self, evt): + _HelpDialog._instance = None # remove global reference + self.DestroyLater() + evt.Skip() + + @classmethod + def show(cls, parent, help_entries): + # if no dialog is shown, create one; otherwise just re-raise it + if cls._instance: + cls._instance.Raise() + return + cls._instance = cls(parent, help_entries) + cls._instance.Show() + + +class HelpWx(backend_tools.ToolHelpBase): + def trigger(self, *args): + _HelpDialog.show(self.figure.canvas.GetTopLevelParent(), + self._get_help_entries()) + + +class ToolCopyToClipboardWx(backend_tools.ToolCopyToClipboardBase): + def trigger(self, *args, **kwargs): + if not self.canvas._isDrawn: + self.canvas.draw() + if not self.canvas.bitmap.IsOk() or not wx.TheClipboard.Open(): + return + try: + wx.TheClipboard.SetData(wx.BitmapDataObject(self.canvas.bitmap)) + finally: + wx.TheClipboard.Close() + + backend_tools.ToolSaveFigure = SaveFigureWx +backend_tools.ToolConfigureSubplots = ConfigureSubplotsWx backend_tools.ToolSetCursor = SetCursorWx backend_tools.ToolRubberband = RubberbandWx +backend_tools.ToolHelp = HelpWx +backend_tools.ToolCopyToClipboard = ToolCopyToClipboardWx # < Additions for printing support: Matt Newville @@ -1901,13 +2049,10 @@ def OnPrintPage(self, page): self.canvas.draw() dc = self.GetDC() - (ppw, pph) = self.GetPPIPrinter() # printer's pixels per in - (pgw, pgh) = self.GetPageSizePixels() # page size in pixels - (dcw, dch) = dc.GetSize() - if wxc.is_phoenix: - (grw, grh) = self.canvas.GetSize() - else: - (grw, grh) = self.canvas.GetSizeTuple() + ppw, pph = self.GetPPIPrinter() # printer's pixels per in + pgw, pgh = self.GetPageSizePixels() # page size in pixels + dcw, dch = dc.GetSize() + grw, grh = self.canvas.GetSize() # save current figure dpi resolution and bg color, # so that we can temporarily set them to the dpi of @@ -1965,6 +2110,7 @@ def OnPrintPage(self, page): @_Backend.export class _BackendWx(_Backend): + required_interactive_framework = "wx" FigureCanvas = FigureCanvasWx FigureManager = FigureManagerWx _frame_class = FigureFrameWx @@ -1983,7 +2129,7 @@ def new_figure_manager(cls, num, *args, **kwargs): # Retain a reference to the app object so that it does not get # garbage collected. _BackendWx._theWxApp = wxapp - return super(_BackendWx, cls).new_figure_manager(num, *args, **kwargs) + return super().new_figure_manager(num, *args, **kwargs) @classmethod def new_figure_manager_given_figure(cls, num, figure): diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 041f274a78b1..52b338f5538a 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -1,13 +1,5 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import wx -import matplotlib -from matplotlib import cbook -from . import wx_compat as wxc from .backend_agg import FigureCanvasAgg from .backend_wx import ( _BackendWx, _FigureCanvasWxBase, FigureFrameWx, @@ -72,11 +64,6 @@ def blit(self, bbox=None): filetypes = FigureCanvasAgg.filetypes -@cbook.deprecated("2.2", alternative="NavigationToolbar2WxAgg") -class Toolbar(NavigationToolbar2WxAgg): - pass - - # agg/wxPython image conversion functions (wxPython >= 2.8) def _convert_agg_to_wx_image(agg, bbox): @@ -88,7 +75,7 @@ def _convert_agg_to_wx_image(agg, bbox): """ if bbox is None: # agg => rgb -> image - image = wxc.EmptyImage(int(agg.width), int(agg.height)) + image = wx.Image(int(agg.width), int(agg.height)) image.SetData(agg.tostring_rgb()) return image else: @@ -105,8 +92,8 @@ def _convert_agg_to_wx_bitmap(agg, bbox): """ if bbox is None: # agg => rgba buffer -> bitmap - return wxc.BitmapFromBuffer(int(agg.width), int(agg.height), - agg.buffer_rgba()) + return wx.Bitmap.FromBufferRGBA(int(agg.width), int(agg.height), + agg.buffer_rgba()) else: # agg => rgba buffer -> bitmap => clipped bitmap return _WX28_clipped_agg_as_bitmap(agg, bbox) @@ -122,12 +109,12 @@ def _WX28_clipped_agg_as_bitmap(agg, bbox): r = l + width t = b + height - srcBmp = wxc.BitmapFromBuffer(int(agg.width), int(agg.height), - agg.buffer_rgba()) + srcBmp = wx.Bitmap.FromBufferRGBA(int(agg.width), int(agg.height), + agg.buffer_rgba()) srcDC = wx.MemoryDC() srcDC.SelectObject(srcBmp) - destBmp = wxc.EmptyBitmap(int(width), int(height)) + destBmp = wx.Bitmap(int(width), int(height)) destDC = wx.MemoryDC() destDC.SelectObject(destBmp) diff --git a/lib/matplotlib/backends/backend_wxcairo.py b/lib/matplotlib/backends/backend_wxcairo.py index fb3290f2bbc4..3da68c525e22 100644 --- a/lib/matplotlib/backends/backend_wxcairo.py +++ b/lib/matplotlib/backends/backend_wxcairo.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import wx from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index d0b71be4ea91..b0fa0a907371 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -11,10 +11,6 @@ ``rcParams["backend.qt4"]``; - otherwise, use whatever the rcParams indicate. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six from distutils.version import LooseVersion import os @@ -22,12 +18,18 @@ from matplotlib import rcParams + QT_API_PYQT5 = "PyQt5" QT_API_PYSIDE2 = "PySide2" QT_API_PYQTv2 = "PyQt4v2" QT_API_PYSIDE = "PySide" QT_API_PYQT = "PyQt4" # Use the old sip v1 API (Py3 defaults to v2). -QT_API_ENV = os.environ.get('QT_API') +QT_API_ENV = os.environ.get("QT_API") +# Mapping of QT_API_ENV to requested binding. ETS does not support PyQt4v1. +# (https://github.com/enthought/pyface/blob/master/pyface/qt/__init__.py) +_ETS = {"pyqt5": QT_API_PYQT5, "pyside2": QT_API_PYSIDE2, + "pyqt": QT_API_PYQTv2, "pyside": QT_API_PYSIDE, + None: None} # First, check if anything is already imported. if "PyQt5" in sys.modules: QT_API = QT_API_PYQT5 @@ -44,13 +46,13 @@ # Otherwise, check the QT_API environment variable (from Enthought). This can # only override the binding, not the backend (in other words, we check that the # requested backend actually matches). -elif rcParams["backend"] == "Qt5Agg": +elif rcParams["backend"] in ["Qt5Agg", "Qt5Cairo"]: if QT_API_ENV == "pyqt5": dict.__setitem__(rcParams, "backend.qt5", QT_API_PYQT5) elif QT_API_ENV == "pyside2": dict.__setitem__(rcParams, "backend.qt5", QT_API_PYSIDE2) QT_API = dict.__getitem__(rcParams, "backend.qt5") -elif rcParams["backend"] == "Qt4Agg": +elif rcParams["backend"] in ["Qt4Agg", "Qt4Cairo"]: if QT_API_ENV == "pyqt4": dict.__setitem__(rcParams, "backend.qt4", QT_API_PYQTv2) elif QT_API_ENV == "pyside": @@ -59,7 +61,12 @@ # A non-Qt backend was selected but we still got there (possible, e.g., when # fully manually embedding Matplotlib in a Qt app without using pyplot). else: - QT_API = None + try: + QT_API = _ETS[QT_API_ENV] + except KeyError: + raise RuntimeError( + "The environment variable QT_API has the unrecognized value {!r};" + "valid values are 'pyqt5', 'pyside2', 'pyqt', and 'pyside'") def _setup_pyqt5(): diff --git a/lib/matplotlib/backends/qt_editor/__init__.py b/lib/matplotlib/backends/qt_editor/__init__.py index 800d82e7ee00..e69de29bb2d1 100644 --- a/lib/matplotlib/backends/qt_editor/__init__.py +++ b/lib/matplotlib/backends/qt_editor/__init__.py @@ -1,2 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index 40572c8bd827..6620f5870920 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -1,18 +1,11 @@ -# -*- coding: utf-8 -*- -# # Copyright © 2009 Pierre Raybaut # Licensed under the terms of the MIT License # see the mpl licenses directory for a copy of the license -"""Module that provides a GUI-based editor for matplotlib's figure options""" +"""Module that provides a GUI-based editor for matplotlib's figure options.""" -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -import os.path as osp +import os.path import re import matplotlib @@ -22,8 +15,8 @@ def get_icon(name): - basedir = osp.join(matplotlib.rcParams['datapath'], 'images') - return QtGui.QIcon(osp.join(basedir, name)) + basedir = os.path.join(matplotlib.rcParams['datapath'], 'images') + return QtGui.QIcon(os.path.join(basedir, name)) LINESTYLES = {'-': 'Solid', @@ -100,6 +93,8 @@ def prepare_data(d, init): FormLayout combobox, namely `[initial_name, (shorthand, style_name), (shorthand, style_name), ...]`. """ + if init not in d: + d = {**d, init: str(init)} # Drop duplicate shorthands from dict (by overwriting them during # the dict comprehension). name2short = {name: short for short, name in d.items()} @@ -247,7 +242,7 @@ def apply_callback(data): ncol = old_legend._ncol new_legend = axes.legend(ncol=ncol) if new_legend: - new_legend.draggable(draggable) + new_legend.set_draggable(draggable) # Redraw figure = axes.get_figure() diff --git a/lib/matplotlib/backends/qt_editor/formlayout.py b/lib/matplotlib/backends/qt_editor/formlayout.py index d5fcdfc901d6..d4cf62601303 100644 --- a/lib/matplotlib/backends/qt_editor/formlayout.py +++ b/lib/matplotlib/backends/qt_editor/formlayout.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ formlayout ========== @@ -38,18 +37,14 @@ # 1.0.7: added support for "Apply" button # 1.0.6: code cleaning -from __future__ import (absolute_import, division, print_function, - unicode_literals) - __version__ = '1.0.10' __license__ = __doc__ import copy import datetime +from numbers import Integral, Real import warnings -import six - from matplotlib import colors as mcolors from matplotlib.backends.qt_compat import QtGui, QtWidgets, QtCore @@ -98,7 +93,7 @@ def to_qcolor(color): try: rgba = mcolors.to_rgba(color) except ValueError: - warnings.warn('Ignoring invalid color %r' % color) + warnings.warn('Ignoring invalid color %r' % color, stacklevel=2) return qcolor # return invalid QColor qcolor.setRgbF(*rgba) return qcolor @@ -133,7 +128,7 @@ def text(self): def font_is_installed(font): """Check if font is installed""" return [fam for fam in QtGui.QFontDatabase().families() - if six.text_type(fam) == font] + if str(fam) == font] def tuple_to_qfont(tup): @@ -143,7 +138,7 @@ def tuple_to_qfont(tup): """ if not (isinstance(tup, tuple) and len(tup) == 4 and font_is_installed(tup[0]) - and isinstance(tup[1], int) + and isinstance(tup[1], Integral) and isinstance(tup[2], bool) and isinstance(tup[3], bool)): return None @@ -157,7 +152,7 @@ def tuple_to_qfont(tup): def qfont_to_tuple(font): - return (six.text_type(font.family()), int(font.pointSize()), + return (str(font.family()), int(font.pointSize()), font.italic(), font.bold()) @@ -176,7 +171,7 @@ def __init__(self, value, parent=None): # Font size self.size = QtWidgets.QComboBox(parent) self.size.setEditable(True) - sizelist = list(range(6, 12)) + list(range(12, 30, 2)) + [36, 48, 72] + sizelist = [*range(6, 12), *range(12, 30, 2), 36, 48, 72] size = font.pointSize() if size not in sizelist: sizelist.append(size) @@ -245,11 +240,15 @@ def setup(self): elif (label.lower() not in BLACKLIST and mcolors.is_color_like(value)): field = ColorLayout(to_qcolor(value), self) - elif isinstance(value, six.string_types): + elif isinstance(value, str): field = QtWidgets.QLineEdit(value, self) elif isinstance(value, (list, tuple)): if isinstance(value, tuple): value = list(value) + # Note: get() below checks the type of value[0] in self.data so + # it is essential that value gets modified in-place. + # This means that the code is actually broken in the case where + # value is a tuple, but fortunately we always pass a list... selindex = value.pop(0) field = QtWidgets.QComboBox(self) if isinstance(value[0], (list, tuple)): @@ -262,10 +261,10 @@ def setup(self): selindex = value.index(selindex) elif selindex in keys: selindex = keys.index(selindex) - elif not isinstance(selindex, int): + elif not isinstance(selindex, Integral): warnings.warn( "index '%s' is invalid (label: %s, value: %s)" % - (selindex, label, value)) + (selindex, label, value), stacklevel=2) selindex = 0 field.setCurrentIndex(selindex) elif isinstance(value, bool): @@ -274,7 +273,11 @@ def setup(self): field.setCheckState(QtCore.Qt.Checked) else: field.setCheckState(QtCore.Qt.Unchecked) - elif isinstance(value, float): + elif isinstance(value, Integral): + field = QtWidgets.QSpinBox(self) + field.setRange(-1e9, 1e9) + field.setValue(value) + elif isinstance(value, Real): field = QtWidgets.QLineEdit(repr(value), self) field.setCursorPosition(0) field.setValidator(QtGui.QDoubleValidator(field)) @@ -282,10 +285,6 @@ def setup(self): dialog = self.get_dialog() dialog.register_float_field(field) field.textChanged.connect(lambda text: dialog.update_buttons()) - elif isinstance(value, int): - field = QtWidgets.QSpinBox(self) - field.setRange(-1e9, 1e9) - field.setValue(value) elif isinstance(value, datetime.datetime): field = QtWidgets.QDateTimeEdit(self) field.setDateTime(value) @@ -306,9 +305,8 @@ def get(self): continue elif tuple_to_qfont(value) is not None: value = field.get_font() - elif (isinstance(value, six.string_types) - or mcolors.is_color_like(value)): - value = six.text_type(field.text()) + elif isinstance(value, str) or mcolors.is_color_like(value): + value = str(field.text()) elif isinstance(value, (list, tuple)): index = int(field.currentIndex()) if isinstance(value[0], (list, tuple)): @@ -317,10 +315,10 @@ def get(self): value = value[index] elif isinstance(value, bool): value = field.checkState() == QtCore.Qt.Checked - elif isinstance(value, float): - value = float(str(field.text())) - elif isinstance(value, int): + elif isinstance(value, Integral): value = int(field.value()) + elif isinstance(value, Real): + value = float(str(field.text())) elif isinstance(value, datetime.datetime): value = field.dateTime().toPyDateTime() elif isinstance(value, datetime.date): diff --git a/lib/matplotlib/backends/qt_editor/formsubplottool.py b/lib/matplotlib/backends/qt_editor/formsubplottool.py index 4906af588a7a..a0914cab880e 100644 --- a/lib/matplotlib/backends/qt_editor/formsubplottool.py +++ b/lib/matplotlib/backends/qt_editor/formsubplottool.py @@ -4,7 +4,7 @@ class UiSubplotTool(QtWidgets.QDialog): def __init__(self, *args, **kwargs): - super(UiSubplotTool, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.setObjectName("SubplotTool") self._widgets = {} diff --git a/lib/matplotlib/backends/tkagg.py b/lib/matplotlib/backends/tkagg.py index 072fcb48fee6..0d2b7801c9bd 100644 --- a/lib/matplotlib/backends/tkagg.py +++ b/lib/matplotlib/backends/tkagg.py @@ -1,13 +1,15 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import tkinter as Tk +import tkinter as Tk import numpy as np +from matplotlib import cbook from matplotlib.backends import _tkagg + +cbook.warn_deprecated( + "3.0", "The matplotlib.backends.tkagg module is deprecated.") + + def blit(photoimage, aggimage, bbox=None, colormode=1): tk = photoimage.tk diff --git a/lib/matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js b/lib/matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js index 6feb11086f45..6ad8974b0f15 100644 --- a/lib/matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js +++ b/lib/matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js @@ -6708,7 +6708,7 @@ jQuery.extend({ value += "px"; } - // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, // but it would mean to define eight (for every problematic property) identical functions if ( !support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { style[ name ] = "inherit"; diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb index dfb264c37591..8ac7434e53e1 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -8,7 +8,6 @@ }, "outputs": [], "source": [ - "from __future__ import print_function\n", "from imp import reload" ] }, diff --git a/lib/matplotlib/backends/windowing.py b/lib/matplotlib/backends/windowing.py index 6c2e495906cb..b989f2d431f6 100644 --- a/lib/matplotlib/backends/windowing.py +++ b/lib/matplotlib/backends/windowing.py @@ -5,13 +5,13 @@ effectively disabled. It uses a tiny C++ extension module to access MS Win functions. + +This module is deprecated and will be removed in version 3.2 """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six +from matplotlib import rcParams, cbook -from matplotlib import rcParams +cbook.warn_deprecated('3.0', obj_type='module', name='backends.windowing') try: if not rcParams['tk.window_focus']: diff --git a/lib/matplotlib/backends/wx_compat.py b/lib/matplotlib/backends/wx_compat.py index e8467fc15d00..78bc34511e3a 100644 --- a/lib/matplotlib/backends/wx_compat.py +++ b/lib/matplotlib/backends/wx_compat.py @@ -6,172 +6,31 @@ For an example see embedding_in_wx2.py """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +import wx -import six -from distutils.version import StrictVersion, LooseVersion +from .. import cbook +from .backend_wx import RendererWx -missingwx = "Matplotlib backend_wx and backend_wxagg require wxPython>=2.9" +cbook.warn_deprecated("3.0", "{} is deprecated.".format(__name__)) -try: - import wx - backend_version = wx.VERSION_STRING - is_phoenix = 'phoenix' in wx.PlatformInfo -except ImportError: - raise ImportError(missingwx) +backend_version = wx.VERSION_STRING +is_phoenix = 'phoenix' in wx.PlatformInfo -try: - wx_version = StrictVersion(wx.VERSION_STRING) -except ValueError: - wx_version = LooseVersion(wx.VERSION_STRING) +fontweights = RendererWx.fontweights +fontangles = RendererWx.fontangles +fontnames = RendererWx.fontnames -# Ensure we have the correct version imported -if wx_version < str("2.9"): - raise ImportError(missingwx) +dashd_wx = {'solid': wx.PENSTYLE_SOLID, + 'dashed': wx.PENSTYLE_SHORT_DASH, + 'dashdot': wx.PENSTYLE_DOT_DASH, + 'dotted': wx.PENSTYLE_DOT} -if is_phoenix: - # define all the wxPython phoenix stuff - - # font styles, families and weight - fontweights = { - 100: wx.FONTWEIGHT_LIGHT, - 200: wx.FONTWEIGHT_LIGHT, - 300: wx.FONTWEIGHT_LIGHT, - 400: wx.FONTWEIGHT_NORMAL, - 500: wx.FONTWEIGHT_NORMAL, - 600: wx.FONTWEIGHT_NORMAL, - 700: wx.FONTWEIGHT_BOLD, - 800: wx.FONTWEIGHT_BOLD, - 900: wx.FONTWEIGHT_BOLD, - 'ultralight': wx.FONTWEIGHT_LIGHT, - 'light': wx.FONTWEIGHT_LIGHT, - 'normal': wx.FONTWEIGHT_NORMAL, - 'medium': wx.FONTWEIGHT_NORMAL, - 'semibold': wx.FONTWEIGHT_NORMAL, - 'bold': wx.FONTWEIGHT_BOLD, - 'heavy': wx.FONTWEIGHT_BOLD, - 'ultrabold': wx.FONTWEIGHT_BOLD, - 'black': wx.FONTWEIGHT_BOLD - } - fontangles = { - 'italic': wx.FONTSTYLE_ITALIC, - 'normal': wx.FONTSTYLE_NORMAL, - 'oblique': wx.FONTSTYLE_SLANT} - - # wxPython allows for portable font styles, choosing them appropriately - # for the target platform. Map some standard font names to the portable - # styles - # QUESTION: Is it be wise to agree standard fontnames across all backends? - fontnames = {'Sans': wx.FONTFAMILY_SWISS, - 'Roman': wx.FONTFAMILY_ROMAN, - 'Script': wx.FONTFAMILY_SCRIPT, - 'Decorative': wx.FONTFAMILY_DECORATIVE, - 'Modern': wx.FONTFAMILY_MODERN, - 'Courier': wx.FONTFAMILY_MODERN, - 'courier': wx.FONTFAMILY_MODERN} - - dashd_wx = {'solid': wx.PENSTYLE_SOLID, - 'dashed': wx.PENSTYLE_SHORT_DASH, - 'dashdot': wx.PENSTYLE_DOT_DASH, - 'dotted': wx.PENSTYLE_DOT} - - # functions changes - BitmapFromBuffer = wx.Bitmap.FromBufferRGBA - EmptyBitmap = wx.Bitmap - EmptyImage = wx.Image - Cursor = wx.Cursor - EventLoop = wx.GUIEventLoop - NamedColour = wx.Colour - StockCursor = wx.Cursor - -else: - # define all the wxPython classic stuff - - # font styles, families and weight - fontweights = { - 100: wx.LIGHT, - 200: wx.LIGHT, - 300: wx.LIGHT, - 400: wx.NORMAL, - 500: wx.NORMAL, - 600: wx.NORMAL, - 700: wx.BOLD, - 800: wx.BOLD, - 900: wx.BOLD, - 'ultralight': wx.LIGHT, - 'light': wx.LIGHT, - 'normal': wx.NORMAL, - 'medium': wx.NORMAL, - 'semibold': wx.NORMAL, - 'bold': wx.BOLD, - 'heavy': wx.BOLD, - 'ultrabold': wx.BOLD, - 'black': wx.BOLD - } - fontangles = { - 'italic': wx.ITALIC, - 'normal': wx.NORMAL, - 'oblique': wx.SLANT} - - # wxPython allows for portable font styles, choosing them appropriately - # for the target platform. Map some standard font names to the portable - # styles - # QUESTION: Is it be wise to agree standard fontnames across all backends? - fontnames = {'Sans': wx.SWISS, - 'Roman': wx.ROMAN, - 'Script': wx.SCRIPT, - 'Decorative': wx.DECORATIVE, - 'Modern': wx.MODERN, - 'Courier': wx.MODERN, - 'courier': wx.MODERN} - - dashd_wx = {'solid': wx.SOLID, - 'dashed': wx.SHORT_DASH, - 'dashdot': wx.DOT_DASH, - 'dotted': wx.DOT} - - # functions changes - BitmapFromBuffer = wx.BitmapFromBufferRGBA - EmptyBitmap = wx.EmptyBitmap - EmptyImage = wx.EmptyImage - Cursor = wx.StockCursor - EventLoop = wx.EventLoop - NamedColour = wx.NamedColour - StockCursor = wx.StockCursor - - -# wxPython Classic's DoAddTool has become AddTool in Phoenix. Otherwise -# they are the same, except for early betas and prerelease builds of -# Phoenix. This function provides a shim that does the RightThing based on -# which wxPython is in use. -def _AddTool(parent, wx_ids, text, bmp, tooltip_text): - if text in ['Pan', 'Zoom']: - kind = wx.ITEM_CHECK - else: - kind = wx.ITEM_NORMAL - if is_phoenix: - add_tool = parent.AddTool - else: - add_tool = parent.DoAddTool - - if not is_phoenix or wx_version >= str("4.0.0b2"): - # NOTE: when support for Phoenix prior to 4.0.0b2 is dropped then - # all that is needed is this clause, and the if and else clause can - # be removed. - kwargs = dict(label=text, - bitmap=bmp, - bmpDisabled=wx.NullBitmap, - shortHelp=text, - longHelp=tooltip_text, - kind=kind) - else: - kwargs = dict(label=text, - bitmap=bmp, - bmpDisabled=wx.NullBitmap, - shortHelpString=text, - longHelpString=tooltip_text, - kind=kind) - - return add_tool(wx_ids[text], **kwargs) +# functions changes +BitmapFromBuffer = wx.Bitmap.FromBufferRGBA +EmptyBitmap = wx.Bitmap +EmptyImage = wx.Image +Cursor = wx.Cursor +EventLoop = wx.GUIEventLoop +NamedColour = wx.Colour +StockCursor = wx.Cursor diff --git a/lib/matplotlib/bezier.py b/lib/matplotlib/bezier.py index 80fbd6137efe..0877038d5776 100644 --- a/lib/matplotlib/bezier.py +++ b/lib/matplotlib/bezier.py @@ -2,17 +2,11 @@ A module providing some utility functions regarding bezier path manipulation. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import warnings import numpy as np from matplotlib.path import Path -from operator import xor -import warnings - class NonIntersectingPathException(ValueError): pass @@ -37,8 +31,9 @@ def get_intersection(cx1, cy1, cos_t1, sin_t1, c, d = sin_t2, -cos_t2 ad_bc = a * d - b * c - if ad_bc == 0.: - raise ValueError("Given lines do not intersect") + if np.abs(ad_bc) < 1.0e-12: + raise ValueError("Given lines do not intersect. Please verify that " + "the angles are not equal or differ by 180 degrees.") # rhs_inverse a_, b_ = d, -b @@ -139,7 +134,7 @@ def find_bezier_t_intersecting_with_closedpath(bezier_point_at_t, middle = bezier_point_at_t(middle_t) middle_inside = inside_closedpath(middle) - if xor(start_inside, middle_inside): + if start_inside ^ middle_inside: t1 = middle_t end = middle end_inside = middle_inside diff --git a/lib/matplotlib/blocking_input.py b/lib/matplotlib/blocking_input.py index 090ffdb8647a..d5e9257c10fe 100644 --- a/lib/matplotlib/blocking_input.py +++ b/lib/matplotlib/blocking_input.py @@ -2,73 +2,61 @@ This provides several classes used for blocking interaction with figure windows: -:class:`BlockingInput` - creates a callable object to retrieve events in a blocking way for - interactive sessions - -:class:`BlockingKeyMouseInput` - creates a callable object to retrieve key or mouse clicks in a blocking - way for interactive sessions. - Note: Subclass of BlockingInput. Used by waitforbuttonpress - -:class:`BlockingMouseInput` - creates a callable object to retrieve mouse clicks in a blocking way for - interactive sessions. - Note: Subclass of BlockingInput. Used by ginput - -:class:`BlockingContourLabeler` - creates a callable object to retrieve mouse clicks in a blocking way that - will then be used to place labels on a ContourSet - Note: Subclass of BlockingMouseInput. Used by clabel -""" +`BlockingInput` + Creates a callable object to retrieve events in a blocking way for + interactive sessions. Base class of the other classes listed here. -from __future__ import (absolute_import, division, print_function, - unicode_literals) +`BlockingKeyMouseInput` + Creates a callable object to retrieve key or mouse clicks in a blocking + way for interactive sessions. Used by `waitforbuttonpress`. -import six -import matplotlib.lines as mlines +`BlockingMouseInput` + Creates a callable object to retrieve mouse clicks in a blocking way for + interactive sessions. Used by `ginput`. + +`BlockingContourLabeler` + Creates a callable object to retrieve mouse clicks in a blocking way that + will then be used to place labels on a `ContourSet`. Used by `clabel`. +""" import logging +from numbers import Integral + +import matplotlib.lines as mlines _log = logging.getLogger(__name__) class BlockingInput(object): - """ - Class that creates a callable object to retrieve events in a - blocking way. - """ + """Callable for retrieving events in a blocking way.""" + def __init__(self, fig, eventslist=()): self.fig = fig self.eventslist = eventslist def on_event(self, event): """ - Event handler that will be passed to the current figure to - retrieve events. + Event handler; will be passed to the current figure to retrieve events. """ - # Add a new event to list - using a separate function is - # overkill for the base class, but this is consistent with - # subclasses + # Add a new event to list - using a separate function is overkill for + # the base class, but this is consistent with subclasses. self.add_event(event) _log.info("Event %i", len(self.events)) - # This will extract info from events + # This will extract info from events. self.post_event() - # Check if we have enough events already - if len(self.events) >= self.n and self.n > 0: + # Check if we have enough events already. + if len(self.events) >= self.n > 0: self.fig.canvas.stop_event_loop() def post_event(self): - """For baseclass, do nothing but collect events""" - pass + """For baseclass, do nothing but collect events.""" def cleanup(self): - """Disconnect all callbacks""" + """Disconnect all callbacks.""" for cb in self.callbacks: self.fig.canvas.mpl_disconnect(cb) - self.callbacks = [] def add_event(self, event): @@ -77,58 +65,45 @@ def add_event(self, event): def pop_event(self, index=-1): """ - This removes an event from the event list. Defaults to - removing last event, but an index can be supplied. Note that - this does not check that there are events, much like the - normal pop method. If not events exist, this will throw an - exception. + Remove an event from the event list -- by default, the last. + + Note that this does not check that there are events, much like the + normal pop method. If no events exist, this will throw an exception. """ self.events.pop(index) - def pop(self, index=-1): - self.pop_event(index) - pop.__doc__ = pop_event.__doc__ + pop = pop_event def __call__(self, n=1, timeout=30): - """ - Blocking call to retrieve n events - """ - - if not isinstance(n, int): + """Blocking call to retrieve *n* events.""" + if not isinstance(n, Integral): raise ValueError("Requires an integer argument") self.n = n - self.events = [] - self.callbacks = [] if hasattr(self.fig.canvas, "manager"): # Ensure that the figure is shown, if we are managing it. self.fig.show() - - # connect the events to the on_event function call - for n in self.eventslist: - self.callbacks.append( - self.fig.canvas.mpl_connect(n, self.on_event)) - + # Connect the events to the on_event function call. + self.callbacks = [self.fig.canvas.mpl_connect(name, self.on_event) + for name in self.eventslist] try: - # Start event loop + # Start event loop. self.fig.canvas.start_event_loop(timeout=timeout) - finally: # Run even on exception like ctrl-c - # Disconnect the callbacks + finally: # Run even on exception like ctrl-c. + # Disconnect the callbacks. self.cleanup() - - # Return the events in this case + # Return the events in this case. return self.events class BlockingMouseInput(BlockingInput): """ - Class that creates a callable object to retrieve mouse clicks in a - blocking way. + Callable for retrieving mouse clicks in a blocking way. - This class will also retrieve keyboard clicks and treat them like - appropriate mouse clicks (delete and backspace are like mouse button 3, - enter is like mouse button 2 and all others are like mouse button 1). + This class will also retrieve keypresses and map them to mouse clicks: + delete and backspace are like mouse button 3, enter is like mouse button 2 + and all others are like mouse button 1. """ button_add = 1 @@ -144,9 +119,7 @@ def __init__(self, fig, mouse_add=1, mouse_pop=3, mouse_stop=2): self.button_stop = mouse_stop def post_event(self): - """ - This will be called to process events - """ + """Process an event.""" if len(self.events) == 0: _log.warning("No events yet") elif self.events[-1].name == 'key_press_event': @@ -155,11 +128,9 @@ def post_event(self): self.mouse_event() def mouse_event(self): - '''Process a mouse click event''' - + """Process a mouse click event.""" event = self.events[-1] button = event.button - if button == self.button_pop: self.mouse_event_pop(event) elif button == self.button_stop: @@ -168,72 +139,51 @@ def mouse_event(self): self.mouse_event_add(event) def key_event(self): - ''' - Process a key click event. This maps certain keys to appropriate - mouse click events. - ''' - + """ + Process a key press event, mapping keys to appropriate mouse clicks. + """ event = self.events[-1] if event.key is None: - # at least in mac os X gtk backend some key returns None. + # At least in OSX gtk backend some keys return None. return - key = event.key.lower() - if key in ['backspace', 'delete']: self.mouse_event_pop(event) elif key in ['escape', 'enter']: - # on windows XP and wxAgg, the enter key doesn't seem to register self.mouse_event_stop(event) else: self.mouse_event_add(event) def mouse_event_add(self, event): - """ - Will be called for any event involving a button other than - button 2 or 3. This will add a click if it is inside axes. - """ + """Process an button-1 event (add a click if inside axes).""" if event.inaxes: self.add_click(event) - else: # If not a valid click, remove from event list - BlockingInput.pop(self, -1) + else: # If not a valid click, remove from event list. + BlockingInput.pop(self) def mouse_event_stop(self, event): - """ - Will be called for any event involving button 2. - Button 2 ends blocking input. - """ - - # Remove last event just for cleanliness - BlockingInput.pop(self, -1) - - # This will exit even if not in infinite mode. This is - # consistent with MATLAB and sometimes quite useful, but will - # require the user to test how many points were actually - # returned before using data. + """Process an button-2 event (end blocking input).""" + # Remove last event just for cleanliness. + BlockingInput.pop(self) + # This will exit even if not in infinite mode. This is consistent with + # MATLAB and sometimes quite useful, but will require the user to test + # how many points were actually returned before using data. self.fig.canvas.stop_event_loop() def mouse_event_pop(self, event): - """ - Will be called for any event involving button 3. - Button 3 removes the last click. - """ - # Remove this last event - BlockingInput.pop(self, -1) - - # Now remove any existing clicks if possible - if len(self.events) > 0: - self.pop(event, -1) + """Process an button-3 event (remove the last click).""" + # Remove this last event. + BlockingInput.pop(self) + # Now remove any existing clicks if possible. + if self.events: + self.pop(event) def add_click(self, event): - """ - This add the coordinates of an event to the list of clicks - """ + """Add the coordinates of an event to the list of clicks.""" self.clicks.append((event.xdata, event.ydata)) - _log.info("input %i: %f,%f" % - (len(self.clicks), event.xdata, event.ydata)) - - # If desired plot up click + _log.info("input %i: %f, %f", + len(self.clicks), event.xdata, event.ydata) + # If desired, plot up click. if self.show_clicks: line = mlines.Line2D([event.xdata], [event.ydata], marker='+', color='r') @@ -242,62 +192,49 @@ def add_click(self, event): self.fig.canvas.draw() def pop_click(self, event, index=-1): - """ - This removes a click from the list of clicks. Defaults to - removing the last click. - """ + """Remove a click (by default, the last) from the list of clicks.""" self.clicks.pop(index) - if self.show_clicks: - - mark = self.marks.pop(index) - mark.remove() - + self.marks.pop(index).remove() self.fig.canvas.draw() - # NOTE: I do NOT understand why the above 3 lines does not work - # for the keyboard backspace event on windows XP wxAgg. - # maybe event.inaxes here is a COPY of the actual axes? def pop(self, event, index=-1): """ - This removes a click and the associated event from the object. - Defaults to removing the last click, but any index can be - supplied. + Removes a click and the associated event from the list of clicks. + + Defaults to the last click. """ self.pop_click(event, index) BlockingInput.pop(self, index) def cleanup(self, event=None): - # clean the figure + # Clean the figure. if self.show_clicks: - for mark in self.marks: mark.remove() self.marks = [] - self.fig.canvas.draw() - - # Call base class to remove callbacks + # Call base class to remove callbacks. BlockingInput.cleanup(self) def __call__(self, n=1, timeout=30, show_clicks=True): """ - Blocking call to retrieve n coordinate pairs through mouse - clicks. + Blocking call to retrieve *n* coordinate pairs through mouse clicks. """ self.show_clicks = show_clicks self.clicks = [] self.marks = [] BlockingInput.__call__(self, n=n, timeout=timeout) - return self.clicks class BlockingContourLabeler(BlockingMouseInput): """ - Class that creates a callable object that uses mouse clicks or key - clicks on a figure window to place contour labels. + Callable for retrieving mouse clicks and key presses in a blocking way. + + Used to place contour labels. """ + def __init__(self, cs): self.cs = cs BlockingMouseInput.__init__(self, fig=cs.ax.figure) @@ -309,11 +246,7 @@ def pop_click(self, event, index=-1): self.button3(event) def button1(self, event): - """ - This will be called if an event involving a button other than - 2 or 3 occcurs. This will add a label to a contour. - """ - + """Process an button-1 event (add a label to a contour).""" # Shorthand if event.inaxes == self.cs.ax: self.cs.add_label_near(event.x, event.y, self.inline, @@ -325,13 +258,12 @@ def button1(self, event): def button3(self, event): """ - This will be called if button 3 is clicked. This will remove - a label if not in inline mode. Unfortunately, if one is doing - inline labels, then there is currently no way to fix the - broken contour - once humpty-dumpty is broken, he can't be put - back together. In inline mode, this does nothing. - """ + Process an button-3 event (remove a label if not in inline mode). + Unfortunately, if one is doing inline labels, then there is currently + no way to fix the broken contour - once humpty-dumpty is broken, he + can't be put back together. In inline mode, this does nothing. + """ if self.inline: pass else: @@ -341,33 +273,32 @@ def button3(self, event): def __call__(self, inline, inline_spacing=5, n=-1, timeout=-1): self.inline = inline self.inline_spacing = inline_spacing - BlockingMouseInput.__call__(self, n=n, timeout=timeout, show_clicks=False) class BlockingKeyMouseInput(BlockingInput): """ - Class that creates a callable object to retrieve a single mouse or - keyboard click + Callable for retrieving mouse clicks and key presses in a blocking way. """ + def __init__(self, fig): BlockingInput.__init__(self, fig=fig, eventslist=( 'button_press_event', 'key_press_event')) def post_event(self): - """ - Determines if it is a key event - """ - if len(self.events) == 0: - _log.warning("No events yet") - else: + """Determine if it is a key event.""" + if self.events: self.keyormouse = self.events[-1].name == 'key_press_event' + else: + _log.warning("No events yet.") def __call__(self, timeout=30): """ - Blocking call to retrieve a single mouse or key click - Returns True if key click, False if mouse, or None if timeout + Blocking call to retrieve a single mouse click or key press. + + Returns ``True`` if key press, ``False`` if mouse click, or ``None`` if + timed out. """ self.keyormouse = None BlockingInput.__call__(self, n=1, timeout=timeout) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index b135bff1ccf5..22a0f2939acc 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Module that allows plotting of string "category" data. i.e. ``plot(['d', 'f', 'a'],[1, 2, 3])`` will plot three points with x-axis @@ -11,32 +10,21 @@ strings to integers, provides a tick locator and formatter, and the class:`.UnitData` that creates and stores the string-to-integer mapping. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) from collections import OrderedDict import itertools -import six - - import numpy as np import matplotlib.units as units import matplotlib.ticker as ticker -# np 1.6/1.7 support -from distutils.version import LooseVersion - -VALID_TYPES = tuple(set(six.string_types + - (bytes, six.text_type, np.str_, np.bytes_))) - class StrCategoryConverter(units.ConversionInterface): @staticmethod def convert(value, unit, axis): """Converts strings in value to floats using - mapping information store in the unit object + mapping information store in the unit object. Parameters ---------- @@ -58,7 +46,7 @@ def convert(value, unit, axis): # pass through sequence of non binary numbers if all((units.ConversionInterface.is_numlike(v) and - not isinstance(v, VALID_TYPES)) for v in values): + not isinstance(v, (str, bytes))) for v in values): return np.asarray(values, dtype=float) # force an update so it also does type checking @@ -96,7 +84,7 @@ def axisinfo(unit, axis): @staticmethod def default_units(data, axis): - """ Sets and updates the :class:`~matplotlib.Axis.axis~ units + """Sets and updates the :class:`~matplotlib.Axis.axis` units. Parameters ---------- @@ -156,36 +144,35 @@ def __call__(self, x, pos=None): @staticmethod def _text(value): - """Converts text values into `utf-8` or `ascii` strings + """Converts text values into utf-8 or ascii strings. """ - if LooseVersion(np.__version__) < LooseVersion('1.7.0'): - if (isinstance(value, (six.text_type, np.unicode))): - value = value.encode('utf-8', 'ignore').decode('utf-8') - if isinstance(value, (np.bytes_, six.binary_type)): + if isinstance(value, bytes): value = value.decode(encoding='utf-8') - elif not isinstance(value, (np.str_, six.string_types)): + elif not isinstance(value, str): value = str(value) return value class UnitData(object): def __init__(self, data=None): - """Create mapping between unique categorical values - and integer identifiers + """ + Create mapping between unique categorical values and integer ids. + + Parameters ---------- data: iterable sequence of string values """ self._mapping = OrderedDict() - self._counter = itertools.count(start=0) + self._counter = itertools.count() if data is not None: self.update(data) def update(self, data): """Maps new values to integer identifiers. - Paramters - --------- + Parameters + ---------- data: iterable sequence of string values @@ -197,7 +184,7 @@ def update(self, data): data = np.atleast_1d(np.array(data, dtype=object)) for val in OrderedDict.fromkeys(data): - if not isinstance(val, VALID_TYPES): + if not isinstance(val, (str, bytes)): raise TypeError("{val!r} is not a string".format(val=val)) if val not in self._mapping: self._mapping[val] = next(self._counter) @@ -206,6 +193,5 @@ def update(self, data): # Connects the convertor to matplotlib units.registry[str] = StrCategoryConverter() units.registry[np.str_] = StrCategoryConverter() -units.registry[six.text_type] = StrCategoryConverter() units.registry[bytes] = StrCategoryConverter() units.registry[np.bytes_] = StrCategoryConverter() diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index dcb2d0549dea..825c3328e3af 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -6,11 +6,8 @@ it imports matplotlib only at runtime. """ -from __future__ import absolute_import, division, print_function - -import six -from six.moves import xrange, zip import collections +import collections.abc import contextlib import datetime import errno @@ -18,26 +15,29 @@ import glob import gzip import io -from itertools import repeat +import itertools import locale import numbers import operator import os +from pathlib import Path import re import sys import time import traceback import types import warnings -from weakref import ref, WeakKeyDictionary +import weakref +from weakref import WeakMethod import numpy as np import matplotlib -from .deprecation import deprecated, warn_deprecated -from .deprecation import mplDeprecation, MatplotlibDeprecationWarning +from .deprecation import ( + mplDeprecation, deprecated, warn_deprecated, MatplotlibDeprecationWarning) +@deprecated("3.0") def unicode_safe(s): if isinstance(s, bytes): @@ -57,200 +57,32 @@ def unicode_safe(s): preferredencoding = None if preferredencoding is None: - return six.text_type(s) + return str(s) else: - return six.text_type(s, preferredencoding) + return str(s, preferredencoding) return s -@deprecated('2.1') -class converter(object): - """ - Base class for handling string -> python type with support for - missing values - """ - def __init__(self, missing='Null', missingval=None): - self.missing = missing - self.missingval = missingval - - def __call__(self, s): - if s == self.missing: - return self.missingval - return s - - def is_missing(self, s): - return not s.strip() or s == self.missing - - -@deprecated('2.1') -class tostr(converter): - """convert to string or None""" - def __init__(self, missing='Null', missingval=''): - converter.__init__(self, missing=missing, missingval=missingval) - - -@deprecated('2.1') -class todatetime(converter): - """convert to a datetime or None""" - def __init__(self, fmt='%Y-%m-%d', missing='Null', missingval=None): - 'use a :func:`time.strptime` format string for conversion' - converter.__init__(self, missing, missingval) - self.fmt = fmt - - def __call__(self, s): - if self.is_missing(s): - return self.missingval - tup = time.strptime(s, self.fmt) - return datetime.datetime(*tup[:6]) - - -@deprecated('2.1') -class todate(converter): - """convert to a date or None""" - def __init__(self, fmt='%Y-%m-%d', missing='Null', missingval=None): - """use a :func:`time.strptime` format string for conversion""" - converter.__init__(self, missing, missingval) - self.fmt = fmt - - def __call__(self, s): - if self.is_missing(s): - return self.missingval - tup = time.strptime(s, self.fmt) - return datetime.date(*tup[:3]) - - -@deprecated('2.1') -class tofloat(converter): - """convert to a float or None""" - def __init__(self, missing='Null', missingval=None): - converter.__init__(self, missing) - self.missingval = missingval - - def __call__(self, s): - if self.is_missing(s): - return self.missingval - return float(s) - - -@deprecated('2.1') -class toint(converter): - """convert to an int or None""" - def __init__(self, missing='Null', missingval=None): - converter.__init__(self, missing) - - def __call__(self, s): - if self.is_missing(s): - return self.missingval - return int(s) +def _exception_printer(exc): + traceback.print_exc() -class _BoundMethodProxy(object): +class _StrongRef: """ - Our own proxy object which enables weak references to bound and unbound - methods and arbitrary callables. Pulls information about the function, - class, and instance out of a bound method. Stores a weak reference to the - instance to support garbage collection. - - @organization: IBM Corporation - @copyright: Copyright (c) 2005, 2006 IBM Corporation - @license: The BSD License - - Minor bugfixes by Michael Droettboom + Wrapper similar to a weakref, but keeping a strong reference to the object. """ - def __init__(self, cb): - self._hash = hash(cb) - self._destroy_callbacks = [] - try: - try: - if six.PY3: - self.inst = ref(cb.__self__, self._destroy) - else: - self.inst = ref(cb.im_self, self._destroy) - except TypeError: - self.inst = None - if six.PY3: - self.func = cb.__func__ - self.klass = cb.__self__.__class__ - else: - self.func = cb.im_func - self.klass = cb.im_class - except AttributeError: - self.inst = None - self.func = cb - self.klass = None - - def add_destroy_callback(self, callback): - self._destroy_callbacks.append(_BoundMethodProxy(callback)) - - def _destroy(self, wk): - for callback in self._destroy_callbacks: - try: - callback(self) - except ReferenceError: - pass - - def __getstate__(self): - d = self.__dict__.copy() - # de-weak reference inst - inst = d['inst'] - if inst is not None: - d['inst'] = inst() - return d - - def __setstate__(self, statedict): - self.__dict__ = statedict - inst = statedict['inst'] - # turn inst back into a weakref - if inst is not None: - self.inst = ref(inst) - - def __call__(self, *args, **kwargs): - """ - Proxy for a call to the weak referenced object. Take - arbitrary params to pass to the callable. - - Raises `ReferenceError`: When the weak reference refers to - a dead object - """ - if self.inst is not None and self.inst() is None: - raise ReferenceError - elif self.inst is not None: - # build a new instance method with a strong reference to the - # instance - mtd = types.MethodType(self.func, self.inst()) + def __init__(self, obj): + self._obj = obj - else: - # not a bound method, just return the func - mtd = self.func - # invoke the callable and return the result - return mtd(*args, **kwargs) + def __call__(self): + return self._obj def __eq__(self, other): - """ - Compare the held function and instance with that held by - another proxy. - """ - try: - if self.inst is None: - return self.func == other.func and other.inst is None - else: - return self.func == other.func and self.inst() == other.inst() - except Exception: - return False - - def __ne__(self, other): - """ - Inverse of __eq__. - """ - return not self.__eq__(other) + return isinstance(other, _StrongRef) and self._obj == other._obj def __hash__(self): - return self._hash - - -def _exception_printer(exc): - traceback.print_exc() + return hash(self._obj) class CallbackRegistry(object): @@ -275,20 +107,13 @@ class CallbackRegistry(object): >>> callbacks.disconnect(id_eat) >>> callbacks.process('eat', 456) # nothing will be called - In practice, one should always disconnect all callbacks when they - are no longer needed to avoid dangling references (and thus memory - leaks). However, real code in matplotlib rarely does so, and due - to its design, it is rather difficult to place this kind of code. - To get around this, and prevent this class of memory leaks, we - instead store weak references to bound methods only, so when the - destination object needs to die, the CallbackRegistry won't keep - it alive. The Python stdlib weakref module can not create weak - references to bound methods directly, so we need to create a proxy - object to handle weak references to bound methods (or regular free - functions). This technique was shared by Peter Parente on his - `"Mindtrove" blog - `_. - + In practice, one should always disconnect all callbacks when they are + no longer needed to avoid dangling references (and thus memory leaks). + However, real code in Matplotlib rarely does so, and due to its design, + it is rather difficult to place this kind of code. To get around this, + and prevent this class of memory leaks, we instead store weak references + to bound methods only, so when the destination object needs to die, the + CallbackRegistry won't keep it alive. Parameters ---------- @@ -307,12 +132,17 @@ def handler(exc: Exception) -> None: def h(exc): traceback.print_exc() - """ + + # We maintain two mappings: + # callbacks: signal -> {cid -> callback} + # _func_cid_map: signal -> {callback -> cid} + # (actually, callbacks are weakrefs to the actual callbacks). + def __init__(self, exception_handler=_exception_printer): self.exception_handler = exception_handler - self.callbacks = dict() - self._cid = 0 + self.callbacks = {} + self._cid_gen = itertools.count() self._func_cid_map = {} # In general, callbacks may not be pickled; thus, we simply recreate an @@ -332,28 +162,26 @@ def __setstate__(self, state): def connect(self, s, func): """Register *func* to be called when signal *s* is generated. """ - self._func_cid_map.setdefault(s, WeakKeyDictionary()) - # Note proxy not needed in python 3. - # TODO rewrite this when support for python2.x gets dropped. - proxy = _BoundMethodProxy(func) + self._func_cid_map.setdefault(s, {}) + try: + proxy = WeakMethod(func, self._remove_proxy) + except TypeError: + proxy = _StrongRef(func) if proxy in self._func_cid_map[s]: return self._func_cid_map[s][proxy] - proxy.add_destroy_callback(self._remove_proxy) - self._cid += 1 - cid = self._cid + cid = next(self._cid_gen) self._func_cid_map[s][proxy] = cid - self.callbacks.setdefault(s, dict()) + self.callbacks.setdefault(s, {}) self.callbacks[s][cid] = proxy return cid def _remove_proxy(self, proxy): - for signal, proxies in list(six.iteritems(self._func_cid_map)): + for signal, proxies in list(self._func_cid_map.items()): try: del self.callbacks[signal][proxies[proxy]] except KeyError: pass - if len(self.callbacks[signal]) == 0: del self.callbacks[signal] del self._func_cid_map[signal] @@ -361,15 +189,14 @@ def _remove_proxy(self, proxy): def disconnect(self, cid): """Disconnect the callback registered with callback id *cid*. """ - for eventname, callbackd in list(six.iteritems(self.callbacks)): + for eventname, callbackd in list(self.callbacks.items()): try: del callbackd[cid] except KeyError: continue else: - for signal, functions in list( - six.iteritems(self._func_cid_map)): - for function, value in list(six.iteritems(functions)): + for signal, functions in list(self._func_cid_map.items()): + for function, value in list(functions.items()): if value == cid: del functions[function] return @@ -381,12 +208,11 @@ def process(self, s, *args, **kwargs): All of the functions registered to receive callbacks on *s* will be called with ``*args`` and ``**kwargs``. """ - if s in self.callbacks: - for cid, proxy in list(six.iteritems(self.callbacks[s])): + for cid, ref in list(self.callbacks.get(s, {}).items()): + func = ref() + if func is not None: try: - proxy(*args, **kwargs) - except ReferenceError: - self._remove_proxy(proxy) + func(*args, **kwargs) # this does not capture KeyboardInterrupt, SystemExit, # and GeneratorExit except Exception as exc: @@ -410,8 +236,7 @@ def __init__(self, type, seq=None): def __repr__(self): return '' % (len(self), self.type) - def __str__(self): - return repr(self) + __str__ = __repr__ def __getstate__(self): # store a dictionary of this SilentList's state @@ -487,7 +312,8 @@ def strip_math(s): return s -class Bunch(object): +@deprecated('3.0', alternative='types.SimpleNamespace') +class Bunch(types.SimpleNamespace): """ Often we want to just collect a bunch of stuff together, naming each item of the bunch; a dictionary's OK for that, but a small do- nothing @@ -496,22 +322,8 @@ class is even handier, and prettier to use. Whenever you want to >>> point = Bunch(datum=2, squared=4, coord=12) >>> point.datum - - By: Alex Martelli - From: https://code.activestate.com/recipes/121294/ """ - def __init__(self, **kwds): - self.__dict__.update(kwds) - - def __repr__(self): - return 'Bunch(%s)' % ', '.join( - '%s=%s' % kv for kv in six.iteritems(vars(self))) - - -@deprecated('2.1') -def unique(x): - """Return a list of unique elements of *x*""" - return list(set(x)) + pass def iterable(obj): @@ -523,30 +335,6 @@ def iterable(obj): return True -@deprecated('2.1') -def is_string_like(obj): - """Return True if *obj* looks like a string""" - # (np.str_ == np.unicode_ on Py3). - return isinstance(obj, (six.string_types, np.str_, np.unicode_)) - - -@deprecated('2.1') -def is_sequence_of_strings(obj): - """Returns true if *obj* is iterable and contains strings""" - if not iterable(obj): - return False - if is_string_like(obj) and not isinstance(obj, np.ndarray): - try: - obj = obj.values - except AttributeError: - # not pandas - return False - for o in obj: - if not is_string_like(o): - return False - return True - - def is_hashable(obj): """Returns true if *obj* can be hashed""" try: @@ -574,12 +362,7 @@ def file_requires_unicode(x): return False -@deprecated('2.1') -def is_scalar(obj): - """return true if *obj* is not string like and is not iterable""" - return not isinstance(obj, six.string_types) and not iterable(obj) - - +@deprecated('3.0', 'isinstance(..., numbers.Number)') def is_numlike(obj): """return true if *obj* looks like a number""" return isinstance(obj, (numbers.Number, np.number)) @@ -591,11 +374,9 @@ def to_filehandle(fname, flag='rU', return_opened=False, encoding=None): files is automatic, if the filename ends in .gz. *flag* is a read/write flag for :func:`file` """ - if hasattr(os, "PathLike") and isinstance(fname, os.PathLike): - return to_filehandle( - os.fspath(fname), - flag=flag, return_opened=return_opened, encoding=encoding) - if isinstance(fname, six.string_types): + if isinstance(fname, getattr(os, "PathLike", ())): + fname = os.fspath(fname) + if isinstance(fname, str): if fname.endswith('.gz'): # get rid of 'U' in flag for gzipped files. flag = flag.replace('U', '') @@ -608,7 +389,7 @@ def to_filehandle(fname, flag='rU', return_opened=False, encoding=None): flag = flag.replace('U', '') fh = bz2.BZ2File(fname, flag) else: - fh = io.open(fname, flag, encoding=encoding) + fh = open(fname, flag, encoding=encoding) opened = True elif hasattr(fname, 'seek'): fh = fname @@ -633,12 +414,12 @@ def open_file_cm(path_or_file, mode="r", encoding=None): def is_scalar_or_string(val): """Return whether the given object is a scalar or string like.""" - return isinstance(val, six.string_types) or not iterable(val) + return isinstance(val, str) or not iterable(val) def _string_to_bool(s): """Parses the string argument as a boolean""" - if not isinstance(s, six.string_types): + if not isinstance(s, str): return bool(s) warn_deprecated("2.2", "Passing one of 'on', 'true', 'off', 'false' as a " "boolean is deprecated; use an actual boolean " @@ -664,15 +445,15 @@ def get_sample_data(fname, asfileobj=True): If the filename ends in .gz, the file is implicitly ungzipped. """ - if matplotlib.rcParams['examples.directory']: + # Don't trigger deprecation warning when just fetching. + if dict.__getitem__(matplotlib.rcParams, 'examples.directory'): root = matplotlib.rcParams['examples.directory'] else: root = os.path.join(matplotlib._get_data_path(), 'sample_data') path = os.path.join(root, fname) if asfileobj: - if (os.path.splitext(fname)[-1].lower() in - ('.csv', '.xrc', '.txt')): + if os.path.splitext(fname)[-1].lower() in ['.csv', '.xrc', '.txt']: mode = 'r' else: mode = 'rb' @@ -705,153 +486,10 @@ def flatten(seq, scalarp=is_scalar_or_string): if scalarp(item) or item is None: yield item else: - for subitem in flatten(item, scalarp): - yield subitem - - -@deprecated('2.1', "sorted(..., key=itemgetter(...))") -class Sorter(object): - """ - Sort by attribute or item - - Example usage:: - - sort = Sorter() - - list = [(1, 2), (4, 8), (0, 3)] - dict = [{'a': 3, 'b': 4}, {'a': 5, 'b': 2}, {'a': 0, 'b': 0}, - {'a': 9, 'b': 9}] - - - sort(list) # default sort - sort(list, 1) # sort by index 1 - sort(dict, 'a') # sort a list of dicts by key 'a' - - """ - - def _helper(self, data, aux, inplace): - aux.sort() - result = [data[i] for junk, i in aux] - if inplace: - data[:] = result - return result - - def byItem(self, data, itemindex=None, inplace=1): - if itemindex is None: - if inplace: - data.sort() - result = data - else: - result = sorted(data) - return result - else: - aux = [(data[i][itemindex], i) for i in range(len(data))] - return self._helper(data, aux, inplace) - - def byAttribute(self, data, attributename, inplace=1): - aux = [(getattr(data[i], attributename), i) for i in range(len(data))] - return self._helper(data, aux, inplace) - - # a couple of handy synonyms - sort = byItem - __call__ = byItem - - -@deprecated('2.1') -class Xlator(dict): - """ - All-in-one multiple-string-substitution class - - Example usage:: - - text = "Larry Wall is the creator of Perl" - adict = { - "Larry Wall" : "Guido van Rossum", - "creator" : "Benevolent Dictator for Life", - "Perl" : "Python", - } - - print(multiple_replace(adict, text)) - - xlat = Xlator(adict) - print(xlat.xlat(text)) - """ - - def _make_regex(self): - """ Build re object based on the keys of the current dictionary """ - return re.compile("|".join(map(re.escape, self))) - - def __call__(self, match): - """ Handler invoked for each regex *match* """ - return self[match.group(0)] - - def xlat(self, text): - """ Translate *text*, returns the modified text. """ - return self._make_regex().sub(self, text) - - -@deprecated('2.1') -def soundex(name, len=4): - """ soundex module conforming to Odell-Russell algorithm """ - - # digits holds the soundex values for the alphabet - soundex_digits = '01230120022455012623010202' - sndx = '' - fc = '' - - # Translate letters in name to soundex digits - for c in name.upper(): - if c.isalpha(): - if not fc: - fc = c # Remember first letter - d = soundex_digits[ord(c) - ord('A')] - # Duplicate consecutive soundex digits are skipped - if not sndx or (d != sndx[-1]): - sndx += d - - # Replace first digit with first letter - sndx = fc + sndx[1:] - - # Remove all 0s from the soundex code - sndx = sndx.replace('0', '') - - # Return soundex code truncated or 0-padded to len characters - return (sndx + (len * '0'))[:len] - - -@deprecated('2.1') -class Null(object): - """ Null objects always and reliably "do nothing." """ - - def __init__(self, *args, **kwargs): - pass - - def __call__(self, *args, **kwargs): - return self - - def __str__(self): - return "Null()" - - def __repr__(self): - return "Null()" - - if six.PY3: - def __bool__(self): - return 0 - else: - def __nonzero__(self): - return 0 - - def __getattr__(self, name): - return self - - def __setattr__(self, name, value): - return self - - def __delattr__(self, name): - return self + yield from flatten(item, scalarp) +@deprecated("3.0") def mkdirs(newdir, mode=0o777): """ make directory *newdir* recursively, and set *mode*. Equivalent to :: @@ -861,16 +499,10 @@ def mkdirs(newdir, mode=0o777): """ # this functionality is now in core python as of 3.2 # LPY DROP - if six.PY3: - os.makedirs(newdir, mode=mode, exist_ok=True) - else: - try: - os.makedirs(newdir, mode=mode) - except OSError as exception: - if exception.errno != errno.EEXIST: - raise + os.makedirs(newdir, mode=mode, exist_ok=True) +@deprecated('3.0') class GetRealpathAndStat(object): def __init__(self): self._cache = {} @@ -889,92 +521,12 @@ def __call__(self, path): return result -get_realpath_and_stat = GetRealpathAndStat() - - -@deprecated('2.1') -def dict_delall(d, keys): - """delete all of the *keys* from the :class:`dict` *d*""" - for key in keys: - try: - del d[key] - except KeyError: - pass - - -@deprecated('2.1') -class RingBuffer(object): - """ class that implements a not-yet-full buffer """ - def __init__(self, size_max): - self.max = size_max - self.data = [] - - class __Full: - """ class that implements a full buffer """ - def append(self, x): - """ Append an element overwriting the oldest one. """ - self.data[self.cur] = x - self.cur = (self.cur + 1) % self.max - - def get(self): - """ return list of elements in correct order """ - return self.data[self.cur:] + self.data[:self.cur] - - def append(self, x): - """append an element at the end of the buffer""" - self.data.append(x) - if len(self.data) == self.max: - self.cur = 0 - # Permanently change self's class from non-full to full - self.__class__ = __Full - - def get(self): - """ Return a list of elements from the oldest to the newest. """ - return self.data - - def __get_item__(self, i): - return self.data[i % len(self.data)] - - -@deprecated('2.1') -def get_split_ind(seq, N): - """ - *seq* is a list of words. Return the index into seq such that:: - - len(' '.join(seq[:ind])<=N - - . - """ - - s_len = 0 - # todo: use Alex's xrange pattern from the cbook for efficiency - for (word, ind) in zip(seq, xrange(len(seq))): - s_len += len(word) + 1 # +1 to account for the len(' ') - if s_len >= N: - return ind - return len(seq) - - -@deprecated('2.1', alternative='textwrap.TextWrapper') -def wrap(prefix, text, cols): - """wrap *text* with *prefix* at length *cols*""" - pad = ' ' * len(prefix.expandtabs()) - available = cols - len(pad) - - seq = text.split(' ') - Nseq = len(seq) - ind = 0 - lines = [] - while ind < Nseq: - lastInd = ind - ind += get_split_ind(seq[ind:], available) - lines.append(seq[lastInd:ind]) - - # add the prefix to the first line, pad with spaces otherwise - ret = prefix + ' '.join(lines[0]) + '\n' - for line in lines[1:]: - ret += pad + ' '.join(line) + '\n' - return ret +@functools.lru_cache() +def get_realpath_and_stat(path): + realpath = os.path.realpath(path) + stat = os.stat(realpath) + stat_key = (stat.st_ino, stat.st_dev) + return realpath, stat_key # A regular expression used to determine the amount of space to @@ -1025,6 +577,7 @@ def dedent(s): return result +@deprecated("3.0") def listFiles(root, patterns='*', recurse=1, return_folders=0): """ Recursively list files @@ -1053,101 +606,6 @@ def listFiles(root, patterns='*', recurse=1, return_folders=0): return results -@deprecated('2.1') -def get_recursive_filelist(args): - """ - Recurse all the files and dirs in *args* ignoring symbolic links - and return the files as a list of strings - """ - files = [] - - for arg in args: - if os.path.isfile(arg): - files.append(arg) - continue - if os.path.isdir(arg): - newfiles = listFiles(arg, recurse=1, return_folders=1) - files.extend(newfiles) - - return [f for f in files if not os.path.islink(f)] - - -@deprecated('2.1') -def pieces(seq, num=2): - """Break up the *seq* into *num* tuples""" - start = 0 - while 1: - item = seq[start:start + num] - if not len(item): - break - yield item - start += num - - -@deprecated('2.1') -def exception_to_str(s=None): - if six.PY3: - sh = io.StringIO() - else: - sh = io.BytesIO() - if s is not None: - print(s, file=sh) - traceback.print_exc(file=sh) - return sh.getvalue() - - -@deprecated('2.1') -def allequal(seq): - """ - Return *True* if all elements of *seq* compare equal. If *seq* is - 0 or 1 length, return *True* - """ - if len(seq) < 2: - return True - val = seq[0] - for i in xrange(1, len(seq)): - thisval = seq[i] - if thisval != val: - return False - return True - - -@deprecated('2.1') -def alltrue(seq): - """ - Return *True* if all elements of *seq* evaluate to *True*. If - *seq* is empty, return *False*. - """ - if not len(seq): - return False - for val in seq: - if not val: - return False - return True - - -@deprecated('2.1') -def onetrue(seq): - """ - Return *True* if one element of *seq* is *True*. It *seq* is - empty, return *False*. - """ - if not len(seq): - return False - for val in seq: - if val: - return True - return False - - -@deprecated('2.1') -def allpairs(x): - """ - return all possible pairs in sequence *x* - """ - return [(s, f) for i, f in enumerate(x) for s in x[i + 1:]] - - class maxdict(dict): """ A dictionary with a maximum size; this doesn't override all the @@ -1170,9 +628,9 @@ def __setitem__(self, k, v): class Stack(object): """ - Implement a stack where elements can be pushed on and you can move - back and forth. But no pop. Should mimic home / back / forward - in a browser + Stack of elements with a movable cursor. + + Mimics home/back/forward in a web browser. """ def __init__(self, default=None): @@ -1180,62 +638,65 @@ def __init__(self, default=None): self._default = default def __call__(self): - """return the current element, or None""" + """Return the current element, or None.""" if not len(self._elements): return self._default else: return self._elements[self._pos] def __len__(self): - return self._elements.__len__() + return len(self._elements) def __getitem__(self, ind): - return self._elements.__getitem__(ind) + return self._elements[ind] def forward(self): - """move the position forward and return the current element""" - n = len(self._elements) - if self._pos < n - 1: - self._pos += 1 + """Move the position forward and return the current element.""" + self._pos = min(self._pos + 1, len(self._elements) - 1) return self() def back(self): - """move the position back and return the current element""" + """Move the position back and return the current element.""" if self._pos > 0: self._pos -= 1 return self() def push(self, o): """ - push object onto stack at current position - all elements - occurring later than the current position are discarded + Push *o* to the stack at current position. Discard all later elements. + + *o* is returned. """ - self._elements = self._elements[:self._pos + 1] - self._elements.append(o) + self._elements = self._elements[:self._pos + 1] + [o] self._pos = len(self._elements) - 1 return self() def home(self): - """push the first element onto the top of the stack""" + """ + Push the first element onto the top of the stack. + + The first element is returned. + """ if not len(self._elements): return self.push(self._elements[0]) return self() def empty(self): + """Return whether the stack is empty.""" return len(self._elements) == 0 def clear(self): - """empty the stack""" + """Empty the stack.""" self._pos = -1 self._elements = [] def bubble(self, o): """ - raise *o* to the top of the stack and return *o*. *o* must be - in the stack - """ + Raise *o* to the top of the stack. *o* must be present in the stack. + *o* is returned. + """ if o not in self._elements: raise ValueError('Unknown element o') old = self._elements[:] @@ -1251,52 +712,19 @@ def bubble(self, o): return o def remove(self, o): - 'remove element *o* from the stack' + """Remove *o* from the stack.""" if o not in self._elements: raise ValueError('Unknown element o') old = self._elements[:] self.clear() for thiso in old: - if thiso == o: - continue - else: + if thiso != o: self.push(thiso) -@deprecated('2.1') -def finddir(o, match, case=False): - """ - return all attributes of *o* which match string in match. if case - is True require an exact case match. - """ - if case: - names = [(name, name) for name in dir(o) - if isinstance(name, six.string_types)] - else: - names = [(name.lower(), name) for name in dir(o) - if isinstance(name, six.string_types)] - match = match.lower() - return [orig for name, orig in names if name.find(match) >= 0] - - -@deprecated('2.1') -def reverse_dict(d): - """reverse the dictionary -- may lose data if values are not unique!""" - return {v: k for k, v in six.iteritems(d)} - - -@deprecated('2.1') -def restrict_dict(d, keys): - """ - Return a dictionary that contains those keys that appear in both - d and keys, with values from d. - """ - return {k: v for k, v in six.iteritems(d) if k in keys} - - def report_memory(i=0): # argument may go away """return the memory consumed by process""" - from matplotlib.compat.subprocess import Popen, PIPE + from subprocess import Popen, PIPE pid = os.getpid() if sys.platform == 'sunos5': try: @@ -1307,7 +735,7 @@ def report_memory(i=0): # argument may go away "report_memory works on Sun OS only if " "the 'ps' program is found") mem = int(a2[-1].strip()) - elif sys.platform.startswith('linux'): + elif sys.platform == 'linux': try: a2 = Popen(['ps', '-p', '%d' % pid, '-o', 'rss,sz'], stdout=PIPE).stdout.readlines() @@ -1316,7 +744,7 @@ def report_memory(i=0): # argument may go away "report_memory works on Linux only if " "the 'ps' program is found") mem = int(a2[1].split()[1]) - elif sys.platform.startswith('darwin'): + elif sys.platform == 'darwin': try: a2 = Popen(['ps', '-p', '%d' % pid, '-o', 'rss,vsz'], stdout=PIPE).stdout.readlines() @@ -1325,9 +753,9 @@ def report_memory(i=0): # argument may go away "report_memory works on Mac OS only if " "the 'ps' program is found") mem = int(a2[1].split()[0]) - elif sys.platform.startswith('win'): + elif sys.platform == 'win32': try: - a2 = Popen([str("tasklist"), "/nh", "/fi", "pid eq %d" % pid], + a2 = Popen(["tasklist", "/nh", "/fi", "pid eq %d" % pid], stdout=PIPE).stdout.read() except OSError: raise NotImplementedError( @@ -1352,16 +780,6 @@ def safezip(*args): return list(zip(*args)) -@deprecated('2.1') -def issubclass_safe(x, klass): - """return issubclass(x, klass) and return False on a TypeError""" - - try: - return issubclass(x, klass) - except TypeError: - return False - - def safe_masked_invalid(x, copy=False): x = np.array(x, subok=True, copy=copy) if not x.dtype.isnative: @@ -1400,14 +818,14 @@ def print_path(path): # next "wraps around" next = path[(i + 1) % len(path)] - outstream.write(" %s -- " % str(type(step))) + outstream.write(" %s -- " % type(step)) if isinstance(step, dict): - for key, val in six.iteritems(step): + for key, val in step.items(): if val is next: - outstream.write("[%s]" % repr(key)) + outstream.write("[{!r}]".format(key)) break if key is next: - outstream.write("[key] = %s" % repr(val)) + outstream.write("[key] = {!r}".format(val)) break elif isinstance(step, list): outstream.write("[%d]" % step.index(next)) @@ -1483,17 +901,13 @@ class Grouper(object): """ def __init__(self, init=()): - mapping = self._mapping = {} - for x in init: - mapping[ref(x)] = [ref(x)] + self._mapping = {weakref.ref(x): [weakref.ref(x)] for x in init} def __contains__(self, item): - return ref(item) in self._mapping + return weakref.ref(item) in self._mapping def clean(self): - """ - Clean dead weak references from the dictionary - """ + """Clean dead weak references from the dictionary.""" mapping = self._mapping to_drop = [key for key in mapping if key() is None] for key in to_drop: @@ -1502,18 +916,14 @@ def clean(self): def join(self, a, *args): """ - Join given arguments into the same set. Accepts one or more - arguments. + Join given arguments into the same set. Accepts one or more arguments. """ mapping = self._mapping - set_a = mapping.setdefault(ref(a), [ref(a)]) + set_a = mapping.setdefault(weakref.ref(a), [weakref.ref(a)]) for arg in args: - set_b = mapping.get(ref(arg)) - if set_b is None: - set_a.append(ref(arg)) - mapping[ref(arg)] = set_a - elif set_b is not set_a: + set_b = mapping.get(weakref.ref(arg), [weakref.ref(arg)]) + if set_b is not set_a: if len(set_b) > len(set_a): set_a, set_b = set_b, set_a set_a.extend(set_b) @@ -1523,24 +933,16 @@ def join(self, a, *args): self.clean() def joined(self, a, b): - """ - Returns True if *a* and *b* are members of the same set. - """ + """Returns True if *a* and *b* are members of the same set.""" self.clean() - - mapping = self._mapping - try: - return mapping[ref(a)] is mapping[ref(b)] - except KeyError: - return False + return (self._mapping.get(weakref.ref(a), object()) + is self._mapping.get(weakref.ref(b))) def remove(self, a): self.clean() - - mapping = self._mapping - seta = mapping.pop(ref(a), None) - if seta is not None: - seta.remove(ref(a)) + set_a = self._mapping.pop(weakref.ref(a), None) + if set_a: + set_a.remove(weakref.ref(a)) def __iter__(self): """ @@ -1549,27 +951,14 @@ def __iter__(self): The iterator is invalid if interleaved with calls to join(). """ self.clean() - token = object() - - # Mark each group as we come across if by appending a token, - # and don't yield it twice - for group in six.itervalues(self._mapping): - if group[-1] is not token: - yield [x() for x in group] - group.append(token) - - # Cleanup the tokens - for group in six.itervalues(self._mapping): - if group[-1] is token: - del group[-1] + unique_groups = {id(group): group for group in self._mapping.values()} + for group in unique_groups.values(): + yield [x() for x in group] def get_siblings(self, a): - """ - Returns all of the items joined with *a*, including itself. - """ + """Returns all of the items joined with *a*, including itself.""" self.clean() - - siblings = self._mapping.get(ref(a), [ref(a)]) + siblings = self._mapping.get(weakref.ref(a), [weakref.ref(a)]) return [x() for x in siblings] @@ -1596,21 +985,6 @@ def simple_linear_interpolation(a, steps): .reshape((len(x),) + a.shape[1:])) -@deprecated('2.1', alternative='shutil.rmtree') -def recursive_remove(path): - if os.path.isdir(path): - for fname in (glob.glob(os.path.join(path, '*')) + - glob.glob(os.path.join(path, '.*'))): - if os.path.isdir(fname): - recursive_remove(fname) - os.removedirs(fname) - else: - os.remove(fname) - # os.removedirs(path) - else: - os.remove(path) - - def delete_masked_points(*args): """ Find all masked and/or non-finite points in a set of arguments, @@ -1645,14 +1019,13 @@ def delete_masked_points(*args): """ if not len(args): return () - if (isinstance(args[0], six.string_types) or not iterable(args[0])): + if is_scalar_or_string(args[0]): raise ValueError("First argument must be a sequence") nrecs = len(args[0]) margs = [] seqlist = [False] * len(args) for i, x in enumerate(args): - if (not isinstance(x, six.string_types) and iterable(x) - and len(x) == nrecs): + if not isinstance(x, str) and iterable(x) and len(x) == nrecs: seqlist[i] = True if isinstance(x, np.ma.MaskedArray): if x.ndim > 1: @@ -1728,10 +1101,9 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, dimensions of `X`. autorange : bool, optional (False) - When `True` and the data are distributed such that the 25th and - 75th percentiles are equal, ``whis`` is set to ``'range'`` such - that the whisker ends are at the minimum and maximum of the - data. + When `True` and the data are distributed such that the 25th and 75th + percentiles are equal, ``whis`` is set to ``'range'`` such that the + whisker ends are at the minimum and maximum of the data. Returns ------- @@ -1804,12 +1176,12 @@ def _compute_conf_interval(data, med, iqr, bootstrap): ncols = len(X) if labels is None: - labels = repeat(None) + labels = itertools.repeat(None) elif len(labels) != ncols: raise ValueError("Dimensions of labels and X must be compatible") input_whis = whis - for ii, (x, label) in enumerate(zip(X, labels), start=0): + for ii, (x, label) in enumerate(zip(X, labels)): # empty dict stats = {} @@ -1896,62 +1268,10 @@ def _compute_conf_interval(data, med, iqr, bootstrap): return bxpstats -# FIXME I don't think this is used anywhere -@deprecated('2.1') -def unmasked_index_ranges(mask, compressed=True): - """ - Find index ranges where *mask* is *False*. - - *mask* will be flattened if it is not already 1-D. - - Returns Nx2 :class:`numpy.ndarray` with each row the start and stop - indices for slices of the compressed :class:`numpy.ndarray` - corresponding to each of *N* uninterrupted runs of unmasked - values. If optional argument *compressed* is *False*, it returns - the start and stop indices into the original :class:`numpy.ndarray`, - not the compressed :class:`numpy.ndarray`. Returns *None* if there - are no unmasked values. - - Example:: - - y = ma.array(np.arange(5), mask = [0,0,1,0,0]) - ii = unmasked_index_ranges(ma.getmaskarray(y)) - # returns array [[0,2,] [2,4,]] - - y.compressed()[ii[1,0]:ii[1,1]] - # returns array [3,4,] - - ii = unmasked_index_ranges(ma.getmaskarray(y), compressed=False) - # returns array [[0, 2], [3, 5]] - - y.filled()[ii[1,0]:ii[1,1]] - # returns array [3,4,] - - Prior to the transforms refactoring, this was used to support - masked arrays in Line2D. - """ - mask = mask.reshape(mask.size) - m = np.concatenate(((1,), mask, (1,))) - indices = np.arange(len(mask) + 1) - mdif = m[1:] - m[:-1] - i0 = np.compress(mdif == -1, indices) - i1 = np.compress(mdif == 1, indices) - assert len(i0) == len(i1) - if len(i1) == 0: - return None # Maybe this should be np.zeros((0,2), dtype=int) - if not compressed: - return np.concatenate((i0[:, np.newaxis], i1[:, np.newaxis]), axis=1) - seglengths = i1 - i0 - breakpoints = np.cumsum(seglengths) - ic0 = np.concatenate(((0,), breakpoints[:-1])) - ic1 = breakpoints - return np.concatenate((ic0[:, np.newaxis], ic1[:, np.newaxis]), axis=1) - - # The ls_mapper maps short codes for line style to their full name used by # backends; the reverse mapper is for mapping full names to short ones. ls_mapper = {'-': 'solid', '--': 'dashed', '-.': 'dashdot', ':': 'dotted'} -ls_mapper_r = {v: k for k, v in six.iteritems(ls_mapper)} +ls_mapper_r = {v: k for k, v in ls_mapper.items()} @deprecated('2.2') @@ -2028,16 +1348,9 @@ def contiguous_regions(mask): def is_math_text(s): # Did we find an even number of non-escaped dollar signs? # If so, treat is as math text. - try: - s = six.text_type(s) - except UnicodeDecodeError: - raise ValueError( - "matplotlib display text must have all code points < 128 or use " - "Unicode strings") - + s = str(s) dollar_count = s.count(r'$') - s.count(r'\$') even_dollars = (dollar_count > 0 and dollar_count % 2 == 0) - return even_dollars @@ -2161,44 +1474,6 @@ def violin_stats(X, method, points=100): return vpstats -class _NestedClassGetter(object): - # recipe from http://stackoverflow.com/a/11493777/741316 - """ - When called with the containing class as the first argument, - and the name of the nested class as the second argument, - returns an instance of the nested class. - """ - def __call__(self, containing_class, class_name): - nested_class = getattr(containing_class, class_name) - - # make an instance of a simple object (this one will do), for which we - # can change the __class__ later on. - nested_instance = _NestedClassGetter() - - # set the class of the instance, the __init__ will never be called on - # the class but the original state will be set later on by pickle. - nested_instance.__class__ = nested_class - return nested_instance - - -class _InstanceMethodPickler(object): - """ - Pickle cannot handle instancemethod saving. _InstanceMethodPickler - provides a solution to this. - """ - def __init__(self, instancemethod): - """Takes an instancemethod as its only argument.""" - if six.PY3: - self.parent_obj = instancemethod.__self__ - self.instancemethod_name = instancemethod.__func__.__name__ - else: - self.parent_obj = instancemethod.im_self - self.instancemethod_name = instancemethod.im_func.__name__ - - def get_instancemethod(self): - return getattr(self.parent_obj, self.instancemethod_name) - - def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. @@ -2287,7 +1562,8 @@ def pts_to_midstep(x, *args): The x location of the steps. May be empty. y1, ..., yp : array - y arrays to be turned into steps; all must be the same length as ``x``. + y arrays to be turned into steps; all must be the same length as + ``x``. Returns ------- @@ -2346,7 +1622,7 @@ def index_of(y): def safe_first_element(obj): - if isinstance(obj, collections.Iterator): + if isinstance(obj, collections.abc.Iterator): # needed to accept `array.flat` as input. # np.flatiter reports as an instance of collections.Iterator # but can still be indexed via []. @@ -2363,7 +1639,8 @@ def safe_first_element(obj): def sanitize_sequence(data): """Converts dictview object to list""" - return list(data) if isinstance(data, collections.MappingView) else data + return (list(data) if isinstance(data, collections.abc.MappingView) + else data) def normalize_kwargs(kw, alias_mapping=None, required=(), forbidden=(), @@ -2419,7 +1696,7 @@ def normalize_kwargs(kw, alias_mapping=None, required=(), forbidden=(), ret = dict() # hit all alias mappings - for canonical, alias_list in six.iteritems(alias_mapping): + for canonical, alias_list in alias_mapping.items(): # the alias lists are ordered from lowest to highest priority # so we know to use the last value in this list @@ -2462,14 +1739,13 @@ def normalize_kwargs(kw, alias_mapping=None, required=(), forbidden=(), "are in kwargs".format(keys=fail_keys)) if allowed is not None: - allowed_set = set(required) | set(allowed) + allowed_set = {*required, *allowed} fail_keys = [k for k in ret if k not in allowed_set] if fail_keys: - raise TypeError("kwargs contains {keys!r} which are not in " - "the required {req!r} or " - "allowed {allow!r} keys".format( - keys=fail_keys, req=required, - allow=allowed)) + raise TypeError( + "kwargs contains {keys!r} which are not in the required " + "{req!r} or allowed {allow!r} keys".format( + keys=fail_keys, req=required, allow=allowed)) return ret @@ -2490,6 +1766,7 @@ def get_label(y, default_name): """ +@deprecated("3.0") class Locked(object): """ Context manager to handle locks. @@ -2545,260 +1822,43 @@ def __exit__(self, exc_type, exc_value, traceback): pass -class _FuncInfo(object): - """ - Class used to store a function. - +@contextlib.contextmanager +def _lock_path(path): """ + Context manager for locking a path. - def __init__(self, function, inverse, bounded_0_1=True, check_params=None): - """ - Parameters - ---------- - - function : callable - A callable implementing the function receiving the variable as - first argument and any additional parameters in a list as second - argument. - inverse : callable - A callable implementing the inverse function receiving the variable - as first argument and any additional parameters in a list as - second argument. It must satisfy 'inverse(function(x, p), p) == x'. - bounded_0_1: bool or callable - A boolean indicating whether the function is bounded in the [0,1] - interval, or a callable taking a list of values for the additional - parameters, and returning a boolean indicating whether the function - is bounded in the [0,1] interval for that combination of - parameters. Default True. - check_params: callable or None - A callable taking a list of values for the additional parameters - and returning a boolean indicating whether that combination of - parameters is valid. It is only required if the function has - additional parameters and some of them are restricted. - Default None. - - """ - - self.function = function - self.inverse = inverse - - if callable(bounded_0_1): - self._bounded_0_1 = bounded_0_1 - else: - self._bounded_0_1 = lambda x: bounded_0_1 - - if check_params is None: - self._check_params = lambda x: True - elif callable(check_params): - self._check_params = check_params - else: - raise ValueError("Invalid 'check_params' argument.") - - def is_bounded_0_1(self, params=None): - """ - Returns a boolean indicating if the function is bounded in the [0,1] - interval for a particular set of additional parameters. - - Parameters - ---------- - - params : list - The list of additional parameters. Default None. - - Returns - ------- - - out : bool - True if the function is bounded in the [0,1] interval for - parameters 'params'. Otherwise False. - - """ - - return self._bounded_0_1(params) - - def check_params(self, params=None): - """ - Returns a boolean indicating if the set of additional parameters is - valid. - - Parameters - ---------- - - params : list - The list of additional parameters. Default None. - - Returns - ------- - - out : bool - True if 'params' is a valid set of additional parameters for the - function. Otherwise False. - - """ - - return self._check_params(params) - - -class _StringFuncParser(object): - """ - A class used to convert predefined strings into - _FuncInfo objects, or to directly obtain _FuncInfo - properties. - - """ - - _funcs = {} - _funcs['linear'] = _FuncInfo(lambda x: x, - lambda x: x, - True) - _funcs['quadratic'] = _FuncInfo(np.square, - np.sqrt, - True) - _funcs['cubic'] = _FuncInfo(lambda x: x**3, - lambda x: x**(1. / 3), - True) - _funcs['sqrt'] = _FuncInfo(np.sqrt, - np.square, - True) - _funcs['cbrt'] = _FuncInfo(lambda x: x**(1. / 3), - lambda x: x**3, - True) - _funcs['log10'] = _FuncInfo(np.log10, - lambda x: (10**(x)), - False) - _funcs['log'] = _FuncInfo(np.log, - np.exp, - False) - _funcs['log2'] = _FuncInfo(np.log2, - lambda x: (2**x), - False) - _funcs['x**{p}'] = _FuncInfo(lambda x, p: x**p[0], - lambda x, p: x**(1. / p[0]), - True) - _funcs['root{p}(x)'] = _FuncInfo(lambda x, p: x**(1. / p[0]), - lambda x, p: x**p, - True) - _funcs['log{p}(x)'] = _FuncInfo(lambda x, p: (np.log(x) / - np.log(p[0])), - lambda x, p: p[0]**(x), - False, - lambda p: p[0] > 0) - _funcs['log10(x+{p})'] = _FuncInfo(lambda x, p: np.log10(x + p[0]), - lambda x, p: 10**x - p[0], - lambda p: p[0] > 0) - _funcs['log(x+{p})'] = _FuncInfo(lambda x, p: np.log(x + p[0]), - lambda x, p: np.exp(x) - p[0], - lambda p: p[0] > 0) - _funcs['log{p}(x+{p})'] = _FuncInfo(lambda x, p: (np.log(x + p[1]) / - np.log(p[0])), - lambda x, p: p[0]**(x) - p[1], - lambda p: p[1] > 0, - lambda p: p[0] > 0) - - def __init__(self, str_func): - """ - Parameters - ---------- - str_func : string - String to be parsed. - - """ - - if not isinstance(str_func, six.string_types): - raise ValueError("'%s' must be a string." % str_func) - self._str_func = six.text_type(str_func) - self._key, self._params = self._get_key_params() - self._func = self._parse_func() - - def _parse_func(self): - """ - Parses the parameters to build a new _FuncInfo object, - replacing the relevant parameters if necessary in the lambda - functions. - - """ - - func = self._funcs[self._key] - - if not self._params: - func = _FuncInfo(func.function, func.inverse, - func.is_bounded_0_1()) - else: - m = func.function - function = (lambda x, m=m: m(x, self._params)) - - m = func.inverse - inverse = (lambda x, m=m: m(x, self._params)) - - is_bounded_0_1 = func.is_bounded_0_1(self._params) - - func = _FuncInfo(function, inverse, - is_bounded_0_1) - return func - - @property - def func_info(self): - """ - Returns the _FuncInfo object. - - """ - return self._func - - @property - def function(self): - """ - Returns the callable for the direct function. - - """ - return self._func.function - - @property - def inverse(self): - """ - Returns the callable for the inverse function. - - """ - return self._func.inverse - - @property - def is_bounded_0_1(self): - """ - Returns a boolean indicating if the function is bounded - in the [0-1 interval]. - - """ - return self._func.is_bounded_0_1() + Usage:: - def _get_key_params(self): - str_func = self._str_func - # Checking if it comes with parameters - regex = r'\{(.*?)\}' - params = re.findall(regex, str_func) + with _lock_path(path): + ... - for i, param in enumerate(params): - try: - params[i] = float(param) - except ValueError: - raise ValueError("Parameter %i is '%s', which is " - "not a number." % - (i, param)) - - str_func = re.sub(regex, '{p}', str_func) + Another thread or process that attempts to lock the same path will wait + until this context manager is exited. + The lock is implemented by creating a temporary file in the parent + directory, so that directory must exist and be writable. + """ + path = Path(path) + lock_path = path.with_name(path.name + ".matplotlib-lock") + retries = 50 + sleeptime = 0.1 + for _ in range(retries): try: - func = self._funcs[str_func] - except (ValueError, KeyError): - raise ValueError("'%s' is an invalid string. The only strings " - "recognized as functions are %s." % - (str_func, list(self._funcs))) - - # Checking that the parameters are valid - if not func.check_params(params): - raise ValueError("%s are invalid values for the parameters " - "in %s." % - (params, str_func)) - - return str_func, params + with lock_path.open("xb"): + break + except FileExistsError: + time.sleep(sleeptime) + else: + raise TimeoutError("""\ +Lock error: Matplotlib failed to acquire the following lock file: + {} +This maybe due to another process holding this lock file. If you are sure no +other Matplotlib process is running, remove this file and try again.""".format( + lock_path)) + try: + yield + finally: + lock_path.unlink() def _topmost_artist( @@ -2807,9 +1867,8 @@ def _topmost_artist( """Get the topmost artist of a list. In case of a tie, return the *last* of the tied artists, as it will be - drawn on top of the others. `max` returns the first maximum in case of ties - (on Py2 this is undocumented but true), so we need to iterate over the list - in reverse order. + drawn on top of the others. `max` returns the first maximum in case of + ties, so we need to iterate over the list in reverse order. """ return _cached_max(reversed(artists)) @@ -2821,7 +1880,7 @@ def _str_equal(obj, s): because in such cases, a naive ``obj == s`` would yield an array, which cannot be used in a boolean context. """ - return isinstance(obj, six.string_types) and obj == s + return isinstance(obj, str) and obj == s def _str_lower_equal(obj, s): @@ -2831,7 +1890,92 @@ def _str_lower_equal(obj, s): because in such cases, a naive ``obj == s`` would yield an array, which cannot be used in a boolean context. """ - return isinstance(obj, six.string_types) and obj.lower() == s + return isinstance(obj, str) and obj.lower() == s + + +def _define_aliases(alias_d, cls=None): + """Class decorator for defining property aliases. + + Use as :: + + @cbook._define_aliases({"property": ["alias", ...], ...}) + class C: ... + + For each property, if the corresponding ``get_property`` is defined in the + class so far, an alias named ``get_alias`` will be defined; the same will + be done for setters. If neither the getter nor the setter exists, an + exception will be raised. + + The alias map is stored as the ``_alias_map`` attribute on the class and + can be used by `~.normalize_kwargs` (which assumes that higher priority + aliases come last). + """ + if cls is None: # Return the actual class decorator. + return functools.partial(_define_aliases, alias_d) + + def make_alias(name): # Enforce a closure over *name*. + def method(self, *args, **kwargs): + return getattr(self, name)(*args, **kwargs) + return method + + for prop, aliases in alias_d.items(): + exists = False + for prefix in ["get_", "set_"]: + if prefix + prop in vars(cls): + exists = True + for alias in aliases: + method = make_alias(prefix + prop) + method.__name__ = prefix + alias + method.__doc__ = "alias for `{}`".format(prefix + prop) + setattr(cls, prefix + alias, method) + if not exists: + raise ValueError( + "Neither getter nor setter exists for {!r}".format(prop)) + + if hasattr(cls, "_alias_map"): + # Need to decide on conflict resolution policy. + raise NotImplementedError("Parent class already defines aliases") + cls._alias_map = alias_d + return cls + + +def _array_perimeter(arr): + """ + Get the elements on the perimeter of ``arr``, + + Parameters + ---------- + arr : ndarray, shape (M, N) + The input array + + Returns + ------- + perimeter : ndarray, shape (2*(M - 1) + 2*(N - 1),) + The elements on the perimeter of the array:: + + [arr[0,0] ... arr[0,-1] ... arr[-1, -1] ... arr[-1,0] ...] + + Examples + -------- + >>> i, j = np.ogrid[:3,:4] + >>> a = i*10 + j + >>> a + array([[ 0, 1, 2, 3], + [10, 11, 12, 13], + [20, 21, 22, 23]]) + >>> _array_perimeter(a) + array([ 0, 1, 2, 3, 13, 23, 22, 21, 20, 10]) + """ + # note we use Python's half-open ranges to avoid repeating + # the corners + forward = np.s_[0:-1] # [0 ... -1) + backward = np.s_[-1:0:-1] # [-1 ... 0) + return np.concatenate(( + arr[0, forward], + arr[forward, -1], + arr[-1, backward], + arr[backward, 0], + )) @contextlib.contextmanager @@ -2850,3 +1994,84 @@ def _setattr_cm(obj, **kwargs): delattr(obj, attr) else: setattr(obj, attr, orig) + + +def _warn_external(message, category=None): + """ + `warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib". + + The original emitter of the warning can be obtained by patching this + function back to `warnings.warn`, i.e. ``cbook._warn_external = + warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``, + etc.). + """ + frame = sys._getframe() + for stacklevel in itertools.count(1): + if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.)", + frame.f_globals["__name__"]): + break + frame = frame.f_back + warnings.warn(message, category, stacklevel) + + +class _OrderedSet(collections.abc.MutableSet): + def __init__(self): + self._od = collections.OrderedDict() + + def __contains__(self, key): + return key in self._od + + def __iter__(self): + return iter(self._od) + + def __len__(self): + return len(self._od) + + def add(self, key): + self._od.pop(key, None) + self._od[key] = None + + def discard(self, key): + self._od.pop(key, None) + + +# Agg's buffers are unmultiplied RGBA8888, which neither PyQt4 nor cairo +# support; however, both do support premultiplied ARGB32. + + +def _premultiplied_argb32_to_unmultiplied_rgba8888(buf): + """ + Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer. + """ + rgba = np.take( # .take() ensures C-contiguity of the result. + buf, + [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2) + rgb = rgba[..., :-1] + alpha = rgba[..., -1] + # Un-premultiply alpha. The formula is the same as in cairo-png.c. + mask = alpha != 0 + for channel in np.rollaxis(rgb, -1): + channel[mask] = ( + (channel[mask].astype(int) * 255 + alpha[mask] // 2) + // alpha[mask]) + return rgba + + +def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888): + """ + Convert an unmultiplied RGBA8888 buffer to a premultiplied ARGB32 buffer. + """ + if sys.byteorder == "little": + argb32 = np.take(rgba8888, [2, 1, 0, 3], axis=2) + rgb24 = argb32[..., :-1] + alpha8 = argb32[..., -1:] + else: + argb32 = np.take(rgba8888, [3, 0, 1, 2], axis=2) + alpha8 = argb32[..., :1] + rgb24 = argb32[..., 1:] + # Only bother premultiplying when the alpha channel is not fully opaque, + # as the cost is not negligible. The unsafe cast is needed to do the + # multiplication in-place in an integer buffer. + if alpha8.min() != 0xff: + np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe") + return argb32 diff --git a/lib/matplotlib/cbook/_backports.py b/lib/matplotlib/cbook/_backports.py deleted file mode 100644 index 83833258551c..000000000000 --- a/lib/matplotlib/cbook/_backports.py +++ /dev/null @@ -1,147 +0,0 @@ -from __future__ import absolute_import - -import os -import sys - -import numpy as np - - -# Copy-pasted from Python 3.4's shutil. -def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - - """ - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) - and not os.path.isdir(fn)) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if not os.curdir in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if not normdir in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -# Copy-pasted from numpy.lib.stride_tricks 1.11.2. -def _maybe_view_as_subclass(original_array, new_array): - if type(original_array) is not type(new_array): - # if input was an ndarray subclass and subclasses were OK, - # then view the result as that subclass. - new_array = new_array.view(type=type(original_array)) - # Since we have done something akin to a view from original_array, we - # should let the subclass finalize (if it has it implemented, i.e., is - # not None). - if new_array.__array_finalize__: - new_array.__array_finalize__(original_array) - return new_array - - -# Copy-pasted from numpy.lib.stride_tricks 1.11.2. -def _broadcast_to(array, shape, subok, readonly): - shape = tuple(shape) if np.iterable(shape) else (shape,) - array = np.array(array, copy=False, subok=subok) - if not shape and array.shape: - raise ValueError('cannot broadcast a non-scalar to a scalar array') - if any(size < 0 for size in shape): - raise ValueError('all elements of broadcast shape must be non-' - 'negative') - needs_writeable = not readonly and array.flags.writeable - extras = ['reduce_ok'] if needs_writeable else [] - op_flag = 'readwrite' if needs_writeable else 'readonly' - broadcast = np.nditer( - (array,), flags=['multi_index', 'refs_ok', 'zerosize_ok'] + extras, - op_flags=[op_flag], itershape=shape, order='C').itviews[0] - result = _maybe_view_as_subclass(array, broadcast) - if needs_writeable and not result.flags.writeable: - result.flags.writeable = True - return result - - -# Copy-pasted from numpy.lib.stride_tricks 1.11.2. -def broadcast_to(array, shape, subok=False): - """Broadcast an array to a new shape. - - Parameters - ---------- - array : array_like - The array to broadcast. - shape : tuple - The shape of the desired array. - subok : bool, optional - If True, then sub-classes will be passed-through, otherwise - the returned array will be forced to be a base-class array (default). - - Returns - ------- - broadcast : array - A readonly view on the original array with the given shape. It is - typically not contiguous. Furthermore, more than one element of a - broadcasted array may refer to a single memory location. - - Raises - ------ - ValueError - If the array is not compatible with the new shape according to NumPy's - broadcasting rules. - - Notes - ----- - .. versionadded:: 1.10.0 - - Examples - -------- - >>> x = np.array([1, 2, 3]) - >>> np.broadcast_to(x, (3, 3)) - array([[1, 2, 3], - [1, 2, 3], - [1, 2, 3]]) - """ - return _broadcast_to(array, shape, subok=subok, readonly=True) diff --git a/lib/matplotlib/cbook/deprecation.py b/lib/matplotlib/cbook/deprecation.py index ca7ae333f272..b93a5ca94f8f 100644 --- a/lib/matplotlib/cbook/deprecation.py +++ b/lib/matplotlib/cbook/deprecation.py @@ -14,51 +14,48 @@ class MatplotlibDeprecationWarning(UserWarning): https://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x """ - pass mplDeprecation = MatplotlibDeprecationWarning +"""mplDeprecation is deprecated. Use MatplotlibDeprecationWarning instead.""" -def _generate_deprecation_message(since, message='', name='', - alternative='', pending=False, - obj_type='attribute', - addendum=''): - - if not message: +def _generate_deprecation_message( + since, message='', name='', alternative='', pending=False, + obj_type='attribute', addendum='', *, removal=''): + if removal == "": + removal = {"2.2": "in 3.1", "3.0": "in 3.2"}.get( + since, "two minor releases later") + elif removal: if pending: - message = ( - 'The %(name)s %(obj_type)s will be deprecated in a ' - 'future version.') - else: - message = ( - 'The %(name)s %(obj_type)s was deprecated in version ' - '%(since)s.') - - altmessage = '' - if alternative: - altmessage = ' Use %s instead.' % alternative - - message = ((message % { - 'func': name, - 'name': name, - 'alternative': alternative, - 'obj_type': obj_type, - 'since': since}) + - altmessage) - - if addendum: - message += addendum + raise ValueError( + "A pending deprecation cannot have a scheduled removal") + removal = "in {}".format(removal) - return message + if not message: + message = ( + "The %(name)s %(obj_type)s" + + (" will be deprecated in a future version" + if pending else + (" was deprecated in Matplotlib %(since)s" + + (" and will be removed %(removal)s" + if removal else + ""))) + + "." + + (" Use %(alternative)s instead." if alternative else "") + + (" %(addendum)s" if addendum else "")) + + return message % dict( + func=name, name=name, obj_type=obj_type, since=since, removal=removal, + alternative=alternative, addendum=addendum) def warn_deprecated( since, message='', name='', alternative='', pending=False, - obj_type='attribute', addendum=''): + obj_type='attribute', addendum='', *, removal=''): """ - Used to display deprecation warning in a standard way. + Used to display deprecation in a standard way. Parameters ---------- @@ -77,13 +74,19 @@ def warn_deprecated( The name of the deprecated object. alternative : str, optional - An alternative function that the user may use in place of the - deprecated function. The deprecation warning will tell the user - about this alternative if provided. + An alternative API that the user may use in place of the deprecated + API. The deprecation warning will tell the user about this alternative + if provided. pending : bool, optional If True, uses a PendingDeprecationWarning instead of a - DeprecationWarning. + DeprecationWarning. Cannot be used together with *removal*. + + removal : str, optional + The expected removal version. With the default (an empty string), a + removal version is automatically computed from *since*. Set to other + Falsy values to not schedule a removal date. Cannot be used together + with *pending*. obj_type : str, optional The object type being deprecated. @@ -101,14 +104,16 @@ def warn_deprecated( obj_type='module') """ - message = _generate_deprecation_message( - since, message, name, alternative, pending, obj_type) - - warnings.warn(message, mplDeprecation, stacklevel=1) + message = '\n' + _generate_deprecation_message( + since, message, name, alternative, pending, obj_type, addendum, + removal=removal) + category = (PendingDeprecationWarning if pending + else MatplotlibDeprecationWarning) + warnings.warn(message, category, stacklevel=2) def deprecated(since, message='', name='', alternative='', pending=False, - obj_type=None, addendum=''): + obj_type=None, addendum='', *, removal=''): """ Decorator to mark a function or a class as deprecated. @@ -123,8 +128,7 @@ def deprecated(since, message='', name='', alternative='', pending=False, specifier `%(name)s` may be used for the name of the object, and `%(alternative)s` may be used in the deprecation message to insert the name of an alternative to the deprecated - object. `%(obj_type)s` may be used to insert a friendly name - for the type of object being deprecated. + object. name : str, optional The name of the deprecated object; if not provided the name @@ -138,13 +142,19 @@ def new_function(): oldFunction = new_function alternative : str, optional - An alternative object that the user may use in place of the - deprecated object. The deprecation warning will tell the user - about this alternative if provided. + An alternative API that the user may use in place of the deprecated + API. The deprecation warning will tell the user about this alternative + if provided. pending : bool, optional If True, uses a PendingDeprecationWarning instead of a - DeprecationWarning. + DeprecationWarning. Cannot be used together with *removal*. + + removal : str, optional + The expected removal version. With the default (an empty string), a + removal version is automatically computed from *since*. Set to other + Falsy values to not schedule a removal date. Cannot be used together + with *pending*. addendum : str, optional Additional text appended directly to the final message. @@ -157,9 +167,14 @@ def new_function(): @deprecated('1.4.0') def the_function_to_deprecate(): pass - """ + if obj_type is not None: + warn_deprecated( + "3.0", "Passing 'obj_type' to the 'deprecated' decorator has no " + "effect, and is deprecated since Matplotlib %(since)s; support " + "for it will be removed %(removal)s.") + def deprecate(obj, message=message, name=name, alternative=alternative, pending=pending, addendum=addendum): @@ -172,12 +187,7 @@ def deprecate(obj, message=message, name=name, alternative=alternative, func = obj.__init__ def finalize(wrapper, new_doc): - try: - obj.__doc__ = new_doc - except (AttributeError, TypeError): - # cls.__doc__ is not writeable on Py2. - # TypeError occurs on PyPy - pass + obj.__doc__ = new_doc obj.__init__ = wrapper return obj else: @@ -200,11 +210,13 @@ def finalize(wrapper, new_doc): return wrapper message = _generate_deprecation_message( - since, message, name, alternative, pending, - obj_type, addendum) + since, message, name, alternative, pending, obj_type, addendum, + removal=removal) + category = (PendingDeprecationWarning if pending + else MatplotlibDeprecationWarning) def wrapper(*args, **kwargs): - warnings.warn(message, mplDeprecation, stacklevel=2) + warnings.warn(message, category, stacklevel=2) return func(*args, **kwargs) old_doc = textwrap.dedent(old_doc or '').strip('\n') diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index e4e3dca5025f..20dbbcbcc84b 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -4,13 +4,12 @@ See :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps. See :doc:`/tutorials/colors/colormaps` for an in-depth discussion of colormaps. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six +import functools import numpy as np from numpy import ma + import matplotlib as mpl import matplotlib.colors as colors import matplotlib.cbook as cbook @@ -25,16 +24,18 @@ # reversed colormaps have '_r' appended to the name. -def _reverser(f): - def freversed(x): - return f(1 - x) - return freversed +def _reverser(f, x=None): + """Helper such that ``_reverser(f)(x) == f(1 - x)``.""" + if x is None: + # Returning a partial object keeps it picklable. + return functools.partial(_reverser, f) + return f(1 - x) def revcmap(data): """Can only handle specification *data* in dictionary format.""" data_r = {} - for key, val in six.iteritems(data): + for key, val in data.items(): if callable(val): valnew = _reverser(val) # This doesn't work: lambda x: val(1-x) @@ -83,7 +84,7 @@ def _generate_cmap(name, lutsize): # Generate the reversed specifications (all at once, to avoid # modify-when-iterating). datad.update({cmapname + '_r': _reverse_cmap_spec(spec) - for cmapname, spec in six.iteritems(datad)}) + for cmapname, spec in datad.items()}) # Precache the cmaps with ``lutsize = LUTSIZE``. # Also add the reversed ones added in the section above: @@ -123,7 +124,7 @@ def register_cmap(name=None, cmap=None, data=None, lut=None): except AttributeError: raise ValueError("Arguments must include a name or a Colormap") - if not isinstance(name, six.string_types): + if not isinstance(name, str): raise ValueError("Colormap name must be a string") if isinstance(cmap, colors.Colormap): @@ -278,8 +279,6 @@ def to_rgba(self, x, alpha=None, bytes=False, norm=True): def set_array(self, A): """Set the image array from numpy array *A*. - .. ACCEPTS: ndarray - Parameters ---------- A : ndarray @@ -323,7 +322,9 @@ def set_cmap(self, cmap): """ set the colormap for luminance data - ACCEPTS: a colormap or registered colormap name + Parameters + ---------- + cmap : colormap or registered colormap name """ cmap = get_cmap(cmap) self.cmap = cmap @@ -332,8 +333,6 @@ def set_cmap(self, cmap): def set_norm(self, norm): """Set the normalization instance. - .. ACCEPTS: `.Normalize` - Parameters ---------- norm : `.Normalize` diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 9e124cdf479d..674167c3057f 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -8,20 +8,13 @@ they are meant to be fast for common use cases (e.g., a large set of solid line segemnts) """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +import math +from numbers import Number import warnings -import six -from six.moves import zip -try: - from math import gcd -except ImportError: - # LPy workaround - from fractions import gcd - import numpy as np + import matplotlib as mpl from . import (_path, artist, cbook, cm, colors as mcolors, docstring, lines as mlines, path as mpath, transforms) @@ -29,10 +22,13 @@ CIRCLE_AREA_FACTOR = 1.0 / np.sqrt(np.pi) -_color_aliases = {'facecolors': ['facecolor'], - 'edgecolors': ['edgecolor']} - - +@cbook._define_aliases({ + "antialiased": ["antialiaseds"], + "edgecolor": ["edgecolors"], + "facecolor": ["facecolors"], + "linestyle": ["linestyles", "dashes"], + "linewidth": ["linewidths", "lw"], +}) class Collection(artist.Artist, cm.ScalarMappable): """ Base class for Collections. Must be subclassed to be usable. @@ -308,8 +304,7 @@ def draw(self, renderer): combined_transform = transform extents = paths[0].get_extents(combined_transform) width, height = renderer.get_canvas_width_height() - if (extents.width < width and - extents.height < height): + if extents.width < width and extents.height < height: do_single_path_optimization = True if self._joinstyle: @@ -343,8 +338,6 @@ def draw(self, renderer): def set_pickradius(self, pr): """Set the pick radius used for containment tests. - .. ACCEPTS: float distance in points - Parameters ---------- d : float @@ -370,7 +363,7 @@ def contains(self, mouseevent): pickradius = ( float(self._picker) - if cbook.is_numlike(self._picker) and + if isinstance(self._picker, Number) and self._picker is not True # the bool, not just nonzero or 1 else self._pickradius) @@ -389,7 +382,6 @@ def set_urls(self, urls): Parameters ---------- urls : List[str] or None - .. ACCEPTS: List[str] or None """ self._urls = urls if urls is not None else [None] self.stale = True @@ -425,7 +417,9 @@ def set_hatch(self, hatch): can only be specified for the collection as a whole, not separately for each member. - ACCEPTS: [ '/' | '\\' | '|' | '-' | '+' | 'x' | 'o' | 'O' | '.' | '*' ] + Parameters + ---------- + hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} """ self._hatch = hatch self.stale = True @@ -439,7 +433,9 @@ def set_offsets(self, offsets): Set the offsets for the collection. *offsets* can be a scalar or a sequence. - ACCEPTS: float or sequence of floats + Parameters + ---------- + offsets : float or sequence of floats """ offsets = np.asanyarray(offsets, float) if offsets.shape == (2,): # Broadcast (2,) -> (1, 2) but nothing else. @@ -467,7 +463,9 @@ def set_offset_position(self, offset_position): If offset_position is 'data', the offset is applied before the master transform, i.e., the offsets are in data coordinates. - .. ACCEPTS: [ 'screen' | 'data' ] + Parameters + ---------- + offset_position : {'screen', 'data'} """ if offset_position not in ('screen', 'data'): raise ValueError("offset_position must be 'screen' or 'data'") @@ -491,7 +489,9 @@ def set_linewidth(self, lw): or a sequence; if it is a sequence the patches will cycle through the sequence - ACCEPTS: float or sequence of floats + Parameters + ---------- + lw : float or sequence of floats """ if lw is None: lw = mpl.rcParams['patch.linewidth'] @@ -505,14 +505,6 @@ def set_linewidth(self, lw): self._us_lw, self._us_linestyles) self.stale = True - def set_linewidths(self, lw): - """alias for set_linewidth""" - return self.set_linewidth(lw) - - def set_lw(self, lw): - """alias for set_linewidth""" - return self.set_linewidth(lw) - def set_linestyle(self, ls): """ Set the linestyle(s) for the collection. @@ -530,21 +522,15 @@ def set_linestyle(self, ls): (offset, onoffseq), - where ``onoffseq`` is an even length tuple of on and off ink - in points. - - ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | - (offset, on-off-dash-seq) | - ``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | - ``' '`` | ``''``] + where ``onoffseq`` is an even length tuple of on and off ink in points. Parameters ---------- - ls : { '-', '--', '-.', ':'} and more see description + ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} The line style. """ try: - if isinstance(ls, six.string_types): + if isinstance(ls, str): ls = cbook.ls_mapper.get(ls, ls) dashes = [mlines._get_dash_pattern(ls)] else: @@ -571,7 +557,7 @@ def set_capstyle(self, cs): Parameters ---------- - cs : ['butt' | 'round' | 'projecting'] + cs : {'butt', 'round', 'projecting'} The capstyle """ if cs in ('butt', 'round', 'projecting'): @@ -589,7 +575,7 @@ def set_joinstyle(self, js): Parameters ---------- - js : ['miter' | 'round' | 'bevel'] + js : {'miter', 'round', 'bevel'} The joinstyle """ if js in ('miter', 'round', 'bevel'): @@ -630,9 +616,9 @@ def _bcast_lwls(linewidths, dashes): if len(dashes) != len(linewidths): l_dashes = len(dashes) l_lw = len(linewidths) - GCD = gcd(l_dashes, l_lw) - dashes = list(dashes) * (l_lw // GCD) - linewidths = list(linewidths) * (l_dashes // GCD) + gcd = math.gcd(l_dashes, l_lw) + dashes = list(dashes) * (l_lw // gcd) + linewidths = list(linewidths) * (l_dashes // gcd) # scale the dash patters dashes = [mlines._scale_dashes(o, d, lw) @@ -640,39 +626,31 @@ def _bcast_lwls(linewidths, dashes): return linewidths, dashes - def set_linestyles(self, ls): - """alias for set_linestyle""" - return self.set_linestyle(ls) - - def set_dashes(self, ls): - """alias for set_linestyle""" - return self.set_linestyle(ls) - def set_antialiased(self, aa): """ Set the antialiasing state for rendering. - ACCEPTS: Boolean or sequence of booleans + Parameters + ---------- + aa : bool or sequence of bools """ if aa is None: aa = mpl.rcParams['patch.antialiased'] self._antialiaseds = np.atleast_1d(np.asarray(aa, bool)) self.stale = True - def set_antialiaseds(self, aa): - """alias for set_antialiased""" - return self.set_antialiased(aa) - def set_color(self, c): """ Set both the edgecolor and the facecolor. - ACCEPTS: matplotlib color arg or sequence of rgba tuples - .. seealso:: :meth:`set_facecolor`, :meth:`set_edgecolor` For setting the edge or face color individually. + + Parameters + ---------- + c : matplotlib color arg or sequence of rgba tuples """ self.set_facecolor(c) self.set_edgecolor(c) @@ -699,26 +677,21 @@ def set_facecolor(self, c): If *c* is 'none', the patch will not be filled. - ACCEPTS: matplotlib color spec or sequence of specs + Parameters + ---------- + c : color or sequence of colors """ self._original_facecolor = c self._set_facecolor(c) - def set_facecolors(self, c): - """alias for set_facecolor""" - return self.set_facecolor(c) - def get_facecolor(self): return self._facecolors - get_facecolors = get_facecolor def get_edgecolor(self): - if (isinstance(self._edgecolors, six.string_types) - and self._edgecolors == str('face')): + if cbook._str_equal(self._edgecolors, 'face'): return self.get_facecolors() else: return self._edgecolors - get_edgecolors = get_edgecolor def _set_edgecolor(self, c): set_hatch_color = True @@ -759,21 +732,21 @@ def set_edgecolor(self, c): the face color. If it is 'none', the patch boundary will not be drawn. - ACCEPTS: matplotlib color spec or sequence of specs + Parameters + ---------- + c : color or sequence of colors """ self._original_edgecolor = c self._set_edgecolor(c) - def set_edgecolors(self, c): - """alias for set_edgecolor""" - return self.set_edgecolor(c) - def set_alpha(self, alpha): """ Set the alpha tranparencies of the collection. *alpha* must be a float or *None*. - ACCEPTS: float or None + Parameters + ---------- + alpha : float or None """ if alpha is not None: try: @@ -785,13 +758,11 @@ def set_alpha(self, alpha): self._set_facecolor(self._original_facecolor) self._set_edgecolor(self._original_edgecolor) - def get_linewidths(self): + def get_linewidth(self): return self._linewidths - get_linewidth = get_linewidths - def get_linestyles(self): + def get_linestyle(self): return self._linestyles - get_dashes = get_linestyle = get_linestyles def update_scalarmappable(self): """ @@ -836,6 +807,7 @@ def update_from(self, other): # self.update_dict = other.update_dict # do we need to copy this? -JJL self.stale = True + # these are not available for the object inspector until after the # class is built so we define an initial set here for the init # function and they will be overridden after object defn @@ -992,7 +964,7 @@ def set_verts(self, verts, closed=True): def set_verts_and_codes(self, verts, codes): '''This allows one to initialize vertices with path codes.''' - if (len(verts) != len(codes)): + if len(verts) != len(codes): raise ValueError("'codes' must be a 1D list or array " "with the same length of 'verts'") self._paths = [] @@ -1076,7 +1048,7 @@ def __init__(self, %(Collection)s - Example: see :file:`examples/dynamic_collection.py` for + Example: see :doc:`/gallery/event_handling/lasso_demo` for a complete example:: offsets = np.random.rand(20,2) @@ -1086,11 +1058,11 @@ def __init__(self, collection = RegularPolyCollection( numsides=5, # a pentagon rotation=0, sizes=(50,), - facecolors = facecolors, - edgecolors = (black,), - linewidths = (1,), - offsets = offsets, - transOffset = ax.transData, + facecolors=facecolors, + edgecolors=(black,), + linewidths=(1,), + offsets=offsets, + transOffset=ax.transData, ) """ Collection.__init__(self, **kwargs) @@ -1556,17 +1528,11 @@ def set_lineoffset(self, lineoffset): self._lineoffset = lineoffset def get_linewidth(self): - ''' - get the width of the lines used to mark each event - ''' - return self.get_linewidths()[0] + """Get the width of the lines used to mark each event.""" + return super(EventCollection, self).get_linewidth()[0] - def get_linestyle(self): - ''' - get the style of the lines used to mark each event - [ 'solid' | 'dashed' | 'dashdot' | 'dotted' ] - ''' - return self.get_linestyles() + def get_linewidths(self): + return super(EventCollection, self).get_linewidth() def get_color(self): ''' @@ -1602,28 +1568,32 @@ class EllipseCollection(Collection): @docstring.dedent_interpd def __init__(self, widths, heights, angles, units='points', **kwargs): """ - *widths*: sequence - lengths of first axes (e.g., major axis lengths) + Parameters + ---------- + widths : array-like + The lengths of the first axes (e.g., major axis lengths). - *heights*: sequence - lengths of second axes + heights : array-like + The lengths of second axes. - *angles*: sequence - angles of first axes, degrees CCW from the X-axis + angles : array-like + The angles of the first axes, degrees CCW from the x-axis. - *units*: ['points' | 'inches' | 'dots' | 'width' | 'height' - | 'x' | 'y' | 'xy'] + units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'} - units in which majors and minors are given; 'width' and + The units in which majors and minors are given; 'width' and 'height' refer to the dimensions of the axes, while 'x' and 'y' refer to the *offsets* data units. 'xy' differs from all others in that the angle as plotted varies with the aspect ratio, and equals the specified angle only when the aspect ratio is unity. Hence it behaves the same as the :class:`~matplotlib.patches.Ellipse` with - axes.transData as its transform. + ``axes.transData`` as its transform. - Additional kwargs inherited from the base :class:`Collection`: + Other Parameters + ---------------- + **kwargs + Additional kwargs inherited from the base :class:`Collection`. %(Collection)s """ @@ -1779,11 +1749,9 @@ def convert_mesh_to_paths(tri): This function is primarily of use to backend implementers. """ - Path = mpath.Path triangles = tri.get_masked_triangles() - verts = np.concatenate((tri.x[triangles][..., np.newaxis], - tri.y[triangles][..., np.newaxis]), axis=2) - return [Path(x) for x in verts] + verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1) + return [mpath.Path(x) for x in verts] @artist.allow_rasterization def draw(self, renderer): @@ -1796,8 +1764,7 @@ def draw(self, renderer): tri = self._triangulation triangles = tri.get_masked_triangles() - verts = np.concatenate((tri.x[triangles][..., np.newaxis], - tri.y[triangles][..., np.newaxis]), axis=2) + verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1) self.update_scalarmappable() colors = self._facecolors[triangles] @@ -1878,22 +1845,19 @@ def convert_mesh_to_paths(meshWidth, meshHeight, coordinates): This function is primarily of use to backend implementers. """ - Path = mpath.Path - if isinstance(coordinates, np.ma.MaskedArray): c = coordinates.data else: c = coordinates - points = np.concatenate(( - c[0:-1, 0:-1], - c[0:-1, 1:], + c[:-1, :-1], + c[:-1, 1:], c[1:, 1:], - c[1:, 0:-1], - c[0:-1, 0:-1] + c[1:, :-1], + c[:-1, :-1] ), axis=2) points = points.reshape((meshWidth * meshHeight, 5, 2)) - return [Path(x) for x in points] + return [mpath.Path(x) for x in points] def convert_mesh_to_triangles(self, meshWidth, meshHeight, coordinates): """ diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 80664a99f939..3df090452a02 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -18,12 +18,8 @@ is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`. ''' -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange, zip +import logging import warnings import numpy as np @@ -44,6 +40,8 @@ import matplotlib._constrained_layout as constrained_layout from matplotlib import docstring +_log = logging.getLogger(__name__) + make_axes_kw_doc = ''' ============= ==================================================== @@ -217,6 +215,91 @@ def _set_ticks_on_axis_warn(*args, **kw): warnings.warn("Use the colorbar set_ticks() method instead.") +class _ColorbarAutoLocator(ticker.MaxNLocator): + """ + AutoLocator for Colorbar + + This locator is just a `.MaxNLocator` except the min and max are + clipped by the norm's min and max (i.e. vmin/vmax from the + image/pcolor/contour object). This is necessary so ticks don't + extrude into the "extend regions". + """ + + def __init__(self, colorbar): + """ + This ticker needs to know the *colorbar* so that it can access + its *vmin* and *vmax*. Otherwise it is the same as + `~.ticker.AutoLocator`. + """ + + self._colorbar = colorbar + nbins = 'auto' + steps = [1, 2, 2.5, 5, 10] + ticker.MaxNLocator.__init__(self, nbins=nbins, steps=steps) + + def tick_values(self, vmin, vmax): + vmin = max(vmin, self._colorbar.norm.vmin) + vmax = min(vmax, self._colorbar.norm.vmax) + ticks = ticker.MaxNLocator.tick_values(self, vmin, vmax) + return ticks[(ticks >= vmin) & (ticks <= vmax)] + + +class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator): + """ + AutoMinorLocator for Colorbar + + This locator is just a `.AutoMinorLocator` except the min and max are + clipped by the norm's min and max (i.e. vmin/vmax from the + image/pcolor/contour object). This is necessary so that the minorticks + don't extrude into the "extend regions". + """ + + def __init__(self, colorbar, n=None): + """ + This ticker needs to know the *colorbar* so that it can access + its *vmin* and *vmax*. + """ + self._colorbar = colorbar + self.ndivs = n + ticker.AutoMinorLocator.__init__(self, n=None) + + def __call__(self): + vmin = self._colorbar.norm.vmin + vmax = self._colorbar.norm.vmax + ticks = ticker.AutoMinorLocator.__call__(self) + rtol = (vmax - vmin) * 1e-10 + return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)] + + +class _ColorbarLogLocator(ticker.LogLocator): + """ + LogLocator for Colorbarbar + + This locator is just a `.LogLocator` except the min and max are + clipped by the norm's min and max (i.e. vmin/vmax from the + image/pcolor/contour object). This is necessary so ticks don't + extrude into the "extend regions". + + """ + def __init__(self, colorbar, *args, **kwargs): + """ + _ColorbarLogLocator(colorbar, *args, **kwargs) + + This ticker needs to know the *colorbar* so that it can access + its *vmin* and *vmax*. Otherwise it is the same as + `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the + same as `~.ticker.LogLocator`. + """ + self._colorbar = colorbar + ticker.LogLocator.__init__(self, *args, **kwargs) + + def tick_values(self, vmin, vmax): + vmin = self._colorbar.norm.vmin + vmax = self._colorbar.norm.vmax + ticks = ticker.LogLocator.tick_values(self, vmin, vmax) + return ticks[(ticks >= vmin) & (ticks <= vmax)] + + class ColorbarBase(cm.ScalarMappable): ''' Draw a colorbar in an existing axes. @@ -318,7 +401,7 @@ def __init__(self, ax, cmap=None, linthresh=self.norm.linthresh) else: self.formatter = ticker.ScalarFormatter() - elif isinstance(format, six.string_types): + elif isinstance(format, str): self.formatter = ticker.FormatStrFormatter(format) else: self.formatter = format # Assume it is a Formatter @@ -346,8 +429,15 @@ def draw_all(self): and do all the drawing. ''' + # sets self._boundaries and self._values in real data units. + # takes into account extend values: self._process_values() + # sets self.vmin and vmax in data units, but just for + # the part of the colorbar that is not part of the extend + # patch: self._find_range() + # returns the X and Y mesh, *but* this was/is in normalized + # units: X, Y = self._mesh() C = self._values[:, np.newaxis] self._config_axes(X, Y) @@ -356,35 +446,105 @@ def draw_all(self): def config_axis(self): ax = self.ax + if (isinstance(self.norm, colors.LogNorm) + and self._use_auto_colorbar_locator()): + # *both* axes are made log so that determining the + # mid point is easier. + ax.set_xscale('log') + ax.set_yscale('log') + if self.orientation == 'vertical': - ax.xaxis.set_ticks([]) - # location is either one of 'bottom' or 'top' - ax.yaxis.set_label_position(self.ticklocation) - ax.yaxis.set_ticks_position(self.ticklocation) + long_axis, short_axis = ax.yaxis, ax.xaxis else: - ax.yaxis.set_ticks([]) - # location is either one of 'left' or 'right' - ax.xaxis.set_label_position(self.ticklocation) - ax.xaxis.set_ticks_position(self.ticklocation) + long_axis, short_axis = ax.xaxis, ax.yaxis + + long_axis.set_label_position(self.ticklocation) + long_axis.set_ticks_position(self.ticklocation) + short_axis.set_ticks([]) + short_axis.set_ticks([], minor=True) self._set_label() + def _get_ticker_locator_formatter(self): + """ + This code looks at the norm being used by the colorbar + and decides what locator and formatter to use. If ``locator`` has + already been set by hand, it just returns + ``self.locator, self.formatter``. + """ + locator = self.locator + formatter = self.formatter + if locator is None: + if self.boundaries is None: + if isinstance(self.norm, colors.NoNorm): + nv = len(self._values) + base = 1 + int(nv / 10) + locator = ticker.IndexLocator(base=base, offset=0) + elif isinstance(self.norm, colors.BoundaryNorm): + b = self.norm.boundaries + locator = ticker.FixedLocator(b, nbins=10) + elif isinstance(self.norm, colors.LogNorm): + locator = _ColorbarLogLocator(self) + elif isinstance(self.norm, colors.SymLogNorm): + # The subs setting here should be replaced + # by logic in the locator. + locator = ticker.SymmetricalLogLocator( + subs=np.arange(1, 10), + linthresh=self.norm.linthresh, + base=10) + else: + if mpl.rcParams['_internal.classic_mode']: + locator = ticker.MaxNLocator() + else: + locator = _ColorbarAutoLocator(self) + else: + b = self._boundaries[self._inside] + locator = ticker.FixedLocator(b, nbins=10) + _log.debug('locator: %r', locator) + return locator, formatter + + def _use_auto_colorbar_locator(self): + """ + Return if we should use an adjustable tick locator or a fixed + one. (check is used twice so factored out here...) + """ + return (self.boundaries is None + and self.values is None + and ((type(self.norm) == colors.Normalize) + or (type(self.norm) == colors.LogNorm))) + def update_ticks(self): """ Force the update of the ticks and ticklabels. This must be called whenever the tick locator and/or tick formatter changes. """ ax = self.ax - ticks, ticklabels, offset_string = self._ticker() - if self.orientation == 'vertical': - ax.yaxis.set_ticks(ticks) - ax.set_yticklabels(ticklabels) - ax.yaxis.get_major_formatter().set_offset_string(offset_string) + # get the locator and formatter. Defaults to + # self.locator if not None.. + locator, formatter = self._get_ticker_locator_formatter() + if self.orientation == 'vertical': + long_axis, short_axis = ax.yaxis, ax.xaxis else: - ax.xaxis.set_ticks(ticks) - ax.set_xticklabels(ticklabels) - ax.xaxis.get_major_formatter().set_offset_string(offset_string) + long_axis, short_axis = ax.xaxis, ax.yaxis + + if self._use_auto_colorbar_locator(): + _log.debug('Using auto colorbar locator on colorbar') + _log.debug('locator: %r', locator) + long_axis.set_major_locator(locator) + long_axis.set_major_formatter(formatter) + if type(self.norm) == colors.LogNorm: + long_axis.set_minor_locator(_ColorbarLogLocator(self, + base=10., subs='auto')) + long_axis.set_minor_formatter( + ticker.LogFormatter() + ) + else: + _log.debug('Using fixed locator on colorbar') + ticks, ticklabels, offset_string = self._ticker(locator, formatter) + long_axis.set_ticks(ticks) + long_axis.set_ticklabels(ticklabels) + long_axis.get_major_formatter().set_offset_string(offset_string) def set_ticks(self, ticks, update_ticks=True): """ @@ -498,9 +658,9 @@ def _edges(self, X, Y): # Using the non-array form of these line segments is much # simpler than making them into arrays. if self.orientation == 'vertical': - return [list(zip(X[i], Y[i])) for i in xrange(1, N - 1)] + return [list(zip(X[i], Y[i])) for i in range(1, N - 1)] else: - return [list(zip(Y[i], X[i])) for i in xrange(1, N - 1)] + return [list(zip(Y[i], X[i])) for i in range(1, N - 1)] def _add_solids(self, X, Y, C): ''' @@ -515,14 +675,9 @@ def _add_solids(self, X, Y, C): norm=self.norm, alpha=self.alpha, edgecolors='None') - # Save, set, and restore hold state to keep pcolor from - # clearing the axes. Ordinarily this will not be needed, - # since the axes object should already have hold set. - _hold = self.ax._hold - self.ax._hold = True + _log.debug('Setting pcolormesh') col = self.ax.pcolormesh(*args, **kw) - self.ax._hold = _hold - #self.add_observer(col) # We should observe, not be observed... + # self.add_observer(col) # We should observe, not be observed... if self.solids is not None: self.solids.remove() @@ -557,13 +712,11 @@ def add_lines(self, levels, colors, linewidths, erase=True): colors = np.asarray(colors)[igood] if cbook.iterable(linewidths): linewidths = np.asarray(linewidths)[igood] - N = len(y) - x = np.array([0.0, 1.0]) - X, Y = np.meshgrid(x, y) + X, Y = np.meshgrid([0, 1], y) if self.orientation == 'vertical': - xy = [list(zip(X[i], Y[i])) for i in xrange(N)] + xy = np.stack([X, Y], axis=-1) else: - xy = [list(zip(Y[i], X[i])) for i in xrange(N)] + xy = np.stack([Y, X], axis=-1) col = collections.LineCollection(xy, linewidths=linewidths) if erase and self.lines: @@ -575,39 +728,11 @@ def add_lines(self, levels, colors, linewidths, erase=True): self.ax.add_collection(col) self.stale = True - def _ticker(self): + def _ticker(self, locator, formatter): ''' Return the sequence of ticks (colorbar data locations), ticklabels (strings), and the corresponding offset string. ''' - locator = self.locator - formatter = self.formatter - if locator is None: - if self.boundaries is None: - if isinstance(self.norm, colors.NoNorm): - nv = len(self._values) - base = 1 + int(nv / 10) - locator = ticker.IndexLocator(base=base, offset=0) - elif isinstance(self.norm, colors.BoundaryNorm): - b = self.norm.boundaries - locator = ticker.FixedLocator(b, nbins=10) - elif isinstance(self.norm, colors.LogNorm): - locator = ticker.LogLocator(subs='all') - elif isinstance(self.norm, colors.SymLogNorm): - # The subs setting here should be replaced - # by logic in the locator. - locator = ticker.SymmetricalLogLocator( - subs=np.arange(1, 10), - linthresh=self.norm.linthresh, - base=10) - else: - if mpl.rcParams['_internal.classic_mode']: - locator = ticker.MaxNLocator() - else: - locator = ticker.AutoLocator() - else: - b = self._boundaries[self._inside] - locator = ticker.FixedLocator(b, nbins=10) if isinstance(self.norm, colors.NoNorm) and self.boundaries is None: intv = self._values[0], self._values[-1] else: @@ -704,8 +829,9 @@ def _process_values(self, b=None): b = self.norm.inverse(self._uniform_y(self.cmap.N + 1)) - if isinstance(self.norm, colors.LogNorm): - # If using a lognorm, ensure extensions don't go negative + if isinstance(self.norm, (colors.PowerNorm, colors.LogNorm)): + # If using a lognorm or powernorm, ensure extensions don't + # go negative if self._extend_lower(): b[0] = 0.9 * b[0] if self._extend_upper(): @@ -755,11 +881,10 @@ def _get_extension_lengths(self, frac, automin, automax, default=0.05): ''' # Set the default value. extendlength = np.array([default, default]) - if isinstance(frac, six.string_types): + if isinstance(frac, str): if frac.lower() == 'auto': # Use the provided values when 'auto' is required. - extendlength[0] = automin - extendlength[1] = automax + extendlength[:] = [automin, automax] else: # Any other string is invalid. raise ValueError('invalid value for extendfrac') @@ -847,17 +972,29 @@ def _mesh(self): transposition for a horizontal colorbar are done outside this function. ''' + # if boundaries and values are None, then we can go ahead and + # scale this up for Auto tick location. Otherwise we + # want to keep normalized between 0 and 1 and use manual tick + # locations. + x = np.array([0.0, 1.0]) if self.spacing == 'uniform': y = self._uniform_y(self._central_N()) else: y = self._proportional_y() + if self._use_auto_colorbar_locator(): + y = self.norm.inverse(y) + x = self.norm.inverse(x) self._y = y X, Y = np.meshgrid(x, y) + if self._use_auto_colorbar_locator(): + xmid = self.norm.inverse(0.5) + else: + xmid = 0.5 if self._extend_lower() and not self.extendrect: - X[0, :] = 0.5 + X[0, :] = xmid if self._extend_upper() and not self.extendrect: - X[-1, :] = 0.5 + X[-1, :] = xmid return X, Y def _locate(self, x): @@ -1055,6 +1192,33 @@ def remove(self): # use_gridspec was True ax.set_subplotspec(subplotspec) + def minorticks_on(self): + """ + Turns on the minor ticks on the colorbar without extruding + into the "extend regions". + """ + ax = self.ax + long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis + + if long_axis.get_scale() == 'log': + warnings.warn('minorticks_on() has no effect on a ' + 'logarithmic colorbar axis') + else: + long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self)) + + def minorticks_off(self): + """ + Turns off the minor ticks on the colorbar. + """ + ax = self.ax + long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis + + if long_axis.get_scale() == 'log': + warnings.warn('minorticks_off() has no effect on a ' + 'logarithmic colorbar axis') + else: + long_axis.set_minor_locator(ticker.NullLocator()) + @docstring.Substitution(make_axes_kw_doc) def make_axes(parents, location=None, orientation=None, fraction=0.15, @@ -1177,7 +1341,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, # transform each of the axes in parents using the new transform for ax in parents: - new_posn = shrinking_trans.transform(ax.get_position()) + new_posn = shrinking_trans.transform(ax.get_position(original=True)) new_posn = mtransforms.Bbox(new_posn) ax._set_position(new_posn) if parent_anchor is not False: @@ -1214,7 +1378,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, @docstring.Substitution(make_axes_kw_doc) -def make_axes_gridspec(parent, **kw): +def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw): ''' Resize and reposition a parent axes, and return a child axes suitable for a colorbar. This function is similar to @@ -1251,10 +1415,6 @@ def make_axes_gridspec(parent, **kw): orientation = kw.setdefault('orientation', 'vertical') kw['ticklocation'] = 'auto' - fraction = kw.pop('fraction', 0.15) - shrink = kw.pop('shrink', 1.0) - aspect = kw.pop('aspect', 20) - x1 = 1 - fraction # for shrinking @@ -1328,12 +1488,6 @@ def _add_solids(self, X, Y, C): Draw the colors using :class:`~matplotlib.patches.Patch`; optionally add separators. """ - # Save, set, and restore hold state to keep pcolor from - # clearing the axes. Ordinarily this will not be needed, - # since the axes object should already have hold set. - _hold = self.ax._hold - self.ax._hold = True - kw = {'alpha': self.alpha, } n_segments = len(C) @@ -1342,7 +1496,7 @@ def _add_solids(self, X, Y, C): hatches = self.mappable.hatches * n_segments patches = [] - for i in xrange(len(X) - 1): + for i in range(len(X) - 1): val = C[i][0] hatch = hatches[i] @@ -1379,8 +1533,6 @@ def _add_solids(self, X, Y, C): linewidths=(0.5 * mpl.rcParams['axes.linewidth'],)) self.ax.add_collection(self.dividers) - self.ax._hold = _hold - def colorbar_factory(cax, mappable, **kwargs): """ @@ -1393,7 +1545,7 @@ def colorbar_factory(cax, mappable, **kwargs): # if the given mappable is a contourset with any hatching, use # ColorbarPatch else use Colorbar if (isinstance(mappable, contour.ContourSet) - and any([hatch is not None for hatch in mappable.hatches])): + and any(hatch is not None for hatch in mappable.hatches)): cb = ColorbarPatch(cax, mappable, **kwargs) else: cb = Colorbar(cax, mappable, **kwargs) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index fdb4294b9696..82969ed18cb7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -44,16 +44,9 @@ All string specifications of color, other than "CN", are case-insensitive. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import zip - -from collections import Sized +from collections.abc import Sized import itertools import re -import warnings import numpy as np import matplotlib.cbook as cbook @@ -62,15 +55,15 @@ class _ColorMapping(dict): def __init__(self, mapping): - super(_ColorMapping, self).__init__(mapping) + super().__init__(mapping) self.cache = {} def __setitem__(self, key, value): - super(_ColorMapping, self).__setitem__(key, value) + super().__setitem__(key, value) self.cache.clear() def __delitem__(self, key): - super(_ColorMapping, self).__delitem__(key) + super().__delitem__(key) self.cache.clear() @@ -106,13 +99,12 @@ def _sanitize_extrema(ex): def _is_nth_color(c): """Return whether *c* can be interpreted as an item in the color cycle.""" - return isinstance(c, six.string_types) and re.match(r"\AC[0-9]\Z", c) + return isinstance(c, str) and re.match(r"\AC[0-9]\Z", c) def is_color_like(c): """Return whether *c* can be interpreted as an RGB(A) color.""" - # Special-case nth color syntax because it cannot be parsed during - # setup. + # Special-case nth color syntax because it cannot be parsed during setup. if _is_nth_color(c): return True try: @@ -180,7 +172,7 @@ def _to_rgba_no_colorcycle(c, alpha=None): ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``. """ orig_c = c - if isinstance(c, six.string_types): + if isinstance(c, str): if c.lower() == "none": return (0., 0., 0., 0.) # Named color. @@ -189,7 +181,7 @@ def _to_rgba_no_colorcycle(c, alpha=None): c = _colors_full_map[c.lower()] except KeyError: pass - if isinstance(c, six.string_types): + if isinstance(c, str): # hex color with no alpha. match = re.match(r"\A#[a-fA-F0-9]{6}\Z", c) if match: @@ -255,7 +247,7 @@ def to_rgba_array(c, alpha=None): # Note that this occurs *after* handling inputs that are already arrays, as # `to_rgba(c, alpha)` (below) is expensive for such inputs, due to the need # to format the array in the ValueError message(!). - if isinstance(c, six.string_types) and c.lower() == "none": + if cbook._str_lower_equal(c, "none"): return np.zeros((0, 4), float) try: return np.array([to_rgba(c, alpha)], float) @@ -522,8 +514,7 @@ def __call__(self, X, alpha=None, bytes=False): lut = self._lut.copy() # Don't let alpha modify original _lut. if alpha is not None: - alpha = min(alpha, 1.0) # alpha must be between 0 and 1 - alpha = max(alpha, 0.0) + alpha = np.clip(alpha, 0, 1) if bytes: alpha = int(alpha * 255) if (lut[-1] == 0).all(): @@ -555,7 +546,7 @@ def __copy__(self): def set_bad(self, color='k', alpha=None): """Set color to be used for masked values. """ - self._rgba_bad = colorConverter.to_rgba(color, alpha) + self._rgba_bad = to_rgba(color, alpha) if self._isinit: self._set_extremes() @@ -563,7 +554,7 @@ def set_under(self, color='k', alpha=None): """Set color to be used for low out-of-range values. Requires norm.clip = False """ - self._rgba_under = colorConverter.to_rgba(color, alpha) + self._rgba_under = to_rgba(color, alpha) if self._isinit: self._set_extremes() @@ -571,7 +562,7 @@ def set_over(self, color='k', alpha=None): """Set color to be used for high out-of-range values. Requires norm.clip = False """ - self._rgba_over = colorConverter.to_rgba(color, alpha) + self._rgba_over = to_rgba(color, alpha) if self._isinit: self._set_extremes() @@ -718,7 +709,7 @@ def from_list(name, colors, N=256, gamma=1.0): raise ValueError('colors must be iterable') if (isinstance(colors[0], Sized) and len(colors[0]) == 2 - and not isinstance(colors[0], six.string_types)): + and not isinstance(colors[0], str)): # List of value, color pairs vals, colors = zip(*colors) else: @@ -726,7 +717,7 @@ def from_list(name, colors, N=256, gamma=1.0): cdict = dict(red=[], green=[], blue=[], alpha=[]) for val, color in zip(vals, colors): - r, g, b, a = colorConverter.to_rgba(color) + r, g, b, a = to_rgba(color) cdict['red'].append((val, r, r)) cdict['green'].append((val, g, g)) cdict['blue'].append((val, b, b)) @@ -764,13 +755,9 @@ def func_r(x): return dat(1.0 - x) return func_r - data_r = dict() - for key, data in six.iteritems(self._segmentdata): - if callable(data): - data_r[key] = factory(data) - else: - new_data = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)] - data_r[key] = new_data + data_r = {key: (factory(data) if callable(data) else + [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)]) + for key, data in self._segmentdata.items()} return LinearSegmentedColormap(name, data_r, self.N, self._gamma) @@ -811,7 +798,7 @@ def __init__(self, colors, name='from_list', N=None): self.colors = colors N = len(colors) else: - if isinstance(colors, six.string_types): + if isinstance(colors, str): self.colors = [colors] * N self.monochrome = True elif cbook.iterable(colors): @@ -830,9 +817,8 @@ def __init__(self, colors, name='from_list', N=None): Colormap.__init__(self, name, N) def _init(self): - rgba = colorConverter.to_rgba_array(self.colors) self._lut = np.zeros((self.N + 3, 4), float) - self._lut[:-3] = rgba + self._lut[:-3] = to_rgba_array(self.colors) self._isinit = True self._set_extremes() @@ -955,11 +941,6 @@ def __call__(self, value, clip=None): resdat -= vmin resdat /= (vmax - vmin) result = np.ma.array(resdat, mask=result.mask, copy=False) - # Agg cannot handle float128. We actually only need 32-bit of - # precision, but on Windows, `np.dtype(np.longdouble) == np.float64`, - # so casting to float32 would lose precision on float64s as well. - if result.dtype == np.longdouble: - result = result.astype(np.float64) if is_scalar: result = result[0] return result @@ -1130,7 +1111,8 @@ def _transform(self, a): """ Inplace transformation. """ - masked = np.abs(a) > self.linthresh + with np.errstate(invalid="ignore"): + masked = np.abs(a) > self.linthresh sign = np.sign(a[masked]) log = (self._linscale_adj + np.log(np.abs(a[masked]) / self.linthresh)) log *= sign * self.linthresh @@ -1187,8 +1169,8 @@ def autoscale_None(self, A): class PowerNorm(Normalize): """ - Normalize a given value to the ``[0, 1]`` interval with a power-law - scaling. This will clip any negative data points to 0. + Linearly map a given value to the 0-1 range and then apply + a power-law normalization over that range. """ def __init__(self, gamma, vmin=None, vmax=None, clip=False): Normalize.__init__(self, vmin, vmax, clip) @@ -1208,18 +1190,17 @@ def __call__(self, value, clip=None): elif vmin == vmax: result.fill(0) else: - res_mask = result.data < 0 if clip: mask = np.ma.getmask(result) result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax), mask=mask) resdat = result.data resdat -= vmin + resdat[resdat < 0] = 0 np.power(resdat, gamma, resdat) resdat /= (vmax - vmin) ** gamma result = np.ma.array(resdat, mask=result.mask, copy=False) - result[res_mask] = 0 if is_scalar: result = result[0] return result @@ -1241,10 +1222,6 @@ def autoscale(self, A): Set *vmin*, *vmax* to min, max of *A*. """ self.vmin = np.ma.min(A) - if self.vmin < 0: - self.vmin = 0 - warnings.warn("Power-law scaling on negative values is " - "ill-defined, clamping to 0.") self.vmax = np.ma.max(A) def autoscale_None(self, A): @@ -1252,10 +1229,6 @@ def autoscale_None(self, A): A = np.asanyarray(A) if self.vmin is None and A.size: self.vmin = A.min() - if self.vmin < 0: - self.vmin = 0 - warnings.warn("Power-law scaling on negative values is " - "ill-defined, clamping to 0.") if self.vmax is None and A.size: self.vmax = A.max() @@ -1496,8 +1469,7 @@ def hsv_to_rgb(hsv): g[idx] = v[idx] b[idx] = v[idx] - # `np.stack([r, g, b], axis=-1)` (numpy 1.10). - rgb = np.concatenate([r[..., None], g[..., None], b[..., None]], -1) + rgb = np.stack([r, g, b], axis=-1) if in_ndim == 1: rgb.shape = (3,) @@ -1519,17 +1491,6 @@ def _vector_magnitude(arr): return np.sqrt(sum_sq) -def _vector_dot(a, b): - # things that don't work here: - # * a.dot(b) - fails on masked arrays until 1.10 - # * np.ma.dot(a, b) - doesn't mask enough things - # * np.ma.dot(a, b, strict=True) - returns a maskedarray with no mask - dot = 0 - for i in range(a.shape[-1]): - dot += a[..., i] * b[..., i] - return dot - - class LightSource(object): """ Create a light source coming from the specified azimuth and elevation. @@ -1666,7 +1627,7 @@ def shade_normals(self, normals, fraction=1.): completely in shadow and 1 is completely illuminated. """ - intensity = _vector_dot(normals, self.direction) + intensity = normals.dot(self.direction) # Apply contrast stretch imin, imax = intensity.min(), intensity.max() diff --git a/lib/matplotlib/compat/subprocess.py b/lib/matplotlib/compat/subprocess.py index 6607a011836e..ad48ed4f137a 100644 --- a/lib/matplotlib/compat/subprocess.py +++ b/lib/matplotlib/compat/subprocess.py @@ -1,10 +1,7 @@ """ -A replacement wrapper around the subprocess module, with a number of -work-arounds: -- Provides a stub implementation of subprocess members on Google App Engine - (which are missing in subprocess). -- Use subprocess32, backport from python 3.2 on Linux/Mac work-around for - https://github.com/matplotlib/matplotlib/issues/5314 +A replacement wrapper around the subprocess module, which provides a stub +implementation of subprocess members on Google App Engine +(which are missing in subprocess). Instead of importing subprocess, other modules should use this as follows: @@ -12,19 +9,13 @@ This module is safe to import from anywhere within matplotlib. """ - -from __future__ import absolute_import # Required to import subprocess -from __future__ import print_function -import os -import sys -if os.name == 'posix' and sys.version_info[0] < 3: - # work around for https://github.com/matplotlib/matplotlib/issues/5314 - try: - import subprocess32 as subprocess - except ImportError: - import subprocess -else: - import subprocess +import subprocess +from matplotlib.cbook import warn_deprecated +warn_deprecated(since='3.0', + name='matplotlib.compat.subprocess', + alternative='the python 3 standard library ' + '"subprocess" module', + obj_type='module') __all__ = ['Popen', 'PIPE', 'STDOUT', 'check_output', 'CalledProcessError'] diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index f96bf9f03f7f..552d6da5a761 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import matplotlib.cbook as cbook import matplotlib.artist as martist @@ -32,6 +27,7 @@ def __init__(self, kl, label=None): self.set_label(label) + @cbook.deprecated("3.0") def set_remove_method(self, f): self._remove_method = f @@ -44,13 +40,6 @@ def remove(self): if self._remove_method: self._remove_method(self) - def __getstate__(self): - d = self.__dict__.copy() - # remove the unpicklable remove method, this will get re-added on load - # (by the axes) if the artist lives on an axes. - d['_remove_method'] = None - return d - def get_label(self): """ Get the label used for this artist in the legend. @@ -61,7 +50,9 @@ def set_label(self, s): """ Set the label to *s* for auto legend. - ACCEPTS: string or anything printable with '%s' conversion. + Parameters + ---------- + s : string or anything printable with '%s' conversion. """ if s is not None: self._label = '%s' % (s, ) @@ -102,7 +93,7 @@ def pchanged(self): Fire an event when property changed, calling all of the registered callbacks. """ - for oid, func in list(six.iteritems(self._propobservers)): + for oid, func in list(self._propobservers.items()): func(self) def get_children(self): diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index f6fdfd61c268..a93207a1557a 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1,21 +1,19 @@ """ These are classes to support contour plotting and labelling for the Axes class. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange +from numbers import Integral import warnings -import matplotlib as mpl + import numpy as np from numpy import ma + +import matplotlib as mpl import matplotlib._contour as _contour import matplotlib.path as mpath import matplotlib.ticker as ticker import matplotlib.cm as cm -import matplotlib.colors as colors +import matplotlib.colors as mcolors import matplotlib.collections as mcoll import matplotlib.font_manager as font_manager import matplotlib.text as text @@ -54,42 +52,46 @@ def get_rotation(self): class ContourLabeler(object): """Mixin to provide labelling capability to `.ContourSet`.""" - def clabel(self, *args, **kwargs): + def clabel(self, *args, + fontsize=None, inline=True, inline_spacing=5, fmt='%1.3f', + colors=None, use_clabeltext=False, manual=False, + rightside_up=True): """ Label a contour plot. Call signature:: - clabel(cs, **kwargs) + clabel(cs, [levels,] **kwargs) Adds labels to line contours in *cs*, where *cs* is a :class:`~matplotlib.contour.ContourSet` object returned by - contour. - - :: - - clabel(cs, v, **kwargs) - - only labels contours listed in *v*. + ``contour()``. Parameters ---------- + cs : `.ContourSet` + The ContourSet to label. + + levels : array-like, optional + A list of level values, that should be labeled. The list must be + a subset of ``cs.levels``. If not given, all levels are labeled. + fontsize : string or float, optional Size in points or relative size e.g., 'smaller', 'x-large'. - See `Text.set_size` for accepted string values. + See `.Text.set_size` for accepted string values. - colors : - Color of each label + colors : color-spec, optional + The label colors: - - if *None*, the color of each label matches the color of - the corresponding contour + - If *None*, the color of each label matches the color of + the corresponding contour. - - if one string color, e.g., *colors* = 'r' or *colors* = - 'red', all labels will be plotted in this color + - If one string color, e.g., *colors* = 'r' or *colors* = + 'red', all labels will be plotted in this color. - - if a tuple of matplotlib color args (string, float, rgb, etc), + - If a tuple of matplotlib color args (string, float, rgb, etc), different labels will be plotted in different colors in the order - specified + specified. inline : bool, optional If ``True`` the underlying contour is removed where the label is @@ -131,10 +133,15 @@ def clabel(self, *args, **kwargs): or minus 90 degrees from level. Default is ``True``. use_clabeltext : bool, optional - If ``True``, `ClabelText` class (instead of `Text`) is used to + If ``True``, `.ClabelText` class (instead of `.Text`) is used to create labels. `ClabelText` recalculates rotation angles of texts during the drawing time, therefore this can be used if aspect of the axes changes. Default is ``False``. + + Returns + ------- + labels + A list of `.Text` instances for the labels. """ """ @@ -150,21 +157,15 @@ def clabel(self, *args, **kwargs): `BlockingContourLabeler` (case of manual label placement). """ - fontsize = kwargs.get('fontsize', None) - inline = kwargs.get('inline', 1) - inline_spacing = kwargs.get('inline_spacing', 5) - self.labelFmt = kwargs.get('fmt', '%1.3f') - _colors = kwargs.get('colors', None) + self.labelFmt = fmt + self._use_clabeltext = use_clabeltext + # Detect if manual selection is desired and remove from argument list. + self.labelManual = manual + self.rightside_up = rightside_up - self._use_clabeltext = kwargs.get('use_clabeltext', False) - - # Detect if manual selection is desired and remove from argument list - self.labelManual = kwargs.get('manual', False) - - self.rightside_up = kwargs.get('rightside_up', True) if len(args) == 0: levels = self.levels - indices = list(xrange(len(self.cvalues))) + indices = list(range(len(self.cvalues))) elif len(args) == 1: levlabs = list(args[0]) indices, levels = [], [] @@ -185,14 +186,14 @@ def clabel(self, *args, **kwargs): font_size_pts = self.labelFontProps.get_size_in_points() self.labelFontSizeList = [font_size_pts] * len(levels) - if _colors is None: + if colors is None: self.labelMappable = self self.labelCValueList = np.take(self.cvalues, self.labelIndiceList) else: - cmap = colors.ListedColormap(_colors, N=len(self.labelLevelList)) - self.labelCValueList = list(xrange(len(self.labelLevelList))) + cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList)) + self.labelCValueList = list(range(len(self.labelLevelList))) self.labelMappable = cm.ScalarMappable(cmap=cmap, - norm=colors.NoNorm()) + norm=mcolors.NoNorm()) self.labelXYs = [] @@ -212,16 +213,16 @@ def clabel(self, *args, **kwargs): else: self.labels(inline, inline_spacing) - # Hold on to some old attribute names. These are deprecated and will - # be removed in the near future (sometime after 2008-08-01), but - # keeping for now for backwards compatibility - self.cl = self.labelTexts - self.cl_xy = self.labelXYs - self.cl_cvalues = self.labelCValues - self.labelTextsList = cbook.silent_list('text.Text', self.labelTexts) return self.labelTextsList + cl = property(cbook.deprecated("3.0", alternative="labelTexts")( + lambda self: self.labelTexts)) + cl_xy = property(cbook.deprecated("3.0", alternative="labelXYs")( + lambda self: self.labelXYs)) + cl_cvalues = property(cbook.deprecated("3.0", alternative="labelCValues")( + lambda self: self.labelCValues)) + def print_label(self, linecontour, labelwidth): "Return *False* if contours are too short for a label." return (len(linecontour) > 10 * labelwidth @@ -263,7 +264,7 @@ def get_label_width(self, lev, fmt, fsize): """ Return the width of the label in points. """ - if not isinstance(lev, six.string_types): + if not isinstance(lev, str): lev = self.get_text(lev, fmt) lev, ismath = text.Text.is_math_text(lev) @@ -320,7 +321,7 @@ def set_label_props(self, label, text, color): def get_text(self, lev, fmt): """Get the text of the label.""" - if isinstance(lev, six.string_types): + if isinstance(lev, str): return lev else: if isinstance(fmt, dict): @@ -791,7 +792,12 @@ class ContourSet(cm.ScalarMappable, ContourLabeler): levels for filled contours. See :meth:`_process_colors`. """ - def __init__(self, ax, *args, **kwargs): + def __init__(self, ax, *args, + levels=None, filled=False, linewidths=None, linestyles=None, + alpha=None, origin=None, extent=None, + cmap=None, colors=None, norm=None, vmin=None, vmax=None, + extend='neither', antialiased=None, + **kwargs): """ Draw contour lines or filled regions, depending on whether keyword arg *filled* is ``False`` (default) or ``True``. @@ -837,23 +843,17 @@ def __init__(self, ax, *args, **kwargs): `~axes.Axes.contour`. """ self.ax = ax - self.levels = kwargs.pop('levels', None) - self.filled = kwargs.pop('filled', False) - self.linewidths = kwargs.pop('linewidths', None) - self.linestyles = kwargs.pop('linestyles', None) - + self.levels = levels + self.filled = filled + self.linewidths = linewidths + self.linestyles = linestyles self.hatches = kwargs.pop('hatches', [None]) - - self.alpha = kwargs.pop('alpha', None) - self.origin = kwargs.pop('origin', None) - self.extent = kwargs.pop('extent', None) - cmap = kwargs.pop('cmap', None) - self.colors = kwargs.pop('colors', None) - norm = kwargs.pop('norm', None) - vmin = kwargs.pop('vmin', None) - vmax = kwargs.pop('vmax', None) - self.extend = kwargs.pop('extend', 'neither') - self.antialiased = kwargs.pop('antialiased', None) + self.alpha = alpha + self.origin = origin + self.extent = extent + self.colors = colors + self.extend = extend + self.antialiased = antialiased if self.antialiased is None and self.filled: self.antialiased = False # eliminate artifacts; we are not # stroking the boundaries. @@ -863,14 +863,11 @@ def __init__(self, ax, *args, **kwargs): self.nchunk = kwargs.pop('nchunk', 0) self.locator = kwargs.pop('locator', None) - if (isinstance(norm, colors.LogNorm) + if (isinstance(norm, mcolors.LogNorm) or isinstance(self.locator, ticker.LogLocator)): self.logscale = True if norm is None: - norm = colors.LogNorm() - if self.extend is not 'neither': - raise ValueError('extend kwarg does not work yet with log ' - ' scale') + norm = mcolors.LogNorm() else: self.logscale = False @@ -906,13 +903,12 @@ def __init__(self, ax, *args, **kwargs): # extend_max case we don't need to worry about passing more colors # than ncolors as ListedColormap will clip. total_levels = ncolors + int(extend_min) + int(extend_max) - if (len(self.colors) == total_levels and - any([extend_min, extend_max])): + if len(self.colors) == total_levels and (extend_min or extend_max): use_set_under_over = True if extend_min: i0 = 1 - cmap = colors.ListedColormap(self.colors[i0:None], N=ncolors) + cmap = mcolors.ListedColormap(self.colors[i0:None], N=ncolors) if use_set_under_over: if extend_min: @@ -1178,6 +1174,9 @@ def _autolev(self, N): """ Select contour levels to span the data. + The target number of levels, *N*, is used only when the + scale is not log and default locator is used. + We need two more levels for filled contours than for line contours, because for the latter we need to specify the lower and upper boundary of each range. For example, @@ -1185,6 +1184,7 @@ def _autolev(self, N): one contour line, but two filled regions, and therefore three levels to provide boundaries for both regions. """ + self._auto = True if self.locator is None: if self.logscale: self.locator = ticker.LogLocator() @@ -1192,8 +1192,27 @@ def _autolev(self, N): self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1) lev = self.locator.tick_values(self.zmin, self.zmax) - self._auto = True - return lev + + try: + if self.locator._symmetric: + return lev + except AttributeError: + pass + + # Trim excess levels the locator may have supplied. + under = np.nonzero(lev < self.zmin)[0] + i0 = under[-1] if len(under) else 0 + over = np.nonzero(lev > self.zmax)[0] + i1 = over[0] + 1 if len(over) else len(lev) + if self.extend in ('min', 'both'): + i0 += 1 + if self.extend in ('max', 'both'): + i1 -= 1 + + if i1 - i0 < 3: + i0, i1 = 0, len(lev) + + return lev[i0:i1] def _contour_level_args(self, z, args): """ @@ -1206,26 +1225,20 @@ def _contour_level_args(self, z, args): self._auto = False if self.levels is None: if len(args) == 0: - lev = self._autolev(7) + levels_arg = 7 # Default, hard-wired. else: - level_arg = args[0] - try: - if type(level_arg) == int: - lev = self._autolev(level_arg) - else: - lev = np.asarray(level_arg).astype(np.float64) - except: - raise TypeError( - "Last {0} arg must give levels; see help({0})" - .format(fn)) - self.levels = lev + levels_arg = args[0] else: - self.levels = np.asarray(self.levels).astype(np.float64) + levels_arg = self.levels + if isinstance(levels_arg, Integral): + self.levels = self._autolev(levels_arg) + else: + self.levels = np.asarray(levels_arg).astype(np.float64) if not self.filled: inside = (self.levels > self.zmin) & (self.levels < self.zmax) - self.levels = self.levels[inside] - if len(self.levels) == 0: + levels_in = self.levels[inside] + if len(levels_in) == 0: self.levels = [self.zmin] warnings.warn("No contour levels were found" " within the data range.") @@ -1250,24 +1263,28 @@ def _process_levels(self): # (Colorbar needs this even for line contours.) self._levels = list(self.levels) + if self.logscale: + lower, upper = 1e-250, 1e250 + else: + lower, upper = -1e250, 1e250 + if self.extend in ('both', 'min'): - self._levels.insert(0, min(self.levels[0], self.zmin) - 1) + self._levels.insert(0, lower) if self.extend in ('both', 'max'): - self._levels.append(max(self.levels[-1], self.zmax) + 1) + self._levels.append(upper) self._levels = np.asarray(self._levels) if not self.filled: self.layers = self.levels return - # layer values are mid-way between levels - self.layers = 0.5 * (self._levels[:-1] + self._levels[1:]) - # ...except that extended layers must be outside the - # normed range: - if self.extend in ('both', 'min'): - self.layers[0] = -1e150 - if self.extend in ('both', 'max'): - self.layers[-1] = 1e150 + # Layer values are mid-way between levels in screen space. + if self.logscale: + # Avoid overflow by taking sqrt before multiplying. + self.layers = (np.sqrt(self._levels[:-1]) + * np.sqrt(self._levels[1:])) + else: + self.layers = 0.5 * (self._levels[:-1] + self._levels[1:]) def _process_colors(self): """ @@ -1304,7 +1321,7 @@ def _process_colors(self): if self.extend in ('both', 'max'): i1 += 1 self.cvalues = list(range(i0, i1)) - self.set_norm(colors.NoNorm()) + self.set_norm(mcolors.NoNorm()) else: self.cvalues = self.layers self.set_array(self.levels) @@ -1344,7 +1361,7 @@ def _process_linestyles(self): if lev < eps: tlinestyles[i] = neg_ls else: - if isinstance(linestyles, six.string_types): + if isinstance(linestyles, str): tlinestyles = [linestyles] * Nlev elif cbook.iterable(linestyles): tlinestyles = list(linestyles) @@ -1400,7 +1417,7 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): # Nonetheless, improvements could probably be made. if indices is None: - indices = list(xrange(len(self.levels))) + indices = list(range(len(self.levels))) dmin = np.inf conmin = None @@ -1681,7 +1698,7 @@ def _initialize_x_y(self, z): Returns ------- - :class:`~matplotlib.contour.QuadContourSet` + c : `~.contour.QuadContourSet` Other Parameters ---------------- diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 2712d64291a7..2f2362332c1b 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1,7 +1,6 @@ """ Matplotlib provides sophisticated date plotting capabilities, standing on the -shoulders of python :mod:`datetime`, the add-on modules :mod:`pytz` and -:mod:`dateutil`. +shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`. .. _date-format: @@ -46,11 +45,9 @@ All the Matplotlib date converters, tickers and formatters are timezone aware. If no explicit timezone is provided, the rcParam ``timezone`` is assumend. If -you want to use a custom time zone, pass a :class:`pytz.timezone` instance +you want to use a custom time zone, pass a :class:`datetime.tzinfo` instance with the tz keyword argument to :func:`num2date`, :func:`.plot_date`, and any custom date tickers or locators you create. -See `pytz `_ for information on :mod:`pytz` and -timezone handling. A wide range of specific and general purpose date tick locators and formatters are provided in this module. See @@ -58,7 +55,7 @@ and formatters. These are described below. -The `dateutil module `_ provides +The `dateutil module `_ provides additional code to handle date ticking, making it easy to place ticks on any kinds of dates. See examples below. @@ -110,7 +107,7 @@ :class:`matplotlib.dates.rrulewrapper`. The :class:`rrulewrapper` is a simple wrapper around a :class:`dateutil.rrule` (`dateutil - `_) which allow almost + `_) which allow almost arbitrary date tick specifications. See `rrule example <../gallery/ticks_and_spines/date_demo_rrule.html>`_. @@ -135,29 +132,23 @@ * :class:`IndexDateFormatter`: date plots with implicit *x* indexing. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six -from six.moves import zip -import re -import time -import math import datetime import functools - -import warnings import logging +import math +import re +import time +import warnings from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY) from dateutil.relativedelta import relativedelta import dateutil.parser -import logging +import dateutil.tz import numpy as np - import matplotlib from matplotlib import rcParams import matplotlib.units as units @@ -182,23 +173,7 @@ _log = logging.getLogger(__name__) -# Make a simple UTC instance so we don't always have to import -# pytz. From the python datetime library docs: - -class _UTC(datetime.tzinfo): - """UTC""" - - def utcoffset(self, dt): - return datetime.timedelta(0) - - def tzname(self, dt): - return str("UTC") - - def dst(self, dt): - return datetime.timedelta(0) - - -UTC = _UTC() +UTC = datetime.timezone.utc def _get_rc_timezone(): @@ -208,8 +183,7 @@ def _get_rc_timezone(): s = matplotlib.rcParams['timezone'] if s == 'UTC': return UTC - import pytz - return pytz.timezone(s) + return dateutil.tz.gettz(s) """ @@ -283,7 +257,8 @@ def _dt64_to_ordinalf(d): # the "extra" ensures that we at least allow the dynamic range out to # seconds. That should get out to +/-2e11 years. - extra = d - d.astype('datetime64[s]') + # NOTE: First cast truncates; second cast back is for NumPy 1.10. + extra = d - d.astype('datetime64[s]').astype(d.dtype) extra = extra.astype('timedelta64[ns]') t0 = np.datetime64('0001-01-01T00:00:00').astype('datetime64[s]') dt = (d.astype('datetime64[s]') - t0).astype(np.float64) @@ -364,7 +339,7 @@ class bytespdate2num(strpdate2num): """ Use this class to parse date strings to matplotlib datenums when you know the date format string of the date you are parsing. See - :file:`examples/misc/load_converter.py`. + :doc:`/gallery/misc/load_converter.py`. """ def __init__(self, fmt, encoding='utf-8'): """ @@ -372,7 +347,7 @@ def __init__(self, fmt, encoding='utf-8'): fmt: any valid strptime format is supported encoding: encoding to use on byte input (default: 'utf-8') """ - super(bytespdate2num, self).__init__(fmt) + super().__init__(fmt) self.encoding = encoding def __call__(self, b): @@ -383,7 +358,7 @@ def __call__(self, b): A date2num float """ s = b.decode(self.encoding) - return super(bytespdate2num, self).__call__(s) + return super().__call__(s) # a version of dateutil.parser.parse that can operate on nump0y arrays @@ -403,7 +378,7 @@ def datestr2num(d, default=None): default : datetime instance, optional The default date to use when fields are missing in *d*. """ - if isinstance(d, six.string_types): + if isinstance(d, str): dt = dateutil.parser.parse(d, default=default) return date2num(dt) else: @@ -628,15 +603,15 @@ def __init__(self, fmt, tz=None): def __call__(self, x, pos=0): if x == 0: raise ValueError('DateFormatter found a value of x=0, which is ' - 'an illegal date. This usually occurs because ' + 'an illegal date; this usually occurs because ' 'you have not informed the axis that it is ' 'plotting dates, e.g., with ax.xaxis_date()') - dt = num2date(x, self.tz) - return self.strftime(dt, self.fmt) + return num2date(x, self.tz).strftime(self.fmt) def set_tzinfo(self, tz): self.tz = tz + @cbook.deprecated("3.0") def _replace_common_substr(self, s1, s2, sub1, sub2, replacement): """Helper function for replacing substrings sub1 and sub2 located at the same indexes in strings s1 and s2 respectively, @@ -662,6 +637,7 @@ def _replace_common_substr(self, s1, s2, sub1, sub2, replacement): return s1, s2 + @cbook.deprecated("3.0") def strftime_pre_1900(self, dt, fmt=None): """Call time.strftime for years before 1900 by rolling forward a multiple of 28 years. @@ -719,6 +695,7 @@ def strftime_pre_1900(self, dt, fmt=None): "{0:02d}".format(dt.year % 100)) return cbook.unicode_safe(s1) + @cbook.deprecated("3.0") def strftime(self, dt, fmt=None): """ Refer to documentation for :meth:`datetime.datetime.strftime` @@ -763,10 +740,7 @@ def __call__(self, x, pos=0): ind = int(np.round(x)) if ind >= len(self.t) or ind <= 0: return '' - - dt = num2date(self.t[ind], self.tz) - - return cbook.unicode_safe(dt.strftime(self.fmt)) + return num2date(self.t[ind], self.tz).strftime(self.fmt) class AutoDateFormatter(ticker.Formatter): @@ -857,7 +831,7 @@ def __call__(self, x, pos=None): if scale >= locator_unit_scale), self.defaultfmt) - if isinstance(fmt, six.string_types): + if isinstance(fmt, str): self._formatter = DateFormatter(fmt, self._tz) result = self._formatter(x, pos) elif callable(fmt): @@ -1165,7 +1139,7 @@ class AutoDateLocator(DateLocator): locations. """ def __init__(self, tz=None, minticks=5, maxticks=None, - interval_multiples=False): + interval_multiples=True): """ *minticks* is the minimum number of ticks desired, which is used to select the type of ticking (yearly, monthly, etc.). @@ -1241,6 +1215,12 @@ def __init__(self, tz=None, minticks=5, maxticks=None, MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000]} + if interval_multiples: + # Swap "3" for "4" in the DAILY list; If we use 3 we get bad + # tick loc for months w/ 31 days: 1, 4,..., 28, 31, 1 + # If we use 4 then we get: 1, 5, ... 25, 29, 1 + self.intervald[DAILY] = [1, 2, 4, 7, 14, 21] + self._byranges = [None, range(1, 13), range(1, 32), range(0, 24), range(0, 60), range(0, 60), None] @@ -1345,7 +1325,11 @@ def get_locator(self, dmin, dmax): self._freq = freq if self._byranges[i] and self.interval_multiples: - byranges[i] = self._byranges[i][::interval] + if i == DAILY and interval == 14: + # just make first and 15th. Avoids 30th. + byranges[i] = [1, 15] + else: + byranges[i] = self._byranges[i][::interval] interval = 1 else: byranges[i] = self._byranges[i] @@ -1400,7 +1384,7 @@ def __init__(self, base=1, month=1, day=1, tz=None): (default jan 1). """ DateLocator.__init__(self, tz) - self.base = ticker.Base(base) + self.base = ticker._Edge_integer(base, 0) self.replaced = {'month': month, 'day': day, 'hour': 0, @@ -1419,15 +1403,15 @@ def __call__(self): return self.tick_values(dmin, dmax) def tick_values(self, vmin, vmax): - ymin = self.base.le(vmin.year) - ymax = self.base.ge(vmax.year) + ymin = self.base.le(vmin.year) * self.base.step + ymax = self.base.ge(vmax.year) * self.base.step ticks = [vmin.replace(year=ymin, **self.replaced)] while True: dt = ticks[-1] if dt.year >= ymax: return date2num(ticks) - year = dt.year + self.base.get_base() + year = dt.year + self.base.step ticks.append(dt.replace(year=year, **self.replaced)) def autoscale(self): diff --git a/lib/matplotlib/docstring.py b/lib/matplotlib/docstring.py index cf9537f0c6fe..9a67c1af86bd 100644 --- a/lib/matplotlib/docstring.py +++ b/lib/matplotlib/docstring.py @@ -1,12 +1,8 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -from matplotlib import cbook import sys import types +from matplotlib import cbook + class Substitution(object): """ @@ -113,8 +109,6 @@ def do_copy(target): def dedent_interpd(func): """A special case of the interpd that first performs a dedent on the incoming docstring""" - if isinstance(func, types.MethodType) and not six.PY3: - func = func.im_func return interpd(dedent(func)) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index b38af56e67ec..f738fb591d3e 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -9,43 +9,34 @@ # iterate over pages: for page in dvi: w, h, d = page.width, page.height, page.descent - for x,y,font,glyph,width in page.text: + for x, y, font, glyph, width in page.text: fontname = font.texname pointsize = font.size ... - for x,y,height,width in page.boxes: + for x, y, height, width in page.boxes: ... - """ -from __future__ import absolute_import, division, print_function - -import six -from six.moves import xrange from collections import namedtuple -from functools import partial, wraps +import enum +from functools import lru_cache, partial, wraps import logging -import numpy as np import os import re import struct -import sys +import subprocess import textwrap -from matplotlib import cbook, rcParams -from matplotlib.compat import subprocess - -try: - from functools import lru_cache -except ImportError: # Py2 - from backports.functools_lru_cache import lru_cache +import numpy as np -if six.PY3: - def ord(x): - return x +from matplotlib import cbook, rcParams _log = logging.getLogger(__name__) +# Many dvi related files are looked for by external processes, require +# additional parsing, and are used many times per rendering, which is why they +# are cached using lru_cache(). + # Dvi is a bytecode format documented in # http://mirrors.ctan.org/systems/knuth/dist/texware/dvitype.web # http://texdoc.net/texmf-dist/doc/generic/knuth/texware/dvitype.pdf @@ -62,7 +53,7 @@ def ord(x): # just stops reading) # finale: the finale (unimplemented in our current implementation) -_dvistate = cbook.Bunch(pre=0, outer=1, inpage=2, post_post=3, finale=4) +_dvistate = enum.Enum('DviState', 'pre outer inpage post_post finale') # The marks on a page consist of text and boxes. A page also has dimensions. Page = namedtuple('Page', 'text boxes height width descent') @@ -172,7 +163,7 @@ def wrapper(self, byte): if max is None: table[min] = wrapper else: - for i in xrange(min, max+1): + for i in range(min, max+1): assert table[i] is None table[i] = wrapper return wrapper @@ -189,12 +180,12 @@ class Dvi(object): file upon exit. Pages can be read via iteration. Here is an overly simple way to extract text without trying to detect whitespace:: - >>> with matplotlib.dviread.Dvi('input.dvi', 72) as dvi: - >>> for page in dvi: - >>> print(''.join(unichr(t.glyph) for t in page.text)) + >>> with matplotlib.dviread.Dvi('input.dvi', 72) as dvi: + ... for page in dvi: + ... print(''.join(chr(t.glyph) for t in page.text)) """ # dispatch table - _dtable = [None for _ in xrange(256)] + _dtable = [None] * 256 _dispatch = partial(_dispatch, _dtable) def __init__(self, filename, dpi): @@ -250,12 +241,8 @@ def __iter__(self): precision is not lost and coordinate values are not clipped to integers. """ - while True: - have_page = self._read() - if have_page: - yield self._output() - else: - break + while self._read(): + yield self._output() def close(self): """ @@ -311,11 +298,11 @@ def _read(self): False if there were no more pages. """ while True: - byte = ord(self.file.read(1)[0]) + byte = self.file.read(1)[0] self._dtable[byte](self, byte) if byte == 140: # end of page return True - if self.state == _dvistate.post_post: # end of file + if self.state is _dvistate.post_post: # end of file self.close() return False @@ -325,11 +312,11 @@ def _arg(self, nbytes, signed=False): Signedness is determined by the *signed* keyword. """ str = self.file.read(nbytes) - value = ord(str[0]) + value = str[0] if signed and value >= 0x80: value = value - 0x100 for i in range(1, nbytes): - value = 0x100*value + ord(str[i]) + value = 0x100*value + str[i] return value @_dispatch(min=0, max=127, state=_dvistate.inpage) @@ -445,14 +432,9 @@ def _fnt_num(self, new_f): @_dispatch(min=239, max=242, args=('ulen1',)) def _xxx(self, datalen): special = self.file.read(datalen) - if six.PY3: - chr_ = chr - else: - def chr_(x): - return x _log.debug( 'Dvi._xxx: encountered special: %s', - ''.join([chr_(ch) if 32 <= ord(ch) < 127 else '<%02x>' % ord(ch) + ''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch for ch in special])) @_dispatch(min=243, max=246, args=('olen1', 'u4', 'u4', 'u4', 'u1', 'u1')) @@ -464,11 +446,7 @@ def _fnt_def_real(self, k, c, s, d, a, l): fontname = n[-l:].decode('ascii') tfm = _tfmfile(fontname) if tfm is None: - if six.PY2: - error_class = OSError - else: - error_class = FileNotFoundError - raise error_class("missing font metrics file: %s" % fontname) + raise FileNotFoundError("missing font metrics file: %s" % fontname) if c != 0 and tfm.checksum != 0 and c != tfm.checksum: raise ValueError('tfm checksum mismatch: %s' % n) @@ -561,7 +539,7 @@ def __init__(self, scale, tfm, texname, vf): except ValueError: nchars = 0 self.widths = [(1000*tfm.width.get(char, 0)) >> 20 - for char in xrange(nchars)] + for char in range(nchars)] def __eq__(self, other): return self.__class__ == other.__class__ and \ @@ -643,9 +621,9 @@ def _read(self): packet_char, packet_ends = None, None packet_len, packet_width = None, None while True: - byte = ord(self.file.read(1)[0]) + byte = self.file.read(1)[0] # If we are in a packet, execute the dvi instructions - if self.state == _dvistate.inpage: + if self.state is _dvistate.inpage: byte_at = self.file.tell()-1 if byte_at == packet_ends: self._finalize_packet(packet_char, packet_width) @@ -701,7 +679,7 @@ def _finalize_packet(self, packet_char, packet_width): self.state = _dvistate.outer def _pre(self, i, x, cs, ds): - if self.state != _dvistate.pre: + if self.state is not _dvistate.pre: raise ValueError("pre command in middle of vf file") if i != 202: raise ValueError("Unknown vf format %d" % i) @@ -774,9 +752,9 @@ def __init__(self, filename): widths, heights, depths = \ [struct.unpack('!%dI' % (len(x)/4), x) for x in (widths, heights, depths)] - for idx, char in enumerate(xrange(bc, ec+1)): - byte0 = ord(char_info[4*idx]) - byte1 = ord(char_info[4*idx+1]) + for idx, char in enumerate(range(bc, ec+1)): + byte0 = char_info[4*idx] + byte1 = char_info[4*idx+1] self.width[char] = _fix2comp(widths[byte0]) self.height[char] = _fix2comp(heights[byte1 >> 4]) self.depth[char] = _fix2comp(depths[byte1 & 0xf]) @@ -833,14 +811,17 @@ class PsfontsMap(object): """ __slots__ = ('_font', '_filename') - def __init__(self, filename): + # Create a filename -> PsfontsMap cache, so that calling + # `PsfontsMap(filename)` with the same filename a second time immediately + # returns the same object. + @lru_cache() + def __new__(cls, filename): + self = object.__new__(cls) self._font = {} - self._filename = filename - if six.PY3 and isinstance(filename, bytes): - encoding = sys.getfilesystemencoding() or 'utf-8' - self._filename = filename.decode(encoding, errors='replace') + self._filename = os.fsdecode(filename) with open(filename, 'rb') as file: self._parse(file) + return self def __getitem__(self, texname): assert isinstance(texname, bytes) @@ -979,10 +960,10 @@ def __init__(self, filename): _log.debug('Result: %s', self.encoding) def __iter__(self): - for name in self.encoding: - yield name + yield from self.encoding - def _parse(self, file): + @staticmethod + def _parse(file): result = [] lines = (line.split(b'%', 1)[0].strip() for line in file) @@ -1001,6 +982,7 @@ def _parse(self, file): return re.findall(br'/([^][{}<>\s]+)', data) +@lru_cache() def find_tex_file(filename, format=None): """ Find a file in the texmf tree. @@ -1024,34 +1006,24 @@ def find_tex_file(filename, format=None): The library that :program:`kpsewhich` is part of. """ - if six.PY3: - # we expect these to always be ascii encoded, but use utf-8 - # out of caution - if isinstance(filename, bytes): - filename = filename.decode('utf-8', errors='replace') - if isinstance(format, bytes): - format = format.decode('utf-8', errors='replace') + # we expect these to always be ascii encoded, but use utf-8 + # out of caution + if isinstance(filename, bytes): + filename = filename.decode('utf-8', errors='replace') + if isinstance(format, bytes): + format = format.decode('utf-8', errors='replace') cmd = ['kpsewhich'] if format is not None: cmd += ['--format=' + format] cmd += [filename] _log.debug('find_tex_file(%s): %s', filename, cmd) - # stderr is unused, but reading it avoids a subprocess optimization - # that breaks EINTR handling in some Python versions: - # http://bugs.python.org/issue12493 - # https://github.com/matplotlib/matplotlib/issues/633 - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) result = pipe.communicate()[0].rstrip() _log.debug('find_tex_file result: %s', result) return result.decode('ascii') -# With multiple text objects per figure (e.g., tick labels) we may end -# up reading the same tfm and vf files many times, so we implement a -# simple cache. TODO: is this worth making persistent? - @lru_cache() def _fontfile(cls, suffix, texname): filename = find_tex_file(texname + suffix) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d80b16d55ab3..487cc812d2f8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -11,12 +11,8 @@ """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import logging +from numbers import Integral import warnings import numpy as np @@ -24,6 +20,7 @@ from matplotlib import rcParams from matplotlib import docstring from matplotlib import __version__ as _mpl_version +from matplotlib import get_backend import matplotlib.artist as martist from matplotlib.artist import Artist, allow_rasterization @@ -44,7 +41,7 @@ from matplotlib.patches import Rectangle from matplotlib.projections import (get_projection_names, process_projection_requirements) -from matplotlib.text import Text, _process_text_args +from matplotlib.text import Text, TextWithDash from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo, TransformedBbox) import matplotlib._layoutbox as layoutbox @@ -82,7 +79,7 @@ def __init__(self): def as_list(self): """ - Return a list of the Axes instances that have been added to the figure + Return a list of the Axes instances that have been added to the figure. """ ia_list = [a for k, a in self._elements] ia_list.sort() @@ -91,7 +88,7 @@ def as_list(self): def get(self, key): """ Return the Axes instance that was added with *key*. - If it is not present, return None. + If it is not present, return *None*. """ item = dict(self._elements).get(key) if item is None: @@ -373,10 +370,6 @@ def __init__(self, self._set_artist_props(self.patch) self.patch.set_aa(False) - self._hold = rcParams['axes.hold'] - if self._hold is None: - self._hold = True - self.canvas = None self._suptitle = None @@ -401,10 +394,8 @@ def __init__(self, self._align_xlabel_grp = cbook.Grouper() self._align_ylabel_grp = cbook.Grouper() - @property - @cbook.deprecated("2.1", alternative="`.Figure.patch`") - def figurePatch(self): - return self.patch + # list of child gridspecs for this figure + self._gridspecs = [] # TODO: I'd like to dynamically add the _repr_html_ method # to the figure in the right context, but then IPython doesn't @@ -429,7 +420,7 @@ def show(self, warn=True): Parameters ---------- - warm : bool + warn : bool If ``True``, issue warning when called on a non-GUI backend Notes @@ -452,10 +443,9 @@ def show(self, warn=True): except NonGuiException: pass if warn: - import warnings - warnings.warn( - "matplotlib is currently using a non-GUI backend, " - "so cannot show the figure") + warnings.warn('Matplotlib is currently using %s, which is a ' + 'non-GUI backend, so cannot show the figure.' + % get_backend()) def _get_axes(self): return self._axstack.as_list() @@ -502,11 +492,6 @@ def set_tight_layout(self, tight): If ``None``, use the ``figure.autolayout`` rcparam instead. If a dict, pass it as kwargs to `.tight_layout`, overriding the default paddings. - - .. - ACCEPTS: [ bool - | dict with keys "pad", "w_pad", "h_pad", "rect" - | None ] """ if tight is None: tight = rcParams['figure.autolayout'] @@ -532,9 +517,11 @@ def set_constrained_layout(self, constrained): overridden. These pads are in inches and default to 3.0/72.0. ``w_pad`` is the width padding and ``h_pad`` is the height padding. - ACCEPTS: [True | False | dict | None ] - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + + Parameters + ---------- + constrained : bool or dict or None """ self._constrained_layout_pads = dict() self._constrained_layout_pads['w_pad'] = None @@ -695,40 +682,64 @@ def suptitle(self, t, **kwargs): """ Add a centered title to the figure. - kwargs are :class:`matplotlib.text.Text` properties. Using figure - coordinates, the defaults are: + Parameters + ---------- + t : str + The title text. - x : 0.5 - The x location of the text in figure coords + x : float, default 0.5 + The x location of the text in figure coordinates. - y : 0.98 - The y location of the text in figure coords + y : float, default 0.98 + The y location of the text in figure coordinates. - horizontalalignment : 'center' - The horizontal alignment of the text + horizontalalignment, ha : {'center', 'left', right'}, default: 'center' + The horizontal alignment of the text. - verticalalignment : 'top' - The vertical alignment of the text + verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \ +default: 'top' + The vertical alignment of the text. - If the `fontproperties` keyword argument is given then the - rcParams defaults for `fontsize` (`figure.titlesize`) and - `fontweight` (`figure.titleweight`) will be ignored in favour - of the `FontProperties` defaults. + fontsize, size : default: :rc:`figure.titlesize` + The font size of the text. See `.Text.set_size` for possible + values. - A :class:`matplotlib.text.Text` instance is returned. + fontweight, weight : default: :rc:`figure.titleweight` + The font weight of the text. See `.Text.set_weight` for possible + values. - Example:: - fig.suptitle('this is the figure title', fontsize=12) + Returns + ------- + text + The `.Text` instance of the title. + + + Other Parameters + ---------------- + fontproperties : None or dict, optional + A dict of font properties. If *fontproperties* is given the + default values for font size and weight are taken from the + `FontProperties` defaults. :rc:`figure.titlesize` and + :rc:`figure.titleweight` are ignored in this case. + + **kwargs + Additional kwargs are :class:`matplotlib.text.Text` properties. + + + Examples + -------- + + >>> fig.suptitle('This is the figure title', fontsize=12) """ manual_position = ('x' in kwargs or 'y' in kwargs) x = kwargs.pop('x', 0.5) y = kwargs.pop('y', 0.98) - if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs): + if 'horizontalalignment' not in kwargs and 'ha' not in kwargs: kwargs['horizontalalignment'] = 'center' - if ('verticalalignment' not in kwargs) and ('va' not in kwargs): + if 'verticalalignment' not in kwargs and 'va' not in kwargs: kwargs['verticalalignment'] = 'top' if 'fontproperties' not in kwargs: @@ -768,29 +779,12 @@ def set_canvas(self, canvas): """ Set the canvas that contains the figure - ACCEPTS: a FigureCanvas instance + Parameters + ---------- + canvas : FigureCanvas """ self.canvas = canvas - @cbook.deprecated("2.0") - def hold(self, b=None): - """ - Set the hold state. If hold is None (default), toggle the - hold state. Else set the hold state to boolean value b. - - e.g.:: - - hold() # toggle hold - hold(True) # hold is on - hold(False) # hold is off - - All "hold" machinery is deprecated. - """ - if b is None: - self._hold = not self._hold - else: - self._hold = b - def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, vmin=None, vmax=None, origin=None, resize=False, **kwargs): """ @@ -860,10 +854,6 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, plt.show() """ - - if not self._hold: - self.clf() - if resize: dpi = self.get_dpi() figsize = [x / dpi for x in (X.shape[1], X.shape[0])] @@ -877,14 +867,14 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, if norm is None: im.set_clim(vmin, vmax) self.images.append(im) - im._remove_method = lambda h: self.images.remove(h) + im._remove_method = self.images.remove self.stale = True return im def set_size_inches(self, w, h=None, forward=True): - """Set the figure size in inches (1in == 2.54cm) + """Set the figure size in inches. - Usage :: + Call signatures:: fig.set_size_inches(w, h) # OR fig.set_size_inches((w, h)) @@ -893,7 +883,7 @@ def set_size_inches(self, w, h=None, forward=True): automatically updated; e.g., you can resize the figure window from the shell - ACCEPTS: a w, h tuple with w, h in inches + ACCEPTS: a (w, h) tuple with w, h in inches See Also -------- @@ -964,7 +954,9 @@ def set_edgecolor(self, color): """ Set the edge color of the Figure rectangle. - ACCEPTS: any matplotlib color - see help(colors) + Parameters + ---------- + color : color """ self.patch.set_edgecolor(color) @@ -972,15 +964,19 @@ def set_facecolor(self, color): """ Set the face color of the Figure rectangle. - ACCEPTS: any matplotlib color - see help(colors) + Parameters + ---------- + color : color """ self.patch.set_facecolor(color) def set_dpi(self, val): """ - Set the dots-per-inch of the figure. + Set the resolution of the figure in dots-per-inch. - ACCEPTS: float + Parameters + ---------- + val : float """ self.dpi = val self.stale = True @@ -989,7 +985,7 @@ def set_figwidth(self, val, forward=True): """ Set the width of the figure in inches. - ACCEPTS: float + .. ACCEPTS: float """ self.set_size_inches(val, self.get_figheight(), forward=forward) @@ -997,15 +993,17 @@ def set_figheight(self, val, forward=True): """ Set the height of the figure in inches. - ACCEPTS: float + .. ACCEPTS: float """ self.set_size_inches(self.get_figwidth(), val, forward=forward) def set_frameon(self, b): """ - Set whether the figure frame (background) is displayed or invisible + Set whether the figure frame (background) is displayed or invisible. - ACCEPTS: boolean + Parameters + ---------- + b : bool """ self.frameon = b self.stale = True @@ -1047,73 +1045,136 @@ def fixlist(args): ret.append(a) return tuple(ret) - key = fixlist(args), fixitems(six.iteritems(kwargs)) + key = fixlist(args), fixitems(kwargs.items()) return key + def add_artist(self, artist, clip=False): + """ + Add any :class:`~matplotlib.artist.Artist` to the figure. + + Usually artists are added to axes objects using + :meth:`matplotlib.axes.Axes.add_artist`, but use this method in the + rare cases that adding directly to the figure is necessary. + + Parameters + ---------- + artist : `~matplotlib.artist.Artist` + The artist to add to the figure. If the added artist has no + transform previously set, its transform will be set to + ``figure.transFigure``. + clip : bool, optional, default ``False`` + An optional parameter ``clip`` determines whether the added artist + should be clipped by the figure patch. Default is *False*, + i.e. no clipping. + + Returns + ------- + artist : The added `~matplotlib.artist.Artist` + """ + artist.set_figure(self) + self.artists.append(artist) + artist._remove_method = self.artists.remove + + if not artist.is_transform_set(): + artist.set_transform(self.transFigure) + + if clip: + artist.set_clip_path(self.patch) + + self.stale = True + return artist + + @docstring.dedent_interpd def add_axes(self, *args, **kwargs): """ Add an axes to the figure. - Call signature:: + Call signatures:: add_axes(rect, projection=None, polar=False, **kwargs) + add_axes(ax) Parameters ---------- + rect : sequence of float The dimensions [left, bottom, width, height] of the new axes. All quantities are in fractions of figure width and height. projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ -'polar', rectilinear'}, optional - The projection type of the axes. +'polar', 'rectilinear', str}, optional + The projection type of the `~.axes.Axes`. *str* is the name of + a custom projection, see `~matplotlib.projections`. The default + None results in a 'rectilinear' projection. polar : boolean, optional If True, equivalent to projection='polar'. + sharex, sharey : `~.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. + The axis will have the same limits, ticks, and scale as the axis + of the shared axes. + + label : str + A label for the returned axes. + + Other Parameters + ---------------- **kwargs This method also takes the keyword arguments for - :class:`~matplotlib.axes.Axes`. + the returned axes class. The keyword arguments for the + rectilinear axes class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used, see the actual axes + class. + %(Axes)s Returns ------- - axes : Axes - The added 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. + + Notes + ----- + If the figure already has an axes with key (*args*, + *kwargs*) then it will simply make that axes current and + return it. This behavior is deprecated. Meanwhile, if you do + not want this behavior (i.e., you want to force the creation of a + new axes), you must use a unique set of args and kwargs. The axes + *label* attribute has been exposed for this purpose: if you want + two axes that are otherwise identical to be added to the figure, + make sure you give them unique labels. + + In rare circumstances, `.add_axes` may be called with a single + argument, a axes instance already created in the present figure but + not in the figure's list of axes. + + See Also + -------- + .Figure.add_subplot + .pyplot.subplot + .pyplot.axes + .Figure.subplots + .pyplot.subplots Examples -------- Some simple examples:: rect = l, b, w, h - fig.add_axes(rect) + fig = plt.figure(1) + fig.add_axes(rect,label=label1) + fig.add_axes(rect,label=label2) fig.add_axes(rect, frameon=False, facecolor='g') fig.add_axes(rect, polar=True) - fig.add_axes(rect, projection='polar') - fig.add_axes(ax) - - If the figure already has an axes with the same parameters, then it - will simply make that axes current and return it. This behavior - has been deprecated as of Matplotlib 2.1. Meanwhile, if you do - not want this behavior (i.e., you want to force the creation of a - new Axes), you must use a unique set of args and kwargs. The axes - :attr:`~matplotlib.axes.Axes.label` attribute has been exposed for this - purpose: if you want two axes that are otherwise identical to be added - to the figure, make sure you give them unique labels:: - - fig.add_axes(rect, label='axes1') - fig.add_axes(rect, label='axes2') - - In rare circumstances, add_axes may be called with a single - argument, an Axes instance already created in the present - figure but not in the figure's list of axes. For example, - if an axes has been removed with :meth:`delaxes`, it can - be restored with:: - + ax=fig.add_axes(rect, projection='polar') + fig.delaxes(ax) fig.add_axes(ax) - - In all cases, the :class:`~matplotlib.axes.Axes` instance - will be returned. """ + if not len(args): return @@ -1150,76 +1211,128 @@ def add_axes(self, *args, **kwargs): self._axstack.add(key, a) self.sca(a) - a._remove_method = self.__remove_ax + a._remove_method = self._remove_ax self.stale = True a.stale_callback = _stale_figure_callback return a + @docstring.dedent_interpd def add_subplot(self, *args, **kwargs): """ - Add a subplot. + Add an `~.axes.Axes` to the figure as part of a subplot arrangement. Call signatures:: add_subplot(nrows, ncols, index, **kwargs) add_subplot(pos, **kwargs) + add_subplot(ax) Parameters ---------- *args Either a 3-digit integer or three separate integers describing the position of the subplot. If the three - integers are R, C, and P in order, the subplot will take - the Pth position on a grid with R rows and C columns. + integers are *nrows*, *ncols*, and *index* in order, the + subplot will take the *index* position on a grid with *nrows* + rows and *ncols* columns. *index* starts at 1 in the upper left + corner and increases to the right. + + *pos* is a three digit integer, where the first digit is the + number of rows, the second the number of columns, and the third + the index of the subplot. i.e. fig.add_subplot(235) is the same as + fig.add_subplot(2, 3, 5). Note that all integers must be less than + 10 for this form to work. projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ -'polar', rectilinear'}, optional - The projection type of the axes. +'polar', 'rectilinear', str}, optional + The projection type of the subplot (`~.axes.Axes`). *str* is the + name of a custom projection, see `~matplotlib.projections`. The + default None results in a 'rectilinear' projection. polar : boolean, optional If True, equivalent to projection='polar'. + sharex, sharey : `~.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. + The axis will have the same limits, ticks, and scale as the axis + of the shared axes. + + label : str + A label for the returned axes. + + Other Parameters + ---------------- **kwargs This method also takes the keyword arguments for - :class:`~matplotlib.axes.Axes`. + the returned axes base class. The keyword arguments for the + rectilinear base class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used. + %(Axes)s Returns ------- - axes : Axes - The axes of the subplot. + axes : an `.axes.SubplotBase` subclass of `~.axes.Axes` (or a \ + 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 ----- If the figure already has a subplot with key (*args*, *kwargs*) then it will simply make that subplot current and - return it. This behavior is deprecated. + return it. This behavior is deprecated. Meanwhile, if you do + not want this behavior (i.e., you want to force the creation of a + new suplot), you must use a unique set of args and kwargs. The axes + *label* attribute has been exposed for this purpose: if you want + two subplots that are otherwise identical to be added to the figure, + make sure you give them unique labels. + + In rare circumstances, `.add_subplot` may be called with a single + argument, a subplot axes instance already created in the + present figure but not in the figure's list of axes. + + See Also + -------- + .Figure.add_axes + .pyplot.subplot + .pyplot.axes + .Figure.subplots + .pyplot.subplots Examples -------- :: - fig.add_subplot(111) + fig=plt.figure(1) + fig.add_subplot(221) # equivalent but more general - fig.add_subplot(1, 1, 1) + ax1=fig.add_subplot(2, 2, 1) - # add subplot with red background - fig.add_subplot(212, facecolor='r') + # add a subplot with no frame + ax2=fig.add_subplot(222, frameon=False) # add a polar subplot - fig.add_subplot(111, projection='polar') + fig.add_subplot(223, projection='polar') - # add Subplot instance sub - fig.add_subplot(sub) + # add a red subplot that share the x-axis with ax1 + fig.add_subplot(224, sharex=ax1, facecolor='red') - See Also - -------- - matplotlib.pyplot.subplot : for an explanation of the args. + #delete x2 from the figure + fig.delaxes(ax2) + + #add x2 to the figure again + fig.add_subplot(ax2) """ if not len(args): return - if len(args) == 1 and isinstance(args[0], int): + if len(args) == 1 and isinstance(args[0], Integral): if not 100 <= args[0] <= 999: raise ValueError("Integer subplot specification must be a " "three-digit number, not {}".format(args[0])) @@ -1257,7 +1370,7 @@ def add_subplot(self, *args, **kwargs): a = subplot_class_factory(projection_class)(self, *args, **kwargs) self._axstack.add(key, a) self.sca(a) - a._remove_method = self.__remove_ax + a._remove_method = self._remove_ax self.stale = True a.stale_callback = _stale_figure_callback return a @@ -1267,10 +1380,13 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, """ Add a set of subplots to this figure. + This utility wrapper makes it convenient to create common layouts of + subplots in a single call. + Parameters ---------- - nrows, ncols : int, default: 1 - Number of rows/cols of the subplot grid. + nrows, ncols : int, optional, default: 1 + Number of rows/columns of the subplot grid. sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False Controls sharing of properties among x (`sharex`) or y (`sharey`) @@ -1287,7 +1403,7 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, labels of the bottom subplot are created. Similarly, when subplots have a shared y-axis along a row, only the y tick labels of the first column subplot are created. To later turn other subplots' - ticklabels on, use :meth:`~matplotlib.axes.Axes.tick_params`. + ticklabels on, use `~matplotlib.axes.Axes.tick_params`. squeeze : bool, optional, default: True - If True, extra dimensions are squeezed out from the returned @@ -1304,28 +1420,71 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, is always a 2D array containing Axes instances, even if it ends up being 1x1. - subplot_kw : dict, default: {} + subplot_kw : dict, optional Dict with keywords passed to the :meth:`~matplotlib.figure.Figure.add_subplot` call used to create - each subplots. + each subplot. - gridspec_kw : dict, default: {} + gridspec_kw : dict, optional Dict with keywords passed to the - :class:`~matplotlib.gridspec.GridSpec` constructor used to create + `~matplotlib.gridspec.GridSpec` constructor used to create the grid the subplots are placed on. Returns ------- - ax : single Axes object or array of Axes objects - The added axes. The dimensions of the resulting array can be - controlled with the squeeze keyword, see above. + ax : `~.axes.Axes` object or array of Axes objects. + *ax* can be either a single `~matplotlib.axes.Axes` object or + an array of Axes objects if more than one subplot was created. The + dimensions of the resulting array can be controlled with the + squeeze keyword, see above. - See Also + Examples -------- - pyplot.subplots : pyplot API; docstring includes examples. - """ + :: + + # First create some toy data: + x = np.linspace(0, 2*np.pi, 400) + y = np.sin(x**2) + + # Create a figure + plt.figure(1, clear=True) + + # Creates a subplot + ax = fig.subplots() + ax.plot(x, y) + ax.set_title('Simple plot') + + # Creates two subplots and unpacks the output array immediately + ax1, ax2 = fig.subplots(1, 2, sharey=True) + ax1.plot(x, y) + ax1.set_title('Sharing Y axis') + ax2.scatter(x, y) + + # Creates four polar axes, and accesses them through the + # returned array + axes = fig.subplots(2, 2, subplot_kw=dict(polar=True)) + axes[0, 0].plot(x, y) + axes[1, 1].scatter(x, y) + + # Share a X axis with each column of subplots + fig.subplots(2, 2, sharex='col') + + # Share a Y axis with each row of subplots + fig.subplots(2, 2, sharey='row') + + # Share both X and Y axes with all subplots + fig.subplots(2, 2, sharex='all', sharey='all') + + # Note that this is the same as + fig.subplots(2, 2, sharex=True, sharey=True) + + See Also + -------- + .pyplot.subplots + .Figure.add_subplot + .pyplot.subplot + """ - # for backwards compatibility if isinstance(sharex, bool): sharex = "all" if sharex else "none" if isinstance(sharey, bool): @@ -1337,7 +1496,7 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, # In most cases, no error will ever occur, but mysterious behavior # will result because what was intended to be the subplot index is # instead treated as a bool for sharex. - if isinstance(sharex, int): + if isinstance(sharex, Integral): warnings.warn( "sharex argument to subplots() was an integer. " "Did you intend to use subplot() (without 's')?") @@ -1351,12 +1510,16 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, subplot_kw = {} if gridspec_kw is None: gridspec_kw = {} + # don't mutate kwargs passed by user... + subplot_kw = subplot_kw.copy() + gridspec_kw = gridspec_kw.copy() if self.get_constrained_layout(): gs = GridSpec(nrows, ncols, figure=self, **gridspec_kw) else: # this should turn constrained_layout off if we don't want it gs = GridSpec(nrows, ncols, figure=None, **gridspec_kw) + self._gridspecs.append(gs) # Create array to hold all axes. axarr = np.empty((nrows, ncols), dtype=object) @@ -1390,7 +1553,7 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, # Returned axis array will be always 2-d, even if nrows=ncols=1. return axarr - def __remove_ax(self, ax): + def _remove_ax(self, ax): def _reset_loc_form(axis): axis.set_major_formatter(axis.get_major_formatter()) axis.set_major_locator(axis.get_major_locator()) @@ -1402,9 +1565,8 @@ def _break_share_link(ax, grouper): if len(siblings) > 1: grouper.remove(ax) for last_ax in siblings: - if ax is last_ax: - continue - return last_ax + if ax is not last_ax: + return last_ax return None self.delaxes(ax) @@ -1474,10 +1636,7 @@ def draw(self, renderer): try: renderer.open_group('figure') if self.get_constrained_layout() and self.axes: - if True: - self.execute_constrained_layout(renderer) - else: - pass + self.execute_constrained_layout(renderer) if self.get_tight_layout() and self.axes: try: self.tight_layout(renderer, @@ -1563,172 +1722,7 @@ def legend(self, *args, **kwargs): Other Parameters ---------------- - loc : int or string or pair of floats, default: 'upper right' - The location of the legend. Possible codes are: - - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= - - - Alternatively can be a 2-tuple giving ``x, y`` of the lower-left - corner of the legend in axes coordinates (in which case - ``bbox_to_anchor`` will be ignored). - - bbox_to_anchor : `.BboxBase` or pair of floats - Specify any arbitrary location for the legend in `bbox_transform` - coordinates (default Axes coordinates). - - For example, to put the legend's upper right hand corner in the - center of the axes the following keywords can be used:: - - loc='upper right', bbox_to_anchor=(0.5, 0.5) - - ncol : integer - The number of columns that the legend has. Default is 1. - - prop : None or :class:`matplotlib.font_manager.FontProperties` or dict - The font properties of the legend. If None (default), the current - :data:`matplotlib.rcParams` will be used. - - fontsize : int or float or {'xx-small', 'x-small', 'small', 'medium', \ -'large', 'x-large', 'xx-large'} - Controls the font size of the legend. If the value is numeric the - size will be the absolute font size in points. String values are - relative to the current default font size. This argument is only - used if `prop` is not specified. - - numpoints : None or int - The number of marker points in the legend when creating a legend - entry for a `.Line2D` (line). - Default is ``None``, which will take the value from - :rc:`legend.numpoints`. - - scatterpoints : None or int - The number of marker points in the legend when creating - a legend entry for a `.PathCollection` (scatter plot). - Default is ``None``, which will take the value from - :rc:`legend.scatterpoints`. - - scatteryoffsets : iterable of floats - The vertical offset (relative to the font size) for the markers - created for a scatter plot legend entry. 0.0 is at the base the - legend text, and 1.0 is at the top. To draw all markers at the - same height, set to ``[0.5]``. Default is ``[0.375, 0.5, 0.3125]``. - - markerscale : None or int or float - The relative size of legend markers compared with the originally - drawn ones. - Default is ``None``, which will take the value from - :rc:`legend.markerscale`. - - markerfirst : bool - If *True*, legend marker is placed to the left of the legend label. - If *False*, legend marker is placed to the right of the legend - label. - Default is *True*. - - frameon : None or bool - Control whether the legend should be drawn on a patch - (frame). - Default is ``None``, which will take the value from - :rc:`legend.frameon`. - - fancybox : None or bool - Control whether round edges should be enabled around the - :class:`~matplotlib.patches.FancyBboxPatch` which makes up the - legend's background. - Default is ``None``, which will take the value from - :rc:`legend.fancybox`. - - shadow : None or bool - Control whether to draw a shadow behind the legend. - Default is ``None``, which will take the value from - :rc:`legend.shadow`. - - framealpha : None or float - Control the alpha transparency of the legend's background. - Default is ``None``, which will take the value from - :rc:`legend.framealpha`. If shadow is activated and - *framealpha* is ``None``, the default value is ignored. - - facecolor : None or "inherit" or a color spec - Control the legend's background color. - Default is ``None``, which will take the value from - :rc:`legend.facecolor`. If ``"inherit"``, it will take - :rc:`axes.facecolor`. - - edgecolor : None or "inherit" or a color spec - Control the legend's background patch edge color. - Default is ``None``, which will take the value from - :rc:`legend.edgecolor` If ``"inherit"``, it will take - :rc:`axes.edgecolor`. - - mode : {"expand", None} - If `mode` is set to ``"expand"`` the legend will be horizontally - expanded to fill the axes area (or `bbox_to_anchor` if defines - the legend's size). - - bbox_transform : None or :class:`matplotlib.transforms.Transform` - The transform for the bounding box (`bbox_to_anchor`). For a value - of ``None`` (default) the Axes' - :data:`~matplotlib.axes.Axes.transAxes` transform will be used. - - title : str or None - The legend's title. Default is no title (``None``). - - borderpad : float or None - The fractional whitespace inside the legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderpad`. - - labelspacing : float or None - The vertical space between the legend entries. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.labelspacing`. - - handlelength : float or None - The length of the legend handles. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handlelength`. - - handletextpad : float or None - The pad between the legend handle and text. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handletextpad`. - - borderaxespad : float or None - The pad between the axes and legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderaxespad`. - - columnspacing : float or None - The spacing between columns. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.columnspacing`. - - handler_map : dict or None - The custom dictionary mapping instances or types to a legend - handler. This `handler_map` updates the default handler map - found at :func:`matplotlib.legend.Legend.get_legend_handler_map`. + %(_legend_kw_doc)s Returns ------- @@ -1757,37 +1751,68 @@ def legend(self, *args, **kwargs): pass l = mlegend.Legend(self, handles, labels, *extra_args, **kwargs) self.legends.append(l) - l._remove_method = lambda h: self.legends.remove(h) + l._remove_method = self.legends.remove self.stale = True return l @docstring.dedent_interpd - def text(self, x, y, s, *args, **kwargs): + def text(self, x, y, s, fontdict=None, withdash=False, **kwargs): """ Add text to figure. - Call signature:: + Parameters + ---------- + x, y : float + The position to place the text. By default, this is in figure + coordinates, floats in [0, 1]. The coordinate system can be changed + using the *transform* keyword. + + s : str + The text string. - text(x, y, s, fontdict=None, **kwargs) + fontdict : dictionary, optional, default: None + A dictionary to override the default text properties. If fontdict + is None, the defaults are determined by your rc parameters. A + property in *kwargs* override the same property in fontdict. - Add text to figure at location *x*, *y* (relative 0-1 - coords). See :func:`~matplotlib.pyplot.text` for the meaning - of the other arguments. + withdash : boolean, optional, default: False + Creates a `~matplotlib.text.TextWithDash` instance instead of a + `~matplotlib.text.Text` instance. - kwargs control the :class:`~matplotlib.text.Text` properties: + Other Parameters + ---------------- + **kwargs : `~matplotlib.text.Text` properties + Other miscellaneous text parameters. + %(Text)s + + Returns + ------- + text : `~.text.Text` - %(Text)s + See Also + -------- + .Axes.text + .pyplot.text """ + default = dict(transform=self.transFigure) + + if withdash: + text = TextWithDash(x=x, y=y, text=s) + else: + text = Text(x=x, y=y, text=s) + + text.update(default) + if fontdict is not None: + text.update(fontdict) + text.update(kwargs) - override = _process_text_args({}, *args, **kwargs) - t = Text(x=x, y=y, text=s) + text.set_figure(self) + text.stale_callback = _stale_figure_callback - t.update(override) - self._set_artist_props(t) - self.texts.append(t) - t._remove_method = lambda h: self.texts.remove(h) + self.texts.append(text) + text._remove_method = self.texts.remove self.stale = True - return t + return text def _set_artist_props(self, a): if a != self: @@ -1820,11 +1845,8 @@ def gca(self, **kwargs): # if the user has specified particular projection detail # then build up a key which can represent this else: - # we don't want to modify the original kwargs - # so take a copy so that we can do what we like to it - kwargs_copy = kwargs.copy() projection_class, _, key = process_projection_requirements( - self, **kwargs_copy) + self, **kwargs) # let the returned axes have any gridspec by removing it from # the key @@ -1872,9 +1894,8 @@ def _gci(self): return None def __getstate__(self): - state = super(Figure, self).__getstate__() + state = super().__getstate__() - # print('\n\n\nStarting pickle') # the axobservers cannot currently be pickled. # Additionally, the canvas cannot currently be pickled, but this has # the benefit of meaning that a figure can be detached from one canvas, @@ -1886,18 +1907,14 @@ def __getstate__(self): # add version information to the state state['__mpl_version__'] = _mpl_version - # check to see if the figure has a manager and whether it is registered - # with pyplot - if getattr(self.canvas, 'manager', None) is not None: - manager = self.canvas.manager - import matplotlib._pylab_helpers - if manager in list(six.itervalues( - matplotlib._pylab_helpers.Gcf.figs)): - state['_restore_to_pylab'] = True - - # set all the layoutbox information to None. kiwisolver - # objects can't be pickeled, so we lose the layout options - # at this point. + # check whether the figure manager (if any) is registered with pyplot + from matplotlib import _pylab_helpers + if getattr(self.canvas, 'manager', None) \ + in _pylab_helpers.Gcf.figs.values(): + state['_restore_to_pylab'] = True + + # set all the layoutbox information to None. kiwisolver objects can't + # be pickled, so we lose the layout options at this point. state.pop('_layoutbox', None) # suptitle: if self._suptitle is not None: @@ -1953,7 +1970,7 @@ def add_axobserver(self, func): """Whenever the axes state change, ``func(self)`` will be called.""" self._axobservers.append(func) - def savefig(self, fname, **kwargs): + def savefig(self, fname, *, frameon=None, transparent=None, **kwargs): """ Save the current figure. @@ -1962,7 +1979,7 @@ def savefig(self, fname, **kwargs): savefig(fname, dpi=None, facecolor='w', edgecolor='w', orientation='portrait', papertype=None, format=None, transparent=False, bbox_inches=None, pad_inches=0.1, - frameon=None) + frameon=None, metadata=None) The output formats available depend on the backend being used. @@ -1976,8 +1993,7 @@ def savefig(self, fname, **kwargs): If *format* is *None* and *fname* is a string, the output format is deduced from the extension of the filename. If - the filename has no extension, the value of the rc parameter - ``savefig.format`` is used. + the filename has no extension, :rc:`savefig.format` is used. If *fname* is not a string, remember to specify *format* to ensure that the correct backend is used. @@ -1985,19 +2001,27 @@ def savefig(self, fname, **kwargs): Other Parameters ---------------- - dpi : [ *None* | scalar > 0 | 'figure'] - The resolution in dots per inch. If *None* it will default to - the value ``savefig.dpi`` in the matplotlibrc file. If 'figure' - it will set the dpi to be the value of the figure. + dpi : [ *None* | scalar > 0 | 'figure' ] + The resolution in dots per inch. If *None*, defaults to + :rc:`savefig.dpi`. If 'figure', uses the figure's dpi value. + + quality : [ *None* | 1 <= scalar <= 100 ] + The image quality, on a scale from 1 (worst) to 95 (best). + Applicable only if *format* is jpg or jpeg, ignored otherwise. + If *None*, defaults to :rc:`savefig.jpeg_quality` (95 by default). + Values above 95 should be avoided; 100 completely disables the + JPEG quantization stage. facecolor : color spec or None, optional - the facecolor of the figure; if None, defaults to savefig.facecolor + The facecolor of the figure; if *None*, defaults to + :rc:`savefig.facecolor`. edgecolor : color spec or None, optional - the edgecolor of the figure; if None, defaults to savefig.edgecolor + The edgecolor of the figure; if *None*, defaults to + :rc:`savefig.edgecolor` orientation : {'landscape', 'portrait'} - not supported on all backends; currently only on postscript output + Currently only supported by the postscript backend. papertype : str One of 'letter', 'legal', 'executive', 'ledger', 'a0' through @@ -2035,11 +2059,22 @@ def savefig(self, fname, **kwargs): A list of extra artists that will be considered when the tight bbox is calculated. + metadata : dict, optional + Key/value pairs to store in the image metadata. The supported keys + and defaults depend on the image format and backend: + + - 'png' with Agg backend: See the parameter ``metadata`` of + `~.FigureCanvasAgg.print_png`. + - 'pdf' with pdf backend: See the parameter ``metadata`` of + `~.backend_pdf.PdfPages`. + - 'eps' and 'ps' with PS backend: Only 'Creator' is supported. + """ kwargs.setdefault('dpi', rcParams['savefig.dpi']) - frameon = kwargs.pop('frameon', rcParams['savefig.frameon']) - transparent = kwargs.pop('transparent', - rcParams['savefig.transparent']) + if frameon is None: + frameon = rcParams['savefig.frameon'] + if transparent is None: + transparent = rcParams['savefig.transparent'] if transparent: kwargs.setdefault('facecolor', 'none') @@ -2074,7 +2109,7 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): """ Create a colorbar for a ScalarMappable instance, *mappable*. - Documentation for the pylab thin wrapper: + Documentation for the pyplot thin wrapper: %(colorbar_doc)s """ if ax is None: @@ -2089,7 +2124,6 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): cax, kw = cbar.make_axes_gridspec(ax, **kw) else: cax, kw = cbar.make_axes(ax, **kw) - cax._hold = True # need to remove kws that cannot be passed to Colorbar NON_COLORBAR_KEYS = ['fraction', 'pad', 'shrink', 'aspect', 'anchor', @@ -2101,13 +2135,9 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): self.stale = True return cb - def subplots_adjust(self, *args, **kwargs): + def subplots_adjust(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): """ - Call signature:: - - subplots_adjust(left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None) - Update the :class:`SubplotParams` with *kwargs* (defaulting to rc when *None*) and update the subplot locations. @@ -2118,8 +2148,7 @@ def subplots_adjust(self, *args, **kwargs): "but that is incompatible with subplots_adjust and " "or tight_layout: setting " "constrained_layout==False. ") - self.subplotpars.update(*args, **kwargs) - + self.subplotpars.update(left, bottom, right, top, wspace, hspace) for ax in self.axes: if not isinstance(ax, SubplotBase): # Check if sharing a subplots axis @@ -2202,7 +2231,7 @@ def waitforbuttonpress(self, timeout=-1): def get_default_bbox_extra_artists(self): bbox_artists = [artist for artist in self.get_children() - if artist.get_visible()] + if (artist.get_visible() and artist.get_in_layout())] for ax in self.axes: if ax.get_visible(): bbox_artists.extend(ax.get_default_bbox_extra_artists()) @@ -2210,18 +2239,44 @@ def get_default_bbox_extra_artists(self): bbox_artists.remove(self.patch) return bbox_artists - def get_tightbbox(self, renderer): + def get_tightbbox(self, renderer, bbox_extra_artists=None): """ Return a (tight) bounding box of the figure in inches. - It only accounts axes title, axis labels, and axis - ticklabels. Needs improvement. + Artists that have ``artist.set_in_layout(False)`` are not included + in the bbox. + + Parameters + ---------- + renderer : `.RendererBase` instance + renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + bbox_extra_artists : list of `.Artist` or ``None`` + List of artists to include in the tight bounding box. If + ``None`` (default), then all artist children of each axes are + included in the tight bounding box. + + Returns + ------- + bbox : `.BboxBase` + containing the bounding box (in figure inches). """ bb = [] + if bbox_extra_artists is None: + artists = self.get_default_bbox_extra_artists() + else: + artists = bbox_extra_artists + + for a in artists: + bbox = a.get_tightbbox(renderer) + if bbox is not None and (bbox.width != 0 or bbox.height != 0): + bb.append(bbox) + for ax in self.axes: if ax.get_visible(): - bb.append(ax.get_tightbbox(renderer)) + bb.append(ax.get_tightbbox(renderer, bbox_extra_artists)) if len(bb) == 0: return self.bbox_inches @@ -2248,20 +2303,18 @@ def execute_constrained_layout(self, renderer=None): See also `.set_constrained_layout_pads`. """ - from matplotlib._constrained_layout import (do_constrained_layout) + from matplotlib._constrained_layout import do_constrained_layout _log.debug('Executing constrainedlayout') if self._layoutbox is None: - warnings.warn("Calling figure.constrained_layout, but figure " - "not setup to do constrained layout. " - " You either called GridSpec without the " - "fig keyword, you are using plt.subplot, " - "or you need to call figure or subplots" - "with the constrained_layout=True kwarg.") + warnings.warn("Calling figure.constrained_layout, but figure not " + "setup to do constrained layout. You either called " + "GridSpec without the fig keyword, you are using " + "plt.subplot, or you need to call figure or " + "subplots with the constrained_layout=True kwarg.") return w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads() # convert to unit-relative lengths - fig = self width, height = fig.get_size_inches() w_pad = w_pad / width @@ -2273,22 +2326,32 @@ def execute_constrained_layout(self, renderer=None): def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, rect=None): """ - Adjust subplot parameters to give specified padding. + Automatically adjust subplot parameters to give specified padding. + + To exclude an artist on the axes from the bounding box calculation + that determines the subplot parameters (i.e. legend, or annotation), + then set `a.set_in_layout(False)` for that artist. Parameters ---------- - pad : float - padding between the figure edge and the edges of subplots, - as a fraction of the font-size. + renderer : subclass of `~.backend_bases.RendererBase`, optional + Defaults to the renderer for the figure. + pad : float, optional + Padding between the figure edge and the edges of subplots, + as a fraction of the font size. h_pad, w_pad : float, optional - padding (height/width) between edges of adjacent subplots. - Defaults to `pad_inches`. - + Padding (height/width) between edges of adjacent subplots, + as a fraction of the font size. Defaults to *pad*. rect : tuple (left, bottom, right, top), optional - a rectangle (left, bottom, right, top) in the normalized + A rectangle (left, bottom, right, top) in the normalized figure coordinate that the whole subplots area (including labels) will fit into. Default is (0, 0, 1, 1). + + See Also + -------- + .Figure.set_tight_layout + .pyplot.tight_layout """ from .tight_layout import ( @@ -2469,33 +2532,96 @@ def align_labels(self, axs=None): self.align_xlabels(axs=axs) self.align_ylabels(axs=axs) + def add_gridspec(self, nrows, ncols, **kwargs): + """ + Return a `.GridSpec` that has this figure as a parent. This allows + complex layout of axes in the figure. + + Parameters + ---------- + nrows : int + Number of rows in grid. + + ncols : int + Number or columns in grid. + + Returns + ------- + gridspec : `.GridSpec` + + Other Parameters + ---------------- + *kwargs* are passed to `.GridSpec`. + + See Also + -------- + matplotlib.pyplot.subplots + + Examples + -------- + Adding a subplot that spans two rows:: + + fig = plt.figure() + gs = fig.add_gridspec(2, 2) + ax1 = fig.add_subplot(gs[0, 0]) + ax2 = fig.add_subplot(gs[1, 0]) + # spans two rows: + ax3 = fig.add_subplot(gs[:, 1]) + + """ + + _ = kwargs.pop('figure', None) # pop in case user has added this... + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs) + self._gridspecs.append(gs) + return gs + def figaspect(arg): """ - Create a figure with specified aspect ratio. If *arg* is a number, - use that aspect ratio. If *arg* is an array, figaspect will - determine the width and height for a figure that would fit array - preserving aspect ratio. The figure width, height in inches are - returned. Be sure to create an axes with equal with and height, - e.g., - - Example usage:: - - # make a figure twice as tall as it is wide - w, h = figaspect(2.) - fig = Figure(figsize=(w,h)) - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) - ax.imshow(A, **kwargs) - - - # make a figure with the proper aspect for an array - A = rand(5,3) - w, h = figaspect(A) - fig = Figure(figsize=(w,h)) - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) - ax.imshow(A, **kwargs) - - Thanks to Fernando Perez for this function + Calculate the width and height for a figure with a specified aspect ratio. + + While the height is taken from :rc:`figure.figsize`, the width is + adjusted to match the desired aspect ratio. Additionally, it is ensured + that the width is in the range [4., 16.] and the height is in the range + [2., 16.]. If necessary, the default height is adjusted to ensure this. + + Parameters + ---------- + arg : scalar or 2d array + If a scalar, this defines the aspect ratio (i.e. the ratio height / + width). + In case of an array the aspect ratio is number of rows / number of + columns, so that the array could be fitted in the figure undistorted. + + Returns + ------- + width, height + The figure size in inches. + + Notes + ----- + If you want to create an axes within the figure, that still preserves the + aspect ratio, be sure to create it with equal width and height. See + examples below. + + Thanks to Fernando Perez for this function. + + Examples + -------- + Make a figure twice as tall as it is wide:: + + w, h = figaspect(2.) + fig = Figure(figsize=(w, h)) + ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) + ax.imshow(A, **kwargs) + + Make a figure with the proper aspect for an array:: + + A = rand(5,3) + w, h = figaspect(A) + fig = Figure(figsize=(w, h)) + ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) + ax.imshow(A, **kwargs) """ isarray = hasattr(arg, 'shape') and not np.isscalar(arg) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 5900fc9b1841..42542b101096 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -19,52 +19,36 @@ platforms, so if a font is installed, it is much more likely to be found. """ -from __future__ import absolute_import, division, print_function -import six - -""" -KNOWN ISSUES - - - documentation - - font variant is untested - - font stretch is incomplete - - font size is incomplete - - default font algorithm needs improvement and testing - - setWeights function needs improvement - - 'light' is an invalid weight value, remove it. - - update_fonts not implemented - -Authors : John Hunter - Paul Barrett - Michael Droettboom -Copyright : John Hunter (2004,2005), Paul Barrett (2004,2005) -License : matplotlib license (PSF compatible) - The font directory code is from ttfquery, - see license/LICENSE_TTFQUERY. -""" - -from collections import Iterable +# KNOWN ISSUES +# +# - documentation +# - font variant is untested +# - font stretch is incomplete +# - font size is incomplete +# - default font algorithm needs improvement and testing +# - setWeights function needs improvement +# - 'light' is an invalid weight value, remove it. +# - update_fonts not implemented + +from functools import lru_cache import json +import logging import os +from pathlib import Path +import subprocess import sys try: from threading import Timer except ImportError: from dummy_threading import Timer import warnings -import logging +import matplotlib as mpl from matplotlib import afm, cbook, ft2font, rcParams, get_cachedir -from matplotlib.compat import subprocess from matplotlib.fontconfig_pattern import ( parse_fontconfig_pattern, generate_fontconfig_pattern) -try: - from functools import lru_cache -except ImportError: - from backports.functools_lru_cache import lru_cache - _log = logging.getLogger(__name__) USE_FONTCONFIG = False @@ -144,18 +128,13 @@ "/Network/Library/Fonts/", "/System/Library/Fonts/", # fonts installed via MacPorts - "/opt/local/share/fonts" - "" + "/opt/local/share/fonts", + "", ] if not USE_FONTCONFIG and sys.platform != 'win32': - home = os.environ.get('HOME') - if home is not None: - # user fonts on OSX - path = os.path.join(home, 'Library', 'Fonts') - OSXFontDirectories.append(path) - path = os.path.join(home, '.fonts') - X11FontDirectories.append(path) + OSXFontDirectories.append(str(Path.home() / "Library/Fonts")) + X11FontDirectories.append(str(Path.home() / ".fonts")) def get_fontext_synonyms(fontext): @@ -170,40 +149,30 @@ def get_fontext_synonyms(fontext): def list_fonts(directory, extensions): """ - Return a list of all fonts matching any of the extensions, - possibly upper-cased, found recursively under the directory. + Return a list of all fonts matching any of the extensions, found + recursively under the directory. """ - pattern = ';'.join(['*.%s;*.%s' % (ext, ext.upper()) - for ext in extensions]) - return cbook.listFiles(directory, pattern) + extensions = ["." + ext for ext in extensions] + return [str(path) + for path in filter(Path.is_file, Path(directory).glob("**/*.*")) + if path.suffix in extensions] def win32FontDirectory(): - """ + r""" Return the user-specified font directory for Win32. This is looked up from the registry key:: - \\\\HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\Fonts + \\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Fonts If the key is not found, $WINDIR/Fonts will be returned. """ + import winreg try: - from six.moves import winreg - except ImportError: - pass # Fall through to default - else: - try: - user = winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) - try: - try: - return winreg.QueryValueEx(user, 'Fonts')[0] - except OSError: - pass # Fall through to default - finally: - winreg.CloseKey(user) - except OSError: - pass # Fall through to default - return os.path.join(os.environ['WINDIR'], 'Fonts') + with winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) as user: + return winreg.QueryValueEx(user, 'Fonts')[0] + except OSError: + return os.path.join(os.environ['WINDIR'], 'Fonts') def win32InstalledFonts(directory=None, fontext='ttf'): @@ -214,62 +183,41 @@ def win32InstalledFonts(directory=None, fontext='ttf'): 'afm'. """ - from six.moves import winreg + import winreg + if directory is None: directory = win32FontDirectory() fontext = get_fontext_synonyms(fontext) - key, items = None, set() + items = set() for fontdir in MSFontDirectories: try: - local = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, fontdir) - except OSError: - continue - if not local: - return list_fonts(directory, fontext) - try: - for j in range(winreg.QueryInfoKey(local)[1]): - try: + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, fontdir) as local: + for j in range(winreg.QueryInfoKey(local)[1]): key, direc, tp = winreg.EnumValue(local, j) - if not isinstance(direc, six.string_types): + if not isinstance(direc, str): continue # Work around for https://bugs.python.org/issue25778, which # is fixed in Py>=3.6.1. direc = direc.split("\0", 1)[0] - if not os.path.dirname(direc): - direc = os.path.join(directory, direc) - direc = os.path.abspath(direc).lower() - if os.path.splitext(direc)[1][1:] in fontext: - items.add(direc) - except EnvironmentError: - continue - except WindowsError: - continue - except MemoryError: - continue - return list(items) - finally: - winreg.CloseKey(local) + path = Path(directory, direc).resolve() + if path.suffix.lower() in fontext: + items.add(str(path)) + return list(items) + except (OSError, MemoryError): + continue return None def OSXInstalledFonts(directories=None, fontext='ttf'): - """ - Get list of font files on OS X - ignores font suffix by default. - """ + """Get list of font files on OS X.""" if directories is None: directories = OSXFontDirectories - - fontext = get_fontext_synonyms(fontext) - - files = [] - for path in directories: - if fontext is None: - files.extend(cbook.listFiles(path, '*')) - else: - files.extend(list_fonts(path, fontext)) - return files + return [path + for directory in directories + for ext in get_fontext_synonyms(fontext) + for path in list_fonts(directory, ext)] @lru_cache() @@ -282,19 +230,12 @@ def _call_fc_list(): 'This may take a moment.')) timer.start() try: - out = subprocess.check_output([str('fc-list'), '--format=%{file}\\n']) + out = subprocess.check_output(['fc-list', '--format=%{file}\\n']) except (OSError, subprocess.CalledProcessError): return [] finally: timer.cancel() - fnames = [] - for fname in out.split(b'\n'): - try: - fname = six.text_type(fname, sys.getfilesystemencoding()) - except UnicodeDecodeError: - continue - fnames.append(fname) - return fnames + return [os.fsdecode(fname) for fname in out.split(b'\n')] def get_fontconfig_fonts(fontext='ttf'): @@ -302,7 +243,7 @@ def get_fontconfig_fonts(fontext='ttf'): """ fontext = get_fontext_synonyms(fontext) return [fname for fname in _call_fc_list() - if os.path.splitext(fname)[1][1:] in fontext] + if Path(fname).suffix[1:] in fontext] def findSystemFonts(fontpaths=None, fontext='ttf'): @@ -318,53 +259,25 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): if fontpaths is None: if sys.platform == 'win32': - fontdir = win32FontDirectory() - - fontpaths = [fontdir] + fontpaths = [win32FontDirectory()] # now get all installed fonts directly... - for f in win32InstalledFonts(fontdir): - base, ext = os.path.splitext(f) - if len(ext)>1 and ext[1:].lower() in fontexts: - fontfiles.add(f) + fontfiles.update(win32InstalledFonts(fontext=fontext)) else: fontpaths = X11FontDirectories + fontfiles.update(get_fontconfig_fonts(fontext)) # check for OS X & load its fonts if present if sys.platform == 'darwin': - for f in OSXInstalledFonts(fontext=fontext): - fontfiles.add(f) - - for f in get_fontconfig_fonts(fontext): - fontfiles.add(f) + fontfiles.update(OSXInstalledFonts(fontext=fontext)) - elif isinstance(fontpaths, six.string_types): + elif isinstance(fontpaths, str): fontpaths = [fontpaths] for path in fontpaths: - files = list_fonts(path, fontexts) - for fname in files: - fontfiles.add(os.path.abspath(fname)) + fontfiles.update(map(os.path.abspath, list_fonts(path, fontexts))) return [fname for fname in fontfiles if os.path.exists(fname)] -@cbook.deprecated("2.1") -def weight_as_number(weight): - """ - Return the weight property as a numeric value. String values - are converted to their corresponding numeric value. - """ - if isinstance(weight, six.string_types): - try: - weight = weight_dict[weight.lower()] - except KeyError: - weight = 400 - elif weight in range(100, 1000, 100): - pass - else: - raise ValueError('weight not a valid integer') - return weight - - class FontEntry(object): """ A class for storing Font properties. It is used when populating @@ -416,16 +329,11 @@ def ttfFontProperty(font): # Styles are: italic, oblique, and normal (default) sfnt = font.get_sfnt() - sfnt2 = sfnt.get((1,0,0,2)) - sfnt4 = sfnt.get((1,0,0,4)) - if sfnt2: - sfnt2 = sfnt2.decode('mac_roman').lower() - else: - sfnt2 = '' - if sfnt4: - sfnt4 = sfnt4.decode('mac_roman').lower() - else: - sfnt4 = '' + # These tables are actually mac_roman-encoded, but mac_roman support may be + # missing in some alternative Python implementations and we are only going + # to look for ASCII substrings, where any ASCII-compatible encoding works. + sfnt2 = sfnt.get((1, 0, 0, 2), b'').decode('latin-1').lower() + sfnt4 = sfnt.get((1, 0, 0, 4), b'').decode('latin-1').lower() if sfnt4.find('oblique') >= 0: style = 'oblique' elif sfnt4.find('italic') >= 0: @@ -504,9 +412,9 @@ def afmFontProperty(fontpath, font): # Styles are: italic, oblique, and normal (default) - if font.get_angle() != 0 or name.lower().find('italic') >= 0: + if font.get_angle() != 0 or 'italic' in name.lower(): style = 'italic' - elif name.lower().find('oblique') >= 0: + elif 'oblique' in name.lower(): style = 'oblique' else: style = 'normal' @@ -527,12 +435,11 @@ def afmFontProperty(fontpath, font): # and ultra-expanded. # Relative stretches are: wider, narrower # Child value is: inherit - if fontname.find('narrow') >= 0 or fontname.find('condensed') >= 0 or \ - fontname.find('cond') >= 0: - stretch = 'condensed' - elif fontname.find('demi cond') >= 0: + if 'demi cond' in fontname: stretch = 'semi-condensed' - elif fontname.find('wide') >= 0 or fontname.find('expanded') >= 0: + elif 'narrow' in fontname or 'cond' in fontname: + stretch = 'condensed' + elif 'wide' in fontname or 'expanded' in fontname: stretch = 'expanded' else: stretch = 'normal' @@ -570,17 +477,14 @@ def createFontList(fontfiles, fontext='ttf'): seen.add(fname) if fontext == 'afm': try: - fh = open(fpath, 'rb') + with open(fpath, 'rb') as fh: + font = afm.AFM(fh) except EnvironmentError: _log.info("Could not open font file %s", fpath) continue - try: - font = afm.AFM(fh) except RuntimeError: _log.info("Could not parse font file %s", fpath) continue - finally: - fh.close() try: prop = afmFontProperty(fpath, font) except KeyError: @@ -594,7 +498,7 @@ def createFontList(fontfiles, fontext='ttf'): except UnicodeError: _log.info("Cannot handle unicode filenames") continue - except IOError: + except OSError: _log.info("IO error - cannot open font file %s", fpath) continue try: @@ -619,7 +523,7 @@ class FontProperties(object): The items may include a generic font family name, either 'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace'. In that case, the actual font to be used will be looked up - from the associated rcParam in :file:`matplotlibrc`. + from the associated rcParam. - style: Either 'normal', 'italic' or 'oblique'. @@ -640,7 +544,7 @@ class FontProperties(object): absolute font size, e.g., 12 The default font property for TrueType fonts (as specified in the - default :file:`matplotlibrc` file) is:: + default rcParams) is:: sans-serif, normal, normal, normal, normal, scalable. @@ -659,9 +563,9 @@ class FontProperties(object): This support does not require fontconfig to be installed. We are merely borrowing its pattern syntax for use here. - Note that matplotlib's internal font manager and fontconfig use a + Note that Matplotlib's internal font manager and fontconfig use a different algorithm to lookup fonts, so the results of the same pattern - may be different in matplotlib than in other applications that use + may be different in Matplotlib than in other applications that use fontconfig. """ @@ -672,7 +576,7 @@ def __init__(self, weight = None, stretch= None, size = None, - fname = None, # if this is set, it's a hardcoded filename to use + fname = None, # if set, it's a hardcoded filename to use _init = None # used only by copy() ): self._family = _normalize_font_family(rcParams['font.family']) @@ -688,7 +592,7 @@ def __init__(self, self.__dict__.update(_init.__dict__) return - if isinstance(family, six.string_types): + if isinstance(family, str): # Treat family as a fontconfig pattern if it is the only # parameter provided. if (style is None and @@ -724,9 +628,6 @@ def __hash__(self): def __eq__(self, other): return hash(self) == hash(other) - def __ne__(self, other): - return hash(self) != hash(other) - def __str__(self): return self.get_fontconfig_pattern() @@ -738,23 +639,20 @@ def get_family(self): def get_name(self): """ - Return the name of the font that best matches the font - properties. + Return the name of the font that best matches the font properties. """ return get_font(findfont(self)).family_name def get_style(self): """ - Return the font style. Values are: 'normal', 'italic' or - 'oblique'. + Return the font style. Values are: 'normal', 'italic' or 'oblique'. """ return self._slant get_slant = get_style def get_variant(self): """ - Return the font variant. Values are: 'normal' or - 'small-caps'. + Return the font variant. Values are: 'normal' or 'small-caps'. """ return self._variant @@ -819,8 +717,7 @@ def set_family(self, family): def set_style(self, style): """ - Set the font style. Values are: 'normal', 'italic' or - 'oblique'. + Set the font style. Values are: 'normal', 'italic' or 'oblique'. """ if style is None: style = rcParams['font.style'] @@ -918,7 +815,7 @@ def set_fontconfig_pattern(self, pattern): support for it to be enabled. We are merely borrowing its pattern syntax for use here. """ - for key, val in six.iteritems(self._parse_fontconfig_pattern(pattern)): + for key, val in self._parse_fontconfig_pattern(pattern).items(): if type(val) == list: getattr(self, "set_" + key)(val[0]) else: @@ -929,34 +826,26 @@ def copy(self): return FontProperties(_init=self) -@cbook.deprecated("2.1") -def ttfdict_to_fnames(d): - """ - flatten a ttfdict to all the filenames it contains - """ - fnames = [] - for named in six.itervalues(d): - for styled in six.itervalues(named): - for variantd in six.itervalues(styled): - for weightd in six.itervalues(variantd): - for stretchd in six.itervalues(weightd): - for fname in six.itervalues(stretchd): - fnames.append(fname) - return fnames - - class JSONEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, FontManager): - return dict(o.__dict__, _class='FontManager') + return dict(o.__dict__, __class__='FontManager') elif isinstance(o, FontEntry): - return dict(o.__dict__, _class='FontEntry') + d = dict(o.__dict__, __class__='FontEntry') + try: + # Cache paths of fonts shipped with mpl relative to the mpl + # data path, which helps in the presence of venvs. + d["fname"] = str( + Path(d["fname"]).relative_to(mpl.get_data_path())) + except ValueError: + pass + return d else: - return super(JSONEncoder, self).default(o) + return super().default(o) def _json_decode(o): - cls = o.pop('_class', None) + cls = o.pop('__class__', None) if cls is None: return o elif cls == 'FontManager': @@ -966,37 +855,47 @@ def _json_decode(o): elif cls == 'FontEntry': r = FontEntry.__new__(FontEntry) r.__dict__.update(o) + if not os.path.isabs(r.fname): + r.fname = os.path.join(mpl.get_data_path(), r.fname) return r else: - raise ValueError("don't know how to deserialize _class=%s" % cls) + raise ValueError("don't know how to deserialize __class__=%s" % cls) def json_dump(data, filename): - """Dumps a data structure as JSON in the named file. - Handles FontManager and its fields.""" + """ + Dumps a data structure as JSON in the named file. + Handles FontManager and its fields. File paths that are children of the + Matplotlib data path (typically, fonts shipped with Matplotlib) are stored + relative to that data path (to remain valid across virtualenvs). + """ with open(filename, 'w') as fh: try: json.dump(data, fh, cls=JSONEncoder, indent=2) - except IOError as e: + except OSError as e: warnings.warn('Could not save font_manager cache {}'.format(e)) + def json_load(filename): - """Loads a data structure as JSON from the named file. - Handles FontManager and its fields.""" + """ + Loads a data structure as JSON from the named file. + Handles FontManager and its fields. Relative file paths are interpreted + as being relative to the Matplotlib data path, and transformed into + absolute paths. + """ with open(filename, 'r') as fh: return json.load(fh, object_hook=_json_decode) def _normalize_font_family(family): - if isinstance(family, six.string_types): - family = [six.text_type(family)] - elif isinstance(family, Iterable): - family = [six.text_type(f) for f in family] + if isinstance(family, str): + family = [family] return family +@cbook.deprecated("3.0") class TempCache(object): """ A class to store temporary caches that are (a) not saved to disk @@ -1046,7 +945,7 @@ class FontManager(object): # Increment this version number whenever the font cache data # format or behavior has changed and requires a existing font # cache files to be rebuilt. - __version__ = 201 + __version__ = 300 def __init__(self, size=None, weight='normal'): self._version = self.__version__ @@ -1068,33 +967,35 @@ def __init__(self, size=None, weight='normal'): paths.extend(ttfpath.split(':')) else: paths.append(ttfpath) - _log.info('font search path %s', str(paths)) + _log.debug('font search path %s', str(paths)) # Load TrueType fonts and create font dictionary. - self.ttffiles = findSystemFonts(paths) + findSystemFonts() self.defaultFamily = { 'ttf': 'DejaVu Sans', 'afm': 'Helvetica'} self.defaultFont = {} - for fname in self.ttffiles: - _log.debug('trying fontname %s', fname) - if fname.lower().find('DejaVuSans.ttf')>=0: - self.defaultFont['ttf'] = fname - break - else: - # use anything - self.defaultFont['ttf'] = self.ttffiles[0] + ttffiles = findSystemFonts(paths) + findSystemFonts() + self.defaultFont['ttf'] = next( + (fname for fname in ttffiles + if fname.lower().endswith("dejavusans.ttf")), + ttffiles[0]) + self.ttflist = createFontList(ttffiles) - self.ttflist = createFontList(self.ttffiles) + afmfiles = (findSystemFonts(paths, fontext='afm') + + findSystemFonts(fontext='afm')) + self.afmlist = createFontList(afmfiles, fontext='afm') + self.defaultFont['afm'] = afmfiles[0] if afmfiles else None - self.afmfiles = (findSystemFonts(paths, fontext='afm') - + findSystemFonts(fontext='afm')) - self.afmlist = createFontList(self.afmfiles, fontext='afm') - if len(self.afmfiles): - self.defaultFont['afm'] = self.afmfiles[0] - else: - self.defaultFont['afm'] = None + @property + @cbook.deprecated("3.0") + def ttffiles(self): + return [font.fname for font in self.ttflist] + + @property + @cbook.deprecated("3.0") + def afmfiles(self): + return [font.fname for font in self.afmlist] def get_default_weight(self): """ @@ -1211,14 +1112,14 @@ def score_weight(self, weight1, weight2): The result is 0.0 if both weight1 and weight 2 are given as strings and have the same value. - Otherwise, the result is the absolute value of the difference between the - CSS numeric values of *weight1* and *weight2*, normalized - between 0.05 and 1.0. + Otherwise, the result is the absolute value of the difference between + the CSS numeric values of *weight1* and *weight2*, normalized between + 0.05 and 1.0. """ - # exact match of the weight names (e.g. weight1 == weight2 == "regular") - if (isinstance(weight1, six.string_types) and - isinstance(weight2, six.string_types) and + # exact match of the weight names, e.g. weight1 == weight2 == "regular" + if (isinstance(weight1, str) and + isinstance(weight2, str) and weight1 == weight2): return 0.0 try: @@ -1281,6 +1182,20 @@ def findfont(self, prop, fontext='ttf', directory=None, `_ documentation for a description of the font finding algorithm. """ + # Pass the relevant rcParams (and the font manager, as `self`) to + # _findfont_cached so to prevent using a stale cache entry after an + # rcParam was changed. + rc_params = tuple(tuple(rcParams[key]) for key in [ + "font.serif", "font.sans-serif", "font.cursive", "font.fantasy", + "font.monospace"]) + return self._findfont_cached( + prop, fontext, directory, fallback_to_default, rebuild_if_missing, + rc_params) + + @lru_cache() + def _findfont_cached(self, prop, fontext, directory, fallback_to_default, + rebuild_if_missing, rc_params): + if not isinstance(prop, FontProperties): prop = FontProperties(prop) fname = prop.get_file() @@ -1294,20 +1209,12 @@ def findfont(self, prop, fontext='ttf', directory=None, else: fontlist = self.ttflist - if directory is None: - cached = _lookup_cache[fontext].get(prop) - if cached is not None: - return cached - else: - directory = os.path.normcase(directory) - best_score = 1e64 best_font = None for font in fontlist: if (directory is not None and - os.path.commonprefix([os.path.normcase(font.fname), - directory]) != directory): + Path(directory) not in Path(font.fname).parents): continue # Matching family should have highest priority, so it is multiplied # by 10.0 @@ -1327,7 +1234,7 @@ def findfont(self, prop, fontext='ttf', directory=None, if best_font is None or best_score >= 10.0: if fallback_to_default: warnings.warn( - 'findfont: Font family %s not found. Falling back to %s' % + 'findfont: Font family %s not found. Falling back to %s.' % (prop.get_family(), self.defaultFamily[fontext])) default_prop = prop.copy() default_prop.set_family(self.defaultFamily[fontext]) @@ -1335,15 +1242,13 @@ def findfont(self, prop, fontext='ttf', directory=None, else: # This is a hard fail -- we can't find anything reasonable, # so just return the DejuVuSans.ttf - warnings.warn( - 'findfont: Could not match %s. Returning %s' % - (prop, self.defaultFont[fontext]), - UserWarning) + warnings.warn('findfont: Could not match %s. Returning %s.' % + (prop, self.defaultFont[fontext]), + UserWarning) result = self.defaultFont[fontext] else: - _log.debug( - 'findfont: Matching %s to %s (%s) with score of %f' % - (prop, best_font.name, repr(best_font.fname), best_score)) + _log.debug('findfont: Matching %s to %s (%r) with score of %f.', + prop, best_font.name, best_font.fname, best_score) result = best_font.fname if not os.path.isfile(result): @@ -1356,11 +1261,9 @@ def findfont(self, prop, fontext='ttf', directory=None, else: raise ValueError("No valid font could be found") - if directory is None: - _lookup_cache[fontext].set(prop, result) return result -_is_opentype_cff_font_cache = {} +@lru_cache() def is_opentype_cff_font(filename): """ Returns True if the given font is a Postscript Compact Font Format @@ -1368,14 +1271,10 @@ def is_opentype_cff_font(filename): PDF backends that can not subset these fonts. """ if os.path.splitext(filename)[1].lower() == '.otf': - result = _is_opentype_cff_font_cache.get(filename) - if result is None: - with open(filename, 'rb') as fd: - tag = fd.read(4) - result = (tag == b'OTTO') - _is_opentype_cff_font_cache[filename] = result - return result - return False + with open(filename, 'rb') as fd: + return fd.read(4) == b"OTTO" + else: + return False fontManager = None _fmcache = None @@ -1401,18 +1300,14 @@ def fc_match(pattern, fontext): stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = pipe.communicate()[0] - except (OSError, IOError): + except OSError: return None # The bulk of the output from fc-list is ascii, so we keep the # result in bytes and parse it as bytes, until we extract the # filename, which is in sys.filesystemencoding(). if pipe.returncode == 0: - for fname in output.split(b'\n'): - try: - fname = six.text_type(fname, sys.getfilesystemencoding()) - except UnicodeDecodeError: - continue + for fname in map(os.fsdecode, output.split(b'\n')): if os.path.splitext(fname)[1][1:] in fontexts: return fname return None @@ -1420,7 +1315,7 @@ def fc_match(pattern, fontext): _fc_match_cache = {} def findfont(prop, fontext='ttf'): - if not isinstance(prop, six.string_types): + if not isinstance(prop, str): prop = prop.get_fontconfig_pattern() cached = _fc_match_cache.get(prop) if cached is not None: @@ -1438,24 +1333,20 @@ def findfont(prop, fontext='ttf'): cachedir = get_cachedir() if cachedir is not None: - _fmcache = os.path.join(cachedir, 'fontList.json') + _fmcache = os.path.join( + cachedir, 'fontlist-v{}.json'.format(FontManager.__version__)) fontManager = None - _lookup_cache = { - 'ttf': TempCache(), - 'afm': TempCache() - } - def _rebuild(): global fontManager fontManager = FontManager() if _fmcache: - with cbook.Locked(cachedir): + with cbook._lock_path(_fmcache): json_dump(fontManager, _fmcache) - _log.info("generated new fontManager") + _log.debug("generated new fontManager") if _fmcache: try: @@ -1466,9 +1357,9 @@ def _rebuild(): else: fontManager.default_size = None _log.debug("Using fontManager instance from %s", _fmcache) - except cbook.Locked.TimeoutError: + except TimeoutError: raise - except: + except Exception: _rebuild() else: _rebuild() diff --git a/lib/matplotlib/fontconfig_pattern.py b/lib/matplotlib/fontconfig_pattern.py index 5104c25d3623..252d709bd6e5 100644 --- a/lib/matplotlib/fontconfig_pattern.py +++ b/lib/matplotlib/fontconfig_pattern.py @@ -13,20 +13,12 @@ # It probably logically belongs in :file:`font_manager.py`, but placing it # there would have created cyclical dependency problems. -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +from functools import lru_cache import re + from pyparsing import (Literal, ZeroOrMore, Optional, Regex, StringEnd, ParseException, Suppress) -try: - from functools import lru_cache -except ImportError: - from backports.functools_lru_cache import lru_cache - family_punc = r'\\\-:,' family_unescape = re.compile(r'\\([%s])' % family_punc).sub family_escape = re.compile(r'([%s])' % family_punc).sub diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 281d605dda7f..9afe44acd5d1 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -13,10 +13,6 @@ """ -from __future__ import absolute_import, division, print_function - -import six - import copy import logging import warnings @@ -24,10 +20,9 @@ import numpy as np import matplotlib as mpl -from matplotlib import _pylab_helpers, tight_layout, rcParams +from matplotlib import _pylab_helpers, cbook, tight_layout, rcParams from matplotlib.transforms import Bbox import matplotlib._layoutbox as layoutbox -from matplotlib.cbook import mplDeprecation _log = logging.getLogger(__name__) @@ -48,6 +43,18 @@ def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None): self.set_height_ratios(height_ratios) self.set_width_ratios(width_ratios) + def __repr__(self): + height_arg = (', height_ratios=%r' % self._row_height_ratios + if self._row_height_ratios is not None else '') + width_arg = (', width_ratios=%r' % self._col_width_ratios + if self._col_width_ratios is not None else '') + return '{clsname}({nrows}, {ncols}{optionals})'.format( + clsname=self.__class__.__name__, + nrows=self._nrows, + ncols=self._ncols, + optionals=height_arg + width_arg, + ) + def get_geometry(self): 'get the geometry of the grid, e.g., 2,3' return self._nrows, self._ncols @@ -189,6 +196,21 @@ def __init__(self, nrows, ncols, figure=None, ncols : int Number or columns in grid. + figure : ~.figure.Figure, optional + + left, right, top, bottom : float + Extent of the subplots as a fraction of figure width or height. + Left cannot be larger than right, and bottom cannot be larger than + top. + + wspace : float + The amount of width reserved for space between subplots, + expressed as a fraction of the average axis width. + + hspace : float + The amount of height reserved for space between subplots, + expressed as a fraction of the average axis height. + Notes ----- See `~.figure.SubplotParams` for descriptions of the layout parameters. @@ -205,7 +227,7 @@ def __init__(self, nrows, ncols, figure=None, width_ratios=width_ratios, height_ratios=height_ratios) - if (self.figure is None) or not self.figure.get_constrained_layout(): + if self.figure is None or not self.figure.get_constrained_layout(): self._layoutbox = None else: self.figure.init_layoutbox() @@ -238,13 +260,13 @@ def update(self, **kwargs): the current value, if set, otherwise to rc. """ - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if k in self._AllowedKeys: setattr(self, k, v) else: raise AttributeError("%s is unknown keyword" % (k,)) - for figmanager in six.itervalues(_pylab_helpers.Gcf.figs): + for figmanager in _pylab_helpers.Gcf.figs.values(): for ax in figmanager.canvas.figure.axes: # copied from Figure.subplots_adjust if not isinstance(ax, mpl.axes.SubplotBase): @@ -269,8 +291,8 @@ def get_subplot_params(self, figure=None, fig=None): parameters are from rcParams unless a figure attribute is set. """ if fig is not None: - warnings.warn("the 'fig' kwarg is deprecated " - "use 'figure' instead", mplDeprecation) + cbook.warn_deprecated("2.2", "fig", obj_type="keyword argument", + alternative="figure") if figure is None: figure = fig @@ -280,8 +302,7 @@ def get_subplot_params(self, figure=None, fig=None): else: subplotpars = copy.copy(figure.subplotpars) - update_kw = {k: getattr(self, k) for k in self._AllowedKeys} - subplotpars.update(**update_kw) + subplotpars.update(**{k: getattr(self, k) for k in self._AllowedKeys}) return subplotpars @@ -360,8 +381,8 @@ def get_subplot_params(self, figure=None, fig=None): """Return a dictionary of subplot layout parameters. """ if fig is not None: - warnings.warn("the 'fig' kwarg is deprecated " - "use 'figure' instead", mplDeprecation) + cbook.warn_deprecated("2.2", "fig", obj_type="keyword argument", + alternative="figure") if figure is None: figure = fig @@ -490,9 +511,46 @@ def __eq__(self, other): getattr(other, "num1", object()), getattr(other, "num2", object()))) - if six.PY2: - def __ne__(self, other): - return not self == other - def __hash__(self): return hash((self._gridspec, self.num1, self.num2)) + + def subgridspec(self, nrows, ncols, **kwargs): + """ + Return a `.GridSpecFromSubplotSpec` that has this subplotspec as + a parent. + + Parameters + ---------- + nrows : int + Number of rows in grid. + + ncols : int + Number or columns in grid. + + Returns + ------- + gridspec : `.GridSpec` + + Other Parameters + ---------------- + **kwargs + All other parameters are passed to `.GridSpec`. + + See Also + -------- + matplotlib.pyplot.subplots + + Examples + -------- + Adding three subplots in the space occupied by a single subplot:: + + fig = plt.figure() + gs0 = fig.add_gridspec(3, 1) + ax1 = fig.add_subplot(gs0[0]) + ax2 = fig.add_subplot(gs0[1]) + gssub = gs0[2].subgridspec(1, 3) + for i in range(3): + fig.add_subplot(gssub[0, i]) + """ + + return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs) diff --git a/lib/matplotlib/hatch.py b/lib/matplotlib/hatch.py index 94294afdf8a8..cb1e2960faf3 100644 --- a/lib/matplotlib/hatch.py +++ b/lib/matplotlib/hatch.py @@ -2,12 +2,6 @@ Contains a classes for generating hatch patterns. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange - import numpy as np from matplotlib.path import Path @@ -115,7 +109,7 @@ def set_vertices_and_codes(self, vertices, codes): shape_size = len(shape_vertices) cursor = 0 - for row in xrange(self.num_rows + 1): + for row in range(self.num_rows + 1): if row % 2 == 0: cols = np.linspace(0.0, 1.0, self.num_rows + 1, True) else: diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 3ea0cb70d3e1..a41428a5f345 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1,25 +1,22 @@ """ The image module supports basic image loading, rescaling and display operations. - """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six -from six.moves.urllib.parse import urlparse -from six.moves.urllib.request import urlopen from io import BytesIO - from math import ceil import os import logging +import urllib.parse +import urllib.request +import warnings import numpy as np from matplotlib import rcParams import matplotlib.artist as martist from matplotlib.artist import allow_rasterization +from matplotlib.backend_bases import FigureCanvasBase import matplotlib.colors as mcolors import matplotlib.cm as cm import matplotlib.cbook as cbook @@ -183,21 +180,6 @@ def _rgb_to_rgba(A): class _ImageBase(martist.Artist, cm.ScalarMappable): zorder = 0 - @property - @cbook.deprecated("2.1") - def _interpd(self): - return _interpd_ - - @property - @cbook.deprecated("2.1") - def _interpdr(self): - return {v: k for k, v in six.iteritems(_interpd_)} - - @property - @cbook.deprecated("2.1", alternative="mpl.image.interpolation_names") - def iterpnames(self): - return interpolations_names - def __str__(self): return "AxesImage(%g,%g;%gx%g)" % tuple(self.axes.bbox.bounds) @@ -206,7 +188,7 @@ def __init__(self, ax, norm=None, interpolation=None, origin=None, - filternorm=1, + filternorm=True, filterrad=4.0, resample=False, **kwargs @@ -241,7 +223,7 @@ def __init__(self, ax, self.update(kwargs) def __getstate__(self): - state = super(_ImageBase, self).__getstate__() + state = super().__getstate__() # We can't pickle the C Image cached object. state['_imcache'] = None return state @@ -255,10 +237,11 @@ def get_size(self): def set_alpha(self, alpha): """ - Set the alpha value used for blending - not supported on - all backends + Set the alpha value used for blending - not supported on all backends. - ACCEPTS: float + Parameters + ---------- + alpha : float """ martist.Artist.set_alpha(self, alpha) self._imcache = None @@ -281,8 +264,8 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, and magnified by the magnification factor. `A` may be a greyscale image (MxN) with a dtype of `float32`, - `float64`, `uint16` or `uint8`, or an RGBA image (MxNx4) with - a dtype of `float32`, `float64`, or `uint8`. + `float64`, `float128`, `uint16` or `uint8`, or an RGBA image (MxNx4) + with a dtype of `float32`, `float64`, `float128`, or `uint8`. If `unsampled` is True, the image will not be scaled, but an appropriate affine transformation will be returned instead. @@ -378,6 +361,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, a_min, a_max = np.int32(0), np.int32(1) if inp_dtype.kind == 'f': scaled_dtype = A.dtype + # Cast to float64 + if A.dtype not in (np.float32, np.float16): + if A.dtype != np.float64: + warnings.warn( + "Casting input data from '{0}' to 'float64'" + "for imshow".format(A.dtype)) + scaled_dtype = np.float64 else: # probably an integer of some type. da = a_max.astype(np.float64) - a_min.astype(np.float64) @@ -435,8 +425,8 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, t, _interpd_[self.get_interpolation()], self.get_resample(), 1.0, - self.get_filternorm() or 0.0, - self.get_filterrad() or 0.0) + self.get_filternorm(), + self.get_filterrad()) # we are done with A_scaled now, remove from namespace # to be sure! @@ -470,8 +460,8 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, t, _interpd_[self.get_interpolation()], True, 1, - self.get_filternorm() or 0.0, - self.get_filterrad() or 0.0) + self.get_filternorm(), + self.get_filterrad()) # we are done with the mask, delete from namespace to be sure! del mask # Agg updates the out_mask in place. If the pixel has @@ -504,7 +494,7 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, _image.resample( A, output, t, _interpd_[self.get_interpolation()], self.get_resample(), alpha, - self.get_filternorm() or 0.0, self.get_filterrad() or 0.0) + self.get_filternorm(), self.get_filterrad()) # at this point output is either a 2D array of normed data # (of int or float) @@ -631,9 +621,11 @@ def set_data(self, A): """ Set the image array. - ACCEPTS: numpy/PIL Image A - Note that this function does *not* update the normalization used. + + Parameters + ---------- + A : array-like """ # check if data is PIL Image without importing Image if hasattr(A, 'getpixel'): @@ -676,13 +668,14 @@ def set_data(self, A): def set_array(self, A): """ - Retained for backwards compatibility - use set_data instead + Retained for backwards compatibility - use set_data instead. - ACCEPTS: numpy array A or PIL Image + Parameters + ---------- + A : array-like """ # This also needs to be here to override the inherited - # cm.ScalarMappable.set_array method so it is not invoked - # by mistake. + # cm.ScalarMappable.set_array method so it is not invoked by mistake. self.set_data(A) @@ -706,10 +699,11 @@ def set_interpolation(self, s): agg, ps and pdf backends and will fall back to 'nearest' mode for other backends. - .. ACCEPTS: ['nearest' | 'bilinear' | 'bicubic' | 'spline16' | - 'spline36' | 'hanning' | 'hamming' | 'hermite' | 'kaiser' | - 'quadric' | 'catrom' | 'gaussian' | 'bessel' | 'mitchell' | - 'sinc' | 'lanczos' | 'none' ] + Parameters + ---------- + s : {'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', \ +'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', \ +'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} """ if s is None: @@ -734,7 +728,9 @@ def set_resample(self, v): """ Set whether or not image resampling is used. - ACCEPTS: True|False + Parameters + ---------- + v : bool """ if v is None: v = rcParams['image.resample'] @@ -747,20 +743,19 @@ def get_resample(self): def set_filternorm(self, filternorm): """ - Set whether the resize filter norms the weights -- see - help for imshow + Set whether the resize filter normalizes the weights. - ACCEPTS: 0 or 1 - """ - if filternorm: - self._filternorm = 1 - else: - self._filternorm = 0 + See help for `~.Axes.imshow`. + Parameters + ---------- + filternorm : bool + """ + self._filternorm = bool(filternorm) self.stale = True def get_filternorm(self): - """Return the filternorm setting.""" + """Return whether the resize filter normalizes the weights.""" return self._filternorm def set_filterrad(self, filterrad): @@ -768,7 +763,9 @@ def set_filterrad(self, filterrad): Set the resize filter radius only applicable to some interpolation schemes -- see help for imshow - ACCEPTS: positive float + Parameters + ---------- + filterrad : positive float """ r = float(filterrad) if r <= 0: @@ -813,7 +810,7 @@ def __init__(self, ax, self._extent = extent - super(AxesImage, self).__init__( + super().__init__( ax, cmap=cmap, norm=norm, @@ -902,15 +899,14 @@ def get_cursor_data(self, event): class NonUniformImage(AxesImage): - def __init__(self, ax, **kwargs): + def __init__(self, ax, *, interpolation='nearest', **kwargs): """ kwargs are identical to those for AxesImage, except that 'nearest' and 'bilinear' are the only supported 'interpolation' options. """ - interp = kwargs.pop('interpolation', 'nearest') - super(NonUniformImage, self).__init__(ax, **kwargs) - self.set_interpolation(interp) + super().__init__(ax, **kwargs) + self.set_interpolation(interpolation) def _check_unsampled_image(self, renderer): """ @@ -938,7 +934,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): if A.dtype != np.uint8: A = (255*A).astype(np.uint8) if A.shape[2] == 3: - B = np.zeros(tuple(list(A.shape[0:2]) + [4]), np.uint8) + B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8) B[:, :, 0:3] = A B[:, :, 3] = 255 A = B @@ -1016,12 +1012,12 @@ def set_filterrad(self, s): def set_norm(self, norm): if self._A is not None: raise RuntimeError('Cannot change colors after loading data') - super(NonUniformImage, self).set_norm(norm) + super().set_norm(norm) def set_cmap(self, cmap): if self._A is not None: raise RuntimeError('Cannot change colors after loading data') - super(NonUniformImage, self).set_cmap(cmap) + super().set_cmap(cmap) class PcolorImage(AxesImage): @@ -1047,7 +1043,7 @@ def __init__(self, ax, Additional kwargs are matplotlib.artist properties """ - super(PcolorImage, self).__init__(ax, norm=norm, cmap=cmap) + super().__init__(ax, norm=norm, cmap=cmap) self.update(kwargs) if A is not None: self.set_data(x, y, A) @@ -1175,7 +1171,7 @@ def __init__(self, fig, kwargs are an optional list of Artist keyword args """ - super(FigureImage, self).__init__( + super().__init__( None, norm=norm, cmap=cmap, @@ -1245,7 +1241,7 @@ def __init__(self, bbox, kwargs are an optional list of Artist keyword args """ - super(BboxImage, self).__init__( + super().__init__( None, cmap=cmap, norm=norm, @@ -1306,39 +1302,39 @@ def imread(fname, format=None): """ Read an image from a file into an array. - *fname* may be a string path, a valid URL, or a Python - file-like object. If using a file object, it must be opened in binary - mode. + Parameters + ---------- + fname : str or file-like + The image file to read. This can be a filename, a URL or a Python + file-like object opened in read-binary mode. + format : str, optional + The image file format assumed for reading the data. If not + given, the format is deduced from the filename. If nothing can + be deduced, PNG is tried. - If *format* is provided, will try to read file of that type, - otherwise the format is deduced from the filename. If nothing can - be deduced, PNG is tried. + Returns + ------- + imagedata : :class:`numpy.array` + The image data. The returned array has shape - Return value is a :class:`numpy.array`. For grayscale images, the - return array is MxN. For RGB images, the return value is MxNx3. - For RGBA images the return value is MxNx4. + - (M, N) for grayscale images. + - (M, N, 3) for RGB images. + - (M, N, 4) for RGBA images. - matplotlib can only read PNGs natively, but if `PIL - `_ is installed, it will - use it to load the image and return an array (if possible) which - can be used with :func:`~matplotlib.pyplot.imshow`. Note, URL strings - may not be compatible with PIL. Check the PIL documentation for more - information. - """ + Notes + ----- + Matplotlib can only read PNGs natively. Further image formats are + supported via the optional dependency on Pillow. Note, URL strings + are not compatible with Pillow. Check the `Pillow documentation`_ + for more information. - def pilread(fname): - """try to load the image with PIL or return None""" - try: - from PIL import Image - except ImportError: - return None - with Image.open(fname) as image: - return pil_to_array(image) + .. _Pillow documentation: http://pillow.readthedocs.io/en/latest/ + """ handlers = {'png': _png.read_png, } if format is None: - if isinstance(fname, six.string_types): - parsed = urlparse(fname) + if isinstance(fname, str): + parsed = urllib.parse.urlparse(fname) # If the string is a URL, assume png if len(parsed.scheme) > 1: ext = 'png' @@ -1353,24 +1349,26 @@ def pilread(fname): else: ext = format - if ext not in handlers: - im = pilread(fname) - if im is None: + if ext not in handlers: # Try to load the image with PIL. + try: + from PIL import Image + except ImportError: raise ValueError('Only know how to handle extensions: %s; ' 'with Pillow installed matplotlib can handle ' 'more images' % list(handlers)) - return im + with Image.open(fname) as image: + return pil_to_array(image) handler = handlers[ext] # To handle Unicode filenames, we pass a file object to the PNG # reader extension, since Python handles them quite well, but it's # tricky in C. - if isinstance(fname, six.string_types): - parsed = urlparse(fname) + if isinstance(fname, str): + parsed = urllib.parse.urlparse(fname) # If fname is a URL, download the data if len(parsed.scheme) > 1: - fd = BytesIO(urlopen(fname).read()) + fd = BytesIO(urllib.request.urlopen(fname).read()) return handler(fd) else: with open(fname, 'rb') as fd: @@ -1389,26 +1387,29 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, Parameters ---------- fname : str or file-like - Path string to a filename, or a Python file-like object. - If *format* is *None* and *fname* is a string, the output - format is deduced from the extension of the filename. + The filename or a Python file-like object to store the image in. + The necessary output format is inferred from the filename extension + but may be explicitly overwritten using *format*. arr : array-like - An MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA) array. - vmin, vmax: [ None | scalar ] + The image data. The shape can be one of + MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA). + vmin, vmax : scalar, optional *vmin* and *vmax* set the color scaling for the image by fixing the values that map to the colormap color limits. If either *vmin* or *vmax* is None, that limit is determined from the *arr* min/max value. - cmap : matplotlib.colors.Colormap, optional - For example, ``cm.viridis``. If ``None``, defaults to the - ``image.cmap`` rcParam. - format : str - One of the file extensions supported by the active backend. Most - backends support png, pdf, ps, eps and svg. - origin : [ 'upper' | 'lower' ] - Indicates whether the ``(0, 0)`` index of the array is in the - upper left or lower left corner of the axes. Defaults to the - ``image.origin`` rcParam. + cmap : str or `~matplotlib.colors.Colormap`, optional + A Colormap instance or registered colormap name. The colormap + maps scalar data to colors. It is ignored for RGB(A) data. + Defaults to :rc:`image.cmap` ('viridis'). + format : str, optional + The file format, e.g. 'png', 'pdf', 'svg', ... . If not given, the + format is deduced form the filename extension in *fname*. + See `.Figure.savefig` for details. + origin : {'upper', 'lower'}, optional + Indicates whether the ``(0, 0)`` index of the array is in the upper + left or lower left corner of the axes. Defaults to :rc:`image.origin` + ('upper'). dpi : int The DPI to store in the metadata of the file. This does not affect the resolution of the output image. @@ -1419,7 +1420,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, fname = os.fspath(fname) if (format == 'png' or (format is None - and isinstance(fname, six.string_types) + and isinstance(fname, str) and fname.lower().endswith('.png'))): image = AxesImage(None, cmap=cmap, origin=origin) image.set_data(arr) @@ -1434,11 +1435,20 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, def pil_to_array(pilImage): - """Load a PIL image and return it as a numpy array. + """Load a `PIL image`_ and return it as a numpy array. + + .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html + + Returns + ------- + numpy.array + + The array shape depends on the image type: + + - (M, N) for grayscale images. + - (M, N, 3) for RGB images. + - (M, N, 4) for RGBA images. - Grayscale images are returned as ``(M, N)`` arrays. RGB images are - returned as ``(M, N, 3)`` arrays. RGBA images are returned as ``(M, N, - 4)`` arrays. """ if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']: # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array @@ -1462,81 +1472,59 @@ def pil_to_array(pilImage): def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear', preview=False): """ - make a thumbnail of image in *infile* with output filename - *thumbfile*. - - *infile* the image file -- must be PNG or Pillow-readable if you - have `Pillow `_ installed - - *thumbfile* - the thumbnail filename + Make a thumbnail of image in *infile* with output filename *thumbfile*. - *scale* - the scale factor for the thumbnail - - *interpolation* - the interpolation scheme used in the resampling - - - *preview* - if True, the default backend (presumably a user interface - backend) will be used which will cause a figure to be raised - if :func:`~matplotlib.pyplot.show` is called. If it is False, - a pure image backend will be used depending on the extension, - 'png'->FigureCanvasAgg, 'pdf'->FigureCanvasPdf, - 'svg'->FigureCanvasSVG + See :doc:`/gallery/misc/image_thumbnail_sgskip`. + Parameters + ---------- + infile : str or file-like + The image file -- must be PNG, Pillow-readable if you have `Pillow + `_ installed. - See examples/misc/image_thumbnail.py. + thumbfile : str or file-like + The thumbnail filename. - .. htmlonly:: + scale : float, optional + The scale factor for the thumbnail. - :ref:`sphx_glr_gallery_misc_image_thumbnail_sgskip.py` + interpolation : str, optional + The interpolation scheme used in the resampling. See the + *interpolation* parameter of `~.Axes.imshow` for possible values. - Return value is the figure instance containing the thumbnail + preview : bool, optional + If True, the default backend (presumably a user interface + backend) will be used which will cause a figure to be raised if + `~matplotlib.pyplot.show` is called. If it is False, the figure is + created using `FigureCanvasBase` and the drawing backend is selected + as `~matplotlib.figure.savefig` would normally do. + Returns + ------- + figure : `~.figure.Figure` + The figure instance containing the thumbnail. """ - basedir, basename = os.path.split(infile) - baseout, extout = os.path.splitext(thumbfile) im = imread(infile) rows, cols, depth = im.shape - # this doesn't really matter, it will cancel in the end, but we - # need it for the mpl API + # This doesn't really matter (it cancels in the end) but the API needs it. dpi = 100 height = rows / dpi * scale width = cols / dpi * scale - extension = extout.lower() - if preview: - # let the UI backend do everything + # Let the UI backend do everything. import matplotlib.pyplot as plt fig = plt.figure(figsize=(width, height), dpi=dpi) else: - if extension == '.png': - from matplotlib.backends.backend_agg \ - import FigureCanvasAgg as FigureCanvas - elif extension == '.pdf': - from matplotlib.backends.backend_pdf \ - import FigureCanvasPdf as FigureCanvas - elif extension == '.svg': - from matplotlib.backends.backend_svg \ - import FigureCanvasSVG as FigureCanvas - else: - raise ValueError("Can only handle " - "extensions 'png', 'svg' or 'pdf'") - from matplotlib.figure import Figure fig = Figure(figsize=(width, height), dpi=dpi) - FigureCanvas(fig) + FigureCanvasBase(fig) ax = fig.add_axes([0, 0, 1, 1], aspect='auto', frameon=False, xticks=[], yticks=[]) - - basename, ext = os.path.splitext(basename) ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation) fig.savefig(thumbfile, dpi=dpi) return fig diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 4e48a9509151..c186bbc32c57 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -20,10 +20,6 @@ the :doc:`legend guide ` for more information. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six import logging import warnings @@ -33,7 +29,7 @@ from matplotlib import rcParams from matplotlib import docstring from matplotlib.artist import Artist, allow_rasterization -from matplotlib.cbook import silent_list, is_hashable +from matplotlib.cbook import silent_list, is_hashable, warn_deprecated import matplotlib.colors as colors from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D @@ -54,11 +50,18 @@ class DraggableLegend(DraggableOffsetBox): def __init__(self, legend, use_blit=False, update="loc"): """ + Wrapper around a `.Legend` to support mouse dragging. + Parameters ---------- - update : string - If "loc", update *loc* parameter of legend upon finalizing. - If "bbox", update *bbox_to_anchor* parameter. + legend : `.Legend` + The `.Legend` instance to wrap. + use_blit : bool, optional + Use blitting for faster image composition. For details see + :ref:`func-animation`. + update : {'loc', 'bbox'}, optional + If "loc", update the *loc* parameter of the legend upon finalizing. + If "bbox", update the *bbox_to_anchor* parameter. """ self.legend = legend @@ -133,12 +136,26 @@ def _update_bbox_to_anchor(self, loc_in_canvas): corner of the legend in axes coordinates (in which case ``bbox_to_anchor`` will be ignored). -bbox_to_anchor : `.BboxBase` or pair of floats - Specify any arbitrary location for the legend in `bbox_transform` - coordinates (default Axes coordinates). +bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats + Box that is used to position the legend in conjunction with *loc*. + Defaults to `axes.bbox` (if called as a method to `.Axes.legend`) or + `figure.bbox` (if `.Figure.legend`). This argument allows arbitrary + placement of the legend. + + Bbox coordinates are interpreted in the coordinate system given by + `bbox_transform`, with the default transform + Axes or Figure coordinates, depending on which ``legend`` is called. + + If a 4-tuple or `.BboxBase` is given, then it specifies the bbox + ``(x, y, width, height)`` that the legend is placed in. + To put the legend in the best location in the bottom right + quadrant of the axes (or figure):: - For example, to put the legend's upper right hand corner in the - center of the axes the following keywords can be used:: + loc='best', bbox_to_anchor=(0.5, 0., 0.5, 0.5) + + A 2-tuple ``(x, y)`` places the corner of the legend specified by *loc* at + x, y. For example, to put the legend's upper right-hand corner in the + center of the axes (or figure) the following keywords can be used:: loc='upper right', bbox_to_anchor=(0.5, 0.5) @@ -235,6 +252,9 @@ def _update_bbox_to_anchor(self, loc_in_canvas): title : str or None The legend's title. Default is no title (``None``). +title_fontsize: str or None + The fontsize of the legend's title. Default is the default fontsize. + borderpad : float or None The fractional whitespace inside the legend border. Measured in font-size units. @@ -336,7 +356,7 @@ def __init__(self, parent, handles, labels, # box, none use rc shadow=None, title=None, # set a title for the legend - + title_fontsize=None, # set to ax.fontsize if None framealpha=None, # set frame alpha edgecolor=None, # frame patch edgecolor facecolor=None, # frame patch facecolor @@ -347,7 +367,6 @@ def __init__(self, parent, handles, labels, handler_map=None, ): """ - Parameters ---------- parent : `~matplotlib.axes.Axes` or `.Figure` @@ -364,172 +383,7 @@ def __init__(self, parent, handles, labels, Other Parameters ---------------- - loc : int or string or pair of floats, default: 'upper right' - The location of the legend. Possible codes are: - - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= - - - Alternatively can be a 2-tuple giving ``x, y`` of the lower-left - corner of the legend in axes coordinates (in which case - ``bbox_to_anchor`` will be ignored). - - bbox_to_anchor : `.BboxBase` or pair of floats - Specify any arbitrary location for the legend in `bbox_transform` - coordinates (default Axes coordinates). - - For example, to put the legend's upper right hand corner in the - center of the axes the following keywords can be used:: - - loc='upper right', bbox_to_anchor=(0.5, 0.5) - - ncol : integer - The number of columns that the legend has. Default is 1. - - prop : None or :class:`matplotlib.font_manager.FontProperties` or dict - The font properties of the legend. If None (default), the current - :data:`matplotlib.rcParams` will be used. - - fontsize : int or float or {'xx-small', 'x-small', 'small', 'medium', \ -'large', 'x-large', 'xx-large'} - Controls the font size of the legend. If the value is numeric the - size will be the absolute font size in points. String values are - relative to the current default font size. This argument is only - used if `prop` is not specified. - - numpoints : None or int - The number of marker points in the legend when creating a legend - entry for a `.Line2D` (line). - Default is ``None``, which will take the value from - :rc:`legend.numpoints`. - - scatterpoints : None or int - The number of marker points in the legend when creating - a legend entry for a `.PathCollection` (scatter plot). - Default is ``None``, which will take the value from - :rc:`legend.scatterpoints`. - - scatteryoffsets : iterable of floats - The vertical offset (relative to the font size) for the markers - created for a scatter plot legend entry. 0.0 is at the base the - legend text, and 1.0 is at the top. To draw all markers at the - same height, set to ``[0.5]``. Default is ``[0.375, 0.5, 0.3125]``. - - markerscale : None or int or float - The relative size of legend markers compared with the originally - drawn ones. - Default is ``None``, which will take the value from - :rc:`legend.markerscale`. - - markerfirst : bool - If *True*, legend marker is placed to the left of the legend label. - If *False*, legend marker is placed to the right of the legend - label. - Default is *True*. - - frameon : None or bool - Control whether the legend should be drawn on a patch - (frame). - Default is ``None``, which will take the value from - :rc:`legend.frameon`. - - fancybox : None or bool - Control whether round edges should be enabled around the - :class:`~matplotlib.patches.FancyBboxPatch` which makes up the - legend's background. - Default is ``None``, which will take the value from - :rc:`legend.fancybox`. - - shadow : None or bool - Control whether to draw a shadow behind the legend. - Default is ``None``, which will take the value from - :rc:`legend.shadow`. - - framealpha : None or float - Control the alpha transparency of the legend's background. - Default is ``None``, which will take the value from - :rc:`legend.framealpha`. If shadow is activated and - *framealpha* is ``None``, the default value is ignored. - - facecolor : None or "inherit" or a color spec - Control the legend's background color. - Default is ``None``, which will take the value from - :rc:`legend.facecolor`. If ``"inherit"``, it will take - :rc:`axes.facecolor`. - - edgecolor : None or "inherit" or a color spec - Control the legend's background patch edge color. - Default is ``None``, which will take the value from - :rc:`legend.edgecolor` If ``"inherit"``, it will take - :rc:`axes.edgecolor`. - - mode : {"expand", None} - If `mode` is set to ``"expand"`` the legend will be horizontally - expanded to fill the axes area (or `bbox_to_anchor` if defines - the legend's size). - - bbox_transform : None or :class:`matplotlib.transforms.Transform` - The transform for the bounding box (`bbox_to_anchor`). For a value - of ``None`` (default) the Axes' - :data:`~matplotlib.axes.Axes.transAxes` transform will be used. - - title : str or None - The legend's title. Default is no title (``None``). - - borderpad : float or None - The fractional whitespace inside the legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderpad`. - - labelspacing : float or None - The vertical space between the legend entries. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.labelspacing`. - - handlelength : float or None - The length of the legend handles. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handlelength`. - - handletextpad : float or None - The pad between the legend handle and text. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.handletextpad`. - - borderaxespad : float or None - The pad between the axes and legend border. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.borderaxespad`. - - columnspacing : float or None - The spacing between columns. - Measured in font-size units. - Default is ``None``, which will take the value from - :rc:`legend.columnspacing`. - - handler_map : dict or None - The custom dictionary mapping instances or types to a legend - handler. This `handler_map` updates the default handler map - found at :func:`matplotlib.legend.Legend.get_legend_handler_map`. + %(_legend_kw_doc)s Notes ----- @@ -585,8 +439,7 @@ def __init__(self, parent, handles, labels, # trim handles and labels if illegal label... _lab, _hand = [], [] for label, handle in zip(labels, handles): - if (isinstance(label, six.string_types) and - label.startswith('_')): + if isinstance(label, str) and label.startswith('_'): warnings.warn('The handle {!r} has a label of {!r} which ' 'cannot be automatically added to the ' 'legend.'.format(handle, label)) @@ -632,7 +485,7 @@ def __init__(self, parent, handles, labels, loc = rcParams["legend.loc"] if not self.isaxes and loc in [0, 'best']: loc = 'upper right' - if isinstance(loc, six.string_types): + if isinstance(loc, str): if loc not in self.codes: if self.isaxes: warnings.warn('Unrecognized location "%s". Falling back ' @@ -709,7 +562,11 @@ def __init__(self, parent, handles, labels, self.get_frame().set_alpha(framealpha) self._loc = loc - self.set_title(title) + # figure out title fontsize: + if title_fontsize is None: + title_fontsize = rcParams['legend.title_fontsize'] + tprop = FontProperties(size=title_fontsize) + self.set_title(title, prop=tprop) self._last_fontsize_points = self._fontsize self._draggable = None @@ -1102,25 +959,45 @@ def set_title(self, title, prop=None): with *prop* parameter. """ self._legend_title_box._text.set_text(title) + if title: + self._legend_title_box._text.set_visible(True) + self._legend_title_box.set_visible(True) + else: + self._legend_title_box._text.set_visible(False) + self._legend_title_box.set_visible(False) if prop is not None: if isinstance(prop, dict): prop = FontProperties(**prop) self._legend_title_box._text.set_fontproperties(prop) - if title: - self._legend_title_box.set_visible(True) - else: - self._legend_title_box.set_visible(False) self.stale = True def get_title(self): 'Return the `.Text` instance for the legend title.' return self._legend_title_box._text - def get_window_extent(self, *args, **kwargs): + def get_window_extent(self, renderer=None): 'Return extent of the legend.' - return self.legendPatch.get_window_extent(*args, **kwargs) + if renderer is None: + renderer = self.figure._cachedRenderer + return self._legend_box.get_window_extent(renderer=renderer) + + def get_tightbbox(self, renderer): + """ + Like `.Legend.get_window_extent`, but uses the box for the legend. + + Parameters + ---------- + renderer : `.RendererBase` instance + renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + Returns + ------- + `.BboxBase` : containing the bounding box in figure pixel co-ordinates. + """ + return self._legend_box.get_window_extent(renderer) def get_frame_on(self): """Get whether the legend box patch is drawn.""" @@ -1133,7 +1010,6 @@ def set_frame_on(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ self._drawFrame = b self.stale = True @@ -1257,6 +1133,43 @@ def _find_best_position(self, width, height, renderer, consider=None): def contains(self, event): return self.legendPatch.contains(event) + def set_draggable(self, state, use_blit=False, update='loc'): + """ + Enable or disable mouse dragging support of the legend. + + Parameters + ---------- + state : bool + Whether mouse dragging is enabled. + use_blit : bool, optional + Use blitting for faster image composition. For details see + :ref:`func-animation`. + update : {'loc', 'bbox'}, optional + The legend parameter to be changed when dragged: + + - 'loc': update the *loc* parameter of the legend + - 'bbox': update the *bbox_to_anchor* parameter of the legend + + Returns + ------- + If *state* is ``True`` this returns the `~.DraggableLegend` helper + instance. Otherwise this returns ``None``. + """ + if state: + if self._draggable is None: + self._draggable = DraggableLegend(self, + use_blit, + update=update) + else: + if self._draggable is not None: + self._draggable.disconnect() + self._draggable = None + return self._draggable + + def get_draggable(self): + """Return ``True`` if the legend is draggable, ``False`` otherwise.""" + return self._draggable is not None + def draggable(self, state=None, use_blit=False, update="loc"): """ Set the draggable state -- if state is @@ -1275,21 +1188,16 @@ def draggable(self, state=None, use_blit=False, update="loc"): when dragged. If update is "loc", the *loc* parameter of the legend is changed. If "bbox", the *bbox_to_anchor* parameter is changed. """ - is_draggable = self._draggable is not None + warn_deprecated("2.2", + message="Legend.draggable() is drepecated in " + "favor of Legend.set_draggable(). " + "Legend.draggable may be reintroduced as a " + "property in future releases.") - # if state is None we'll toggle if state is None: - state = not is_draggable + state = not self.get_draggable() # toggle state - if state: - if self._draggable is None: - self._draggable = DraggableLegend(self, - use_blit, - update=update) - else: - if self._draggable is not None: - self._draggable.disconnect() - self._draggable = None + self.set_draggable(state, use_blit, update) return self._draggable @@ -1336,13 +1244,13 @@ def _get_legend_handles_labels(axs, legend_handler_map=None): for handle in _get_legend_handles(axs, legend_handler_map): label = handle.get_label() - if (label and not label.startswith('_')): + if label and not label.startswith('_'): handles.append(handle) labels.append(label) return handles, labels -def _parse_legend_args(axs, *args, **kwargs): +def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): """ Get the handles and labels from the calls to either ``figure.legend`` or ``axes.legend``. @@ -1352,17 +1260,11 @@ def _parse_legend_args(axs, *args, **kwargs): log = logging.getLogger(__name__) handlers = kwargs.get('handler_map', {}) or {} - - # Support handles and labels being passed as keywords. - handles = kwargs.pop('handles', None) - labels = kwargs.pop('labels', None) - extra_args = () - if (handles is not None or labels is not None) and len(args): - warnings.warn("You have mixed positional and keyword " - "arguments, some input may be " - "discarded.") + if (handles is not None or labels is not None) and args: + warnings.warn("You have mixed positional and keyword arguments, some " + "input may be discarded.") # if got both handles and labels as kwargs, make same length if handles and labels: diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index e1a7e2d03d11..ad574dcf33d0 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -23,11 +23,7 @@ def legend_artist(self, legend, orig_handle, fontsize, handlebox): """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six -from six.moves import zip from itertools import cycle import numpy as np diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index dac18d498552..cbd7a8224294 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -4,11 +4,7 @@ """ # TODO: expose cap and join style attrs -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +from numbers import Integral, Number, Real import warnings import numpy as np @@ -16,7 +12,7 @@ from . import artist, cbook, colors as mcolors, docstring, rcParams from .artist import Artist, allow_rasterization from .cbook import ( - _to_unmasked_float_array, iterable, is_numlike, ls_mapper, ls_mapper_r, + _to_unmasked_float_array, iterable, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP) from .markers import MarkerStyle from .path import Path @@ -35,7 +31,7 @@ def _get_dash_pattern(style): """Convert linestyle -> dash pattern """ # go from short hand -> full strings - if isinstance(style, six.string_types): + if isinstance(style, str): style = ls_mapper.get(style, style) # un-dashed styles if style in ['solid', 'None']: @@ -131,90 +127,73 @@ def _slice_or_none(in_v, slc): return None return in_v[slc] - # if just a float, assume starting at 0.0 and make a tuple - if isinstance(markevery, float): - markevery = (0.0, markevery) # if just an int, assume starting at 0 and make a tuple - elif isinstance(markevery, int): + if isinstance(markevery, Integral): markevery = (0, markevery) - # if just an numpy int, assume starting at 0 and make a tuple - elif isinstance(markevery, np.integer): - markevery = (0, markevery.item()) + # if just a float, assume starting at 0.0 and make a tuple + elif isinstance(markevery, Real): + markevery = (0.0, markevery) if isinstance(markevery, tuple): if len(markevery) != 2: - raise ValueError('`markevery` is a tuple but its ' - 'len is not 2; ' - 'markevery=%s' % (markevery,)) + raise ValueError('`markevery` is a tuple but its len is not 2; ' + 'markevery={}'.format(markevery)) start, step = markevery # if step is an int, old behavior - if isinstance(step, int): - #tuple of 2 int is for backwards compatibility, - if not(isinstance(start, int)): - raise ValueError('`markevery` is a tuple with ' - 'len 2 and second element is an int, but ' - 'the first element is not an int; ' - 'markevery=%s' % (markevery,)) + if isinstance(step, Integral): + # tuple of 2 int is for backwards compatibility, + if not isinstance(start, Integral): + raise ValueError( + '`markevery` is a tuple with len 2 and second element is ' + 'an int, but the first element is not an int; markevery={}' + .format(markevery)) # just return, we are done here return Path(verts[slice(start, None, step)], _slice_or_none(codes, slice(start, None, step))) - elif isinstance(step, float): - if not (isinstance(start, int) or - isinstance(start, float)): - raise ValueError('`markevery` is a tuple with ' - 'len 2 and second element is a float, but ' - 'the first element is not a float or an ' - 'int; ' - 'markevery=%s' % (markevery,)) - #calc cumulative distance along path (in display - # coords): + elif isinstance(step, Real): + if not isinstance(start, Real): + raise ValueError( + '`markevery` is a tuple with len 2 and second element is ' + 'a float, but the first element is not a float or an int; ' + 'markevery={}'.format(markevery)) + # calc cumulative distance along path (in display coords): disp_coords = affine.transform(tpath.vertices) - delta = np.empty((len(disp_coords), 2), - dtype=float) - delta[0, :] = 0.0 - delta[1:, :] = (disp_coords[1:, :] - - disp_coords[:-1, :]) + delta = np.empty((len(disp_coords), 2)) + delta[0, :] = 0 + delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :] delta = np.sum(delta**2, axis=1) delta = np.sqrt(delta) delta = np.cumsum(delta) - #calc distance between markers along path based on - # the axes bounding box diagonal being a distance - # of unity: - scale = ax_transform.transform( - np.array([[0, 0], [1, 1]])) + # calc distance between markers along path based on the axes + # bounding box diagonal being a distance of unity: + scale = ax_transform.transform(np.array([[0, 0], [1, 1]])) scale = np.diff(scale, axis=0) scale = np.sum(scale**2) scale = np.sqrt(scale) - marker_delta = np.arange(start * scale, - delta[-1], - step * scale) - #find closest actual data point that is closest to + marker_delta = np.arange(start * scale, delta[-1], step * scale) + # find closest actual data point that is closest to # the theoretical distance along the path: - inds = np.abs(delta[np.newaxis, :] - - marker_delta[:, np.newaxis]) + inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis]) inds = inds.argmin(axis=1) inds = np.unique(inds) # return, we are done here return Path(verts[inds], _slice_or_none(codes, inds)) else: - raise ValueError('`markevery` is a tuple with ' - 'len 2, but its second element is not an int ' - 'or a float; ' - 'markevery=%s' % (markevery,)) + raise ValueError( + '`markevery` is a tuple with len 2, but its second element is ' + 'not an int or a float; markevery=%s' % (markevery,)) elif isinstance(markevery, slice): # mazol tov, it's already a slice, just return - return Path(verts[markevery], - _slice_or_none(codes, markevery)) + return Path(verts[markevery], _slice_or_none(codes, markevery)) elif iterable(markevery): #fancy indexing try: - return Path(verts[markevery], - _slice_or_none(codes, markevery)) + return Path(verts[markevery], _slice_or_none(codes, markevery)) except (ValueError, IndexError): raise ValueError('`markevery` is iterable but ' @@ -226,15 +205,25 @@ def _slice_or_none(in_v, slc): 'markevery=%s' % (markevery,)) +@cbook._define_aliases({ + "antialiased": ["aa"], + "color": ["c"], + "linestyle": ["ls"], + "linewidth": ["lw"], + "markeredgecolor": ["mec"], + "markeredgewidth": ["mew"], + "markerfacecolor": ["mfc"], + "markerfacecoloralt": ["mfcalt"], + "markersize": ["ms"], +}) class Line2D(Artist): """ A line - the line can have both a solid linestyle connecting all the vertices, and a marker at each vertex. Additionally, the drawing of the solid line is influenced by the drawstyle, e.g., one can create "stepped" lines in various styles. - - """ + lineStyles = _lineStyles = { # hidden names deprecated '-': '_draw_solid', '--': '_draw_dashed', @@ -257,11 +246,9 @@ class Line2D(Artist): } # drawStyles should now be deprecated. - drawStyles = {} - drawStyles.update(_drawStyles_l) - drawStyles.update(_drawStyles_s) + drawStyles = {**_drawStyles_l, **_drawStyles_s} # Need a list ordered with long names first: - drawStyleKeys = list(_drawStyles_l) + list(_drawStyles_s) + drawStyleKeys = [*_drawStyles_l, *_drawStyles_s] # Referenced here to maintain API. These are defined in # MarkerStyle @@ -316,7 +303,7 @@ def __init__(self, xdata, ydata, %(Line2D)s - See :meth:`set_linestyle` for a decription of the line styles, + See :meth:`set_linestyle` for a description of the line styles, :meth:`set_marker` for a description of the markers, and :meth:`set_drawstyle` for a description of the draw styles. @@ -336,6 +323,10 @@ def __init__(self, xdata, ydata, linestyle = rcParams['lines.linestyle'] if marker is None: marker = rcParams['lines.marker'] + if markerfacecolor is None: + markerfacecolor = rcParams['lines.markerfacecolor'] + if markeredgecolor is None: + markeredgecolor = rcParams['lines.markeredgecolor'] if color is None: color = rcParams['lines.color'] @@ -352,13 +343,11 @@ def __init__(self, xdata, ydata, if solid_joinstyle is None: solid_joinstyle = rcParams['lines.solid_joinstyle'] - if isinstance(linestyle, six.string_types): + if isinstance(linestyle, str): ds, ls = self._split_drawstyle_linestyle(linestyle) if ds is not None and drawstyle is not None and ds != drawstyle: - raise ValueError("Inconsistent drawstyle ({0!r}) and " - "linestyle ({1!r})".format(drawstyle, - linestyle) - ) + raise ValueError("Inconsistent drawstyle ({!r}) and linestyle " + "({!r})".format(drawstyle, linestyle)) linestyle = ls if ds is not None: @@ -388,9 +377,9 @@ def __init__(self, xdata, ydata, self._us_dashSeq = None self._us_dashOffset = 0 + self.set_linewidth(linewidth) self.set_linestyle(linestyle) self.set_drawstyle(drawstyle) - self.set_linewidth(linewidth) self._color = None self.set_color(color) @@ -421,7 +410,7 @@ def __init__(self, xdata, ydata, self.update(kwargs) self.pickradius = pickradius self.ind_offset = 0 - if is_numlike(self._picker): + if isinstance(self._picker, Number): self.pickradius = self._picker self._xorig = np.asarray([]) @@ -456,7 +445,7 @@ def contains(self, mouseevent): if callable(self._contains): return self._contains(self, mouseevent) - if not is_numlike(self.pickradius): + if not isinstance(self.pickradius, Number): raise ValueError("pick radius should be a distance") # Make sure we have data to plot @@ -509,8 +498,6 @@ def get_pickradius(self): def set_pickradius(self, d): """Set the pick radius used for containment tests. - .. ACCEPTS: float distance in points - Parameters ---------- d : float @@ -529,7 +516,9 @@ def set_fillstyle(self, fs): Set the marker fill style; 'full' means fill the whole marker. 'none' means no filling; other options are for half-filled markers. - ACCEPTS: ['full' | 'left' | 'right' | 'bottom' | 'top' | 'none'] + Parameters + ---------- + fs : {'full', 'left', 'right', 'bottom', 'top', 'none'} """ self._marker.set_fillstyle(fs) self.stale = True @@ -539,13 +528,10 @@ def set_markevery(self, every): e.g., if `every=5`, every 5-th marker will be plotted. - ACCEPTS: [None | int | length-2 tuple of int | slice | - list/array of int | float | length-2 tuple of float] - Parameters ---------- - every: None | int | length-2 tuple of int | slice | list/array of int \ -| float | length-2 tuple of float + every: None or int or (int, int) or slice or List[int] or float or \ +(float, float) Which markers to plot. - every=None, every point will be plotted. @@ -554,7 +540,7 @@ def set_markevery(self, every): - every=(start, N), every N-th marker, starting at point start, will be plotted. - every=slice(start, end, N), every N-th marker, starting at - point start, upto but not including point end, will be plotted. + 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=0.1, (i.e. a float) then markers will be spaced at @@ -596,8 +582,10 @@ def get_markevery(self): def set_picker(self, p): """Sets the event picker details for the line. - ACCEPTS: float distance in points or callable pick function - ``fn(artist, event)`` + Parameters + ---------- + p : float or callable[[Artist, Event], Tuple[bool, dict]] + If a float, it is used as the pick radius in points. """ if callable(p): self._contains = p @@ -716,7 +704,9 @@ def set_transform(self, t): """ set the Transformation instance used by this artist - ACCEPTS: a :class:`matplotlib.transforms.Transform` instance + Parameters + ---------- + t : matplotlib.transforms.Transform """ Artist.set_transform(self, t) self._invalidx = True @@ -800,6 +790,9 @@ def draw(self, renderer): self.get_markerfacecolor(), "none")): ec_rgba = ec_rgba[:3] + (fc_rgba[3],) gc.set_foreground(ec_rgba, isRGBA=True) + if self.get_sketch_params() is not None: + scale, length, randomness = self.get_sketch_params() + gc.set_sketch_params(scale/2, length/2, 2*randomness) marker = self._marker @@ -826,7 +819,7 @@ def draw(self, renderer): subsampled = tpath snap = marker.get_snap_threshold() - if type(snap) == float: + if isinstance(snap, Real): snap = renderer.points_to_pixels(self._markersize) >= snap gc.set_snap(snap) gc.set_joinstyle(marker.get_joinstyle()) @@ -840,7 +833,6 @@ def draw(self, renderer): else: # Don't scale for pixels, and don't stroke them marker_trans = marker_trans.scale(w) - renderer.draw_markers(gc, marker_path, marker_trans, subsampled, affine.frozen(), fc_rgba) @@ -878,7 +870,7 @@ def get_marker(self): def get_markeredgecolor(self): mec = self._markeredgecolor - if isinstance(mec, six.string_types) and mec == 'auto': + if cbook._str_equal(mec, 'auto'): if rcParams['_internal.classic_mode']: if self._marker.get_marker() in ('.', ','): return self._color @@ -892,10 +884,7 @@ def get_markeredgewidth(self): return self._markeredgewidth def _get_markerfacecolor(self, alt=False): - if alt: - fc = self._markerfacecoloralt - else: - fc = self._markerfacecolor + fc = self._markerfacecoloralt if alt else self._markerfacecolor if cbook._str_lower_equal(fc, 'auto'): if self.get_fillstyle() == 'none': return 'none' @@ -971,7 +960,6 @@ def set_antialiased(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ if self._antialiased != b: self.stale = True @@ -981,7 +969,9 @@ def set_color(self, color): """ Set the color of the line - ACCEPTS: any matplotlib color + Parameters + ---------- + color : color """ self._color = color self.stale = True @@ -994,8 +984,10 @@ def set_drawstyle(self, drawstyle): produce step-plots. 'steps' is equivalent to 'steps-pre' and is maintained for backward-compatibility. - ACCEPTS: ['default' | 'steps' | 'steps-pre' | 'steps-mid' | - 'steps-post'] + Parameters + ---------- + drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \ +'steps-post'} """ if drawstyle is None: drawstyle = 'default' @@ -1011,7 +1003,9 @@ def set_linewidth(self, w): """ Set the line width in points - ACCEPTS: float value in points + Parameters + ---------- + w : float """ w = float(w) @@ -1042,17 +1036,10 @@ def _split_drawstyle_linestyle(self, ls): ls : str The linestyle with the drawstyle (if any) stripped. ''' - ret_ds = None for ds in self.drawStyleKeys: # long names are first in the list if ls.startswith(ds): - ret_ds = ds - if len(ls) > len(ds): - ls = ls[len(ds):] - else: - ls = '-' - break - - return ret_ds, ls + return ds, ls[len(ds):] or '-' + return None, ls def set_linestyle(self, ls): """ @@ -1079,14 +1066,7 @@ def set_linestyle(self, ls): (offset, onoffseq), - where ``onoffseq`` is an even length tuple of on and off ink - in points. - - - ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | - (offset, on-off-dash-seq) | - ``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | - ``' '`` | ``''``] + where ``onoffseq`` is an even length tuple of on and off ink in points. .. seealso:: @@ -1095,10 +1075,10 @@ def set_linestyle(self, ls): Parameters ---------- - ls : { ``'-'``, ``'--'``, ``'-.'``, ``':'``} and more see description + ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} The line style. """ - if isinstance(ls, six.string_types): + if isinstance(ls, str): ds, ls = self._split_drawstyle_linestyle(ls) if ds is not None: self.set_drawstyle(ds) @@ -1110,10 +1090,9 @@ def set_linestyle(self, ls): try: ls = ls_mapper_r[ls] except KeyError: - raise ValueError(("You passed in an invalid linestyle, " - "`{0}`. See " - "docs of Line2D.set_linestyle for " - "valid values.").format(ls)) + raise ValueError("Invalid linestyle {!r}; see docs of " + "Line2D.set_linestyle for valid values" + .format(ls)) self._linestyle = ls else: self._linestyle = '--' @@ -1127,39 +1106,39 @@ def set_linestyle(self, ls): @docstring.dedent_interpd def set_marker(self, marker): """ - Set the line marker - - ACCEPTS: :mod:`A valid marker style ` + Set the line marker. Parameters ---------- - marker: marker style See `~matplotlib.markers` for full description of possible - argument - + arguments. """ self._marker.set_marker(marker) self.stale = True def set_markeredgecolor(self, ec): """ - Set the marker edge color + Set the marker edge color. - ACCEPTS: any matplotlib color + Parameters + ---------- + ec : color """ if ec is None: ec = 'auto' - if self._markeredgecolor is None or \ - np.any(self._markeredgecolor != ec): + if (self._markeredgecolor is None + or np.any(self._markeredgecolor != ec)): self.stale = True self._markeredgecolor = ec def set_markeredgewidth(self, ew): """ - Set the marker edge width in points + Set the marker edge width in points. - ACCEPTS: float value in points + Parameters + ---------- + ew : float """ if ew is None: ew = rcParams['lines.markeredgewidth'] @@ -1171,7 +1150,9 @@ def set_markerfacecolor(self, fc): """ Set the marker face color. - ACCEPTS: any matplotlib color + Parameters + ---------- + fc : color """ if fc is None: fc = 'auto' @@ -1183,7 +1164,9 @@ def set_markerfacecoloralt(self, fc): """ Set the alternate marker face color. - ACCEPTS: any matplotlib color + Parameters + ---------- + fc : color """ if fc is None: fc = 'auto' @@ -1193,9 +1176,11 @@ def set_markerfacecoloralt(self, fc): def set_markersize(self, sz): """ - Set the marker size in points + Set the marker size in points. - ACCEPTS: float + Parameters + ---------- + sz : float """ sz = float(sz) if self._markersize != sz: @@ -1204,9 +1189,11 @@ def set_markersize(self, sz): def set_xdata(self, x): """ - Set the data np.array for x + Set the data array for x. - ACCEPTS: 1D array + Parameters + ---------- + x : 1D array """ self._xorig = x self._invalidx = True @@ -1214,9 +1201,11 @@ def set_xdata(self, x): def set_ydata(self, y): """ - Set the data np.array for y + Set the data array for y. - ACCEPTS: 1D array + Parameters + ---------- + y : 1D array """ self._yorig = y self._invalidy = True @@ -1228,7 +1217,9 @@ def set_dashes(self, seq): points. If seq is empty or if seq = (None, None), the linestyle will be set to solid. - ACCEPTS: sequence of on/off ink in points + Parameters + ---------- + seq : sequence of floats (on/off ink in points) or (None, None) """ if seq == (None, None) or len(seq) == 0: self.set_linestyle('-') @@ -1260,89 +1251,13 @@ def update_from(self, other): other._marker.get_fillstyle()) self._drawstyle = other._drawstyle - def _get_rgba_face(self, alt=False): - return mcolors.to_rgba(self._get_markerfacecolor(alt=alt), self._alpha) - - def _get_rgba_ln_color(self, alt=False): - return mcolors.to_rgba(self._color, self._alpha) - - # some aliases.... - def set_aa(self, val): - 'alias for set_antialiased' - self.set_antialiased(val) - - def set_c(self, val): - 'alias for set_color' - self.set_color(val) - - def set_ls(self, val): - """alias for set_linestyle""" - self.set_linestyle(val) - - def set_lw(self, val): - """alias for set_linewidth""" - self.set_linewidth(val) - - def set_mec(self, val): - """alias for set_markeredgecolor""" - self.set_markeredgecolor(val) - - def set_mew(self, val): - """alias for set_markeredgewidth""" - self.set_markeredgewidth(val) - - def set_mfc(self, val): - """alias for set_markerfacecolor""" - self.set_markerfacecolor(val) - - def set_mfcalt(self, val): - """alias for set_markerfacecoloralt""" - self.set_markerfacecoloralt(val) - - def set_ms(self, val): - """alias for set_markersize""" - self.set_markersize(val) - - def get_aa(self): - """alias for get_antialiased""" - return self.get_antialiased() - - def get_c(self): - """alias for get_color""" - return self.get_color() - - def get_ls(self): - """alias for get_linestyle""" - return self.get_linestyle() - - def get_lw(self): - """alias for get_linewidth""" - return self.get_linewidth() - - def get_mec(self): - """alias for get_markeredgecolor""" - return self.get_markeredgecolor() - - def get_mew(self): - """alias for get_markeredgewidth""" - return self.get_markeredgewidth() - - def get_mfc(self): - """alias for get_markerfacecolor""" - return self.get_markerfacecolor() - - def get_mfcalt(self, alt=False): - """alias for get_markerfacecoloralt""" - return self.get_markerfacecoloralt() - - def get_ms(self): - """alias for get_markersize""" - return self.get_markersize() - def set_dash_joinstyle(self, s): """ - Set the join style for dashed linestyles - ACCEPTS: ['miter' | 'round' | 'bevel'] + Set the join style for dashed linestyles. + + Parameters + ---------- + s : {'miter', 'round', 'bevel'} """ s = s.lower() if s not in self.validJoin: @@ -1354,8 +1269,11 @@ def set_dash_joinstyle(self, s): def set_solid_joinstyle(self, s): """ - Set the join style for solid linestyles - ACCEPTS: ['miter' | 'round' | 'bevel'] + Set the join style for solid linestyles. + + Parameters + ---------- + s : {'miter', 'round', 'bevel'} """ s = s.lower() if s not in self.validJoin: @@ -1380,9 +1298,11 @@ def get_solid_joinstyle(self): def set_dash_capstyle(self, s): """ - Set the cap style for dashed linestyles + Set the cap style for dashed linestyles. - ACCEPTS: ['butt' | 'round' | 'projecting'] + Parameters + ---------- + s : {'butt', 'round', 'projecting'} """ s = s.lower() if s not in self.validCap: @@ -1394,9 +1314,11 @@ def set_dash_capstyle(self, s): def set_solid_capstyle(self, s): """ - Set the cap style for solid linestyles + Set the cap style for solid linestyles. - ACCEPTS: ['butt' | 'round' | 'projecting'] + Parameters + ---------- + s : {'butt', 'round', 'projecting'} """ s = s.lower() if s not in self.validCap: diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index ff27c4b253bf..747cfc766b45 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -5,103 +5,159 @@ All possible markers are defined here: -============================== =============================================== -marker description -============================== =============================================== -`"."` point -`","` pixel -`"o"` circle -`"v"` triangle_down -`"^"` triangle_up -`"<"` triangle_left -`">"` triangle_right -`"1"` tri_down -`"2"` tri_up -`"3"` tri_left -`"4"` tri_right -`"8"` octagon -`"s"` square -`"p"` pentagon -`"P"` plus (filled) -`"*"` star -`"h"` hexagon1 -`"H"` hexagon2 -`"+"` plus -`"x"` x -`"X"` x (filled) -`"D"` diamond -`"d"` thin_diamond -`"|"` vline -`"_"` hline -TICKLEFT tickleft -TICKRIGHT tickright -TICKUP tickup -TICKDOWN tickdown -CARETLEFT caretleft (centered at tip) -CARETRIGHT caretright (centered at tip) -CARETUP caretup (centered at tip) -CARETDOWN caretdown (centered at tip) -CARETLEFTBASE caretleft (centered at base) -CARETRIGHTBASE caretright (centered at base) -CARETUPBASE caretup (centered at base) -`"None"`, `" "` or `""` nothing -``'$...$'`` render the string using mathtext. -`verts` a list of (x, y) pairs used for Path vertices. - The center of the marker is located at (0,0) and - the size is normalized. -path a `~matplotlib.path.Path` instance. -(`numsides`, `style`, `angle`) The marker can also be a tuple (`numsides`, - `style`, `angle`), which will create a custom, - regular symbol. - - `numsides`: - the number of sides - - `style`: - the style of the regular symbol: - - 0 - a regular polygon - 1 - a star-like symbol - 2 - an asterisk - 3 - a circle (`numsides` and `angle` is - ignored) - - `angle`: - the angle of rotation of the symbol -============================== =============================================== - -For backward compatibility, the form (`verts`, 0) is also accepted, -but it is equivalent to just `verts` for giving a raw set of vertices +============================== ====== ========================================= +marker symbol description +============================== ====== ========================================= +``"."`` |m00| point +``","`` |m01| pixel +``"o"`` |m02| circle +``"v"`` |m03| triangle_down +``"^"`` |m04| triangle_up +``"<"`` |m05| triangle_left +``">"`` |m06| triangle_right +``"1"`` |m07| tri_down +``"2"`` |m08| tri_up +``"3"`` |m09| tri_left +``"4"`` |m10| tri_right +``"8"`` |m11| octagon +``"s"`` |m12| square +``"p"`` |m13| pentagon +``"P"`` |m23| plus (filled) +``"*"`` |m14| star +``"h"`` |m15| hexagon1 +``"H"`` |m16| hexagon2 +``"+"`` |m17| plus +``"x"`` |m18| x +``"X"`` |m24| x (filled) +``"D"`` |m19| diamond +``"d"`` |m20| thin_diamond +``"|"`` |m21| vline +``"_"`` |m22| hline +``0`` (``TICKLEFT``) |m25| tickleft +``1`` (``TICKRIGHT``) |m26| tickright +``2`` (``TICKUP``) |m27| tickup +``3`` (``TICKDOWN``) |m28| tickdown +``4`` (``CARETLEFT``) |m29| caretleft +``5`` (``CARETRIGHT``) |m30| caretright +``6`` (``CARETUP``) |m31| caretup +``7`` (``CARETDOWN``) |m32| caretdown +``8`` (``CARETLEFTBASE``) |m33| caretleft (centered at base) +``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base) +``10`` (``CARETUPBASE``) |m35| caretup (centered at base) +``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base) +``"None"``, ``" "`` or ``""`` nothing +``'$...$'`` |m37| Render the string using mathtext. + E.g ``"$f$"`` for marker showing the + letter ``f``. +``verts`` A list of (x, y) pairs used for Path + vertices. The center of the marker is + located at (0,0) and the size is + normalized, such that the created path + is encapsulated inside the unit cell. +path A `~matplotlib.path.Path` instance. +``(numsides, style, angle)`` The marker can also be a tuple + ``(numsides, style, angle)``, which + will create a custom, regular symbol. + + ``numsides``: + the number of sides + + ``style``: + the style of the regular symbol: + + - 0: a regular polygon + - 1: a star-like symbol + - 2: an asterisk + - 3: a circle (``numsides`` and + ``angle`` is ignored); + deprecated. + + ``angle``: + the angle of rotation of the symbol +============================== ====== ========================================= + +For backward compatibility, the form ``(verts, 0)`` is also accepted, but it is +deprecated and equivalent to just ``verts`` for giving a raw set of vertices that define the shape. -`None` is the default which means 'nothing', however this table is +``None`` is the default which means 'nothing', however this table is referred to from other docs for the valid inputs from marker inputs and in -those cases `None` still means 'default'. +those cases ``None`` still means 'default'. + +Note that special symbols can be defined via the +:doc:`STIX math font `, +e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the +`STIX font table `_. +Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`. + +Integer numbers from ``0`` to ``11`` create lines and triangles. Those are +equally accessible via capitalized variables, like ``CARETDOWNBASE``. +Hence the following are equivalent:: + + plt.plot([1,2,3], marker=11) + plt.plot([1,2,3], marker=matplotlib.markers.CARETDOWNBASE) + + +Examples showing the use of markers: + +* :doc:`/gallery/lines_bars_and_markers/marker_reference` +* :doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference` +* :doc:`/gallery/shapes_and_collections/marker_path` + + +.. |m00| image:: /_static/markers/m00.png +.. |m01| image:: /_static/markers/m01.png +.. |m02| image:: /_static/markers/m02.png +.. |m03| image:: /_static/markers/m03.png +.. |m04| image:: /_static/markers/m04.png +.. |m05| image:: /_static/markers/m05.png +.. |m06| image:: /_static/markers/m06.png +.. |m07| image:: /_static/markers/m07.png +.. |m08| image:: /_static/markers/m08.png +.. |m09| image:: /_static/markers/m09.png +.. |m10| image:: /_static/markers/m10.png +.. |m11| image:: /_static/markers/m11.png +.. |m12| image:: /_static/markers/m12.png +.. |m13| image:: /_static/markers/m13.png +.. |m14| image:: /_static/markers/m14.png +.. |m15| image:: /_static/markers/m15.png +.. |m16| image:: /_static/markers/m16.png +.. |m17| image:: /_static/markers/m17.png +.. |m18| image:: /_static/markers/m18.png +.. |m19| image:: /_static/markers/m19.png +.. |m20| image:: /_static/markers/m20.png +.. |m21| image:: /_static/markers/m21.png +.. |m22| image:: /_static/markers/m22.png +.. |m23| image:: /_static/markers/m23.png +.. |m24| image:: /_static/markers/m24.png +.. |m25| image:: /_static/markers/m25.png +.. |m26| image:: /_static/markers/m26.png +.. |m27| image:: /_static/markers/m27.png +.. |m28| image:: /_static/markers/m28.png +.. |m29| image:: /_static/markers/m29.png +.. |m30| image:: /_static/markers/m30.png +.. |m31| image:: /_static/markers/m31.png +.. |m32| image:: /_static/markers/m32.png +.. |m33| image:: /_static/markers/m33.png +.. |m34| image:: /_static/markers/m34.png +.. |m35| image:: /_static/markers/m35.png +.. |m36| image:: /_static/markers/m36.png +.. |m37| image:: /_static/markers/m37.png """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange - -from collections import Sized +from collections.abc import Sized +from numbers import Number import numpy as np -from . import rcParams -from .cbook import is_math_text, is_numlike +from . import cbook, rcParams from .path import Path from .transforms import IdentityTransform, Affine2D # special-purpose marker identifiers: (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, - CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = xrange(12) + CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12) _empty_path = Path(np.empty((0, 2))) @@ -188,15 +244,6 @@ def __init__(self, marker=None, fillstyle=None): self.set_fillstyle(fillstyle) self.set_marker(marker) - def __getstate__(self): - d = self.__dict__.copy() - d.pop('_marker_function') - return d - - def __setstate__(self, statedict): - self.__dict__ = statedict - self.set_marker(self._marker) - def _recache(self): if self._marker_function is None: return @@ -210,12 +257,8 @@ def _recache(self): self._filled = True self._marker_function() - if six.PY3: - def __bool__(self): - return bool(len(self._path.vertices)) - else: - def __nonzero__(self): - return bool(len(self._path.vertices)) + def __bool__(self): + return bool(len(self._path.vertices)) def is_filled(self): return self._filled @@ -252,6 +295,10 @@ def set_marker(self, marker): if (isinstance(marker, np.ndarray) and marker.ndim == 2 and marker.shape[1] == 2): self._marker_function = self._set_vertices + elif isinstance(marker, str) and cbook.is_math_text(marker): + self._marker_function = self._set_mathtext_path + elif isinstance(marker, Path): + self._marker_function = self._set_path_marker elif (isinstance(marker, Sized) and len(marker) in (2, 3) and marker[1] in (0, 1, 2, 3)): self._marker_function = self._set_tuple_marker @@ -259,17 +306,13 @@ def set_marker(self, marker): marker in self.markers): self._marker_function = getattr( self, '_set_' + self.markers[marker]) - elif isinstance(marker, six.string_types) and is_math_text(marker): - self._marker_function = self._set_mathtext_path - elif isinstance(marker, Path): - self._marker_function = self._set_path_marker else: try: Path(marker) self._marker_function = self._set_vertices except ValueError: - raise ValueError('Unrecognized marker style' - ' {0}'.format(marker)) + raise ValueError('Unrecognized marker style {!r}' + .format(marker)) self._marker = marker self._recache() @@ -309,7 +352,7 @@ def _set_vertices(self): def _set_tuple_marker(self): marker = self._marker - if is_numlike(marker[0]): + if isinstance(marker[0], Number): if len(marker) == 2: numsides, rotation = marker[0], 0.0 elif len(marker) == 3: @@ -326,9 +369,17 @@ def _set_tuple_marker(self): self._filled = False self._joinstyle = 'bevel' elif symstyle == 3: + cbook.warn_deprecated( + "3.0", "Setting a circle marker using `(..., 3)` is " + "deprecated since Matplotlib 3.0, and support for it will " + "be removed in 3.2. Directly pass 'o' instead.") self._path = Path.unit_circle() self._transform = Affine2D().scale(0.5).rotate_deg(rotation) else: + cbook.warn_deprecated( + "3.0", "Passing vertices as `(verts, 0)` is deprecated since " + "Matplotlib 3.0, and support for it will be removed in 3.2. " + "Directly pass `verts` instead.") verts = np.asarray(marker[0]) path = Path(verts) self._set_custom_marker(path) diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index dee778b0d0aa..1d4328b44b9d 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -14,18 +14,14 @@ arbitrary fonts, but results may vary without proper tweaking and metrics for those fonts. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six import unichr +import functools +from io import StringIO import os -from math import ceil +import types import unicodedata -from warnings import warn +import warnings -from numpy import inf, isinf import numpy as np from pyparsing import ( @@ -35,9 +31,9 @@ ParserElement.enablePackrat() -from matplotlib import _png, colors as mcolors, get_data_path, rcParams +from matplotlib import _png, cbook, colors as mcolors, get_data_path, rcParams from matplotlib.afm import AFM -from matplotlib.cbook import Bunch, get_realpath_and_stat, maxdict +from matplotlib.cbook import get_realpath_and_stat from matplotlib.ft2font import FT2Image, KERNING_DEFAULT, LOAD_NO_HINTING from matplotlib.font_manager import findfont, FontProperties, get_font from matplotlib._mathtext_data import (latex_to_bakoma, latex_to_standard, @@ -69,25 +65,20 @@ def get_unicode_index(symbol, math=True): # length, usually longer than a hyphen. if symbol == '-': return 0x2212 - try:# This will succeed if symbol is a single unicode char + try: # This will succeed if symbol is a single unicode char return ord(symbol) except TypeError: pass - try:# Is symbol a TeX symbol (i.e. \alpha) + try: # Is symbol a TeX symbol (i.e. \alpha) return tex2uni[symbol.strip("\\")] except KeyError: - message = """'%(symbol)s' is not a valid Unicode character or -TeX/Type1 symbol"""%locals() - raise ValueError(message) - -def unichr_safe(index): - """Return the Unicode character corresponding to the index, -or the replacement character if this is a narrow build of Python -and the requested character is outside the BMP.""" - try: - return unichr(index) - except ValueError: - return unichr(0xFFFD) + raise ValueError( + "'{}' is not a valid Unicode character or TeX/Type1 symbol" + .format(symbol)) + + +unichr_safe = cbook.deprecated("3.0")(chr) + class MathtextBackend(object): """ @@ -165,7 +156,7 @@ def _update_bbox(self, x1, y1, x2, y2): def set_canvas_size(self, w, h, d): MathtextBackend.set_canvas_size(self, w, h, d) if self.mode != 'bbox': - self.image = FT2Image(ceil(w), ceil(h + max(d, 0))) + self.image = FT2Image(np.ceil(w), np.ceil(h + max(d, 0))) def render_glyph(self, ox, oy, info): if self.mode == 'bbox': @@ -188,7 +179,7 @@ def render_rect_filled(self, x1, y1, x2, y2): y = int(center - (height + 1) / 2.0) else: y = int(y1) - self.image.draw_rect_filled(int(x1), y, ceil(x2), y + height) + self.image.draw_rect_filled(int(x1), y, np.ceil(x2), y + height) def get_results(self, box, used_characters): self.mode = 'bbox' @@ -229,7 +220,7 @@ class MathtextBackendPs(MathtextBackend): backend. """ def __init__(self): - self.pswriter = six.moves.cStringIO() + self.pswriter = StringIO() self.lastfont = None def render_glyph(self, ox, oy, info): @@ -312,8 +303,8 @@ def render_rect_filled(self, x1, y1, x2, y2): def get_results(self, box, used_characters): ship(0, 0, box) - svg_elements = Bunch(svg_glyphs = self.svg_glyphs, - svg_rects = self.svg_rects) + svg_elements = types.SimpleNamespace(svg_glyphs=self.svg_glyphs, + svg_rects=self.svg_rects) return (self.width, self.height + self.depth, self.depth, @@ -360,7 +351,7 @@ def __init__(self): def render_glyph(self, ox, oy, info): oy = oy - info.offset - self.height - thetext = unichr_safe(info.num) + thetext = chr(info.num) self.glyphs.append( (info.font, info.fontsize, thetext, ox, oy)) @@ -463,8 +454,9 @@ def set_canvas_size(self, w, h, d): Set the size of the buffer used to render the math expression. Only really necessary for the bitmap backends. """ - self.width, self.height, self.depth = ceil(w), ceil(h), ceil(d) - self.mathtext_backend.set_canvas_size(self.width, self.height, self.depth) + self.width, self.height, self.depth = np.ceil([w, h, d]) + self.mathtext_backend.set_canvas_size( + self.width, self.height, self.depth) def render_glyph(self, ox, oy, facename, font_class, sym, fontsize, dpi): """ @@ -587,7 +579,7 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi, math=True): xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox] offset = self._get_offset(font, glyph, fontsize, dpi) - metrics = Bunch( + metrics = types.SimpleNamespace( advance = glyph.linearHoriAdvance/65536.0, height = glyph.height/64.0, width = glyph.width/64.0, @@ -600,7 +592,7 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi, math=True): slanted = slanted ) - result = self.glyphd[key] = Bunch( + result = self.glyphd[key] = types.SimpleNamespace( font = font, fontsize = fontsize, postscript_name = font.postscript_name, @@ -660,7 +652,7 @@ def __init__(self, *args, **kwargs): TruetypeFonts.__init__(self, *args, **kwargs) self.fontmap = {} - for key, val in six.iteritems(self._fontmap): + for key, val in self._fontmap.items(): fullpath = findfont(val) self.fontmap[key] = fullpath self.fontmap[val] = fullpath @@ -800,9 +792,9 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): found_symbol = True except ValueError: uniindex = ord('?') - warn("No TeX to unicode mapping for '%s'" % - sym.encode('ascii', 'backslashreplace'), - MathTextWarning) + warnings.warn( + "No TeX to unicode mapping for {!a}.".format(sym), + MathTextWarning) fontname, uniindex = self._map_virtual_font( fontname, font_class, uniindex) @@ -814,7 +806,7 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): if found_symbol: if fontname == 'it': if uniindex < 0x10000: - unistring = unichr(uniindex) + unistring = chr(uniindex) if (not unicodedata.category(unistring)[0] == "L" or unicodedata.name(unistring).startswith("GREEK CAPITAL")): new_fontname = 'rm' @@ -830,8 +822,9 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): if not found_symbol: if self.cm_fallback: if isinstance(self.cm_fallback, BakomaFonts): - warn("Substituting with a symbol from Computer Modern.", - MathTextWarning) + warnings.warn( + "Substituting with a symbol from Computer Modern.", + MathTextWarning) if (fontname in ('it', 'regular') and isinstance(self.cm_fallback, StixFonts)): return self.cm_fallback._get_glyph( @@ -840,14 +833,14 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): return self.cm_fallback._get_glyph( fontname, font_class, sym, fontsize) else: - if fontname in ('it', 'regular') and isinstance(self, StixFonts): + if (fontname in ('it', 'regular') + and isinstance(self, StixFonts)): return self._get_glyph('rm', font_class, sym, fontsize) - warn("Font '%s' does not have a glyph for '%s' [U+%x]" % - (new_fontname, - sym.encode('ascii', 'backslashreplace').decode('ascii'), - uniindex), - MathTextWarning) - warn("Substituting with a dummy symbol.", MathTextWarning) + warnings.warn( + "Font {!r} does not have a glyph for {!a} [U+{:x}], " + "substituting with a dummy symbol.".format( + new_fontname, sym, uniindex), + MathTextWarning) fontname = 'rm' new_fontname = fontname font = self._get_font(fontname) @@ -884,7 +877,7 @@ def __init__(self, *args, **kwargs): 3 : 'STIXSizeThreeSym', 4 : 'STIXSizeFourSym', 5 : 'STIXSizeFiveSym'}) - for key, name in six.iteritems(self._fontmap): + for key, name in self._fontmap.items(): fullpath = findfont(name) self.fontmap[key] = fullpath self.fontmap[name] = fullpath @@ -892,8 +885,8 @@ def __init__(self, *args, **kwargs): def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): """ Override prime symbol to use Bakoma """ if sym == r'\prime': - return self.bakoma._get_glyph(fontname, - font_class, sym, fontsize, math) + return self.bakoma._get_glyph( + fontname, font_class, sym, fontsize, math) else: # check whether the glyph is available in the display font uniindex = get_unicode_index(sym) @@ -901,11 +894,11 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True): if font is not None: glyphindex = font.get_char_index(uniindex) if glyphindex != 0: - return super(DejaVuFonts, self)._get_glyph('ex', - font_class, sym, fontsize, math) + return super()._get_glyph( + 'ex', font_class, sym, fontsize, math) # otherwise return regular glyph - return super(DejaVuFonts, self)._get_glyph(fontname, - font_class, sym, fontsize, math) + return super()._get_glyph( + fontname, font_class, sym, fontsize, math) class DejaVuSerifFonts(DejaVuFonts): @@ -971,7 +964,7 @@ class StixFonts(UnicodeFonts): def __init__(self, *args, **kwargs): TruetypeFonts.__init__(self, *args, **kwargs) self.fontmap = {} - for key, name in six.iteritems(self._fontmap): + for key, name in self._fontmap.items(): fullpath = findfont(name) self.fontmap[key] = fullpath self.fontmap[name] = fullpath @@ -1007,7 +1000,7 @@ def _map_virtual_font(self, fontname, font_class, uniindex): else: lo = mid + 1 - if uniindex >= range[0] and uniindex <= range[1]: + if range[0] <= uniindex <= range[1]: uniindex = uniindex - range[0] + range[3] fontname = range[2] elif not doing_sans_conversion: @@ -1047,7 +1040,7 @@ def get_sized_alternatives_for_symbol(self, fontname, sym): font = self._get_font(i) glyphindex = font.get_char_index(uniindex) if glyphindex != 0: - alternatives.append((i, unichr_safe(uniindex))) + alternatives.append((i, chr(uniindex))) # The largest size of the radical symbol in STIX has incorrect # metrics that cause it to be disconnected from the stem. @@ -1098,7 +1091,7 @@ def __init__(self, default_font_prop): self.fonts['default'] = default_font self.fonts['regular'] = default_font - self.pswriter = six.moves.cStringIO() + self.pswriter = StringIO() def _get_font(self, font): if font in self.fontmap: @@ -1116,7 +1109,7 @@ def _get_font(self, font): self.fonts[cached_font.get_fontname()] = cached_font return cached_font - def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): + def _get_info(self, fontname, font_class, sym, fontsize, dpi, math=True): 'load the cmfont, metrics and glyph with caching' key = fontname, sym, fontsize, dpi tup = self.glyphd.get(key) @@ -1127,8 +1120,7 @@ def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): # Only characters in the "Letter" class should really be italicized. # This class includes greek letters, so we're ok if (fontname == 'it' and - (len(sym) > 1 or - not unicodedata.category(six.text_type(sym)).startswith("L"))): + (len(sym) > 1 or not unicodedata.category(sym).startswith("L"))): fontname = 'rm' found_symbol = False @@ -1142,8 +1134,9 @@ def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): num = ord(glyph) found_symbol = True else: - warn("No TeX to built-in Postscript mapping for {!r}".format(sym), - MathTextWarning) + warnings.warn( + "No TeX to built-in Postscript mapping for {!r}".format(sym), + MathTextWarning) slanted = (fontname == 'it') font = self._get_font(fontname) @@ -1152,8 +1145,10 @@ def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): try: symbol_name = font.get_name_char(glyph) except KeyError: - warn("No glyph in standard Postscript font {!r} for {!r}" - .format(font.get_fontname(), sym), MathTextWarning) + warnings.warn( + "No glyph in standard Postscript font {!r} for {!r}" + .format(font.get_fontname(), sym), + MathTextWarning) found_symbol = False if not found_symbol: @@ -1167,7 +1162,7 @@ def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): xmin, ymin, xmax, ymax = [val * scale for val in font.get_bbox_char(glyph)] - metrics = Bunch( + metrics = types.SimpleNamespace( advance = font.get_width_char(glyph) * scale, width = font.get_width_char(glyph) * scale, height = font.get_height_char(glyph) * scale, @@ -1180,7 +1175,7 @@ def _get_info (self, fontname, font_class, sym, fontsize, dpi, math=True): slanted = slanted ) - self.glyphd[key] = Bunch( + self.glyphd[key] = types.SimpleNamespace( font = font, fontsize = fontsize, postscript_name = font.get_fontname(), @@ -1365,9 +1360,6 @@ def __init__(self): self.size = 0 def __repr__(self): - return self.__internal_repr__() - - def __internal_repr__(self): return self.__class__.__name__ def get_kerning(self, next): @@ -1454,7 +1446,7 @@ def __init__(self, c, state, math=True): # pack phase, after we know the real fontsize self._update_metrics() - def __internal_repr__(self): + def __repr__(self): return '`%s`' % self.c def _update_metrics(self): @@ -1552,22 +1544,22 @@ def __init__(self, elements): def __repr__(self): return '[%s <%.02f %.02f %.02f %.02f> %s]' % ( - self.__internal_repr__(), + super().__repr__(), self.width, self.height, self.depth, self.shift_amount, ' '.join([repr(x) for x in self.children])) - def _determine_order(self, totals): + @staticmethod + def _determine_order(totals): """ - A helper function to determine the highest order of glue - used by the members of this list. Used by vpack and hpack. + Determine the highest order of glue used by the members of this list. + + Helper function used by vpack and hpack. """ - o = 0 - for i in range(len(totals) - 1, 0, -1): - if totals[i] != 0.0: - o = i - break - return o + for i in range(len(totals))[::-1]: + if totals[i] != 0: + return i + return 0 def _set_glue(self, x, sign, totals, error_type): o = self._determine_order(totals) @@ -1580,8 +1572,9 @@ def _set_glue(self, x, sign, totals, error_type): self.glue_ratio = 0. if o == 0: if len(self.children): - warn("%s %s: %r" % (error_type, self.__class__.__name__, self), - MathTextWarning) + warnings.warn( + "%s %s: %r" % (error_type, self.__class__.__name__, self), + MathTextWarning) def shrink(self): for child in self.children: @@ -1823,19 +1816,19 @@ def __init__(self, state): class Glue(Node): """ Most of the information in this object is stored in the underlying - :class:`GlueSpec` class, which is shared between multiple glue objects. (This - is a memory optimization which probably doesn't matter anymore, but it's - easier to stick to what TeX does.) + :class:`GlueSpec` class, which is shared between multiple glue objects. + (This is a memory optimization which probably doesn't matter anymore, but + it's easier to stick to what TeX does.) """ def __init__(self, glue_type, copy=False): Node.__init__(self) self.glue_subtype = 'normal' - if isinstance(glue_type, six.string_types): + if isinstance(glue_type, str): glue_spec = GlueSpec.factory(glue_type) elif isinstance(glue_type, GlueSpec): glue_spec = glue_type else: - raise ValueError("glue_type must be a glue spec name or instance.") + raise ValueError("glue_type must be a glue spec name or instance") if copy: glue_spec = glue_spec.copy() self.glue_spec = glue_spec @@ -2290,7 +2283,7 @@ class Parser(object): _right_delim = set(r") ] \} > \rfloor \rangle \rceil".split()) def __init__(self): - p = Bunch() + p = types.SimpleNamespace() # All forward declarations are here p.accent = Forward() p.ambi_delim = Forward() @@ -2373,7 +2366,7 @@ def __init__(self): p.accent <<= Group( Suppress(p.bslash) - + oneOf(list(self._accent_map) + list(self._wide_accents)) + + oneOf([*self._accent_map, *self._wide_accents]) - p.placeable ) @@ -2411,7 +2404,7 @@ def __init__(self): p.ambi_delim <<= oneOf(list(self._ambi_delim)) p.left_delim <<= oneOf(list(self._left_delim)) p.right_delim <<= oneOf(list(self._right_delim)) - p.right_delim_safe <<= oneOf(list(self._right_delim - {'}'}) + [r'\}']) + p.right_delim_safe <<= oneOf([*(self._right_delim - {'}'}), r'\}']) p.genfrac <<= Group( Suppress(Literal(r"\genfrac")) @@ -2514,11 +2507,10 @@ def parse(self, s, fonts_object, fontsize, dpi): try: result = self._expression.parseString(s) except ParseBaseException as err: - raise ValueError("\n".join([ - "", - err.line, - " " * (err.column - 1) + "^", - six.text_type(err)])) + raise ValueError("\n".join(["", + err.line, + " " * (err.column - 1) + "^", + str(err)])) self._state_stack = None self._em_width_cache = {} self._expression.resetCache() @@ -2640,14 +2632,11 @@ def symbol(self, s, loc, toks): if c in self._spaced_symbols: # iterate until we find previous character, needed for cases # such as ${ -2}$, $ -2$, or $ -2$. - for i in six.moves.xrange(1, loc + 1): - prev_char = s[loc-i] - if prev_char != ' ': - break + prev_char = next((c for c in s[:loc][::-1] if c != ' '), '') # Binary operators at start of string should not be spaced if (c in self._binary_operators and (len(s[:loc].split()) == 0 or prev_char == '{' or - prev_char in self._left_delim)): + prev_char in self._left_delim)): return [char] else: return [Hlist([self._make_space(0.2), @@ -2658,20 +2647,13 @@ def symbol(self, s, loc, toks): # Do not space commas between brackets if c == ',': - prev_char, next_char = '', '' - for i in six.moves.xrange(1, loc + 1): - prev_char = s[loc - i] - if prev_char != ' ': - break - for i in six.moves.xrange(1, len(s) - loc): - next_char = s[loc + i] - if next_char != ' ': - break - if (prev_char == '{' and next_char == '}'): + prev_char = next((c for c in s[:loc][::-1] if c != ' '), '') + next_char = next((c for c in s[loc + 1:] if c != ' '), '') + if prev_char == '{' and next_char == '}': return [char] # Do not space dots as decimal separators - if (c == '.' and s[loc - 1].isdigit() and s[loc + 1].isdigit()): + if c == '.' and s[loc - 1].isdigit() and s[loc + 1].isdigit(): return [char] else: return [Hlist([char, @@ -2856,7 +2838,7 @@ def subsuper(self, s, loc, toks): napostrophes = 0 new_toks = [] for tok in toks[0]: - if isinstance(tok, six.string_types) and tok not in ('^', '_'): + if isinstance(tok, str) and tok not in ('^', '_'): napostrophes += len(tok) elif isinstance(tok, Char) and tok.c == "'": napostrophes += 1 @@ -3247,8 +3229,8 @@ def __init__(self, output): Create a MathTextParser for the given backend *output*. """ self._output = output.lower() - self._cache = maxdict(50) + @functools.lru_cache(50) def parse(self, s, dpi = 72, prop = None): """ Parse the given math expression *s* at the given *dpi*. If @@ -3260,16 +3242,10 @@ def parse(self, s, dpi = 72, prop = None): The results are cached, so multiple calls to :meth:`parse` with the same expression should be fast. """ - # There is a bug in Python 3.x where it leaks frame references, - # and therefore can't handle this caching + if prop is None: prop = FontProperties() - cacheKey = (s, dpi, hash(prop)) - result = self._cache.get(cacheKey) - if result is not None: - return result - if self._output == 'ps' and rcParams['ps.useafm']: font_output = StandardPsFonts(prop) else: @@ -3292,9 +3268,7 @@ def parse(self, s, dpi = 72, prop = None): box = self._parser.parse(s, font_output, fontsize, dpi) font_output.set_canvas_size(box.width, box.height, box.depth) - result = font_output.get_results(box) - self._cache[cacheKey] = result - return result + return font_output.get_results(box) def to_mask(self, texstr, dpi=120, fontsize=14): """ diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index bf4bc52a9314..4ba3f3215a14 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -117,7 +117,7 @@ Import record array from CSV file with type inspection :func:`rec_append_fields` - Adds field(s)/array(s) to record array + Adds field(s)/array(s) to record array :func:`rec_drop_fields` Drop fields from record array @@ -151,23 +151,9 @@ rec2excel(r, 'test.xls', formatd=formatd) rec2csv(r, 'test.csv', formatd=formatd) - scroll = rec2gtk(r, formatd=formatd) - - win = gtk.Window() - win.set_size_request(600,800) - win.add(scroll) - win.show_all() - gtk.main() - """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import map, xrange, zip - import copy import csv import operator @@ -182,10 +168,6 @@ import math -if six.PY3: - long = int - - @cbook.deprecated("2.2", alternative='numpy.logspace or numpy.geomspace') def logspace(xmin, xmax, N): ''' @@ -312,7 +294,7 @@ def detrend(x, key=None, axis=None): return detrend(x, key=detrend_linear, axis=axis) elif key == 'none': return detrend(x, key=detrend_none, axis=axis) - elif isinstance(key, six.string_types): + elif isinstance(key, str): raise ValueError("Unknown value for key %s, must be one of: " "'default', 'constant', 'mean', " "'linear', or a function" % key) @@ -400,17 +382,7 @@ def detrend_mean(x, axis=None): if axis is not None and axis+1 > x.ndim: raise ValueError('axis(=%s) out of bounds' % axis) - # short-circuit 0-D array. - if not x.ndim: - return np.array(0., dtype=x.dtype) - - # short-circuit simple operations - if axis == 0 or axis is None or x.ndim <= 1: - return x - x.mean(axis) - - ind = [slice(None)] * x.ndim - ind[axis] = np.newaxis - return x - x.mean(axis)[ind] + return x - x.mean(axis, keepdims=True) def detrend_none(x, axis=None): @@ -818,7 +790,7 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, argument, it must take a data segment as an argument and return the windowed version of the segment. - sides : [ 'default' | 'onesided' | 'twosided' ] + sides : {'default', 'onesided', 'twosided'} Specifies which sides of the spectrum to return. Default gives the default behavior, which returns one-sided for real data and both for complex data. 'onesided' forces the return of a one-sided @@ -827,7 +799,7 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, docstring.interpd.update(Single_Spectrum=cbook.dedent(""" - pad_to : integer + pad_to : int The number of points to which the data segment is padded when performing the FFT. While not increasing the actual resolution of the spectrum (the minimum distance between resolvable peaks), @@ -839,7 +811,7 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, docstring.interpd.update(PSD=cbook.dedent(""" - pad_to : integer + pad_to : int The number of points to which the data segment is padded when performing the FFT. This can be different from *NFFT*, which specifies the number of data points used. While not increasing @@ -849,7 +821,7 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, in the call to fft(). The default is None, which sets *pad_to* equal to *NFFT* - NFFT : integer + NFFT : int The number of data points used in each block for the FFT. A power 2 is most efficient. The default value is 256. This should *NOT* be used to get zero padding, or the scaling of the @@ -859,17 +831,17 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, The function applied to each segment before fft-ing, designed to remove the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter is a vector, in - matplotlib is it a function. The :mod:`~matplotlib.pylab` - module defines :func:`~matplotlib.pylab.detrend_none`, - :func:`~matplotlib.pylab.detrend_mean`, and - :func:`~matplotlib.pylab.detrend_linear`, but you can use + matplotlib is it a function. The :mod:`~matplotlib.mlab` + module defines :func:`~matplotlib.mlab.detrend_none`, + :func:`~matplotlib.mlab.detrend_mean`, and + :func:`~matplotlib.mlab.detrend_linear`, but you can use a custom function as well. You can also use a string to choose one of the functions. 'default', 'constant', and 'mean' call - :func:`~matplotlib.pylab.detrend_mean`. 'linear' calls - :func:`~matplotlib.pylab.detrend_linear`. 'none' calls - :func:`~matplotlib.pylab.detrend_none`. + :func:`~matplotlib.mlab.detrend_mean`. 'linear' calls + :func:`~matplotlib.mlab.detrend_linear`. 'none' calls + :func:`~matplotlib.mlab.detrend_none`. - scale_by_freq : boolean, optional + scale_by_freq : bool, optional Specifies whether the resulting density values should be scaled by the scaling frequency, which gives density in units of Hz^-1. This allows for integration over the returned frequency values. @@ -1453,7 +1425,7 @@ def cohere_pairs(X, ij, NFFT=256, Fs=2, detrend=detrend_none, windowVals = window else: windowVals = window(np.ones(NFFT, X.dtype)) - ind = list(xrange(0, numRows-NFFT+1, NFFT-noverlap)) + ind = list(range(0, numRows-NFFT+1, NFFT-noverlap)) numSlices = len(ind) FFTSlices = {} FFTConjSlices = {} @@ -2238,7 +2210,7 @@ def base_repr(number, base=2, padding=0): if number < base: return (padding - 1) * chars[0] + chars[int(number)] max_exponent = int(math.log(number)/math.log(base)) - max_power = long(base) ** max_exponent + max_power = int(base) ** max_exponent lead_digit = int(number/max_power) return (chars[lead_digit] + base_repr(number - max_power * lead_digit, base, @@ -2322,7 +2294,7 @@ def isvector(X): @cbook.deprecated("2.2", 'numpy.isnan') def safe_isnan(x): ':func:`numpy.isnan` for arbitrary types' - if isinstance(x, six.string_types): + if isinstance(x, str): return False try: b = np.isnan(x) @@ -2337,7 +2309,7 @@ def safe_isnan(x): @cbook.deprecated("2.2", 'numpy.isinf') def safe_isinf(x): ':func:`numpy.isinf` for arbitrary types' - if isinstance(x, six.string_types): + if isinstance(x, str): return False try: b = np.isinf(x) @@ -2357,8 +2329,8 @@ def rec_append_fields(rec, names, arrs, dtypes=None): *arrs* and *dtypes* do not have to be lists. They can just be the values themselves. """ - if (not isinstance(names, six.string_types) and cbook.iterable(names) - and len(names) and isinstance(names[0], six.string_types)): + if (not isinstance(names, str) and cbook.iterable(names) + and len(names) and isinstance(names[0], str)): if len(names) != len(arrs): raise ValueError("number of arrays do not match number of names") else: # we have only 1 name and 1 array @@ -2375,8 +2347,6 @@ def rec_append_fields(rec, names, arrs, dtypes=None): else: raise ValueError("dtypes must be None, a single dtype or a list") old_dtypes = rec.dtype.descr - if six.PY2: - old_dtypes = [(name.encode('utf-8'), dt) for name, dt in old_dtypes] newdtype = np.dtype(old_dtypes + list(zip(names, dtypes))) newrec = np.recarray(rec.shape, dtype=newdtype) for field in rec.dtype.fields: @@ -2410,7 +2380,7 @@ def rec_keep_fields(rec, names): Return a new numpy record array with only fields listed in names """ - if isinstance(names, six.string_types): + if isinstance(names, str): names = names.split(',') arrays = [] @@ -2510,7 +2480,7 @@ def rec_join(key, r1, r2, jointype='inner', defaults=None, r1postfix='1', (other than keys) that are both in *r1* and *r2*. """ - if isinstance(key, six.string_types): + if isinstance(key, str): key = (key, ) for name in key: @@ -2587,8 +2557,6 @@ def mapped_r2field(name): r2desc = [(mapped_r2field(desc[0]), desc[1]) for desc in r2.dtype.descr if desc[0] not in key] all_dtypes = keydesc + r1desc + r2desc - if six.PY2: - all_dtypes = [(name.encode('utf-8'), dt) for name, dt in all_dtypes] newdtype = np.dtype(all_dtypes) newrec = np.recarray((common_len + left_len + right_len,), dtype=newdtype) @@ -2606,7 +2574,7 @@ def mapped_r2field(name): if jointype != 'inner' and defaults is not None: # fill in the defaults enmasse newrec_fields = list(newrec.dtype.fields) - for k, v in six.iteritems(defaults): + for k, v in defaults.items(): if k in newrec_fields: newrec[k] = v @@ -2922,7 +2890,7 @@ def get_converters(reader, comments): seen[item] = cnt+1 else: - if isinstance(names, six.string_types): + if isinstance(names, str): names = [n.strip() for n in names.split(',')] # get the converter functions by inspecting checkrows @@ -3688,7 +3656,7 @@ def __init__(self, dataset, bw_method=None): raise ValueError("`dataset` input should have multiple elements.") self.dim, self.num_dp = np.array(self.dataset).shape - isString = isinstance(bw_method, six.string_types) + isString = isinstance(bw_method, str) if bw_method is None: pass @@ -4021,13 +3989,15 @@ def offset_line(y, yerr): * A tuple of length 2. In this case, yerr[0] is the error below *y* and yerr[1] is error above *y*. For example:: - from pylab import * - x = linspace(0, 2*pi, num=100, endpoint=True) - y = sin(x) + import numpy as np + import matplotlib.pyplot as plt + + x = np.linspace(0, 2*np.pi, num=100, endpoint=True) + y = np.sin(x) y_minus, y_plus = mlab.offset_line(y, 0.1) - plot(x, y) - fill_between(x, ym, y2=yp) - show() + plt.plot(x, y) + plt.fill_between(x, y_minus, y2=y_plus) + plt.show() """ if cbook.is_numlike(yerr) or (cbook.iterable(yerr) and diff --git a/lib/matplotlib/mpl-data/images/help.pdf b/lib/matplotlib/mpl-data/images/help.pdf new file mode 100644 index 000000000000..38178d05b272 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.pdf differ diff --git a/lib/matplotlib/mpl-data/images/help.png b/lib/matplotlib/mpl-data/images/help.png new file mode 100644 index 000000000000..a52fbbe819e2 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.png differ diff --git a/lib/matplotlib/mpl-data/images/help.ppm b/lib/matplotlib/mpl-data/images/help.ppm new file mode 100644 index 000000000000..aed6f506df4d Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.ppm differ diff --git a/lib/matplotlib/mpl-data/images/help.svg b/lib/matplotlib/mpl-data/images/help.svg new file mode 100644 index 000000000000..484bdbcbf659 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/help.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/help_large.png b/lib/matplotlib/mpl-data/images/help_large.png new file mode 100644 index 000000000000..3f3d4dfed7e9 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help_large.png differ diff --git a/lib/matplotlib/mpl-data/images/help_large.ppm b/lib/matplotlib/mpl-data/images/help_large.ppm new file mode 100644 index 000000000000..4cf30807b0a1 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help_large.ppm differ diff --git a/lib/matplotlib/mpl-data/lineprops.glade b/lib/matplotlib/mpl-data/lineprops.glade deleted file mode 100644 index d731f3370bb7..000000000000 --- a/lib/matplotlib/mpl-data/lineprops.glade +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - True - Line Properties - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_DIALOG - GDK_GRAVITY_NORTH_WEST - True - - - - True - False - 0 - - - - True - GTK_BUTTONBOX_END - - - - True - True - True - gtk-cancel - True - GTK_RELIEF_NORMAL - True - -6 - - - - - - - True - True - True - gtk-ok - True - GTK_RELIEF_NORMAL - True - -5 - - - - - - 0 - False - True - GTK_PACK_END - - - - - - True - False - 0 - - - - True - - - - - 0 - True - True - - - - - - True - 0 - 0.5 - GTK_SHADOW_NONE - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 0 - 12 - 0 - - - - True - False - 0 - - - - True - 2 - 3 - False - 0 - 0 - - - - True - Marker - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - - - 0 - 1 - 1 - 2 - fill - - - - - - - True - - - - - 1 - 2 - 0 - 1 - fill - - - - - - True - - - - - 1 - 2 - 1 - 2 - fill - fill - - - - - - True - True - True - Line color - True - - - - 2 - 3 - 0 - 1 - fill - - - - - - - True - True - False - Marker color - True - - - - 2 - 3 - 1 - 2 - fill - - - - - - - True - Line style - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - - - 0 - 1 - 0 - 1 - fill - - - - - - 0 - True - True - - - - - - - - - - True - <b>Line properties</b> - False - True - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - - - label_item - - - - - 0 - True - True - - - - - 0 - True - True - - - - - - - diff --git a/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle b/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle index 70071b47b41e..853b3481e4c7 100644 --- a/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle @@ -109,8 +109,6 @@ text.usetex : False # use latex for all text handling. The following fo # If another font is desired which can loaded using the # LaTeX \usepackage command, please inquire at the # matplotlib mailing list -text.latex.unicode : False # use "ucs" and "inputenc" LaTeX packages for handling - # unicode strings. text.latex.preamble : # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. @@ -441,7 +439,6 @@ pdf.inheritcolor : False pdf.use14corefonts : False # pgf backend params -pgf.debug : False pgf.texsystem : xelatex pgf.rcfonts : True pgf.preamble : diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 493cc15d7f28..6da7b07b27dd 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -8,6 +8,8 @@ lines.linewidth : 1.0 # line width in points lines.linestyle : - # solid line lines.color : b # has no affect on plot(); see axes.prop_cycle lines.marker : None # the default marker +lines.markerfacecolor : auto # the default markerfacecolor +lines.markeredgecolor : auto # the default markeredgecolor lines.markeredgewidth : 0.5 # the line width around the marker symbol lines.markersize : 6 # markersize, in points lines.dash_joinstyle : round # miter|round|bevel @@ -109,8 +111,6 @@ text.usetex : False # use latex for all text handling. The following fo # If another font is desired which can loaded using the # LaTeX \usepackage command, please inquire at the # matplotlib mailing list -text.latex.unicode : False # use "ucs" and "inputenc" LaTeX packages for handling - # unicode strings. text.latex.preamble : # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES # AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP # IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. @@ -443,7 +443,6 @@ pdf.inheritcolor : False pdf.use14corefonts : False # pgf backend params -pgf.debug : False pgf.texsystem : xelatex pgf.rcfonts : True pgf.preamble : diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 86c3a0d525a7..75d910936555 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -14,18 +14,14 @@ width and height of the its child text. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +import warnings -import six -from six.moves import xrange, zip +import numpy as np -import warnings import matplotlib.transforms as mtransforms import matplotlib.artist as martist import matplotlib.text as mtext import matplotlib.path as mpath -import numpy as np from matplotlib.transforms import Bbox, BboxBase, TransformedBbox from matplotlib.font_manager import FontProperties @@ -146,7 +142,7 @@ class OffsetBox(martist.Artist): """ def __init__(self, *args, **kwargs): - super(OffsetBox, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Clipping has not been implemented in the OffesetBox family, so # disable the clip flag for consistency. It can always be turned back @@ -156,25 +152,6 @@ def __init__(self, *args, **kwargs): self._children = [] self._offset = (0, 0) - def __getstate__(self): - state = martist.Artist.__getstate__(self) - - # pickle cannot save instancemethods, so handle them here - from .cbook import _InstanceMethodPickler - import inspect - - offset = state['_offset'] - if inspect.ismethod(offset): - state['_offset'] = _InstanceMethodPickler(offset) - return state - - def __setstate__(self, state): - self.__dict__ = state - from .cbook import _InstanceMethodPickler - if isinstance(self._offset, _InstanceMethodPickler): - self._offset = self._offset.get_instancemethod() - self.stale = True - def set_figure(self, fig): """ Set the figure @@ -318,7 +295,7 @@ def __init__(self, pad=None, sep=None, width=None, height=None, the renderer dpi, while *width* and *height* need to be in pixels. """ - super(PackerBase, self).__init__() + super().__init__() self.height = height self.width = width @@ -366,9 +343,7 @@ def __init__(self, pad=None, sep=None, width=None, height=None, the renderer dpi, while *width* and *height* need to be in pixels. """ - super(VPacker, self).__init__(pad, sep, width, height, - align, mode, - children) + super().__init__(pad, sep, width, height, align, mode, children) def get_extent_offsets(self, renderer): """ @@ -403,9 +378,9 @@ def get_extent_offsets(self, renderer): yoffsets = yoffsets - ydescent - return width + 2 * pad, height + 2 * pad, \ - xdescent + pad, ydescent + pad, \ - list(zip(xoffsets, yoffsets)) + return (width + 2 * pad, height + 2 * pad, + xdescent + pad, ydescent + pad, + list(zip(xoffsets, yoffsets))) class HPacker(PackerBase): @@ -443,8 +418,7 @@ def __init__(self, pad=None, sep=None, width=None, height=None, the renderer dpi, while *width* and *height* need to be in pixels. """ - super(HPacker, self).__init__(pad, sep, width, height, - align, mode, children) + super().__init__(pad, sep, width, height, align, mode, children) def get_extent_offsets(self, renderer): """ @@ -482,9 +456,9 @@ def get_extent_offsets(self, renderer): xdescent = whd_list[0][2] xoffsets = xoffsets - xdescent - return width + 2 * pad, height + 2 * pad, \ - xdescent + pad, ydescent + pad, \ - list(zip(xoffsets, yoffsets)) + return (width + 2 * pad, height + 2 * pad, + xdescent + pad, ydescent + pad, + list(zip(xoffsets, yoffsets))) class PaddedBox(OffsetBox): @@ -498,7 +472,7 @@ def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None): need to be in pixels. """ - super(PaddedBox, self).__init__() + super().__init__() self.pad = pad self._children = [child] @@ -586,7 +560,7 @@ def __init__(self, width, height, xdescent=0., *clip* : Whether to clip the children """ - super(DrawingArea, self).__init__() + super().__init__() self.width = width self.height = height @@ -868,7 +842,7 @@ def draw(self, renderer): class AuxTransformBox(OffsetBox): """ - Offset Box with the aux_transform . Its children will be + Offset Box with the aux_transform. Its children will be transformed with the aux_transform first then will be offseted. The absolute coordinate of the aux_transform is meaning as it will be automatically adjust so that the left-lower corner @@ -1033,12 +1007,12 @@ def __init__(self, loc, bbox_transform : with which the bbox_to_anchor will be transformed. """ - super(AnchoredOffsetbox, self).__init__(**kwargs) + super().__init__(**kwargs) self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform) self.set_child(child) - if isinstance(loc, six.string_types): + if isinstance(loc, str): try: loc = self.codes[loc] except KeyError: @@ -1201,7 +1175,7 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad): """ assert loc in range(1, 11) # called only internally - BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = xrange(11) + BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11) anchor_coefs = {UR: "NE", UL: "NW", @@ -1261,7 +1235,7 @@ def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs): self.txt = TextArea(s, textprops=prop, minimumdescent=False) fp = self.txt._text.get_fontproperties() - super(AnchoredText, self).__init__( + super().__init__( loc, pad=pad, borderpad=borderpad, child=self.txt, prop=fp, **kwargs) @@ -1641,8 +1615,8 @@ def finalize_offset(self): *update_offset* places the artists simply in display coordinates. And *finalize_offset* recalculate their position in the normalized axes coordinate and set a relavant attribute. - """ + def __init__(self, ref_artist, use_blit=False): self.ref_artist = ref_artist self.got_artist = False @@ -1657,14 +1631,14 @@ def __init__(self, ref_artist, use_blit=False): self.cids = [c2, c3] def on_motion(self, evt): - if self.got_artist: + if self._check_still_parented() and self.got_artist: dx = evt.x - self.mouse_x dy = evt.y - self.mouse_y self.update_offset(dx, dy) self.canvas.draw() def on_motion_blit(self, evt): - if self.got_artist: + if self._check_still_parented() and self.got_artist: dx = evt.x - self.mouse_x dy = evt.y - self.mouse_y self.update_offset(dx, dy) @@ -1673,7 +1647,7 @@ def on_motion_blit(self, evt): self.canvas.blit(self.ref_artist.figure.bbox) def on_pick(self, evt): - if evt.artist == self.ref_artist: + if self._check_still_parented() and evt.artist == self.ref_artist: self.mouse_x = evt.mouseevent.x self.mouse_y = evt.mouseevent.y @@ -1694,7 +1668,7 @@ def on_pick(self, evt): self.save_offset() def on_release(self, event): - if self.got_artist: + if self._check_still_parented() and self.got_artist: self.finalize_offset() self.got_artist = False self.canvas.mpl_disconnect(self._c1) @@ -1702,6 +1676,13 @@ def on_release(self, event): if self._use_blit: self.ref_artist.set_animated(False) + def _check_still_parented(self): + if self.ref_artist.figure is None: + self.disconnect() + return False + else: + return True + def disconnect(self): """disconnect the callbacks""" for cid in self.cids: diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 1d66125561b1..1544c48d43b4 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1,12 +1,7 @@ -# -*- coding: utf-8 -*- - -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import map, zip - +import functools import math +from numbers import Number +import textwrap import warnings import numpy as np @@ -14,20 +9,20 @@ import matplotlib as mpl from . import artist, cbook, colors, docstring, lines as mlines, transforms from .bezier import ( - concatenate_paths, get_cos_sin, get_intersection, get_parallels, - inside_circle, make_path_regular, make_wedged_bezier2, - split_bezier_intersecting_with_closedpath, split_path_inout) + NonIntersectingPathException, concatenate_paths, get_cos_sin, + get_intersection, get_parallels, inside_circle, make_path_regular, + make_wedged_bezier2, split_bezier_intersecting_with_closedpath, + split_path_inout) from .path import Path -_patch_alias_map = { - 'antialiased': ['aa'], - 'edgecolor': ['ec'], - 'facecolor': ['fc'], - 'linewidth': ['lw'], - 'linestyle': ['ls'] - } - +@cbook._define_aliases({ + "antialiased": ["aa"], + "edgecolor": ["ec"], + "facecolor": ["fc"], + "linewidth": ["lw"], + "linestyle": ["ls"], +}) class Patch(artist.Artist): """ A patch is a 2D artist with a face color and an edge color. @@ -43,9 +38,6 @@ class Patch(artist.Artist): # subclass-by-subclass basis. _edge_default = False - def __str__(self): - return str(self.__class__).split('.')[-1] - def __init__(self, edgecolor=None, facecolor=None, @@ -79,9 +71,9 @@ def __init__(self, self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color']) self._fill = True # needed for set_facecolor call if color is not None: - if (edgecolor is not None or facecolor is not None): + if edgecolor is not None or facecolor is not None: warnings.warn("Setting the 'color' property will override" - "the edgecolor or facecolor properties. ") + "the edgecolor or facecolor properties.") self.set_color(color) else: self.set_edgecolor(edgecolor) @@ -120,7 +112,7 @@ def get_verts(self): def _process_radius(self, radius): if radius is not None: return radius - if cbook.is_numlike(self._picker): + if isinstance(self._picker, Number): _radius = self._picker else: if self.get_edgecolor()[3] == 0: @@ -176,6 +168,8 @@ def update_from(self, other): # getters/setters, so we just copy them directly. self._edgecolor = other._edgecolor self._facecolor = other._facecolor + self._original_edgecolor = other._original_edgecolor + self._original_facecolor = other._original_facecolor self._fill = other._fill self._hatch = other._hatch self._hatch_color = other._hatch_color @@ -183,6 +177,9 @@ def update_from(self, other): self._us_dashes = other._us_dashes self.set_linewidth(other._linewidth) # also sets dash properties self.set_transform(other.get_data_transform()) + # If the transform of other needs further initialization, then it will + # be the case for this artist too. + self._transformSet = other.is_transform_set() def get_extents(self): """ @@ -221,36 +218,30 @@ def get_antialiased(self): Returns True if the :class:`Patch` is to be drawn with antialiasing. """ return self._antialiased - get_aa = get_antialiased def get_edgecolor(self): """ Return the edge color of the :class:`Patch`. """ return self._edgecolor - get_ec = get_edgecolor def get_facecolor(self): """ Return the face color of the :class:`Patch`. """ return self._facecolor - get_fc = get_facecolor def get_linewidth(self): """ Return the line width in points. """ return self._linewidth - get_lw = get_linewidth def get_linestyle(self): """ - Return the linestyle. Will be one of ['solid' | 'dashed' | - 'dashdot' | 'dotted'] + Return the linestyle. """ return self._linestyle - get_ls = get_linestyle def set_antialiased(self, aa): """ @@ -259,17 +250,12 @@ def set_antialiased(self, aa): Parameters ---------- b : bool or None - .. ACCEPTS: bool or None """ if aa is None: aa = mpl.rcParams['patch.antialiased'] self._antialiased = aa self.stale = True - def set_aa(self, aa): - """alias for set_antialiased""" - return self.set_antialiased(aa) - def _set_edgecolor(self, color): set_hatch_color = True if color is None: @@ -287,17 +273,15 @@ def _set_edgecolor(self, color): def set_edgecolor(self, color): """ - Set the patch edge color + Set the patch edge color. - ACCEPTS: mpl color spec, None, 'none', or 'auto' + Parameters + ---------- + color : color or None or 'auto' """ self._original_edgecolor = color self._set_edgecolor(color) - def set_ec(self, color): - """alias for set_edgecolor""" - return self.set_edgecolor(color) - def _set_facecolor(self, color): if color is None: color = mpl.rcParams['patch.facecolor'] @@ -307,36 +291,38 @@ def _set_facecolor(self, color): def set_facecolor(self, color): """ - Set the patch face color + Set the patch face color. - ACCEPTS: mpl color spec, or None for default, or 'none' for no color + Parameters + ---------- + color : color or None """ self._original_facecolor = color self._set_facecolor(color) - def set_fc(self, color): - """alias for set_facecolor""" - return self.set_facecolor(color) - def set_color(self, c): """ Set both the edgecolor and the facecolor. - ACCEPTS: matplotlib color spec - .. seealso:: :meth:`set_facecolor`, :meth:`set_edgecolor` For setting the edge or face color individually. + + Parameters + ---------- + c : color """ self.set_facecolor(c) self.set_edgecolor(c) def set_alpha(self, alpha): """ - Set the alpha tranparency of the patch. + Set the alpha transparency of the patch. - ACCEPTS: float or None + Parameters + ---------- + alpha : float or None """ if alpha is not None: try: @@ -366,13 +352,9 @@ def set_linewidth(self, w): offset, ls, self._linewidth) self.stale = True - def set_lw(self, lw): - """alias for set_linewidth""" - return self.set_linewidth(lw) - def set_linestyle(self, ls): """ - Set the patch linestyle + Set the patch linestyle. =========================== ================= linestyle description @@ -387,33 +369,23 @@ def set_linestyle(self, ls): (offset, onoffseq), - where ``onoffseq`` is an even length tuple of on and off ink - in points. - - ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | - (offset, on-off-dash-seq) | - ``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | - ``' '`` | ``''``] + where ``onoffseq`` is an even length tuple of on and off ink in points. Parameters ---------- - ls : { '-', '--', '-.', ':'} and more see description + ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} The line style. """ if ls is None: ls = "solid" self._linestyle = ls - # get the unscalled dash pattern + # get the unscaled dash pattern offset, ls = self._us_dashes = mlines._get_dash_pattern(ls) # scale the dash pattern by the linewidth self._dashoffset, self._dashes = mlines._scale_dashes( offset, ls, self._linewidth) self.stale = True - def set_ls(self, ls): - """alias for set_linestyle""" - return self.set_linestyle(ls) - def set_fill(self, b): """ Set whether to fill the patch. @@ -421,7 +393,6 @@ def set_fill(self, b): Parameters ---------- b : bool - .. ACCEPTS: bool """ self._fill = bool(b) self._set_facecolor(self._original_facecolor) @@ -441,7 +412,9 @@ def set_capstyle(self, s): """ Set the patch capstyle - ACCEPTS: ['butt' | 'round' | 'projecting'] + Parameters + ---------- + s : {'butt', 'round', 'projecting'} """ s = s.lower() if s not in self.validCap: @@ -458,7 +431,9 @@ def set_joinstyle(self, s): """ Set the patch joinstyle - ACCEPTS: ['miter' | 'round' | 'bevel'] + Parameters + ---------- + s : {'miter', 'round', 'bevel'} """ s = s.lower() if s not in self.validJoin: @@ -472,13 +447,13 @@ def get_joinstyle(self): return self._joinstyle def set_hatch(self, hatch): - """ + r""" Set the hatching pattern *hatch* can be one of:: / - diagonal hatching - \\ - back diagonal + \ - back diagonal | - vertical - - horizontal + - crossed @@ -495,7 +470,9 @@ def set_hatch(self, hatch): Hatching is supported in the PostScript, PDF, SVG and Agg backends only. - ACCEPTS: ['/' | '\\\\' | '|' | '-' | '+' | 'x' | 'o' | 'O' | '.' | '*'] + Parameters + ---------- + hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} """ self._hatch = hatch self.stale = True @@ -540,8 +517,8 @@ def draw(self, renderer): gc.set_hatch_color(self._hatch_color) except AttributeError: # if we end up with a GC that does not have this method - warnings.warn("Your backend does not have support for " - "setting the hatch color.") + warnings.warn( + "Your backend does not support setting the hatch color.") if self.get_sketch_params() is not None: gc.set_sketch_params(*self.get_sketch_params()) @@ -612,14 +589,9 @@ def _update(self): if self.props is not None: self.update(self.props) else: - r, g, b, a = colors.to_rgba(self.patch.get_facecolor()) - rho = 0.3 - r = rho * r - g = rho * g - b = rho * b - - self.set_facecolor((r, g, b, 0.5)) - self.set_edgecolor((r, g, b, 0.5)) + color = .3 * np.asarray(colors.to_rgb(self.patch.get_facecolor())) + self.set_facecolor(color) + self.set_edgecolor(color) self.set_alpha(0.5) def _update_transform(self, renderer): @@ -700,7 +672,7 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs): def get_path(self): """ - Return the vertices of the rectangle + Return the vertices of the rectangle. """ return Path.unit_rectangle() @@ -724,9 +696,9 @@ def _update_y1(self): self._y1 = self._y0 + self._height def _convert_units(self): - ''' - Convert bounds of the rectangle - ''' + """ + Convert bounds of the rectangle. + """ x0 = self.convert_xunits(self._x0) y0 = self.convert_yunits(self._y0) x1 = self.convert_xunits(self._x1) @@ -738,42 +710,44 @@ def get_patch_transform(self): return self._rect_transform def get_x(self): - "Return the left coord of the rectangle" + "Return the left coord of the rectangle." return self._x0 def get_y(self): - "Return the bottom coord of the rectangle" + "Return the bottom coord of the rectangle." return self._y0 def get_xy(self): - "Return the left and bottom coords of the rectangle" + "Return the left and bottom coords of the rectangle." return self._x0, self._y0 def get_width(self): - "Return the width of the rectangle" + "Return the width of the rectangle." return self._width def get_height(self): - "Return the height of the rectangle" + "Return the height of the rectangle." return self._height def set_x(self, x): - "Set the left coord of the rectangle" + "Set the left coord of the rectangle." self._x0 = x self._update_x1() self.stale = True def set_y(self, y): - "Set the bottom coord of the rectangle" + "Set the bottom coord of the rectangle." self._y0 = y self._update_y1() self.stale = True def set_xy(self, xy): """ - Set the left and bottom coords of the rectangle + Set the left and bottom coords of the rectangle. - ACCEPTS: 2-item sequence + Parameters + ---------- + xy : 2-item sequence """ self._x0, self._y0 = xy self._update_x1() @@ -781,13 +755,13 @@ def set_xy(self, xy): self.stale = True def set_width(self, w): - "Set the width of the rectangle" + "Set the width of the rectangle." self._width = w self._update_x1() self.stale = True def set_height(self, h): - "Set the height of the rectangle" + "Set the height of the rectangle." self._height = h self._update_y1() self.stale = True @@ -798,7 +772,7 @@ def set_bounds(self, *args): ACCEPTS: (left, bottom, width, height) """ - if len(args) == 0: + if len(args) == 1: l, b, w, h = args[0] else: l, b, w, h = args @@ -822,7 +796,9 @@ class RegularPolygon(Patch): A regular polygon patch. """ def __str__(self): - return "Poly%d(%g,%g)" % (self._numVertices, self._xy[0], self._xy[1]) + s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)" + return s % (self._xy[0], self._xy[1], self._numVertices, self._radius, + self._orientation) @docstring.dedent_interpd def __init__(self, xy, numVertices, radius=5, orientation=0, @@ -908,7 +884,8 @@ class PathPatch(Patch): _edge_default = True def __str__(self): - return "Poly((%g, %g) ...)" % tuple(self._path.vertices[0]) + s = "PathPatch%d((%g, %g) ...)" + return s % (len(self._path.vertices), *tuple(self._path.vertices[0])) @docstring.dedent_interpd def __init__(self, path, **kwargs): @@ -936,7 +913,8 @@ class Polygon(Patch): A general polygon patch. """ def __str__(self): - return "Poly((%g, %g) ...)" % tuple(self._path.vertices[0]) + s = "Polygon%d((%g, %g) ...)" + return s % (len(self._path.vertices), *tuple(self._path.vertices[0])) @docstring.dedent_interpd def __init__(self, xy, closed=True, **kwargs): @@ -999,25 +977,23 @@ def set_closed(self, closed): def get_xy(self): """ - Get the vertices of the path + Get the vertices of the path. Returns ------- - vertices : numpy array - The coordinates of the vertices as a Nx2 - ndarray. + vertices : (N, 2) numpy array + The coordinates of the vertices. """ return self._path.vertices def set_xy(self, xy): """ - Set the vertices of the polygon + Set the vertices of the polygon. Parameters ---------- - xy : numpy array or iterable of pairs - The coordinates of the vertices as a Nx2 - ndarray or iterable of pairs. + xy : (N, 2) array-like + The coordinates of the vertices. """ xy = np.asarray(xy) if self._closed: @@ -1031,13 +1007,8 @@ def set_xy(self, xy): _get_xy = get_xy _set_xy = set_xy - xy = property( - get_xy, set_xy, None, - """Set/get the vertices of the polygon. This property is - provided for backward compatibility with matplotlib 0.91.x - only. New code should use - :meth:`~matplotlib.patches.Polygon.get_xy` and - :meth:`~matplotlib.patches.Polygon.set_xy` instead.""") + xy = property(get_xy, set_xy, + doc='The vertices of the path as (N, 2) numpy array.') class Wedge(Patch): @@ -1258,10 +1229,10 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, # start by drawing horizontal arrow, point at (0,0) hw, hl, hs, lw = head_width, head_length, overhang, width left_half_arrow = np.array([ - [0.0, 0.0], # tip - [-hl, -hw / 2.0], # leftmost - [-hl * (1 - hs), -lw / 2.0], # meets stem - [-length, -lw / 2.0], # bottom left + [0.0, 0.0], # tip + [-hl, -hw / 2], # leftmost + [-hl * (1 - hs), -lw / 2], # meets stem + [-length, -lw / 2], # bottom left [-length, 0], ]) # if we're not including the head, shift up by head length @@ -1269,7 +1240,7 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, left_half_arrow += [head_length, 0] # if the head starts at 0, shift up by another head length if head_starts_at_zero: - left_half_arrow += [head_length / 2.0, 0] + left_half_arrow += [head_length / 2, 0] # figure out the shape, and complete accordingly if shape == 'left': coords = left_half_arrow @@ -1294,12 +1265,13 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, M = [[cx, sx], [-sx, cx]] verts = np.dot(coords, M) + (x + dx, y + dy) - Polygon.__init__(self, list(map(tuple, verts)), closed=True, **kwargs) + super().__init__(verts, closed=True, **kwargs) docstring.interpd.update({"FancyArrow": FancyArrow.__init__.__doc__}) +@cbook.deprecated("3.0", alternative="FancyArrowPatch") class YAArrow(Patch): """ Yet another arrow class. @@ -1323,8 +1295,7 @@ def __init__(self, figure, xytip, xybase, (*x*, *y*) location the arrow base mid point *figure* - The :class:`~matplotlib.figure.Figure` instance - (fig.dpi) + The `Figure` instance (used to get the dpi setting). *width* The width of the arrow in points @@ -1409,7 +1380,8 @@ class CirclePolygon(RegularPolygon): A polygon-approximation of a circle patch. """ def __str__(self): - return "CirclePolygon(%d,%d)" % self.center + s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)" + return s % (self._xy[0], self._xy[1], self._radius, self._numVertices) @docstring.dedent_interpd def __init__(self, xy, radius=5, @@ -1437,32 +1409,33 @@ class Ellipse(Patch): A scale-free ellipse. """ def __str__(self): - pars = (self.center[0], self.center[1], + pars = (self._center[0], self._center[1], self.width, self.height, self.angle) fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)" return fmt % pars @docstring.dedent_interpd - def __init__(self, xy, width, height, angle=0.0, **kwargs): + def __init__(self, xy, width, height, angle=0, **kwargs): """ - *xy* - center of ellipse - - *width* - total length (diameter) of horizontal axis - - *height* - total length (diameter) of vertical axis - - *angle* - rotation in degrees (anti-clockwise) + Parameters + ---------- + xy : tuple of (scalar, scalar) + xy coordinates of ellipse centre. + width : scalar + Total length (diameter) of horizontal axis. + height : scalar + Total length (diameter) of vertical axis. + angle : scalar, optional + Rotation in degrees anti-clockwise. - Valid kwargs are: + Notes + ----- + Valid keyword arguments are %(Patch)s """ Patch.__init__(self, **kwargs) - self.center = xy + self._center = xy self.width, self.height = width, height self.angle = angle self._path = Path.unit_circle() @@ -1475,8 +1448,8 @@ def _recompute_transform(self): makes it very important to call the accessor method and not directly access the transformation member variable. """ - center = (self.convert_xunits(self.center[0]), - self.convert_yunits(self.center[1])) + center = (self.convert_xunits(self._center[0]), + self.convert_yunits(self._center[1])) width = self.convert_xunits(self.width) height = self.convert_yunits(self.height) self._patch_transform = transforms.Affine2D() \ @@ -1494,6 +1467,25 @@ def get_patch_transform(self): self._recompute_transform() return self._patch_transform + def set_center(self, xy): + """ + Set the center of the ellipse. + + Parameters + ---------- + xy : (float, float) + """ + self._center = xy + self.stale = True + + def get_center(self): + """ + Return the center of the ellipse + """ + return self._center + + center = property(get_center, set_center) + class Circle(Ellipse): """ @@ -1509,7 +1501,7 @@ def __init__(self, xy, radius=5, **kwargs): """ Create true circle at center *xy* = (*x*, *y*) with given *radius*. Unlike :class:`~matplotlib.patches.CirclePolygon` - which is a polygonal approximation, this uses Bézier splines + which is a polygonal approximation, this uses Bezier splines and is much closer to a scale-free circle. Valid kwargs are: @@ -1523,13 +1515,17 @@ def set_radius(self, radius): """ Set the radius of the circle - ACCEPTS: float + Parameters + ---------- + radius : float """ self.width = self.height = 2 * radius self.stale = True def get_radius(self): - 'return the radius of the circle' + """ + Return the radius of the circle + """ return self.width / 2. radius = property(get_radius, set_radius) @@ -1597,7 +1593,7 @@ def __init__(self, xy, width, height, angle=0.0, def draw(self, renderer): """ Ellipses are normally drawn using an approximation that uses - eight cubic bezier splines. The error of this approximation + eight cubic Bezier splines. The error of this approximation is 1.89818e-6, according to this unverified source: Lancaster, Don. Approximating a Circle or an Ellipse Using @@ -1635,7 +1631,7 @@ def draw(self, renderer): 3. Proceeding counterclockwise starting in the positive x-direction, each of the visible arc-segments between the - pairs of vertices are drawn using the bezier arc + pairs of vertices are drawn using the Bezier arc approximation technique implemented in :meth:`matplotlib.path.Path.arc`. """ @@ -1705,7 +1701,7 @@ def iter_circle_intersect_on_line_seg(x0, y0, x1, y1): x1e += epsilon y1e += epsilon for x, y in iter_circle_intersect_on_line(x0, y0, x1, y1): - if x >= x0e and x <= x1e and y >= y0e and y <= y1e: + if x0e <= x <= x1e and y0e <= y <= y1e: yield x, y # Transforms the axes box_path so that it is relative to the unit @@ -1804,47 +1800,29 @@ def draw_bbox(bbox, renderer, color='k', trans=None): r.draw(renderer) -def _pprint_table(_table, leadingspace=2): +def _pprint_table(table, leadingspace=2): """ Given the list of list of strings, return a string of REST table format. """ - if leadingspace: - pad = ' ' * leadingspace - else: - pad = '' - - columns = [[] for cell in _table[0]] - - for row in _table: - for column, cell in zip(columns, row): - column.append(cell) - - col_len = [max(len(cell) for cell in column) for column in columns] - - lines = [] - table_formatstr = pad + ' '.join([('=' * cl) for cl in col_len]) - - lines.append('') - lines.append(table_formatstr) - lines.append(pad + ' '.join([cell.ljust(cl) - for cell, cl - in zip(_table[0], col_len)])) - lines.append(table_formatstr) - - lines.extend([(pad + ' '.join([cell.ljust(cl) - for cell, cl - in zip(row, col_len)])) - for row in _table[1:]]) - - lines.append(table_formatstr) - lines.append('') - return "\n".join(lines) + col_len = [max(len(cell) for cell in column) for column in zip(*table)] + table_formatstr = ' '.join('=' * cl for cl in col_len) + lines = [ + '', + table_formatstr, + ' '.join(cell.ljust(cl) for cell, cl in zip(table[0], col_len)), + table_formatstr, + *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len)) + for row in table[1:]], + table_formatstr, + '', + ] + return textwrap.indent('\n'.join(lines), ' ' * leadingspace) def _pprint_styles(_styles): """ A helper function for the _Style class. Given the dictionary of - (stylename : styleclass), return a formatted string listing all the + {stylename: styleclass}, return a formatted string listing all the styles. Used to update the documentation. """ import inspect @@ -1852,24 +1830,13 @@ def _pprint_styles(_styles): _table = [["Class", "Name", "Attrs"]] for name, cls in sorted(_styles.items()): - if six.PY2: - args, varargs, varkw, defaults = inspect.getargspec(cls.__init__) + spec = inspect.getfullargspec(cls.__init__) + if spec.defaults: + argstr = ", ".join(map( + "{}={}".format, spec.args[-len(spec.defaults):], spec.defaults + )) else: - (args, varargs, varkw, defaults, kwonlyargs, kwonlydefs, - annotations) = inspect.getfullargspec(cls.__init__) - if defaults: - args = [(argname, argdefault) - for argname, argdefault in zip(args[1:], defaults)] - else: - args = None - - if args is None: argstr = 'None' - else: - argstr = ",".join([("%s=%s" % (an, av)) - for an, av - in args]) - # adding ``quotes`` since - and | have special meaning in reST _table.append([cls.__name__, "``%s``" % name, argstr]) @@ -1879,7 +1846,7 @@ def _pprint_styles(_styles): def _simpleprint_styles(_styles): """ A helper function for the _Style class. Given the dictionary of - (stylename : styleclass), return a string rep of the list of keys. + {stylename: styleclass}, return a string rep of the list of keys. Used to update the documentation. """ return "[{}]".format("|".join(map(" '{}' ".format, sorted(_styles)))) @@ -1891,18 +1858,18 @@ class _Style(object): where actual styles are declared as subclass of it, and it provides some helper functions. """ - def __new__(self, stylename, **kw): + def __new__(cls, stylename, **kw): """ return the instance of the subclass with the given style name. """ - # the "class" should have the _style_list attribute, which is - # a dictionary of stylname, style class paie. + # The "class" should have the _style_list attribute, which is a mapping + # of style names to style classes. _list = stylename.replace(" ", "").split(",") _name = _list[0].lower() try: - _cls = self._style_list[_name] + _cls = cls._style_list[_name] except KeyError: raise ValueError("Unknown style : %s" % stylename) @@ -1916,29 +1883,37 @@ def __new__(self, stylename, **kw): return _cls(**_args) @classmethod - def get_styles(klass): + def get_styles(cls): """ A class method which returns a dictionary of available styles. """ - return klass._style_list + return cls._style_list @classmethod - def pprint_styles(klass): + def pprint_styles(cls): """ A class method which returns a string of the available styles. """ - return _pprint_styles(klass._style_list) + return _pprint_styles(cls._style_list) @classmethod - def register(klass, name, style): + def register(cls, name, style): """ Register a new style. """ - if not issubclass(style, klass._Base): + if not issubclass(style, cls._Base): raise ValueError("%s must be a subclass of %s" % (style, - klass._Base)) - klass._style_list[name] = style + cls._Base)) + cls._style_list[name] = style + + +def _register_style(style_list, cls=None, *, name=None): + """Class decorator that stashes a class in a (style) dictionary.""" + if cls is None: + return functools.partial(_register_style, style_list, name=name) + style_list[name or cls.__name__.lower()] = cls + return cls class BoxStyle(_Style): @@ -1990,12 +1965,6 @@ class is not an artist and actual drawing of the fancy box is done # w/o arguments, i.e., all its argument (except self) must have # the default values. - def __init__(self): - """ - initializtion. - """ - super(BoxStyle._Base, self).__init__() - def transmute(self, x0, y0, width, height, mutation_size): """ The transmute method is a very core of the @@ -2018,7 +1987,7 @@ def __call__(self, x0, y0, width, height, mutation_size, - *aspect_ratio* : aspect-ration for the mutation. """ # The __call__ method is a thin wrapper around the transmute method - # and take care of the aspect. + # and takes care of the aspect. if aspect_ratio is not None: # Squeeze the given height by the aspect_ratio @@ -2032,14 +2001,7 @@ def __call__(self, x0, y0, width, height, mutation_size, else: return self.transmute(x0, y0, width, height, mutation_size) - def __reduce__(self): - # because we have decided to nest these classes, we need to - # add some more information to allow instance pickling. - return (cbook._NestedClassGetter(), - (BoxStyle, self.__class__.__name__), - self.__dict__ - ) - + @_register_style(_style_list) class Square(_Base): """ A simple square box. @@ -2052,7 +2014,7 @@ def __init__(self, pad=0.3): """ self.pad = pad - super(BoxStyle.Square, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad @@ -2068,8 +2030,7 @@ def transmute(self, x0, y0, width, height, mutation_size): codes = [Path.MOVETO] + [Path.LINETO] * 3 + [Path.CLOSEPOLY] return Path(vertices, codes) - _style_list["square"] = Square - + @_register_style(_style_list) class Circle(_Base): """A simple circle box.""" def __init__(self, pad=0.3): @@ -2080,7 +2041,7 @@ def __init__(self, pad=0.3): The amount of padding around the original box. """ self.pad = pad - super(BoxStyle.Circle, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): pad = mutation_size * self.pad @@ -2091,15 +2052,14 @@ def transmute(self, x0, y0, width, height, mutation_size): return Path.circle((x0 + width / 2, y0 + height / 2), max(width, height) / 2) - _style_list["circle"] = Circle - + @_register_style(_style_list) class LArrow(_Base): """ (left) Arrow Box """ def __init__(self, pad=0.3): self.pad = pad - super(BoxStyle.LArrow, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): # padding @@ -2129,27 +2089,23 @@ def transmute(self, x0, y0, width, height, mutation_size): path = Path(cp, com) return path - _style_list["larrow"] = LArrow + @_register_style(_style_list) class RArrow(LArrow): """ (right) Arrow Box """ def __init__(self, pad=0.3): - super(BoxStyle.RArrow, self).__init__(pad) + super().__init__(pad) def transmute(self, x0, y0, width, height, mutation_size): - p = BoxStyle.LArrow.transmute(self, x0, y0, width, height, mutation_size) - p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0] - return p - _style_list["rarrow"] = RArrow - + @_register_style(_style_list) class DArrow(_Base): """ (Double) Arrow Box @@ -2159,7 +2115,7 @@ class DArrow(_Base): def __init__(self, pad=0.3): self.pad = pad - super(BoxStyle.DArrow, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): @@ -2174,7 +2130,7 @@ def transmute(self, x0, y0, width, height, mutation_size): x0, y0 = x0 - pad, y0 - pad x1, y1 = x0 + width, y0 + height - dx = (y1 - y0)/2. + dx = (y1 - y0) / 2 dxx = dx * .5 # adjust x0. 1.4 <- sqrt(2) x0 = x0 + pad / 1.4 @@ -2199,8 +2155,7 @@ def transmute(self, x0, y0, width, height, mutation_size): return path - _style_list['darrow'] = DArrow - + @_register_style(_style_list) class Round(_Base): """ A box with round corners. @@ -2216,14 +2171,14 @@ def __init__(self, pad=0.3, rounding_size=None): """ self.pad = pad self.rounding_size = rounding_size - super(BoxStyle.Round, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad - # size of the roudning corner + # size of the rounding corner if self.rounding_size: dr = mutation_size * self.rounding_size else: @@ -2234,7 +2189,7 @@ def transmute(self, x0, y0, width, height, mutation_size): x0, y0 = x0 - pad, y0 - pad, x1, y1 = x0 + width, y0 + height - # Round corners are implemented as quadratic bezier. e.g., + # Round corners are implemented as quadratic Bezier, e.g., # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner. cp = [(x0 + dr, y0), (x1 - dr, y0), @@ -2262,8 +2217,7 @@ def transmute(self, x0, y0, width, height, mutation_size): return path - _style_list["round"] = Round - + @_register_style(_style_list) class Round4(_Base): """ Another box with round edges. @@ -2277,17 +2231,16 @@ def __init__(self, pad=0.3, rounding_size=None): *rounding_size* rounding size of edges. *pad* if None """ - self.pad = pad self.rounding_size = rounding_size - super(BoxStyle.Round4, self).__init__() + super().__init__() def transmute(self, x0, y0, width, height, mutation_size): # padding pad = mutation_size * self.pad - # roudning size. Use a half of the pad if not set. + # Rounding size; defaults to half of the padding. if self.rounding_size: dr = mutation_size * self.rounding_size else: @@ -2317,8 +2270,7 @@ def transmute(self, x0, y0, width, height, mutation_size): return path - _style_list["round4"] = Round4 - + @_register_style(_style_list) class Sawtooth(_Base): """ A sawtooth box. @@ -2334,7 +2286,7 @@ def __init__(self, pad=0.3, tooth_size=None): """ self.pad = pad self.tooth_size = tooth_size - super(BoxStyle.Sawtooth, self).__init__() + super().__init__() def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size): @@ -2361,73 +2313,62 @@ def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size): x0, y0 = x0 - pad + tooth_size2, y0 - pad + tooth_size2 x1, y1 = x0 + width, y0 + height - bottom_saw_x = [x0] + \ - [x0 + tooth_size2 + dsx * .5 * i - for i - in range(dsx_n * 2)] + \ - [x1 - tooth_size2] - - bottom_saw_y = [y0] + \ - [y0 - tooth_size2, y0, - y0 + tooth_size2, y0] * dsx_n + \ - [y0 - tooth_size2] - - right_saw_x = [x1] + \ - [x1 + tooth_size2, - x1, - x1 - tooth_size2, - x1] * dsx_n + \ - [x1 + tooth_size2] - - right_saw_y = [y0] + \ - [y0 + tooth_size2 + dsy * .5 * i - for i - in range(dsy_n * 2)] + \ - [y1 - tooth_size2] - - top_saw_x = [x1] + \ - [x1 - tooth_size2 - dsx * .5 * i - for i - in range(dsx_n * 2)] + \ - [x0 + tooth_size2] - - top_saw_y = [y1] + \ - [y1 + tooth_size2, - y1, - y1 - tooth_size2, - y1] * dsx_n + \ - [y1 + tooth_size2] - - left_saw_x = [x0] + \ - [x0 - tooth_size2, - x0, - x0 + tooth_size2, - x0] * dsy_n + \ - [x0 - tooth_size2] - - left_saw_y = [y1] + \ - [y1 - tooth_size2 - dsy * .5 * i - for i - in range(dsy_n * 2)] + \ - [y0 + tooth_size2] - - saw_vertices = (list(zip(bottom_saw_x, bottom_saw_y)) + - list(zip(right_saw_x, right_saw_y)) + - list(zip(top_saw_x, top_saw_y)) + - list(zip(left_saw_x, left_saw_y)) + - [(bottom_saw_x[0], bottom_saw_y[0])]) + bottom_saw_x = [ + x0, + *(x0 + tooth_size2 + dsx * .5 * np.arange(dsx_n * 2)), + x1 - tooth_size2, + ] + bottom_saw_y = [ + y0, + *([y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n), + y0 - tooth_size2, + ] + right_saw_x = [ + x1, + *([x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n), + x1 + tooth_size2, + ] + right_saw_y = [ + y0, + *(y0 + tooth_size2 + dsy * .5 * np.arange(dsy_n * 2)), + y1 - tooth_size2, + ] + top_saw_x = [ + x1, + *(x1 - tooth_size2 - dsx * .5 * np.arange(dsx_n * 2)), + x0 + tooth_size2, + ] + top_saw_y = [ + y1, + *([y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n), + y1 + tooth_size2, + ] + left_saw_x = [ + x0, + *([x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n), + x0 - tooth_size2, + ] + left_saw_y = [ + y1, + *(y1 - tooth_size2 - dsy * .5 * np.arange(dsy_n * 2)), + y0 + tooth_size2, + ] + + saw_vertices = [*zip(bottom_saw_x, bottom_saw_y), + *zip(right_saw_x, right_saw_y), + *zip(top_saw_x, top_saw_y), + *zip(left_saw_x, left_saw_y), + (bottom_saw_x[0], bottom_saw_y[0])] return saw_vertices def transmute(self, x0, y0, width, height, mutation_size): - saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size) path = Path(saw_vertices, closed=True) return path - _style_list["sawtooth"] = Sawtooth - + @_register_style(_style_list) class Roundtooth(Sawtooth): """A rounded tooth box.""" def __init__(self, pad=0.3, tooth_size=None): @@ -2438,7 +2379,7 @@ def __init__(self, pad=0.3, tooth_size=None): *tooth_size* size of the sawtooth. pad* if None """ - super(BoxStyle.Roundtooth, self).__init__(pad, tooth_size) + super().__init__(pad, tooth_size) def transmute(self, x0, y0, width, height, mutation_size): saw_vertices = self._get_sawtooth_vertices(x0, y0, @@ -2452,8 +2393,6 @@ def transmute(self, x0, y0, width, height, mutation_size): [Path.CLOSEPOLY]) return Path(saw_vertices, codes) - _style_list["roundtooth"] = Roundtooth - if __doc__: # __doc__ could be None if -OO optimization is enabled __doc__ = cbook.dedent(__doc__) % \ {"AvailableBoxstyles": _pprint_styles(_style_list)} @@ -2478,9 +2417,8 @@ class FancyBboxPatch(Patch): _edge_default = True def __str__(self): - return self.__class__.__name__ \ - + "(%g,%g;%gx%g)" % (self._x, self._y, - self._width, self._height) + s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)" + return s % (self._x, self._y, self._width, self._height) @docstring.dedent_interpd def __init__(self, xy, width, height, @@ -2568,7 +2506,9 @@ def set_mutation_scale(self, scale): """ Set the mutation scale. - ACCEPTS: float + Parameters + ---------- + scale : float """ self._mutation_scale = scale self.stale = True @@ -2583,7 +2523,9 @@ def set_mutation_aspect(self, aspect): """ Set the aspect ratio of the bbox mutation. - ACCEPTS: float + Parameters + ---------- + aspect : float """ self._mutation_aspect = aspect self.stale = True @@ -2620,7 +2562,7 @@ def get_y(self): return self._y def get_width(self): - "Return the width of the rectangle" + "Return the width of the rectangle" return self._width def get_height(self): @@ -2629,36 +2571,44 @@ def get_height(self): def set_x(self, x): """ - Set the left coord of the rectangle + Set the left coord of the rectangle. - ACCEPTS: float + Parameters + ---------- + x : float """ self._x = x self.stale = True def set_y(self, y): """ - Set the bottom coord of the rectangle + Set the bottom coord of the rectangle. - ACCEPTS: float + Parameters + ---------- + y : float """ self._y = y self.stale = True def set_width(self, w): """ - Set the width rectangle + Set the rectangle width. - ACCEPTS: float + Parameters + ---------- + w : float """ self._width = w self.stale = True def set_height(self, h): """ - Set the width rectangle + Set the rectangle height. - ACCEPTS: float + Parameters + ---------- + h : float """ self._height = h self.stale = True @@ -2669,7 +2619,7 @@ def set_bounds(self, *args): ACCEPTS: (left, bottom, width, height) """ - if len(args) == 0: + if len(args) == 1: l, b, w, h = args[0] else: l, b, w, h = args @@ -2818,17 +2768,10 @@ def __call__(self, posA, posB, return shrunk_path - def __reduce__(self): - # because we have decided to nest these classes, we need to - # add some more information to allow instance pickling. - return (cbook._NestedClassGetter(), - (ConnectionStyle, self.__class__.__name__), - self.__dict__ - ) - + @_register_style(_style_list) class Arc3(_Base): """ - Creates a simple quadratic bezier curve between two + Creates a simple quadratic Bezier curve between two points. The curve is created so that the middle control point (C1) is located at the same distance from the start (C0) and end points(C2) and the distance of the C1 to the line @@ -2861,14 +2804,13 @@ def connect(self, posA, posB): return Path(vertices, codes) - _style_list["arc3"] = Arc3 - + @_register_style(_style_list) class Angle3(_Base): """ - Creates a simple quadratic bezier curve between two + Creates a simple quadratic Bezier curve between two points. The middle control points is placed at the - intersecting point of two lines which crosses the start (or - end) point and has a angle of angleA (or angleB). + intersecting point of two lines which cross the start and + end point, and have a slope of angleA and angleB, respectively. """ def __init__(self, angleA=90, angleB=0): @@ -2900,15 +2842,14 @@ def connect(self, posA, posB): return Path(vertices, codes) - _style_list["angle3"] = Angle3 - + @_register_style(_style_list) class Angle(_Base): """ - Creates a picewise continuous quadratic bezier path between + Creates a piecewise continuous quadratic Bezier path between two points. The path has a one passing-through point placed at - the intersecting point of two lines which crosses the start - (or end) point and has a angle of angleA (or angleB). The - connecting edges are rounded with *rad*. + the intersecting point of two lines which cross the start + and end point, and have a slope of angleA and angleB, respectively. + The connecting edges are rounded with *rad*. """ def __init__(self, angleA=90, angleB=0, rad=0.): @@ -2963,11 +2904,10 @@ def connect(self, posA, posB): return Path(vertices, codes) - _style_list["angle"] = Angle - + @_register_style(_style_list) class Arc(_Base): """ - Creates a picewise continuous quadratic bezier path between + Creates a piecewise continuous quadratic Bezier path between two points. The path can have two passing-through points, a point placed at the distance of armA and angle of angleA from point A, another point with respect to point B. The edges are @@ -3058,8 +2998,7 @@ def connect(self, posA, posB): return Path(vertices, codes) - _style_list["arc"] = Arc - + @_register_style(_style_list) class Bar(_Base): """ A line with *angle* between A and B with *armA* and @@ -3135,8 +3074,6 @@ def connect(self, posA, posB): return Path(vertices, codes) - _style_list["bar"] = Bar - if __doc__: __doc__ = cbook.dedent(__doc__) % \ {"AvailableConnectorstyles": _pprint_styles(_style_list)} @@ -3212,41 +3149,38 @@ class is not an artist and actual drawing of the fancy arrow is @staticmethod def ensure_quadratic_bezier(path): - """ Some ArrowStyle class only wokrs with a simple - quaratic bezier curve (created with Arc3Connetion or - Angle3Connector). This static method is to check if the - provided path is a simple quadratic bezier curve and returns - its control points if true. + """ + Some ArrowStyle class only works with a simple quadratic Bezier + curve (created with Arc3Connetion or Angle3Connector). This static + method is to check if the provided path is a simple quadratic + Bezier curve and returns its control points if true. """ segments = list(path.iter_segments()) if (len(segments) != 2 or segments[0][1] != Path.MOVETO or segments[1][1] != Path.CURVE3): raise ValueError( - "'path' it's not a valid quadratic Bezier curve") - - return list(segments[0][0]) + list(segments[1][0]) + "'path' is not a valid quadratic Bezier curve") + return [*segments[0][0], *segments[1][0]] def transmute(self, path, mutation_size, linewidth): """ - The transmute method is the very core of the ArrowStyle - class and must be overridden in the subclasses. It receives - the path object along which the arrow will be drawn, and - the mutation_size, with which the arrow head etc. - will be scaled. The linewidth may be used to adjust - the path so that it does not pass beyond the given - points. It returns a tuple of a Path instance and a - boolean. The boolean value indicate whether the path can - be filled or not. The return value can also be a list of paths - and list of booleans of a same length. + The transmute method is the very core of the ArrowStyle class and + must be overridden in the subclasses. It receives the path object + along which the arrow will be drawn, and the mutation_size, with + which the arrow head etc. will be scaled. The linewidth may be + used to adjust the path so that it does not pass beyond the given + points. It returns a tuple of a Path instance and a boolean. The + boolean value indicate whether the path can be filled or not. The + return value can also be a list of paths and list of booleans of a + same length. """ - raise NotImplementedError('Derived must override') def __call__(self, path, mutation_size, linewidth, aspect_ratio=1.): """ The __call__ method is a thin wrapper around the transmute method - and take care of the aspect ratio. + and takes care of the aspect ratio. """ path = make_path_regular(path) @@ -3275,14 +3209,6 @@ def __call__(self, path, mutation_size, linewidth, else: return self.transmute(path, mutation_size, linewidth) - def __reduce__(self): - # because we have decided to nest these classes, we need to - # add some more information to allow instance pickling. - return (cbook._NestedClassGetter(), - (ArrowStyle, self.__class__.__name__), - self.__dict__ - ) - class _Curve(_Base): """ A simple arrow which will work with any path instance. The @@ -3304,7 +3230,7 @@ def __init__(self, beginarrow=None, endarrow=None, self.beginarrow, self.endarrow = beginarrow, endarrow self.head_length, self.head_width = head_length, head_width self.fillbegin, self.fillend = fillbegin, fillend - super(ArrowStyle._Curve, self).__init__() + super().__init__() def _get_arrow_wedge(self, x0, y0, x1, y1, head_dist, cos_t, sin_t, linewidth @@ -3376,7 +3302,7 @@ def transmute(self, path, mutation_size, linewidth): x3, y3 = path.vertices[-1] # If there is no room for an arrow and a line, then skip the arrow - has_end_arrow = (self.endarrow and not ((x2 == x3) and (y2 == y3))) + has_end_arrow = (self.endarrow and not (x2 == x3 and y2 == y3)) if has_end_arrow: verticesB, codesB, ddxB, ddyB = \ self._get_arrow_wedge(x2, y2, x3, y3, @@ -3386,8 +3312,8 @@ def transmute(self, path, mutation_size, linewidth): verticesB, codesB = [], [] ddxB, ddyB = 0., 0. - # this simple code will not work if ddx, ddy is greater than - # separation bettern vertices. + # This simple code will not work if ddx, ddy is greater than the + # separation between vertices. _path = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], path.vertices[1:-1], [(x3 + ddxB, y3 + ddyB)]]), @@ -3418,17 +3344,16 @@ def transmute(self, path, mutation_size, linewidth): return _path, _fillable + @_register_style(_style_list, name="-") class Curve(_Curve): """ A simple curve without any arrow head. """ def __init__(self): - super(ArrowStyle.Curve, self).__init__( - beginarrow=False, endarrow=False) - - _style_list["-"] = Curve + super().__init__(beginarrow=False, endarrow=False) + @_register_style(_style_list, name="<-") class CurveA(_Curve): """ An arrow with a head at its begin point. @@ -3444,13 +3369,10 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ + super().__init__(beginarrow=True, endarrow=False, + head_length=head_length, head_width=head_width) - super(ArrowStyle.CurveA, self).__init__( - beginarrow=True, endarrow=False, - head_length=head_length, head_width=head_width) - - _style_list["<-"] = CurveA - + @_register_style(_style_list, name="->") class CurveB(_Curve): """ An arrow with a head at its end point. @@ -3466,13 +3388,10 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ + super().__init__(beginarrow=False, endarrow=True, + head_length=head_length, head_width=head_width) - super(ArrowStyle.CurveB, self).__init__( - beginarrow=False, endarrow=True, - head_length=head_length, head_width=head_width) - - _style_list["->"] = CurveB - + @_register_style(_style_list, name="<->") class CurveAB(_Curve): """ An arrow with heads both at the begin and the end point. @@ -3488,13 +3407,10 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ + super().__init__(beginarrow=True, endarrow=True, + head_length=head_length, head_width=head_width) - super(ArrowStyle.CurveAB, self).__init__( - beginarrow=True, endarrow=True, - head_length=head_length, head_width=head_width) - - _style_list["<->"] = CurveAB - + @_register_style(_style_list, name="<|-") class CurveFilledA(_Curve): """ An arrow with filled triangle head at the begin. @@ -3510,14 +3426,11 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ + super().__init__(beginarrow=True, endarrow=False, + fillbegin=True, fillend=False, + head_length=head_length, head_width=head_width) - super(ArrowStyle.CurveFilledA, self).__init__( - beginarrow=True, endarrow=False, - fillbegin=True, fillend=False, - head_length=head_length, head_width=head_width) - - _style_list["<|-"] = CurveFilledA - + @_register_style(_style_list, name="-|>") class CurveFilledB(_Curve): """ An arrow with filled triangle head at the end. @@ -3533,14 +3446,11 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ + super().__init__(beginarrow=False, endarrow=True, + fillbegin=False, fillend=True, + head_length=head_length, head_width=head_width) - super(ArrowStyle.CurveFilledB, self).__init__( - beginarrow=False, endarrow=True, - fillbegin=False, fillend=True, - head_length=head_length, head_width=head_width) - - _style_list["-|>"] = CurveFilledB - + @_register_style(_style_list, name="<|-|>") class CurveFilledAB(_Curve): """ An arrow with filled triangle heads at both ends. @@ -3556,13 +3466,9 @@ def __init__(self, head_length=.4, head_width=.2): head_width : float, optional, default : 0.2 Width of the arrow head """ - - super(ArrowStyle.CurveFilledAB, self).__init__( - beginarrow=True, endarrow=True, - fillbegin=True, fillend=True, - head_length=head_length, head_width=head_width) - - _style_list["<|-|>"] = CurveFilledAB + super().__init__(beginarrow=True, endarrow=True, + fillbegin=True, fillend=True, + head_length=head_length, head_width=head_width) class _Bracket(_Base): @@ -3641,6 +3547,7 @@ def transmute(self, path, mutation_size, linewidth): return p, False + @_register_style(_style_list, name="]-[") class BracketAB(_Bracket): """ An arrow with a bracket(]) at both ends. @@ -3670,14 +3577,11 @@ def __init__(self, angleB : float, optional, default : None Angle between the bracket and the line """ + super().__init__(True, True, + widthA=widthA, lengthA=lengthA, angleA=angleA, + widthB=widthB, lengthB=lengthB, angleB=angleB) - super(ArrowStyle.BracketAB, self).__init__( - True, True, widthA=widthA, lengthA=lengthA, - angleA=angleA, widthB=widthB, lengthB=lengthB, - angleB=angleB) - - _style_list["]-["] = BracketAB - + @_register_style(_style_list, name="]-") class BracketA(_Bracket): """ An arrow with a bracket(]) at its end. @@ -3696,14 +3600,10 @@ def __init__(self, widthA=1., lengthA=0.2, angleA=None): angleA : float, optional, default : None Angle between the bracket and the line """ + super().__init__(True, None, + widthA=widthA, lengthA=lengthA, angleA=angleA) - super(ArrowStyle.BracketA, self).__init__(True, None, - widthA=widthA, - lengthA=lengthA, - angleA=angleA) - - _style_list["]-"] = BracketA - + @_register_style(_style_list, name="-[") class BracketB(_Bracket): """ An arrow with a bracket([) at its end. @@ -3722,14 +3622,10 @@ def __init__(self, widthB=1., lengthB=0.2, angleB=None): angleB : float, optional, default : None Angle between the bracket and the line """ + super().__init__(None, True, + widthB=widthB, lengthB=lengthB, angleB=angleB) - super(ArrowStyle.BracketB, self).__init__(None, True, - widthB=widthB, - lengthB=lengthB, - angleB=angleB) - - _style_list["-["] = BracketB - + @_register_style(_style_list, name="|-|") class BarAB(_Bracket): """ An arrow with a bar(|) at both ends. @@ -3753,16 +3649,14 @@ def __init__(self, angleB : float, optional, default : None Angle between the bracket and the line """ + super().__init__(True, True, + widthA=widthA, lengthA=0, angleA=angleA, + widthB=widthB, lengthB=0, angleB=angleB) - super(ArrowStyle.BarAB, self).__init__( - True, True, widthA=widthA, lengthA=0, angleA=angleA, - widthB=widthB, lengthB=0, angleB=angleB) - - _style_list["|-|"] = BarAB - + @_register_style(_style_list) class Simple(_Base): """ - A simple arrow. Only works with a quadratic bezier curve. + A simple arrow. Only works with a quadratic Bezier curve. """ def __init__(self, head_length=.5, head_width=.5, tail_width=.2): @@ -3778,10 +3672,9 @@ def __init__(self, head_length=.5, head_width=.5, tail_width=.2): tail_width : float, optional, default : 0.2 Width of the arrow tail """ - self.head_length, self.head_width, self.tail_width = \ head_length, head_width, tail_width - super(ArrowStyle.Simple, self).__init__() + super().__init__() def transmute(self, path, mutation_size, linewidth): @@ -3792,8 +3685,6 @@ def transmute(self, path, mutation_size, linewidth): in_f = inside_circle(x2, y2, head_length) arrow_path = [(x0, y0), (x1, y1), (x2, y2)] - from .bezier import NonIntersectingPathException - try: arrow_out, arrow_in = \ split_bezier_intersecting_with_closedpath(arrow_path, @@ -3845,11 +3736,10 @@ def transmute(self, path, mutation_size, linewidth): return path, True - _style_list["simple"] = Simple - + @_register_style(_style_list) class Fancy(_Base): """ - A fancy arrow. Only works with a quadratic bezier curve. + A fancy arrow. Only works with a quadratic Bezier curve. """ def __init__(self, head_length=.4, head_width=.4, tail_width=.4): @@ -3865,10 +3755,9 @@ def __init__(self, head_length=.4, head_width=.4, tail_width=.4): tail_width : float, optional, default : 0.4 Width of the arrow tail """ - self.head_length, self.head_width, self.tail_width = \ head_length, head_width, tail_width - super(ArrowStyle.Fancy, self).__init__() + super().__init__() def transmute(self, path, mutation_size, linewidth): @@ -3878,8 +3767,6 @@ def transmute(self, path, mutation_size, linewidth): head_length = self.head_length * mutation_size arrow_path = [(x0, y0), (x1, y1), (x2, y2)] - from .bezier import NonIntersectingPathException - # path for head in_f = inside_circle(x2, y2, head_length) try: @@ -3948,11 +3835,10 @@ def transmute(self, path, mutation_size, linewidth): return path, True - _style_list["fancy"] = Fancy - + @_register_style(_style_list) class Wedge(_Base): """ - Wedge(?) shape. Only works with a quadratic bezier curve. The + Wedge(?) shape. Only works with a quadratic Bezier curve. The begin point has a width of the tail_width and the end point has a width of 0. At the middle, the width is shrink_factor*tail_width. """ @@ -3967,10 +3853,9 @@ def __init__(self, tail_width=.3, shrink_factor=0.5): shrink_factor : float, optional, default : 0.5 Fraction of the arrow width at the middle point """ - self.tail_width = tail_width self.shrink_factor = shrink_factor - super(ArrowStyle.Wedge, self).__init__() + super().__init__() def transmute(self, path, mutation_size, linewidth): @@ -3994,8 +3879,6 @@ def transmute(self, path, mutation_size, linewidth): return path, True - _style_list["wedge"] = Wedge - if __doc__: __doc__ = cbook.dedent(__doc__) % \ {"AvailableArrowstyles": _pprint_styles(_style_list)} @@ -4022,7 +3905,7 @@ def __str__(self): if self._posA_posB is not None: (x1, y1), (x2, y2) = self._posA_posB return self.__class__.__name__ \ - + "(%g,%g->%g,%g)" % (x1, y1, x2, y2) + + "((%g, %g)->(%g, %g))" % (x1, y1, x2, y2) else: return self.__class__.__name__ \ + "(%s)" % (str(self._path_original),) @@ -4113,6 +3996,20 @@ def __init__(self, posA=None, posB=None, Valid kwargs are: %(Patch)s """ + if arrow_transmuter is not None: + cbook.warn_deprecated( + 3.0, + message=('The "arrow_transmuter" keyword argument is not used,' + ' and will be removed in Matplotlib 3.1'), + name='arrow_transmuter', + obj_type='keyword argument') + if connector is not None: + cbook.warn_deprecated( + 3.0, + message=('The "connector" keyword argument is not used,' + ' and will be removed in Matplotlib 3.1'), + name='connector', + obj_type='keyword argument') Patch.__init__(self, **kwargs) if posA is not None and posB is not None and path is None: @@ -4430,7 +4327,7 @@ class ConnectionPatch(FancyArrowPatch): connecting lines between two points (possibly in different axes). """ def __str__(self): - return "ConnectionPatch((%g,%g),(%g,%g))" % \ + return "ConnectionPatch((%g, %g), (%g, %g))" % \ (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1]) @docstring.dedent_interpd @@ -4679,10 +4576,7 @@ def get_path_in_displaycoord(self): return _path, fillable def _check_xy(self, renderer): - """ - check if the annotation need to - be drawn. - """ + """Check whether the annotation needs to be drawn.""" b = self.get_annotation_clip() @@ -4705,16 +4599,8 @@ def _check_xy(self, renderer): return True def draw(self, renderer): - """ - Draw. - """ - if renderer is not None: self._renderer = renderer - if not self.get_visible(): + if not self.get_visible() or not self._check_xy(renderer): return - - if not self._check_xy(renderer): - return - FancyArrowPatch.draw(self, renderer) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 77d752ec2409..3fb70ed8d3ac 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -9,18 +9,13 @@ visualisation. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +from functools import lru_cache from weakref import WeakValueDictionary import numpy as np from . import _path, rcParams -from .cbook import (_to_unmasked_float_array, simple_linear_interpolation, - maxdict) +from .cbook import _to_unmasked_float_array, simple_linear_interpolation class Path(object): @@ -130,19 +125,19 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1, and codes as read-only arrays. """ vertices = _to_unmasked_float_array(vertices) - if (vertices.ndim != 2) or (vertices.shape[1] != 2): + if vertices.ndim != 2 or vertices.shape[1] != 2: raise ValueError( "'vertices' must be a 2D list or array with shape Nx2") if codes is not None: codes = np.asarray(codes, self.code_type) - if (codes.ndim != 1) or len(codes) != len(vertices): + if codes.ndim != 1 or len(codes) != len(vertices): raise ValueError("'codes' must be a 1D list or array with the " "same length of 'vertices'") if len(codes) and codes[0] != self.MOVETO: raise ValueError("The first element of 'code' must be equal " "to 'MOVETO' ({})".format(self.MOVETO)) - elif closed: + elif closed and len(vertices): codes = np.empty(len(vertices), dtype=self.code_type) codes[0] = self.MOVETO codes[1:-1] = self.LINETO @@ -308,7 +303,7 @@ def make_compound_path_from_polys(cls, XY): numsides x 2) numpy array of vertices. Return object is a :class:`Path` - .. plot:: gallery/api/histogram_path.py + .. plot:: gallery/misc/histogram_path.py """ @@ -583,7 +578,7 @@ def to_polygons(self, transform=None, width=0, height=0, closed_only=True): polygon/polyline is an Nx2 array of vertices. In other words, each polygon has no ``MOVETO`` instructions or curves. This is useful for displaying in backends that do not support - compound paths or Bezier curves, such as GDK. + compound paths or Bezier curves. If *width* and *height* are both non-zero then the lines will be simplified so that vertices outside of (0, 0), (width, @@ -609,7 +604,7 @@ def to_polygons(self, transform=None, width=0, height=0, closed_only=True): if len(vertices) < 3: return [] elif np.any(vertices[0] != vertices[-1]): - vertices = list(vertices) + [vertices[0]] + vertices = [*vertices, vertices[0]] if transform is None: return [vertices] @@ -643,22 +638,20 @@ def unit_rectangle(cls): @classmethod def unit_regular_polygon(cls, numVertices): """ - Return a :class:`Path` instance for a unit regular - polygon with the given *numVertices* and radius of 1.0, - centered at (0, 0). + Return a :class:`Path` instance for a unit regular polygon with the + given *numVertices* and radius of 1.0, centered at (0, 0). """ if numVertices <= 16: path = cls._unit_regular_polygons.get(numVertices) else: path = None if path is None: - theta = (2*np.pi/numVertices * - np.arange(numVertices + 1).reshape((numVertices + 1, 1))) - # This initial rotation is to make sure the polygon always - # "points-up" - theta += np.pi / 2.0 - verts = np.concatenate((np.cos(theta), np.sin(theta)), 1) - codes = np.empty((numVertices + 1,)) + theta = ((2 * np.pi / numVertices) * np.arange(numVertices + 1) + # This initial rotation is to make sure the polygon always + # "points-up". + + np.pi / 2) + verts = np.column_stack((np.cos(theta), np.sin(theta))) + codes = np.empty(numVertices + 1) codes[0] = cls.MOVETO codes[1:-1] = cls.LINETO codes[-1] = cls.CLOSEPOLY @@ -672,9 +665,8 @@ def unit_regular_polygon(cls, numVertices): @classmethod def unit_regular_star(cls, numVertices, innerCircle=0.5): """ - Return a :class:`Path` for a unit regular star - with the given numVertices and radius of 1.0, centered at (0, - 0). + Return a :class:`Path` for a unit regular star with the given + numVertices and radius of 1.0, centered at (0, 0). """ if numVertices <= 16: path = cls._unit_regular_stars.get((numVertices, innerCircle)) @@ -701,9 +693,8 @@ def unit_regular_star(cls, numVertices, innerCircle=0.5): @classmethod def unit_regular_asterisk(cls, numVertices): """ - Return a :class:`Path` for a unit regular - asterisk with the given numVertices and radius of 1.0, - centered at (0, 0). + Return a :class:`Path` for a unit regular asterisk with the given + numVertices and radius of 1.0, centered at (0, 0). """ return cls.unit_regular_star(numVertices, 0.0) @@ -936,27 +927,17 @@ def wedge(cls, theta1, theta2, n=None): """ return cls.arc(theta1, theta2, n, True) - _hatch_dict = maxdict(8) - - @classmethod - def hatch(cls, hatchpattern, density=6): + @staticmethod + @lru_cache(8) + def hatch(hatchpattern, density=6): """ Given a hatch specifier, *hatchpattern*, generates a Path that can be used in a repeated hatching pattern. *density* is the number of lines per unit square. """ from matplotlib.hatch import get_path - - if hatchpattern is None: - return None - - hatch_path = cls._hatch_dict.get((hatchpattern, density)) - if hatch_path is not None: - return hatch_path - - hatch_path = get_path(hatchpattern, density) - cls._hatch_dict[(hatchpattern, density)] = hatch_path - return hatch_path + return (get_path(hatchpattern, density) + if hatchpattern is not None else None) def clip_to_bbox(self, bbox, inside=True): """ diff --git a/lib/matplotlib/patheffects.py b/lib/matplotlib/patheffects.py index c0265ec71914..5d63c24b21bb 100644 --- a/lib/matplotlib/patheffects.py +++ b/lib/matplotlib/patheffects.py @@ -4,11 +4,6 @@ and :class:`~matplotlib.patches.Patch`. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from matplotlib.backend_bases import RendererBase from matplotlib import colors as mcolors from matplotlib import patches as mpatches @@ -53,7 +48,7 @@ def _update_gc(self, gc, new_gc_dict): if dashes: gc.set_dashes(**dashes) - for k, v in six.iteritems(new_gc_dict): + for k, v in new_gc_dict.items(): set_method = getattr(gc, 'set_' + k, None) if not callable(set_method): raise AttributeError('Unknown property {0}'.format(k)) @@ -183,7 +178,7 @@ def __init__(self, offset=(0, 0), **kwargs): keyword arguments, i.e., the keyword arguments should be valid gc parameter values. """ - super(Stroke, self).__init__(offset) + super().__init__(offset) self._gc = kwargs def draw_path(self, renderer, gc, tpath, affine, rgbFace): @@ -236,7 +231,7 @@ def __init__(self, offset=(2, -2), :meth:`AbstractPathEffect._update_gc`. """ - super(SimplePatchShadow, self).__init__(offset) + super().__init__(offset) if shadow_rgbFace is None: self._shadow_rgbFace = shadow_rgbFace @@ -318,7 +313,7 @@ def __init__(self, offset=(2,-2), :meth:`AbstractPathEffect._update_gc`. """ - super(SimpleLineShadow, self).__init__(offset) + super().__init__(offset) if shadow_color is None: self._shadow_color = shadow_color else: @@ -379,7 +374,7 @@ def __init__(self, offset=(0, 0), **kwargs): properties which cannot be overridden are "path", "clip_box" "transform" and "clip_path". """ - super(PathPatchEffect, self).__init__(offset=offset) + super().__init__(offset=offset) self.patch = mpatches.PathPatch([], **kwargs) def draw_path(self, renderer, gc, tpath, affine, rgbFace): diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 1e423420b0b6..2b63bca083ca 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -1,13 +1,9 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +from .. import axes from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes from .polar import PolarAxes -from matplotlib import axes -class ProjectionRegistry(object): + +class ProjectionRegistry: """ Manages the set of projections available to the system. """ @@ -16,7 +12,7 @@ def __init__(self): def register(self, *projections): """ - Register a new set of projection(s). + Register a new set of projections. """ for projection in projections: name = projection.name @@ -30,12 +26,12 @@ def get_projection_class(self, name): def get_projection_names(self): """ - Get a list of the names of all projections currently - registered. + Get a list of the names of all projections currently registered. """ return sorted(self._all_projection_types) -projection_registry = ProjectionRegistry() + +projection_registry = ProjectionRegistry() projection_registry.register( axes.Axes, PolarAxes, @@ -53,8 +49,7 @@ def get_projection_class(projection=None): """ Get a projection class from its name. - If *projection* is None, a standard rectilinear projection is - returned. + If *projection* is None, a standard rectilinear projection is returned. """ if projection is None: projection = 'rectilinear' @@ -62,24 +57,19 @@ def get_projection_class(projection=None): try: return projection_registry.get_projection_class(projection) except KeyError: - raise ValueError("Unknown projection '%s'" % projection) + raise ValueError("Unknown projection %r" % projection) -def process_projection_requirements(figure, *args, **kwargs): +def process_projection_requirements( + figure, *args, polar=False, projection=None, **kwargs): """ - Handle the args/kwargs to for add_axes/add_subplot/gca, - returning:: + Handle the args/kwargs to add_axes/add_subplot/gca, returning:: (axes_proj_class, proj_class_kwargs, proj_stack_key) - Which can be used for new axes initialization/identification. - - .. note:: **kwargs** is modified in place. - + which can be used for new axes initialization/identification. """ - ispolar = kwargs.pop('polar', False) - projection = kwargs.pop('projection', None) - if ispolar: + if polar: if projection is not None and projection != 'polar': raise ValueError( "polar=True, yet projection=%r. " @@ -87,14 +77,14 @@ def process_projection_requirements(figure, *args, **kwargs): projection) projection = 'polar' - if isinstance(projection, six.string_types) or projection is None: + if isinstance(projection, str) or projection is None: projection_class = get_projection_class(projection) elif hasattr(projection, '_as_mpl_axes'): projection_class, extra_kwargs = projection._as_mpl_axes() kwargs.update(**extra_kwargs) else: raise TypeError('projection must be a string, None or implement a ' - '_as_mpl_axes method. Got %r' % projection) + '_as_mpl_axes method. Got %r' % projection) # Make the key without projection kwargs, this is used as a unique # lookup for axes instances diff --git a/lib/matplotlib/projections/geo.py b/lib/matplotlib/projections/geo.py index 3ed5dc745643..f82bed8b2244 100644 --- a/lib/matplotlib/projections/geo.py +++ b/lib/matplotlib/projections/geo.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import numpy as np import matplotlib @@ -523,10 +518,10 @@ def inverted(self): self._resolution) inverted.__doc__ = Transform.inverted.__doc__ - def __init__(self, *args, **kwargs): - self._longitude_cap = np.pi / 2.0 - self._center_longitude = kwargs.pop("center_longitude", 0.0) - self._center_latitude = kwargs.pop("center_latitude", 0.0) + def __init__(self, *args, center_longitude=0, center_latitude=0, **kwargs): + self._longitude_cap = np.pi / 2 + self._center_longitude = center_longitude + self._center_latitude = center_latitude GeoAxes.__init__(self, *args, **kwargs) self.set_aspect('equal', adjustable='box', anchor='C') self.cla() diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 62fc0f9ac60d..cc077f9667c1 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1,9 +1,5 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - from collections import OrderedDict +import types import numpy as np @@ -291,22 +287,22 @@ def __init__(self, axes, *args, **kwargs): self._text2_translate = mtransforms.ScaledTranslation( 0, 0, axes.figure.dpi_scale_trans) - super(ThetaTick, self).__init__(axes, *args, **kwargs) + super().__init__(axes, *args, **kwargs) def _get_text1(self): - t = super(ThetaTick, self)._get_text1() + t = super()._get_text1() t.set_rotation_mode('anchor') t.set_transform(t.get_transform() + self._text1_translate) return t def _get_text2(self): - t = super(ThetaTick, self)._get_text2() + t = super()._get_text2() t.set_rotation_mode('anchor') t.set_transform(t.get_transform() + self._text2_translate) return t def _apply_params(self, **kw): - super(ThetaTick, self)._apply_params(**kw) + super()._apply_params(**kw) # Ensure transform is correct; sometimes this gets reset. trans = self.label1.get_transform() @@ -325,7 +321,7 @@ def _update_padding(self, pad, angle): self._text2_translate.invalidate() def update_position(self, loc): - super(ThetaTick, self).update_position(loc) + super().update_position(loc) axes = self.axes angle = loc * axes.get_theta_direction() + axes.get_theta_offset() text_angle = np.rad2deg(angle) % 360 - 90 @@ -398,19 +394,19 @@ def _wrap_locator_formatter(self): self.isDefault_majfmt = True def cla(self): - super(ThetaAxis, self).cla() + super().cla() self.set_ticks_position('none') self._wrap_locator_formatter() def _set_scale(self, value, **kwargs): - super(ThetaAxis, self)._set_scale(value, **kwargs) + super()._set_scale(value, **kwargs) self._wrap_locator_formatter() def _copy_tick_props(self, src, dest): 'Copy the props from src tick to dest tick' if src is None or dest is None: return - super(ThetaAxis, self)._copy_tick_props(src, dest) + super()._copy_tick_props(src, dest) # Ensure that tick transforms are independent so that padding works. trans = dest._get_text1_transform()[0] @@ -533,12 +529,12 @@ class RadialTick(maxis.YTick): enabled. """ def _get_text1(self): - t = super(RadialTick, self)._get_text1() + t = super()._get_text1() t.set_rotation_mode('anchor') return t def _get_text2(self): - t = super(RadialTick, self)._get_text2() + t = super()._get_text2() t.set_rotation_mode('anchor') return t @@ -597,7 +593,7 @@ def _determine_anchor(self, mode, angle, start): return 'center', 'bottom' def update_position(self, loc): - super(RadialTick, self).update_position(loc) + super().update_position(loc) axes = self.axes thetamin = axes.get_thetamin() thetamax = axes.get_thetamax() @@ -717,7 +713,7 @@ class RadialAxis(maxis.YAxis): axis_name = 'radius' def __init__(self, *args, **kwargs): - super(RadialAxis, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.sticky_edges.y.append(0) def _get_tick(self, major): @@ -733,12 +729,12 @@ def _wrap_locator_formatter(self): self.isDefault_majloc = True def cla(self): - super(RadialAxis, self).cla() + super().cla() self.set_ticks_position('none') self._wrap_locator_formatter() def _set_scale(self, value, **kwargs): - super(RadialAxis, self)._set_scale(value, **kwargs) + super()._set_scale(value, **kwargs) self._wrap_locator_formatter() @@ -842,16 +838,17 @@ class PolarAxes(Axes): """ name = 'polar' - def __init__(self, *args, **kwargs): + def __init__(self, *args, + theta_offset=0, theta_direction=1, rlabel_position=22.5, + **kwargs): """ Create a new Polar Axes for a polar plot. """ - self._default_theta_offset = kwargs.pop('theta_offset', 0) - self._default_theta_direction = kwargs.pop('theta_direction', 1) - self._default_rlabel_position = np.deg2rad( - kwargs.pop('rlabel_position', 22.5)) + self._default_theta_offset = theta_offset + self._default_theta_direction = theta_direction + self._default_rlabel_position = np.deg2rad(rlabel_position) - Axes.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.use_sticky_edges = True self.set_aspect('equal', adjustable='box', anchor='C') self.cla() @@ -1224,37 +1221,40 @@ def set_rscale(self, *args, **kwargs): def set_rticks(self, *args, **kwargs): return Axes.set_yticks(self, *args, **kwargs) - @docstring.dedent_interpd - def set_thetagrids(self, angles, labels=None, frac=None, fmt=None, - **kwargs): + def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): """ - Set the angles at which to place the theta grids (these - gridlines are equal along the theta dimension). *angles* is in - degrees. - - *labels*, if not None, is a ``len(angles)`` list of strings of - the labels to use at each angle. - - If *labels* is None, the labels will be ``fmt %% angle`` + Set the theta gridlines in a polar plot. - *frac* is the fraction of the polar axes radius at which to - place the label (1 is the edge). e.g., 1.05 is outside the axes - and 0.95 is inside the axes. - - Return value is a list of tuples (*line*, *label*), where - *line* is :class:`~matplotlib.lines.Line2D` instances and the - *label* is :class:`~matplotlib.text.Text` instances. + Parameters + ---------- + angles : tuple with floats, degrees + The angles of the theta gridlines. - kwargs are optional text properties for the labels: + labels : tuple with strings or None + The labels to use at each theta gridline. The + `.projections.polar.ThetaFormatter` will be used if None. - %(Text)s + fmt : str or None + Format string used in `matplotlib.ticker.FormatStrFormatter`. + For example '%f'. Note that the angle that is used is in + radians. - ACCEPTS: sequence of floats + Returns + ------- + lines, labels : list of `.lines.Line2D`, list of `.text.Text` + *lines* are the theta gridlines and *labels* are the tick labels. + + Other Parameters + ---------------- + **kwargs + *kwargs* are optional `~.Text` properties for the labels. + + See Also + -------- + .PolarAxes.set_rgrids + .Axis.get_gridlines + .Axis.get_ticklabels """ - if frac is not None: - cbook.warn_deprecated('2.1', name='frac', obj_type='parameter', - alternative='tick padding via ' - 'Axes.tick_params') # Make sure we take into account unitized data angles = self.convert_yunits(angles) @@ -1268,29 +1268,42 @@ def set_thetagrids(self, angles, labels=None, frac=None, fmt=None, t.update(kwargs) return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels() - @docstring.dedent_interpd def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs): """ - Set the radial locations and labels of the *r* grids. - - The labels will appear at radial distances *radii* at the - given *angle* in degrees. + Set the radial gridlines on a polar plot. - *labels*, if not None, is a ``len(radii)`` list of strings of the - labels to use at each radius. - - If *labels* is None, the built-in formatter will be used. + Parameters + ---------- + radii : tuple with floats + The radii for the radial gridlines - Return value is a list of tuples (*line*, *label*), where - *line* is :class:`~matplotlib.lines.Line2D` instances and the - *label* is :class:`~matplotlib.text.Text` instances. + labels : tuple with strings or None + The labels to use at each radial gridline. The + `matplotlib.ticker.ScalarFormatter` will be used if None. - kwargs are optional text properties for the labels: + angle : float + The angular position of the radius labels in degrees. - %(Text)s + fmt : str or None + Format string used in `matplotlib.ticker.FormatStrFormatter`. + For example '%f'. - ACCEPTS: sequence of floats + Returns + ------- + lines, labels : list of `.lines.Line2D`, list of `.text.Text` + *lines* are the radial gridlines and *labels* are the tick labels. + + Other Parameters + ---------------- + **kwargs + *kwargs* are optional `~.Text` properties for the labels. + + See Also + -------- + .PolarAxes.set_thetagrids + .Axis.get_gridlines + .Axis.get_ticklabels """ # Make sure we take into account unitized data radii = self.convert_xunits(radii) @@ -1357,12 +1370,12 @@ def start_pan(self, x, y, button): if button == 1: epsilon = np.pi / 45.0 t, r = self.transData.inverted().transform_point((x, y)) - if t >= angle - epsilon and t <= angle + epsilon: + if angle - epsilon <= t <= angle + epsilon: mode = 'drag_r_labels' elif button == 3: mode = 'zoom' - self._pan_start = cbook.Bunch( + self._pan_start = types.SimpleNamespace( rmax=self.get_rmax(), trans=self.transData.frozen(), trans_inverse=self.transData.inverted().frozen(), diff --git a/lib/matplotlib/pylab.py b/lib/matplotlib/pylab.py index 67bb7fa1f1c6..5c90445bfaec 100644 --- a/lib/matplotlib/pylab.py +++ b/lib/matplotlib/pylab.py @@ -131,7 +131,7 @@ cumsum - the cumulative sum along a dimension detrend - remove the mean or besdt fit line from an array diag - the k-th diagonal of matrix - diff - the n-th differnce of an array + diff - the n-th difference of an array eig - the eigenvalues and eigen vectors of v eye - a matrix where the k-th diagonal is ones, else zero find - return the indices where a condition is nonzero @@ -212,15 +212,8 @@ """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six - -import warnings - -from matplotlib.cbook import ( - flatten, exception_to_str, silent_list, iterable, dedent) +from matplotlib.cbook import flatten, silent_list, iterable, dedent import matplotlib as mpl @@ -265,4 +258,4 @@ # This is needed, or bytes will be numpy.random.bytes from # "from numpy.random import *" above -bytes = six.moves.builtins.bytes +bytes = __import__("builtins").bytes diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index fb5928dc65ff..3ab2a6549e7a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -17,11 +17,12 @@ The object-oriented API is recommended for more complex plots. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import importlib +import inspect +import logging +from numbers import Number +import re import sys import time import warnings @@ -29,18 +30,16 @@ from cycler import cycler import matplotlib import matplotlib.colorbar -from matplotlib import style +import matplotlib.image +from matplotlib import rcsetup, style from matplotlib import _pylab_helpers, interactive -from matplotlib.cbook import dedent, silent_list, is_numlike -from matplotlib.cbook import _string_to_bool -from matplotlib.cbook import deprecated, warn_deprecated +from matplotlib.cbook import ( + dedent, deprecated, silent_list, warn_deprecated, _string_to_bool) from matplotlib import docstring from matplotlib.backend_bases import FigureCanvasBase from matplotlib.figure import Figure, figaspect from matplotlib.gridspec import GridSpec -from matplotlib.image import imread as _imread -from matplotlib.image import imsave as _imsave -from matplotlib import rcParams, rcParamsDefault, get_backend +from matplotlib import rcParams, rcParamsDefault, get_backend, rcParamsOrig from matplotlib import rc_context from matplotlib.rcsetup import interactive_bk as _interactive_bk from matplotlib.artist import getp, get, Artist @@ -68,51 +67,13 @@ Locator, IndexLocator, FixedLocator, NullLocator,\ LinearLocator, LogLocator, AutoLocator, MultipleLocator,\ MaxNLocator -from matplotlib.backends import pylab_setup +from matplotlib.backends import pylab_setup, _get_running_interactive_framework -## Backend detection ## -def _backend_selection(): - """ If rcParams['backend_fallback'] is true, check to see if the - current backend is compatible with the current running event - loop, and if not switches to a compatible one. - """ - backend = rcParams['backend'] - if not rcParams['backend_fallback'] or backend not in _interactive_bk: - return - is_agg_backend = rcParams['backend'].endswith('Agg') - if 'wx' in sys.modules and not backend in ('WX', 'WXAgg'): - import wx - if wx.App.IsMainLoopRunning(): - rcParams['backend'] = 'wx' + 'Agg' * is_agg_backend - elif 'PyQt4.QtCore' in sys.modules and not backend == 'Qt4Agg': - import PyQt4.QtGui - if not PyQt4.QtGui.qApp.startingUp(): - # The mainloop is running. - rcParams['backend'] = 'qt4Agg' - elif 'PyQt5.QtCore' in sys.modules and not backend == 'Qt5Agg': - import PyQt5.QtWidgets - if not PyQt5.QtWidgets.qApp.startingUp(): - # The mainloop is running. - rcParams['backend'] = 'qt5Agg' - elif ('gtk' in sys.modules and - backend not in ('GTK', 'GTKAgg', 'GTKCairo')): - if 'gi' in sys.modules: - from gi.repository import GObject - ml = GObject.MainLoop - else: - import gobject - ml = gobject.MainLoop - if ml().is_running(): - rcParams['backend'] = 'gtk' + 'Agg' * is_agg_backend - elif 'Tkinter' in sys.modules and not backend == 'TkAgg': - # import Tkinter - pass # what if anything do we need to do for tkinter? +_log = logging.getLogger(__name__) -_backend_selection() ## Global ## -_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() _IP_REGISTERED = None _INSTALL_FIG_OBSERVER = False @@ -123,8 +84,7 @@ def install_repl_displayhook(): Install a repl display hook so that any stale figure are automatically redrawn when control is returned to the repl. - This works with IPython terminals and kernels, - as well as vanilla python shells. + This works both with IPython and with vanilla python shells. """ global _IP_REGISTERED global _INSTALL_FIG_OBSERVER @@ -174,7 +134,7 @@ def post_execute(): def uninstall_repl_displayhook(): """ - Uninstalls the matplotlib display hook. + Uninstall the matplotlib display hook. .. warning @@ -216,21 +176,61 @@ def findobj(o=None, match=None, include_self=True): def switch_backend(newbackend): """ - Switch the default backend. This feature is **experimental**, and - is only expected to work switching to an image backend. e.g., if - you have a bunch of PostScript scripts that you want to run from - an interactive ipython session, you may want to switch to the PS - backend before running them to avoid having a bunch of GUI windows - popup. If you try to interactively switch from one GUI backend to - another, you will explode. - - Calling this command will close all open windows. + Close all open figures and set the Matplotlib backend. + + The argument is case-insensitive. Switching to an interactive backend is + possible only if no event loop for another interactive backend has started. + Switching to and from non-interactive backends is always possible. + + Parameters + ---------- + newbackend : str + The name of the backend to use. """ - close('all') + close("all") + + if newbackend is rcsetup._auto_backend_sentinel: + for candidate in ["macosx", "qt5agg", "qt4agg", "gtk3agg", "gtk3cairo", + "tkagg", "wxagg", "agg", "cairo"]: + try: + switch_backend(candidate) + except ImportError: + continue + else: + rcParamsOrig['backend'] = candidate + return + + backend_name = ( + newbackend[9:] if newbackend.startswith("module://") + else "matplotlib.backends.backend_{}".format(newbackend.lower())) + + backend_mod = importlib.import_module(backend_name) + Backend = type( + "Backend", (matplotlib.backends._Backend,), vars(backend_mod)) + _log.debug("Loaded backend %s version %s.", + newbackend, Backend.backend_version) + + required_framework = Backend.required_interactive_framework + current_framework = \ + matplotlib.backends._get_running_interactive_framework() + if (current_framework and required_framework + and current_framework != required_framework): + raise ImportError( + "Cannot load backend {!r} which requires the {!r} interactive " + "framework, as {!r} is currently running".format( + newbackend, required_framework, current_framework)) + + rcParams['backend'] = rcParamsDefault['backend'] = newbackend + global _backend_mod, new_figure_manager, draw_if_interactive, _show - matplotlib.use(newbackend, warn=False, force=True) - from matplotlib.backends import pylab_setup - _backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() + _backend_mod = backend_mod + new_figure_manager = Backend.new_figure_manager + draw_if_interactive = Backend.draw_if_interactive + _show = Backend.show + + # Need to keep a global reference to the backend for compatibility reasons. + # See https://github.com/matplotlib/matplotlib/issues/6092 + matplotlib.backends.backend = newbackend def show(*args, **kw): @@ -254,20 +254,18 @@ def show(*args, **kw): def isinteractive(): - """ - Return status of interactive mode. - """ + """Return the status of interactive mode.""" return matplotlib.is_interactive() def ioff(): - """Turn interactive mode off.""" + """Turn the interactive mode off.""" matplotlib.interactive(False) uninstall_repl_displayhook() def ion(): - """Turn interactive mode on.""" + """Turn the interactive mode on.""" matplotlib.interactive(True) install_repl_displayhook() @@ -299,8 +297,8 @@ def pause(interval): @docstring.copy_dedent(matplotlib.rc) -def rc(*args, **kwargs): - matplotlib.rc(*args, **kwargs) +def rc(group, **kwargs): + matplotlib.rc(group, **kwargs) @docstring.copy_dedent(matplotlib.rc_context) @@ -315,9 +313,9 @@ def rcdefaults(): draw_all() -# The current "image" (ScalarMappable) is retrieved or set -# only via the pyplot interface using the following two -# functions: +## Current image ## + + def gci(): """ Get the current colorable artist. Specifically, returns the @@ -335,27 +333,18 @@ def gci(): return gcf()._gci() -def sci(im): - """ - Set the current image. This image will be the target of colormap - commands like :func:`~matplotlib.pyplot.jet`, - :func:`~matplotlib.pyplot.hot` or - :func:`~matplotlib.pyplot.clim`). The current image is an - attribute of the current axes. - """ - gca()._sci(im) +## Any Artist ## -## Any Artist ## # (getp is simply imported) @docstring.copy(_setp) -def setp(*args, **kwargs): - return _setp(*args, **kwargs) +def setp(obj, *args, **kwargs): + return _setp(obj, *args, **kwargs) def xkcd(scale=1, length=100, randomness=2): """ - Turns on `xkcd `_ sketch-style drawing mode. + Turn on `xkcd `_ sketch-style drawing mode. This will only have effect on things drawn after this function is called. @@ -424,12 +413,12 @@ def figure(num=None, # autoincrement if None, else integer from 1-N **kwargs ): """ - Creates a new figure. + Create a new figure. Parameters ---------- - num : integer or string, optional, default: none + num : integer or string, optional, default: None If not provided, a new figure will be created, and the figure number will be incremented. The figure objects holds this number in a `number` attribute. @@ -440,44 +429,46 @@ def figure(num=None, # autoincrement if None, else integer from 1-N `num`. figsize : tuple of integers, optional, default: None - width, height in inches. If not provided, defaults to rc - figure.figsize. + width, height in inches. If not provided, defaults to + :rc:`figure.figsize` = ``[6.4, 4.8]``. dpi : integer, optional, default: None - resolution of the figure. If not provided, defaults to rc figure.dpi. + resolution of the figure. If not provided, defaults to + :rc:`figure.dpi` = ``100``. facecolor : - the background color. If not provided, defaults to rc figure.facecolor. + the background color. If not provided, defaults to + :rc:`figure.facecolor` = ``'w'``. edgecolor : - the border color. If not provided, defaults to rc figure.edgecolor. + the border color. If not provided, defaults to + :rc:`figure.edgecolor` = ``'w'``. frameon : bool, optional, default: True If False, suppress drawing the figure frame. - FigureClass : class derived from matplotlib.figure.Figure - Optionally use a custom Figure instance. + FigureClass : subclass of `~matplotlib.figure.Figure` + Optionally use a custom `.Figure` instance. clear : bool, optional, default: False If True and the figure already exists, then it is cleared. Returns ------- - figure : Figure - The Figure instance returned will also be passed to new_figure_manager - in the backends, which allows to hook custom Figure classes into the - pylab interface. Additional kwargs will be passed to the figure init - function. + figure : `~matplotlib.figure.Figure` + The `.Figure` instance returned will also be passed to new_figure_manager + in the backends, which allows to hook custom `.Figure` classes into the + pyplot interface. Additional kwargs will be passed to the `.Figure` + init function. Notes ----- - If you are creating many figures, make sure you explicitly call "close" - on the figures you are not using, because this will enable pylab - to properly clean up the memory. - - rcParams defines the default values, which can be modified in the - matplotlibrc file + If you are creating many figures, make sure you explicitly call + :func:`.pyplot.close` on the figures you are not using, because this will + enable pyplot to properly clean up the memory. + `~matplotlib.rcParams` defines the default values, which can be modified + in the matplotlibrc file. """ if figsize is None: @@ -494,7 +485,7 @@ def figure(num=None, # autoincrement if None, else integer from 1-N figLabel = '' if num is None: num = next_num - elif isinstance(num, six.string_types): + elif isinstance(num, str): figLabel = num allLabels = get_figlabels() if figLabel not in allLabels: @@ -511,7 +502,7 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if figManager is None: max_open_warning = rcParams['figure.max_open_warning'] - if (max_open_warning >= 1 and len(allnums) >= max_open_warning): + if len(allnums) >= max_open_warning >= 1: warnings.warn( "More than %d figures have been opened. Figures " "created through the pyplot interface " @@ -587,6 +578,7 @@ def gcf(): def fignum_exists(num): + """Return whether the figure with the given id exists.""" return _pylab_helpers.Gcf.has_fignum(num) or num in get_figlabels() @@ -603,6 +595,11 @@ def get_figlabels(): def get_current_fig_manager(): + """ + Return the figure manager of the active figure. + + If there is currently no active figure, a new one is created. + """ figManager = _pylab_helpers.Gcf.get_active() if figManager is None: gcf() # creates an active figure as a side effect @@ -620,54 +617,50 @@ def disconnect(cid): return get_current_fig_manager().canvas.mpl_disconnect(cid) -def close(*args): +def close(fig=None): """ Close a figure window. - ``close()`` by itself closes the current figure - - ``close(fig)`` closes the `.Figure` instance *fig* - - ``close(num)`` closes the figure number *num* + Parameters + ---------- + fig : None or int or str or `.Figure` + The figure to close. There are a number of ways to specify this: - ``close(name)`` where *name* is a string, closes figure with that label + - *None*: the current figure + - `.Figure`: the given `.Figure` instance + - ``int``: a figure number + - ``str``: a figure name + - 'all': all figures - ``close('all')`` closes all the figure windows """ - - if len(args) == 0: + if fig is None: figManager = _pylab_helpers.Gcf.get_active() if figManager is None: return else: _pylab_helpers.Gcf.destroy(figManager.num) - elif len(args) == 1: - arg = args[0] - if arg == 'all': - _pylab_helpers.Gcf.destroy_all() - elif isinstance(arg, six.integer_types): - _pylab_helpers.Gcf.destroy(arg) - elif hasattr(arg, 'int'): - # if we are dealing with a type UUID, we - # can use its integer representation - _pylab_helpers.Gcf.destroy(arg.int) - elif isinstance(arg, six.string_types): - allLabels = get_figlabels() - if arg in allLabels: - num = get_fignums()[allLabels.index(arg)] - _pylab_helpers.Gcf.destroy(num) - elif isinstance(arg, Figure): - _pylab_helpers.Gcf.destroy_fig(arg) - else: - raise TypeError('Unrecognized argument type %s to close' % type(arg)) + elif fig == 'all': + _pylab_helpers.Gcf.destroy_all() + elif isinstance(fig, int): + _pylab_helpers.Gcf.destroy(fig) + elif hasattr(fig, 'int'): + # if we are dealing with a type UUID, we + # can use its integer representation + _pylab_helpers.Gcf.destroy(fig.int) + elif isinstance(fig, str): + allLabels = get_figlabels() + if fig in allLabels: + num = get_fignums()[allLabels.index(fig)] + _pylab_helpers.Gcf.destroy(num) + elif isinstance(fig, Figure): + _pylab_helpers.Gcf.destroy_fig(fig) else: - raise TypeError('close takes 0 or 1 arguments') + raise TypeError("close() argument must be a Figure, an int, a string, " + "or None, not '%s'") def clf(): - """ - Clear the current figure. - """ + """Clear the current figure.""" gcf().clf() @@ -724,16 +717,17 @@ def waitforbuttonpress(*args, **kwargs): return gcf().waitforbuttonpress(*args, **kwargs) -# Putting things in figures +## Putting things in figures ## + @docstring.copy_dedent(Figure.text) -def figtext(*args, **kwargs): - return gcf().text(*args, **kwargs) +def figtext(x, y, s, *args, **kwargs): + return gcf().text(x, y, s, *args, **kwargs) @docstring.copy_dedent(Figure.suptitle) -def suptitle(*args, **kwargs): - return gcf().suptitle(*args, **kwargs) +def suptitle(t, **kwargs): + return gcf().suptitle(t, **kwargs) @docstring.copy_dedent(Figure.figimage) @@ -779,83 +773,22 @@ def figlegend(*args, **kwargs): return gcf().legend(*args, **kwargs) -## Figure and Axes hybrid ## - -_hold_msg = """pyplot.hold is deprecated. - Future behavior will be consistent with the long-time default: - plot commands add elements without first clearing the - Axes and/or Figure.""" - -@deprecated("2.0", message=_hold_msg) -def hold(b=None): - """ - Set the hold state. If *b* is None (default), toggle the - hold state, else set the hold state to boolean value *b*:: - - hold() # toggle hold - hold(True) # hold is on - hold(False) # hold is off - - When *hold* is *True*, subsequent plot commands will add elements to - the current axes. When *hold* is *False*, the current axes and - figure will be cleared on the next plot command. - - """ - - fig = gcf() - ax = fig.gca() - - if b is not None: - b = bool(b) - fig._hold = b - ax._hold = b - - # b=None toggles the hold state, so let's get get the current hold - # state; but should pyplot hold toggle the rc setting - me thinks - # not - b = ax._hold - - # The comment above looks ancient; and probably the line below, - # contrary to the comment, is equally ancient. It will trigger - # a second warning, but "Oh, well...". - rc('axes', hold=b) - -@deprecated("2.0", message=_hold_msg) -def ishold(): - """ - Return the hold status of the current axes. - """ - return gca()._hold - - -@deprecated("2.0", message=_hold_msg) -def over(func, *args, **kwargs): - """ - Call a function with hold(True). - - Calls:: - - func(*args, **kwargs) - - with ``hold(True)`` and then restores the hold state. - - """ - ax = gca() - h = ax._hold - ax._hold = True - func(*args, **kwargs) - ax._hold = h - ## Axes ## - +@docstring.dedent_interpd def axes(arg=None, **kwargs): """ Add an axes to the current figure and make it the current axes. + Call signatures:: + + plt.axes() + plt.axes(rect, projection=None, polar=False, **kwargs) + plt.axes(ax) + Parameters ---------- - arg : None or 4-tuple or Axes + arg : { None, 4-tuple, Axes } The exact behavior of this function depends on the type: - *None*: A new full window axes is added using @@ -863,48 +796,79 @@ def axes(arg=None, **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. - - `~matplotlib.axes.Axes`: This is equivalent to `.pyplot.sca`. + - `~.axes.Axes`: This is equivalent to `.pyplot.sca`. It sets the current axes to *arg*. Note: This implicitly changes the current figure to the parent of *arg*. - .. note:: The use of an Axes as an argument is deprecated and will be - removed in v3.0. Please use `.pyplot.sca` instead. + .. note:: The use of an `.axes.Axes` as an argument is deprecated + and will be removed in v3.0. Please use `.pyplot.sca` + instead. + + projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ +'polar', 'rectilinear', str}, optional + The projection type of the `~.axes.Axes`. *str* is the name of + a costum projection, see `~matplotlib.projections`. The default + None results in a 'rectilinear' projection. + + polar : boolean, optional + If True, equivalent to projection='polar'. + + sharex, sharey : `~.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. + The axis will have the same limits, ticks, and scale as the axis + of the shared axes. + + + label : str + A label for the returned axes. Other Parameters ---------------- - **kwargs : - For allowed keyword arguments see `.pyplot.subplot` and - `.Figure.add_axes` respectively. Some common keyword arguments are - listed below: - - ========= =========== ================================================= - kwarg Accepts Description - ========= =========== ================================================= - facecolor color the axes background color - frameon bool whether to display the frame - sharex otherax share x-axis with *otherax* - sharey otherax share y-axis with *otherax* - polar bool whether to use polar axes - aspect [str | num] ['equal', 'auto'] or a number. If a number, the - ratio of y-unit/x-unit in screen-space. See also - `~.Axes.set_aspect`. - ========= =========== ================================================= + **kwargs + This method also takes the keyword arguments for + the returned axes class. The keyword arguments for the + rectilinear axes class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used, see the actual axes + class. + %(Axes)s Returns ------- - axes : Axes - The created or activated 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. - Examples - -------- - Creating a new full window axes:: + Notes + ----- + If the figure already has a axes with key (*args*, + *kwargs*) then it will simply make that axes current and + return it. This behavior is deprecated. Meanwhile, if you do + not want this behavior (i.e., you want to force the creation of a + new axes), you must use a unique set of args and kwargs. The axes + *label* attribute has been exposed for this purpose: if you want + two axes that are otherwise identical to be added to the figure, + make sure you give them unique labels. - >>> plt.axes() + See Also + -------- + .Figure.add_axes + .pyplot.subplot + .Figure.add_subplot + .Figure.subplots + .pyplot.subplots - Creating a new axes with specified dimensions and some kwargs:: + Examples + -------- + :: - >>> plt.axes((left, bottom, width, height), facecolor='w') + #Creating a new full window axes + plt.axes() + #Creating a new axes with specified dimensions and some kwargs + plt.axes((left, bottom, width, height), facecolor='w') """ if arg is None: @@ -925,12 +889,13 @@ def axes(arg=None, **kwargs): def delaxes(ax=None): """ - Remove the given `Axes` *ax* from the current figure. If *ax* is *None*, - the current axes is removed. A KeyError is raised if the axes doesn't exist. + Remove the `Axes` *ax* (defaulting to the current axes) from its figure. + + A KeyError is raised if the axes doesn't exist. """ if ax is None: ax = gca() - gcf().delaxes(ax) + ax.figure.delaxes(ax) def sca(ax): @@ -945,7 +910,7 @@ def sca(ax): _pylab_helpers.Gcf.set_active(m) m.canvas.figure.sca(ax) return - raise ValueError("Axes instance argument was not found in a figure.") + raise ValueError("Axes instance argument was not found in a figure") def gca(**kwargs): @@ -968,80 +933,141 @@ def gca(**kwargs): """ return gcf().gca(**kwargs) -# More ways of creating axes: +## More ways of creating axes ## +@docstring.dedent_interpd def subplot(*args, **kwargs): """ - Return a subplot axes at the given grid position. + Add a subplot to the current figure. - Call signature:: + Wrapper of `.Figure.add_subplot` with a difference in behavior + explained in the notes section. - subplot(nrows, ncols, index, **kwargs) + Call signatures:: - In the current figure, create and return an `~matplotlib.axes.Axes`, - at position *index* of a (virtual) grid of *nrows* by *ncols* axes. - Indexes go from 1 to ``nrows * ncols``, incrementing in row-major order. + subplot(nrows, ncols, index, **kwargs) + subplot(pos, **kwargs) + subplot(ax) - If *nrows*, *ncols* and *index* are all less than 10, they can also be - given as a single, concatenated, three-digit number. + Parameters + ---------- + *args + Either a 3-digit integer or three separate integers + describing the position of the subplot. If the three + integers are *nrows*, *ncols*, and *index* in order, the + subplot will take the *index* position on a grid with *nrows* + rows and *ncols* columns. *index* starts at 1 in the upper left + corner and increases to the right. + + *pos* is a three digit integer, where the first digit is the + number of rows, the second the number of columns, and the third + the index of the subplot. i.e. fig.add_subplot(235) is the same as + fig.add_subplot(2, 3, 5). Note that all integers must be less than + 10 for this form to work. + + projection : {None, 'aitoff', 'hammer', 'lambert', 'mollweide', \ +'polar', 'rectilinear', str}, optional + The projection type of the subplot (`~.axes.Axes`). *str* is the name + of a costum projection, see `~matplotlib.projections`. The default + None results in a 'rectilinear' projection. + + polar : boolean, optional + If True, equivalent to projection='polar'. + + sharex, sharey : `~.axes.Axes`, optional + Share the x or y `~matplotlib.axis` with sharex and/or sharey. The + axis will have the same limits, ticks, and scale as the axis of the + shared axes. - For example, ``subplot(2, 3, 3)`` and ``subplot(233)`` both create an - `matplotlib.axes.Axes` at the top right corner of the current figure, - occupying half of the figure height and a third of the figure width. + label : str + A label for the returned axes. - .. note:: + Other Parameters + ---------------- + **kwargs + This method also takes the keyword arguments for + the returned axes base class. The keyword arguments for the + rectilinear base class `~.axes.Axes` can be found in + the following table but there might also be other keyword + arguments if another projection is used. + %(Axes)s - Creating a subplot will delete any pre-existing subplot that overlaps - with it beyond sharing a boundary:: + Returns + ------- + axes : an `.axes.SubplotBase` subclass of `~.axes.Axes` (or a subclass \ + of `~.axes.Axes`) - import matplotlib.pyplot as plt - # plot a line, implicitly creating a subplot(111) - plt.plot([1,2,3]) - # now create a subplot which represents the top plot of a grid - # with 2 rows and 1 column. Since this subplot will overlap the - # first, the plot (and its axes) previously created, will be removed - plt.subplot(211) - plt.plot(range(12)) - plt.subplot(212, facecolor='y') # creates 2nd subplot with yellow background + 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. - If you do not want this behavior, use the - :meth:`~matplotlib.figure.Figure.add_subplot` method or the - :func:`~matplotlib.pyplot.axes` function instead. + Notes + ----- + Creating a subplot will delete any pre-existing subplot that overlaps + with it beyond sharing a boundary:: + + import matplotlib.pyplot as plt + # plot a line, implicitly creating a subplot(111) + plt.plot([1,2,3]) + # now create a subplot which represents the top plot of a grid + # with 2 rows and 1 column. Since this subplot will overlap the + # first, the plot (and its axes) previously created, will be removed + plt.subplot(211) + + If you do not want this behavior, use the `.Figure.add_subplot` method + or the `.pyplot.axes` function instead. + + If the figure already has a subplot with key (*args*, + *kwargs*) then it will simply make that subplot current and + return it. This behavior is deprecated. Meanwhile, if you do + not want this behavior (i.e., you want to force the creation of a + new suplot), you must use a unique set of args and kwargs. The axes + *label* attribute has been exposed for this purpose: if you want + two subplots that are otherwise identical to be added to the figure, + make sure you give them unique labels. + + In rare circumstances, `.add_subplot` may be called with a single + argument, a subplot axes instance already created in the + present figure but not in the figure's list of axes. - Keyword arguments: + See Also + -------- + .Figure.add_subplot + .pyplot.subplots + .pyplot.axes + .Figure.subplots - *facecolor*: - The background color of the subplot, which can be any valid - color specifier. See :mod:`matplotlib.colors` for more - information. + Examples + -------- + :: - *polar*: - A boolean flag indicating whether the subplot plot should be - a polar projection. Defaults to *False*. + plt.subplot(221) - *projection*: - A string giving the name of a custom projection to be used - for the subplot. This projection must have been previously - registered. See :mod:`matplotlib.projections`. + # equivalent but more general + ax1=plt.subplot(2, 2, 1) - .. seealso:: + # add a subplot with no frame + ax2=plt.subplot(222, frameon=False) - :func:`~matplotlib.pyplot.axes` - For additional information on :func:`axes` and - :func:`subplot` keyword arguments. + # add a polar subplot + plt.subplot(223, projection='polar') - :file:`gallery/pie_and_polar_charts/polar_scatter.py` - For an example + # add a red subplot that shares the x-axis with ax1 + plt.subplot(224, sharex=ax1, facecolor='red') - **Example:** + #delete ax2 from the figure + plt.delaxes(ax2) - .. plot:: gallery/subplots_axes_and_figures/subplot.py + #add ax2 to the figure again + plt.subplot(ax2) + """ - """ # if subplot called without arguments, create subplot(1,1,1) - if len(args)==0: - args=(1,1,1) + if len(args) == 0: + args = (1, 1, 1) # This check was added because it is very easy to type # subplot(1, 2, False) when subplots(1, 2, False) was intended @@ -1049,19 +1075,21 @@ def subplot(*args, **kwargs): # ever occur, but mysterious behavior can result because what was # intended to be the sharex argument is instead treated as a # subplot index for subplot() - if len(args) >= 3 and isinstance(args[2], bool) : - warnings.warn("The subplot index argument to subplot() appears" - " to be a boolean. Did you intend to use subplots()?") + if len(args) >= 3 and isinstance(args[2], bool): + warnings.warn("The subplot index argument to subplot() appears " + "to be a boolean. Did you intend to use subplots()?") fig = gcf() a = fig.add_subplot(*args, **kwargs) bbox = a.bbox byebye = [] for other in fig.axes: - if other==a: continue + if other == a: + continue if bbox.fully_overlaps(other.bbox): byebye.append(other) - for ax in byebye: delaxes(ax) + for ax in byebye: + delaxes(ax) return a @@ -1069,7 +1097,7 @@ def subplot(*args, **kwargs): def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw): """ - Create a figure and a set of subplots + Create a figure and a set of subplots. This utility wrapper makes it convenient to create common layouts of subplots, including the enclosing figure object, in a single call. @@ -1094,11 +1122,11 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, labels of the bottom subplot are created. Similarly, when subplots have a shared y-axis along a row, only the y tick labels of the first column subplot are created. To later turn other subplots' ticklabels - on, use :meth:`~matplotlib.axes.Axes.tick_params`. + on, use `~matplotlib.axes.Axes.tick_params`. squeeze : bool, optional, default: True - If True, extra dimensions are squeezed out from the returned - array of Axes: + array of `~matplotlib.axes.Axes`: - if only one subplot is constructed (nrows=ncols=1), the resulting single Axes object is returned as a scalar. @@ -1110,76 +1138,80 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, always a 2D array containing Axes instances, even if it ends up being 1x1. + num : integer or string, optional, default: None + A `.pyplot.figure` keyword that sets the figure number or label. + subplot_kw : dict, optional Dict with keywords passed to the - :meth:`~matplotlib.figure.Figure.add_subplot` call used to create each + `~matplotlib.figure.Figure.add_subplot` call used to create each subplot. gridspec_kw : dict, optional - Dict with keywords passed to the - :class:`~matplotlib.gridspec.GridSpec` constructor used to create the - grid the subplots are placed on. + Dict with keywords passed to the `~matplotlib.gridspec.GridSpec` + constructor used to create the grid the subplots are placed on. **fig_kw : - All additional keyword arguments are passed to the :func:`figure` call. + All additional keyword arguments are passed to the + `.pyplot.figure` call. Returns ------- - fig : :class:`matplotlib.figure.Figure` object + fig : `~.figure.Figure` - ax : Axes object or array of Axes objects. - - ax can be either a single :class:`matplotlib.axes.Axes` object or an + ax : `.axes.Axes` object or array of Axes objects. + *ax* can be either a single `~matplotlib.axes.Axes` object or an array of Axes objects if more than one subplot was created. The dimensions of the resulting array can be controlled with the squeeze keyword, see above. Examples -------- - First create some toy data: - - >>> x = np.linspace(0, 2*np.pi, 400) - >>> y = np.sin(x**2) - - Creates just a figure and only one subplot - - >>> fig, ax = plt.subplots() - >>> ax.plot(x, y) - >>> ax.set_title('Simple plot') + :: - Creates two subplots and unpacks the output array immediately + #First create some toy data: + x = np.linspace(0, 2*np.pi, 400) + y = np.sin(x**2) - >>> f, (ax1, ax2) = plt.subplots(1, 2, sharey=True) - >>> ax1.plot(x, y) - >>> ax1.set_title('Sharing Y axis') - >>> ax2.scatter(x, y) + #Creates just a figure and only one subplot + fig, ax = plt.subplots() + ax.plot(x, y) + ax.set_title('Simple plot') - Creates four polar axes, and accesses them through the returned array + #Creates two subplots and unpacks the output array immediately + f, (ax1, ax2) = plt.subplots(1, 2, sharey=True) + ax1.plot(x, y) + ax1.set_title('Sharing Y axis') + ax2.scatter(x, y) - >>> fig, axes = plt.subplots(2, 2, subplot_kw=dict(polar=True)) - >>> axes[0, 0].plot(x, y) - >>> axes[1, 1].scatter(x, y) + #Creates four polar axes, and accesses them through the returned array + fig, axes = plt.subplots(2, 2, subplot_kw=dict(polar=True)) + axes[0, 0].plot(x, y) + axes[1, 1].scatter(x, y) - Share a X axis with each column of subplots + #Share a X axis with each column of subplots + plt.subplots(2, 2, sharex='col') - >>> plt.subplots(2, 2, sharex='col') + #Share a Y axis with each row of subplots + plt.subplots(2, 2, sharey='row') - Share a Y axis with each row of subplots + #Share both X and Y axes with all subplots + plt.subplots(2, 2, sharex='all', sharey='all') - >>> plt.subplots(2, 2, sharey='row') + #Note that this is the same as + plt.subplots(2, 2, sharex=True, sharey=True) - Share both X and Y axes with all subplots - - >>> plt.subplots(2, 2, sharex='all', sharey='all') - - Note that this is the same as - - >>> plt.subplots(2, 2, sharex=True, sharey=True) + #Creates figure number 10 with a single subplot + #and clears it if it already exists. + fig, ax=plt.subplots(num=10, clear=True) See Also -------- - figure - subplot + .pyplot.figure + .pyplot.subplot + .pyplot.axes + .Figure.subplots + .Figure.add_subplot + """ fig = figure(**fig_kw) axs = fig.subplots(nrows=nrows, ncols=ncols, sharex=sharex, sharey=sharey, @@ -1258,11 +1290,11 @@ def twinx(ax=None): .. seealso:: - :file:`examples/api_examples/two_scales.py` - For an example + :doc:`/gallery/subplots_axes_and_figures/two_scales` + """ if ax is None: - ax=gca() + ax = gca() ax1 = ax.twinx() return ax1 @@ -1275,20 +1307,16 @@ def twiny(ax=None): returned. """ if ax is None: - ax=gca() + ax = gca() ax1 = ax.twiny() return ax1 -def subplots_adjust(*args, **kwargs): +def subplots_adjust(left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): """ Tune the subplot layout. - call signature:: - - subplots_adjust(left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None) - The parameter meanings (and suggested defaults) are:: left = 0.125 # the left side of the subplots of the figure @@ -1303,7 +1331,7 @@ def subplots_adjust(*args, **kwargs): The actual defaults are controlled by the rc file """ fig = gcf() - fig.subplots_adjust(*args, **kwargs) + fig.subplots_adjust(left, bottom, right, top, wspace, hspace) def subplot_tool(targetfig=None): @@ -1320,8 +1348,10 @@ def subplot_tool(targetfig=None): else: # find the manager for this figure for manager in _pylab_helpers.Gcf._activeQue: - if manager.canvas.figure==targetfig: break - else: raise RuntimeError('Could not find manager for targetfig') + if manager.canvas.figure == targetfig: + break + else: + raise RuntimeError('Could not find manager for targetfig') toolfig = figure(figsize=(6,3)) toolfig.subplots_adjust(top=0.9) @@ -1338,18 +1368,17 @@ def tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None): Parameters ---------- pad : float - padding between the figure edge and the edges of subplots, as a fraction of the font-size. - h_pad, w_pad : float - padding (height/width) between edges of adjacent subplots. - Defaults to `pad_inches`. - rect : if rect is given, it is interpreted as a rectangle - (left, bottom, right, top) in the normalized figure - coordinate that the whole subplots area (including + Padding between the figure edge and the edges of subplots, + as a fraction of the font size. + h_pad, w_pad : float, optional + Padding (height/width) between edges of adjacent subplots, + as a fraction of the font size. Defaults to *pad*. + rect : tuple (left, bottom, right, top), optional + A rectangle (left, bottom, right, top) in the normalized + figure coordinate that the whole subplots area (including labels) will fit into. Default is (0, 0, 1, 1). - """ - fig = gcf() - fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) def box(on=None): @@ -1373,171 +1402,30 @@ def box(on=None): on = _string_to_bool(on) ax.set_frame_on(on) - -def title(s, *args, **kwargs): - """ - Set a title of the current axes. - - Set one of the three available axes titles. The available titles are - positioned above the axes in the center, flush with the left edge, - and flush with the right edge. - - .. seealso:: - See :func:`~matplotlib.pyplot.text` for adding text - to the current axes - - Parameters - ---------- - label : str - Text to use for the title - - fontdict : dict - A dictionary controlling the appearance of the title text, - the default `fontdict` is: - - {'fontsize': rcParams['axes.titlesize'], - 'fontweight' : rcParams['axes.titleweight'], - 'verticalalignment': 'baseline', - 'horizontalalignment': loc} - - loc : {'center', 'left', 'right'}, str, optional - Which title to set, defaults to 'center' - - Returns - ------- - text : :class:`~matplotlib.text.Text` - The matplotlib text instance representing the title - - Other parameters - ---------------- - kwargs : text properties - Other keyword arguments are text properties, see - :class:`~matplotlib.text.Text` for a list of valid text - properties. - - """ - return gca().set_title(s, *args, **kwargs) - ## Axis ## -def axis(*v, **kwargs): - """ - Convenience method to get or set axis properties. - - Calling with no arguments:: - - >>> axis() - - returns the current axes limits ``[xmin, xmax, ymin, ymax]``.:: - - >>> axis(v) - - sets the min and max of the x and y axes, with - ``v = [xmin, xmax, ymin, ymax]``.:: - - >>> axis('off') - - turns off the axis lines and labels.:: - - >>> axis('equal') - - changes limits of *x* or *y* axis so that equal increments of *x* - and *y* have the same length; a circle is circular.:: - - >>> axis('scaled') - - achieves the same result by changing the dimensions of the plot box instead - of the axis data limits.:: - - >>> axis('tight') - - changes *x* and *y* axis limits such that all data is shown. If - all data is already shown, it will move it to the center of the - figure without modifying (*xmax* - *xmin*) or (*ymax* - - *ymin*). Note this is slightly different than in MATLAB.:: - - >>> axis('image') - - is 'scaled' with the axis limits equal to the data limits.:: - - >>> axis('auto') - - and:: - - >>> axis('normal') - - are deprecated. They restore default behavior; axis limits are automatically - scaled to make the data fit comfortably within the plot box. - - if ``len(*v)==0``, you can pass in *xmin*, *xmax*, *ymin*, *ymax* - as kwargs selectively to alter just those limits without changing - the others. - - >>> axis('square') - - changes the limit ranges (*xmax*-*xmin*) and (*ymax*-*ymin*) of - the *x* and *y* axes to be the same, and have the same scaling, - resulting in a square plot. - - The xmin, xmax, ymin, ymax tuple is returned - - .. seealso:: - - :func:`xlim`, :func:`ylim` - For setting the x- and y-limits individually. - """ - return gca().axis(*v, **kwargs) - - -def xlabel(s, *args, **kwargs): - """ - Set the x-axis label of the current axes. - - Call signature:: - - xlabel(label, fontdict=None, labelpad=None, **kwargs) - - This is the pyplot equivalent of calling `.set_xlabel` on the current axes. - See there for a full parameter description. - """ - return gca().set_xlabel(s, *args, **kwargs) - - -def ylabel(s, *args, **kwargs): - """ - Set the y-axis label of the current axes. - - Call signature:: - - ylabel(label, fontdict=None, labelpad=None, **kwargs) - - This is the pyplot equivalent of calling `.set_ylabel` on the current axes. - See there for a full parameter description. - """ - return gca().set_ylabel(s, *args, **kwargs) - - def xlim(*args, **kwargs): """ Get or set the x limits of the current axes. Call signatures:: - xmin, xmax = xlim() # return the current xlim - xlim((xmin, xmax)) # set the xlim to xmin, xmax - xlim(xmin, xmax) # set the xlim to xmin, xmax + left, right = xlim() # return the current xlim + xlim((left, right)) # set the xlim to left, right + xlim(left, right) # set the xlim to left, right - If you do not specify args, you can pass *xmin* or *xmax* as kwargs, i.e.:: + If you do not specify args, you can pass *left* or *right* as kwargs, + i.e.:: - xlim(xmax=3) # adjust the max leaving min unchanged - xlim(xmin=1) # adjust the min leaving max unchanged + xlim(right=3) # adjust the right leaving left unchanged + xlim(left=1) # adjust the left leaving right unchanged Setting limits turns autoscaling off for the x-axis. Returns ------- - xmin, xmax + left, right A tuple of the new x-axis limits. Notes @@ -1560,21 +1448,21 @@ def ylim(*args, **kwargs): Call signatures:: - ymin, ymax = ylim() # return the current ylim - ylim((ymin, ymax)) # set the ylim to ymin, ymax - ylim(ymin, ymax) # set the ylim to ymin, ymax + bottom, top = ylim() # return the current ylim + ylim((bottom, top)) # set the ylim to bottom, top + ylim(bottom, top) # set the ylim to bottom, top - If you do not specify args, you can alternatively pass *ymin* or *ymax* as - kwargs, i.e.:: + If you do not specify args, you can alternatively pass *bottom* or + *top* as kwargs, i.e.:: - ylim(ymax=3) # adjust the max leaving min unchanged - ylim(ymin=1) # adjust the min leaving max unchanged + ylim(top=3) # adjust the top leaving bottom unchanged + ylim(bottom=1) # adjust the top leaving bottom unchanged Setting limits turns autoscaling off for the y-axis. Returns ------- - ymin, ymax + bottom, top A tuple of the new y-axis limits. Notes @@ -1591,63 +1479,7 @@ def ylim(*args, **kwargs): return ret -@docstring.dedent_interpd -def xscale(*args, **kwargs): - """ - Set the scaling of the x-axis. - - Call signature:: - - xscale(scale, **kwargs) - - Parameters - ---------- - scale : [%(scale)s] - The scaling type. - **kwargs - Additional parameters depend on *scale*. See Notes. - - Notes - ----- - This is the pyplot equivalent of calling `~.Axes.set_xscale` on the - current axes. - - Different keywords may be accepted, depending on the scale: - - %(scale_docs)s - """ - gca().set_xscale(*args, **kwargs) - - -@docstring.dedent_interpd -def yscale(*args, **kwargs): - """ - Set the scaling of the y-axis. - - Call signature:: - - yscale(scale, **kwargs) - - Parameters - ---------- - scale : [%(scale)s] - The scaling type. - **kwargs - Additional parameters depend on *scale*. See Notes. - - Notes - ----- - This is the pyplot equivalent of calling `~.Axes.set_yscale` on the - current axes. - - Different keywords may be accepted, depending on the scale: - - %(scale_docs)s - """ - gca().set_yscale(*args, **kwargs) - - -def xticks(*args, **kwargs): +def xticks(ticks=None, labels=None, **kwargs): """ Get or set the current tick locations and labels of the x-axis. @@ -1655,11 +1487,11 @@ def xticks(*args, **kwargs): locs, labels = xticks() # Get locations and labels - xticks(locs, [labels], **kwargs) # Set locations and labels + xticks(ticks, [labels], **kwargs) # Set locations and labels Parameters ---------- - locs : array_like + ticks : array_like A list of positions at which ticks should be placed. You can pass an empty list to disable xticks. @@ -1709,24 +1541,22 @@ def xticks(*args, **kwargs): """ ax = gca() - if len(args)==0: + if ticks is None and labels is None: locs = ax.get_xticks() labels = ax.get_xticklabels() - elif len(args)==1: - locs = ax.set_xticks(args[0]) + elif labels is None: + locs = ax.set_xticks(ticks) labels = ax.get_xticklabels() - elif len(args)==2: - locs = ax.set_xticks(args[0]) - labels = ax.set_xticklabels(args[1], **kwargs) - else: raise TypeError('Illegal number of arguments to xticks') - if len(kwargs): - for l in labels: - l.update(kwargs) + else: + locs = ax.set_xticks(ticks) + labels = ax.set_xticklabels(labels, **kwargs) + for l in labels: + l.update(kwargs) return locs, silent_list('Text xticklabel', labels) -def yticks(*args, **kwargs): +def yticks(ticks=None, labels=None, **kwargs): """ Get or set the current tick locations and labels of the y-axis. @@ -1734,11 +1564,11 @@ def yticks(*args, **kwargs): locs, labels = yticks() # Get locations and labels - yticks(locs, [labels], **kwargs) # Set locations and labels + yticks(ticks, [labels], **kwargs) # Set locations and labels Parameters ---------- - locs : array_like + ticks : array_like A list of positions at which ticks should be placed. You can pass an empty list to disable yticks. @@ -1788,72 +1618,76 @@ def yticks(*args, **kwargs): """ ax = gca() - if len(args)==0: + if ticks is None and labels is None: locs = ax.get_yticks() labels = ax.get_yticklabels() - elif len(args)==1: - locs = ax.set_yticks(args[0]) + elif labels is None: + locs = ax.set_yticks(ticks) labels = ax.get_yticklabels() - elif len(args)==2: - locs = ax.set_yticks(args[0]) - labels = ax.set_yticklabels(args[1], **kwargs) - else: raise TypeError('Illegal number of arguments to yticks') - if len(kwargs): - for l in labels: - l.update(kwargs) - - - return ( locs, - silent_list('Text yticklabel', labels) - ) - + else: + locs = ax.set_yticks(ticks) + labels = ax.set_yticklabels(labels, **kwargs) + for l in labels: + l.update(kwargs) -def minorticks_on(): - """ - Display minor ticks on the current plot. + return locs, silent_list('Text yticklabel', labels) - Displaying minor ticks reduces performance; turn them off using - minorticks_off() if drawing speed is a problem. +def rgrids(*args, **kwargs): """ - gca().minorticks_on() + Get or set the radial gridlines on the current polar plot. + Call signatures:: -def minorticks_off(): - """ - Remove minor ticks from the current plot. - """ - gca().minorticks_off() + lines, labels = rgrids() + lines, labels = rgrids(radii, labels=None, angle=22.5, fmt=None, **kwargs) + When called with no arguments, `.rgrids` simply returns the tuple + (*lines*, *labels*). When called with arguments, the labels will + appear at the specified radial distances and angle. -def rgrids(*args, **kwargs): - """ - Get or set the radial gridlines on a polar plot. + Parameters + ---------- + radii : tuple with floats + The radii for the radial gridlines - call signatures:: + labels : tuple with strings or None + The labels to use at each radial gridline. The + `matplotlib.ticker.ScalarFormatter` will be used if None. - lines, labels = rgrids() - lines, labels = rgrids(radii, labels=None, angle=22.5, **kwargs) + angle : float + The angular position of the radius labels in degrees. - When called with no arguments, :func:`rgrid` simply returns the - tuple (*lines*, *labels*), where *lines* is an array of radial - gridlines (:class:`~matplotlib.lines.Line2D` instances) and - *labels* is an array of tick labels - (:class:`~matplotlib.text.Text` instances). When called with - arguments, the labels will appear at the specified radial - distances and angles. + fmt : str or None + Format string used in `matplotlib.ticker.FormatStrFormatter`. + For example '%f'. - *labels*, if not *None*, is a len(*radii*) list of strings of the - labels to use at each angle. + Returns + ------- + lines, labels : list of `.lines.Line2D`, list of `.text.Text` + *lines* are the radial gridlines and *labels* are the tick labels. - If *labels* is None, the rformatter will be used + Other Parameters + ---------------- + **kwargs + *kwargs* are optional `~.Text` properties for the labels. - Examples:: + Examples + -------- + :: - # set the locations of the radial gridlines and labels + # set the locations of the radial gridlines lines, labels = rgrids( (0.25, 0.5, 1.0) ) - # set the locations and labels of the radial gridlines and labels - lines, labels = rgrids( (0.25, 0.5, 1.0), ('Tom', 'Dick', 'Harry' ) + # set the locations and labels of the radial gridlines + lines, labels = rgrids( (0.25, 0.5, 1.0), ('Tom', 'Dick', 'Harry' )) + + See Also + -------- + .pyplot.thetagrids + .projections.polar.PolarAxes.set_rgrids + .Axis.get_gridlines + .Axis.get_ticklabels + """ ax = gca() @@ -1868,57 +1702,62 @@ def rgrids(*args, **kwargs): return ( silent_list('Line2D rgridline', lines), silent_list('Text rgridlabel', labels) ) - def thetagrids(*args, **kwargs): """ - Get or set the theta locations of the gridlines in a polar plot. - - If no arguments are passed, return a tuple (*lines*, *labels*) - where *lines* is an array of radial gridlines - (:class:`~matplotlib.lines.Line2D` instances) and *labels* is an - array of tick labels (:class:`~matplotlib.text.Text` instances):: + Get or set the theta gridlines on the current polar plot. - lines, labels = thetagrids() - - Otherwise the syntax is:: - - lines, labels = thetagrids(angles, labels=None, fmt='%d', frac = 1.1) - - set the angles at which to place the theta grids (these gridlines - are equal along the theta dimension). - - *angles* is in degrees. + Call signatures:: - *labels*, if not *None*, is a len(angles) list of strings of the - labels to use at each angle. + lines, labels = thetagrids() + lines, labels = thetagrids(angles, labels=None, fmt=None, **kwargs) - If *labels* is *None*, the labels will be ``fmt%angle``. + When called with no arguments, `.thetagrids` simply returns the tuple + (*lines*, *labels*). When called with arguments, the labels will + appear at the specified angles. - *frac* is the fraction of the polar axes radius at which to place - the label (1 is the edge). e.g., 1.05 is outside the axes and 0.95 - is inside the axes. + Parameters + ---------- + angles : tuple with floats, degrees + The angles of the theta gridlines. - Return value is a list of tuples (*lines*, *labels*): + labels : tuple with strings or None + The labels to use at each radial gridline. The + `.projections.polar.ThetaFormatter` will be used if None. - - *lines* are :class:`~matplotlib.lines.Line2D` instances + fmt : str or None + Format string used in `matplotlib.ticker.FormatStrFormatter`. + For example '%f'. Note that the angle in radians will be used. - - *labels* are :class:`~matplotlib.text.Text` instances. + Returns + ------- + lines, labels : list of `.lines.Line2D`, list of `.text.Text` + *lines* are the theta gridlines and *labels* are the tick labels. - Note that on input, the *labels* argument is a list of strings, - and on output it is a list of :class:`~matplotlib.text.Text` - instances. + Other Parameters + ---------------- + **kwargs + *kwargs* are optional `~.Text` properties for the labels. - Examples:: + Examples + -------- + :: - # set the locations of the radial gridlines and labels + # set the locations of the angular gridlines lines, labels = thetagrids( range(45,360,90) ) - # set the locations and labels of the radial gridlines and labels + # set the locations and labels of the angular gridlines lines, labels = thetagrids( range(45,360,90), ('NE', 'NW', 'SW','SE') ) + + See Also + -------- + .pyplot.rgrids + .projections.polar.PolarAxes.set_thetagrids + .Axis.get_gridlines + .Axis.get_ticklabels """ ax = gca() if not isinstance(ax, PolarAxes): - raise RuntimeError('rgrids only defined for polar axes') + raise RuntimeError('thetagrids only defined for polar axes') if len(args)==0: lines = ax.xaxis.get_ticklines() labels = ax.xaxis.get_ticklabels() @@ -1932,6 +1771,7 @@ def thetagrids(*args, **kwargs): ## Plotting Info ## + def plotting(): pass @@ -1940,77 +1780,19 @@ def get_plot_commands(): """ Get a sorted list of all of the plotting commands. """ - # This works by searching for all functions in this module and - # removing a few hard-coded exclusions, as well as all of the - # colormap-setting functions, and anything marked as private with - # a preceding underscore. - - import inspect - + # This works by searching for all functions in this module and removing + # a few hard-coded exclusions, as well as all of the colormap-setting + # functions, and anything marked as private with a preceding underscore. exclude = {'colormaps', 'colors', 'connect', 'disconnect', 'get_plot_commands', 'get_current_fig_manager', 'ginput', 'plotting', 'waitforbuttonpress'} exclude |= set(colormaps()) this_module = inspect.getmodule(get_plot_commands) - - commands = set() - for name, obj in list(six.iteritems(globals())): - if name.startswith('_') or name in exclude: - continue - if inspect.isfunction(obj) and inspect.getmodule(obj) is this_module: - commands.add(name) - - return sorted(commands) - - -@deprecated('2.1') -def colors(): - """ - This is a do-nothing function to provide you with help on how - matplotlib handles colors. - - Commands which take color arguments can use several formats to - specify the colors. For the basic built-in colors, you can use a - single letter - - ===== ======= - Alias Color - ===== ======= - 'b' blue - 'g' green - 'r' red - 'c' cyan - 'm' magenta - 'y' yellow - 'k' black - 'w' white - ===== ======= - - For a greater range of colors, you have two options. You can - specify the color using an html hex string, as in:: - - color = '#eeefff' - - or you can pass an R,G,B tuple, where each of R,G,B are in the - range [0,1]. - - You can also use any legal html name for a color, for example:: - - color = 'red' - color = 'burlywood' - color = 'chartreuse' - - The example below creates a subplot with a dark - slate gray background:: - - subplot(111, facecolor=(0.1843, 0.3098, 0.3098)) - - Here is an example that creates a pale turquoise title:: - - title('Is this the best color?', color='#afeeee') - - """ - pass + return sorted( + name for name, obj in globals().items() + if not name.startswith('_') and name not in exclude + and inspect.isfunction(obj) + and inspect.getmodule(obj) is this_module) def colormaps(): @@ -2044,8 +1826,8 @@ def colormaps(): for bipolar data that emphasizes positive or negative deviations from a central value Cyclic schemes - meant for plotting values that wrap around at the - endpoints, such as phase angle, wind direction, or time of day + for plotting values that wrap around at the endpoints, such as phase + angle, wind direction, or time of day Qualitative schemes for nominal data that has no inherent ordering, where color is used only to distinguish categories @@ -2140,8 +1922,6 @@ def colormaps(): grayscale hot sequential black-red-yellow-white, to emulate blackbody radiation from an object at increasing temperatures - hsv cyclic red-yellow-green-cyan-blue-magenta-red, formed - by changing the hue component in the HSV color space jet a spectral map with dark endpoints, blue-cyan-yellow-red; based on a fluid-jet simulation by NCSA [#]_ pink sequential increasing pastel black-pink-white, meant @@ -2173,6 +1953,17 @@ def colormaps(): Language software ============ ======================================================= + A set of cyclic color maps: + + ================ ========================================================= + Colormap Description + ================ ========================================================= + hsv red-yellow-green-cyan-blue-magenta-red, formed by changing + the hue component in the HSV color space + twilight perceptually uniform shades of white-blue-black-red-white + twilight_shifted perceptually uniform shades of black-blue-white-red-black + ================ ========================================================= + Other miscellaneous schemes: @@ -2222,7 +2013,6 @@ def colormaps(): gist_gray identical to *gray* gist_yarg identical to *gray_r* binary identical to *gray_r* - spectral identical to *nipy_spectral* [#]_ ========= ======================================================= .. rubric:: Footnotes @@ -2243,33 +2033,18 @@ def colormaps(): Color-Scale Images `_ by Carey Rappaport - - .. [#] Changed to distinguish from ColorBrewer's *Spectral* map. - :func:`spectral` still works, but - ``set_cmap('nipy_spectral')`` is recommended for clarity. - - """ return sorted(cm.cmap_d) def _setup_pyplot_info_docstrings(): """ - Generates the plotting and docstring. + Generates the plotting docstring. 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. """ - # Generate the plotting docstring - import re - - def pad(s, l): - """Pad string *s* to length *l*.""" - if l < len(s): - return s[:l] - return s + ' ' * (l - len(s)) - commands = get_plot_commands() first_sentence = re.compile(r"(?:\s*).+?\.(?:\s+|$)", flags=re.DOTALL) @@ -2277,35 +2052,37 @@ def pad(s, l): # Collect the first sentence of the docstring for all of the # plotting commands. rows = [] - max_name = 0 - max_summary = 0 + max_name = len("Function") + max_summary = len("Description") for name in commands: doc = globals()[name].__doc__ summary = '' if doc is not None: match = first_sentence.match(doc) if match is not None: - summary = match.group(0).strip().replace('\n', ' ') + summary = inspect.cleandoc(match.group(0)).replace('\n', ' ') name = '`%s`' % name rows.append([name, summary]) max_name = max(max_name, len(name)) max_summary = max(max_summary, len(summary)) - lines = [] - sep = '=' * max_name + ' ' + '=' * max_summary - lines.append(sep) - lines.append(' '.join([pad("Function", max_name), - pad("Description", max_summary)])) - lines.append(sep) - for name, summary in rows: - lines.append(' '.join([pad(name, max_name), - pad(summary, max_summary)])) - lines.append(sep) - + separator = '=' * max_name + ' ' + '=' * max_summary + lines = [ + separator, + '{:{}} {:{}}'.format('Function', max_name, 'Description', max_summary), + separator, + ] + [ + '{:{}} {:{}}'.format(name, max_name, summary, max_summary) + for name, summary in rows + ] + [ + separator, + ] plotting.__doc__ = '\n'.join(lines) + ## Plotting part 1: manually generated functions and wrappers ## + def colorbar(mappable=None, cax=None, ax=None, **kw): if mappable is None: mappable = gci() @@ -2367,15 +2144,14 @@ def set_cmap(cmap): im.set_cmap(cmap) +@docstring.copy_dedent(matplotlib.image.imread) +def imread(fname, format=None): + return matplotlib.image.imread(fname, format) -@docstring.copy_dedent(_imread) -def imread(*args, **kwargs): - return _imread(*args, **kwargs) - -@docstring.copy_dedent(_imsave) -def imsave(*args, **kwargs): - return _imsave(*args, **kwargs) +@docstring.copy_dedent(matplotlib.image.imsave) +def imsave(fname, arr, **kwargs): + return matplotlib.image.imsave(fname, arr, **kwargs) def matshow(A, fignum=None, **kwargs): @@ -2486,7 +2262,7 @@ def plotfile(fname, cols=(0,), plotfuncs=None, columns. *comments*, *skiprows*, *checkrows*, *delimiter*, and *names* - are all passed on to :func:`matplotlib.pylab.csv2rec` to + are all passed on to :func:`matplotlib.mlab.csv2rec` to load the data into a record array. If *newfig* is *True*, the plot always will be made in a new figure; @@ -2519,17 +2295,17 @@ def plotfile(fname, cols=(0,), plotfuncs=None, if plotfuncs is None: plotfuncs = dict() - from matplotlib.cbook import mplDeprecation + from matplotlib.cbook import MatplotlibDeprecationWarning with warnings.catch_warnings(): - warnings.simplefilter('ignore', mplDeprecation) + warnings.simplefilter('ignore', MatplotlibDeprecationWarning) r = mlab.csv2rec(fname, comments=comments, skiprows=skiprows, checkrows=checkrows, delimiter=delimiter, names=names) def getname_val(identifier): 'return the name and column data for identifier' - if isinstance(identifier, six.string_types): + if isinstance(identifier, str): return identifier, r[identifier] - elif is_numlike(identifier): + elif isinstance(identifier, Number): name = r.dtype.names[int(identifier)] return name, r[name] else: @@ -2570,7 +2346,7 @@ def getname_val(identifier): ax.set_xlabel('') if not subplots: - ax.legend(ynamelist, loc='best') + ax.legend(ynamelist) if xname=='date': fig.autofmt_xdate() @@ -2579,1297 +2355,631 @@ def getname_val(identifier): def _autogen_docstring(base): """Autogenerated wrappers will get their docstring from a base function with an addendum.""" - #msg = "\n\nAdditional kwargs: hold = [True|False] overrides default hold state" msg = '' addendum = docstring.Appender(msg, '\n\n') return lambda func: addendum(docstring.copy_dedent(base)(func)) -# This function cannot be generated by boilerplate.py because it may -# return an image or a line. -@_autogen_docstring(Axes.spy) -def spy(Z, precision=0, marker=None, markersize=None, aspect='equal', **kwargs): - ax = gca() - hold = kwargs.pop('hold', None) - # allow callers to override the hold state by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.spy(Z, precision, marker, markersize, aspect, **kwargs) - finally: - ax._hold = washold - if isinstance(ret, cm.ScalarMappable): - sci(ret) - return ret -# just to be safe. Interactive mode can be turned on without +# If rcParams['backend_fallback'] is true, and an interactive backend is +# requested, ignore rcParams['backend'] and force selection of a backend that +# is compatible with the current running interactive framework. +if (rcParams["backend_fallback"] + and dict.__getitem__(rcParams, "backend") in _interactive_bk + and _get_running_interactive_framework()): + dict.__setitem__(rcParams, "backend", rcsetup._auto_backend_sentinel) +# Set up the backend. +switch_backend(rcParams["backend"]) + +# Just to be safe. Interactive mode can be turned on without # calling `plt.ion()` so register it again here. # This is safe because multiple calls to `install_repl_displayhook` # are no-ops and the registered function respect `mpl.is_interactive()` # to determine if they should trigger a draw. install_repl_displayhook() + ################# REMAINING CONTENT GENERATED BY boilerplate.py ############## # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.acorr) -def acorr(x, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.acorr(x, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.acorr) +def acorr(x, *, data=None, **kwargs): + return gca().acorr(x=x, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.angle_spectrum) -def angle_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.angle_spectrum(x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, - sides=sides, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.angle_spectrum) +def angle_spectrum( + x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, *, + data=None, **kwargs): + return gca().angle_spectrum( + x=x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, + data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.arrow) -def arrow(x, y, dx, dy, hold=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.arrow(x, y, dx, dy, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.annotate) +def annotate(text, xy, *args, **kwargs): + return gca().annotate(text=text, xy=xy, *args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.axhline) -def axhline(y=0, xmin=0, xmax=1, hold=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.axhline(y=y, xmin=xmin, xmax=xmax, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.arrow) +def arrow(x, y, dx, dy, **kwargs): + return gca().arrow(x=x, y=y, dx=dx, dy=dy, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.axhspan) -def axhspan(ymin, ymax, xmin=0, xmax=1, hold=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.autoscale) +def autoscale(enable=True, axis='both', tight=None): + return gca().autoscale(enable=enable, axis=axis, tight=tight) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.axvline) -def axvline(x=0, ymin=0, ymax=1, hold=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.axvline(x=x, ymin=ymin, ymax=ymax, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.axhline) +def axhline(y=0, xmin=0, xmax=1, **kwargs): + return gca().axhline(y=y, xmin=xmin, xmax=xmax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.axvspan) -def axvspan(xmin, xmax, ymin=0, ymax=1, hold=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.axhspan) +def axhspan(ymin, ymax, xmin=0, xmax=1, **kwargs): + return gca().axhspan(ymin=ymin, ymax=ymax, xmin=xmin, xmax=xmax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.bar) -def bar(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.bar(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.axis) +def axis(*v, **kwargs): + return gca().axis(*v, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.barh) -def barh(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.barh(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.axvline) +def axvline(x=0, ymin=0, ymax=1, **kwargs): + return gca().axvline(x=x, ymin=ymin, ymax=ymax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.broken_barh) -def broken_barh(xranges, yrange, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.broken_barh(xranges, yrange, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.axvspan) +def axvspan(xmin, xmax, ymin=0, ymax=1, **kwargs): + return gca().axvspan(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=None, sym=None, vert=None, whis=None, positions=None, - widths=None, patch_artist=None, bootstrap=None, usermedians=None, - conf_intervals=None, meanline=None, showmeans=None, showcaps=None, - showbox=None, showfliers=None, boxprops=None, labels=None, - flierprops=None, medianprops=None, meanprops=None, capprops=None, - whiskerprops=None, manage_xticks=True, autorange=False, zorder=None, - hold=None, data=None): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.boxplot(x, notch=notch, sym=sym, vert=vert, whis=whis, - positions=positions, widths=widths, - patch_artist=patch_artist, bootstrap=bootstrap, - usermedians=usermedians, - conf_intervals=conf_intervals, meanline=meanline, - showmeans=showmeans, showcaps=showcaps, - showbox=showbox, showfliers=showfliers, - boxprops=boxprops, labels=labels, - flierprops=flierprops, medianprops=medianprops, - meanprops=meanprops, capprops=capprops, - whiskerprops=whiskerprops, - manage_xticks=manage_xticks, autorange=autorange, - zorder=zorder, data=data) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.bar) +def bar( + x, height, width=0.8, bottom=None, *, align='center', + data=None, **kwargs): + return gca().bar( + x=x, height=height, width=width, bottom=bottom, align=align, + data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.cohere) -def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, - window=mlab.window_hanning, noverlap=0, pad_to=None, sides='default', - scale_by_freq=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.cohere(x, y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, - window=window, noverlap=noverlap, pad_to=pad_to, - sides=sides, scale_by_freq=scale_by_freq, data=data, - **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.barbs) +def barbs(*args, data=None, **kw): + return gca().barbs(*args, data=data, **kw) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.clabel) -def clabel(CS, *args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.clabel(CS, *args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.barh) +def barh(y, width, height=0.8, left=None, *, align='center', **kwargs): + return gca().barh( + y=y, width=width, height=height, left=left, align=align, + **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.contour) -def contour(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.contour(*args, **kwargs) - finally: - ax._hold = washold - if ret._A is not None: sci(ret) - return ret +@docstring.copy_dedent(Axes.boxplot) +def boxplot( + x, notch=None, sym=None, vert=None, whis=None, + positions=None, widths=None, patch_artist=None, + bootstrap=None, usermedians=None, conf_intervals=None, + meanline=None, showmeans=None, showcaps=None, showbox=None, + showfliers=None, boxprops=None, labels=None, flierprops=None, + medianprops=None, meanprops=None, capprops=None, + whiskerprops=None, manage_xticks=True, autorange=False, + zorder=None, *, data=None): + return gca().boxplot( + x=x, notch=notch, sym=sym, vert=vert, whis=whis, + positions=positions, widths=widths, patch_artist=patch_artist, + bootstrap=bootstrap, usermedians=usermedians, + conf_intervals=conf_intervals, meanline=meanline, + showmeans=showmeans, showcaps=showcaps, showbox=showbox, + showfliers=showfliers, boxprops=boxprops, labels=labels, + flierprops=flierprops, medianprops=medianprops, + meanprops=meanprops, capprops=capprops, + whiskerprops=whiskerprops, manage_xticks=manage_xticks, + autorange=autorange, zorder=zorder, data=data) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.contourf) -def contourf(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.contourf(*args, **kwargs) - finally: - ax._hold = washold - if ret._A is not None: sci(ret) - return ret +@docstring.copy_dedent(Axes.broken_barh) +def broken_barh(xranges, yrange, *, data=None, **kwargs): + return gca().broken_barh( + xranges=xranges, yrange=yrange, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.csd) -def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, - noverlap=None, pad_to=None, sides=None, scale_by_freq=None, - return_line=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.csd(x, y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, - window=window, noverlap=noverlap, pad_to=pad_to, - sides=sides, scale_by_freq=scale_by_freq, - return_line=return_line, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.cla) +def cla(): + return gca().cla() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.errorbar) -def errorbar(x, y, yerr=None, xerr=None, fmt='', ecolor=None, elinewidth=None, - capsize=None, barsabove=False, lolims=False, uplims=False, - xlolims=False, xuplims=False, errorevery=1, capthick=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.errorbar(x, y, yerr=yerr, xerr=xerr, fmt=fmt, ecolor=ecolor, - elinewidth=elinewidth, capsize=capsize, - barsabove=barsabove, lolims=lolims, uplims=uplims, - xlolims=xlolims, xuplims=xuplims, - errorevery=errorevery, capthick=capthick, data=data, - **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.clabel) +def clabel(CS, *args, **kwargs): + return gca().clabel(CS=CS, *args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation='horizontal', lineoffsets=1, linelengths=1, - linewidths=None, colors=None, linestyles='solid', hold=None, - data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.eventplot(positions, orientation=orientation, - lineoffsets=lineoffsets, linelengths=linelengths, - linewidths=linewidths, colors=colors, - linestyles=linestyles, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.cohere) +def cohere( + x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, + window=mlab.window_hanning, noverlap=0, pad_to=None, + sides='default', scale_by_freq=None, *, data=None, **kwargs): + return gca().cohere( + x=x, y=y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, + window=window, noverlap=noverlap, pad_to=pad_to, sides=sides, + scale_by_freq=scale_by_freq, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.fill) -def fill(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.fill(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.contour) +def contour(*args, data=None, **kwargs): + __ret = gca().contour(*args, data=data, **kwargs) + if __ret._A is not None: sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.fill_between) -def fill_between(x, y1, y2=0, where=None, interpolate=False, step=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.fill_between(x, y1, y2=y2, where=where, - interpolate=interpolate, step=step, data=data, - **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.contourf) +def contourf(*args, data=None, **kwargs): + __ret = gca().contourf(*args, data=data, **kwargs) + if __ret._A is not None: sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.fill_betweenx) -def fill_betweenx(y, x1, x2=0, where=None, step=None, interpolate=False, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.fill_betweenx(y, x1, x2=x2, where=where, step=step, - interpolate=interpolate, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.csd) +def csd( + x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, + noverlap=None, pad_to=None, sides=None, scale_by_freq=None, + return_line=None, *, data=None, **kwargs): + return gca().csd( + x=x, y=y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, + window=window, noverlap=noverlap, pad_to=pad_to, sides=sides, + scale_by_freq=scale_by_freq, return_line=return_line, + data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.errorbar) +def errorbar( + x, y, yerr=None, xerr=None, fmt='', ecolor=None, + elinewidth=None, capsize=None, barsabove=False, lolims=False, + uplims=False, xlolims=False, xuplims=False, errorevery=1, + capthick=None, *, data=None, **kwargs): + return gca().errorbar( + x=x, y=y, yerr=yerr, xerr=xerr, fmt=fmt, ecolor=ecolor, + elinewidth=elinewidth, capsize=capsize, barsabove=barsabove, + lolims=lolims, uplims=uplims, xlolims=xlolims, + xuplims=xuplims, errorevery=errorevery, capthick=capthick, + data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.eventplot) +def eventplot( + positions, orientation='horizontal', lineoffsets=1, + linelengths=1, linewidths=None, colors=None, + linestyles='solid', *, data=None, **kwargs): + return gca().eventplot( + positions=positions, orientation=orientation, + lineoffsets=lineoffsets, linelengths=linelengths, + linewidths=linewidths, colors=colors, linestyles=linestyles, + data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.fill) +def fill(*args, data=None, **kwargs): + return gca().fill(*args, data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.fill_between) +def fill_between( + x, y1, y2=0, where=None, interpolate=False, step=None, *, + data=None, **kwargs): + return gca().fill_between( + x=x, y1=y1, y2=y2, where=where, interpolate=interpolate, + step=step, data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.fill_betweenx) +def fill_betweenx( + y, x1, x2=0, where=None, step=None, interpolate=False, *, + data=None, **kwargs): + return gca().fill_betweenx( + y=y, x1=x1, x2=x2, where=where, step=step, + interpolate=interpolate, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.hexbin) -def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', - yscale='linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors='face', - reduce_C_function=np.mean, mincnt=None, marginals=False, hold=None, - data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.hexbin(x, y, C=C, gridsize=gridsize, bins=bins, xscale=xscale, - yscale=yscale, extent=extent, cmap=cmap, norm=norm, - vmin=vmin, vmax=vmax, alpha=alpha, - linewidths=linewidths, edgecolors=edgecolors, - reduce_C_function=reduce_C_function, mincnt=mincnt, - marginals=marginals, data=data, **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret +@docstring.copy_dedent(Axes.grid) +def grid(b=None, which='major', axis='both', **kwargs): + return gca().grid(b=b, which=which, axis=axis, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.hist) -def hist(x, bins=None, range=None, density=None, weights=None, cumulative=False, - bottom=None, histtype='bar', align='mid', orientation='vertical', - rwidth=None, log=False, color=None, label=None, stacked=False, - normed=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.hist(x, bins=bins, range=range, density=density, - weights=weights, cumulative=cumulative, bottom=bottom, - histtype=histtype, align=align, orientation=orientation, - rwidth=rwidth, log=log, color=color, label=label, - stacked=stacked, normed=normed, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.hexbin) +def hexbin( + x, y, C=None, gridsize=100, bins=None, xscale='linear', + yscale='linear', extent=None, cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, edgecolors='face', + reduce_C_function=np.mean, mincnt=None, marginals=False, *, + data=None, **kwargs): + __ret = gca().hexbin( + x=x, y=y, C=C, gridsize=gridsize, bins=bins, xscale=xscale, + yscale=yscale, extent=extent, cmap=cmap, norm=norm, vmin=vmin, + vmax=vmax, alpha=alpha, linewidths=linewidths, + edgecolors=edgecolors, reduce_C_function=reduce_C_function, + mincnt=mincnt, marginals=marginals, data=data, **kwargs) + sci(__ret) + return __ret + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.hist) +def hist( + x, bins=None, range=None, density=None, weights=None, + cumulative=False, bottom=None, histtype='bar', align='mid', + orientation='vertical', rwidth=None, log=False, color=None, + label=None, stacked=False, normed=None, *, data=None, + **kwargs): + return gca().hist( + x=x, bins=bins, range=range, density=density, weights=weights, + cumulative=cumulative, bottom=bottom, histtype=histtype, + align=align, orientation=orientation, rwidth=rwidth, log=log, + color=color, label=label, stacked=stacked, normed=normed, + data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_autogen_docstring(Axes.hist2d) -def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, - cmax=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.hist2d(x, y, bins=bins, range=range, normed=normed, - weights=weights, cmin=cmin, cmax=cmax, data=data, - **kwargs) - finally: - ax._hold = washold - sci(ret[-1]) - return ret +def hist2d( + x, y, bins=10, range=None, normed=False, weights=None, + cmin=None, cmax=None, *, data=None, **kwargs): + __ret = gca().hist2d( + x=x, y=y, bins=bins, range=range, normed=normed, + weights=weights, cmin=cmin, cmax=cmax, data=data, **kwargs) + sci(__ret[-1]) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, - data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.hlines(y, xmin, xmax, colors=colors, linestyles=linestyles, - label=label, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.hlines) +def hlines( + y, xmin, xmax, colors='k', linestyles='solid', label='', *, + data=None, **kwargs): + return gca().hlines( + y=y, xmin=xmin, xmax=xmax, colors=colors, + linestyles=linestyles, label=label, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_autogen_docstring(Axes.imshow) -def imshow(X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None, - vmin=None, vmax=None, origin=None, extent=None, shape=None, - filternorm=1, filterrad=4.0, imlim=None, resample=None, url=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.imshow(X, cmap=cmap, norm=norm, aspect=aspect, - interpolation=interpolation, alpha=alpha, vmin=vmin, - vmax=vmax, origin=origin, extent=extent, shape=shape, - filternorm=filternorm, filterrad=filterrad, - imlim=imlim, resample=resample, url=url, data=data, - **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret +def imshow( + X, cmap=None, norm=None, aspect=None, interpolation=None, + alpha=None, vmin=None, vmax=None, origin=None, extent=None, + shape=None, filternorm=1, filterrad=4.0, imlim=None, + resample=None, url=None, *, data=None, **kwargs): + __ret = gca().imshow( + X=X, cmap=cmap, norm=norm, aspect=aspect, + interpolation=interpolation, alpha=alpha, vmin=vmin, + vmax=vmax, origin=origin, extent=extent, shape=shape, + filternorm=filternorm, filterrad=filterrad, imlim=imlim, + resample=resample, url=url, data=data, **kwargs) + sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.loglog) -def loglog(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.loglog(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.legend) +def legend(*args, **kwargs): + return gca().legend(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.magnitude_spectrum) -def magnitude_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, - sides=None, scale=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.magnitude_spectrum(x, Fs=Fs, Fc=Fc, window=window, - pad_to=pad_to, sides=sides, scale=scale, - data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.locator_params) +def locator_params(axis='both', tight=None, **kwargs): + return gca().locator_params(axis=axis, tight=tight, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.pcolor) -def pcolor(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.pcolor(*args, **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret +@docstring.copy_dedent(Axes.loglog) +def loglog(*args, **kwargs): + return gca().loglog(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.pcolormesh) -def pcolormesh(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.pcolormesh(*args, **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret +@docstring.copy_dedent(Axes.magnitude_spectrum) +def magnitude_spectrum( + x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, + scale=None, *, data=None, **kwargs): + return gca().magnitude_spectrum( + x=x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, + scale=scale, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.phase_spectrum) -def phase_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.phase_spectrum(x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, - sides=sides, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.margins) +def margins(*margins, x=None, y=None, tight=True): + return gca().margins(*margins, x=x, y=y, tight=tight) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.pie) -def pie(x, explode=None, labels=None, colors=None, autopct=None, - pctdistance=0.6, shadow=False, labeldistance=1.1, startangle=None, - radius=None, counterclock=True, wedgeprops=None, textprops=None, - center=(0, 0), frame=False, rotatelabels=False, hold=None, data=None): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.pie(x, explode=explode, labels=labels, colors=colors, - autopct=autopct, pctdistance=pctdistance, shadow=shadow, - labeldistance=labeldistance, startangle=startangle, - radius=radius, counterclock=counterclock, - wedgeprops=wedgeprops, textprops=textprops, center=center, - frame=frame, rotatelabels=rotatelabels, data=data) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.minorticks_off) +def minorticks_off(): + return gca().minorticks_off() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.plot) -def plot(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.plot(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.minorticks_on) +def minorticks_on(): + return gca().minorticks_on() # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.plot_date) -def plot_date(x, y, fmt='o', tz=None, xdate=True, ydate=False, hold=None, - data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.plot_date(x, y, fmt=fmt, tz=tz, xdate=xdate, ydate=ydate, - data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.pcolor) +def pcolor( + *args, alpha=None, norm=None, cmap=None, vmin=None, + vmax=None, data=None, **kwargs): + __ret = gca().pcolor( + *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, + vmax=vmax, data=data, **kwargs) + sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.psd) -def psd(x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, +@_autogen_docstring(Axes.pcolormesh) +def pcolormesh( + *args, alpha=None, norm=None, cmap=None, vmin=None, + vmax=None, shading='flat', antialiased=False, data=None, + **kwargs): + __ret = gca().pcolormesh( + *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, + vmax=vmax, shading=shading, antialiased=antialiased, + data=data, **kwargs) + sci(__ret) + return __ret + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.phase_spectrum) +def phase_spectrum( + x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, *, + data=None, **kwargs): + return gca().phase_spectrum( + x=x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, + data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.pie) +def pie( + x, explode=None, labels=None, colors=None, autopct=None, + pctdistance=0.6, shadow=False, labeldistance=1.1, + startangle=None, radius=None, counterclock=True, + wedgeprops=None, textprops=None, center=(0, 0), frame=False, + rotatelabels=False, *, data=None): + return gca().pie( + x=x, explode=explode, labels=labels, colors=colors, + autopct=autopct, pctdistance=pctdistance, shadow=shadow, + labeldistance=labeldistance, startangle=startangle, + radius=radius, counterclock=counterclock, + wedgeprops=wedgeprops, textprops=textprops, center=center, + frame=frame, rotatelabels=rotatelabels, data=data) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.plot) +def plot(*args, scalex=True, scaley=True, data=None, **kwargs): + return gca().plot( + *args, scalex=scalex, scaley=scaley, data=data, **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.plot_date) +def plot_date( + x, y, fmt='o', tz=None, xdate=True, ydate=False, *, + data=None, **kwargs): + return gca().plot_date( + x=x, y=y, fmt=fmt, tz=tz, xdate=xdate, ydate=ydate, data=data, + **kwargs) + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.psd) +def psd( + x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None, - return_line=None, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.psd(x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, - window=window, noverlap=noverlap, pad_to=pad_to, - sides=sides, scale_by_freq=scale_by_freq, - return_line=return_line, data=data, **kwargs) - finally: - ax._hold = washold - - return ret + return_line=None, *, data=None, **kwargs): + return gca().psd( + x=x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, + noverlap=noverlap, pad_to=pad_to, sides=sides, + scale_by_freq=scale_by_freq, return_line=return_line, + data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_autogen_docstring(Axes.quiver) -def quiver(*args, **kw): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kw.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.quiver(*args, **kw) - finally: - ax._hold = washold - sci(ret) - return ret +def quiver(*args, data=None, **kw): + __ret = gca().quiver(*args, data=data, **kw) + sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.quiverkey) -def quiverkey(*args, **kw): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kw.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.quiverkey(*args, **kw) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.quiverkey) +def quiverkey(Q, X, Y, U, label, **kw): + return gca().quiverkey(Q=Q, X=X, Y=Y, U=U, label=label, **kw) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.scatter(x, y, s=s, c=c, marker=marker, cmap=cmap, norm=norm, - vmin=vmin, vmax=vmax, alpha=alpha, - linewidths=linewidths, verts=verts, - edgecolors=edgecolors, data=data, **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.semilogx) +def scatter( + x, y, s=None, c=None, marker=None, cmap=None, norm=None, + vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, + edgecolors=None, *, data=None, **kwargs): + __ret = gca().scatter( + x=x, y=y, s=s, c=c, marker=marker, cmap=cmap, norm=norm, + vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, + verts=verts, edgecolors=edgecolors, data=data, **kwargs) + sci(__ret) + return __ret + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@docstring.copy_dedent(Axes.semilogx) def semilogx(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.semilogx(*args, **kwargs) - finally: - ax._hold = washold - - return ret + return gca().semilogx(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.semilogy) +@docstring.copy_dedent(Axes.semilogy) def semilogy(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.semilogy(*args, **kwargs) - finally: - ax._hold = washold - - return ret + return gca().semilogy(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_autogen_docstring(Axes.specgram) -def specgram(x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, - noverlap=None, cmap=None, xextent=None, pad_to=None, sides=None, - scale_by_freq=None, mode=None, scale=None, vmin=None, vmax=None, - hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.specgram(x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, - window=window, noverlap=noverlap, cmap=cmap, - xextent=xextent, pad_to=pad_to, sides=sides, - scale_by_freq=scale_by_freq, mode=mode, scale=scale, - vmin=vmin, vmax=vmax, data=data, **kwargs) - finally: - ax._hold = washold - sci(ret[-1]) - return ret - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.stackplot) -def stackplot(x, *args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.stackplot(x, *args, **kwargs) - finally: - ax._hold = washold - - return ret +def specgram( + x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, + noverlap=None, cmap=None, xextent=None, pad_to=None, + sides=None, scale_by_freq=None, mode=None, scale=None, + vmin=None, vmax=None, *, data=None, **kwargs): + __ret = gca().specgram( + x=x, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, + noverlap=noverlap, cmap=cmap, xextent=xextent, pad_to=pad_to, + sides=sides, scale_by_freq=scale_by_freq, mode=mode, + scale=scale, vmin=vmin, vmax=vmax, data=data, **kwargs) + sci(__ret[-1]) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.stem) -def stem(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.stem(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.spy) +def spy( + Z, precision=0, marker=None, markersize=None, aspect='equal', + origin='upper', **kwargs): + __ret = gca().spy( + Z=Z, precision=precision, marker=marker, + markersize=markersize, aspect=aspect, origin=origin, **kwargs) + if isinstance(__ret, cm.ScalarMappable): sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.step) -def step(x, y, *args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.step(x, y, *args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.stackplot) +def stackplot(x, *args, data=None, **kwargs): + return gca().stackplot(x=x, *args, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.streamplot) -def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, - transform=None, zorder=None, start_points=None, maxlength=4.0, - integration_direction='both', hold=None, data=None): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.streamplot(x, y, u, v, density=density, linewidth=linewidth, - color=color, cmap=cmap, norm=norm, - arrowsize=arrowsize, arrowstyle=arrowstyle, - minlength=minlength, transform=transform, - zorder=zorder, start_points=start_points, - maxlength=maxlength, - integration_direction=integration_direction, - data=data) - finally: - ax._hold = washold - sci(ret.lines) - return ret +@docstring.copy_dedent(Axes.stem) +def stem( + *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, + label=None, data=None): + return gca().stem( + *args, linefmt=linefmt, markerfmt=markerfmt, basefmt=basefmt, + bottom=bottom, label=label, data=data) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.tricontour) -def tricontour(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.tricontour(*args, **kwargs) - finally: - ax._hold = washold - if ret._A is not None: sci(ret) - return ret +@docstring.copy_dedent(Axes.step) +def step(x, y, *args, where='pre', data=None, **kwargs): + return gca().step(x=x, y=y, *args, where=where, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.tricontourf) -def tricontourf(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.tricontourf(*args, **kwargs) - finally: - ax._hold = washold - if ret._A is not None: sci(ret) - return ret +@_autogen_docstring(Axes.streamplot) +def streamplot( + x, y, u, v, density=1, linewidth=None, color=None, cmap=None, + norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, + transform=None, zorder=None, start_points=None, maxlength=4.0, + integration_direction='both', *, data=None): + __ret = gca().streamplot( + x=x, y=y, u=u, v=v, density=density, linewidth=linewidth, + color=color, cmap=cmap, norm=norm, arrowsize=arrowsize, + arrowstyle=arrowstyle, minlength=minlength, + transform=transform, zorder=zorder, start_points=start_points, + maxlength=maxlength, + integration_direction=integration_direction, data=data) + sci(__ret.lines) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.tripcolor) -def tripcolor(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.tripcolor(*args, **kwargs) - finally: - ax._hold = washold - sci(ret) - return ret +@docstring.copy_dedent(Axes.table) +def table(**kwargs): + return gca().table(**kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.triplot) -def triplot(*args, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kwargs.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.triplot(*args, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.text) +def text(x, y, s, fontdict=None, withdash=False, **kwargs): + return gca().text( + x=x, y=y, s=s, fontdict=fontdict, withdash=withdash, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.violinplot) -def violinplot(dataset, positions=None, vert=True, widths=0.5, showmeans=False, - showextrema=True, showmedians=False, points=100, bw_method=None, - hold=None, data=None): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.violinplot(dataset, positions=positions, vert=vert, - widths=widths, showmeans=showmeans, - showextrema=showextrema, showmedians=showmedians, - points=points, bw_method=bw_method, data=data) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.tick_params) +def tick_params(axis='both', **kwargs): + return gca().tick_params(axis=axis, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, - data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.vlines(x, ymin, ymax, colors=colors, linestyles=linestyles, - label=label, data=data, **kwargs) - finally: - ax._hold = washold - - return ret +@docstring.copy_dedent(Axes.ticklabel_format) +def ticklabel_format( + *, axis='both', style='', scilimits=None, useOffset=None, + useLocale=None, useMathText=None): + return gca().ticklabel_format( + axis=axis, style=style, scilimits=scilimits, + useOffset=useOffset, useLocale=useLocale, + useMathText=useMathText) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.xcorr) -def xcorr(x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, - maxlags=10, hold=None, data=None, **kwargs): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.xcorr(x, y, normed=normed, detrend=detrend, - usevlines=usevlines, maxlags=maxlags, data=data, - **kwargs) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.tricontour) +def tricontour(*args, **kwargs): + __ret = gca().tricontour(*args, **kwargs) + if __ret._A is not None: sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_autogen_docstring(Axes.barbs) -def barbs(*args, **kw): - ax = gca() - # Deprecated: allow callers to override the hold state - # by passing hold=True|False - washold = ax._hold - hold = kw.pop('hold', None) - if hold is not None: - ax._hold = hold - from matplotlib.cbook import mplDeprecation - warnings.warn("The 'hold' keyword argument is deprecated since 2.0.", - mplDeprecation) - try: - ret = ax.barbs(*args, **kw) - finally: - ax._hold = washold - - return ret +@_autogen_docstring(Axes.tricontourf) +def tricontourf(*args, **kwargs): + __ret = gca().tricontourf(*args, **kwargs) + if __ret._A is not None: sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.cla) -def cla(): - ret = gca().cla() - return ret +@_autogen_docstring(Axes.tripcolor) +def tripcolor(*args, **kwargs): + __ret = gca().tripcolor(*args, **kwargs) + sci(__ret) + return __ret # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.grid) -def grid(b=None, which='major', axis='both', **kwargs): - ret = gca().grid(b=b, which=which, axis=axis, **kwargs) - return ret +@docstring.copy_dedent(Axes.triplot) +def triplot(*args, **kwargs): + return gca().triplot(*args, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.legend) -def legend(*args, **kwargs): - ret = gca().legend(*args, **kwargs) - return ret +@docstring.copy_dedent(Axes.violinplot) +def violinplot( + dataset, positions=None, vert=True, widths=0.5, + showmeans=False, showextrema=True, showmedians=False, + points=100, bw_method=None, *, data=None): + return gca().violinplot( + dataset=dataset, positions=positions, vert=vert, + widths=widths, showmeans=showmeans, showextrema=showextrema, + showmedians=showmedians, points=points, bw_method=bw_method, + data=data) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.table) -def table(**kwargs): - ret = gca().table(**kwargs) - return ret +@docstring.copy_dedent(Axes.vlines) +def vlines( + x, ymin, ymax, colors='k', linestyles='solid', label='', *, + data=None, **kwargs): + return gca().vlines( + x=x, ymin=ymin, ymax=ymax, colors=colors, + linestyles=linestyles, label=label, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.text) -def text(x, y, s, fontdict=None, withdash=False, **kwargs): - ret = gca().text(x, y, s, fontdict=fontdict, withdash=withdash, **kwargs) - return ret +@docstring.copy_dedent(Axes.xcorr) +def xcorr( + x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, + maxlags=10, *, data=None, **kwargs): + return gca().xcorr( + x=x, y=y, normed=normed, detrend=detrend, usevlines=usevlines, + maxlags=maxlags, data=data, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.annotate) -def annotate(*args, **kwargs): - ret = gca().annotate(*args, **kwargs) - return ret +@docstring.copy_dedent(Axes._sci) +def sci(im): + return gca()._sci(im=im) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.ticklabel_format) -def ticklabel_format(**kwargs): - ret = gca().ticklabel_format(**kwargs) - return ret +@docstring.copy_dedent(Axes.set_title) +def title(label, fontdict=None, loc='center', pad=None, **kwargs): + return gca().set_title( + label=label, fontdict=fontdict, loc=loc, pad=pad, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.locator_params) -def locator_params(axis='both', tight=None, **kwargs): - ret = gca().locator_params(axis=axis, tight=tight, **kwargs) - return ret +@docstring.copy_dedent(Axes.set_xlabel) +def xlabel(xlabel, fontdict=None, labelpad=None, **kwargs): + return gca().set_xlabel( + xlabel=xlabel, fontdict=fontdict, labelpad=labelpad, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.tick_params) -def tick_params(axis='both', **kwargs): - ret = gca().tick_params(axis=axis, **kwargs) - return ret +@docstring.copy_dedent(Axes.set_ylabel) +def ylabel(ylabel, fontdict=None, labelpad=None, **kwargs): + return gca().set_ylabel( + ylabel=ylabel, fontdict=fontdict, labelpad=labelpad, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.margins) -def margins(*args, **kw): - ret = gca().margins(*args, **kw) - return ret +@docstring.copy_dedent(Axes.set_xscale) +def xscale(value, **kwargs): + return gca().set_xscale(value=value, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@docstring.copy_dedent(Axes.autoscale) -def autoscale(enable=True, axis='both', tight=None): - ret = gca().autoscale(enable=enable, axis=axis, tight=tight) - return ret +@docstring.copy_dedent(Axes.set_yscale) +def yscale(value, **kwargs): + return gca().set_yscale(value=value, **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def autumn(): @@ -3881,7 +2991,6 @@ def autumn(): """ set_cmap("autumn") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def bone(): """ @@ -3892,7 +3001,6 @@ def bone(): """ set_cmap("bone") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def cool(): """ @@ -3903,7 +3011,6 @@ def cool(): """ set_cmap("cool") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def copper(): """ @@ -3914,7 +3021,6 @@ def copper(): """ set_cmap("copper") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def flag(): """ @@ -3925,7 +3031,6 @@ def flag(): """ set_cmap("flag") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def gray(): """ @@ -3936,7 +3041,6 @@ def gray(): """ set_cmap("gray") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def hot(): """ @@ -3947,7 +3051,6 @@ def hot(): """ set_cmap("hot") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def hsv(): """ @@ -3958,7 +3061,6 @@ def hsv(): """ set_cmap("hsv") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def jet(): """ @@ -3969,7 +3071,6 @@ def jet(): """ set_cmap("jet") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def pink(): """ @@ -3980,7 +3081,6 @@ def pink(): """ set_cmap("pink") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def prism(): """ @@ -3991,7 +3091,6 @@ def prism(): """ set_cmap("prism") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def spring(): """ @@ -4002,7 +3101,6 @@ def spring(): """ set_cmap("spring") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def summer(): """ @@ -4013,7 +3111,6 @@ def summer(): """ set_cmap("summer") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def winter(): """ @@ -4024,7 +3121,6 @@ def winter(): """ set_cmap("winter") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def magma(): """ @@ -4035,7 +3131,6 @@ def magma(): """ set_cmap("magma") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def inferno(): """ @@ -4046,7 +3141,6 @@ def inferno(): """ set_cmap("inferno") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def plasma(): """ @@ -4057,7 +3151,6 @@ def plasma(): """ set_cmap("plasma") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def viridis(): """ @@ -4068,7 +3161,6 @@ def viridis(): """ set_cmap("viridis") - # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def nipy_spectral(): """ @@ -4078,22 +3170,4 @@ def nipy_spectral(): image if there is one. See ``help(colormaps)`` for more information. """ set_cmap("nipy_spectral") - - -# Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def spectral(): - """ - Set the colormap to "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. - """ - from matplotlib.cbook import warn_deprecated - warn_deprecated( - "2.0", - name="spectral", - obj_type="colormap" - ) - set_cmap("spectral") - _setup_pyplot_info_docstrings() diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index 92de37ecb89a..9b63e7734198 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -14,15 +14,11 @@ the Quiver code. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import math import weakref import numpy as np + from numpy import ma import matplotlib.collections as mcollections import matplotlib.transforms as transforms @@ -242,17 +238,20 @@ class QuiverKey(martist.Artist): valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'} pivot = {'N': 'middle', 'S': 'middle', 'E': 'tip', 'W': 'tail'} - def __init__(self, Q, X, Y, U, label, **kw): + def __init__(self, Q, X, Y, U, label, + *, angle=0, coordinates='axes', color=None, labelsep=0.1, + labelpos='N', labelcolor=None, fontproperties=None, + **kw): martist.Artist.__init__(self) self.Q = Q self.X = X self.Y = Y self.U = U - self.angle = kw.pop('angle', 0) - self.coord = kw.pop('coordinates', 'axes') - self.color = kw.pop('color', None) + self.angle = angle + self.coord = coordinates + self.color = color self.label = label - self._labelsep_inches = kw.pop('labelsep', 0.1) + self._labelsep_inches = labelsep self.labelsep = (self._labelsep_inches * Q.ax.figure.dpi) # try to prevent closure over the real self @@ -270,9 +269,9 @@ def on_dpi_change(fig): self._cid = Q.ax.figure.callbacks.connect('dpi_changed', on_dpi_change) - self.labelpos = kw.pop('labelpos', 'N') - self.labelcolor = kw.pop('labelcolor', None) - self.fontproperties = kw.pop('fontproperties', dict()) + self.labelpos = labelpos + self.labelcolor = labelcolor + self.fontproperties = fontproperties or dict() self.kw = kw _fp = self.fontproperties # boxprops = dict(facecolor='red') @@ -407,9 +406,9 @@ def _parse_args(*args): def _check_consistent_shapes(*arrays): - all_shapes = set(a.shape for a in arrays) + all_shapes = {a.shape for a in arrays} if len(all_shapes) != 1: - raise ValueError('The shapes of the passed in arrays do not match.') + raise ValueError('The shapes of the passed in arrays do not match') class Quiver(mcollections.PolyCollection): @@ -430,44 +429,44 @@ class Quiver(mcollections.PolyCollection): in the draw() method. """ - _PIVOT_VALS = ('tail', 'mid', 'middle', 'tip') + _PIVOT_VALS = ('tail', 'middle', 'tip') @docstring.Substitution(_quiver_doc) - def __init__(self, ax, *args, **kw): + def __init__(self, ax, *args, + scale=None, headwidth=3, headlength=5, headaxislength=4.5, + minshaft=1, minlength=1, units='width', scale_units=None, + angles='uv', width=None, color='k', pivot='tail', **kw): """ The constructor takes one required argument, an Axes instance, followed by the args and kwargs described - by the following pylab interface documentation: + by the following pyplot interface documentation: %s """ self.ax = ax X, Y, U, V, C = _parse_args(*args) self.X = X self.Y = Y - self.XY = np.hstack((X[:, np.newaxis], Y[:, np.newaxis])) + self.XY = np.column_stack((X, Y)) self.N = len(X) - self.scale = kw.pop('scale', None) - self.headwidth = kw.pop('headwidth', 3) - self.headlength = float(kw.pop('headlength', 5)) - self.headaxislength = kw.pop('headaxislength', 4.5) - self.minshaft = kw.pop('minshaft', 1) - self.minlength = kw.pop('minlength', 1) - self.units = kw.pop('units', 'width') - self.scale_units = kw.pop('scale_units', None) - self.angles = kw.pop('angles', 'uv') - self.width = kw.pop('width', None) - self.color = kw.pop('color', 'k') - - pivot = kw.pop('pivot', 'tail').lower() - # validate pivot - if pivot not in self._PIVOT_VALS: + self.scale = scale + self.headwidth = headwidth + self.headlength = float(headlength) + self.headaxislength = headaxislength + self.minshaft = minshaft + self.minlength = minlength + self.units = units + self.scale_units = scale_units + self.angles = angles + self.width = width + self.color = color + + if pivot.lower() == 'mid': + pivot = 'middle' + self.pivot = pivot.lower() + if self.pivot not in self._PIVOT_VALS: raise ValueError( 'pivot must be one of {keys}, you passed {inp}'.format( keys=self._PIVOT_VALS, inp=pivot)) - # normalize to 'middle' - if pivot == 'mid': - pivot = 'middle' - self.pivot = pivot self.transform = kw.pop('transform', ax.transData) kw.setdefault('facecolors', self.color) @@ -617,7 +616,7 @@ def _set_transform(self): def _angles_lengths(self, U, V, eps=1): xy = self.ax.transData.transform(self.XY) - uv = np.hstack((U[:, np.newaxis], V[:, np.newaxis])) + uv = np.column_stack((U, V)) xyp = self.ax.transData.transform(self.XY + eps * uv) dxy = xyp - xy angles = np.arctan2(dxy[:, 1], dxy[:, 0]) @@ -626,7 +625,7 @@ def _angles_lengths(self, U, V, eps=1): def _make_verts(self, U, V, angles): uv = (U + V * 1j) - str_angles = angles if isinstance(angles, six.string_types) else '' + str_angles = angles if isinstance(angles, str) else '' if str_angles == 'xy' and self.scale_units == 'xy': # Here eps is 1 so that if we get U, V by diffing # the X, Y arrays, the vectors will connect the @@ -673,8 +672,7 @@ def _make_verts(self, U, V, angles): theta = ma.masked_invalid(np.deg2rad(angles)).filled(0) theta = theta.reshape((-1, 1)) # for broadcasting xy = (X + Y * 1j) * np.exp(1j * theta) * self.width - xy = xy[:, :, np.newaxis] - XY = np.concatenate((xy.real, xy.imag), axis=2) + XY = np.stack((xy.real, xy.imag), axis=2) if self.Umask is not ma.nomask: XY = ma.array(XY) XY[self.Umask] = ma.masked @@ -907,23 +905,26 @@ class Barbs(mcollections.PolyCollection): # 1 triangle and a series of lines. It works fine as far as I can tell # however. @docstring.interpd - def __init__(self, ax, *args, **kw): + def __init__(self, ax, *args, + pivot='tip', length=7, barbcolor=None, flagcolor=None, + sizes=None, fill_empty=False, barb_increments=None, + rounding=True, flip_barb=False, **kw): """ The constructor takes one required argument, an Axes instance, followed by the args and kwargs described - by the following pylab interface documentation: + by the following pyplot interface documentation: %(barbs_doc)s """ - self._pivot = kw.pop('pivot', 'tip') - self._length = kw.pop('length', 7) - barbcolor = kw.pop('barbcolor', None) - flagcolor = kw.pop('flagcolor', None) - self.sizes = kw.pop('sizes', dict()) - self.fill_empty = kw.pop('fill_empty', False) - self.barb_increments = kw.pop('barb_increments', dict()) - self.rounding = kw.pop('rounding', True) - self.flip = kw.pop('flip_barb', False) + self.sizes = sizes or dict() + self.fill_empty = fill_empty + self.barb_increments = barb_increments or dict() + self.rounding = rounding + self.flip = flip_barb transform = kw.pop('transform', ax.transData) + self._pivot = pivot + self._length = length + barbcolor = barbcolor + flagcolor = flagcolor # Flagcolor and barbcolor provide convenience parameters for # setting the facecolor and edgecolor, respectively, of the barb @@ -952,7 +953,7 @@ def __init__(self, ax, *args, **kw): x, y, u, v, c = _parse_args(*args) self.x = x self.y = y - xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) + xy = np.column_stack((x, y)) # Make a collection barb_size = self._length ** 2 / 4 # Empirically determined @@ -1171,7 +1172,7 @@ def set_UVC(self, U, V, C=None): self.set_array(c) # Update the offsets in case the masked data changed - xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) + xy = np.column_stack((x, y)) self._offsets = xy self.stale = True @@ -1181,14 +1182,16 @@ def set_offsets(self, xy): in and actually sets version masked as appropriate for the existing U/V data. *offsets* should be a sequence. - ACCEPTS: sequence of pairs of floats + Parameters + ---------- + offsets : sequence of pairs of floats """ self.x = xy[:, 0] self.y = xy[:, 1] x, y, u, v = delete_masked_points(self.x.ravel(), self.y.ravel(), self.u, self.v) _check_consistent_shapes(x, y, u, v) - xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) + xy = np.column_stack((x, y)) mcollections.PolyCollection.set_offsets(self, xy) self.stale = True diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index f8d5ad5036d5..4e57b29205b9 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -13,19 +13,15 @@ parameter set listed here should also be visited to the :file:`matplotlibrc.template` in matplotlib's root source directory. """ -from __future__ import absolute_import, division, print_function - -import six - -from collections import Iterable, Mapping +from collections.abc import Iterable, Mapping from functools import reduce import operator import os -import warnings import re +import sys -from matplotlib import cbook, testing -from matplotlib.cbook import mplDeprecation, deprecated, ls_mapper +from matplotlib import cbook +from matplotlib.cbook import ls_mapper from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import is_color_like @@ -35,17 +31,14 @@ # The capitalized forms are needed for ipython at present; this may # change for later versions. -interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'MacOSX', - 'Qt4Agg', 'Qt5Agg', 'TkAgg', 'WX', 'WXAgg', - 'GTK3Cairo', 'GTK3Agg', 'WebAgg', 'nbAgg'] -interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'GTK3Agg', 'GTK3Cairo', +interactive_bk = ['GTK3Agg', 'GTK3Cairo', 'MacOSX', 'nbAgg', 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', 'WXCairo'] -non_interactive_bk = ['agg', 'cairo', 'gdk', +non_interactive_bk = ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template'] all_backends = interactive_bk + non_interactive_bk @@ -68,13 +61,13 @@ def __call__(self, s): s = s.lower() if s in self.valid: return self.valid[s] - raise ValueError('Unrecognized %s string "%s": valid strings are %s' - % (self.key, s, list(six.itervalues(self.valid)))) + raise ValueError('Unrecognized %s string %r: valid strings are %s' + % (self.key, s, list(self.valid.values()))) def _listify_validator(scalar_validator, allow_stringlist=False): def f(s): - if isinstance(s, six.string_types): + if isinstance(s, str): try: return [scalar_validator(v.strip()) for v in s.split(',') if v.strip()] @@ -95,7 +88,7 @@ def f(s): # from the original validate_stringlist()), while allowing # any non-string/text scalar values such as numbers and arrays. return [scalar_validator(v) for v in s - if not isinstance(v, six.string_types) or v] + if not isinstance(v, str) or v] else: raise ValueError("{!r} must be of type: string or non-dictionary " "iterable".format(s)) @@ -124,7 +117,7 @@ def validate_path_exists(s): def validate_bool(b): """Convert b to a boolean or raise""" - if isinstance(b, six.string_types): + if isinstance(b, str): b = b.lower() if b in ('t', 'y', 'yes', 'on', 'true', '1', 1, True): return True @@ -136,7 +129,7 @@ def validate_bool(b): def validate_bool_maybe_none(b): 'Convert b to a boolean or raise' - if isinstance(b, six.string_types): + if isinstance(b, str): b = b.lower() if b is None or b == 'none': return None @@ -148,15 +141,6 @@ def validate_bool_maybe_none(b): raise ValueError('Could not convert "%s" to boolean' % b) -def deprecate_axes_hold(value): - if value is None: - return None # converted to True where accessed in figure.py, - # axes/_base.py - warnings.warn("axes.hold is deprecated, will be removed in 3.0", - mplDeprecation) - return validate_bool(value) - - def validate_float(s): """convert s to float or raise""" try: @@ -195,7 +179,7 @@ def validate_axisbelow(s): try: return validate_bool(s) except ValueError: - if isinstance(s, six.string_types): + if isinstance(s, str): s = s.lower() if s.startswith('line'): return 'line' @@ -250,22 +234,23 @@ def validate_fonttype(s): raise ValueError( 'Supported Postscript/PDF font types are %s' % list(fonttypes)) else: - if fonttype not in six.itervalues(fonttypes): + if fonttype not in fonttypes.values(): raise ValueError( 'Supported Postscript/PDF font types are %s' % - list(six.itervalues(fonttypes))) + list(fonttypes.values())) return fonttype _validate_standard_backends = ValidateInStrings( 'backend', all_backends, ignorecase=True) +_auto_backend_sentinel = object() def validate_backend(s): - if s.startswith('module://'): - return s - else: - return _validate_standard_backends(s) + backend = ( + s if s is _auto_backend_sentinel or s.startswith("module://") + else _validate_standard_backends(s)) + return backend def validate_qt4(s): @@ -302,7 +287,7 @@ def __init__(self, n=None, allow_none=False): def __call__(self, s): """return a seq of n floats or raise""" - if isinstance(s, six.string_types): + if isinstance(s, str): s = [x.strip() for x in s.split(',')] err_msg = _str_err_msg else: @@ -326,7 +311,7 @@ def __init__(self, n=None): def __call__(self, s): """return a seq of n ints or raise""" - if isinstance(s, six.string_types): + if isinstance(s, str): s = [x.strip() for x in s.split(',')] err_msg = _str_err_msg else: @@ -362,7 +347,7 @@ def validate_color_for_prop_cycle(s): if match is not None: raise ValueError('Can not put cycle reference ({cn!r}) in ' 'prop_cycler'.format(cn=s)) - elif isinstance(s, six.string_types): + elif isinstance(s, str): match = re.match('^C[0-9]$', s) if match is not None: raise ValueError('Can not put cycle reference ({cn!r}) in ' @@ -378,7 +363,7 @@ def validate_color(s): except AttributeError: pass - if isinstance(s, six.string_types): + if isinstance(s, str): if len(s) == 6 or len(s) == 8: stmp = '#' + s if is_color_like(stmp): @@ -412,7 +397,7 @@ def validate_color(s): validate_colorlist.__doc__ = 'return a list of colorspecs' def validate_string(s): - if isinstance(s, (str, six.text_type)): + if isinstance(s, (str, str)): # Always leave str as str and unicode as unicode return s else: @@ -434,10 +419,17 @@ def validate_aspect(s): raise ValueError('not a valid aspect specification') +def validate_fontsize_None(s): + if s is None or s == 'None': + return None + else: + return validate_fontsize(s) + + def validate_fontsize(s): fontsizes = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'smaller', 'larger'] - if isinstance(s, six.string_types): + if isinstance(s, str): s = s.lower() if s in fontsizes: return s @@ -507,7 +499,7 @@ def update_savefig_format(value): def validate_ps_distiller(s): - if isinstance(s, six.string_types): + if isinstance(s, str): s = s.lower() if s in ('none', None): return None @@ -537,27 +529,54 @@ def validate_ps_distiller(s): _validate_negative_linestyle = ValidateInStrings('negative_linestyle', ['solid', 'dashed'], ignorecase=True) +def validate_markevery(s): + """ + Validate the markevery property of a Line2D object. + Parameters + ---------- + s : None, int, float, slice, length-2 tuple of ints, + length-2 tuple of floats, list of ints -@deprecated('2.1', - addendum=(" See 'validate_negative_linestyle_legacy' " + - "deprecation warning for more information.")) -def validate_negative_linestyle(s): - return _validate_negative_linestyle(s) + Returns + ------- + s : None, int, float, slice, length-2 tuple of ints, + length-2 tuple of floats, list of ints + """ + # Validate s against type slice + if isinstance(s, slice): + return s + # Validate s against type tuple + if isinstance(s, tuple): + tupMaxLength = 2 + tupType = type(s[0]) + if len(s) != tupMaxLength: + raise TypeError("'markevery' tuple must have a length of " + "%d" % (tupMaxLength)) + if tupType is int and not all(isinstance(e, int) for e in s): + raise TypeError("'markevery' tuple with first element of " + "type int must have all elements of type " + "int") + if tupType is float and not all(isinstance(e, float) for e in s): + raise TypeError("'markevery' tuple with first element of " + "type float must have all elements of type " + "float") + if tupType is not float and tupType is not int: + raise TypeError("'markevery' tuple contains an invalid type") + # Validate s against type list + elif isinstance(s, list): + if not all(isinstance(e, int) for e in s): + raise TypeError("'markevery' list must have all elements of " + "type int") + # Validate s against type float int and None + elif not isinstance(s, (float, int)): + if s is not None: + raise TypeError("'markevery' is of an invalid type") -@deprecated('2.1', - addendum=(" The 'contour.negative_linestyle' rcParam now " + - "follows the same validation as the other rcParams " + - "that are related to line style.")) -def validate_negative_linestyle_legacy(s): - try: - res = validate_negative_linestyle(s) - return res - except ValueError: - dashes = validate_nseq_float(2)(s) - return (0, dashes) # (offset, (solid, blank)) + return s +validate_markeverylist = _listify_validator(validate_markevery) validate_legend_loc = ValidateInStrings( 'legend_loc', @@ -610,7 +629,7 @@ def validate_hinting(s): ['html5', 'jshtml', 'none']) def validate_bbox(s): - if isinstance(s, six.string_types): + if isinstance(s, str): s = s.lower() if s == 'tight': return s @@ -623,11 +642,11 @@ def validate_bbox(s): return s def validate_sketch(s): - if isinstance(s, six.string_types): + if isinstance(s, str): s = s.lower() if s == 'none' or s is None: return None - if isinstance(s, six.string_types): + if isinstance(s, str): result = tuple([float(v.strip()) for v in s.split(',')]) elif isinstance(s, (list, tuple)): result = tuple([float(v) for v in s]) @@ -676,7 +695,7 @@ def validate_hatch(s): characters: ``\\ / | - + * . x o O``. """ - if not isinstance(s, six.string_types): + if not isinstance(s, str): raise ValueError("Hatch pattern must be a string") unknown = set(s) - {'\\', '/', '|', '-', '+', '*', '.', 'x', 'o', 'O'} if unknown: @@ -699,6 +718,7 @@ def validate_hatch(s): 'markersize': validate_floatlist, 'markeredgewidth': validate_floatlist, 'markeredgecolor': validate_colorlist, + 'markevery': validate_markeverylist, 'alpha': validate_floatlist, 'marker': validate_stringlist, 'hatch': validate_hatchlist, @@ -786,7 +806,7 @@ def cycler(*args, **kwargs): elif len(args) > 2: raise TypeError("No more than 2 positional arguments allowed") else: - pairs = six.iteritems(kwargs) + pairs = kwargs.items() validated = [] for prop, vals in pairs: @@ -804,7 +824,7 @@ def cycler(*args, **kwargs): def validate_cycler(s): 'return a Cycler object from a string repr or the object itself' - if isinstance(s, six.string_types): + if isinstance(s, str): try: # TODO: We might want to rethink this... # While I think I have it quite locked down, @@ -868,7 +888,7 @@ def validate_cycler(s): def validate_hist_bins(s): - if isinstance(s, six.string_types) and s == 'auto': + if cbook._str_equal(s, "auto"): return s try: return int(s) @@ -887,7 +907,7 @@ def validate_animation_writer_path(p): # Make sure it's a string and then figure out if the animations # are already loaded and reset the writers (which will validate # the path on next call) - if not isinstance(p, six.string_types): + if not isinstance(p, str): raise ValueError("path must be a (unicode) string") from sys import modules # set dirty, so that the next call to the registry will re-evaluate @@ -910,11 +930,10 @@ def validate_webagg_address(s): # A validator dedicated to the named line styles, based on the items in # ls_mapper, and a list of possible strings read from Line2D.set_linestyle -_validate_named_linestyle = ValidateInStrings('linestyle', - list(six.iterkeys(ls_mapper)) + - list(six.itervalues(ls_mapper)) + - ['None', 'none', ' ', ''], - ignorecase=True) +_validate_named_linestyle = ValidateInStrings( + 'linestyle', + [*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''], + ignorecase=True) def _validate_linestyle(ls): @@ -922,25 +941,13 @@ def _validate_linestyle(ls): A validator for all possible line styles, the named ones *and* the on-off ink sequences. """ - # Look first for a valid named line style, like '--' or 'solid' - if isinstance(ls, six.string_types): - try: - return _validate_named_linestyle(ls) - except (UnicodeDecodeError, KeyError): - # On Python 2, string-like *ls*, like for example - # 'solid'.encode('utf-16'), may raise a unicode error. - raise ValueError("the linestyle string {!r} is not a valid " - "string.".format(ls)) - - if isinstance(ls, (bytes, bytearray)): - # On Python 2, a string-like *ls* should already have lead to a - # successful return or to raising an exception. On Python 3, we have - # to manually raise an exception in the case of a byte-like *ls*. - # Otherwise, if *ls* is of even-length, it will be passed to the - # instance of validate_nseq_float, which will return an absurd on-off - # ink sequence... - raise ValueError("linestyle {!r} neither looks like an on-off ink " - "sequence nor a valid string.".format(ls)) + # Look first for a valid named line style, like '--' or 'solid' Also + # includes bytes(-arrays) here (they all fail _validate_named_linestyle); + # otherwise, if *ls* is of even-length, it will be passed to the instance + # of validate_nseq_float, which will return an absurd on-off ink + # sequence... + if isinstance(ls, (str, bytes, bytearray)): + return _validate_named_linestyle(ls) # Look for an on-off ink sequence (in points) *of even length*. # Offset is set to None. @@ -960,9 +967,8 @@ def _validate_linestyle(ls): # a map from key -> value, converter defaultParams = { - 'backend': ['Agg', validate_backend], # agg is certainly - # present - 'backend_fallback': [True, validate_bool], # agg is certainly present + 'backend': [_auto_backend_sentinel, validate_backend], + 'backend_fallback': [True, validate_bool], 'backend.qt4': [None, validate_qt4], 'backend.qt5': [None, validate_qt5], 'webagg.port': [8988, validate_int], @@ -985,6 +991,8 @@ def _validate_linestyle(ls): 'lines.linestyle': ['-', _validate_linestyle], # solid line 'lines.color': ['C0', validate_color], # first color in color cycle 'lines.marker': ['None', validate_string], # marker name + 'lines.markerfacecolor': ['auto', validate_color_or_auto], # default color + 'lines.markeredgecolor': ['auto', validate_color_or_auto], # default color 'lines.markeredgewidth': [1.0, validate_float], 'lines.markersize': [6, validate_float], # markersize, in points 'lines.antialiased': [True, validate_bool], # antialiased (no jaggies) @@ -1003,13 +1011,13 @@ def _validate_linestyle(ls): ## patch props 'patch.linewidth': [1.0, validate_float], # line width in points - 'patch.edgecolor': ['k', validate_color], + 'patch.edgecolor': ['black', validate_color], 'patch.force_edgecolor' : [False, validate_bool], 'patch.facecolor': ['C0', validate_color], # first color in cycle 'patch.antialiased': [True, validate_bool], # antialiased (no jaggies) ## hatch props - 'hatch.color': ['k', validate_color], + 'hatch.color': ['black', validate_color], 'hatch.linewidth': [1.0, validate_float], ## Histogram properties @@ -1027,23 +1035,23 @@ def _validate_linestyle(ls): 'boxplot.showfliers': [True, validate_bool], 'boxplot.meanline': [False, validate_bool], - 'boxplot.flierprops.color': ['k', validate_color], + 'boxplot.flierprops.color': ['black', validate_color], 'boxplot.flierprops.marker': ['o', validate_string], 'boxplot.flierprops.markerfacecolor': ['none', validate_color_or_auto], - 'boxplot.flierprops.markeredgecolor': ['k', validate_color], + 'boxplot.flierprops.markeredgecolor': ['black', validate_color], 'boxplot.flierprops.markersize': [6, validate_float], 'boxplot.flierprops.linestyle': ['none', _validate_linestyle], 'boxplot.flierprops.linewidth': [1.0, validate_float], - 'boxplot.boxprops.color': ['k', validate_color], + 'boxplot.boxprops.color': ['black', validate_color], 'boxplot.boxprops.linewidth': [1.0, validate_float], 'boxplot.boxprops.linestyle': ['-', _validate_linestyle], - 'boxplot.whiskerprops.color': ['k', validate_color], + 'boxplot.whiskerprops.color': ['black', validate_color], 'boxplot.whiskerprops.linewidth': [1.0, validate_float], 'boxplot.whiskerprops.linestyle': ['-', _validate_linestyle], - 'boxplot.capprops.color': ['k', validate_color], + 'boxplot.capprops.color': ['black', validate_color], 'boxplot.capprops.linewidth': [1.0, validate_float], 'boxplot.capprops.linestyle': ['-', _validate_linestyle], @@ -1081,7 +1089,7 @@ def _validate_linestyle(ls): 'font.cursive': [['Apple Chancery', 'Textile', 'Zapf Chancery', 'Sand', 'Script MT', 'Felipa', 'cursive'], validate_stringlist], - 'font.fantasy': [['Comic Sans MS', 'Chicago', 'Charcoal', 'Impact' + 'font.fantasy': [['Comic Sans MS', 'Chicago', 'Charcoal', 'Impact', 'Western', 'Humor Sans', 'xkcd', 'fantasy'], validate_stringlist], 'font.monospace': [['DejaVu Sans Mono', 'Bitstream Vera Sans Mono', @@ -1091,9 +1099,9 @@ def _validate_linestyle(ls): validate_stringlist], # text props - 'text.color': ['k', validate_color], # black + 'text.color': ['black', validate_color], 'text.usetex': [False, validate_bool], - 'text.latex.unicode': [False, validate_bool], + 'text.latex.unicode': [True, validate_bool], 'text.latex.preamble': [[''], validate_stringlist], 'text.latex.preview': [False, validate_bool], 'text.dvipnghack': [None, validate_bool_maybe_none], @@ -1130,9 +1138,8 @@ def _validate_linestyle(ls): # axes props 'axes.axisbelow': ['line', validate_axisbelow], - 'axes.hold': [None, deprecate_axes_hold], - 'axes.facecolor': ['w', validate_color], # background color; white - 'axes.edgecolor': ['k', validate_color], # edge color; black + 'axes.facecolor': ['white', validate_color], # background color + 'axes.edgecolor': ['black', validate_color], # edge color 'axes.linewidth': [0.8, validate_float], # edge linewidth 'axes.spines.left': [True, validate_bool], # Set visibility of axes @@ -1155,7 +1162,7 @@ def _validate_linestyle(ls): # x any y labels 'axes.labelpad': [4.0, validate_float], # space between label and axis 'axes.labelweight': ['normal', validate_string], # fontsize of the x any y labels - 'axes.labelcolor': ['k', validate_color], # color of axis label + 'axes.labelcolor': ['black', validate_color], # color of axis label 'axes.formatter.limits': [[-7, 7], validate_nseq_int(2)], # use scientific notation if log10 # of the axis range is smaller than the @@ -1212,6 +1219,7 @@ def _validate_linestyle(ls): # the number of points in the legend line for scatter 'legend.scatterpoints': [1, validate_int], 'legend.fontsize': ['medium', validate_fontsize], + 'legend.title_fontsize': [None, validate_fontsize_None], # the relative size of legend markers vs. original 'legend.markerscale': [1.0, validate_float], 'legend.shadow': [False, validate_bool], @@ -1248,7 +1256,7 @@ def _validate_linestyle(ls): 'xtick.minor.width': [0.6, validate_float], # minor xtick width in points 'xtick.major.pad': [3.5, validate_float], # distance to label in points 'xtick.minor.pad': [3.4, validate_float], # distance to label in points - 'xtick.color': ['k', validate_color], # color of the xtick labels + 'xtick.color': ['black', validate_color], # color of the xtick labels 'xtick.minor.visible': [False, validate_bool], # visibility of the x axis minor ticks 'xtick.minor.top': [True, validate_bool], # draw x axis top minor ticks 'xtick.minor.bottom': [True, validate_bool], # draw x axis bottom minor ticks @@ -1270,7 +1278,7 @@ def _validate_linestyle(ls): 'ytick.minor.width': [0.6, validate_float], # minor ytick width in points 'ytick.major.pad': [3.5, validate_float], # distance to label in points 'ytick.minor.pad': [3.4, validate_float], # distance to label in points - 'ytick.color': ['k', validate_color], # color of the ytick labels + 'ytick.color': ['black', validate_color], # color of the ytick labels 'ytick.minor.visible': [False, validate_bool], # visibility of the y axis minor ticks 'ytick.minor.left': [True, validate_bool], # draw y axis left minor ticks 'ytick.minor.right': [True, validate_bool], # draw y axis right minor ticks @@ -1297,8 +1305,8 @@ def _validate_linestyle(ls): # figure size in inches: width by height 'figure.figsize': [[6.4, 4.8], validate_nseq_float(2)], 'figure.dpi': [100, validate_float], # DPI - 'figure.facecolor': ['w', validate_color], # facecolor; white - 'figure.edgecolor': ['w', validate_color], # edgecolor; white + 'figure.facecolor': ['white', validate_color], + 'figure.edgecolor': ['white', validate_color], 'figure.frameon': [True, validate_bool], 'figure.autolayout': [False, validate_bool], 'figure.max_open_warning': [20, validate_int], @@ -1331,8 +1339,8 @@ def _validate_linestyle(ls): ## Saving figure's properties 'savefig.dpi': ['figure', validate_dpi], # DPI - 'savefig.facecolor': ['w', validate_color], # facecolor; white - 'savefig.edgecolor': ['w', validate_color], # edgecolor; white + 'savefig.facecolor': ['white', validate_color], + 'savefig.edgecolor': ['white', validate_color], 'savefig.frameon': [True, validate_bool], 'savefig.orientation': ['portrait', validate_orientation], # edgecolor; #white @@ -1405,6 +1413,8 @@ def _validate_linestyle(ls): 'keymap.yscale': [['l'], validate_stringlist], 'keymap.xscale': [['k', 'L'], validate_stringlist], 'keymap.all_axes': [['a'], validate_stringlist], + 'keymap.help': [['f1'], validate_stringlist], + 'keymap.copy': [['ctrl+c', 'cmd+c'], validate_stringlist], # sample data 'examples.directory': ['', validate_string], diff --git a/lib/matplotlib/sankey.py b/lib/matplotlib/sankey.py index 88def21ce631..72460f4a0ed2 100644 --- a/lib/matplotlib/sankey.py +++ b/lib/matplotlib/sankey.py @@ -1,15 +1,13 @@ """ Module for creating Sankey diagrams using matplotlib """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six import logging -from six.moves import zip +from types import SimpleNamespace + import numpy as np -from matplotlib.cbook import iterable, Bunch +from matplotlib.cbook import iterable from matplotlib.path import Path from matplotlib.patches import PathPatch from matplotlib.transforms import Affine2D @@ -120,7 +118,7 @@ def __init__(self, ax=None, scale=1.0, unit='', format='%G', gap=0.25, **Examples:** - .. plot:: gallery/api/sankey_basics.py + .. plot:: gallery/specialty_plots/sankey_basics.py """ # Check the arguments. if gap < 0: @@ -780,8 +778,9 @@ def _get_angle(a, r): # where either could determine the margins (e.g., arrow shoulders). # Add this diagram as a subdiagram. - self.diagrams.append(Bunch(patch=patch, flows=flows, angles=angles, - tips=tips, text=text, texts=texts)) + self.diagrams.append( + SimpleNamespace(patch=patch, flows=flows, angles=angles, tips=tips, + text=text, texts=texts)) # Allow a daisy-chained call structure (see docstring for the class). return self diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 357aff9fc210..c4c0cdd92851 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -1,8 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import numpy as np from numpy import ma @@ -225,7 +220,7 @@ def __init__(self, axis, **kwargs): *basex*/*basey*: The base of the logarithm - *nonposx*/*nonposy*: ['mask' | 'clip' ] + *nonposx*/*nonposy*: {'mask', 'clip'} non-positive values in *x* or *y* can be masked as invalid, or clipped to a very small positive number @@ -512,7 +507,7 @@ class LogitScale(ScaleBase): def __init__(self, axis, nonpos='mask'): """ - *nonpos*: ['mask' | 'clip' ] + *nonpos*: {'mask', 'clip'} values beyond ]0, 1[ can be masked as invalid, or clipped to a number very close to 0 or 1 """ diff --git a/lib/matplotlib/sphinxext/__init__.py b/lib/matplotlib/sphinxext/__init__.py index 800d82e7ee00..e69de29bb2d1 100644 --- a/lib/matplotlib/sphinxext/__init__.py +++ b/lib/matplotlib/sphinxext/__init__.py @@ -1,2 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) diff --git a/lib/matplotlib/sphinxext/mathmpl.py b/lib/matplotlib/sphinxext/mathmpl.py index 26968cb03e54..b1b934304a76 100644 --- a/lib/matplotlib/sphinxext/mathmpl.py +++ b/lib/matplotlib/sphinxext/mathmpl.py @@ -1,15 +1,11 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +import hashlib import os import sys -from hashlib import md5 +import warnings from docutils import nodes from docutils.parsers.rst import directives -import warnings +import sphinx from matplotlib import rcParams from matplotlib.mathtext import MathTextParser @@ -55,7 +51,7 @@ def latex2png(latex, filename, fontset='cm'): depth = mathtext_parser.to_png(filename, latex, dpi=100) except: warnings.warn("Could not render math expression %s" % latex, - Warning) + Warning, stacklevel=2) depth = 0 rcParams['mathtext.fontset'] = orig_fontset sys.stdout.write("#") @@ -66,7 +62,7 @@ def latex2png(latex, filename, fontset='cm'): def latex2html(node, source): inline = isinstance(node.parent, nodes.TextElement) latex = node['latex'] - name = 'math-%s' % md5(latex.encode()).hexdigest()[-10:] + name = 'math-%s' % hashlib.md5(latex.encode()).hexdigest()[-10:] destdir = os.path.join(setup.app.builder.outdir, '_images', 'mathmpl') if not os.path.exists(destdir): @@ -115,9 +111,13 @@ def depart_latex_math_latex(self, node): app.add_node(latex_math, html=(visit_latex_math_html, depart_latex_math_html), latex=(visit_latex_math_latex, depart_latex_math_latex)) - app.add_role('math', math_role) - app.add_directive('math', math_directive, + app.add_role('mathmpl', math_role) + app.add_directive('mathmpl', math_directive, True, (0, 0, 0), **options_spec) + if sphinx.version_info < (1, 8): + app.add_role('math', math_role) + app.add_directive('math', math_directive, + True, (0, 0, 0), **options_spec) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} return metadata diff --git a/lib/matplotlib/sphinxext/only_directives.py b/lib/matplotlib/sphinxext/only_directives.py deleted file mode 100644 index 0a5ed70f800e..000000000000 --- a/lib/matplotlib/sphinxext/only_directives.py +++ /dev/null @@ -1,75 +0,0 @@ -# -# A pair of directives for inserting content that will only appear in -# either html or latex. -# - -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -from docutils.nodes import Body, Element - - -class only_base(Body, Element): - def dont_traverse(self, *args, **kwargs): - return [] - -class html_only(only_base): - pass - -class latex_only(only_base): - pass - -def run(content, node_class, state, content_offset): - text = '\n'.join(content) - node = node_class(text) - state.nested_parse(content, content_offset, node) - return [node] - -def html_only_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return run(content, html_only, state, content_offset) - -def latex_only_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return run(content, latex_only, state, content_offset) - -def builder_inited(app): - if app.builder.name == 'html': - latex_only.traverse = only_base.dont_traverse - else: - html_only.traverse = only_base.dont_traverse - - -def setup(app): - app.add_directive('htmlonly', html_only_directive, True, (0, 0, 0)) - app.add_directive('latexonly', latex_only_directive, True, (0, 0, 0)) - - # This will *really* never see the light of day As it turns out, - # this results in "broken" image nodes since they never get - # processed, so best not to do this. - # app.connect('builder-inited', builder_inited) - - # Add visit/depart methods to HTML-Translator: - def visit_perform(self, node): - pass - - def depart_perform(self, node): - pass - - def visit_ignore(self, node): - node.children = [] - - def depart_ignore(self, node): - node.children = [] - - app.add_node(html_only, - html=(visit_perform, depart_perform), - latex=(visit_ignore, depart_ignore)) - app.add_node(latex_only, - latex=(visit_perform, depart_perform), - html=(visit_ignore, depart_ignore)) - - metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} - return metadata diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 434bc50aee29..c7b0749a0715 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -134,35 +134,29 @@ plot_template Provide a customized template for preparing restructured text. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) -import six -from six.moves import xrange - -import sys, os, shutil, io, re, textwrap +import contextlib +from io import StringIO +import itertools +import os from os.path import relpath +from pathlib import Path +import re +import shutil +import sys +import textwrap import traceback import warnings -if not six.PY3: - import cStringIO - from docutils.parsers.rst import directives from docutils.parsers.rst.directives.images import Image align = Image.align import sphinx -sphinx_version = sphinx.__version__.split(".") -# The split is necessary for sphinx beta versions where the string is -# '6b1' -sphinx_version = tuple([int(re.split('[^0-9]', x)[0]) - for x in sphinx_version[:2]]) - import jinja2 # Sphinx dependency. import matplotlib -import matplotlib.cbook as cbook +from matplotlib.backend_bases import FigureManagerBase try: with warnings.catch_warnings(record=True): warnings.simplefilter("error", UserWarning) @@ -172,7 +166,7 @@ plt.switch_backend("Agg") else: import matplotlib.pyplot as plt -from matplotlib import _pylab_helpers +from matplotlib import _pylab_helpers, cbook __version__ = 2 @@ -222,7 +216,7 @@ def mark_plot_labels(app, document): the "htmlonly" (or "latexonly") node to the actual figure node itself. """ - for name, explicit in six.iteritems(document.nametypes): + for name, explicit in document.nametypes.items(): if not explicit: continue labelid = document.nameids[name] @@ -277,7 +271,7 @@ def setup(app): app.add_config_value('plot_working_directory', None, True) app.add_config_value('plot_template', None, True) - app.connect(str('doctree-read'), mark_plot_labels) + app.connect('doctree-read', mark_plot_labels) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} return metadata @@ -346,6 +340,7 @@ def remove_coding(text): r""" Remove the coding comment, which six.exec\_ doesn't like. """ + cbook.warn_deprecated('3.0', name='remove_coding', removal='3.1') sub_re = re.compile(r"^#\s*-\*-\s*coding:\s*.*-\*-$", flags=re.MULTILINE) return sub_re.sub("", text) @@ -419,7 +414,7 @@ def remove_coding(text): """ exception_template = """ -.. htmlonly:: +.. only:: html [`source code <%(linkdir)s/%(basename)s.py>`__] @@ -467,11 +462,7 @@ def run_code(code, code_path, ns=None, function_name=None): # Change the working directory to the directory of the example, so # it can get at its data files, if any. Add its path to sys.path # so it can import any helper modules sitting beside it. - if six.PY2: - pwd = os.getcwdu() - else: - pwd = os.getcwd() - old_sys_path = list(sys.path) + pwd = os.getcwd() if setup.config.plot_working_directory is not None: try: os.chdir(setup.config.plot_working_directory) @@ -483,54 +474,36 @@ def run_code(code, code_path, ns=None, function_name=None): raise TypeError(str(err) + '\n`plot_working_directory` option in ' 'Sphinx configuration file must be a string or ' 'None') - sys.path.insert(0, setup.config.plot_working_directory) elif code_path is not None: dirname = os.path.abspath(os.path.dirname(code_path)) os.chdir(dirname) - sys.path.insert(0, dirname) - - # Reset sys.argv - old_sys_argv = sys.argv - sys.argv = [code_path] - - # Redirect stdout - stdout = sys.stdout - if six.PY3: - sys.stdout = io.StringIO() - else: - sys.stdout = cStringIO.StringIO() - - # Assign a do-nothing print function to the namespace. There - # doesn't seem to be any other way to provide a way to (not) print - # that works correctly across Python 2 and 3. - def _dummy_print(*arg, **kwarg): - pass - try: + with cbook._setattr_cm( + sys, argv=[code_path], path=[os.getcwd(), *sys.path]), \ + contextlib.redirect_stdout(StringIO()): try: code = unescape_doctest(code) if ns is None: ns = {} if not ns: if setup.config.plot_pre_code is None: - six.exec_(six.text_type("import numpy as np\n" + - "from matplotlib import pyplot as plt\n"), ns) + exec('import numpy as np\n' + 'from matplotlib import pyplot as plt\n', ns) else: - six.exec_(six.text_type(setup.config.plot_pre_code), ns) - ns['print'] = _dummy_print + exec(str(setup.config.plot_pre_code), ns) if "__main__" in code: - six.exec_("__name__ = '__main__'", ns) - code = remove_coding(code) - six.exec_(code, ns) - if function_name is not None: - six.exec_(function_name + "()", ns) + ns['__name__'] = '__main__' + + # Patch out non-interactive show() to avoid triggering a warning. + with cbook._setattr_cm(FigureManagerBase, show=lambda self: None): + exec(code, ns) + if function_name is not None: + exec(function_name + "()", ns) + except (Exception, SystemExit) as err: raise PlotError(traceback.format_exc()) - finally: - os.chdir(pwd) - sys.argv = old_sys_argv - sys.path[:] = old_sys_path - sys.stdout = stdout + finally: + os.chdir(pwd) return ns @@ -545,19 +518,14 @@ def get_plot_formats(config): default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200} formats = [] plot_formats = config.plot_formats - if isinstance(plot_formats, six.string_types): - # String Sphinx < 1.3, Split on , to mimic - # Sphinx 1.3 and later. Sphinx 1.3 always - # returns a list. - plot_formats = plot_formats.split(',') for fmt in plot_formats: - if isinstance(fmt, six.string_types): + if isinstance(fmt, str): if ':' in fmt: suffix, dpi = fmt.split(':') formats.append((str(suffix), int(dpi))) else: formats.append((fmt, default_dpi.get(fmt, 80))) - elif type(fmt) in (tuple, list) and len(fmt) == 2: + elif isinstance(fmt, (tuple, list)) and len(fmt) == 2: formats.append((str(fmt[0]), int(fmt[1]))) else: raise PlotError('invalid image format "%r" in plot_formats' % fmt) @@ -582,21 +550,17 @@ def render_figures(code, code_path, output_dir, output_base, context, # Look for single-figure output files first all_exists = True img = ImageFile(output_base, output_dir) - for format, dpi in formats: - if out_of_date(code_path, img.filename(format)): - all_exists = False - break - img.formats.append(format) - - if all_exists: + if not any(out_of_date(code_path, img.filename(fmt)) + for fmt, dpi in formats): return [(code, [img])] + img.formats.extend(fmt for fmt, dpi in formats) # Then look for multi-figure output files results = [] all_exists = True for i, code_piece in enumerate(code_pieces): images = [] - for j in xrange(1000): + for j in itertools.count(): if len(code_pieces) > 1: img = ImageFile('%s_%02d_%02d' % (output_base, i, j), output_dir) @@ -701,12 +665,11 @@ def run(arguments, content, options, state_machine, state, lineno): else: function_name = None - with io.open(source_file_name, 'r', encoding='utf-8') as fd: - code = fd.read() + code = Path(source_file_name).read_text(encoding='utf-8') output_base = os.path.basename(source_file_name) else: source_file_name = rst_file - code = textwrap.dedent("\n".join(map(six.text_type, content))) + code = textwrap.dedent("\n".join(map(str, content))) counter = document.attributes.get('_plot_counter', 0) + 1 document.attributes['_plot_counter'] = counter base, ext = os.path.splitext(os.path.basename(source_file_name)) @@ -796,12 +759,10 @@ def run(arguments, content, options, state_machine, state, lineno): for j, (code_piece, images) in enumerate(results): if options['include-source']: if is_doctest: - lines = [''] - lines += [row.rstrip() for row in code_piece.split('\n')] + lines = ['', *code_piece.splitlines()] else: - lines = ['.. code-block:: python', ''] - lines += [' %s' % row.rstrip() - for row in code_piece.split('\n')] + lines = ['.. code-block:: python', '', + *textwrap.indent(code_piece, ' ').splitlines()] source_code = "\n".join(lines) else: source_code = "" @@ -810,7 +771,7 @@ def run(arguments, content, options, state_machine, state, lineno): images = [] opts = [ - ':%s: %s' % (key, val) for key, val in six.iteritems(options) + ':%s: %s' % (key, val) for key, val in options.items() if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] only_html = ".. only:: html" @@ -846,8 +807,7 @@ def run(arguments, content, options, state_machine, state, lineno): state_machine.insert_input(total_lines, source=source_file_name) # copy image files to builder's output directory, if necessary - if not os.path.exists(dest_dir): - cbook.mkdirs(dest_dir) + Path(dest_dir).mkdir(parents=True, exist_ok=True) for code_piece, images in results: for img in images: @@ -857,12 +817,8 @@ def run(arguments, content, options, state_machine, state, lineno): shutil.copyfile(fn, destimg) # copy script (if necessary) - target_name = os.path.join(dest_dir, output_base + source_ext) - with io.open(target_name, 'w', encoding="utf-8") as f: - if source_file_name == rst_file: - code_escaped = unescape_doctest(code) - else: - code_escaped = code - f.write(code_escaped) + Path(dest_dir, output_base + source_ext).write_text( + unescape_doctest(code) if source_file_name == rst_file else code, + encoding='utf-8') return errors diff --git a/lib/matplotlib/sphinxext/tests/conftest.py b/lib/matplotlib/sphinxext/tests/conftest.py index 2971a4314146..81829c903c58 100644 --- a/lib/matplotlib/sphinxext/tests/conftest.py +++ b/lib/matplotlib/sphinxext/tests/conftest.py @@ -1,6 +1,3 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - from matplotlib.testing.conftest import (mpl_test_settings, mpl_image_comparison_parameters, pytest_configure, pytest_unconfigure) diff --git a/lib/matplotlib/sphinxext/tests/test_tinypages.py b/lib/matplotlib/sphinxext/tests/test_tinypages.py index 748a3f381900..50d94ee30311 100644 --- a/lib/matplotlib/sphinxext/tests/test_tinypages.py +++ b/lib/matplotlib/sphinxext/tests/test_tinypages.py @@ -1,4 +1,4 @@ -""" Tests for tinypages build using sphinx extensions """ +"""Tests for tinypages build using sphinx extensions.""" import filecmp from os.path import join as pjoin, dirname, isdir @@ -10,18 +10,7 @@ from matplotlib import cbook -needs_sphinx = pytest.mark.skipif( - call([sys.executable, '-msphinx', '--help'], stdout=PIPE, stderr=PIPE), - reason="'{} -msphinx' does not return 0".format(sys.executable)) - - -@cbook.deprecated("2.1", alternative="filecmp.cmp") -def file_same(file1, file2): - with open(file1, 'rb') as fobj: - contents1 = fobj.read() - with open(file2, 'rb') as fobj: - contents2 = fobj.read() - return contents1 == contents2 +pytest.importorskip('sphinx') def test_tinypages(tmpdir): @@ -30,11 +19,13 @@ def test_tinypages(tmpdir): # Build the pages with warnings turned into errors cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', '-d', doctree_dir, pjoin(dirname(__file__), 'tinypages'), html_dir] - proc = Popen(cmd, stdout=PIPE, stderr=PIPE) + proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate() assert proc.returncode == 0, \ - "'{} -msphinx' failed with stdout:\n{}\nstderr:\n{}\n".format( - sys.executable, out, err) + "sphinx build failed with stdout:\n{}\nstderr:\n{}\n".format(out, err) + if err: + pytest.fail("sphinx build emitted the following warnings:\n{}" + .format(err)) assert isdir(html_dir) diff --git a/lib/matplotlib/sphinxext/tests/tinypages/conf.py b/lib/matplotlib/sphinxext/tests/tinypages/conf.py index d2d26c18eceb..970a3c5a4d45 100644 --- a/lib/matplotlib/sphinxext/tests/tinypages/conf.py +++ b/lib/matplotlib/sphinxext/tests/tinypages/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # tinypages documentation build configuration file, created by # sphinx-quickstart on Tue Mar 18 11:58:34 2014. # @@ -46,8 +44,8 @@ master_doc = 'index' # General information about the project. -project = u'tinypages' -copyright = u'2014, Matplotlib developers' +project = 'tinypages' +copyright = '2014, Matplotlib developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -202,8 +200,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'tinypages.tex', u'tinypages Documentation', - u'Matplotlib developers', 'manual'), + ('index', 'tinypages.tex', 'tinypages Documentation', + 'Matplotlib developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -232,8 +230,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'tinypages', u'tinypages Documentation', - [u'Matplotlib developers'], 1) + ('index', 'tinypages', 'tinypages Documentation', + ['Matplotlib developers'], 1) ] # If true, show URL addresses after external links. @@ -246,8 +244,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'tinypages', u'tinypages Documentation', - u'Matplotlib developers', 'tinypages', 'One line description of project.', + ('index', 'tinypages', 'tinypages Documentation', + 'Matplotlib developers', 'tinypages', 'One line description of project.', 'Miscellaneous'), ] diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 1e75c6ed6168..677dd4edc48d 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -1,19 +1,13 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +import warnings -import six +import numpy as np import matplotlib - +from matplotlib import docstring, rcParams from matplotlib.artist import allow_rasterization -from matplotlib import docstring import matplotlib.transforms as mtransforms import matplotlib.patches as mpatches import matplotlib.path as mpath -import numpy as np -import warnings - -rcParams = matplotlib.rcParams class Spine(mpatches.Patch): @@ -49,7 +43,7 @@ def __init__(self, axes, spine_type, path, **kwargs): Valid kwargs are: %(Patch)s """ - super(Spine, self).__init__(**kwargs) + super().__init__(**kwargs) self.axes = axes self.set_figure(self.axes.figure) self.spine_type = spine_type @@ -150,7 +144,13 @@ def get_patch_transform(self): self._recompute_transform() return self._patch_transform else: - return super(Spine, self).get_patch_transform() + return super().get_patch_transform() + + def get_window_extent(self, renderer=None): + # make sure the location is updated so that transforms etc are + # correct: + self._adjust_location() + return super().get_window_extent(renderer=renderer) def get_path(self): return self._path @@ -187,7 +187,7 @@ def is_frame_like(self): """ self._ensure_position_is_set() position = self._position - if isinstance(position, six.string_types): + if isinstance(position, str): if position == 'center': position = ('axes', 0.5) elif position == 'zero': @@ -311,7 +311,7 @@ def _adjust_location(self): @allow_rasterization def draw(self, renderer): self._adjust_location() - ret = super(Spine, self).draw(renderer) + ret = super().draw(renderer) self.stale = False return ret @@ -319,7 +319,7 @@ def _calc_offset_transform(self): """calculate the offset transform performed by the spine""" self._ensure_position_is_set() position = self._position - if isinstance(position, six.string_types): + if isinstance(position, str): if position == 'center': position = ('axes', 0.5) elif position == 'zero': @@ -487,15 +487,15 @@ def linear_spine(cls, axes, spine_type, **kwargs): """ (staticmethod) Returns a linear :class:`Spine`. """ - # all values of 13 get replaced upon call to set_bounds() + # all values of 0.999 get replaced upon call to set_bounds() if spine_type == 'left': - path = mpath.Path([(0.0, 13), (0.0, 13)]) + path = mpath.Path([(0.0, 0.999), (0.0, 0.999)]) elif spine_type == 'right': - path = mpath.Path([(1.0, 13), (1.0, 13)]) + path = mpath.Path([(1.0, 0.999), (1.0, 0.999)]) elif spine_type == 'bottom': - path = mpath.Path([(13, 0.0), (13, 0.0)]) + path = mpath.Path([(0.999, 0.0), (0.999, 0.0)]) elif spine_type == 'top': - path = mpath.Path([(13, 1.0), (13, 1.0)]) + path = mpath.Path([(0.999, 1.0), (0.999, 1.0)]) else: raise ValueError('unable to make path for spine "%s"' % spine_type) result = cls(axes, spine_type, path, **kwargs) @@ -529,7 +529,9 @@ def set_color(self, c): """ Set the edgecolor. - ACCEPTS: matplotlib color arg or sequence of rgba tuples + Parameters + ---------- + c : color or sequence of rgba tuples .. seealso:: diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 2b57aeb2b965..d18d3d76531b 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -6,19 +6,16 @@ (http://stackoverflow.com/users/66549/doug) """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange import numpy as np __all__ = ['stackplot'] -def stackplot(axes, x, *args, **kwargs): +def stackplot(axes, x, *args, + labels=(), colors=None, baseline='zero', + **kwargs): """ - Draws a stacked area plot. + Draw a stacked area plot. Parameters ---------- @@ -32,7 +29,7 @@ def stackplot(axes, x, *args, **kwargs): stackplot(x, y) # where y is MxN stackplot(x, y1, y2, y3, y4) # where y1, y2, y3, y4, are all 1xNm - baseline : ['zero' | 'sym' | 'wiggle' | 'weighted_wiggle'] + baseline : {'zero', 'sym', 'wiggle', 'weighted_wiggle'} Method used to calculate the baseline: - ``'zero'``: Constant zero baseline, i.e. a simple stacked plot. @@ -63,13 +60,10 @@ def stackplot(axes, x, *args, **kwargs): y = np.row_stack(args) - labels = iter(kwargs.pop('labels', [])) - - colors = kwargs.pop('colors', None) + labels = iter(labels) if colors is not None: axes.set_prop_cycle(color=colors) - baseline = kwargs.pop('baseline', 'zero') # Assume data passed has not been 'stacked', so stack it here. # We'll need a float buffer for the upcoming calculations. stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32)) @@ -118,7 +112,7 @@ def stackplot(axes, x, *args, **kwargs): r = [coll] # Color between array i-1 and array i - for i in xrange(len(y) - 1): + for i in range(len(y) - 1): color = axes._get_lines.get_next_color() r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], facecolor=color, label=next(labels, None), diff --git a/lib/matplotlib/streamplot.py b/lib/matplotlib/streamplot.py index 752a11eb4aaf..12bcf9159e91 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -2,13 +2,9 @@ Streamline plotting for 2D vector fields. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange import numpy as np + import matplotlib import matplotlib.cm as cm import matplotlib.colors as mcolors @@ -24,7 +20,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, cmap=None, norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, transform=None, zorder=None, start_points=None, maxlength=4.0, integration_direction='both'): - """Draws streamlines of a vector flow. + """Draw streamlines of a vector flow. *x*, *y* : 1d arrays an *evenly spaced* grid. @@ -553,7 +549,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength): dmap.update_trajectory(xi, yi) except InvalidIndexError: break - if (stotal + ds) > maxlength: + if stotal + ds > maxlength: break stotal += ds @@ -607,11 +603,11 @@ def interpgrid(a, xi, yi): x = int(xi) y = int(yi) # conditional is faster than clipping for integers - if x == (Nx - 2): + if x == (Nx - 1): xn = x else: xn = x + 1 - if y == (Ny - 2): + if y == (Ny - 1): yn = y else: yn = y + 1 @@ -648,7 +644,7 @@ def _gen_starting_points(shape): x, y = 0, 0 i = 0 direction = 'right' - for i in xrange(nx * ny): + for i in range(nx * ny): yield x, y diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py index cb0592f41e78..42d050d22cd0 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import - from .core import use, context, available, library, reload_library diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 593dd9dcb1cd..a29970f4748c 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six - """ Core functions and attributes for the matplotlib style library: @@ -14,13 +10,15 @@ ``library`` A dictionary of style names and matplotlib settings. """ + +import contextlib import os import re -import contextlib import warnings import matplotlib as mpl from matplotlib import rc_params_from_file, rcParamsDefault +from matplotlib.cbook import MatplotlibDeprecationWarning __all__ = ['use', 'context', 'available', 'library', 'reload_library'] @@ -28,7 +26,7 @@ BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib') # Users may want multiple library paths, so store a list of paths. -USER_LIBRARY_PATHS = [os.path.join(mpl._get_configdir(), 'stylelib')] +USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')] STYLE_EXTENSION = 'mplstyle' STYLE_FILE_PATTERN = re.compile(r'([\S]+).%s$' % STYLE_EXTENSION) @@ -48,7 +46,7 @@ def _remove_blacklisted_style_params(d, warn=True): if warn: warnings.warn( "Style includes a parameter, '{0}', that is not related " - "to style. Ignoring".format(key)) + "to style. Ignoring".format(key), stacklevel=3) else: o[key] = val return o @@ -89,21 +87,23 @@ def use(style): """ style_alias = {'mpl20': 'default', 'mpl15': 'classic'} - if isinstance(style, six.string_types) or hasattr(style, 'keys'): + if isinstance(style, str) or hasattr(style, 'keys'): # If name is a single str or dict, make it a single element list. styles = [style] else: styles = style - styles = (style_alias.get(s, s) - if isinstance(s, six.string_types) - else s + styles = (style_alias.get(s, s) if isinstance(s, str) else s for s in styles) for style in styles: - if not isinstance(style, six.string_types): + if not isinstance(style, str): _apply_style(style) elif style == 'default': - _apply_style(rcParamsDefault, warn=False) + # Deprecation warnings were already handled when creating + # rcParamsDefault, no need to reemit them here. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + _apply_style(rcParamsDefault, warn=False) elif style in library: _apply_style(library[style]) else: @@ -141,25 +141,16 @@ def context(style, after_reset=False): If True, apply style after resetting settings to their defaults; otherwise, apply style on top of the current settings. """ - initial_settings = mpl.rcParams.copy() - if after_reset: - mpl.rcdefaults() - try: + with mpl.rc_context(): + if after_reset: + mpl.rcdefaults() use(style) - except: - # Restore original settings before raising errors during the update. - mpl.rcParams.update(initial_settings) - raise - else: yield - finally: - mpl.rcParams.update(initial_settings) def load_base_library(): """Load style library defined in this package.""" - library = dict() - library.update(read_style_directory(BASE_LIBRARY_PATH)) + library = read_style_directory(BASE_LIBRARY_PATH) return library @@ -198,7 +189,7 @@ def read_style_directory(style_dir): for w in warns: message = 'In %s: %s' % (path, w.message) - warnings.warn(message) + warnings.warn(message, stacklevel=2) return styles @@ -211,11 +202,8 @@ def update_nested_dict(main_dict, new_dict): already exists. Instead you should update the sub-dict. """ # update named styles specified by user - for name, rc_dict in six.iteritems(new_dict): - if name in main_dict: - main_dict[name].update(rc_dict) - else: - main_dict[name] = rc_dict + for name, rc_dict in new_dict.items(): + main_dict.setdefault(name, {}).update(rc_dict) return main_dict diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index ee7908ca9d7a..885aa7b0cf06 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -19,27 +19,19 @@ License : matplotlib license """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange - import warnings -from . import artist +from . import artist, cbook, docstring from .artist import Artist, allow_rasterization from .patches import Rectangle -from matplotlib import docstring from .text import Text from .transforms import Bbox -from matplotlib.path import Path +from .path import Path class Cell(Rectangle): """ - A cell is a Rectangle with some associated text. - + A cell is a `.Rectangle` with some associated text. """ PAD = 0.1 # padding between text and rectangle @@ -74,7 +66,7 @@ def set_figure(self, fig): self._text.set_figure(fig) def get_text(self): - 'Return the cell Text intance' + """Return the cell `.Text` instance.""" return self._text def set_fontsize(self, size): @@ -82,7 +74,7 @@ def set_fontsize(self, size): self.stale = True def get_fontsize(self): - 'Return the cell fontsize' + """Return the cell fontsize.""" return self._text.get_fontsize() def auto_set_font_size(self, renderer): @@ -153,7 +145,6 @@ def set_text_props(self, **kwargs): class CustomCell(Cell): """ A subclass of Cell where the sides may be visibly toggled. - """ _edges = 'BRTL' @@ -163,9 +154,8 @@ class CustomCell(Cell): 'vertical': 'RL' } - def __init__(self, *args, **kwargs): - visible_edges = kwargs.pop('visible_edges') - Cell.__init__(self, *args, **kwargs) + def __init__(self, *args, visible_edges, **kwargs): + super().__init__(*args, **kwargs) self.visible_edges = visible_edges @property @@ -190,8 +180,9 @@ def visible_edges(self, value): self.stale = True def get_path(self): - 'Return a path where the edges specified by _visible_edges are drawn' - + """ + Return a path where the edges specified by _visible_edges are drawn. + """ codes = [Path.MOVETO] for edge in self._edges: @@ -250,13 +241,13 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs): Artist.__init__(self) - if isinstance(loc, six.string_types) and loc not in self.codes: - warnings.warn('Unrecognized location %s. Falling back on ' - 'bottom; valid locations are\n%s\t' % - (loc, '\n\t'.join(self.codes))) - loc = 'bottom' - if isinstance(loc, six.string_types): - loc = self.codes.get(loc, 1) + if isinstance(loc, str): + if loc not in self.codes: + warnings.warn('Unrecognized location %s. Falling back on ' + 'bottom; valid locations are\n%s\t' % + (loc, '\n\t'.join(self.codes))) + loc = 'bottom' + loc = self.codes[loc] self.set_figure(ax.figure) self._axes = ax self._loc = loc @@ -282,9 +273,9 @@ def add_cell(self, row, col, *args, **kwargs): Parameters ---------- row : int - Row index + Row index. col : int - Column index + Column index. Returns ------- @@ -298,7 +289,7 @@ def add_cell(self, row, col, *args, **kwargs): def __setitem__(self, position, cell): """ - Set a customcell in a given position + Set a custom cell in a given position. """ if not isinstance(cell, CustomCell): raise TypeError('Table only accepts CustomCell') @@ -314,7 +305,7 @@ def __setitem__(self, position, cell): def __getitem__(self, position): """ - Retreive a custom cell from a given position + Retrieve a custom cell from a given position. """ try: row, col = position[0], position[1] @@ -360,7 +351,7 @@ def _get_grid_bbox(self, renderer): Only include those in the range (0,0) to (maxRow, maxCol)""" boxes = [cell.get_window_extent(renderer) - for (row, col), cell in six.iteritems(self._cells) + for (row, col), cell in self._cells.items() if row >= 0 and col >= 0] bbox = Bbox.union(boxes) return bbox.inverse_transformed(self.get_transform()) @@ -378,7 +369,7 @@ def contains(self, mouseevent): renderer = self.figure._cachedRenderer if renderer is not None: boxes = [cell.get_window_extent(renderer) - for (row, col), cell in six.iteritems(self._cells) + for (row, col), cell in self._cells.items() if row >= 0 and col >= 0] bbox = Bbox.union(boxes) return bbox.contains(mouseevent.x, mouseevent.y), {} @@ -386,14 +377,14 @@ def contains(self, mouseevent): return False, {} def get_children(self): - 'Return the Artists contained by the table' - return list(six.itervalues(self._cells)) - get_child_artists = get_children # backward compatibility + """Return the Artists contained by the table.""" + return list(self._cells.values()) + get_child_artists = cbook.deprecated("3.0")(get_children) def get_window_extent(self, renderer): - 'Return the bounding box of the table in window coords' + """Return the bounding box of the table in window coords.""" boxes = [cell.get_window_extent(renderer) - for cell in six.itervalues(self._cells)] + for cell in self._cells.values()] return Bbox.union(boxes) def _do_cell_alignment(self): @@ -404,7 +395,7 @@ def _do_cell_alignment(self): # Calculate row/column widths widths = {} heights = {} - for (row, col), cell in six.iteritems(self._cells): + for (row, col), cell in self._cells.items(): height = heights.setdefault(row, 0.0) heights[row] = max(height, cell.get_height()) width = widths.setdefault(col, 0.0) @@ -424,7 +415,7 @@ def _do_cell_alignment(self): ypos += heights[row] # set cell positions - for (row, col), cell in six.iteritems(self._cells): + for (row, col), cell in self._cells.items(): cell.set_x(lefts[col]) cell.set_y(bottoms[row]) @@ -462,8 +453,7 @@ def auto_set_column_width(self, col): self.stale = True def _auto_set_column_width(self, col, renderer): - """ Automagically set width for column. - """ + """Automatically set width for column.""" cells = [key for key in self._cells if key[1] == col] # find max width @@ -485,9 +475,9 @@ def _auto_set_font_size(self, renderer): if len(self._cells) == 0: return - fontsize = list(six.itervalues(self._cells))[0].get_fontsize() + fontsize = next(iter(self._cells.values())).get_fontsize() cells = [] - for key, cell in six.iteritems(self._cells): + for key, cell in self._cells.items(): # ignore auto-sized columns if key[1] in self._autoColumns: continue @@ -496,30 +486,31 @@ def _auto_set_font_size(self, renderer): cells.append(cell) # now set all fontsizes equal - for cell in six.itervalues(self._cells): + for cell in self._cells.values(): cell.set_fontsize(fontsize) def scale(self, xscale, yscale): """ Scale column widths by xscale and row heights by yscale. """ - for c in six.itervalues(self._cells): + for c in self._cells.values(): c.set_width(c.get_width() * xscale) c.set_height(c.get_height() * yscale) def set_fontsize(self, size): """ - Set the fontsize of the cell text + Set the font size, in points, of the cell text. - ACCEPTS: a float in points + Parameters + ---------- + size : float """ - for cell in six.itervalues(self._cells): + for cell in self._cells.values(): cell.set_fontsize(size) self.stale = True def _offset(self, ox, oy): - 'Move all the artists by ox,oy (axes coords)' - - for c in six.itervalues(self._cells): + """Move all the artists by ox, oy (axes coords).""" + for c in self._cells.values(): x, y = c.get_x(), c.get_y() c.set_x(x + ox) c.set_y(y + oy) @@ -551,7 +542,7 @@ def _update_positions(self, renderer): else: # Position using loc (BEST, UR, UL, LL, LR, CL, CR, LC, UC, C, - TR, TL, BL, BR, R, L, T, B) = xrange(len(self.codes)) + TR, TL, BL, BR, R, L, T, B) = range(len(self.codes)) # defaults for center ox = (0.5 - w / 2) - l oy = (0.5 - h / 2) - b @@ -580,7 +571,7 @@ def _update_positions(self, renderer): self._offset(ox, oy) def get_celld(self): - 'return a dict of cells in the table' + """Return a dict of cells in the table.""" return self._cells @@ -670,8 +661,8 @@ def table(ax, height = table._approx_text_height() # Add the cells - for row in xrange(rows): - for col in xrange(cols): + for row in range(rows): + for col in range(cols): table.add_cell(row + offset, col, width=colWidths[col], height=height, text=cellText[row][col], @@ -679,7 +670,7 @@ def table(ax, loc=cellLoc) # Do column labels if colLabels is not None: - for col in xrange(cols): + for col in range(cols): table.add_cell(0, col, width=colWidths[col], height=height, text=colLabels[col], facecolor=colColours[col], @@ -687,7 +678,7 @@ def table(ax, # Do row labels if rowLabels is not None: - for row in xrange(rows): + for row in range(rows): table.add_cell(row + offset, -1, width=rowLabelWidth or 1e-15, height=height, text=rowLabels[row], facecolor=rowColours[row], diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 2184be03ef88..2e19bd6d1563 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -1,13 +1,10 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import functools +import locale import warnings import matplotlib as mpl from matplotlib import cbook +from matplotlib.cbook import MatplotlibDeprecationWarning def is_called_from_pytest(): @@ -15,13 +12,6 @@ def is_called_from_pytest(): return getattr(mpl, '_called_from_pytest', False) -def _copy_metadata(src_func, tgt_func): - """Replicates metadata of the function. Returns target function.""" - functools.update_wrapper(tgt_func, src_func) - tgt_func.__wrapped__ = src_func # Python2 compatibility. - return tgt_func - - def set_font_settings_for_testing(): mpl.rcParams['font.family'] = 'DejaVu Sans' mpl.rcParams['text.hinting'] = False @@ -35,25 +25,24 @@ def set_reproducibility_for_testing(): def setup(): # The baseline images are created in this locale, so we should use # it during all of the tests. - import locale - from matplotlib.backends import backend_agg, backend_pdf, backend_svg try: - locale.setlocale(locale.LC_ALL, str('en_US.UTF-8')) + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') except locale.Error: try: - locale.setlocale(locale.LC_ALL, str('English_United States.1252')) + locale.setlocale(locale.LC_ALL, 'English_United States.1252') except locale.Error: warnings.warn( "Could not set locale to English/United States. " - "Some date-related tests may fail") + "Some date-related tests may fail.") - mpl.use('Agg', warn=False) # use Agg backend for these tests + mpl.use('Agg', force=True, warn=False) # use Agg backend for these tests - # These settings *must* be hardcoded for running the comparison - # tests and are not necessarily the default values as specified in - # rcsetup.py - mpl.rcdefaults() # Start with all defaults + with warnings.catch_warnings(): + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + mpl.rcdefaults() # Start with all defaults + # These settings *must* be hardcoded for running the comparison tests and + # are not necessarily the default values as specified in rcsetup.py. set_font_settings_for_testing() set_reproducibility_for_testing() diff --git a/lib/matplotlib/testing/_nose/__init__.py b/lib/matplotlib/testing/_nose/__init__.py deleted file mode 100644 index d513c7b14f4b..000000000000 --- a/lib/matplotlib/testing/_nose/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import sys - - -def get_extra_test_plugins(): - from .plugins.performgc import PerformGC - from .plugins.knownfailure import KnownFailure - from nose.plugins import attrib - - return [PerformGC, KnownFailure, attrib.Plugin] - - -def get_env(): - env = {'NOSE_COVER_PACKAGE': ['matplotlib', 'mpl_toolkits'], - 'NOSE_COVER_HTML': 1, - 'NOSE_COVER_NO_PRINT': 1} - return env - - -def check_deps(): - try: - import nose - try: - from unittest import mock - except ImportError: - import mock - except ImportError: - print("matplotlib.test requires nose and mock to run.") - raise - - -def test(verbosity=None, coverage=False, switch_backend_warn=True, - recursionlimit=0, **kwargs): - from ... import default_test_modules, get_backend, use - - old_backend = get_backend() - old_recursionlimit = sys.getrecursionlimit() - try: - use('agg') - if recursionlimit: - sys.setrecursionlimit(recursionlimit) - import nose - from nose.plugins import multiprocess - - # Nose doesn't automatically instantiate all of the plugins in the - # child processes, so we have to provide the multiprocess plugin with - # a list. - extra_plugins = get_extra_test_plugins() - multiprocess._instantiate_plugins = extra_plugins - - env = get_env() - if coverage: - env['NOSE_WITH_COVERAGE'] = 1 - - if verbosity is not None: - env['NOSE_VERBOSE'] = verbosity - - success = nose.run( - addplugins=[plugin() for plugin in extra_plugins], - env=env, - defaultTest=default_test_modules, - **kwargs - ) - finally: - if old_backend.lower() != 'agg': - use(old_backend, warn=switch_backend_warn) - if recursionlimit: - sys.setrecursionlimit(old_recursionlimit) - - return success - - -def knownfail(msg): - from .exceptions import KnownFailureTest - # Keep the next ultra-long comment so it shows in console. - raise KnownFailureTest(msg) # An error here when running nose means that you don't have the matplotlib.testing.nose.plugins:KnownFailure plugin in use. # noqa diff --git a/lib/matplotlib/testing/_nose/decorators.py b/lib/matplotlib/testing/_nose/decorators.py deleted file mode 100644 index 1f0807df2004..000000000000 --- a/lib/matplotlib/testing/_nose/decorators.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from .. import _copy_metadata -from . import knownfail -from .exceptions import KnownFailureDidNotFailTest - - -def knownfailureif(fail_condition, msg=None, known_exception_class=None): - # based on numpy.testing.dec.knownfailureif - if msg is None: - msg = 'Test known to fail' - - def known_fail_decorator(f): - def failer(*args, **kwargs): - try: - # Always run the test (to generate images). - result = f(*args, **kwargs) - except Exception as err: - if fail_condition: - if known_exception_class is not None: - if not isinstance(err, known_exception_class): - # This is not the expected exception - raise - knownfail(msg) - else: - raise - if fail_condition and fail_condition != 'indeterminate': - raise KnownFailureDidNotFailTest(msg) - return result - return _copy_metadata(f, failer) - return known_fail_decorator diff --git a/lib/matplotlib/testing/_nose/exceptions.py b/lib/matplotlib/testing/_nose/exceptions.py deleted file mode 100644 index 51fc6f782d78..000000000000 --- a/lib/matplotlib/testing/_nose/exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -class KnownFailureTest(Exception): - """ - Raise this exception to mark a test as a known failing test. - """ - - -class KnownFailureDidNotFailTest(Exception): - """ - Raise this exception to mark a test should have failed but did not. - """ diff --git a/lib/matplotlib/testing/_nose/plugins/__init__.py b/lib/matplotlib/testing/_nose/plugins/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/lib/matplotlib/testing/_nose/plugins/knownfailure.py b/lib/matplotlib/testing/_nose/plugins/knownfailure.py deleted file mode 100644 index 3a5c86c35048..000000000000 --- a/lib/matplotlib/testing/_nose/plugins/knownfailure.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os -from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin -from ..exceptions import KnownFailureTest - - -class KnownFailure(ErrorClassPlugin): - '''Plugin that installs a KNOWNFAIL error class for the - KnownFailureClass exception. When KnownFailureTest is raised, - the exception will be logged in the knownfail attribute of the - result, 'K' or 'KNOWNFAIL' (verbose) will be output, and the - exception will not be counted as an error or failure. - - This is based on numpy.testing.noseclasses.KnownFailure. - ''' - enabled = True - knownfail = ErrorClass(KnownFailureTest, - label='KNOWNFAIL', - isfailure=False) - - def options(self, parser, env=os.environ): - env_opt = 'NOSE_WITHOUT_KNOWNFAIL' - parser.add_option('--no-knownfail', action='store_true', - dest='noKnownFail', default=env.get(env_opt, False), - help='Disable special handling of KnownFailureTest ' - 'exceptions') - - def configure(self, options, conf): - if not self.can_configure: - return - self.conf = conf - disable = getattr(options, 'noKnownFail', False) - if disable: - self.enabled = False - - def addError(self, test, err, *zero_nine_capt_args): - # Fixme (Really weird): if I don't leave empty method here, - # nose gets confused and KnownFails become testing errors when - # using the MplNosePlugin and MplTestCase. - - # The *zero_nine_capt_args captures an extra argument. There - # seems to be a bug in - # nose.testing.manager.ZeroNinePlugin.addError() in which a - # 3rd positional argument ("capt") is passed to the plugin's - # addError() method, even if one is not explicitly using the - # ZeroNinePlugin. - pass diff --git a/lib/matplotlib/testing/_nose/plugins/performgc.py b/lib/matplotlib/testing/_nose/plugins/performgc.py deleted file mode 100644 index 818fbd96f44f..000000000000 --- a/lib/matplotlib/testing/_nose/plugins/performgc.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import gc -import os -from nose.plugins import Plugin - - -class PerformGC(Plugin): - """This plugin adds option to call ``gc.collect`` after each test""" - enabled = False - - def options(self, parser, env=os.environ): - env_opt = 'PERFORM_GC' - parser.add_option('--perform-gc', action='store_true', - dest='performGC', default=env.get(env_opt, False), - help='Call gc.collect() after each test') - - def configure(self, options, conf): - if not self.can_configure: - return - - self.enabled = getattr(options, 'performGC', False) - - def afterTest(self, test): - gc.collect() diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index dcda681d4384..fc0e618de4f4 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -2,28 +2,23 @@ Provides a collection of utilities for comparing (image) results. """ -from __future__ import absolute_import, division, print_function - -import six import atexit import functools import hashlib -import itertools import os +from pathlib import Path import re import shutil +import subprocess import sys from tempfile import TemporaryFile import numpy as np import matplotlib -from matplotlib.compat import subprocess from matplotlib.testing.exceptions import ImageComparisonFailure -from matplotlib import _png -from matplotlib import _get_cachedir -from matplotlib import cbook +from matplotlib import _png, cbook __all__ = ['compare_float', 'compare_images', 'comparable_formats'] @@ -37,6 +32,7 @@ def make_test_filename(fname, purpose): return '%s-%s%s' % (base, purpose, ext) +@cbook.deprecated("3.0") def compare_float(expected, actual, relTol=None, absTol=None): """ Fail if the floating point values are not close enough, with @@ -81,15 +77,14 @@ def compare_float(expected, actual, relTol=None, absTol=None): def get_cache_dir(): - cachedir = _get_cachedir() + cachedir = matplotlib.get_cachedir() if cachedir is None: raise RuntimeError('Could not find a suitable configuration directory') cache_dir = os.path.join(cachedir, 'test_cache') - if not os.path.exists(cache_dir): - try: - cbook.mkdirs(cache_dir) - except IOError: - return None + try: + Path(cache_dir).mkdir(parents=True, exist_ok=True) + except IOError: + return None if not os.access(cache_dir, os.W_OK): return None return cache_dir @@ -141,38 +136,81 @@ def _shlex_quote_bytes(b): else b"'" + b.replace(b"'", b"'\"'\"'") + b"'") -class _SVGConverter(object): +class _ConverterError(Exception): + pass + + +class _Converter(object): def __init__(self): self._proc = None - # We cannot rely on the GC to trigger `__del__` at exit because - # other modules (e.g. `subprocess`) may already have their globals - # set to `None`, which make `proc.communicate` or `proc.terminate` - # fail. By relying on `atexit` we ensure the destructor runs before - # `None`-setting occurs. + # Explicitly register deletion from an atexit handler because if we + # wait until the object is GC'd (which occurs later), then some module + # globals (e.g. signal.SIGKILL) has already been set to None, and + # kill() doesn't work anymore... atexit.register(self.__del__) - def _read_to_prompt(self): - """Did Inkscape reach the prompt without crashing? - """ - stream = iter(functools.partial(self._proc.stdout.read, 1), b"") - prompt = (b"\n", b">") - n = len(prompt) - its = itertools.tee(stream, n) - for i, it in enumerate(its): - next(itertools.islice(it, i, i), None) # Advance `it` by `i`. + def __del__(self): + if self._proc: + self._proc.kill() + self._proc.wait() + for stream in filter(None, [self._proc.stdin, + self._proc.stdout, + self._proc.stderr]): + stream.close() + self._proc = None + + def _read_until(self, terminator): + """Read until the prompt is reached.""" + buf = bytearray() while True: - window = tuple(map(next, its)) - if len(window) != n: - # Ran out of data -- one of the `next(it)` raised - # StopIteration, so the tuple is shorter. - return False - if self._proc.poll() is not None: - # Inkscape exited. - return False - if window == prompt: - # Successfully read until prompt. - return True + c = self._proc.stdout.read(1) + if not c: + raise _ConverterError + buf.extend(c) + if buf.endswith(terminator): + return bytes(buf[:-len(terminator)]) + +class _GSConverter(_Converter): + def __call__(self, orig, dest): + if not self._proc: + self._stdout = TemporaryFile() + self._proc = subprocess.Popen( + [matplotlib.checkdep_ghostscript.executable, + "-dNOPAUSE", "-sDEVICE=png16m"], + # As far as I can see, ghostscript never outputs to stderr. + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + try: + self._read_until(b"\nGS") + except _ConverterError: + raise OSError("Failed to start Ghostscript") + + def encode_and_escape(name): + return (os.fsencode(name) + .replace(b"\\", b"\\\\") + .replace(b"(", br"\(") + .replace(b")", br"\)")) + + self._proc.stdin.write( + b"<< /OutputFile (" + + encode_and_escape(dest) + + b") >> setpagedevice (" + + encode_and_escape(orig) + + b") run flush\n") + self._proc.stdin.flush() + # GS> if nothing left on the stack; GS if n items left on the stack. + err = self._read_until(b"GS") + stack = self._read_until(b">") + if stack or not os.path.exists(dest): + stack_size = int(stack[1:]) if stack else 0 + self._proc.stdin.write(b"pop\n" * stack_size) + # Using the systemencoding should at least get the filenames right. + raise ImageComparisonFailure( + (err + b"GS" + stack + b">") + .decode(sys.getfilesystemencoding(), "replace")) + + +class _SVGConverter(_Converter): def __call__(self, orig, dest): if (not self._proc # First run. or self._proc.poll() is not None): # Inkscape terminated. @@ -184,67 +222,52 @@ def __call__(self, orig, dest): # reported as a regular exception below). env.pop("DISPLAY", None) # May already be unset. # Do not load any user options. - # `os.environ` needs native strings on Py2+Windows. - env[str("INKSCAPE_PROFILE_DIR")] = os.devnull + env["INKSCAPE_PROFILE_DIR"] = os.devnull # Old versions of Inkscape (0.48.3.1, used on Travis as of now) # seem to sometimes deadlock when stderr is redirected to a pipe, # so we redirect it to a temporary file instead. This is not # necessary anymore as of Inkscape 0.92.1. - self._stderr = TemporaryFile() + stderr = TemporaryFile() self._proc = subprocess.Popen( - [str("inkscape"), "--without-gui", "--shell"], + ["inkscape", "--without-gui", "--shell"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=self._stderr, env=env) - if not self._read_to_prompt(): - raise OSError("Failed to start Inkscape") - - try: - fsencode = os.fsencode - except AttributeError: # Py2. - def fsencode(s): - return s.encode(sys.getfilesystemencoding()) + stderr=stderr, env=env) + # Slight abuse, but makes shutdown handling easier. + self._proc.stderr = stderr + try: + self._read_until(b"\n>") + except _ConverterError: + raise OSError("Failed to start Inkscape in interactive mode") # Inkscape uses glib's `g_shell_parse_argv`, which has a consistent # behavior across platforms, so we can just use `shlex.quote`. - orig_b, dest_b = map(_shlex_quote_bytes, map(fsencode, [orig, dest])) + orig_b, dest_b = map(_shlex_quote_bytes, + map(os.fsencode, [orig, dest])) if b"\n" in orig_b or b"\n" in dest_b: # Who knows whether the current folder name has a newline, or if # our encoding is even ASCII compatible... Just fall back on the # slow solution (Inkscape uses `fgets` so it will always stop at a # newline). return make_external_conversion_command(lambda old, new: [ - str('inkscape'), '-z', old, '--export-png', new])(orig, dest) + 'inkscape', '-z', old, '--export-png', new])(orig, dest) self._proc.stdin.write(orig_b + b" --export-png=" + dest_b + b"\n") self._proc.stdin.flush() - if not self._read_to_prompt(): - # Inkscape's output is not localized but gtk's is, so the - # output stream probably has a mixed encoding. Using - # `getfilesystemencoding` should at least get the filenames - # right... + try: + self._read_until(b"\n>") + except _ConverterError: + # 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) raise ImageComparisonFailure( self._stderr.read().decode( sys.getfilesystemencoding(), "replace")) - def __del__(self): - if self._proc: - if self._proc.poll() is None: # Not exited yet. - self._proc.communicate(b"quit\n") - self._proc.wait() - self._proc.stdin.close() - self._proc.stdout.close() - self._stderr.close() - def _update_converter(): gs, gs_v = matplotlib.checkdep_ghostscript() if gs_v is not None: - def cmd(old, new): - return [str(gs), '-q', '-sDEVICE=png16m', '-dNOPAUSE', '-dBATCH', - '-sOutputFile=' + new, old] - converter['pdf'] = make_external_conversion_command(cmd) - converter['eps'] = make_external_conversion_command(cmd) - + converter['pdf'] = converter['eps'] = _GSConverter() if matplotlib.checkdep_inkscape() is not None: converter['svg'] = _SVGConverter() @@ -263,20 +286,17 @@ def comparable_formats(): on this system. """ - return ['png'] + list(converter) + return ['png', *converter] def convert(filename, cache): """ - Convert the named file into a png file. Returns the name of the - created file. + Convert the named file to png; return the name of the created file. If *cache* is True, the result of the conversion is cached in - `matplotlib._get_cachedir() + '/test_cache/'`. The caching is based - on a hash of the exact contents of the input file. The is no limit - on the size of the cache, so it may need to be manually cleared - periodically. - + `matplotlib.get_cachedir() + '/test_cache/'`. The caching is based on a + hash of the exact contents of the input file. There is no limit on the + size of the cache, so it may need to be manually cleared periodically. """ base, extension = filename.rsplit('.', 1) if extension not in converter: @@ -316,39 +336,6 @@ def convert(filename, cache): return newname -#: Maps file extensions to a function which takes a filename as its -#: only argument to return a list suitable for execution with Popen. -#: The purpose of this is so that the result file (with the given -#: extension) can be verified with tools such as xmllint for svg. -verifiers = {} - -# Turning this off, because it seems to cause multiprocessing issues -if False and matplotlib.checkdep_xmllint(): - verifiers['svg'] = lambda filename: [ - 'xmllint', '--valid', '--nowarning', '--noout', filename] - - -@cbook.deprecated("2.1") -def verify(filename): - """Verify the file through some sort of verification tool.""" - if not os.path.exists(filename): - raise IOError("'%s' does not exist" % filename) - base, extension = filename.rsplit('.', 1) - verifier = verifiers.get(extension, None) - if verifier is not None: - cmd = verifier(filename) - pipe = subprocess.Popen(cmd, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = pipe.communicate() - errcode = pipe.wait() - if errcode != 0: - msg = "File verification command failed:\n%s\n" % ' '.join(cmd) - if stdout: - msg += "Standard output:\n%s\n" % stdout - if stderr: - msg += "Standard error:\n%s\n" % stderr - raise IOError(msg) - def crop_to_same(actual_path, actual_image, expected_path, expected_image): # clip the images to the same size -- this is useful only when @@ -365,8 +352,8 @@ def calculate_rms(expectedImage, actualImage): "Calculate the per-pixel errors, then compute the root mean square error." if expectedImage.shape != actualImage.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {0} " - "actual size {1}".format(expectedImage.shape, actualImage.shape)) + "Image sizes do not match expected size: {} " + "actual size {}".format(expectedImage.shape, actualImage.shape)) # Convert to float to avoid overflowing finite integer types. return np.sqrt(((expectedImage - actualImage).astype(float) ** 2).mean()) @@ -397,7 +384,7 @@ def compare_images(expected, actual, tol, in_decorator=False): -------- img1 = "./baseline/plot.png" img2 = "./output/plot.png" - compare_images( img1, img2, 0.001 ): + compare_images(img1, img2, 0.001): """ if not os.path.exists(actual): @@ -427,7 +414,7 @@ def compare_images(expected, actual, tol, in_decorator=False): diff_image = make_test_filename(actual, 'failed-diff') - if tol <= 0.0: + if tol <= 0: if np.array_equal(expectedImage, actualImage): return None @@ -459,16 +446,27 @@ def compare_images(expected, actual, tol, in_decorator=False): def save_diff_image(expected, actual, output): - expectedImage = _png.read_png(expected) - actualImage = _png.read_png(actual) + ''' + Parameters + ---------- + expected : str + File path of expected image. + actual : str + File path of actual image. + output : str + File path to save difference image to. + ''' + # Drop alpha channels, similarly to compare_images. + expectedImage = _png.read_png(expected)[..., :3] + actualImage = _png.read_png(actual)[..., :3] actualImage, expectedImage = crop_to_same( actual, actualImage, expected, expectedImage) expectedImage = np.array(expectedImage).astype(float) actualImage = np.array(actualImage).astype(float) if expectedImage.shape != actualImage.shape: raise ImageComparisonFailure( - "Image sizes do not match expected size: {0} " - "actual size {1}".format(expectedImage.shape, actualImage.shape)) + "Image sizes do not match expected size: {} " + "actual size {}".format(expectedImage.shape, actualImage.shape)) absDiffImage = np.abs(expectedImage - actualImage) # expand differences in luminance domain diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 9dc180c96000..40ce56c4895a 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -1,13 +1,14 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +import warnings import pytest import matplotlib +from matplotlib import cbook +from matplotlib.cbook import MatplotlibDeprecationWarning def pytest_configure(config): - matplotlib.use('agg') + matplotlib.use('agg', force=True) matplotlib._called_from_pytest = True matplotlib._init_tests() @@ -18,40 +19,39 @@ def pytest_unconfigure(config): @pytest.fixture(autouse=True) def mpl_test_settings(request): - from matplotlib.testing.decorators import _do_cleanup - - original_units_registry = matplotlib.units.registry.copy() - original_settings = matplotlib.rcParams.copy() - - backend = None - backend_marker = request.keywords.get('backend') - if backend_marker is not None: - assert len(backend_marker.args) == 1, \ - "Marker 'backend' must specify 1 backend." - backend = backend_marker.args[0] - prev_backend = matplotlib.get_backend() - - style = '_classic_test' # Default of cleanup and image_comparison too. - style_marker = request.keywords.get('style') - if style_marker is not None: - assert len(style_marker.args) == 1, \ - "Marker 'style' must specify 1 style." - style = style_marker.args[0] - - matplotlib.testing.setup() - if backend is not None: - # This import must come after setup() so it doesn't load the default - # backend prematurely. - import matplotlib.pyplot as plt - plt.switch_backend(backend) - matplotlib.style.use(style) - try: - yield - finally: + from matplotlib.testing.decorators import _cleanup_cm + + with _cleanup_cm(): + + backend = None + backend_marker = request.keywords.get('backend') + if backend_marker is not None: + assert len(backend_marker.args) == 1, \ + "Marker 'backend' must specify 1 backend." + backend = backend_marker.args[0] + prev_backend = matplotlib.get_backend() + + style = '_classic_test' # Default of cleanup and image_comparison too. + style_marker = request.keywords.get('style') + if style_marker is not None: + assert len(style_marker.args) == 1, \ + "Marker 'style' must specify 1 style." + style = style_marker.args[0] + + matplotlib.testing.setup() if backend is not None: - plt.switch_backend(prev_backend) - _do_cleanup(original_units_registry, - original_settings) + # This import must come after setup() so it doesn't load the + # default backend prematurely. + import matplotlib.pyplot as plt + plt.switch_backend(backend) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + matplotlib.style.use(style) + try: + yield + finally: + if backend is not None: + plt.switch_backend(prev_backend) @pytest.fixture @@ -71,11 +71,9 @@ def mpl_image_comparison_parameters(request, extension): baseline_images = request.getfixturevalue('baseline_images') func = request.function - func.__wrapped__.parameters = (baseline_images, extension) - try: + with cbook._setattr_cm(func.__wrapped__, + parameters=(baseline_images, extension)): yield - finally: - delattr(func.__wrapped__, 'parameters') @pytest.fixture diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 0ce6e6252493..85b8d5e87d6b 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -1,105 +1,59 @@ -from __future__ import absolute_import, division, print_function - -import six - +import contextlib +from distutils.version import StrictVersion import functools import inspect import os -import sys +from pathlib import Path import shutil -import warnings +import sys import unittest - -# Note - don't import nose up here - import it only as needed in functions. -# This allows other functions here to be used by pytest-based testing suites -# without requiring nose to be installed. - +import warnings import matplotlib as mpl import matplotlib.style import matplotlib.units import matplotlib.testing from matplotlib import cbook -from matplotlib import ticker -from matplotlib import pyplot as plt from matplotlib import ft2font -from matplotlib.testing.compare import ( - comparable_formats, compare_images, make_test_filename) -from . import _copy_metadata, is_called_from_pytest +from matplotlib import pyplot as plt +from matplotlib import ticker +from . import is_called_from_pytest +from .compare import comparable_formats, compare_images, make_test_filename from .exceptions import ImageComparisonFailure -def _knownfailureif(fail_condition, msg=None, known_exception_class=None): - """ - - Assume a will fail if *fail_condition* is True. *fail_condition* - may also be False or the string 'indeterminate'. - - *msg* is the error message displayed for the test. - - If *known_exception_class* is not None, the failure is only known - if the exception is an instance of this class. (Default = None) - - """ - if is_called_from_pytest(): - import pytest - if fail_condition == 'indeterminate': - fail_condition, strict = True, False - else: - fail_condition, strict = bool(fail_condition), True - return pytest.mark.xfail(condition=fail_condition, reason=msg, - raises=known_exception_class, strict=strict) - else: - from ._nose.decorators import knownfailureif - return knownfailureif(fail_condition, msg, known_exception_class) - - -@cbook.deprecated('2.1', - alternative='pytest.xfail or import the plugin') -def knownfailureif(fail_condition, msg=None, known_exception_class=None): - _knownfailureif(fail_condition, msg, known_exception_class) +@contextlib.contextmanager +def _cleanup_cm(): + orig_units_registry = matplotlib.units.registry.copy() + try: + with warnings.catch_warnings(), matplotlib.rc_context(): + yield + finally: + matplotlib.units.registry.clear() + matplotlib.units.registry.update(orig_units_registry) + plt.close("all") -def _do_cleanup(original_units_registry, original_settings): - plt.close('all') +class CleanupTestCase(unittest.TestCase): + """A wrapper for unittest.TestCase that includes cleanup operations.""" + @classmethod + def setUpClass(cls): + cls._cm = _cleanup_cm().__enter__() - mpl.rcParams.clear() - mpl.rcParams.update(original_settings) - matplotlib.units.registry.clear() - matplotlib.units.registry.update(original_units_registry) - warnings.resetwarnings() # reset any warning filters set in tests + @classmethod + def tearDownClass(cls): + cls._cm.__exit__(None, None, None) +@cbook.deprecated("3.0") class CleanupTest(object): - @classmethod - def setup_class(cls): - cls.original_units_registry = matplotlib.units.registry.copy() - cls.original_settings = mpl.rcParams.copy() - matplotlib.testing.setup() - - @classmethod - def teardown_class(cls): - _do_cleanup(cls.original_units_registry, - cls.original_settings) + setup_class = classmethod(CleanupTestCase.setUpClass.__func__) + teardown_class = classmethod(CleanupTestCase.tearDownClass.__func__) def test(self): self._func() -class CleanupTestCase(unittest.TestCase): - '''A wrapper for unittest.TestCase that includes cleanup operations''' - @classmethod - def setUpClass(cls): - import matplotlib.units - cls.original_units_registry = matplotlib.units.registry.copy() - cls.original_settings = mpl.rcParams.copy() - - @classmethod - def tearDownClass(cls): - _do_cleanup(cls.original_units_registry, - cls.original_settings) - - def cleanup(style=None): """ A decorator to ensure that any global state is reset before @@ -111,41 +65,27 @@ def cleanup(style=None): The name of the style to apply. """ - # If cleanup is used without arguments, `style` will be a - # callable, and we pass it directly to the wrapper generator. If - # cleanup if called with an argument, it is a string naming a - # style, and the function will be passed as an argument to what we - # return. This is a confusing, but somewhat standard, pattern for - # writing a decorator with optional arguments. + # If cleanup is used without arguments, `style` will be a callable, and we + # pass it directly to the wrapper generator. If cleanup if called with an + # argument, it is a string naming a style, and the function will be passed + # as an argument to what we return. This is a confusing, but somewhat + # standard, pattern for writing a decorator with optional arguments. def make_cleanup(func): if inspect.isgeneratorfunction(func): @functools.wraps(func) def wrapped_callable(*args, **kwargs): - original_units_registry = matplotlib.units.registry.copy() - original_settings = mpl.rcParams.copy() - matplotlib.style.use(style) - try: - for yielded in func(*args, **kwargs): - yield yielded - finally: - _do_cleanup(original_units_registry, - original_settings) + with _cleanup_cm(), matplotlib.style.context(style): + yield from func(*args, **kwargs) else: @functools.wraps(func) def wrapped_callable(*args, **kwargs): - original_units_registry = matplotlib.units.registry.copy() - original_settings = mpl.rcParams.copy() - matplotlib.style.use(style) - try: + with _cleanup_cm(), matplotlib.style.context(style): func(*args, **kwargs) - finally: - _do_cleanup(original_units_registry, - original_settings) return wrapped_callable - if isinstance(style, six.string_types): + if isinstance(style, str): return make_cleanup else: result = make_cleanup(style) @@ -158,24 +98,22 @@ def check_freetype_version(ver): if ver is None: return True - from distutils import version - if isinstance(ver, six.string_types): + if isinstance(ver, str): ver = (ver, ver) - ver = [version.StrictVersion(x) for x in ver] - found = version.StrictVersion(ft2font.__freetype_version__) + ver = [StrictVersion(x) for x in ver] + found = StrictVersion(ft2font.__freetype_version__) - return found >= ver[0] and found <= ver[1] + return ver[0] <= found <= ver[1] def _checked_on_freetype_version(required_freetype_version): - if check_freetype_version(required_freetype_version): - return lambda f: f - + import pytest reason = ("Mismatched version of freetype. " "Test requires '%s', you have '%s'" % (required_freetype_version, ft2font.__freetype_version__)) - return _knownfailureif('indeterminate', msg=reason, - known_exception_class=ImageComparisonFailure) + return pytest.mark.xfail( + not check_freetype_version(required_freetype_version), + reason=reason, raises=ImageComparisonFailure, strict=False) def remove_ticks_and_titles(figure): @@ -211,18 +149,15 @@ def _raise_on_image_difference(expected, actual, tol): def _xfail_if_format_is_uncomparable(extension): - will_fail = extension not in comparable_formats() - if will_fail: - fail_msg = 'Cannot compare %s files on this system' % extension - else: - fail_msg = 'No failure expected' - - return _knownfailureif(will_fail, fail_msg, - known_exception_class=ImageComparisonFailure) + import pytest + return pytest.mark.xfail( + extension not in comparable_formats(), + reason='Cannot compare {} files on this system'.format(extension), + raises=ImageComparisonFailure, strict=True) def _mark_xfail_if_format_is_uncomparable(extension): - if isinstance(extension, six.string_types): + if isinstance(extension, str): will_fail = extension not in comparable_formats() else: # Extension might be a pytest marker instead of a plain string. @@ -266,9 +201,9 @@ def copy_baseline(self, baseline, extension): if os.path.exists(orig_expected_fname): shutil.copyfile(orig_expected_fname, expected_fname) else: - reason = ("Do not have baseline image {0} because this " - "file does not exist: {1}".format(expected_fname, - orig_expected_fname)) + reason = ("Do not have baseline image {} because this " + "file does not exist: {}".format(expected_fname, + orig_expected_fname)) raise ImageComparisonFailure(reason) return expected_fname @@ -293,6 +228,7 @@ def compare(self, idx, baseline, extension): _raise_on_image_difference(expected_fname, actual_fname, self.tol) +@cbook.deprecated("3.0") class ImageComparisonTest(CleanupTest, _ImageComparisonBase): """ Nose-based image comparison class @@ -330,12 +266,6 @@ def setup(self): def teardown(self): self.teardown_class() - @staticmethod - @cbook.deprecated('2.1', - alternative='remove_ticks_and_titles') - def remove_text(figure): - remove_ticks_and_titles(figure) - def nose_runner(self): func = self.compare func = _checked_on_freetype_version(self.freetype_version)(func) @@ -349,12 +279,12 @@ def __call__(self, func): self.delayed_init(func) import nose.tools + @functools.wraps(func) @nose.tools.with_setup(self.setup, self.teardown) def runner_wrapper(): - for case in self.nose_runner(): - yield case + yield from self.nose_runner() - return _copy_metadata(func, runner_wrapper) + return runner_wrapper def _pytest_image_comparison(baseline_images, extensions, tol, @@ -373,6 +303,7 @@ def _pytest_image_comparison(baseline_images, extensions, tol, extensions = map(_mark_xfail_if_format_is_uncomparable, extensions) def decorator(func): + @functools.wraps(func) # Parameter indirection; see docstring above and comment below. @pytest.mark.usefixtures('mpl_image_comparison_parameters') @pytest.mark.parametrize('extension', extensions) @@ -402,8 +333,7 @@ def wrapper(*args, **kwargs): for idx, baseline in enumerate(baseline_images): img.compare(idx, baseline, extension) - wrapper.__wrapped__ = func # For Python 2.7. - return _copy_metadata(func, wrapper) + return wrapper return decorator @@ -431,7 +361,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, extensions : [ None | list ] If None, defaults to all supported extensions. - Otherwise, a list of extensions to test. For example ['png','pdf']. + Otherwise, a list of extensions to test, e.g. ``['png', 'pdf']``. tol : float, optional, default: 0 The RMS threshold above which the test is considered failed. @@ -477,87 +407,82 @@ def image_comparison(baseline_images, extensions=None, tol=0, savefig_kwargs=savefig_kwarg, style=style) +def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0): + """ + Decorator for test cases that generate and compare two figures. + + The decorated function must take two arguments, *fig_test* and *fig_ref*, + and draw the test and reference images on them. After the function + returns, the figures are saved and compared. + + Arguments + --------- + extensions : list, default: ["png", "pdf", "svg"] + The extensions to test. + tol : float + The RMS threshold above which the test is considered failed. + """ + + def decorator(func): + import pytest + + _, result_dir = map(Path, _image_directories(func)) + + @pytest.mark.parametrize("ext", extensions) + def wrapper(ext): + fig_test = plt.figure("test") + fig_ref = plt.figure("reference") + func(fig_test, fig_ref) + test_image_path = str( + result_dir / (func.__name__ + "." + ext)) + ref_image_path = str( + result_dir / (func.__name__ + "-expected." + ext)) + fig_test.savefig(test_image_path) + fig_ref.savefig(ref_image_path) + _raise_on_image_difference( + ref_image_path, test_image_path, tol=tol) + + return wrapper + + return decorator + + def _image_directories(func): """ Compute the baseline and result image directories for testing *func*. - Create the result directory if it doesn't exist. + + For test module ``foo.bar.test_baz``, the baseline directory is at + ``foo/bar/baseline_images/test_baz`` and the result directory at + ``$(pwd)/result_images/test_baz``. The result directory is created if it + doesn't exist. """ - module_name = func.__module__ - if module_name == '__main__': - # FIXME: this won't work for nested packages in matplotlib.tests - warnings.warn( - 'Test module run as script. Guessing baseline image locations.') - script_name = sys.argv[0] - basedir = os.path.abspath(os.path.dirname(script_name)) - subdir = os.path.splitext(os.path.split(script_name)[1])[0] - else: - mods = module_name.split('.') - if len(mods) >= 3: - mods.pop(0) - # mods[0] will be the name of the package being tested (in - # most cases "matplotlib") However if this is a - # namespace package pip installed and run via the nose - # multiprocess plugin or as a specific test this may be - # missing. See https://github.com/matplotlib/matplotlib/issues/3314 - if mods.pop(0) != 'tests': - warnings.warn( - "Module {!r} does not live in a parent module named 'tests'. " - "This is probably ok, but we may not be able to guess the " - "correct subdirectory containing the baseline images. If " - "things go wrong please make sure that there is a parent " - "directory named 'tests' and that it contains a __init__.py " - "file (can be empty).".format(module_name)) - subdir = os.path.join(*mods) - - import imp - def find_dotted_module(module_name, path=None): - """A version of imp which can handle dots in the module name. - As for imp.find_module(), the return value is a 3-element - tuple (file, pathname, description).""" - res = None - for sub_mod in module_name.split('.'): - try: - res = file, path, _ = imp.find_module(sub_mod, path) - path = [path] - if file is not None: - file.close() - except ImportError: - # assume namespace package - path = list(sys.modules[sub_mod].__path__) - res = None, path, None - return res - - mod_file = find_dotted_module(func.__module__)[1] - basedir = os.path.dirname(mod_file) - - baseline_dir = os.path.join(basedir, 'baseline_images', subdir) - result_dir = os.path.abspath(os.path.join('result_images', subdir)) - - if not os.path.exists(result_dir): - cbook.mkdirs(result_dir) - - return baseline_dir, result_dir + module_path = Path(sys.modules[func.__module__].__file__) + baseline_dir = module_path.parent / "baseline_images" / module_path.stem + result_dir = Path().resolve() / "result_images" / module_path.stem + result_dir.mkdir(parents=True, exist_ok=True) + return str(baseline_dir), str(result_dir) def switch_backend(backend): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. + def switch_backend_decorator(func): + @functools.wraps(func) def backend_switcher(*args, **kwargs): try: prev_backend = mpl.get_backend() matplotlib.testing.setup() plt.switch_backend(backend) - result = func(*args, **kwargs) + return func(*args, **kwargs) finally: plt.switch_backend(prev_backend) - return result - return _copy_metadata(func, backend_switcher) + return backend_switcher + return switch_backend_decorator +@cbook.deprecated("3.0") def skip_if_command_unavailable(cmd): """ skips a test if a command is unavailable. @@ -569,7 +494,7 @@ def skip_if_command_unavailable(cmd): return a non zero exit code, something like ["latex", "-version"] """ - from matplotlib.compat.subprocess import check_output + from subprocess import check_output try: check_output(cmd) except: diff --git a/lib/matplotlib/testing/determinism.py b/lib/matplotlib/testing/determinism.py index 614544ce28ec..f43706ea5beb 100644 --- a/lib/matplotlib/testing/determinism.py +++ b/lib/matplotlib/testing/determinism.py @@ -2,16 +2,11 @@ Provides utilities to test output reproducibility. """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import io import os import re +import subprocess import sys -from subprocess import check_output import pytest @@ -34,11 +29,11 @@ def _determinism_save(objects='mhi', format="pdf", usetex=False): # use different markers... ax1 = fig.add_subplot(1, 6, 1) x = range(10) - ax1.plot(x, [1] * 10, marker=u'D') - ax1.plot(x, [2] * 10, marker=u'x') - ax1.plot(x, [3] * 10, marker=u'^') - ax1.plot(x, [4] * 10, marker=u'H') - ax1.plot(x, [5] * 10, marker=u'v') + ax1.plot(x, [1] * 10, marker='D') + ax1.plot(x, [2] * 10, marker='x') + ax1.plot(x, [3] * 10, marker='^') + ax1.plot(x, [4] * 10, marker='H') + ax1.plot(x, [5] * 10, marker='v') if 'h' in objects: # also use different hatch patterns @@ -63,13 +58,8 @@ def _determinism_save(objects='mhi', format="pdf", usetex=False): x = range(5) fig.add_subplot(1, 6, 6).plot(x, x) - if six.PY2 and format == 'ps': - stdout = io.StringIO() - else: - stdout = getattr(sys.stdout, 'buffer', sys.stdout) + stdout = getattr(sys.stdout, 'buffer', sys.stdout) fig.savefig(stdout, format=format) - if six.PY2 and format == 'ps': - sys.stdout.write(stdout.getvalue()) # Restores SOURCE_DATE_EPOCH if sde is None: @@ -94,14 +84,14 @@ def _determinism_check(objects='mhi', format="pdf", usetex=False): """ plots = [] for i in range(3): - result = check_output([sys.executable, '-R', '-c', - 'import matplotlib; ' - 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use(%r); ' - 'from matplotlib.testing.determinism ' - 'import _determinism_save;' - '_determinism_save(%r,%r,%r)' - % (format, objects, format, usetex)]) + result = subprocess.check_output([ + sys.executable, '-R', '-c', + 'import matplotlib; ' + 'matplotlib._called_from_pytest = True; ' + 'matplotlib.use(%r); ' + 'from matplotlib.testing.determinism import _determinism_save;' + '_determinism_save(%r, %r, %r)' + % (format, objects, format, usetex)]) plots.append(result) for p in plots[1:]: if usetex: @@ -128,14 +118,14 @@ def _determinism_source_date_epoch(format, string, keyword=b"CreationDate"): a string to look at when searching for the timestamp in the document (used in case the test fails). """ - buff = check_output([sys.executable, '-R', '-c', - 'import matplotlib; ' - 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use(%r); ' - 'from matplotlib.testing.determinism ' - 'import _determinism_save;' - '_determinism_save(%r,%r)' - % (format, "", format)]) + buff = subprocess.check_output([ + sys.executable, '-R', '-c', + 'import matplotlib; ' + 'matplotlib._called_from_pytest = True; ' + 'matplotlib.use(%r); ' + 'from matplotlib.testing.determinism import _determinism_save;' + '_determinism_save(%r, %r)' + % (format, "", format)]) find_keyword = re.compile(b".*" + keyword + b".*") key = find_keyword.search(buff) if key: diff --git a/lib/matplotlib/testing/disable_internet.py b/lib/matplotlib/testing/disable_internet.py index e70c6565276f..e0bece410596 100644 --- a/lib/matplotlib/testing/disable_internet.py +++ b/lib/matplotlib/testing/disable_internet.py @@ -1,13 +1,9 @@ # Originally from astropy project (http://astropy.org), under BSD # 3-clause license. -from __future__ import (absolute_import, division, print_function, - unicode_literals) - import contextlib import socket - -from six.moves import urllib +import urllib.request # save original socket method for restoration # These are global so that re-calling the turn_off_internet function doesn't @@ -65,7 +61,7 @@ def new_function(*args, **kwargs): new_addr = (host, args[addr_arg][1]) args = args[:addr_arg] + (new_addr,) + args[addr_arg + 1:] - if any([h in host for h in valid_hosts]): + if any(h in host for h in valid_hosts): return original_function(*args, **kwargs) else: raise IOError("An attempt was made to connect to the internet " diff --git a/lib/matplotlib/testing/jpl_units/Duration.py b/lib/matplotlib/testing/jpl_units/Duration.py index 99b2f9872985..bdfb4cfb5f9d 100644 --- a/lib/matplotlib/testing/jpl_units/Duration.py +++ b/lib/matplotlib/testing/jpl_units/Duration.py @@ -1,211 +1,214 @@ -#=========================================================================== +# ========================================================================== # # Duration # -#=========================================================================== +# ========================================================================== """Duration module.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import operator # # Place all imports before here. -#=========================================================================== +# ========================================================================== + -#=========================================================================== +# ========================================================================== class Duration(object): - """Class Duration in development. - """ - allowed = [ "ET", "UTC" ] - - #----------------------------------------------------------------------- - def __init__( self, frame, seconds ): - """Create a new Duration object. - - = ERROR CONDITIONS - - If the input frame is not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - frame The frame of the duration. Must be 'ET' or 'UTC' - - seconds The number of seconds in the Duration. - """ - if frame not in self.allowed: - msg = "Input frame '%s' is not one of the supported frames of %s" \ - % ( frame, str( self.allowed ) ) - raise ValueError( msg ) - - self._frame = frame - self._seconds = seconds - - #----------------------------------------------------------------------- - def frame( self ): - """Return the frame the duration is in.""" - return self._frame - - #----------------------------------------------------------------------- - def __abs__( self ): - """Return the absolute value of the duration.""" - return Duration( self._frame, abs( self._seconds ) ) - - #----------------------------------------------------------------------- - def __neg__( self ): - """Return the negative value of this Duration.""" - return Duration( self._frame, -self._seconds ) - - #----------------------------------------------------------------------- - def seconds( self ): - """Return the number of seconds in the Duration.""" - return self._seconds + """Class Duration in development. + """ + allowed = ["ET", "UTC"] + + # ---------------------------------------------------------------------- + def __init__(self, frame, seconds): + """Create a new Duration object. + + = ERROR CONDITIONS + - If the input frame is not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - frame The frame of the duration. Must be 'ET' or 'UTC' + - seconds The number of seconds in the Duration. + """ + if frame not in self.allowed: + msg = "Input frame '%s' is not one of the supported frames of %s" \ + % (frame, str(self.allowed)) + raise ValueError(msg) + + self._frame = frame + self._seconds = seconds + + # ---------------------------------------------------------------------- + def frame(self): + """Return the frame the duration is in.""" + return self._frame + + # ---------------------------------------------------------------------- + def __abs__(self): + """Return the absolute value of the duration.""" + return Duration(self._frame, abs(self._seconds)) + + # ---------------------------------------------------------------------- + def __neg__(self): + """Return the negative value of this Duration.""" + return Duration(self._frame, -self._seconds) + + # ---------------------------------------------------------------------- + def seconds(self): + """Return the number of seconds in the Duration.""" + return self._seconds + + # ---------------------------------------------------------------------- + def __bool__(self): + return self._seconds != 0 + + # ---------------------------------------------------------------------- + def __eq__(self, rhs): + return self._cmp(rhs, operator.eq) + + def __ne__(self, rhs): + return self._cmp(rhs, operator.ne) + + def __lt__(self, rhs): + return self._cmp(rhs, operator.lt) + + def __le__(self, rhs): + return self._cmp(rhs, operator.le) + + def __gt__(self, rhs): + return self._cmp(rhs, operator.gt) + + def __ge__(self, rhs): + return self._cmp(rhs, operator.ge) + + def _cmp(self, rhs, op): + """Compare two Durations. + + = INPUT VARIABLES + - rhs The Duration to compare against. + - op The function to do the comparison + + = RETURN VALUE + - Returns op(self, rhs) + """ + self.checkSameFrame(rhs, "compare") + return op(self._seconds, rhs._seconds) + + # ---------------------------------------------------------------------- + def __add__(self, rhs): + """Add two Durations. + + = ERROR CONDITIONS + - If the input rhs is not in the same frame, an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to add. + + = RETURN VALUE + - Returns the sum of ourselves and the input Duration. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U + + if isinstance(rhs, U.Epoch): + return rhs + self + + self.checkSameFrame(rhs, "add") + return Duration(self._frame, self._seconds + rhs._seconds) + + # ---------------------------------------------------------------------- + def __sub__(self, rhs): + """Subtract two Durations. + + = ERROR CONDITIONS + - If the input rhs is not in the same frame, an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input Duration. + """ + self.checkSameFrame(rhs, "sub") + return Duration(self._frame, self._seconds - rhs._seconds) + + # ---------------------------------------------------------------------- + def __mul__(self, rhs): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration(self._frame, self._seconds * float(rhs)) + + # ---------------------------------------------------------------------- + def __rmul__(self, lhs): + """Scale a Duration by a value. + + = INPUT VARIABLES + - lhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration(self._frame, self._seconds * float(lhs)) + + # ---------------------------------------------------------------------- + def __div__(self, rhs): + """Divide a Duration by a value. + + = INPUT VARIABLES + - rhs The scalar to divide by. - #----------------------------------------------------------------------- - def __nonzero__( self ): - """Compare two Durations. - - = INPUT VARIABLES - - rhs The Duration to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - return self._seconds != 0 + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration(self._frame, self._seconds / rhs) - if six.PY3: - __bool__ = __nonzero__ - - #----------------------------------------------------------------------- - def __cmp__( self, rhs ): - """Compare two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - self.checkSameFrame( rhs, "compare" ) - return cmp( self._seconds, rhs._seconds ) - - #----------------------------------------------------------------------- - def __add__( self, rhs ): - """Add two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to add. - - = RETURN VALUE - - Returns the sum of ourselves and the input Duration. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U - - if isinstance( rhs, U.Epoch ): - return rhs + self - - self.checkSameFrame( rhs, "add" ) - return Duration( self._frame, self._seconds + rhs._seconds ) - - #----------------------------------------------------------------------- - def __sub__( self, rhs ): - """Subtract two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to subtract. - - = RETURN VALUE - - Returns the difference of ourselves and the input Duration. - """ - self.checkSameFrame( rhs, "sub" ) - return Duration( self._frame, self._seconds - rhs._seconds ) - - #----------------------------------------------------------------------- - def __mul__( self, rhs ): - """Scale a UnitDbl by a value. - - = INPUT VARIABLES - - rhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds * float( rhs ) ) - - #----------------------------------------------------------------------- - def __rmul__( self, lhs ): - """Scale a Duration by a value. - - = INPUT VARIABLES - - lhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds * float( lhs ) ) - - #----------------------------------------------------------------------- - def __div__( self, rhs ): - """Divide a Duration by a value. - - = INPUT VARIABLES - - rhs The scalar to divide by. + # ---------------------------------------------------------------------- + def __rdiv__(self, rhs): + """Divide a Duration by a value. - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds / rhs ) - - #----------------------------------------------------------------------- - def __rdiv__( self, rhs ): - """Divide a Duration by a value. - - = INPUT VARIABLES - - rhs The scalar to divide by. + = INPUT VARIABLES + - rhs The scalar to divide by. - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, rhs / self._seconds ) - - #----------------------------------------------------------------------- - def __str__( self ): - """Print the Duration.""" - return "%g %s" % ( self._seconds, self._frame ) - - #----------------------------------------------------------------------- - def __repr__( self ): - """Print the Duration.""" - return "Duration( '%s', %g )" % ( self._frame, self._seconds ) - - #----------------------------------------------------------------------- - def checkSameFrame( self, rhs, func ): - """Check to see if frames are the same. - - = ERROR CONDITIONS - - If the frame of the rhs Duration is not the same as our frame, - an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to check for the same frame - - func The name of the function doing the check. - """ - if self._frame != rhs._frame: - msg = "Cannot %s Duration's with different frames.\n" \ - "LHS: %s\n" \ - "RHS: %s" % ( func, self._frame, rhs._frame ) - raise ValueError( msg ) - -#=========================================================================== + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration(self._frame, rhs / self._seconds) + + # ---------------------------------------------------------------------- + def __str__(self): + """Print the Duration.""" + return "%g %s" % (self._seconds, self._frame) + + # ---------------------------------------------------------------------- + def __repr__(self): + """Print the Duration.""" + return "Duration('%s', %g)" % (self._frame, self._seconds) + + # ---------------------------------------------------------------------- + def checkSameFrame(self, rhs, func): + """Check to see if frames are the same. + + = ERROR CONDITIONS + - If the frame of the rhs Duration is not the same as our frame, + an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to check for the same frame + - func The name of the function doing the check. + """ + if self._frame != rhs._frame: + msg = "Cannot %s Duration's with different frames.\n" \ + "LHS: %s\n" \ + "RHS: %s" % (func, self._frame, rhs._frame) + raise ValueError(msg) + +# ========================================================================== diff --git a/lib/matplotlib/testing/jpl_units/Epoch.py b/lib/matplotlib/testing/jpl_units/Epoch.py index 91b4c127eb5c..8cf956366d53 100644 --- a/lib/matplotlib/testing/jpl_units/Epoch.py +++ b/lib/matplotlib/testing/jpl_units/Epoch.py @@ -1,238 +1,258 @@ -#=========================================================================== +# =========================================================================== # # Epoch # -#=========================================================================== +# =========================================================================== """Epoch module.""" -#=========================================================================== +# =========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - +import operator import math import datetime as DT from matplotlib.dates import date2num # # Place all imports before here. -#=========================================================================== +# =========================================================================== + -#=========================================================================== +# =========================================================================== class Epoch(object): - # Frame conversion offsets in seconds - # t(TO) = t(FROM) + allowed[ FROM ][ TO ] - allowed = { - "ET" : { - "UTC" : +64.1839, - }, - "UTC" : { - "ET" : -64.1839, - }, - } - - #----------------------------------------------------------------------- - def __init__( self, frame, sec=None, jd=None, daynum=None, dt=None ): - """Create a new Epoch object. - - Build an epoch 1 of 2 ways: - - Using seconds past a Julian date: - # Epoch( 'ET', sec=1e8, jd=2451545 ) - - or using a matplotlib day number - # Epoch( 'ET', daynum=730119.5 ) - - - = ERROR CONDITIONS - - If the input units are not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - frame The frame of the epoch. Must be 'ET' or 'UTC' - - sec The number of seconds past the input JD. - - jd The Julian date of the epoch. - - daynum The matplotlib day number of the epoch. - - dt A python datetime instance. - """ - if ( ( sec is None and jd is not None ) or - ( sec is not None and jd is None ) or - ( daynum is not None and ( sec is not None or jd is not None ) ) or - ( daynum is None and dt is None and ( sec is None or jd is None ) ) or - ( daynum is not None and dt is not None ) or - ( dt is not None and ( sec is not None or jd is not None ) ) or - ( (dt is not None) and not isinstance(dt, DT.datetime) ) ): - msg = "Invalid inputs. Must enter sec and jd together, " \ - "daynum by itself, or dt (must be a python datetime).\n" \ - "Sec = %s\nJD = %s\ndnum= %s\ndt = %s" \ - % ( str( sec ), str( jd ), str( daynum ), str( dt ) ) - raise ValueError( msg ) - - if frame not in self.allowed: - msg = "Input frame '%s' is not one of the supported frames of %s" \ - % ( frame, str( list(six.iterkeys(self.allowed) ) ) ) - raise ValueError(msg) - - self._frame = frame - - if dt is not None: - daynum = date2num( dt ) - - if daynum is not None: - # 1-JAN-0001 in JD = 1721425.5 - jd = float( daynum ) + 1721425.5 - self._jd = math.floor( jd ) - self._seconds = ( jd - self._jd ) * 86400.0 - - else: - self._seconds = float( sec ) - self._jd = float( jd ) - - # Resolve seconds down to [ 0, 86400 ) - deltaDays = int( math.floor( self._seconds / 86400.0 ) ) - self._jd += deltaDays - self._seconds -= deltaDays * 86400.0 - - #----------------------------------------------------------------------- - def convert( self, frame ): - if self._frame == frame: - return self - - offset = self.allowed[ self._frame ][ frame ] - - return Epoch( frame, self._seconds + offset, self._jd ) - - #----------------------------------------------------------------------- - def frame( self ): - return self._frame - - #----------------------------------------------------------------------- - def julianDate( self, frame ): - t = self - if frame != self._frame: - t = self.convert( frame ) - - return t._jd + t._seconds / 86400.0 - - #----------------------------------------------------------------------- - def secondsPast( self, frame, jd ): - t = self - if frame != self._frame: - t = self.convert( frame ) - - delta = t._jd - jd - return t._seconds + delta * 86400 - - #----------------------------------------------------------------------- - def __cmp__( self, rhs ): - """Compare two Epoch's. - - = INPUT VARIABLES - - rhs The Epoch to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - t = self - if self._frame != rhs._frame: - t = self.convert( rhs._frame ) - - if t._jd != rhs._jd: - return cmp( t._jd, rhs._jd ) - - return cmp( t._seconds, rhs._seconds ) - - #----------------------------------------------------------------------- - def __add__( self, rhs ): - """Add a duration to an Epoch. - - = INPUT VARIABLES - - rhs The Epoch to subtract. - - = RETURN VALUE - - Returns the difference of ourselves and the input Epoch. - """ - t = self - if self._frame != rhs.frame(): - t = self.convert( rhs._frame ) - - sec = t._seconds + rhs.seconds() - - return Epoch( t._frame, sec, t._jd ) + # Frame conversion offsets in seconds + # t(TO) = t(FROM) + allowed[ FROM ][ TO ] + allowed = { + "ET": { + "UTC": +64.1839, + }, + "UTC": { + "ET": -64.1839, + }, + } + + # ----------------------------------------------------------------------- + def __init__(self, frame, sec=None, jd=None, daynum=None, dt=None): + """Create a new Epoch object. + + Build an epoch 1 of 2 ways: + + Using seconds past a Julian date: + # Epoch('ET', sec=1e8, jd=2451545) + + or using a matplotlib day number + # Epoch('ET', daynum=730119.5) + + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - frame The frame of the epoch. Must be 'ET' or 'UTC' + - sec The number of seconds past the input JD. + - jd The Julian date of the epoch. + - daynum The matplotlib day number of the epoch. + - dt A python datetime instance. + """ + if ((sec is None and jd is not None) or + (sec is not None and jd is None) or + (daynum is not None and + (sec is not None or jd is not None)) or + (daynum is None and dt is None and + (sec is None or jd is None)) or + (daynum is not None and dt is not None) or + (dt is not None and (sec is not None or jd is not None)) or + ((dt is not None) and not isinstance(dt, DT.datetime))): + raise ValueError( + "Invalid inputs. Must enter sec and jd together, " + "daynum by itself, or dt (must be a python datetime).\n" + "Sec = %s\n" + "JD = %s\n" + "dnum= %s\n" + "dt = %s" % (sec, jd, daynum, dt)) + + if frame not in self.allowed: + raise ValueError( + "Input frame '%s' is not one of the supported frames of %s" % + (frame, list(self.allowed.keys()))) + + self._frame = frame + + if dt is not None: + daynum = date2num(dt) + + if daynum is not None: + # 1-JAN-0001 in JD = 1721425.5 + jd = float(daynum) + 1721425.5 + self._jd = math.floor(jd) + self._seconds = (jd - self._jd) * 86400.0 + + else: + self._seconds = float(sec) + self._jd = float(jd) + + # Resolve seconds down to [ 0, 86400) + deltaDays = int(math.floor(self._seconds / 86400.0)) + self._jd += deltaDays + self._seconds -= deltaDays * 86400.0 + + # ----------------------------------------------------------------------- + def convert(self, frame): + if self._frame == frame: + return self + + offset = self.allowed[self._frame][frame] + + return Epoch(frame, self._seconds + offset, self._jd) + + # ----------------------------------------------------------------------- + def frame(self): + return self._frame + + # ----------------------------------------------------------------------- + def julianDate(self, frame): + t = self + if frame != self._frame: + t = self.convert(frame) + + return t._jd + t._seconds / 86400.0 + + # ----------------------------------------------------------------------- + def secondsPast(self, frame, jd): + t = self + if frame != self._frame: + t = self.convert(frame) + + delta = t._jd - jd + return t._seconds + delta * 86400 + + # ----------------------------------------------------------------------- + def __eq__(self, rhs): + return self._cmp(rhs, operator.eq) + + def __ne__(self, rhs): + return self._cmp(rhs, operator.ne) + + def __lt__(self, rhs): + return self._cmp(rhs, operator.lt) + + def __le__(self, rhs): + return self._cmp(rhs, operator.le) + + def __gt__(self, rhs): + return self._cmp(rhs, operator.gt) + + def __ge__(self, rhs): + return self._cmp(rhs, operator.ge) + + def _cmp(self, rhs, op): + """Compare two Epoch's. + + = INPUT VARIABLES + - rhs The Epoch to compare against. + - op The function to do the comparison + + = RETURN VALUE + - Returns op(self, rhs) + """ + t = self + if self._frame != rhs._frame: + t = self.convert(rhs._frame) + + if t._jd != rhs._jd: + return op(t._jd, rhs._jd) + + return op(t._seconds, rhs._seconds) + + # ----------------------------------------------------------------------- + def __add__(self, rhs): + """Add a duration to an Epoch. + + = INPUT VARIABLES + - rhs The Epoch to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input Epoch. + """ + t = self + if self._frame != rhs.frame(): + t = self.convert(rhs._frame) + + sec = t._seconds + rhs.seconds() + + return Epoch(t._frame, sec, t._jd) - #----------------------------------------------------------------------- - def __sub__( self, rhs ): - """Subtract two Epoch's or a Duration from an Epoch. + # ----------------------------------------------------------------------- + def __sub__(self, rhs): + """Subtract two Epoch's or a Duration from an Epoch. - Valid: - Duration = Epoch - Epoch - Epoch = Epoch - Duration + Valid: + Duration = Epoch - Epoch + Epoch = Epoch - Duration - = INPUT VARIABLES - - rhs The Epoch to subtract. + = INPUT VARIABLES + - rhs The Epoch to subtract. - = RETURN VALUE - - Returns either the duration between to Epoch's or the a new - Epoch that is the result of subtracting a duration from an epoch. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U + = RETURN VALUE + - Returns either the duration between to Epoch's or the a new + Epoch that is the result of subtracting a duration from an epoch. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U - # Handle Epoch - Duration - if isinstance( rhs, U.Duration ): - return self + -rhs + # Handle Epoch - Duration + if isinstance(rhs, U.Duration): + return self + -rhs - t = self - if self._frame != rhs._frame: - t = self.convert( rhs._frame ) + t = self + if self._frame != rhs._frame: + t = self.convert(rhs._frame) - days = t._jd - rhs._jd - sec = t._seconds - rhs._seconds + days = t._jd - rhs._jd + sec = t._seconds - rhs._seconds - return U.Duration( rhs._frame, days*86400 + sec ) + return U.Duration(rhs._frame, days*86400 + sec) - #----------------------------------------------------------------------- - def __str__( self ): - """Print the Epoch.""" - return "%22.15e %s" % ( self.julianDate( self._frame ), self._frame ) + # ----------------------------------------------------------------------- + def __str__(self): + """Print the Epoch.""" + return "%22.15e %s" % (self.julianDate(self._frame), self._frame) - #----------------------------------------------------------------------- - def __repr__( self ): - """Print the Epoch.""" - return str( self ) + # ----------------------------------------------------------------------- + def __repr__(self): + """Print the Epoch.""" + return str(self) - #----------------------------------------------------------------------- - def range( start, stop, step ): - """Generate a range of Epoch objects. + # ----------------------------------------------------------------------- + def range(start, stop, step): + """Generate a range of Epoch objects. - Similar to the Python range() method. Returns the range [ - start, stop ) at the requested step. Each element will be a - Epoch object. + Similar to the Python range() method. Returns the range [ + start, stop) at the requested step. Each element will be a + Epoch object. - = INPUT VARIABLES - - start The starting value of the range. - - stop The stop value of the range. - - step Step to use. + = INPUT VARIABLES + - start The starting value of the range. + - stop The stop value of the range. + - step Step to use. - = RETURN VALUE - - Returns a list contianing the requested Epoch values. - """ - elems = [] + = RETURN VALUE + - Returns a list contianing the requested Epoch values. + """ + elems = [] - i = 0 - while True: - d = start + i * step - if d >= stop: - break + i = 0 + while True: + d = start + i * step + if d >= stop: + break - elems.append( d ) - i += 1 + elems.append(d) + i += 1 - return elems + return elems - range = staticmethod( range ) + range = staticmethod(range) -#=========================================================================== +# =========================================================================== diff --git a/lib/matplotlib/testing/jpl_units/EpochConverter.py b/lib/matplotlib/testing/jpl_units/EpochConverter.py index eecf3321135b..cc85d104409a 100644 --- a/lib/matplotlib/testing/jpl_units/EpochConverter.py +++ b/lib/matplotlib/testing/jpl_units/EpochConverter.py @@ -1,165 +1,159 @@ -#=========================================================================== +# ========================================================================== # # EpochConverter # -#=========================================================================== +# ========================================================================== """EpochConverter module containing class EpochConverter.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import matplotlib.units as units import matplotlib.dates as date_ticker from matplotlib.cbook import iterable # # Place all imports before here. -#=========================================================================== - -__all__ = [ 'EpochConverter' ] - -#=========================================================================== -class EpochConverter( units.ConversionInterface ): - """: A matplotlib converter class. Provides matplotlib conversion - functionality for Monte Epoch and Duration classes. - """ - - # julian date reference for "Jan 1, 0001" minus 1 day because - # matplotlib really wants "Jan 0, 0001" - jdRef = 1721425.5 - 1 - - #------------------------------------------------------------------------ - @staticmethod - def axisinfo( unit, axis ): - """: Returns information on how to handle an axis that has Epoch data. - - = INPUT VARIABLES - - unit The units to use for a axis with Epoch data. - - = RETURN VALUE - - Returns a matplotlib AxisInfo data structure that contains - minor/major formatters, major/minor locators, and default - label information. - """ - - majloc = date_ticker.AutoDateLocator() - majfmt = date_ticker.AutoDateFormatter( majloc ) - - return units.AxisInfo( majloc = majloc, - majfmt = majfmt, - label = unit ) - - #------------------------------------------------------------------------ - @staticmethod - def float2epoch( value, unit ): - """: Convert a matplotlib floating-point date into an Epoch of the - specified units. - - = INPUT VARIABLES - - value The matplotlib floating-point date. - - unit The unit system to use for the Epoch. - - = RETURN VALUE - - Returns the value converted to an Epoch in the specified time system. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U - - secPastRef = value * 86400.0 * U.UnitDbl( 1.0, 'sec' ) - return U.Epoch( unit, secPastRef, EpochConverter.jdRef ) - - #------------------------------------------------------------------------ - @staticmethod - def epoch2float( value, unit ): - """: Convert an Epoch value to a float suitible for plotting as a - python datetime object. - - = INPUT VARIABLES - - value An Epoch or list of Epochs that need to be converted. - - unit The units to use for an axis with Epoch data. - - = RETURN VALUE - - Returns the value parameter converted to floats. - """ - return value.julianDate( unit ) - EpochConverter.jdRef - - #------------------------------------------------------------------------ - @staticmethod - def duration2float( value ): - """: Convert a Duration value to a float suitible for plotting as a - python datetime object. - - = INPUT VARIABLES - - value A Duration or list of Durations that need to be converted. - - = RETURN VALUE - - Returns the value parameter converted to floats. - """ - return value.seconds() / 86400.0 - - #------------------------------------------------------------------------ - @staticmethod - def convert( value, unit, axis ): - """: Convert value using unit to a float. If value is a sequence, return - the converted sequence. - - = INPUT VARIABLES - - value The value or list of values that need to be converted. - - unit The units to use for an axis with Epoch data. - - = RETURN VALUE - - Returns the value parameter converted to floats. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U - - isNotEpoch = True - isDuration = False - - if ( iterable(value) and not isinstance(value, six.string_types) ): - if ( len(value) == 0 ): - return [] - else: - return [ EpochConverter.convert( x, unit, axis ) for x in value ] - - if ( isinstance(value, U.Epoch) ): - isNotEpoch = False - elif ( isinstance(value, U.Duration) ): - isDuration = True - - if ( isNotEpoch and not isDuration and - units.ConversionInterface.is_numlike( value ) ): - return value - - if ( unit == None ): - unit = EpochConverter.default_units( value, axis ) - - if ( isDuration ): - return EpochConverter.duration2float( value ) - else: - return EpochConverter.epoch2float( value, unit ) - - #------------------------------------------------------------------------ - @staticmethod - def default_units( value, axis ): - """: Return the default unit for value, or None. - - = INPUT VARIABLES - - value The value or list of values that need units. - - = RETURN VALUE - - Returns the default units to use for value. - """ - frame = None - if ( iterable(value) and not isinstance(value, six.string_types) ): - return EpochConverter.default_units( value[0], axis ) - else: - frame = value.frame() - - return frame +# ========================================================================== + +__all__ = ['EpochConverter'] + + +# ========================================================================== +class EpochConverter(units.ConversionInterface): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for Monte Epoch and Duration classes. + """ + + # julian date reference for "Jan 1, 0001" minus 1 day because + # matplotlib really wants "Jan 0, 0001" + jdRef = 1721425.5 - 1 + + # ----------------------------------------------------------------------- + @staticmethod + def axisinfo(unit, axis): + """: Returns information on how to handle an axis that has Epoch data. + + = INPUT VARIABLES + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + + majloc = date_ticker.AutoDateLocator() + majfmt = date_ticker.AutoDateFormatter(majloc) + + return units.AxisInfo(majloc=majloc, majfmt=majfmt, label=unit) + + # ----------------------------------------------------------------------- + @staticmethod + def float2epoch(value, unit): + """: Convert a matplotlib floating-point date into an Epoch of the + specified units. + + = INPUT VARIABLES + - value The matplotlib floating-point date. + - unit The unit system to use for the Epoch. + + = RETURN VALUE + - Returns the value converted to an Epoch in the specified time system. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U + + secPastRef = value * 86400.0 * U.UnitDbl(1.0, 'sec') + return U.Epoch(unit, secPastRef, EpochConverter.jdRef) + + # ----------------------------------------------------------------------- + @staticmethod + def epoch2float(value, unit): + """: Convert an Epoch value to a float suitible for plotting as a + python datetime object. + + = INPUT VARIABLES + - value An Epoch or list of Epochs that need to be converted. + - unit The units to use for an axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + return value.julianDate(unit) - EpochConverter.jdRef + + # ----------------------------------------------------------------------- + @staticmethod + def duration2float(value): + """: Convert a Duration value to a float suitible for plotting as a + python datetime object. + + = INPUT VARIABLES + - value A Duration or list of Durations that need to be converted. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + return value.seconds() / 86400.0 + + # ----------------------------------------------------------------------- + @staticmethod + def convert(value, unit, axis): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - value The value or list of values that need to be converted. + - unit The units to use for an axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U + + isNotEpoch = True + isDuration = False + + if iterable(value) and not isinstance(value, str): + if len(value) == 0: + return [] + else: + return [EpochConverter.convert(x, unit, axis) for x in value] + + if isinstance(value, U.Epoch): + isNotEpoch = False + elif isinstance(value, U.Duration): + isDuration = True + + if (isNotEpoch and not isDuration and + units.ConversionInterface.is_numlike(value)): + return value + + if unit is None: + unit = EpochConverter.default_units(value, axis) + + if isDuration: + return EpochConverter.duration2float(value) + else: + return EpochConverter.epoch2float(value, unit) + + # ----------------------------------------------------------------------- + @staticmethod + def default_units(value, axis): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + """ + frame = None + if iterable(value) and not isinstance(value, str): + return EpochConverter.default_units(value[0], axis) + else: + frame = value.frame() + + return frame diff --git a/lib/matplotlib/testing/jpl_units/StrConverter.py b/lib/matplotlib/testing/jpl_units/StrConverter.py index b5b8814f7c78..7b6d8b3847fa 100644 --- a/lib/matplotlib/testing/jpl_units/StrConverter.py +++ b/lib/matplotlib/testing/jpl_units/StrConverter.py @@ -1,164 +1,158 @@ -#=========================================================================== +# ========================================================================== # # StrConverter # -#=========================================================================== +# ========================================================================== """StrConverter module containing class StrConverter.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -from six.moves import xrange - import matplotlib.units as units from matplotlib.cbook import iterable - +# # Place all imports before here. -#=========================================================================== - -__all__ = [ 'StrConverter' ] - -#=========================================================================== -class StrConverter( units.ConversionInterface ): - """: A matplotlib converter class. Provides matplotlib conversion - functionality for string data values. - - Valid units for string are: - - 'indexed' : Values are indexed as they are specified for plotting. - - 'sorted' : Values are sorted alphanumerically. - - 'inverted' : Values are inverted so that the first value is on top. - - 'sorted-inverted' : A combination of 'sorted' and 'inverted' - """ - - #------------------------------------------------------------------------ - @staticmethod - def axisinfo( unit, axis ): - """: Returns information on how to handle an axis that has string data. - - = INPUT VARIABLES - - axis The axis using this converter. - - unit The units to use for a axis with string data. - - = RETURN VALUE - - Returns a matplotlib AxisInfo data structure that contains - minor/major formatters, major/minor locators, and default - label information. - """ - - return None - - #------------------------------------------------------------------------ - @staticmethod - def convert( value, unit, axis ): - """: Convert value using unit to a float. If value is a sequence, return - the converted sequence. - - = INPUT VARIABLES - - axis The axis using this converter. - - value The value or list of values that need to be converted. - - unit The units to use for a axis with Epoch data. - - = RETURN VALUE - - Returns the value parameter converted to floats. - """ - - if ( units.ConversionInterface.is_numlike( value ) ): - return value - - if ( value == [] ): - return [] - - # we delay loading to make matplotlib happy - ax = axis.axes - if axis is ax.get_xaxis(): - isXAxis = True - else: - isXAxis = False - - axis.get_major_ticks() - ticks = axis.get_ticklocs() - labels = axis.get_ticklabels() - - labels = [ l.get_text() for l in labels if l.get_text() ] - - if ( not labels ): - ticks = [] - labels = [] - - - if ( not iterable( value ) ): - value = [ value ] - - newValues = [] - for v in value: - if ( (v not in labels) and (v not in newValues) ): - newValues.append( v ) - - for v in newValues: - if ( labels ): - labels.append( v ) - else: - labels = [ v ] - - #DISABLED: This is disabled because matplotlib bar plots do not - #DISABLED: recalculate the unit conversion of the data values - #DISABLED: this is due to design and is not really a bug. - #DISABLED: If this gets changed, then we can activate the following - #DISABLED: block of code. Note that this works for line plots. - #DISABLED if ( unit ): - #DISABLED if ( unit.find( "sorted" ) > -1 ): - #DISABLED labels.sort() - #DISABLED if ( unit.find( "inverted" ) > -1 ): - #DISABLED labels = labels[ ::-1 ] - - # add padding (so they do not appear on the axes themselves) - labels = [ '' ] + labels + [ '' ] - ticks = list(xrange( len(labels) )) - ticks[0] = 0.5 - ticks[-1] = ticks[-1] - 0.5 - - axis.set_ticks( ticks ) - axis.set_ticklabels( labels ) - # we have to do the following lines to make ax.autoscale_view work - loc = axis.get_major_locator() - loc.set_bounds( ticks[0], ticks[-1] ) - - if ( isXAxis ): - ax.set_xlim( ticks[0], ticks[-1] ) - else: - ax.set_ylim( ticks[0], ticks[-1] ) - - result = [] - for v in value: - # If v is not in labels then something went wrong with adding new - # labels to the list of old labels. - errmsg = "This is due to a logic error in the StrConverter class. " - errmsg += "Please report this error and its message in bugzilla." - assert ( v in labels ), errmsg - result.append( ticks[ labels.index(v) ] ) - - ax.viewLim.ignore(-1) - return result - - #------------------------------------------------------------------------ - @staticmethod - def default_units( value, axis ): - """: Return the default unit for value, or None. - - = INPUT VARIABLES - - axis The axis using this converter. - - value The value or list of values that need units. - - = RETURN VALUE - - Returns the default units to use for value. - Return the default unit for value, or None. - """ - - # The default behavior for string indexing. - return "indexed" +# ========================================================================== + +__all__ = ['StrConverter'] + + +# ========================================================================== +class StrConverter(units.ConversionInterface): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for string data values. + + Valid units for string are: + - 'indexed' : Values are indexed as they are specified for plotting. + - 'sorted' : Values are sorted alphanumerically. + - 'inverted' : Values are inverted so that the first value is on top. + - 'sorted-inverted' : A combination of 'sorted' and 'inverted' + """ + + # ----------------------------------------------------------------------- + @staticmethod + def axisinfo(unit, axis): + """: Returns information on how to handle an axis that has string data. + + = INPUT VARIABLES + - axis The axis using this converter. + - unit The units to use for a axis with string data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + + return None + + # ----------------------------------------------------------------------- + @staticmethod + def convert(value, unit, axis): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - axis The axis using this converter. + - value The value or list of values that need to be converted. + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + + if units.ConversionInterface.is_numlike(value): + return value + + if value == []: + return [] + + # we delay loading to make matplotlib happy + ax = axis.axes + if axis is ax.get_xaxis(): + isXAxis = True + else: + isXAxis = False + + axis.get_major_ticks() + ticks = axis.get_ticklocs() + labels = axis.get_ticklabels() + + labels = [l.get_text() for l in labels if l.get_text()] + + if not labels: + ticks = [] + labels = [] + + if not iterable(value): + value = [value] + + newValues = [] + for v in value: + if v not in labels and v not in newValues: + newValues.append(v) + + for v in newValues: + if labels: + labels.append(v) + else: + labels = [v] + + # DISABLED: This is disabled because matplotlib bar plots do not + # DISABLED: recalculate the unit conversion of the data values + # DISABLED: this is due to design and is not really a bug. + # DISABLED: If this gets changed, then we can activate the following + # DISABLED: block of code. Note that this works for line plots. + # DISABLED if (unit): + # DISABLED if (unit.find("sorted") > -1): + # DISABLED labels.sort() + # DISABLED if (unit.find("inverted") > -1): + # DISABLED labels = labels[::-1] + + # add padding (so they do not appear on the axes themselves) + labels = [''] + labels + [''] + ticks = list(range(len(labels))) + ticks[0] = 0.5 + ticks[-1] = ticks[-1] - 0.5 + + axis.set_ticks(ticks) + axis.set_ticklabels(labels) + # we have to do the following lines to make ax.autoscale_view work + loc = axis.get_major_locator() + loc.set_bounds(ticks[0], ticks[-1]) + + if isXAxis: + ax.set_xlim(ticks[0], ticks[-1]) + else: + ax.set_ylim(ticks[0], ticks[-1]) + + result = [] + for v in value: + # If v is not in labels then something went wrong with adding new + # labels to the list of old labels. + errmsg = "This is due to a logic error in the StrConverter class." + errmsg += " Please report this error and its message in bugzilla." + assert v in labels, errmsg + result.append(ticks[labels.index(v)]) + + ax.viewLim.ignore(-1) + return result + + # ----------------------------------------------------------------------- + @staticmethod + def default_units(value, axis): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - axis The axis using this converter. + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + Return the default unit for value, or None. + """ + + # The default behavior for string indexing. + return "indexed" diff --git a/lib/matplotlib/testing/jpl_units/UnitDbl.py b/lib/matplotlib/testing/jpl_units/UnitDbl.py index 20c89308dfd1..b65b6a357bd6 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDbl.py +++ b/lib/matplotlib/testing/jpl_units/UnitDbl.py @@ -1,297 +1,303 @@ -#=========================================================================== +# ========================================================================== # # UnitDbl # -#=========================================================================== +# ========================================================================== """UnitDbl module.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +import operator # # Place all imports before here. -#=========================================================================== +# ========================================================================== -#=========================================================================== +# ========================================================================== class UnitDbl(object): - """Class UnitDbl in development. - """ - #----------------------------------------------------------------------- - # Unit conversion table. Small subset of the full one but enough - # to test the required functions. First field is a scale factor to - # convert the input units to the units of the second field. Only - # units in this table are allowed. - allowed = { - "m" : ( 0.001, "km" ), - "km" : ( 1, "km" ), - "mile" : ( 1.609344, "km" ), - - "rad" : ( 1, "rad" ), - "deg" : ( 1.745329251994330e-02, "rad" ), - - "sec" : ( 1, "sec" ), - "min" : ( 60.0, "sec" ), - "hour" : ( 3600, "sec" ), - } - - _types = { - "km" : "distance", - "rad" : "angle", - "sec" : "time", - } - - #----------------------------------------------------------------------- - def __init__( self, value, units ): - """Create a new UnitDbl object. - - Units are internally converted to km, rad, and sec. The only - valid inputs for units are [ m, km, mile, rad, deg, sec, min, hour ]. - - The field UnitDbl.value will contain the converted value. Use - the convert() method to get a specific type of units back. - - = ERROR CONDITIONS - - If the input units are not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - value The numeric value of the UnitDbl. - - units The string name of the units the value is in. - """ - self.checkUnits( units ) - - data = self.allowed[ units ] - self._value = float( value * data[0] ) - self._units = data[1] - - #----------------------------------------------------------------------- - def convert( self, units ): - """Convert the UnitDbl to a specific set of units. - - = ERROR CONDITIONS - - If the input units are not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - units The string name of the units to convert to. - - = RETURN VALUE - - Returns the value of the UnitDbl in the requested units as a floating - point number. - """ - if self._units == units: - return self._value - - self.checkUnits( units ) - - data = self.allowed[ units ] - if self._units != data[1]: - msg = "Error trying to convert to different units.\n" \ - " Invalid conversion requested.\n" \ - " UnitDbl: %s\n" \ - " Units: %s\n" % ( str( self ), units ) - raise ValueError( msg ) - - return self._value / data[0] - - #----------------------------------------------------------------------- - def __abs__( self ): - """Return the absolute value of this UnitDbl.""" - return UnitDbl( abs( self._value ), self._units ) - - #----------------------------------------------------------------------- - def __neg__( self ): - """Return the negative value of this UnitDbl.""" - return UnitDbl( -self._value, self._units ) - - #----------------------------------------------------------------------- - def __nonzero__( self ): - """Test a UnitDbl for a non-zero value. - - = RETURN VALUE - - Returns true if the value is non-zero. - """ - if six.PY3: - return self._value.__bool__() - else: - return self._value.__nonzero__() - - if six.PY3: - __bool__ = __nonzero__ - - #----------------------------------------------------------------------- - def __cmp__( self, rhs ): - """Compare two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - self.checkSameUnits( rhs, "compare" ) - return cmp( self._value, rhs._value ) - - #----------------------------------------------------------------------- - def __add__( self, rhs ): - """Add two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to add. - - = RETURN VALUE - - Returns the sum of ourselves and the input UnitDbl. - """ - self.checkSameUnits( rhs, "add" ) - return UnitDbl( self._value + rhs._value, self._units ) - - #----------------------------------------------------------------------- - def __sub__( self, rhs ): - """Subtract two UnitDbl's. - - = ERROR CONDITIONS - - If the input rhs units are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to subtract. - - = RETURN VALUE - - Returns the difference of ourselves and the input UnitDbl. - """ - self.checkSameUnits( rhs, "subtract" ) - return UnitDbl( self._value - rhs._value, self._units ) - - #----------------------------------------------------------------------- - def __mul__( self, rhs ): - """Scale a UnitDbl by a value. - - = INPUT VARIABLES - - rhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled UnitDbl. - """ - return UnitDbl( self._value * rhs, self._units ) - - #----------------------------------------------------------------------- - def __rmul__( self, lhs ): - """Scale a UnitDbl by a value. - - = INPUT VARIABLES - - lhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled UnitDbl. - """ - return UnitDbl( self._value * lhs, self._units ) - - #----------------------------------------------------------------------- - def __div__( self, rhs ): - """Divide a UnitDbl by a value. - - = INPUT VARIABLES - - rhs The scalar to divide by. - - = RETURN VALUE - - Returns the scaled UnitDbl. - """ - return UnitDbl( self._value / rhs, self._units ) - - #----------------------------------------------------------------------- - def __str__( self ): - """Print the UnitDbl.""" - return "%g *%s" % ( self._value, self._units ) - - #----------------------------------------------------------------------- - def __repr__( self ): - """Print the UnitDbl.""" - return "UnitDbl( %g, '%s' )" % ( self._value, self._units ) - - #----------------------------------------------------------------------- - def type( self ): - """Return the type of UnitDbl data.""" - return self._types[ self._units ] - - #----------------------------------------------------------------------- - def range( start, stop, step=None ): - """Generate a range of UnitDbl objects. - - Similar to the Python range() method. Returns the range [ - start, stop ) at the requested step. Each element will be a - UnitDbl object. - - = INPUT VARIABLES - - start The starting value of the range. - - stop The stop value of the range. - - step Optional step to use. If set to None, then a UnitDbl of - value 1 w/ the units of the start is used. - - = RETURN VALUE - - Returns a list contianing the requested UnitDbl values. - """ - if step is None: - step = UnitDbl( 1, start._units ) - - elems = [] - - i = 0 - while True: - d = start + i * step - if d >= stop: - break - - elems.append( d ) - i += 1 - - return elems - - range = staticmethod( range ) - - #----------------------------------------------------------------------- - def checkUnits( self, units ): - """Check to see if some units are valid. - - = ERROR CONDITIONS - - If the input units are not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - units The string name of the units to check. - """ - if units not in self.allowed: - msg = "Input units '%s' are not one of the supported types of %s" \ - % ( units, str( list(six.iterkeys(self.allowed)) ) ) - raise ValueError( msg ) - - #----------------------------------------------------------------------- - def checkSameUnits( self, rhs, func ): - """Check to see if units are the same. - - = ERROR CONDITIONS - - If the units of the rhs UnitDbl are not the same as our units, - an error is thrown. - - = INPUT VARIABLES - - rhs The UnitDbl to check for the same units - - func The name of the function doing the check. - """ - if self._units != rhs._units: - msg = "Cannot %s units of different types.\n" \ - "LHS: %s\n" \ - "RHS: %s" % ( func, self._units, rhs._units ) - raise ValueError( msg ) - -#=========================================================================== + """Class UnitDbl in development. + """ + # ---------------------------------------------------------------------- + # Unit conversion table. Small subset of the full one but enough + # to test the required functions. First field is a scale factor to + # convert the input units to the units of the second field. Only + # units in this table are allowed. + allowed = { + "m": (0.001, "km"), + "km": (1, "km"), + "mile": (1.609344, "km"), + + "rad": (1, "rad"), + "deg": (1.745329251994330e-02, "rad"), + + "sec": (1, "sec"), + "min": (60.0, "sec"), + "hour": (3600, "sec"), + } + + _types = { + "km": "distance", + "rad": "angle", + "sec": "time", + } + + # ---------------------------------------------------------------------- + def __init__(self, value, units): + """Create a new UnitDbl object. + + Units are internally converted to km, rad, and sec. The only + valid inputs for units are [m, km, mile, rad, deg, sec, min, hour]. + + The field UnitDbl.value will contain the converted value. Use + the convert() method to get a specific type of units back. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - value The numeric value of the UnitDbl. + - units The string name of the units the value is in. + """ + self.checkUnits(units) + + data = self.allowed[units] + self._value = float(value * data[0]) + self._units = data[1] + + # ---------------------------------------------------------------------- + def convert(self, units): + """Convert the UnitDbl to a specific set of units. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - units The string name of the units to convert to. + + = RETURN VALUE + - Returns the value of the UnitDbl in the requested units as a floating + point number. + """ + if self._units == units: + return self._value + + self.checkUnits(units) + + data = self.allowed[units] + if self._units != data[1]: + msg = "Error trying to convert to different units.\n" \ + " Invalid conversion requested.\n" \ + " UnitDbl: %s\n" \ + " Units: %s\n" % (str(self), units) + raise ValueError(msg) + + return self._value / data[0] + + # ---------------------------------------------------------------------- + def __abs__(self): + """Return the absolute value of this UnitDbl.""" + return UnitDbl(abs(self._value), self._units) + + # ---------------------------------------------------------------------- + def __neg__(self): + """Return the negative value of this UnitDbl.""" + return UnitDbl(-self._value, self._units) + + # ---------------------------------------------------------------------- + def __bool__(self): + """Return the truth value of a UnitDbl.""" + return bool(self._value) + + # ---------------------------------------------------------------------- + def __eq__(self, rhs): + return self._cmp(rhs, operator.eq) + + def __ne__(self, rhs): + return self._cmp(rhs, operator.ne) + + def __lt__(self, rhs): + return self._cmp(rhs, operator.lt) + + def __le__(self, rhs): + return self._cmp(rhs, operator.le) + + def __gt__(self, rhs): + return self._cmp(rhs, operator.gt) + + def __ge__(self, rhs): + return self._cmp(rhs, operator.ge) + + def _cmp(self, rhs, op): + """Compare two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to compare against. + - op The function to do the comparison + + = RETURN VALUE + - Returns op(self, rhs) + """ + self.checkSameUnits(rhs, "compare") + return op(self._value, rhs._value) + + # ---------------------------------------------------------------------- + def __add__(self, rhs): + """Add two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to add. + + = RETURN VALUE + - Returns the sum of ourselves and the input UnitDbl. + """ + self.checkSameUnits(rhs, "add") + return UnitDbl(self._value + rhs._value, self._units) + + # ---------------------------------------------------------------------- + def __sub__(self, rhs): + """Subtract two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input UnitDbl. + """ + self.checkSameUnits(rhs, "subtract") + return UnitDbl(self._value - rhs._value, self._units) + + # ---------------------------------------------------------------------- + def __mul__(self, rhs): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl(self._value * rhs, self._units) + + # ---------------------------------------------------------------------- + def __rmul__(self, lhs): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - lhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl(self._value * lhs, self._units) + + # ---------------------------------------------------------------------- + def __div__(self, rhs): + """Divide a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to divide by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl(self._value / rhs, self._units) + + # ---------------------------------------------------------------------- + def __str__(self): + """Print the UnitDbl.""" + return "%g *%s" % (self._value, self._units) + + # ---------------------------------------------------------------------- + def __repr__(self): + """Print the UnitDbl.""" + return "UnitDbl(%g, '%s')" % (self._value, self._units) + + # ---------------------------------------------------------------------- + def type(self): + """Return the type of UnitDbl data.""" + return self._types[self._units] + + # ---------------------------------------------------------------------- + def range(start, stop, step=None): + """Generate a range of UnitDbl objects. + + Similar to the Python range() method. Returns the range [ + start, stop) at the requested step. Each element will be a + UnitDbl object. + + = INPUT VARIABLES + - start The starting value of the range. + - stop The stop value of the range. + - step Optional step to use. If set to None, then a UnitDbl of + value 1 w/ the units of the start is used. + + = RETURN VALUE + - Returns a list contianing the requested UnitDbl values. + """ + if step is None: + step = UnitDbl(1, start._units) + + elems = [] + + i = 0 + while True: + d = start + i * step + if d >= stop: + break + + elems.append(d) + i += 1 + + return elems + + range = staticmethod(range) + + # ---------------------------------------------------------------------- + def checkUnits(self, units): + """Check to see if some units are valid. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - units The string name of the units to check. + """ + if units not in self.allowed: + raise ValueError("Input units '%s' are not one of the supported " + "types of %s" % ( + units, list(self.allowed.keys()))) + + # ---------------------------------------------------------------------- + def checkSameUnits(self, rhs, func): + """Check to see if units are the same. + + = ERROR CONDITIONS + - If the units of the rhs UnitDbl are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to check for the same units + - func The name of the function doing the check. + """ + if self._units != rhs._units: + msg = "Cannot %s units of different types.\n" \ + "LHS: %s\n" \ + "RHS: %s" % (func, self._units, rhs._units) + raise ValueError(msg) + +# ========================================================================== diff --git a/lib/matplotlib/testing/jpl_units/UnitDblConverter.py b/lib/matplotlib/testing/jpl_units/UnitDblConverter.py index 41fe8e19a9b2..cf2b4a2c4784 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDblConverter.py +++ b/lib/matplotlib/testing/jpl_units/UnitDblConverter.py @@ -1,159 +1,151 @@ -#=========================================================================== +# ========================================================================== # # UnitDblConverter # -#=========================================================================== - +# ========================================================================== """UnitDblConverter module containing class UnitDblConverter.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import numpy as np import matplotlib.units as units import matplotlib.projections.polar as polar from matplotlib.cbook import iterable # # Place all imports before here. -#=========================================================================== +# ========================================================================== -__all__ = [ 'UnitDblConverter' ] +__all__ = ['UnitDblConverter'] -#=========================================================================== +# ========================================================================== # A special function for use with the matplotlib FuncFormatter class # for formatting axes with radian units. # This was copied from matplotlib example code. -def rad_fn(x, pos = None ): - """Radian function formatter.""" - n = int((x / np.pi) * 2.0 + 0.25) - if n == 0: - return str(x) - elif n == 1: - return r'$\pi/2$' - elif n == 2: - return r'$\pi$' - elif n % 2 == 0: - return r'$%s\pi$' % (n/2,) - else: - return r'$%s\pi/2$' % (n,) - -#=========================================================================== -class UnitDblConverter( units.ConversionInterface ): - """: A matplotlib converter class. Provides matplotlib conversion - functionality for the Monte UnitDbl class. - """ - - # default for plotting - defaults = { - "distance" : 'km', - "angle" : 'deg', - "time" : 'sec', - } - - #------------------------------------------------------------------------ - @staticmethod - def axisinfo( unit, axis ): - """: Returns information on how to handle an axis that has Epoch data. - - = INPUT VARIABLES - - unit The units to use for a axis with Epoch data. - - = RETURN VALUE - - Returns a matplotlib AxisInfo data structure that contains - minor/major formatters, major/minor locators, and default - label information. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U - - # Check to see if the value used for units is a string unit value - # or an actual instance of a UnitDbl so that we can use the unit - # value for the default axis label value. - if ( unit ): - if ( isinstance( unit, six.string_types ) ): - label = unit - else: - label = unit.label() - else: - label = None - - if ( label == "deg" ) and isinstance( axis.axes, polar.PolarAxes ): - # If we want degrees for a polar plot, use the PolarPlotFormatter - majfmt = polar.PolarAxes.ThetaFormatter() - else: - majfmt = U.UnitDblFormatter( useOffset = False ) - - return units.AxisInfo( majfmt = majfmt, label = label ) - - #------------------------------------------------------------------------ - @staticmethod - def convert( value, unit, axis ): - """: Convert value using unit to a float. If value is a sequence, return - the converted sequence. - - = INPUT VARIABLES - - value The value or list of values that need to be converted. - - unit The units to use for a axis with Epoch data. - - = RETURN VALUE - - Returns the value parameter converted to floats. - """ - # Delay-load due to circular dependencies. - import matplotlib.testing.jpl_units as U - - isNotUnitDbl = True - - if ( iterable(value) and not isinstance(value, six.string_types) ): - if ( len(value) == 0 ): - return [] - else: - return [ UnitDblConverter.convert( x, unit, axis ) for x in value ] - - # We need to check to see if the incoming value is actually a UnitDbl and - # set a flag. If we get an empty list, then just return an empty list. - if ( isinstance(value, U.UnitDbl) ): - isNotUnitDbl = False - - # If the incoming value behaves like a number, but is not a UnitDbl, - # then just return it because we don't know how to convert it - # (or it is already converted) - if ( isNotUnitDbl and units.ConversionInterface.is_numlike( value ) ): - return value - - # If no units were specified, then get the default units to use. - if ( unit == None ): - unit = UnitDblConverter.default_units( value, axis ) - - # Convert the incoming UnitDbl value/values to float/floats - if isinstance( axis.axes, polar.PolarAxes ) and value.type() == "angle": - # Guarantee that units are radians for polar plots. - return value.convert( "rad" ) - - return value.convert( unit ) - - #------------------------------------------------------------------------ - @staticmethod - def default_units( value, axis ): - """: Return the default unit for value, or None. - - = INPUT VARIABLES - - value The value or list of values that need units. - - = RETURN VALUE - - Returns the default units to use for value. - Return the default unit for value, or None. - """ - - # Determine the default units based on the user preferences set for - # default units when printing a UnitDbl. - if ( iterable(value) and not isinstance(value, six.string_types) ): - return UnitDblConverter.default_units( value[0], axis ) - else: - return UnitDblConverter.defaults[ value.type() ] +def rad_fn(x, pos=None): + """Radian function formatter.""" + n = int((x / np.pi) * 2.0 + 0.25) + if n == 0: + return str(x) + elif n == 1: + return r'$\pi/2$' + elif n == 2: + return r'$\pi$' + elif n % 2 == 0: + return r'$%s\pi$' % (n/2,) + else: + return r'$%s\pi/2$' % (n,) + + +# ========================================================================== +class UnitDblConverter(units.ConversionInterface): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for the Monte UnitDbl class. + """ + # default for plotting + defaults = { + "distance": 'km', + "angle": 'deg', + "time": 'sec', + } + + # ----------------------------------------------------------------------- + @staticmethod + def axisinfo(unit, axis): + """: Returns information on how to handle an axis that has Epoch data. + + = INPUT VARIABLES + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U + + # Check to see if the value used for units is a string unit value + # or an actual instance of a UnitDbl so that we can use the unit + # value for the default axis label value. + if unit: + label = unit if isinstance(unit, str) else unit.label() + else: + label = None + + if (label == "deg") and isinstance(axis.axes, polar.PolarAxes): + # If we want degrees for a polar plot, use the PolarPlotFormatter + majfmt = polar.PolarAxes.ThetaFormatter() + else: + majfmt = U.UnitDblFormatter(useOffset=False) + + return units.AxisInfo(majfmt=majfmt, label=label) + + # ----------------------------------------------------------------------- + @staticmethod + def convert(value, unit, axis): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - value The value or list of values that need to be converted. + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + # Delay-load due to circular dependencies. + import matplotlib.testing.jpl_units as U + + isNotUnitDbl = True + + if iterable(value) and not isinstance(value, str): + if len(value) == 0: + return [] + else: + return [UnitDblConverter.convert(x, unit, axis) for x in value] + + # We need to check to see if the incoming value is actually a + # UnitDbl and set a flag. If we get an empty list, then just + # return an empty list. + if (isinstance(value, U.UnitDbl)): + isNotUnitDbl = False + + # If the incoming value behaves like a number, but is not a UnitDbl, + # then just return it because we don't know how to convert it + # (or it is already converted) + if (isNotUnitDbl and units.ConversionInterface.is_numlike(value)): + return value + + # If no units were specified, then get the default units to use. + if unit is None: + unit = UnitDblConverter.default_units(value, axis) + + # Convert the incoming UnitDbl value/values to float/floats + if isinstance(axis.axes, polar.PolarAxes) and value.type() == "angle": + # Guarantee that units are radians for polar plots. + return value.convert("rad") + + return value.convert(unit) + + # ----------------------------------------------------------------------- + @staticmethod + def default_units(value, axis): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + Return the default unit for value, or None. + """ + + # Determine the default units based on the user preferences set for + # default units when printing a UnitDbl. + if iterable(value) and not isinstance(value, str): + return UnitDblConverter.default_units(value[0], axis) + else: + return UnitDblConverter.defaults[value.type()] diff --git a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py index 269044748c58..25ebf6042c78 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py +++ b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py @@ -1,47 +1,43 @@ -#=========================================================================== +# ========================================================================== # # UnitDblFormatter # -#=========================================================================== +# ========================================================================== """UnitDblFormatter module containing class UnitDblFormatter.""" -#=========================================================================== +# ========================================================================== # Place all imports after here. # -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - import matplotlib.ticker as ticker # # Place all imports before here. -#=========================================================================== - -__all__ = [ 'UnitDblFormatter' ] - -#=========================================================================== -class UnitDblFormatter( ticker.ScalarFormatter ): - """The formatter for UnitDbl data types. This allows for formatting - with the unit string. - """ - def __init__( self, *args, **kwargs ): - 'The arguments are identical to matplotlib.ticker.ScalarFormatter.' - ticker.ScalarFormatter.__init__( self, *args, **kwargs ) - - def __call__( self, x, pos = None ): - 'Return the format for tick val x at position pos' - if len(self.locs) == 0: - return '' - else: - return '{:.12}'.format(x) - - def format_data_short( self, value ): - "Return the value formatted in 'short' format." - return '{:.12}'.format(value) - - def format_data( self, value ): - "Return the value formatted into a string." - return '{:.12}'.format(value) +# ========================================================================== + +__all__ = ['UnitDblFormatter'] + + +# ========================================================================== +class UnitDblFormatter(ticker.ScalarFormatter): + """The formatter for UnitDbl data types. This allows for formatting + with the unit string. + """ + def __init__(self, *args, **kwargs): + 'The arguments are identical to matplotlib.ticker.ScalarFormatter.' + ticker.ScalarFormatter.__init__(self, *args, **kwargs) + + def __call__(self, x, pos=None): + 'Return the format for tick val x at position pos' + if len(self.locs) == 0: + return '' + else: + return '{:.12}'.format(x) + + def format_data_short(self, value): + "Return the value formatted in 'short' format." + return '{:.12}'.format(value) + + def format_data(self, value): + "Return the value formatted into a string." + return '{:.12}'.format(value) diff --git a/lib/matplotlib/testing/jpl_units/__init__.py b/lib/matplotlib/testing/jpl_units/__init__.py index 074af4e83589..47e6c3dee554 100644 --- a/lib/matplotlib/testing/jpl_units/__init__.py +++ b/lib/matplotlib/testing/jpl_units/__init__.py @@ -1,4 +1,4 @@ -#======================================================================= +# ====================================================================== """ This is a sample set of units for use with testing unit conversion @@ -30,11 +30,7 @@ in one frame may not be the same in another. """ -#======================================================================= -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six +# ====================================================================== from .Duration import Duration from .Epoch import Epoch @@ -46,7 +42,7 @@ from .UnitDblFormatter import UnitDblFormatter -#======================================================================= +# ====================================================================== __version__ = "1.0" @@ -58,31 +54,33 @@ 'UnitDblFormatter', ] -#======================================================================= + +# ====================================================================== def register(): - """Register the unit conversion classes with matplotlib.""" - import matplotlib.units as mplU + """Register the unit conversion classes with matplotlib.""" + import matplotlib.units as mplU - mplU.registry[ str ] = StrConverter() - mplU.registry[ Epoch ] = EpochConverter() - mplU.registry[ Duration ] = EpochConverter() - mplU.registry[ UnitDbl ] = UnitDblConverter() + mplU.registry[str] = StrConverter() + mplU.registry[Epoch] = EpochConverter() + mplU.registry[Duration] = EpochConverter() + mplU.registry[UnitDbl] = UnitDblConverter() -#======================================================================= +# ====================================================================== # Some default unit instances + # Distances -m = UnitDbl( 1.0, "m" ) -km = UnitDbl( 1.0, "km" ) -mile = UnitDbl( 1.0, "mile" ) +m = UnitDbl(1.0, "m") +km = UnitDbl(1.0, "km") +mile = UnitDbl(1.0, "mile") # Angles -deg = UnitDbl( 1.0, "deg" ) -rad = UnitDbl( 1.0, "rad" ) +deg = UnitDbl(1.0, "deg") +rad = UnitDbl(1.0, "rad") # Time -sec = UnitDbl( 1.0, "sec" ) -min = UnitDbl( 1.0, "min" ) -hr = UnitDbl( 1.0, "hour" ) -day = UnitDbl( 24.0, "hour" ) -sec = UnitDbl( 1.0, "sec" ) +sec = UnitDbl(1.0, "sec") +min = UnitDbl(1.0, "min") +hr = UnitDbl(1.0, "hour") +day = UnitDbl(24.0, "hour") +sec = UnitDbl(1.0, "sec") diff --git a/lib/matplotlib/testing/noseclasses.py b/lib/matplotlib/testing/noseclasses.py deleted file mode 100644 index 2983b93d7fa6..000000000000 --- a/lib/matplotlib/testing/noseclasses.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -The module testing.noseclasses is deprecated as of 2.1 -""" - -from __future__ import (absolute_import, division, print_function, - unicode_literals) -try: - from ._nose.plugins.knownfailure import KnownFailure as _KnownFailure - has_nose = True -except ImportError: - has_nose = False - _KnownFailure = object - -from .. import cbook - -cbook.warn_deprecated( - since="2.1", - message="The noseclass module has been deprecated in 2.1 and will " - "be removed in matplotlib 2.3.") - - -@cbook.deprecated("2.1") -class KnownFailure(_KnownFailure): - def __init__(self): - if not has_nose: - raise ImportError("Need nose for this plugin.") diff --git a/lib/matplotlib/tests/__init__.py b/lib/matplotlib/tests/__init__.py index 271e67ad6422..855d68142300 100644 --- a/lib/matplotlib/tests/__init__.py +++ b/lib/matplotlib/tests/__init__.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six - import difflib import os @@ -17,22 +13,3 @@ 'This is most likely because the test data is not installed. ' 'You may need to install matplotlib from source to get the ' 'test data.') - - -@cbook.deprecated("2.1") -def assert_str_equal(reference_str, test_str, - format_str=('String {str1} and {str2} do not ' - 'match:\n{differences}')): - """ - Assert the two strings are equal. If not, fail and print their - diffs using difflib. - - """ - if reference_str != test_str: - diff = difflib.unified_diff(reference_str.splitlines(1), - test_str.splitlines(1), - 'Reference', 'Test result', - '', '', 0) - raise ValueError(format_str.format(str1=reference_str, - str2=test_str, - differences=''.join(diff))) diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/connection_styles.png b/lib/matplotlib/tests/baseline_images/test_arrow_patches/connection_styles.png new file mode 100644 index 000000000000..8255d0d3bf58 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_arrow_patches/connection_styles.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf index 39a7fac1c5b7..f4090c83aad3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png index 4097aad6bc12..abca7ea4a6bb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg index 0a39e4cf487b..b60e679f6b3a 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg @@ -1,8 +1,8 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +L 0 3.5 +" id="mba93285ecf" style="stroke:#000000;stroke-width:0.8;"/> - + @@ -17711,7 +14417,7 @@ L 73.1875 35.5 L 73.1875 27.203125 L 10.59375 27.203125 z -" id="DejaVuSans-2212"/> +" id="DejaVuSans-8722"/> +" id="DejaVuSans-51"/>
    - - - + + + - - - - - - + - + @@ -17788,23 +14489,18 @@ Q 49.859375 40.875 45.40625 35.40625 Q 44.1875 33.984375 37.640625 27.21875 Q 31.109375 20.453125 19.1875 8.296875 z -" id="DejaVuSans-32"/> +" id="DejaVuSans-50"/>
    - - - + + + - - - - - - + - + @@ -17822,23 +14518,18 @@ L 54.390625 8.296875 L 54.390625 0 L 12.40625 0 z -" id="DejaVuSans-31"/> +" id="DejaVuSans-49"/>
    - - - + + + - - - - - - + - + @@ -17864,76 +14555,56 @@ Q 6.59375 17.96875 6.59375 36.375 Q 6.59375 54.828125 13.0625 64.515625 Q 19.53125 74.21875 31.78125 74.21875 z -" id="DejaVuSans-30"/> +" id="DejaVuSans-48"/>
    - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + @@ -17955,22 +14626,17 @@ L 37.796875 17.1875 L 4.890625 17.1875 L 4.890625 26.703125 z -" id="DejaVuSans-34"/> +" id="DejaVuSans-52"/> - - + + - - - - - - + - + @@ -17999,391 +14665,3371 @@ Q 35.15625 39.890625 26.703125 39.890625 Q 22.75 39.890625 18.8125 39.015625 Q 14.890625 38.140625 10.796875 36.28125 z -" id="DejaVuSans-35"/> +" id="DejaVuSans-53"/> - - + + - - - - - - - - - + +L -3.5 0 +" id="mc8a02073fc" style="stroke:#000000;stroke-width:0.8;"/> - + - - - + + + - - - - - - + - + - - - + + + - - - - - - + - + - - - + + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + - - - - - - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + @@ -18394,7 +18040,7 @@ L 21 12.40625 L 21 0 L 10.6875 0 z -" id="DejaVuSans-2e"/> +" id="DejaVuSans-46"/> +" id="DejaVuSans-54"/> - - - - - + + + + + - + - + - - - - - + + + + + - + - + @@ -18497,116 +18143,183 @@ Q 38.140625 66.40625 31.78125 66.40625 Q 25.390625 66.40625 21.84375 63.234375 Q 18.3125 60.0625 18.3125 54.390625 z -" id="DejaVuSans-38"/> +" id="DejaVuSans-56"/> - - - - - + + + + + - + - + - - - - - + + + + + - + - + - - - - + + + + - + - + - - - - + + + + - + - + - - - - + + + + - + - + - - - - + + + + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf index f79e7605a34a..ac6f579cdafc 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png index 2090e4d208a3..0aa9049a87ae 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg index d4dba2221dc8..2cfa55ec7696 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg @@ -1,8 +1,8 @@ - - + + - - - - - + - + - + - + +" style="fill:url(#he7211442b0);fill-opacity:0.5;"/> - - + - + - + - + +" style="fill:url(#hc8e4ad36cd);fill-opacity:0.5;"/> - - + - + - + - + +" style="fill:url(#h8124db33bb);fill-opacity:0.5;"/> - - + - + - + - + - + +" style="fill:url(#hb1742779c6);fill-opacity:0.5;"/> - - + - + +" style="fill:url(#hcbba77881f);fill-opacity:0.5;"/> - - + - + - + +" style="fill:url(#h11d5e7b694);fill-opacity:0.5;"/> - - + - + +" style="fill:url(#h3be0aff0a3);fill-opacity:0.5;"/> - - + - - - - - - - - - - - - - +" style="fill:url(#h779f96334f);fill-opacity:0.5;"/> +L 0 3.5 +" id="ma57288aa69" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - - - - + +L -3.5 0 +" id="m9a4ee1f972" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - + - - - - - - - - +L 0 3.5 +" id="m0b727597d4" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - - - - + +L -3.5 0 +" id="m4baa07b7ca" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf index 919ae14014e6..d7a58f772a40 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.png b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.png index 9ec9980c498f..1979567f6b24 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.png and b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg index 240f7789944a..8111cb56486a 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/hist2d_transpose.svg @@ -1,8 +1,8 @@ - - + + - - - - - - - - - - - - - - - + - - - - - - - - +L 0 3.5 +" id="m430282c97a" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - - + - - - - - - + - - - - - - - - - + - - - - - - - - - + +L -3.5 0 +" id="md176de54a1" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolor_datetime_axis.png b/lib/matplotlib/tests/baseline_images/test_axes/pcolor_datetime_axis.png index c5901ad07c98..e91562470215 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pcolor_datetime_axis.png and b/lib/matplotlib/tests/baseline_images/test_axes/pcolor_datetime_axis.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg index c472c8904f2d..c94b782c5ee0 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh.svg @@ -1,7 +1,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + @@ -22956,13 +23975,13 @@ L 518.4 43.2 - + - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_datetime_axis.png b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_datetime_axis.png index c5901ad07c98..e91562470215 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_datetime_axis.png and b/lib/matplotlib/tests/baseline_images/test_axes/pcolormesh_datetime_axis.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png new file mode 100644 index 000000000000..1c0c6c2c0577 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/single_date.pdf b/lib/matplotlib/tests/baseline_images/test_axes/single_date.pdf deleted file mode 100644 index 35043f550854..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/single_date.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/single_date.png b/lib/matplotlib/tests/baseline_images/test_axes/single_date.png index 08d8390448dd..9df3334340c2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/single_date.png and b/lib/matplotlib/tests/baseline_images/test_axes/single_date.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/single_date.svg b/lib/matplotlib/tests/baseline_images/test_axes/single_date.svg deleted file mode 100644 index b6a1c013fc2b..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/single_date.svg +++ /dev/null @@ -1,1259 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_axes/titletwiny.png b/lib/matplotlib/tests/baseline_images/test_axes/titletwiny.png new file mode 100644 index 000000000000..670a4bebbd65 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/titletwiny.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png index 1ff3c2422e46..84e95b527442 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png index 97cf2dca98a0..32c5089c6404 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout10.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png index e0bb47296b2c..4916c44892c1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_datetime_axis.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_datetime_axis.png index 91d04aa441e5..11e17fc64d7e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_datetime_axis.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_datetime_axis.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_labels_size_color.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_labels_size_color.png index 21afe6eccab5..8021a444cdfe 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_labels_size_color.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_labels_size_color.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png new file mode 100644 index 000000000000..42c71cb561fe Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_contour/contour_log_extension.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf index cbe59ab0e74c..8e4a28a55362 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png index 97cce15d3a1f..d6acf227567a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg index fb8905928bdc..3fbf8b835e46 100644 --- a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg +++ b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_labels.svg @@ -1,7 +1,7 @@ - + - + - + - + - + - + - + - + - + - + @@ -109,68 +109,72 @@ L 0 3.5 +" id="m57021d4b2f" style="stroke:#000000;stroke-width:0.8;"/> - + - + - + - + - + - + + + +" style="fill:none;stroke:#443983;stroke-width:1.5;"/> - - + - + +" style="fill:none;stroke:#31688e;stroke-width:1.5;"/> - - + - - - + - + +" style="fill:none;stroke:#35b779;stroke-width:1.5;"/> - - + +" style="fill:none;stroke:#90d743;stroke-width:1.5;"/> + - + +" id="DejaVuSans-51"/> +" id="DejaVuSans-46"/> +" id="DejaVuSans-48"/> - - - - - - + + + + + + - + +" id="DejaVuSans-52"/> +" id="DejaVuSans-53"/> - - - - - + + + + + - + +" id="DejaVuSans-54"/> - - - - - - + + + + + + - + diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png index 59176950533e..723e501ed287 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_test_label_transforms.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_axhline.png b/lib/matplotlib/tests/baseline_images/test_dates/date_axhline.png index 8077953f5a57..57c4ad763f37 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_dates/date_axhline.png and b/lib/matplotlib/tests/baseline_images/test_dates/date_axhline.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_axhspan.png b/lib/matplotlib/tests/baseline_images/test_dates/date_axhspan.png index 2b3faae7396c..f13d879e9f53 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_dates/date_axhspan.png and b/lib/matplotlib/tests/baseline_images/test_dates/date_axhspan.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_axvline.png b/lib/matplotlib/tests/baseline_images/test_dates/date_axvline.png index 856b6cab10a0..f6b19c37af88 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_dates/date_axvline.png and b/lib/matplotlib/tests/baseline_images/test_dates/date_axvline.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png b/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png index 4e55706c55fd..4b8d15155717 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png and b/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png index a94b635b1c64..904e0c3d44a0 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png and b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf index 9a5b3f3d3ca5..18b2e46e0f11 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf and b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png index 4ba990cdcdc2..aa1d865353ec 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png and b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg index 3790836f4405..c12572fa9384 100644 --- a/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg +++ b/lib/matplotlib/tests/baseline_images/test_lines/line_collection_dashes.svg @@ -1,8 +1,8 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +L 0 3.5 +" id="m3c049a1f51" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - + +L -3.5 0 +" id="mee99decfa6" style="stroke:#000000;stroke-width:0.8;"/> - + - - - - + + + - + - - + + - + - + + + - + - - + + - + - + + + - + - - + + - + - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd.pdf b/lib/matplotlib/tests/baseline_images/test_path/xkcd.pdf deleted file mode 100644 index 290886947443..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_path/xkcd.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd.png b/lib/matplotlib/tests/baseline_images/test_path/xkcd.png index 2900e407b038..fd486c42305f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_path/xkcd.png and b/lib/matplotlib/tests/baseline_images/test_path/xkcd.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd.svg b/lib/matplotlib/tests/baseline_images/test_path/xkcd.svg deleted file mode 100644 index 2fa9277fbb30..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_path/xkcd.svg +++ /dev/null @@ -1,12164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png b/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png new file mode 100644 index 000000000000..c4224f74c1ec Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_path/xkcd_marker.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf index 1adf8eef8a5b..d74333643910 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf and b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png index 90e62bdbea4b..07410efba929 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png and b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg index d65624e4d375..988cc34ebe56 100644 --- a/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg +++ b/lib/matplotlib/tests/baseline_images/test_patheffects/collection.svg @@ -1,8 +1,8 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +" id="DejaVuSans-49"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + - - - - - - - - - - - - - + + - - - - + + - - - - - - + + - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - + + + + + + + - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png b/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png index 0f5ab85ea029..c1b2e7872fdb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png and b/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf index c45f9f78a8f0..afc2f2966475 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png index 00d0b177c7ae..fed48765d45e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg index 816041b724d4..ae46e125d162 100644 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg +++ b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg @@ -1,8 +1,8 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +" style="fill:#ffb500;stroke:#ffb500;stroke-linecap:round;stroke-width:2;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +" style="fill:#ffad00;stroke:#ffad00;stroke-linecap:round;stroke-width:2;"/> - - - - + +" style="fill:#ffcd00;stroke:#ffcd00;stroke-linecap:round;stroke-width:2;"/> - - - - - - - - - - - - - - + +" style="fill:#ffb000;stroke:#ffb000;stroke-linecap:round;stroke-width:2;"/> - - - - - - - - + - - - + - + + + + - - + +" style="fill:#ff9700;stroke:#ff9700;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ff9d00;stroke:#ff9d00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ff8e00;stroke:#ff8e00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ff7e00;stroke:#ff7e00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ffcc00;stroke:#ffcc00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ffe500;stroke:#ffe500;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#fff500;stroke:#fff500;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#fff500;stroke:#fff500;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ffff00;stroke:#ffff00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ffbd00;stroke:#ffbd00;stroke-linecap:round;stroke-width:2;"/> - - + +" style="fill:#ff7e00;stroke:#ff7e00;stroke-linecap:round;stroke-width:2;"/> - - - - - - - - - - - - - - - - - - - - - - - - + - - - + - - - + - - - - + + +" style="fill:#fff100;stroke:#fff100;stroke-linecap:round;stroke-width:2;"/> - - + - - - + - - - + - - - + - - - - - - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + - - - - + - + + + + - - - - - - - - - + - + - - - - - - - - - + - + - - - - - - - - - - - + - + - - - - - - - - - - - + - + - - - - - - - - - - - + - + - - - - - - - - + - + - - - - - - - + + + + - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png index 77786b3e4875..8bfb6ba1b63a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_direction.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf index 1e3a72baa567..9f29d679f5b8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png index 3fe60b95e572..c1147e478698 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg index 912b24ff76d5..8ce3e3ed3eae 100644 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg +++ b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg @@ -1,8 +1,8 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - + + - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - + + - - - - - - - - - + + - - - - - - + + - - - - - - - - - - - + + - - - - - - + + - - - - - - - - - - - - - + + - - - - + + - - - - - - - + + - - - - - + + - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +" style="stroke:#000000;stroke-linecap:round;stroke-width:1.034552;"/> - - - - - - - - + +" style="stroke:#000000;stroke-linecap:round;stroke-width:1.701266;"/> - - - - - - - - - - - - - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf index b635b4f3c7bc..d06b6cf7159c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png index 88610c78abfa..3a273017491d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg index b4d4d4b270f6..f027722f3e91 100644 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg +++ b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg @@ -1,8 +1,8 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + +" style="fill:#2d7dbb;stroke:#2d7dbb;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3e8ec4;stroke:#3e8ec4;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3282be;stroke:#3282be;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3989c1;stroke:#3989c1;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#58a1cf;stroke:#58a1cf;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4493c7;stroke:#4493c7;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#7fb9da;stroke:#7fb9da;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#94c4df;stroke:#94c4df;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#74b3d8;stroke:#74b3d8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#bcd7eb;stroke:#bcd7eb;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#d3e3f3;stroke:#d3e3f3;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#e6f0f9;stroke:#e6f0f9;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#74b3d8;stroke:#74b3d8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#56a0ce;stroke:#56a0ce;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#7cb7da;stroke:#7cb7da;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3b8bc2;stroke:#3b8bc2;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4896c8;stroke:#4896c8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3080bd;stroke:#3080bd;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#2676b8;stroke:#2676b8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#1f6eb3;stroke:#1f6eb3;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#1b69af;stroke:#1b69af;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#2f7fbc;stroke:#2f7fbc;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4d99ca;stroke:#4d99ca;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#519ccc;stroke:#519ccc;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#7fb9da;stroke:#7fb9da;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#5aa2cf;stroke:#5aa2cf;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#a3cce3;stroke:#a3cce3;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#2777b8;stroke:#2777b8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#9cc9e1;stroke:#9cc9e1;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#5da5d1;stroke:#5da5d1;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#6dafd7;stroke:#6dafd7;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#69add5;stroke:#69add5;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3a8ac2;stroke:#3a8ac2;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#519ccc;stroke:#519ccc;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4b98ca;stroke:#4b98ca;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#6aaed6;stroke:#6aaed6;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3f8fc5;stroke:#3f8fc5;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#57a0ce;stroke:#57a0ce;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#7fb9da;stroke:#7fb9da;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4896c8;stroke:#4896c8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#68acd5;stroke:#68acd5;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#64a9d3;stroke:#64a9d3;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#79b5d9;stroke:#79b5d9;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#71b1d7;stroke:#71b1d7;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#6aaed6;stroke:#6aaed6;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3282be;stroke:#3282be;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#1460a8;stroke:#1460a8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#084f99;stroke:#084f99;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#08509b;stroke:#08509b;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#08458a;stroke:#08458a;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#083c7d;stroke:#083c7d;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#083c7d;stroke:#083c7d;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#083c7d;stroke:#083c7d;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#08458a;stroke:#08458a;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#08468b;stroke:#08468b;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4695c8;stroke:#4695c8;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#4997c9;stroke:#4997c9;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3181bd;stroke:#3181bd;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#3888c1;stroke:#3888c1;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#65aad4;stroke:#65aad4;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#a8cee4;stroke:#a8cee4;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#084e98;stroke:#084e98;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#63a8d3;stroke:#63a8d3;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#2f7fbc;stroke:#2f7fbc;stroke-linecap:round;stroke-width:1.5;"/> - - + +" style="fill:#1865ac;stroke:#1865ac;stroke-linecap:round;stroke-width:1.5;"/> - - + - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png index 2ccb71c0581f..a71e6ee90226 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_maxlength.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf index c312bb6d357e..60d0bd0af49b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png index 734179dc3a7d..ef60a9e7e3c8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png and b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg index 38bb079de5f7..1e484908206c 100644 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg +++ b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_startpoints.svg @@ -1,8 +1,8 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +L 0 3.5 +" id="m6fa6f7c42e" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - - - - + +L -3.5 0 +" id="m78f8f0da60" style="stroke:#000000;stroke-width:0.8;"/> - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_as_path_opacity.svg b/lib/matplotlib/tests/baseline_images/test_text/text_as_path_opacity.svg new file mode 100644 index 000000000000..a7fdb7707994 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_text/text_as_path_opacity.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg new file mode 100644 index 000000000000..69d287e3536c --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_text/text_as_text_opacity.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + 50% using `color` + + + 50% using `alpha` + + + 50% using `alpha` and 100% `color` + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf index 95155db23c99..f65e0cd9e57e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png index 9a3c3186ea57..9429546bb10a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png and b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg index 64674b5f5bd2..1144e3c0b141 100644 --- a/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg +++ b/lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg @@ -1,8 +1,8 @@ - - + + - - - +" style="fill:#48186a;"/> - +" style="fill:#424086;"/> - +" style="fill:#33638d;"/> - +" style="fill:#26828e;"/> - +" style="fill:#1fa088;"/> - +" style="fill:#3fbc73;"/> - +" style="fill:#84d44b;"/> - +" style="fill:#d8e219;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - +" id="mfd12c44519" style="stroke:#1f77b4;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.343969;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.992787;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.58805;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.469881;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:5.163474;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.216076;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.186015;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.292078;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.316706;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.930897;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.8223;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.678638;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.364802;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.586196;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.518533;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.377069;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.428706;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.320976;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.452545;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.740901;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.170965;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:5.056674;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.287501;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.929263;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.042637;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.395039;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.839752;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.754237;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.228663;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.826903;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.947553;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.499873;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.204768;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.018506;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.157335;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.217392;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.248936;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.774416;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.089037;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.567302;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.930279;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.500934;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:4.083729;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.496781;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.158756;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.653559;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.949675;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.420672;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:0.447127;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.710205;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:2.409923;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:3.465176;"/> - - + +" style="fill:#1f77b4;stroke:#1f77b4;stroke-linecap:round;stroke-width:1.948223;"/> - - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png new file mode 100644 index 000000000000..e071860dfde6 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_widgets/check_bunch_of_radio_buttons.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png index c1ed74347bfe..e96085d9bffd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png and b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png differ diff --git a/lib/matplotlib/tests/conftest.py b/lib/matplotlib/tests/conftest.py index c20d626ae2e1..722a7ff91484 100644 --- a/lib/matplotlib/tests/conftest.py +++ b/lib/matplotlib/tests/conftest.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - from matplotlib.testing.conftest import (mpl_test_settings, mpl_image_comparison_parameters, pytest_configure, pytest_unconfigure, diff --git a/lib/matplotlib/tests/test_afm.py b/lib/matplotlib/tests/test_afm.py index d4cfce2c61e6..25c7a2ad0f92 100644 --- a/lib/matplotlib/tests/test_afm.py +++ b/lib/matplotlib/tests/test_afm.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function -from six import BytesIO +from io import BytesIO import matplotlib.afm as afm @@ -33,7 +30,7 @@ def test_nonascii_str(): # This tests that we also decode bytes as utf-8 properly. # Else, font files with non ascii characters fail to load. - inp_str = u"привет" + inp_str = "привет" byte_str = inp_str.encode("utf8") ret = afm._to_str(byte_str) @@ -70,9 +67,9 @@ def test_parse_char_metrics(): 42: (1141.0, 'foo', [40, 60, 800, 360]), 99: (583.0, 'bar', [40, -10, 543, 210]), }, - {'space': (250.0, [0, 0, 0, 0]), - 'foo': (1141.0, [40, 60, 800, 360]), - 'bar': (583.0, [40, -10, 543, 210]), + {'space': (250.0, 'space', [0, 0, 0, 0]), + 'foo': (1141.0, 'foo', [40, 60, 800, 360]), + 'bar': (583.0, 'bar', [40, -10, 543, 210]), }) diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 6dca4468d32b..028291d18f86 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - import io import numpy as np diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index 31543da9d32f..93d30a692be9 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -1,9 +1,6 @@ -from __future__ import absolute_import, division, print_function - -import six - +import os +from pathlib import Path import sys -import tempfile import numpy as np import pytest @@ -25,8 +22,6 @@ class NullMovieWriter(animation.AbstractMovieWriter): it cannot be added to the 'writers' registry. """ - frame_size_can_vary = True - def setup(self, fig, outfile, dpi, *args): self.fig = fig self.outfile = outfile @@ -42,29 +37,35 @@ def finish(self): pass -def test_null_movie_writer(): - # Test running an animation with NullMovieWriter. - - fig = plt.figure() +def make_animation(**kwargs): + fig, ax = plt.subplots() + line, = ax.plot([]) def init(): pass def animate(i): - pass + line.set_data([0, 1], [0, i]) + return line, + + return animation.FuncAnimation(fig, animate, **kwargs) + + +def test_null_movie_writer(): + # Test running an animation with NullMovieWriter. num_frames = 5 + anim = make_animation(frames=num_frames) + filename = "unused.null" dpi = 50 savefig_kwargs = dict(foo=0) - - anim = animation.FuncAnimation(fig, animate, init_func=init, - frames=num_frames) writer = NullMovieWriter() + anim.save(filename, dpi=dpi, writer=writer, savefig_kwargs=savefig_kwargs) - assert writer.fig == fig + assert writer.fig == plt.figure(1) # The figure used by make_animation. assert writer.outfile == filename assert writer.dpi == dpi assert writer.args == () @@ -106,7 +107,7 @@ def __init__(self, fps=None, codec=None, bitrate=None, pass @classmethod - def isAvailable(self): + def isAvailable(cls): return True @@ -132,6 +133,8 @@ def isAvailable(self): # matplotlib.testing.image_comparison @pytest.mark.parametrize('writer, output', WRITER_OUTPUT) def test_save_animation_smoketest(tmpdir, writer, output): + if writer == 'pillow': + pytest.importorskip("PIL") try: # for ImageMagick the rcparams must be patched to account for # 'convert' being a built in MS tool, not the imagemagick @@ -178,23 +181,8 @@ def animate(i): def test_no_length_frames(): - fig, ax = plt.subplots() - line, = ax.plot([], []) - - def init(): - line.set_data([], []) - return line, - - def animate(i): - x = np.linspace(0, 10, 100) - y = np.sin(x + i) - line.set_data(x, y) - return line, - - anim = animation.FuncAnimation(fig, animate, init_func=init, - frames=iter(range(5))) - writer = NullMovieWriter() - anim.save('unused.null', writer=writer) + (make_animation(frames=iter(range(5))) + .save('unused.null', writer=NullMovieWriter())) def test_movie_writer_registry(): @@ -205,17 +193,70 @@ def test_movie_writer_registry(): assert len(animation.writers._registered) > 0 animation.writers.list() # resets dirty state assert not animation.writers._dirty - mpl.rcParams['animation.ffmpeg_path'] = u"not_available_ever_xxxx" + mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx" assert animation.writers._dirty animation.writers.list() # resets assert not animation.writers._dirty assert not animation.writers.is_available("ffmpeg") # something which is guaranteed to be available in path # and exits immediately - bin = u"true" if sys.platform != 'win32' else u"where" + bin = "true" if sys.platform != 'win32' else "where" mpl.rcParams['animation.ffmpeg_path'] = bin assert animation.writers._dirty animation.writers.list() # resets assert not animation.writers._dirty assert animation.writers.is_available("ffmpeg") mpl.rcParams['animation.ffmpeg_path'] = ffmpeg_path + + +@pytest.mark.skipif( + not animation.writers.is_available(mpl.rcParams["animation.writer"]), + reason="animation writer not installed") +@pytest.mark.parametrize("method_name", ["to_html5_video", "to_jshtml"]) +def test_embed_limit(method_name, caplog, tmpdir): + with tmpdir.as_cwd(): + with mpl.rc_context({"animation.embed_limit": 1e-6}): # ~1 byte. + getattr(make_animation(frames=1), method_name)() + assert len(caplog.records) == 1 + record, = caplog.records + assert (record.name == "matplotlib.animation" + and record.levelname == "WARNING") + + +@pytest.mark.skipif( + not animation.writers.is_available(mpl.rcParams["animation.writer"]), + reason="animation writer not installed") +@pytest.mark.parametrize( + "method_name", + ["to_html5_video", + pytest.mark.xfail("to_jshtml")]) # Needs to be fixed. +def test_cleanup_temporaries(method_name, tmpdir): + with tmpdir.as_cwd(): + getattr(make_animation(frames=1), method_name)() + assert list(Path(str(tmpdir)).iterdir()) == [] + + +# Currently, this fails with a ValueError after we try to communicate() twice +# with the Popen. +@pytest.mark.xfail +@pytest.mark.skipif(os.name != "posix", reason="requires a POSIX OS") +def test_failing_ffmpeg(tmpdir, monkeypatch): + """ + Test that we correctly raise an OSError when ffmpeg fails. + + To do so, mock ffmpeg using a simple executable shell script that + succeeds when called with no arguments (so that it gets registered by + `isAvailable`), but fails otherwise, and add it to the $PATH. + """ + try: + with tmpdir.as_cwd(): + monkeypatch.setenv("PATH", ".:" + os.environ["PATH"]) + exe_path = Path(tmpdir, "ffmpeg") + exe_path.write_text("#!/bin/sh\n" + "[[ $@ -eq 0 ]]\n") + os.chmod(str(exe_path), 0o755) + animation.writers.reset_available_writers() + with pytest.raises(OSError): + make_animation().save("test.mpeg") + finally: + animation.writers.reset_available_writers() diff --git a/lib/matplotlib/tests/test_arrow_patches.py b/lib/matplotlib/tests/test_arrow_patches.py index 44f87c5fe59a..f678fbed36b9 100644 --- a/lib/matplotlib/tests/test_arrow_patches.py +++ b/lib/matplotlib/tests/test_arrow_patches.py @@ -1,5 +1,5 @@ -from __future__ import absolute_import, division, print_function - +import pytest +import platform import matplotlib.pyplot as plt from matplotlib.testing.decorators import image_comparison import matplotlib.patches as mpatches @@ -62,13 +62,14 @@ def __prepare_fancyarrow_dpi_cor_test(): ax.set_xlim([0, 1]) ax.set_ylim([0, 1]) ax.add_patch(mpatches.FancyArrowPatch(posA=(0.3, 0.4), posB=(0.8, 0.6), - lw=3, arrowstyle=u'->', + lw=3, arrowstyle='->', mutation_scale=100)) return fig2 @image_comparison(baseline_images=['fancyarrow_dpi_cor_100dpi'], remove_text=True, extensions=['png'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), savefig_kwarg=dict(dpi=100)) def test_fancyarrow_dpi_cor_100dpi(): """ @@ -84,6 +85,7 @@ def test_fancyarrow_dpi_cor_100dpi(): @image_comparison(baseline_images=['fancyarrow_dpi_cor_200dpi'], remove_text=True, extensions=['png'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), savefig_kwarg=dict(dpi=200)) def test_fancyarrow_dpi_cor_200dpi(): """ @@ -135,3 +137,34 @@ def test_arrow_styles(): arrowstyle=stylename, mutation_scale=25) ax.add_patch(patch) + + +@image_comparison(baseline_images=['connection_styles'], extensions=['png'], + style='mpl20', remove_text=True) +def test_connection_styles(): + styles = mpatches.ConnectionStyle.get_styles() + + n = len(styles) + fig, ax = plt.subplots(figsize=(6, 10)) + ax.set_xlim(0, 1) + ax.set_ylim(-1, n) + + for i, stylename in enumerate(sorted(styles)): + patch = mpatches.FancyArrowPatch((0.1, i), (0.8, i + 0.5), + arrowstyle="->", + connectionstyle=stylename, + mutation_scale=25) + ax.add_patch(patch) + + +def test_invalid_intersection(): + conn_style_1 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=200) + p1 = mpatches.FancyArrowPatch((.2, .2), (.5, .5), + connectionstyle=conn_style_1) + with pytest.raises(ValueError): + plt.gca().add_patch(p1) + + conn_style_2 = mpatches.ConnectionStyle.Angle3(angleA=20, angleB=199.9) + p2 = mpatches.FancyArrowPatch((.2, .2), (.5, .5), + connectionstyle=conn_style_2) + plt.gca().add_patch(p2) diff --git a/lib/matplotlib/tests/test_artist.py b/lib/matplotlib/tests/test_artist.py index e6aff72bf1d8..283db9abe97e 100644 --- a/lib/matplotlib/tests/test_artist.py +++ b/lib/matplotlib/tests/test_artist.py @@ -1,8 +1,6 @@ -from __future__ import absolute_import, division, print_function - import io -import warnings from itertools import chain +import warnings import numpy as np @@ -186,8 +184,8 @@ def test_remove(): assert not ax.stale assert not ln.stale - assert im in ax.mouseover_set - assert ln not in ax.mouseover_set + assert im in ax._mouseover_set + assert ln not in ax._mouseover_set assert im.axes is ax im.remove() @@ -197,7 +195,7 @@ def test_remove(): assert art.axes is None assert art.figure is None - assert im not in ax.mouseover_set + assert im not in ax._mouseover_set assert fig.stale assert ax.stale @@ -245,7 +243,7 @@ def test_setp(): # Check `file` argument sio = io.StringIO() plt.setp(lines1, 'zorder', file=sio) - assert sio.getvalue() == ' zorder: float \n' + assert sio.getvalue() == ' zorder: float\n' def test_None_zorder(): @@ -263,16 +261,15 @@ def test_None_zorder(): ("ACCEPTS: [ '-' | '--' | '-.' ]", "[ '-' | '--' | '-.' ] "), ('ACCEPTS: Some description.', 'Some description. '), ('.. ACCEPTS: Some description.', 'Some description. '), + ('arg : int', 'int'), + ('arg : int\nACCEPTS: Something else.', 'Something else. '), ]) def test_artist_inspector_get_valid_values(accept_clause, expected): class TestArtist(martist.Artist): - def set_f(self): + def set_f(self, arg): pass - func = TestArtist.set_f - if hasattr(func, '__func__'): - func = func.__func__ # python 2 must write via __func__.__doc__ - func.__doc__ = """ + TestArtist.set_f.__doc__ = """ Some text. %s diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2fa6c2c94b97..6afed91f34ff 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1,14 +1,11 @@ -from __future__ import absolute_import, division, print_function - -import six -from six.moves import xrange -from itertools import chain, product +from itertools import product from distutils.version import LooseVersion import io +import platform import datetime -import pytz +import dateutil.tz as dutz import numpy as np from numpy import ma @@ -18,15 +15,15 @@ import warnings import matplotlib -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal import matplotlib.pyplot as plt import matplotlib.markers as mmarkers import matplotlib.patches as mpatches import matplotlib.colors as mcolors -from numpy.testing import assert_allclose, assert_array_equal +from numpy.testing import ( + assert_allclose, assert_array_equal, assert_array_almost_equal) from matplotlib.cbook import ( IgnoredKeywordWarning, MatplotlibDeprecationWarning) -from matplotlib.cbook._backports import broadcast_to # Note: Some test cases are run twice: once normally and once with labeled data # These two must be defined in the same test function or need to have @@ -283,7 +280,7 @@ def test_autoscale_tiny_range(): # github pull #904 fig, ax = plt.subplots(2, 2) ax = ax.flatten() - for i in xrange(4): + for i in range(4): y1 = 10**(-11 - i) ax[i].plot([0, 1], [1, 1 + y1]) @@ -361,7 +358,7 @@ def test_arrow_simple(): shape = ('full', 'left', 'right') head_starts_at_zero = (True, False) # Create outer product of values - kwargs = list(product(length_includes_head, shape, head_starts_at_zero)) + kwargs = product(length_includes_head, shape, head_starts_at_zero) fig, axs = plt.subplots(3, 4) for i, (ax, kwarg) in enumerate(zip(axs.flatten(), kwargs)): @@ -555,7 +552,8 @@ def test_single_point(): plt.plot('b', 'b', 'o', data=data) -@image_comparison(baseline_images=['single_date']) +@image_comparison(baseline_images=['single_date'], extensions=['png'], + style='mpl20') def test_single_date(): time1 = [721964.0] data1 = [-65.54] @@ -618,6 +616,16 @@ def test_shaped_data(): plt.plot(xdata[:, 1], xdata[1, :], 'o') +def test_structured_data(): + # support for stuctured data + pts = np.array([(1, 1), (2, 2)], dtype=[("ones", float), ("twos", float)]) + + # this should not read second name as a format and raise ValueError + fig, ax = plt.subplots(2) + ax[0].plot("ones", "twos", data=pts) + ax[1].plot("ones", "twos", "r", data=pts) + + @image_comparison(baseline_images=['const_xy']) def test_const_xy(): fig = plt.figure() @@ -739,8 +747,7 @@ def test_polar_rlabel_position(): ax.tick_params(rotation='auto') -@image_comparison(baseline_images=['polar_theta_wedge'], style='default', - tol=0.01 if six.PY2 else 0) +@image_comparison(baseline_images=['polar_theta_wedge'], style='default') def test_polar_theta_limits(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -852,18 +859,18 @@ def __init__(self, x, y): @image_comparison(baseline_images=['hexbin_log'], - remove_text=True, - extensions=['png']) + extensions=['png'], style='mpl20') def test_hexbin_log(): - # Issue #1636 - np.random.seed(0) + # Issue #1636 (and also test log scaled colorbar) + np.random.seed(19680801) n = 100000 x = np.random.standard_normal(n) y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n) y = np.power(2, y * 0.5) fig, ax = plt.subplots() - ax.hexbin(x, y, yscale='log') + h = ax.hexbin(x, y, yscale='log', bins='log') + plt.colorbar(h) def test_inverted_limits(): @@ -891,11 +898,8 @@ def test_inverted_limits(): def test_nonfinite_limits(): x = np.arange(0., np.e, 0.01) # silence divide by zero warning from log(0) - olderr = np.seterr(divide='ignore') - try: + with np.errstate(divide='ignore'): y = np.log(x) - finally: - np.seterr(**olderr) x[len(x)//2] = np.nan fig, ax = plt.subplots() ax.plot(x, y) @@ -1105,7 +1109,7 @@ def test_pcolormesh(): @image_comparison(baseline_images=['pcolormesh_datetime_axis'], - extensions=['png'], remove_text=False) + extensions=['png'], remove_text=False, style='mpl20') def test_pcolormesh_datetime_axis(): fig = plt.figure() fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15) @@ -1131,7 +1135,7 @@ def test_pcolormesh_datetime_axis(): @image_comparison(baseline_images=['pcolor_datetime_axis'], - extensions=['png'], remove_text=False) + extensions=['png'], remove_text=False, style='mpl20') def test_pcolor_datetime_axis(): fig = plt.figure() fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15) @@ -1418,14 +1422,14 @@ def test_marker_edges(): def test_bar_tick_label_single(): # From 2516: plot bar with array of string labels for x axis ax = plt.gca() - ax.bar(0, 1, tick_label='0') + ax.bar(0, 1, align='edge', tick_label='0') # Reuse testcase from above for a labeled data test data = {"a": 0, "b": 1} fig = plt.figure() ax = fig.add_subplot(111) ax = plt.gca() - ax.bar("a", "b", tick_label='0', data=data) + ax.bar("a", "b", align='edge', tick_label='0', data=data) def test_bar_ticklabel_fail(): @@ -1453,6 +1457,22 @@ def test_bar_tick_label_multiple_old_alignment(): align='center') +def test_bar_color_none_alpha(): + ax = plt.gca() + rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='none', edgecolor='r') + for rect in rects: + assert rect.get_facecolor() == (0, 0, 0, 0) + assert rect.get_edgecolor() == (1, 0, 0, 0.3) + + +def test_bar_edgecolor_none_alpha(): + ax = plt.gca() + rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='r', edgecolor='none') + for rect in rects: + assert rect.get_facecolor() == (1, 0, 0, 0.3) + assert rect.get_edgecolor() == (0, 0, 0, 0) + + @image_comparison(baseline_images=['barh_tick_label'], extensions=['png']) def test_barh_tick_label(): @@ -1526,10 +1546,10 @@ def test_hist_step_filled(): for kg, _type, ax in zip(kwargs, types, axes): ax.hist(x, n_bins, histtype=_type, stacked=True, **kg) ax.set_title('%s/%s' % (kg, _type)) - ax.set_ylim(ymin=-50) + ax.set_ylim(bottom=-50) patches = axes[0].patches - assert all([p.get_facecolor() == p.get_edgecolor() for p in patches]) + assert all(p.get_facecolor() == p.get_edgecolor() for p in patches) @image_comparison(baseline_images=['hist_density'], extensions=['png']) @@ -1590,18 +1610,20 @@ def contour_dat(): return x, y, z -@image_comparison(baseline_images=['contour_hatching']) +@image_comparison(baseline_images=['contour_hatching'], + remove_text=True, style='mpl20') def test_contour_hatching(): x, y, z = contour_dat() fig = plt.figure() ax = fig.add_subplot(111) - cs = ax.contourf(x, y, z, hatches=['-', '/', '\\', '//'], + cs = ax.contourf(x, y, z, 7, hatches=['/', '\\', '//', '-'], cmap=plt.get_cmap('gray'), extend='both', alpha=0.5) -@image_comparison(baseline_images=['contour_colorbar']) +@image_comparison(baseline_images=['contour_colorbar'], + style='mpl20') def test_contour_colorbar(): x, y, z = contour_dat() @@ -1624,7 +1646,8 @@ def test_contour_colorbar(): cbar.add_lines(cs2, erase=False) -@image_comparison(baseline_images=['hist2d', 'hist2d']) +@image_comparison(baseline_images=['hist2d', 'hist2d'], + remove_text=True, style='mpl20') def test_hist2d(): np.random.seed(0) # make it not symmetric in case we switch x and y axis @@ -1632,16 +1655,17 @@ def test_hist2d(): y = np.random.randn(100)-2 fig = plt.figure() ax = fig.add_subplot(111) - ax.hist2d(x, y, bins=10) + ax.hist2d(x, y, bins=10, rasterized=True) # Reuse testcase from above for a labeled data test data = {"x": x, "y": y} fig = plt.figure() ax = fig.add_subplot(111) - ax.hist2d("x", "y", bins=10, data=data) + ax.hist2d("x", "y", bins=10, data=data, rasterized=True) -@image_comparison(baseline_images=['hist2d_transpose']) +@image_comparison(baseline_images=['hist2d_transpose'], + remove_text=True, style='mpl20') def test_hist2d_transpose(): np.random.seed(0) # make sure the output from np.histogram is transposed before @@ -1650,66 +1674,123 @@ def test_hist2d_transpose(): y = np.random.randn(100)-2 fig = plt.figure() ax = fig.add_subplot(111) - ax.hist2d(x, y, bins=10) + ax.hist2d(x, y, bins=10, rasterized=True) -@image_comparison(baseline_images=['scatter', 'scatter']) -def test_scatter_plot(): - fig, ax = plt.subplots() - data = {"x": [3, 4, 2, 6], "y": [2, 5, 2, 3], "c": ['r', 'y', 'b', 'lime'], - "s": [24, 15, 19, 29]} - - ax.scatter(data["x"], data["y"], c=data["c"], s=data["s"]) - - # Reuse testcase from above for a labeled data test - fig, ax = plt.subplots() - ax.scatter("x", "y", c="c", s="s", data=data) +class TestScatter(object): + @image_comparison(baseline_images=['scatter', 'scatter']) + def test_scatter_plot(self): + fig, ax = plt.subplots() + data = {"x": [3, 4, 2, 6], "y": [2, 5, 2, 3], + "c": ['r', 'y', 'b', 'lime'], "s": [24, 15, 19, 29]} + ax.scatter(data["x"], data["y"], c=data["c"], s=data["s"]) -@image_comparison(baseline_images=['scatter_marker'], remove_text=True, - extensions=['png']) -def test_scatter_marker(): - fig, (ax0, ax1, ax2) = plt.subplots(ncols=3) - ax0.scatter([3, 4, 2, 6], [2, 5, 2, 3], - c=[(1, 0, 0), 'y', 'b', 'lime'], - s=[60, 50, 40, 30], - edgecolors=['k', 'r', 'g', 'b'], - marker='s') - ax1.scatter([3, 4, 2, 6], [2, 5, 2, 3], - c=[(1, 0, 0), 'y', 'b', 'lime'], - s=[60, 50, 40, 30], - edgecolors=['k', 'r', 'g', 'b'], - marker=mmarkers.MarkerStyle('o', fillstyle='top')) - # unit area ellipse - rx, ry = 3, 1 - area = rx * ry * np.pi - theta = np.linspace(0, 2 * np.pi, 21) - verts = np.column_stack([np.cos(theta) * rx / area, - np.sin(theta) * ry / area]) - ax2.scatter([3, 4, 2, 6], [2, 5, 2, 3], - c=[(1, 0, 0), 'y', 'b', 'lime'], - s=[60, 50, 40, 30], - edgecolors=['k', 'r', 'g', 'b'], - verts=verts) - - -@image_comparison(baseline_images=['scatter_2D'], remove_text=True, - extensions=['png']) -def test_scatter_2D(): - x = np.arange(3) - y = np.arange(2) - x, y = np.meshgrid(x, y) - z = x + y - fig, ax = plt.subplots() - ax.scatter(x, y, c=z, s=200, edgecolors='face') + # Reuse testcase from above for a labeled data test + fig, ax = plt.subplots() + ax.scatter("x", "y", c="c", s="s", data=data) + + @image_comparison(baseline_images=['scatter_marker'], remove_text=True, + extensions=['png']) + def test_scatter_marker(self): + fig, (ax0, ax1, ax2) = plt.subplots(ncols=3) + ax0.scatter([3, 4, 2, 6], [2, 5, 2, 3], + c=[(1, 0, 0), 'y', 'b', 'lime'], + s=[60, 50, 40, 30], + edgecolors=['k', 'r', 'g', 'b'], + marker='s') + ax1.scatter([3, 4, 2, 6], [2, 5, 2, 3], + c=[(1, 0, 0), 'y', 'b', 'lime'], + s=[60, 50, 40, 30], + edgecolors=['k', 'r', 'g', 'b'], + marker=mmarkers.MarkerStyle('o', fillstyle='top')) + # unit area ellipse + rx, ry = 3, 1 + area = rx * ry * np.pi + theta = np.linspace(0, 2 * np.pi, 21) + verts = np.column_stack([np.cos(theta) * rx / area, + np.sin(theta) * ry / area]) + ax2.scatter([3, 4, 2, 6], [2, 5, 2, 3], + c=[(1, 0, 0), 'y', 'b', 'lime'], + s=[60, 50, 40, 30], + edgecolors=['k', 'r', 'g', 'b'], + marker=verts) + + @image_comparison(baseline_images=['scatter_2D'], remove_text=True, + extensions=['png']) + def test_scatter_2D(self): + x = np.arange(3) + y = np.arange(2) + x, y = np.meshgrid(x, y) + z = x + y + fig, ax = plt.subplots() + ax.scatter(x, y, c=z, s=200, edgecolors='face') + + def test_scatter_color(self): + # Try to catch cases where 'c' kwarg should have been used. + with pytest.raises(ValueError): + plt.scatter([1, 2], [1, 2], color=[0.1, 0.2]) + with pytest.raises(ValueError): + plt.scatter([1, 2, 3], [1, 2, 3], color=[1, 2, 3]) + + # Parameters for *test_scatter_c*. NB: assuming that the + # scatter plot will have 4 elements. The tuple scheme is: + # (*c* parameter case, exception regexp key or None if no exception) + params_test_scatter_c = [ + # Single letter-sequences + ("rgby", None), + ("rgb", "shape"), + ("rgbrgb", "shape"), + (["rgby"], "conversion"), + # Special cases + ("red", None), + ("none", None), + (None, None), + (["r", "g", "b", "none"], None), + # Non-valid color spec (FWIW, 'jaune' means yellow in French) + ("jaune", "conversion"), + (["jaune"], "conversion"), # wrong type before wrong size + (["jaune"]*4, "conversion"), + # Value-mapping like + ([0.5]*3, None), # should emit a warning for user's eyes though + ([0.5]*4, None), # NB: no warning as matching size allows mapping + ([0.5]*5, "shape"), + # RGB values + ([[1, 0, 0]], None), + ([[1, 0, 0]]*3, "shape"), + ([[1, 0, 0]]*4, None), + ([[1, 0, 0]]*5, "shape"), + # RGBA values + ([[1, 0, 0, 0.5]], None), + ([[1, 0, 0, 0.5]]*3, "shape"), + ([[1, 0, 0, 0.5]]*4, None), + ([[1, 0, 0, 0.5]]*5, "shape"), + # Mix of valid color specs + ([[1, 0, 0, 0.5]]*3 + [[1, 0, 0]], None), + ([[1, 0, 0, 0.5], "red", "0.0"], "shape"), + ([[1, 0, 0, 0.5], "red", "0.0", "C5"], None), + ([[1, 0, 0, 0.5], "red", "0.0", "C5", [0, 1, 0]], "shape"), + # Mix of valid and non valid color specs + ([[1, 0, 0, 0.5], "red", "jaune"], "conversion"), + ([[1, 0, 0, 0.5], "red", "0.0", "jaune"], "conversion"), + ([[1, 0, 0, 0.5], "red", "0.0", "C5", "jaune"], "conversion"), + ] + @pytest.mark.parametrize('c_case, re_key', params_test_scatter_c) + def test_scatter_c(self, c_case, re_key): + # Additional checking of *c* (introduced in #11383). + REGEXP = { + "shape": "^'c' argument has [0-9]+ elements", # shape mismatch + "conversion": "^'c' argument must either be valid", # bad vals + } + x = y = [0, 1, 2, 3] + fig, ax = plt.subplots() -def test_scatter_color(): - # Try to catch cases where 'c' kwarg should have been used. - with pytest.raises(ValueError): - plt.scatter([1, 2], [1, 2], color=[0.1, 0.2]) - with pytest.raises(ValueError): - plt.scatter([1, 2, 3], [1, 2, 3], color=[1, 2, 3]) + if re_key is None: + ax.scatter(x, y, c=c_case, edgecolors="black") + else: + with pytest.raises(ValueError, match=REGEXP[re_key]): + ax.scatter(x, y, c=c_case, edgecolors="black") def test_as_mpl_axes_api(): @@ -1724,6 +1805,7 @@ def __init__(self): def _as_mpl_axes(self): # implement the matplotlib axes interface return PolarAxes, {'theta_offset': self.theta_offset} + prj = Polar() prj2 = Polar() prj2.theta_offset = np.pi @@ -1738,7 +1820,7 @@ def _as_mpl_axes(self): # testing axes creation with gca ax = plt.gca(projection=prj) - assert type(ax) == maxes._subplots._subplot_classes[PolarAxes] + assert type(ax) == maxes._subplots.subplot_class_factory(PolarAxes) ax_via_gca = plt.gca(projection=prj) assert ax_via_gca is ax # try getting the axes given a different polar projection @@ -1759,7 +1841,7 @@ def _as_mpl_axes(self): # testing axes creation with subplot ax = plt.subplot(121, projection=prj) - assert type(ax) == maxes._subplots._subplot_classes[PolarAxes] + assert type(ax) == maxes._subplots.subplot_class_factory(PolarAxes) plt.close() @@ -1767,7 +1849,8 @@ def test_pyplot_axes(): # test focusing of Axes in other Figure fig1, ax1 = plt.subplots() fig2, ax2 = plt.subplots() - assert ax1 is plt.axes(ax1) + with pytest.warns(MatplotlibDeprecationWarning): + assert ax1 is plt.axes(ax1) assert ax1 is plt.gca() assert fig1 is plt.gcf() plt.close(fig1) @@ -2871,8 +2954,8 @@ def test_stem_args(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) - x = list(xrange(10)) - y = list(xrange(10)) + x = list(range(10)) + y = list(range(10)) # Test the call signatures ax.stem(y) @@ -2913,24 +2996,21 @@ def test_hist_stacked_step(): ax.hist((d1, d2), histtype="step", stacked=True) -@image_comparison(baseline_images=['hist_stacked_normed']) -def test_hist_stacked_normed(): - # make some data - d1 = np.linspace(1, 3, 20) - d2 = np.linspace(0, 10, 50) - fig, ax = plt.subplots() - with pytest.warns(UserWarning): - ax.hist((d1, d2), stacked=True, normed=True) - - -@image_comparison(baseline_images=['hist_stacked_normed'], extensions=['png']) +@image_comparison(baseline_images=['hist_stacked_normed', + 'hist_stacked_normed']) def test_hist_stacked_density(): # make some data d1 = np.linspace(1, 3, 20) d2 = np.linspace(0, 10, 50) + fig, ax = plt.subplots() ax.hist((d1, d2), stacked=True, density=True) + # Also check that the old keyword works. + fig, ax = plt.subplots() + with pytest.warns(UserWarning): + ax.hist((d1, d2), stacked=True, normed=True) + @pytest.mark.parametrize('normed', [False, True]) @pytest.mark.parametrize('density', [False, True]) @@ -3171,7 +3251,7 @@ def test_eventplot_colors(colors): # NB: ['rgbk'] is not a valid argument for to_rgba_array, while 'rgbk' is. if len(expected) == 1: expected = expected[0] - expected = broadcast_to(mcolors.to_rgba_array(expected), (len(data), 4)) + expected = np.broadcast_to(mcolors.to_rgba_array(expected), (len(data), 4)) fig, ax = plt.subplots() if len(colors) == 1: # tuple with a single string (like '0.5' or 'rgbk') @@ -3255,7 +3335,7 @@ def test_markers_fillstyle_rcparams(): @image_comparison(baseline_images=['vertex_markers'], extensions=['png'], remove_text=True) def test_vertex_markers(): - data = list(xrange(10)) + data = list(range(10)) marker_as_tuple = ((-1, -1), (1, -1), (1, 1), (-1, 1)) marker_as_list = [(-1, -1), (1, -1), (1, 1), (-1, 1)] fig = plt.figure() @@ -3267,9 +3347,10 @@ def test_vertex_markers(): @image_comparison(baseline_images=['vline_hline_zorder', - 'errorbar_zorder']) + 'errorbar_zorder'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0)) def test_eb_line_zorder(): - x = list(xrange(10)) + x = list(range(10)) # First illustrate basic pyplot interface, using defaults where possible. fig = plt.figure() @@ -3285,9 +3366,9 @@ def test_eb_line_zorder(): # Now switch to a more OO interface to exercise more features. fig = plt.figure() ax = fig.gca() - x = list(xrange(10)) + x = list(range(10)) y = np.zeros(10) - yerr = list(xrange(10)) + yerr = list(range(10)) ax.errorbar(x, y, yerr=yerr, zorder=5, lw=5, color='r') for j in range(10): ax.axhline(j, lw=5, color='k', zorder=j) @@ -3416,7 +3497,7 @@ def test_mixed_collection(): from matplotlib import patches from matplotlib import collections - x = list(xrange(10)) + x = list(range(10)) # First illustrate basic pyplot interface, using defaults where possible. fig = plt.figure() @@ -4314,7 +4395,7 @@ def test_twin_spines(): def make_patch_spines_invisible(ax): ax.set_frame_on(True) ax.patch.set_visible(False) - for sp in six.itervalues(ax.spines): + for sp in ax.spines.values(): sp.set_visible(False) fig = plt.figure(figsize=(4, 3)) @@ -4518,6 +4599,18 @@ def test_text_labelsize(): ax.tick_params(direction='out') +@image_comparison(baseline_images=['pie_default'], extensions=['png']) +def test_pie_default(): + # The slices will be ordered and plotted counter-clockwise. + labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' + sizes = [15, 30, 45, 10] + colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] + explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') + fig1, ax1 = plt.subplots(figsize=(8, 6)) + pie1 = ax1.pie(sizes, explode=explode, labels=labels, colors=colors, + autopct='%1.1f%%', shadow=True, startangle=90) + + @image_comparison(baseline_images=['pie_linewidth_0', 'pie_linewidth_0', 'pie_linewidth_0'], extensions=['png']) @@ -4644,6 +4737,28 @@ def test_pie_rotatelabels_true(): plt.axis('equal') +def test_pie_textprops(): + data = [23, 34, 45] + labels = ["Long name 1", "Long name 2", "Long name 3"] + + textprops = dict(horizontalalignment="center", + verticalalignment="top", + rotation=90, + rotation_mode="anchor", + size=12, color="red") + + _, texts, autopct = plt.gca().pie(data, labels=labels, autopct='%.2f', + textprops=textprops) + for labels in [texts, autopct]: + for tx in labels: + assert tx.get_ha() == textprops["horizontalalignment"] + assert tx.get_va() == textprops["verticalalignment"] + assert tx.get_rotation() == textprops["rotation"] + assert tx.get_rotation_mode() == textprops["rotation_mode"] + assert tx.get_size() == textprops["size"] + assert tx.get_color() == textprops["color"] + + @image_comparison(baseline_images=['set_get_ticklabels'], extensions=['png']) def test_set_get_ticklabels(): # test issue 2246 @@ -4882,6 +4997,12 @@ def test_square_plot(): xlim, ylim = ax.get_xlim(), ax.get_ylim() assert np.diff(xlim) == np.diff(ylim) assert ax.get_aspect() == 'equal' + assert_array_almost_equal( + ax.get_position(original=True).extents, + np.array((0.125, 0.1, 0.9, 0.9))) + assert_array_almost_equal( + ax.get_position(original=False).extents, + np.array((0.2125, 0.1, 0.8125, 0.9))) def test_no_None(): @@ -4946,9 +5067,7 @@ def generate_errorbar_inputs(): yerr_only = base_xy * yerr_cy both_err = base_xy * yerr_cy * xerr_cy - test_cyclers = chain(xerr_only, yerr_only, both_err, empty) - - return test_cyclers + return [*xerr_only, *yerr_only, *both_err, *empty] @pytest.mark.parametrize('kwargs', generate_errorbar_inputs()) @@ -5000,7 +5119,8 @@ def test_title_location_roundtrip(): @image_comparison(baseline_images=["loglog"], remove_text=True, - extensions=['png']) + extensions=['png'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0)) def test_loglog(): fig, ax = plt.subplots() x = np.arange(1, 11) @@ -5112,6 +5232,23 @@ def test_remove_shared_axes_relim(): assert_array_equal(ax_lst[0][1].get_xlim(), orig_xlim) +def test_shared_axes_autoscale(): + l = np.arange(-80, 90, 40) + t = np.random.random_sample((l.size, l.size)) + + ax1 = plt.subplot(211) + ax1.set_xlim(-1000, 1000) + ax1.set_ylim(-1000, 1000) + ax1.contour(l, l, t) + + ax2 = plt.subplot(212, sharex=ax1, sharey=ax1) + ax2.contour(l, l, t) + assert not ax1.get_autoscalex_on() and not ax2.get_autoscalex_on() + assert not ax1.get_autoscaley_on() and not ax2.get_autoscaley_on() + assert ax1.get_xlim() == ax2.get_xlim() == (-1000, 1000) + assert ax1.get_ylim() == ax2.get_ylim() == (-1000, 1000) + + def test_adjust_numtick_aspect(): fig, ax = plt.subplots() ax.yaxis.get_major_locator().set_params(nbins='auto') @@ -5261,7 +5398,7 @@ def test_ls_ds_conflict(): def test_bar_uint8(): xs = [0, 1, 2, 3] - b = plt.bar(np.array(xs, dtype=np.uint8), [2, 3, 4, 5]) + b = plt.bar(np.array(xs, dtype=np.uint8), [2, 3, 4, 5], align="edge") for (patch, x) in zip(b.patches, xs): assert patch.xy[0] == x @@ -5269,8 +5406,9 @@ def test_bar_uint8(): @image_comparison(baseline_images=['date_timezone_x'], extensions=['png']) def test_date_timezone_x(): # Tests issue 5575 - time_index = [pytz.timezone('Canada/Eastern').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + time_index = [datetime.datetime(2016, 2, 22, hour=x, + tzinfo=dutz.gettz('Canada/Eastern')) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) @@ -5286,8 +5424,9 @@ def test_date_timezone_x(): extensions=['png']) def test_date_timezone_y(): # Tests issue 5575 - time_index = [pytz.timezone('Canada/Eastern').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + time_index = [datetime.datetime(2016, 2, 22, hour=x, + tzinfo=dutz.gettz('Canada/Eastern')) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) @@ -5304,8 +5443,9 @@ def test_date_timezone_y(): extensions=['png']) def test_date_timezone_x_and_y(): # Tests issue 5575 - time_index = [pytz.timezone('UTC').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + UTC = datetime.timezone.utc + time_index = [datetime.datetime(2016, 2, 22, hour=x, tzinfo=UTC) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) @@ -5338,6 +5478,42 @@ def test_axisbelow(): ax.set_axisbelow(setting) +@image_comparison(baseline_images=['titletwiny'], style='mpl20', + extensions=['png']) +def test_titletwiny(): + # Test that title is put above xlabel if xlabel at top + fig, ax = plt.subplots() + fig.subplots_adjust(top=0.8) + ax2 = ax.twiny() + ax.set_xlabel('Xlabel') + ax2.set_xlabel('Xlabel2') + ax.set_title('Title') + + +def test_titlesetpos(): + # Test that title stays put if we set it manually + fig, ax = plt.subplots() + fig.subplots_adjust(top=0.8) + ax2 = ax.twiny() + ax.set_xlabel('Xlabel') + ax2.set_xlabel('Xlabel2') + ax.set_title('Title') + pos = (0.5, 1.11) + ax.title.set_position(pos) + renderer = fig.canvas.get_renderer() + ax._update_title_position(renderer) + assert ax.title.get_position() == pos + + +def test_title_xticks_top(): + # Test that title moves if xticks on top of axes. + fig, ax = plt.subplots() + ax.xaxis.set_ticks_position('top') + ax.set_title('xlabel top') + fig.canvas.draw() + assert ax.title.get_position()[1] > 1.04 + + def test_offset_label_color(): # Tests issue 6440 fig = plt.figure() @@ -5370,13 +5546,13 @@ def test_quiver_units(): def test_bar_color_cycle(): - ccov = mcolors.colorConverter.to_rgb + to_rgb = mcolors.to_rgb fig, ax = plt.subplots() for j in range(5): ln, = ax.plot(range(3)) brs = ax.bar(range(3), range(3)) for br in brs: - assert ccov(ln.get_color()) == ccov(br.get_facecolor()) + assert to_rgb(ln.get_color()) == to_rgb(br.get_facecolor()) def test_tick_param_label_rotation(): @@ -5521,52 +5697,11 @@ def test_twinx_knows_limits(): assert_array_equal(xtwin.viewLim.intervalx, ax2.viewLim.intervalx) -@pytest.mark.style('mpl20') -@pytest.mark.parametrize('args, kwargs, warning_count', - [((1, 1), {'width': 1, 'bottom': 1}, 0), - ((1, ), {'height': 1, 'bottom': 1}, 0), - ((), {'x': 1, 'height': 1}, 0), - ((), {'left': 1, 'height': 1}, 1)]) -def test_bar_signature(args, kwargs, warning_count): - fig, ax = plt.subplots() - with warnings.catch_warnings(record=True) as w: - r, = ax.bar(*args, **kwargs) - - assert r.get_width() == kwargs.get('width', 0.8) - assert r.get_y() == kwargs.get('bottom', 0) - assert len(w) == warning_count - - -@pytest.mark.style('mpl20') -@pytest.mark.parametrize('args, kwargs, warning_count', - [((1, 1), {'height': 1, 'left': 1}, 0), - ((1, ), {'width': 1, 'left': 1}, 0), - ((), {'y': 1, 'width': 1}, 0), - ((), {'bottom': 1, 'width': 1}, 1)]) -def test_barh_signature(args, kwargs, warning_count): - fig, ax = plt.subplots() - with warnings.catch_warnings(record=True) as w: - r, = ax.barh(*args, **kwargs) - - assert r.get_height() == kwargs.get('height', 0.8) - assert r.get_x() == kwargs.get('left', 0) - assert len(w) == warning_count - - def test_zero_linewidth(): # Check that setting a zero linewidth doesn't error plt.plot([0, 1], [0, 1], ls='--', lw=0) -def test_patch_deprecations(): - fig, ax = plt.subplots() - with warnings.catch_warnings(record=True) as w: - assert ax.patch == ax.axesPatch - assert fig.patch == fig.figurePatch - - assert len(w) == 2 - - def test_polar_gridlines(): fig = plt.figure() ax = fig.add_subplot(111, polar=True) @@ -5595,15 +5730,80 @@ def test_plot_columns_cycle_deprecation(): plt.plot(np.zeros((2, 2)), np.zeros((2, 3))) -def test_markerfacecolor_none_alpha(): - fig1, ax1 = plt.subplots() - ax1.plot(0, "o", mfc="none", alpha=.5) - buf1 = io.BytesIO() - fig1.savefig(buf1) +# pdf and svg tests fail using travis' old versions of gs and inkscape. +@check_figures_equal(extensions=["png"]) +def test_markerfacecolor_none_alpha(fig_test, fig_ref): + fig_test.subplots().plot(0, "o", mfc="none", alpha=.5) + fig_ref.subplots().plot(0, "o", mfc="w", alpha=.5) - fig2, ax2 = plt.subplots() - ax2.plot(0, "o", mfc="w", alpha=.5) - buf2 = io.BytesIO() - fig2.savefig(buf2) - assert buf1.getvalue() == buf2.getvalue() +def test_tick_padding_tightbbox(): + "Test that tick padding gets turned off if axis is off" + plt.rcParams["xtick.direction"] = "out" + plt.rcParams["ytick.direction"] = "out" + fig, ax = plt.subplots() + bb = ax.get_window_extent(fig.canvas.get_renderer()) + ax.axis('off') + bb2 = ax.get_window_extent(fig.canvas.get_renderer()) + assert bb.x0 < bb2.x0 + assert bb.y0 < bb2.y0 + + +def test_zoom_inset(): + dx, dy = 0.05, 0.05 + # generate 2 2d grids for the x & y bounds + 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) + + fig, ax = plt.subplots() + ax.pcolormesh(x, y, z) + ax.set_aspect(1.) + ax.apply_aspect() + # we need to apply_aspect to make the drawing below work. + + # Make the inset_axes... Position axes co-ordinates... + axin1 = ax.inset_axes([0.7, 0.7, 0.35, 0.35]) + # redraw the data in the inset axes... + axin1.pcolormesh(x, y, z) + axin1.set_xlim([1.5, 2.15]) + axin1.set_ylim([2, 2.5]) + axin1.set_aspect(ax.get_aspect()) + + rec, connectors = ax.indicate_inset_zoom(axin1) + fig.canvas.draw() + xx = np.array([[1.5, 2.], + [2.15, 2.5]]) + assert(np.all(rec.get_bbox().get_points() == xx)) + xx = np.array([[0.6325, 0.692308], + [0.8425, 0.907692]]) + np.testing.assert_allclose(axin1.get_position().get_points(), + xx, rtol=1e-4) + + +def test_spines_properbbox_after_zoom(): + fig, ax = plt.subplots() + bb = ax.spines['bottom'].get_window_extent(fig.canvas.get_renderer()) + # this is what zoom calls: + ax._set_view_from_bbox((320, 320, 500, 500), 'in', + None, False, False) + bb2 = ax.spines['bottom'].get_window_extent(fig.canvas.get_renderer()) + np.testing.assert_allclose(bb.get_points(), bb2.get_points(), rtol=1e-6) + + +def test_cartopy_backcompat(): + import matplotlib + import matplotlib.axes + import matplotlib.axes._subplots + + class Dummy(matplotlib.axes.Axes): + ... + + class DummySubplot(matplotlib.axes.SubplotBase, Dummy): + _axes_class = Dummy + + matplotlib.axes._subplots._subplot_classes[Dummy] = DummySubplot + + FactoryDummySubplot = matplotlib.axes.subplot_class_factory(Dummy) + + assert DummySubplot is FactoryDummySubplot diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 0ab7e67a7666..5310db42f956 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -1,14 +1,11 @@ -from matplotlib.backend_bases import FigureCanvasBase -from matplotlib.backend_bases import RendererBase - +from matplotlib.backend_bases import ( + FigureCanvasBase, LocationEvent, RendererBase) import matplotlib.pyplot as plt import matplotlib.transforms as transforms import matplotlib.path as path import numpy as np -import os -import shutil -import tempfile +import pytest def test_uses_per_path(): @@ -50,30 +47,46 @@ def check(master_transform, paths, all_transforms, check(id, paths, tforms, offsets, facecolors[0:1], edgecolors) -def test_get_default_filename(): - try: - test_dir = tempfile.mkdtemp() - plt.rcParams['savefig.directory'] = test_dir - fig = plt.figure() - canvas = FigureCanvasBase(fig) - filename = canvas.get_default_filename() - assert filename == 'image.png' - finally: - shutil.rmtree(test_dir) +def test_get_default_filename(tmpdir): + plt.rcParams['savefig.directory'] = str(tmpdir) + fig = plt.figure() + canvas = FigureCanvasBase(fig) + filename = canvas.get_default_filename() + assert filename == 'image.png' + +@pytest.mark.backend('pdf') +def test_non_gui_warning(): + plt.subplots() + with pytest.warns(UserWarning) as rec: + plt.show() + assert len(rec) == 1 + assert ('Matplotlib is currently using pdf, which is a non-GUI backend' + in str(rec[0].message)) -def test_get_default_filename_already_exists(): - # From #3068: Suggest non-existing default filename - try: - test_dir = tempfile.mkdtemp() - plt.rcParams['savefig.directory'] = test_dir - fig = plt.figure() - canvas = FigureCanvasBase(fig) + with pytest.warns(UserWarning) as rec: + plt.gcf().show() + assert len(rec) == 1 + assert ('Matplotlib is currently using pdf, which is a non-GUI backend' + in str(rec[0].message)) - # create 'image.png' in figure's save dir - open(os.path.join(test_dir, 'image.png'), 'w').close() - filename = canvas.get_default_filename() - assert filename == 'image-1.png' - finally: - shutil.rmtree(test_dir) +def test_location_event_position(): + # LocationEvent should cast its x and y arguments + # to int unless it is None + fig = plt.figure() + canvas = FigureCanvasBase(fig) + test_positions = [(42, 24), (None, 42), (None, None), + (200, 100.01), (205.75, 2.0)] + for x, y in test_positions: + event = LocationEvent("test_event", canvas, x, y) + if x is None: + assert event.x is None + else: + assert event.x == int(x) + assert isinstance(event.x, int) + if y is None: + assert event.y is None + else: + assert event.y == int(y) + assert isinstance(event.y, int) diff --git a/lib/matplotlib/tests/test_backend_nbagg.py b/lib/matplotlib/tests/test_backend_nbagg.py new file mode 100644 index 000000000000..f08460b892cc --- /dev/null +++ b/lib/matplotlib/tests/test_backend_nbagg.py @@ -0,0 +1,34 @@ +from pathlib import Path +import subprocess +import tempfile + +import pytest + +nbformat = pytest.importorskip('nbformat') + +# From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/ + + +def _notebook_run(nb_file): + """Execute a notebook via nbconvert and collect output. + :returns (parsed nb object, execution errors) + """ + with tempfile.NamedTemporaryFile(suffix=".ipynb") as fout: + args = ["jupyter", "nbconvert", "--to", "notebook", "--execute", + "--ExecutePreprocessor.timeout=500", + "--output", fout.name, nb_file] + subprocess.check_call(args) + + fout.seek(0) + nb = nbformat.read(fout, nbformat.current_nbformat) + + errors = [output for cell in nb.cells if "outputs" in cell + for output in cell["outputs"] + if output.output_type == "error"] + return nb, errors + + +def test_ipynb(): + nb, errors = _notebook_run( + str(Path(__file__).parent / 'test_nbagg_01.ipynb')) + assert errors == [] diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 01fb0f2439a4..773c6021be3d 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -1,9 +1,3 @@ -# -*- encoding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - -import six - import io import os import sys @@ -20,7 +14,7 @@ _determinism_check) -needs_usetex = pytest.mark.xfail( +needs_usetex = pytest.mark.skipif( not checkdep_usetex(True), reason="This test needs a TeX installation") @@ -34,7 +28,7 @@ def test_use14corefonts(): rcParams['font.sans-serif'] = ['Helvetica'] rcParams['pdf.compression'] = 0 - text = u'''A three-line text positioned just above a blue line + text = '''A three-line text positioned just above a blue line and containing some French characters and the euro symbol: "Merci pépé pour les 10 €"''' @@ -238,3 +232,11 @@ def test_failing_latex(tmpdir): plt.xlabel("$22_2_2$") with pytest.raises(RuntimeError): plt.savefig(path) + + +def test_empty_rasterised(): + # Check that emtpy figures that are rasterised save to pdf files fine + with PdfPages(io.BytesIO()) as pdf: + fig, ax = plt.subplots() + ax.plot([], [], rasterized=True) + fig.savefig(pdf, format="pdf") diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index 8269808af9d6..ed5e8c2bedfb 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -1,45 +1,47 @@ -# -*- encoding: utf-8 -*- -from __future__ import absolute_import, division, print_function - import os +from pathlib import Path import shutil +import subprocess +from tempfile import TemporaryDirectory import numpy as np import pytest import matplotlib as mpl import matplotlib.pyplot as plt -from matplotlib.compat import subprocess from matplotlib.testing.compare import compare_images, ImageComparisonFailure from matplotlib.testing.decorators import image_comparison, _image_directories +from matplotlib.backends.backend_pgf import PdfPages baseline_dir, result_dir = _image_directories(lambda: 'dummy func') def check_for(texsystem): - header = """ - \\documentclass{minimal} - \\usepackage{pgf} - \\begin{document} - \\typeout{pgfversion=\\pgfversion} - \\makeatletter - \\@@end - """ - try: - latex = subprocess.Popen([str(texsystem), "-halt-on-error"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - stdout, stderr = latex.communicate(header.encode("utf8")) - except OSError: - return False - - return latex.returncode == 0 + with TemporaryDirectory() as tmpdir: + tex_path = Path(tmpdir, "test.tex") + tex_path.write_text(r""" + \documentclass{minimal} + \usepackage{pgf} + \begin{document} + \typeout{pgfversion=\pgfversion} + \makeatletter + \@@end + """) + try: + subprocess.check_call( + [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except (OSError, subprocess.CalledProcessError): + return False + return True needs_xelatex = pytest.mark.skipif(not check_for('xelatex'), reason='xelatex + pgf is required') needs_pdflatex = pytest.mark.skipif(not check_for('pdflatex'), reason='pdflatex + pgf is required') +needs_lualatex = pytest.mark.skipif(not check_for('lualatex'), + reason='lualatex + pgf is required') def compare_figure(fname, savefig_kwargs={}, tol=0): @@ -70,7 +72,7 @@ def create_figure(): # text and typesetting plt.plot([0.9], [0.5], "ro", markersize=3) - plt.text(0.9, 0.5, u'unicode (ü, °, µ) and math ($\\mu_i = x_i^2$)', + plt.text(0.9, 0.5, 'unicode (ü, °, µ) and math ($\\mu_i = x_i^2$)', ha='right', fontsize=20) plt.ylabel('sans-serif, blue, $\\frac{\\sqrt{x}}{y^2}$..', family='sans-serif', color='blue') @@ -97,7 +99,6 @@ def test_xelatex(): @image_comparison(baseline_images=['pgf_pdflatex'], extensions=['pdf'], style='default') def test_pdflatex(): - import os if os.environ.get('APPVEYOR', False): pytest.xfail("pdflatex test does not work on appveyor due to missing " "LaTeX fonts") @@ -117,30 +118,26 @@ def test_pdflatex(): @pytest.mark.style('default') @pytest.mark.backend('pgf') def test_rcupdate(): - rc_sets = [] - rc_sets.append({'font.family': 'sans-serif', - 'font.size': 30, - 'figure.subplot.left': .2, - 'lines.markersize': 10, - 'pgf.rcfonts': False, - 'pgf.texsystem': 'xelatex'}) - rc_sets.append({'font.family': 'monospace', - 'font.size': 10, - 'figure.subplot.left': .1, - 'lines.markersize': 20, - 'pgf.rcfonts': False, - 'pgf.texsystem': 'pdflatex', - 'pgf.preamble': ['\\usepackage[utf8x]{inputenc}', - '\\usepackage[T1]{fontenc}', - '\\usepackage{sfmath}']}) - tol = (6, 0) - original_params = mpl.rcParams.copy() + rc_sets = [{'font.family': 'sans-serif', + 'font.size': 30, + 'figure.subplot.left': .2, + 'lines.markersize': 10, + 'pgf.rcfonts': False, + 'pgf.texsystem': 'xelatex'}, + {'font.family': 'monospace', + 'font.size': 10, + 'figure.subplot.left': .1, + 'lines.markersize': 20, + 'pgf.rcfonts': False, + 'pgf.texsystem': 'pdflatex', + 'pgf.preamble': ['\\usepackage[utf8x]{inputenc}', + '\\usepackage[T1]{fontenc}', + '\\usepackage{sfmath}']}] + tol = [6, 0] for i, rc_set in enumerate(rc_sets): - mpl.rcParams.clear() - mpl.rcParams.update(original_params) - mpl.rcParams.update(rc_set) - create_figure() - compare_figure('pgf_rcupdate%d.pdf' % (i + 1), tol=tol[i]) + with mpl.rc_context(rc_set): + create_figure() + compare_figure('pgf_rcupdate%d.pdf' % (i + 1), tol=tol[i]) # test backend-side clipping, since large numbers are not supported by TeX @@ -195,3 +192,81 @@ def test_bbox_inches(): bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox}, tol=0) + + +@needs_pdflatex +@pytest.mark.style('default') +@pytest.mark.backend('pgf') +def test_pdf_pages(): + rc_pdflatex = { + 'font.family': 'serif', + 'pgf.rcfonts': False, + 'pgf.texsystem': 'pdflatex', + } + mpl.rcParams.update(rc_pdflatex) + + fig1 = plt.figure() + ax1 = fig1.add_subplot(1, 1, 1) + ax1.plot(range(5)) + fig1.tight_layout() + + fig2 = plt.figure(figsize=(3, 2)) + ax2 = fig2.add_subplot(1, 1, 1) + ax2.plot(range(5)) + fig2.tight_layout() + + with PdfPages(os.path.join(result_dir, 'pdfpages.pdf')) as pdf: + pdf.savefig(fig1) + pdf.savefig(fig2) + + +@needs_xelatex +@pytest.mark.style('default') +@pytest.mark.backend('pgf') +def test_pdf_pages_metadata(): + rc_pdflatex = { + 'font.family': 'serif', + 'pgf.rcfonts': False, + 'pgf.texsystem': 'xelatex', + } + mpl.rcParams.update(rc_pdflatex) + + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + ax.plot(range(5)) + fig.tight_layout() + + md = {'author': 'me', 'title': 'Multipage PDF with pgf'} + path = os.path.join(result_dir, 'pdfpages_meta.pdf') + + with PdfPages(path, metadata=md) as pdf: + pdf.savefig(fig) + pdf.savefig(fig) + pdf.savefig(fig) + + assert pdf.get_pagecount() == 3 + + +@needs_lualatex +@pytest.mark.style('default') +@pytest.mark.backend('pgf') +def test_pdf_pages_lualatex(): + rc_pdflatex = { + 'font.family': 'serif', + 'pgf.rcfonts': False, + 'pgf.texsystem': 'lualatex' + } + mpl.rcParams.update(rc_pdflatex) + + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + ax.plot(range(5)) + fig.tight_layout() + + md = {'author': 'me', 'title': 'Multipage PDF with pgf'} + path = os.path.join(result_dir, 'pdfpages_lua.pdf') + with PdfPages(path, metadata=md) as pdf: + pdf.savefig(fig) + pdf.savefig(fig) + + assert pdf.get_pagecount() == 2 diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 8bf6e7dde38e..ad5febf0c420 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -1,28 +1,24 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - import io +import os +from pathlib import Path import re +import tempfile import numpy as np import pytest -import six import matplotlib import matplotlib.pyplot as plt -from matplotlib import patheffects +from matplotlib import cbook, patheffects from matplotlib.testing.decorators import image_comparison from matplotlib.testing.determinism import (_determinism_source_date_epoch, _determinism_check) -needs_ghostscript = pytest.mark.xfail( +needs_ghostscript = pytest.mark.skipif( matplotlib.checkdep_ghostscript()[0] is None, reason="This test needs a ghostscript installation") - - -needs_usetex = pytest.mark.xfail( +needs_usetex = pytest.mark.skipif( not matplotlib.checkdep_usetex(True), reason="This test needs a TeX installation") @@ -31,13 +27,14 @@ @pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('format, use_log, rcParams', [ ('ps', False, {}), - needs_ghostscript(('ps', False, {'ps.usedistiller': 'ghostscript'})), - needs_usetex(needs_ghostscript(('ps', False, {'text.latex.unicode': True, - 'text.usetex': True}))), + needs_ghostscript( + ('ps', False, {'ps.usedistiller': 'ghostscript'})), + needs_usetex(needs_ghostscript( + ('ps', False, {'text.usetex': True}))), ('eps', False, {}), ('eps', True, {'ps.useafm': True}), - needs_usetex(needs_ghostscript(('eps', False, {'text.latex.unicode': True, - 'text.usetex': True}))), + needs_usetex(needs_ghostscript( + ('eps', False, {'text.usetex': True}))), ], ids=[ 'ps', 'ps with distiller', @@ -50,35 +47,25 @@ def test_savefig_to_stringio(format, use_log, rcParams): matplotlib.rcParams.update(rcParams) fig, ax = plt.subplots() - buffers = [ - six.moves.StringIO(), - io.StringIO(), - io.BytesIO()] - if use_log: - ax.set_yscale('log') + with io.StringIO() as s_buf, io.BytesIO() as b_buf: - ax.plot([1, 2], [1, 2]) - ax.set_title(u"Déjà vu") - for buffer in buffers: - fig.savefig(buffer, format=format) + if use_log: + ax.set_yscale('log') - values = [x.getvalue() for x in buffers] + ax.plot([1, 2], [1, 2]) + ax.set_title("Déjà vu") + fig.savefig(s_buf, format=format) + fig.savefig(b_buf, format=format) - if six.PY3: - values = [ - values[0].encode('ascii'), - values[1].encode('ascii'), - values[2]] + s_val = s_buf.getvalue().encode('ascii') + b_val = b_buf.getvalue() - # Remove comments from the output. This includes things that - # could change from run to run, such as the time. - values = [re.sub(b'%%.*?\n', b'', x) for x in values] + # Remove comments from the output. This includes things that could + # change from run to run, such as the time. + s_val, b_val = [re.sub(b'%%.*?\n', b'', x) for x in [s_val, b_val]] - assert values[0] == values[1] - assert values[1] == values[2].replace(b'\r\n', b'\n') - for buffer in buffers: - buffer.close() + assert s_val == b_val.replace(b'\r\n', b'\n') def test_patheffects(): @@ -93,40 +80,22 @@ def test_patheffects(): @needs_usetex @needs_ghostscript -def test_tilde_in_tempfilename(): +def test_tilde_in_tempfilename(tmpdir): # Tilde ~ in the tempdir path (e.g. TMPDIR, TMP or TEMP on windows # when the username is very long and windows uses a short name) breaks # latex before https://github.com/matplotlib/matplotlib/pull/5928 - import tempfile - import shutil - import os - import os.path - - tempdir = None - old_tempdir = tempfile.tempdir - try: - # change the path for new tempdirs, which is used - # internally by the ps backend to write a file - tempdir = tempfile.mkdtemp() - base_tempdir = os.path.join(tempdir, "short~1") - os.makedirs(base_tempdir) - tempfile.tempdir = base_tempdir - + base_tempdir = Path(str(tmpdir), "short-1") + base_tempdir.mkdir() + # Change the path for new tempdirs, which is used internally by the ps + # backend to write a file. + with cbook._setattr_cm(tempfile, tempdir=str(base_tempdir)): # usetex results in the latex call, which does not like the ~ plt.rc('text', usetex=True) plt.plot([1, 2, 3, 4]) plt.xlabel(r'\textbf{time} (s)') - output_eps = os.path.join(base_tempdir, 'tex_demo.eps') + output_eps = os.path.join(str(base_tempdir), 'tex_demo.eps') # use the PS backend to write the file... plt.savefig(output_eps, format="ps") - finally: - tempfile.tempdir = old_tempdir - if tempdir: - try: - shutil.rmtree(tempdir) - except Exception as e: - # do not break if this is not removable... - print(e) def test_source_date_epoch(): diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index a621329772ed..253c7768b250 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -1,23 +1,26 @@ -from __future__ import absolute_import, division, print_function +import copy +from unittest import mock +import matplotlib from matplotlib import pyplot as plt from matplotlib._pylab_helpers import Gcf -import matplotlib -import copy import pytest + try: - # mock in python 3.3+ - from unittest import mock -except ImportError: - import mock + import PyQt4 +except (ImportError, RuntimeError): # RuntimeError if PyQt5 already imported. + try: + import PySide + except ImportError: + pytestmark = pytest.mark.skip("Failed to import a Qt4 binding.") + +qt_compat = pytest.importorskip('matplotlib.backends.qt_compat') +QtCore = qt_compat.QtCore -with matplotlib.rc_context(rc={'backend': 'Qt4Agg'}): - qt_compat = pytest.importorskip('matplotlib.backends.qt_compat') from matplotlib.backends.backend_qt4 import ( MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) # noqa -QtCore = qt_compat.QtCore _, ControlModifier, ControlKey = MODIFIER_KEYS[CTRL] _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] @@ -29,7 +32,7 @@ py_qt_ver = QtCore.__version_info__[0] if py_qt_ver != 4: - pytestmark = pytest.mark.xfail(reason='Qt4 is not available') + pytestmark = pytest.mark.skipif(reason='Qt4 is not available') @pytest.mark.backend('Qt4Agg') diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index 81a23081ddbd..478e2f2bbdde 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -1,27 +1,26 @@ -from __future__ import absolute_import, division, print_function - import copy +from unittest import mock import matplotlib from matplotlib import pyplot as plt from matplotlib._pylab_helpers import Gcf -from numpy.testing import assert_equal - import pytest + try: - # mock in python 3.3+ - from unittest import mock -except ImportError: - import mock - -with matplotlib.rc_context(rc={'backend': 'Qt5Agg'}): - qt_compat = pytest.importorskip('matplotlib.backends.qt_compat', - minversion='5') + import PyQt5 +except (ImportError, RuntimeError): # RuntimeError if PyQt4 already imported. + try: + import PySide2 + except ImportError: + pytestmark = pytest.mark.skip("Failed to import a Qt5 binding.") + +qt_compat = pytest.importorskip('matplotlib.backends.qt_compat') +QtCore = qt_compat.QtCore + from matplotlib.backends.backend_qt5 import ( MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) # noqa -QtCore = qt_compat.QtCore _, ControlModifier, ControlKey = MODIFIER_KEYS[CTRL] _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] @@ -135,8 +134,8 @@ def test_dpi_ratio_change(): # The actual widget size and figure physical size don't change assert size.width() == 600 assert size.height() == 240 - assert_equal(qt_canvas.get_width_height(), (600, 240)) - assert_equal(fig.get_size_inches(), (5, 2)) + assert qt_canvas.get_width_height() == (600, 240) + assert (fig.get_size_inches() == (5, 2)).all() p.return_value = 2 @@ -158,8 +157,8 @@ def test_dpi_ratio_change(): # The actual widget size and figure physical size don't change assert size.width() == 600 assert size.height() == 240 - assert_equal(qt_canvas.get_width_height(), (600, 240)) - assert_equal(fig.get_size_inches(), (5, 2)) + assert qt_canvas.get_width_height() == (600, 240) + assert (fig.get_size_inches() == (5, 2)).all() @pytest.mark.backend('Qt5Agg') diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index e0cbdfa8bce1..835ff01bb5bd 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six - import numpy as np from io import BytesIO import os @@ -16,7 +12,7 @@ from matplotlib import dviread -needs_usetex = pytest.mark.xfail( +needs_usetex = pytest.mark.skipif( not matplotlib.checkdep_usetex(True), reason="This test needs a TeX installation") @@ -143,7 +139,7 @@ def test_determinism(filename, usetex): [sys.executable, '-R', '-c', 'import matplotlib; ' 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use("svg"); ' + 'matplotlib.use("svg", force=True); ' 'from matplotlib.tests.test_backend_svg ' 'import _test_determinism_save;' '_test_determinism_save(%r, %r)' % (filename, usetex)], diff --git a/lib/matplotlib/tests/test_backend_tools.py b/lib/matplotlib/tests/test_backend_tools.py new file mode 100644 index 000000000000..cc05a1a98f78 --- /dev/null +++ b/lib/matplotlib/tests/test_backend_tools.py @@ -0,0 +1,20 @@ +import pytest + +from matplotlib.backend_tools import ToolHelpBase + + +@pytest.mark.parametrize('rc_shortcut,expected', [ + ('home', 'Home'), + ('backspace', 'Backspace'), + ('f1', 'F1'), + ('ctrl+a', 'Ctrl+A'), + ('ctrl+A', 'Ctrl+Shift+A'), + ('a', 'a'), + ('A', 'A'), + ('ctrl+shift+f1', 'Ctrl+Shift+F1'), + ('1', '1'), + ('cmd+p', 'Cmd+P'), + ('cmd+1', 'Cmd+1'), +]) +def test_format_shortcut(rc_shortcut, expected): + assert ToolHelpBase.format_shortcut(rc_shortcut) == expected diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index df7a5d08a10b..7ba47be93efd 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -1,33 +1,34 @@ import importlib import os +import signal +import subprocess import sys +import time +import urllib.request -from matplotlib.compat.subprocess import Popen import pytest +import matplotlib as mpl + # Minimal smoke-testing of the backends for which the dependencies are # PyPI-installable on Travis. They are not available for all tested Python # versions so we don't fail on missing backends. -# -# We also don't test on Py2 because its subprocess module doesn't support -# timeouts, and it would require a separate code path to check for module -# existence without actually trying to import the module (which may install -# an undesirable input hook). - def _get_testable_interactive_backends(): backends = [] - for deps, backend in [(["cairocffi", "pgi"], "gtk3agg"), - (["cairocffi", "pgi"], "gtk3cairo"), - (["PyQt5"], "qt5agg"), - (["cairocffi", "PyQt5"], "qt5cairo"), - (["tkinter"], "tkagg"), - (["wx"], "wxagg")]: + for deps, backend in [ + # gtk3agg fails on Travis, needs to be investigated. + # (["cairocffi", "pgi"], "gtk3agg"), + (["cairocffi", "pgi"], "gtk3cairo"), + (["PyQt5"], "qt5agg"), + (["PyQt5", "cairocffi"], "qt5cairo"), + (["tkinter"], "tkagg"), + (["wx"], "wx"), + (["wx"], "wxagg"), + ]: reason = None - if sys.version_info < (3,): - reason = "Py3-only test" - elif not os.environ.get("DISPLAY"): + if not os.environ.get("DISPLAY"): reason = "No $DISPLAY" elif any(importlib.util.find_spec(dep) is None for dep in deps): reason = "Missing dependency" @@ -36,23 +37,99 @@ def _get_testable_interactive_backends(): return backends +# Using a timer not only allows testing of timers (on other backends), but is +# also necessary on gtk3 and wx, where a direct call to key_press_event("q") +# from draw_event causes breakage due to the canvas widget being deleted too +# early. Also, gtk3 redefines key_press_event with a different signature, so +# we directly invoke it from the superclass instead. _test_script = """\ +import importlib import sys -from matplotlib import pyplot as plt +from unittest import TestCase + +import matplotlib as mpl +from matplotlib import pyplot as plt, rcParams +from matplotlib.backend_bases import FigureCanvasBase +rcParams.update({ + "webagg.open_in_browser": False, + "webagg.port_retries": 1, +}) +backend = plt.rcParams["backend"].lower() +assert_equal = TestCase().assertEqual +assert_raises = TestCase().assertRaises + +if backend.endswith("agg") and not backend.startswith(("gtk3", "web")): + # Force interactive framework setup. + plt.figure() + + # Check that we cannot switch to a backend using another interactive + # framework, but can switch to a backend using cairo instead of agg, or a + # non-interactive backend. In the first case, we use tkagg as the "other" + # interactive backend as it is (essentially) guaranteed to be present. + # Moreover, don't test switching away from gtk3 (as Gtk.main_level() is + # not set up at this point yet) and webagg (which uses no interactive + # framework). + + if backend != "tkagg": + with assert_raises(ImportError): + mpl.use("tkagg", force=True) + + def check_alt_backend(alt_backend): + mpl.use(alt_backend, force=True) + fig = plt.figure() + assert_equal( + type(fig.canvas).__module__, + "matplotlib.backends.backend_{}".format(alt_backend)) + + if importlib.util.find_spec("cairocffi"): + check_alt_backend(backend[:-3] + "cairo") + check_alt_backend("svg") + +mpl.use(backend, force=True) fig, ax = plt.subplots() +assert_equal( + type(fig.canvas).__module__, + "matplotlib.backends.backend_{}".format(backend)) + ax.plot([0, 1], [2, 3]) -fig.canvas.mpl_connect("draw_event", lambda event: sys.exit()) +timer = fig.canvas.new_timer(1) +timer.add_callback(FigureCanvasBase.key_press_event, fig.canvas, "q") +# Trigger quitting upon draw. +fig.canvas.mpl_connect("draw_event", lambda event: timer.start()) + plt.show() """ +_test_timeout = 10 # Empirically, 1s is not enough on Travis. @pytest.mark.parametrize("backend", _get_testable_interactive_backends()) @pytest.mark.flaky(reruns=3) -def test_backend(backend): - environ = os.environ.copy() - environ["MPLBACKEND"] = backend - proc = Popen([sys.executable, "-c", _test_script], env=environ) - # Empirically, 1s is not enough on Travis. - assert proc.wait(timeout=10) == 0 +def test_interactive_backend(backend): + if subprocess.run([sys.executable, "-c", _test_script], + env={**os.environ, "MPLBACKEND": backend}, + timeout=_test_timeout).returncode: + pytest.fail("The subprocess returned an error.") + + +@pytest.mark.skipif(os.name == "nt", reason="Cannot send SIGINT on Windows.") +def test_webagg(): + pytest.importorskip("tornado") + proc = subprocess.Popen([sys.executable, "-c", _test_script], + env={**os.environ, "MPLBACKEND": "webagg"}) + url = "http://{}:{}".format( + mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"]) + timeout = time.perf_counter() + _test_timeout + while True: + try: + conn = urllib.request.urlopen(url) + break + except urllib.error.URLError: + if time.perf_counter() > timeout: + pytest.fail("Failed to connect to the webagg server.") + else: + continue + conn.close() + proc.send_signal(signal.SIGINT) + assert proc.wait(timeout=_test_timeout) == 0 diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index e514269a9847..dee7f640c97f 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -1,7 +1,4 @@ -from __future__ import absolute_import, division, print_function - -import six -import sys +import builtins import matplotlib @@ -24,14 +21,6 @@ def test_override_builtins(): 'sum', 'divmod' } - - # We could use six.moves.builtins here, but that seems - # to do a little more than just this. - if six.PY3: - builtins = sys.modules['builtins'] - else: - builtins = sys.modules['__builtin__'] - overridden = False for key in dir(pylab): if key in dir(builtins): diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py index 5eb4b6b3ba2c..46f37632e944 100644 --- a/lib/matplotlib/tests/test_bbox_tight.py +++ b/lib/matplotlib/tests/test_bbox_tight.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function - import numpy as np from matplotlib.testing.decorators import image_comparison @@ -29,7 +27,7 @@ def test_bbox_inches_tight(): # the bottom values for stacked bar chart fig, ax = plt.subplots(1, 1) for row in range(rows): - ax.bar(ind, data[row], width, bottom=yoff, color='b') + ax.bar(ind, data[row], width, bottom=yoff, align='edge', color='b') yoff = yoff + data[row] cellText.append(['']) plt.xticks([]) @@ -45,7 +43,7 @@ def test_bbox_inches_tight(): remove_text=False, savefig_kwarg={'bbox_inches': 'tight'}) def test_bbox_inches_tight_suptile_legend(): plt.plot(np.arange(10), label='a straight line') - plt.legend(bbox_to_anchor=(0.9, 1), loc=2, ) + plt.legend(bbox_to_anchor=(0.9, 1), loc='upper left') plt.title('Axis title') plt.suptitle('Figure title') diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index 40f9d078ec5e..ee5d3ec7888d 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- """Catch all for categorical functions""" -from __future__ import absolute_import, division, print_function - import pytest import numpy as np diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index f6595b56a351..745844fb6a1c 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -1,11 +1,8 @@ -from __future__ import absolute_import, division, print_function import itertools import pickle from weakref import ref import warnings -import six - from datetime import datetime import numpy as np @@ -26,24 +23,6 @@ def test_is_hashable(): assert not cbook.is_hashable(lst) -def test_restrict_dict(): - d = {'foo': 'bar', 1: 2} - with pytest.warns(cbook.deprecation.MatplotlibDeprecationWarning) as rec: - d1 = cbook.restrict_dict(d, ['foo', 1]) - assert d1 == d - d2 = cbook.restrict_dict(d, ['bar', 2]) - assert d2 == {} - d3 = cbook.restrict_dict(d, {'foo': 1}) - assert d3 == {'foo': 'bar'} - d4 = cbook.restrict_dict(d, {}) - assert d4 == {} - d5 = cbook.restrict_dict(d, {'foo', 2}) - assert d5 == {'foo': 'bar'} - assert len(rec) == 5 - # check that d was not modified - assert d == {'foo': 'bar', 1: 2} - - class Test_delete_masked_points(object): def setup_method(self): self.mask1 = [False, False, True, True, False, False] @@ -503,64 +482,3 @@ def test_flatiter(): assert 0 == next(it) assert 1 == next(it) - - -class TestFuncParser(object): - x_test = np.linspace(0.01, 0.5, 3) - validstrings = ['linear', 'quadratic', 'cubic', 'sqrt', 'cbrt', - 'log', 'log10', 'log2', 'x**{1.5}', 'root{2.5}(x)', - 'log{2}(x)', - 'log(x+{0.5})', 'log10(x+{0.1})', 'log{2}(x+{0.1})', - 'log{2}(x+{0})'] - results = [(lambda x: x), - np.square, - (lambda x: x**3), - np.sqrt, - (lambda x: x**(1. / 3)), - np.log, - np.log10, - np.log2, - (lambda x: x**1.5), - (lambda x: x**(1 / 2.5)), - (lambda x: np.log2(x)), - (lambda x: np.log(x + 0.5)), - (lambda x: np.log10(x + 0.1)), - (lambda x: np.log2(x + 0.1)), - (lambda x: np.log2(x))] - - bounded_list = [True, True, True, True, True, - False, False, False, True, True, - False, - True, True, True, - False] - - @pytest.mark.parametrize("string, func", - zip(validstrings, results), - ids=validstrings) - def test_values(self, string, func): - func_parser = cbook._StringFuncParser(string) - f = func_parser.function - assert_array_almost_equal(f(self.x_test), func(self.x_test)) - - @pytest.mark.parametrize("string", validstrings, ids=validstrings) - def test_inverse(self, string): - func_parser = cbook._StringFuncParser(string) - f = func_parser.func_info - fdir = f.function - finv = f.inverse - assert_array_almost_equal(finv(fdir(self.x_test)), self.x_test) - - @pytest.mark.parametrize("string", validstrings, ids=validstrings) - def test_get_inverse(self, string): - func_parser = cbook._StringFuncParser(string) - finv1 = func_parser.inverse - finv2 = func_parser.func_info.inverse - assert_array_almost_equal(finv1(self.x_test), finv2(self.x_test)) - - @pytest.mark.parametrize("string, bounded", - zip(validstrings, bounded_list), - ids=validstrings) - def test_bounded(self, string, bounded): - func_parser = cbook._StringFuncParser(string) - b = func_parser.is_bounded_0_1 - assert_array_equal(b, bounded) diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 291647d178f7..6812ee1ad427 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -1,13 +1,11 @@ """ Tests specific to the collections module. """ -from __future__ import absolute_import, division, print_function - import io +import platform import numpy as np -from numpy.testing import ( - assert_array_equal, assert_array_almost_equal, assert_equal) +from numpy.testing import assert_array_equal, assert_array_almost_equal import pytest import matplotlib.pyplot as plt @@ -87,7 +85,7 @@ def test__EventCollection__get_orientation(): orientation ''' _, coll, props = generate_EventCollection_plot() - assert_equal(props['orientation'], coll.get_orientation()) + assert props['orientation'] == coll.get_orientation() def test__EventCollection__is_horizontal(): @@ -96,7 +94,7 @@ def test__EventCollection__is_horizontal(): orientation ''' _, coll, _ = generate_EventCollection_plot() - assert_equal(True, coll.is_horizontal()) + assert coll.is_horizontal() def test__EventCollection__get_linelength(): @@ -104,7 +102,7 @@ def test__EventCollection__get_linelength(): check to make sure the default linelength matches the input linelength ''' _, coll, props = generate_EventCollection_plot() - assert_equal(props['linelength'], coll.get_linelength()) + assert props['linelength'] == coll.get_linelength() def test__EventCollection__get_lineoffset(): @@ -112,7 +110,7 @@ def test__EventCollection__get_lineoffset(): check to make sure the default lineoffset matches the input lineoffset ''' _, coll, props = generate_EventCollection_plot() - assert_equal(props['lineoffset'], coll.get_lineoffset()) + assert props['lineoffset'] == coll.get_lineoffset() def test__EventCollection__get_linestyle(): @@ -120,7 +118,7 @@ def test__EventCollection__get_linestyle(): check to make sure the default linestyle matches the input linestyle ''' _, coll, _ = generate_EventCollection_plot() - assert_equal(coll.get_linestyle(), [(None, None)]) + assert coll.get_linestyle() == [(None, None)] def test__EventCollection__get_color(): @@ -214,8 +212,8 @@ def test__EventCollection__switch_orientation(): splt, coll, props = generate_EventCollection_plot() new_orientation = 'vertical' coll.switch_orientation() - assert_equal(new_orientation, coll.get_orientation()) - assert_equal(False, coll.is_horizontal()) + assert new_orientation == coll.get_orientation() + assert not coll.is_horizontal() new_positions = coll.get_positions() check_segments(coll, new_positions, @@ -237,8 +235,8 @@ def test__EventCollection__switch_orientation_2x(): coll.switch_orientation() coll.switch_orientation() new_positions = coll.get_positions() - assert_equal(props['orientation'], coll.get_orientation()) - assert_equal(True, coll.is_horizontal()) + assert props['orientation'] == coll.get_orientation() + assert coll.is_horizontal() np.testing.assert_array_equal(props['positions'], new_positions) check_segments(coll, new_positions, @@ -256,8 +254,8 @@ def test__EventCollection__set_orientation(): splt, coll, props = generate_EventCollection_plot() new_orientation = 'vertical' coll.set_orientation(new_orientation) - assert_equal(new_orientation, coll.get_orientation()) - assert_equal(False, coll.is_horizontal()) + assert new_orientation == coll.get_orientation() + assert not coll.is_horizontal() check_segments(coll, props['positions'], props['linelength'], @@ -276,7 +274,7 @@ def test__EventCollection__set_linelength(): splt, coll, props = generate_EventCollection_plot() new_linelength = 15 coll.set_linelength(new_linelength) - assert_equal(new_linelength, coll.get_linelength()) + assert new_linelength == coll.get_linelength() check_segments(coll, props['positions'], new_linelength, @@ -294,7 +292,7 @@ def test__EventCollection__set_lineoffset(): splt, coll, props = generate_EventCollection_plot() new_lineoffset = -5. coll.set_lineoffset(new_lineoffset) - assert_equal(new_lineoffset, coll.get_lineoffset()) + assert new_lineoffset == coll.get_lineoffset() check_segments(coll, props['positions'], props['linelength'], @@ -312,7 +310,7 @@ def test__EventCollection__set_linestyle(): splt, coll, _ = generate_EventCollection_plot() new_linestyle = 'dashed' coll.set_linestyle(new_linestyle) - assert_equal(coll.get_linestyle(), [(0, (6.0, 6.0))]) + assert coll.get_linestyle() == [(0, (6.0, 6.0))] splt.set_title('EventCollection: set_linestyle') @@ -325,7 +323,7 @@ def test__EventCollection__set_linestyle_single_dash(): splt, coll, _ = generate_EventCollection_plot() new_linestyle = (0, (6., 6.)) coll.set_linestyle(new_linestyle) - assert_equal(coll.get_linestyle(), [(0, (6.0, 6.0))]) + assert coll.get_linestyle() == [(0, (6.0, 6.0))] splt.set_title('EventCollection: set_linestyle') @@ -337,7 +335,7 @@ def test__EventCollection__set_linewidth(): splt, coll, _ = generate_EventCollection_plot() new_linewidth = 5 coll.set_linewidth(new_linewidth) - assert_equal(coll.get_linewidth(), new_linewidth) + assert coll.get_linewidth() == new_linewidth splt.set_title('EventCollection: set_linewidth') @@ -376,10 +374,10 @@ def check_segments(coll, positions, linelength, lineoffset, orientation): # test to make sure each segment is correct for i, segment in enumerate(segments): - assert_equal(segment[0, pos1], lineoffset + linelength / 2.) - assert_equal(segment[1, pos1], lineoffset - linelength / 2.) - assert_equal(segment[0, pos2], positions[i]) - assert_equal(segment[1, pos2], positions[i]) + assert segment[0, pos1] == lineoffset + linelength / 2 + assert segment[1, pos1] == lineoffset - linelength / 2 + assert segment[0, pos2] == positions[i] + assert segment[1, pos2] == positions[i] def check_allprop_array(values, target): @@ -408,7 +406,7 @@ def test_add_collection(): ax.add_collection(coll) bounds = ax.dataLim.bounds coll = ax.scatter([], []) - assert_equal(ax.dataLim.bounds, bounds) + assert ax.dataLim.bounds == bounds def test_quiver_limits(): @@ -416,7 +414,7 @@ def test_quiver_limits(): x, y = np.arange(8), np.arange(10) u = v = np.linspace(0, 10, 80).reshape(10, 8) q = plt.quiver(x, y, u, v) - assert_equal(q.get_datalim(ax.transData).bounds, (0., 0., 7., 9.)) + assert q.get_datalim(ax.transData).bounds == (0., 0., 7., 9.) plt.figure() ax = plt.axes() @@ -425,7 +423,7 @@ def test_quiver_limits(): y, x = np.meshgrid(y, x) trans = mtransforms.Affine2D().translate(25, 32) + ax.transData plt.quiver(x, y, np.sin(x), np.cos(y), transform=trans) - assert_equal(ax.dataLim.bounds, (20.0, 30.0, 15.0, 6.0)) + assert ax.dataLim.bounds == (20.0, 30.0, 15.0, 6.0) def test_barb_limits(): @@ -444,6 +442,7 @@ def test_barb_limits(): @image_comparison(baseline_images=['EllipseCollection_test_image'], extensions=['png'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), remove_text=True) def test_EllipseCollection(): # Test basic functionality @@ -527,8 +526,7 @@ def test_regularpolycollection_scale(): class SquareCollection(mcollections.RegularPolyCollection): def __init__(self, **kwargs): - super(SquareCollection, self).__init__( - 4, rotation=np.pi/4., **kwargs) + super().__init__(4, rotation=np.pi/4., **kwargs) def get_transform(self): """Return transform scaling circle areas to data space.""" @@ -616,28 +614,28 @@ def test_lslw_bcast(): col.set_linestyles(['-', '-']) col.set_linewidths([1, 2, 3]) - assert_equal(col.get_linestyles(), [(None, None)] * 6) - assert_equal(col.get_linewidths(), [1, 2, 3] * 2) + assert col.get_linestyles() == [(None, None)] * 6 + assert col.get_linewidths() == [1, 2, 3] * 2 col.set_linestyles(['-', '-', '-']) - assert_equal(col.get_linestyles(), [(None, None)] * 3) - assert_equal(col.get_linewidths(), [1, 2, 3]) + assert col.get_linestyles() == [(None, None)] * 3 + assert (col.get_linewidths() == [1, 2, 3]).all() @pytest.mark.style('default') def test_capstyle(): col = mcollections.PathCollection([], capstyle='round') - assert_equal(col.get_capstyle(), 'round') + assert col.get_capstyle() == 'round' col.set_capstyle('butt') - assert_equal(col.get_capstyle(), 'butt') + assert col.get_capstyle() == 'butt' @pytest.mark.style('default') def test_joinstyle(): col = mcollections.PathCollection([], joinstyle='round') - assert_equal(col.get_joinstyle(), 'round') + assert col.get_joinstyle() == 'round' col.set_joinstyle('miter') - assert_equal(col.get_joinstyle(), 'miter') + assert col.get_joinstyle() == 'miter' @image_comparison(baseline_images=['cap_and_joinstyle'], diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 12a9bed3be3b..56a829418910 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -1,12 +1,10 @@ -from __future__ import absolute_import, division, print_function - import numpy as np import pytest from matplotlib import rc_context from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt -from matplotlib.colors import BoundaryNorm, LogNorm +from matplotlib.colors import BoundaryNorm, LogNorm, PowerNorm from matplotlib.cm import get_cmap from matplotlib.colorbar import ColorbarBase @@ -272,6 +270,83 @@ def test_colorbar_ticks(): assert len(cbar.ax.xaxis.get_ticklocs()) == len(clevs) +def test_colorbar_minorticks_on_off(): + # test for github issue #11510 and PR #11584 + np.random.seed(seed=12345) + data = np.random.randn(20, 20) + with rc_context({'_internal.classic_mode': False}): + fig, ax = plt.subplots() + # purposefully setting vmin and vmax to odd fractions + # so as to check for the correct locations of the minor ticks + im = ax.pcolormesh(data, vmin=-2.3, vmax=3.3) + + cbar = fig.colorbar(im, extend='both') + cbar.minorticks_on() + correct_minorticklocs = np.array([-2.2, -1.8, -1.6, -1.4, -1.2, -0.8, + -0.6, -0.4, -0.2, 0.2, 0.4, 0.6, + 0.8, 1.2, 1.4, 1.6, 1.8, 2.2, 2.4, + 2.6, 2.8, 3.2]) + # testing after minorticks_on() + np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), + correct_minorticklocs) + cbar.minorticks_off() + # testing after minorticks_off() + np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), + np.array([])) + + im.set_clim(vmin=-1.2, vmax=1.2) + cbar.minorticks_on() + correct_minorticklocs = np.array([-1.2, -1.1, -0.9, -0.8, -0.7, -0.6, + -0.4, -0.3, -0.2, -0.1, 0.1, 0.2, + 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, + 1.1, 1.2]) + np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), + correct_minorticklocs) + + +def test_colorbar_autoticks(): + # Test new autotick modes. Needs to be classic because + # non-classic doesn't go this route. + with rc_context({'_internal.classic_mode': False}): + fig, ax = plt.subplots(2, 1) + x = np.arange(-3.0, 4.001) + y = np.arange(-4.0, 3.001) + X, Y = np.meshgrid(x, y) + Z = X * Y + pcm = ax[0].pcolormesh(X, Y, Z) + cbar = fig.colorbar(pcm, ax=ax[0], extend='both', + orientation='vertical') + + pcm = ax[1].pcolormesh(X, Y, Z) + cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both', + orientation='vertical', shrink=0.4) + np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(), + np.arange(-10, 11., 5.)) + np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(), + np.arange(-10, 11., 10.)) + + +def test_colorbar_autotickslog(): + # Test new autotick modes... + with rc_context({'_internal.classic_mode': False}): + fig, ax = plt.subplots(2, 1) + x = np.arange(-3.0, 4.001) + y = np.arange(-4.0, 3.001) + X, Y = np.meshgrid(x, y) + Z = X * Y + pcm = ax[0].pcolormesh(X, Y, 10**Z, norm=LogNorm()) + cbar = fig.colorbar(pcm, ax=ax[0], extend='both', + orientation='vertical') + + pcm = ax[1].pcolormesh(X, Y, 10**Z, norm=LogNorm()) + cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both', + orientation='vertical', shrink=0.4) + np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(), + 10**np.arange(-12, 12.2, 4.)) + np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(), + 10**np.arange(-12, 13., 12.)) + + def test_colorbar_get_ticks(): # test feature for #5792 plt.figure() @@ -306,6 +381,14 @@ def test_colorbar_lognorm_extension(): assert cb._values[0] >= 0.0 +def test_colorbar_powernorm_extension(): + # Test that colorbar with powernorm is extended correctly + f, ax = plt.subplots() + cb = ColorbarBase(ax, norm=PowerNorm(gamma=0.5, vmin=0.0, vmax=1.0), + orientation='vertical', extend='both') + assert cb._values[0] >= 0.0 + + def test_colorbar_axes_kw(): # test fix for #8493: This does only test, that axes-related keywords pass # and do not raise an exception. diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index b2d5a0c65688..2f221f07d036 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1,10 +1,5 @@ -from __future__ import absolute_import, division, print_function - import copy -import six import itertools -import warnings -from distutils.version import LooseVersion as V import numpy as np import pytest @@ -106,7 +101,7 @@ def test_BoundaryNorm(): expected = [-1, 0, 1, 2] for v, ex in zip(vals, expected): ret = bn(v) - assert isinstance(ret, six.integer_types) + assert isinstance(ret, int) assert_array_equal(ret, ex) assert_array_equal(bn([v]), ex) @@ -115,7 +110,7 @@ def test_BoundaryNorm(): expected = [-1, 0, 2, 3] for v, ex in zip(vals, expected): ret = bn(v) - assert isinstance(ret, six.integer_types) + assert isinstance(ret, int) assert_array_equal(ret, ex) assert_array_equal(bn([v]), ex) @@ -124,7 +119,7 @@ def test_BoundaryNorm(): expected = [0, 0, 2, 2] for v, ex in zip(vals, expected): ret = bn(v) - assert isinstance(ret, six.integer_types) + assert isinstance(ret, int) assert_array_equal(ret, ex) assert_array_equal(bn([v]), ex) @@ -191,6 +186,15 @@ def test_PowerNorm(): assert pnorm(a[-1], clip=True) == expected[-1] +def test_PowerNorm_translation_invariance(): + a = np.array([0, 1/2, 1], dtype=float) + expected = [0, 1/8, 1] + pnorm = mcolors.PowerNorm(vmin=0, vmax=1, gamma=3) + assert_array_almost_equal(pnorm(a), expected) + pnorm = mcolors.PowerNorm(vmin=-2, vmax=-1, gamma=3) + assert_array_almost_equal(pnorm(a - 2), expected) + + def test_Normalize(): norm = mcolors.Normalize() vals = np.arange(-10, 10, 1, dtype=float) @@ -458,17 +462,9 @@ def test_light_source_shading_default(): [1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00]] ]).T - if (V(np.__version__) == V('1.9.0')): - # Numpy 1.9.0 uses a 2. order algorithm on the edges by default - # This was changed back again in 1.9.1 - expect = expect[1:-1, 1:-1, :] - rgb = rgb[1:-1, 1:-1, :] - assert_array_almost_equal(rgb, expect, decimal=2) -@pytest.mark.xfail(V('1.7.0') <= V(np.__version__) <= V('1.9.0'), - reason='NumPy version is not buggy') # Numpy 1.9.1 fixed a bug in masked arrays which resulted in # additional elements being masked when calculating the gradient thus # the output is different with earlier numpy versions. @@ -538,14 +534,7 @@ def alternative_hillshade(azimuth, elev, z): dy = -dy dz = np.ones_like(dy) normals = np.dstack([dx, dy, dz]) - dividers = np.zeros_like(z)[..., None] - for i, mat in enumerate(normals): - for j, vec in enumerate(mat): - dividers[i, j, 0] = np.linalg.norm(vec) - normals /= dividers - # once we drop support for numpy 1.7.x the above can be written as - # normals /= np.linalg.norm(normals, axis=2)[..., None] - # aviding the double loop. + normals /= np.linalg.norm(normals, axis=2)[..., None] intensity = np.tensordot(normals, illum, axes=(2, 0)) intensity -= intensity.min() @@ -717,13 +706,7 @@ def __add__(self, other): fig, ax = plt.subplots() ax.imshow(mydata, norm=norm) fig.canvas.draw() - if isinstance(norm, mcolors.PowerNorm): - assert len(recwarn) == 1 - warn = recwarn.pop(UserWarning) - assert ('Power-law scaling on negative values is ill-defined' - in str(warn.message)) - else: - assert len(recwarn) == 0 + assert len(recwarn) == 0 recwarn.clear() diff --git a/lib/matplotlib/tests/test_compare_images.py b/lib/matplotlib/tests/test_compare_images.py index 746462c62b07..162c5a1932aa 100644 --- a/lib/matplotlib/tests/test_compare_images.py +++ b/lib/matplotlib/tests/test_compare_images.py @@ -1,19 +1,11 @@ -from __future__ import absolute_import, division, print_function - -import six - -import io import os import shutil -import warnings -from numpy.testing import assert_almost_equal import pytest from pytest import approx from matplotlib.testing.compare import compare_images -from matplotlib.testing.decorators import _image_directories, image_comparison -from matplotlib.testing.exceptions import ImageComparisonFailure +from matplotlib.testing.decorators import _image_directories baseline_dir, result_dir = _image_directories(lambda: 'dummy func') @@ -78,124 +70,3 @@ def test_image_comparison_expect_rms(im1, im2, tol, expect_rms): else: assert results is not None assert results['rms'] == approx(expect_rms, abs=1e-4) - - -# The following tests are used by test_nose_image_comparison to ensure that the -# image_comparison decorator continues to work with nose. They should not be -# prefixed by test_ so they don't run with pytest. - - -def nosetest_empty(): - pass - - -def nosetest_simple_figure(): - import matplotlib.pyplot as plt - fig, ax = plt.subplots(figsize=(6.4, 4), dpi=100) - ax.plot([1, 2, 3], [3, 4, 5]) - return fig - - -def nosetest_manual_text_removal(): - from matplotlib.testing.decorators import ImageComparisonTest - - fig = nosetest_simple_figure() - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - # Make sure this removes text like it should. - ImageComparisonTest.remove_text(fig) - - assert len(w) == 1 - assert 'remove_text function was deprecated in version 2.1.' in str(w[0]) - - -@pytest.mark.parametrize( - 'func, kwargs, errors, failures, dots', - [ - (nosetest_empty, {'baseline_images': []}, [], [], ''), - (nosetest_empty, {'baseline_images': ['foo']}, - [(AssertionError, - 'Test generated 0 images but there are 1 baseline images')], - [], - 'E'), - (nosetest_simple_figure, - {'baseline_images': ['basn3p02'], 'extensions': ['png'], - 'remove_text': True}, - [], - [(ImageComparisonFailure, 'Image sizes do not match expected size:')], - 'F'), - (nosetest_simple_figure, - {'baseline_images': ['simple']}, - [], - [(ImageComparisonFailure, 'images not close')] * 3, - 'FFF'), - (nosetest_simple_figure, - {'baseline_images': ['simple'], 'remove_text': True}, - [], - [], - '...'), - (nosetest_manual_text_removal, - {'baseline_images': ['simple']}, - [], - [], - '...'), - ], - ids=[ - 'empty', - 'extra baselines', - 'incorrect shape', - 'failing figure', - 'passing figure', - 'manual text removal', - ]) -def test_nose_image_comparison(func, kwargs, errors, failures, dots, - monkeypatch): - nose = pytest.importorskip('nose') - monkeypatch.setattr('matplotlib._called_from_pytest', False) - - class TestResultVerifier(nose.result.TextTestResult): - def __init__(self, *args, **kwargs): - super(TestResultVerifier, self).__init__(*args, **kwargs) - self.error_count = 0 - self.failure_count = 0 - - def addError(self, test, err): - super(TestResultVerifier, self).addError(test, err) - - if self.error_count < len(errors): - assert err[0] is errors[self.error_count][0] - assert errors[self.error_count][1] in str(err[1]) - else: - raise err[1] - self.error_count += 1 - - def addFailure(self, test, err): - super(TestResultVerifier, self).addFailure(test, err) - - assert self.failure_count < len(failures), err[1] - assert err[0] is failures[self.failure_count][0] - assert failures[self.failure_count][1] in str(err[1]) - self.failure_count += 1 - - # Make sure that multiple extensions work, but don't require LaTeX or - # Inkscape to do so. - kwargs.setdefault('extensions', ['png', 'png', 'png']) - - func = image_comparison(**kwargs)(func) - loader = nose.loader.TestLoader() - suite = loader.loadTestsFromGenerator( - func, - 'matplotlib.tests.test_compare_images') - if six.PY2: - output = io.BytesIO() - else: - output = io.StringIO() - result = TestResultVerifier(stream=output, descriptions=True, verbosity=1) - with warnings.catch_warnings(): - # Nose uses deprecated stuff; we don't care about it. - warnings.simplefilter('ignore', DeprecationWarning) - suite.run(result=result) - - assert output.getvalue() == dots - assert result.error_count == len(errors) - assert result.failure_count == len(failures) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index fcf633a0821d..0803504cea94 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -1,17 +1,8 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six -import warnings - - import numpy as np import pytest from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt -from matplotlib.offsetbox import AnchoredOffsetbox, DrawingArea -from matplotlib.patches import Rectangle import matplotlib.gridspec as gridspec from matplotlib import ticker, rcParams @@ -103,9 +94,9 @@ def test_constrained_layout5(): def test_constrained_layout6(): 'Test constrained_layout for nested gridspecs' fig = plt.figure(constrained_layout=True) - gs = gridspec.GridSpec(1, 2, figure=fig) - gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs[0]) - gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs[1]) + gs = fig.add_gridspec(1, 2, figure=fig) + gsl = gs[0].subgridspec(2, 2) + gsr = gs[1].subgridspec(1, 2) axsl = [] for gs in gsl: ax = fig.add_subplot(gs) @@ -123,40 +114,6 @@ def test_constrained_layout6(): ticks=ticker.MaxNLocator(nbins=5)) -@image_comparison(baseline_images=['constrained_layout8'], - extensions=['png']) -def test_constrained_layout8(): - 'Test for gridspecs that are not completely full' - fig = plt.figure(figsize=(7, 4), constrained_layout=True) - gs = gridspec.GridSpec(3, 5, figure=fig) - axs = [] - j = 1 - for i in [0, 1]: - ax = fig.add_subplot(gs[j, i]) - axs += [ax] - pcm = example_pcolor(ax, fontsize=10) - if i > 0: - ax.set_ylabel('') - if j < 1: - ax.set_xlabel('') - ax.set_title('') - j = 0 - for i in [2, 4]: - ax = fig.add_subplot(gs[j, i]) - axs += [ax] - pcm = example_pcolor(ax, fontsize=10) - if i > 0: - ax.set_ylabel('') - if j < 1: - ax.set_xlabel('') - ax.set_title('') - ax = fig.add_subplot(gs[2, :]) - axs += [ax] - pcm = example_pcolor(ax, fontsize=10) - - fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6) - - def test_constrained_layout7(): 'Test for proper warning if fig not set in GridSpec' with pytest.warns(UserWarning, match='Calling figure.constrained_layout, ' @@ -179,26 +136,20 @@ def test_constrained_layout8(): fig = plt.figure(figsize=(10, 5), constrained_layout=True) gs = gridspec.GridSpec(3, 5, figure=fig) axs = [] - j = 1 - for i in [0, 4]: - ax = fig.add_subplot(gs[j, i]) - axs += [ax] - pcm = example_pcolor(ax, fontsize=9) - if i > 0: - ax.set_ylabel('') - if j < 1: - ax.set_xlabel('') - ax.set_title('') - j = 0 - for i in [1]: - ax = fig.add_subplot(gs[j, i]) - axs += [ax] - pcm = example_pcolor(ax, fontsize=9) - if i > 0: - ax.set_ylabel('') - if j < 1: - ax.set_xlabel('') - ax.set_title('') + for j in [0, 1]: + if j == 0: + ilist = [1] + else: + ilist = [0, 4] + for i in ilist: + ax = fig.add_subplot(gs[j, i]) + axs += [ax] + pcm = example_pcolor(ax, fontsize=9) + if i > 0: + ax.set_ylabel('') + if j < 1: + ax.set_xlabel('') + ax.set_title('') ax = fig.add_subplot(gs[2, :]) axs += [ax] pcm = example_pcolor(ax, fontsize=9) diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index 2e1b24d4e424..8e894d9e9084 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six import matplotlib.pyplot as plt diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 42903ac6897e..9868f4023cd7 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -1,11 +1,10 @@ -from __future__ import absolute_import, division, print_function - import datetime import numpy as np from matplotlib.testing.decorators import image_comparison from matplotlib import pyplot as plt from numpy.testing import assert_array_almost_equal +from matplotlib.colors import LogNorm import pytest import warnings @@ -131,6 +130,17 @@ def test_contour_empty_levels(): assert len(record) == 1 +def test_contour_Nlevels(): + # A scalar levels arg or kwarg should trigger auto level generation. + # https://github.com/matplotlib/matplotlib/issues/11913 + z = np.arange(12).reshape((3, 4)) + fig, ax = plt.subplots() + cs1 = ax.contour(z, 5) + assert len(cs1.levels) > 1 + cs2 = ax.contour(z, levels=5) + assert (cs1.levels == cs2.levels).all() + + def test_contour_badlevel_fmt(): # test funny edge case from # https://github.com/matplotlib/matplotlib/issues/9742 @@ -217,7 +227,7 @@ def test_given_colors_levels_and_extends(): @image_comparison(baseline_images=['contour_datetime_axis'], - extensions=['png'], remove_text=False) + extensions=['png'], remove_text=False, style='mpl20') def test_contour_datetime_axis(): fig = plt.figure() fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15) @@ -243,7 +253,7 @@ def test_contour_datetime_axis(): @image_comparison(baseline_images=['contour_test_label_transforms'], - extensions=['png'], remove_text=True) + extensions=['png'], remove_text=True, style='mpl20') def test_labels(): # Adapted from pylab_examples example code: contour_demo.py # see issues #2475, #2843, and #2818 for explanation @@ -359,3 +369,36 @@ def test_circular_contour_warning(): cs = plt.contour(x, y, r) plt.clabel(cs) assert len(record) == 0 + + +@image_comparison(baseline_images=['contour_log_extension'], + extensions=['png'], remove_text=True, style='mpl20') +def test_contourf_log_extension(): + # Test that contourf with lognorm is extended correctly + fig = plt.figure(figsize=(10, 5)) + fig.subplots_adjust(left=0.05, right=0.95) + ax1 = fig.add_subplot(131) + ax2 = fig.add_subplot(132) + ax3 = fig.add_subplot(133) + + # make data set with large range e.g. between 1e-8 and 1e10 + data_exp = np.linspace(-7.5, 9.5, 1200) + data = np.power(10, data_exp).reshape(30, 40) + # make manual levels e.g. between 1e-4 and 1e-6 + levels_exp = np.arange(-4., 7.) + levels = np.power(10., levels_exp) + + # original data + c1 = ax1.contourf(data, + norm=LogNorm(vmin=data.min(), vmax=data.max())) + # just show data in levels + c2 = ax2.contourf(data, levels=levels, + norm=LogNorm(vmin=levels.min(), vmax=levels.max()), + extend='neither') + # extend data from levels + c3 = ax3.contourf(data, levels=levels, + norm=LogNorm(vmin=levels.min(), vmax=levels.max()), + extend='both') + plt.colorbar(c1, ax=ax1) + plt.colorbar(c2, ax=ax2) + plt.colorbar(c3, ax=ax3) diff --git a/lib/matplotlib/tests/test_cycles.py b/lib/matplotlib/tests/test_cycles.py index dfa0f7c796f1..8184d3eeeb0c 100644 --- a/lib/matplotlib/tests/test_cycles.py +++ b/lib/matplotlib/tests/test_cycles.py @@ -1,7 +1,7 @@ import warnings +import platform from matplotlib.testing.decorators import image_comparison -from matplotlib.cbook import MatplotlibDeprecationWarning import matplotlib.pyplot as plt import numpy as np import pytest @@ -10,6 +10,7 @@ @image_comparison(baseline_images=['color_cycle_basic'], remove_text=True, + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), extensions=['png']) def test_colorcycle_basic(): fig, ax = plt.subplots() @@ -27,6 +28,7 @@ def test_colorcycle_basic(): @image_comparison(baseline_images=['marker_cycle', 'marker_cycle'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), remove_text=True, extensions=['png']) def test_marker_cycle(): fig, ax = plt.subplots() @@ -60,6 +62,7 @@ def test_marker_cycle(): @image_comparison(baseline_images=['lineprop_cycle_basic'], remove_text=True, + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), extensions=['png']) def test_linestylecycle_basic(): fig, ax = plt.subplots() @@ -175,17 +178,6 @@ def test_cycle_reset(): got = next(ax._get_lines.prop_cycler) assert prop == got - fig, ax = plt.subplots() - # Need to double-check the old set/get_color_cycle(), too - with warnings.catch_warnings(): - warnings.simplefilter("ignore", MatplotlibDeprecationWarning) - prop = next(ax._get_lines.prop_cycler) - ax.set_color_cycle(['c', 'm', 'y', 'k']) - assert prop != next(ax._get_lines.prop_cycler) - ax.set_color_cycle(None) - got = next(ax._get_lines.prop_cycler) - assert prop == got - def test_invalid_input_forms(): fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index dfc5d3bda399..db153be5ff97 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -1,27 +1,26 @@ -from __future__ import absolute_import, division, print_function - -from six.moves import map - - import datetime -import dateutil import tempfile +from unittest.mock import Mock +import dateutil.tz +import dateutil.rrule import numpy as np import pytest -import pytz - -try: - # mock in python 3.3+ - from unittest import mock -except ImportError: - import mock from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt +from matplotlib.cbook import MatplotlibDeprecationWarning import matplotlib.dates as mdates +def __has_pytz(): + try: + import pytz + return True + except ImportError: + return False + + def test_date_numpyx(): # test that numpy dates work properly... base = datetime.datetime(2017, 1, 1) @@ -189,8 +188,8 @@ def test_RRuleLocator(): def test_RRuleLocator_dayrange(): loc = mdates.DayLocator() - x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=pytz.UTC) - y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=pytz.UTC) + x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC) + y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC) loc.tick_values(x1, y1) # On success, no overflow error shall be thrown @@ -249,7 +248,8 @@ def test_strftime_fields(dt): minute=dt.minute, second=dt.second, microsecond=dt.microsecond)) - assert formatter.strftime(dt) == formatted_date_str + with pytest.warns(MatplotlibDeprecationWarning): + assert formatter.strftime(dt) == formatted_date_str try: # Test strftime("%x") with the current locale. @@ -257,8 +257,9 @@ def test_strftime_fields(dt): locale_formatter = mdates.DateFormatter("%x") locale_d_fmt = locale.nl_langinfo(locale.D_FMT) expanded_formatter = mdates.DateFormatter(locale_d_fmt) - assert locale_formatter.strftime(dt) == \ - expanded_formatter.strftime(dt) + with pytest.warns(MatplotlibDeprecationWarning): + assert locale_formatter.strftime(dt) == \ + expanded_formatter.strftime(dt) except (ImportError, AttributeError): pass @@ -270,7 +271,7 @@ def test_strftime_fields(dt): def test_date_formatter_callable(): scale = -11 - locator = mock.Mock(_get_unit=mock.Mock(return_value=scale)) + locator = Mock(_get_unit=Mock(return_value=scale)) callable_formatting_function = (lambda dates, _: [dt.strftime('%d-%m//%Y') for dt in dates]) @@ -329,7 +330,7 @@ def test_empty_date_with_year_formatter(): def test_auto_date_locator(): def _create_auto_date_locator(date1, date2): - locator = mdates.AutoDateLocator() + locator = mdates.AutoDateLocator(interval_multiples=False) locator.create_dummy_axis() locator.set_view_interval(mdates.date2num(date1), mdates.date2num(date2)) @@ -430,10 +431,12 @@ def _create_auto_date_locator(date1, date2): '1997-05-01 00:00:00+00:00', '1997-05-22 00:00:00+00:00'] ], [datetime.timedelta(days=40), - ['1997-01-01 00:00:00+00:00', '1997-01-08 00:00:00+00:00', - '1997-01-15 00:00:00+00:00', '1997-01-22 00:00:00+00:00', - '1997-01-29 00:00:00+00:00', '1997-02-01 00:00:00+00:00', - '1997-02-08 00:00:00+00:00'] + ['1997-01-01 00:00:00+00:00', '1997-01-05 00:00:00+00:00', + '1997-01-09 00:00:00+00:00', '1997-01-13 00:00:00+00:00', + '1997-01-17 00:00:00+00:00', '1997-01-21 00:00:00+00:00', + '1997-01-25 00:00:00+00:00', '1997-01-29 00:00:00+00:00', + '1997-02-01 00:00:00+00:00', '1997-02-05 00:00:00+00:00', + '1997-02-09 00:00:00+00:00'] ], [datetime.timedelta(hours=40), ['1997-01-01 00:00:00+00:00', '1997-01-01 04:00:00+00:00', @@ -487,8 +490,8 @@ def test_date_inverted_limit(): def _test_date2num_dst(date_range, tz_convert): # Timezones - BRUSSELS = pytz.timezone('Europe/Brussels') - UTC = pytz.UTC + BRUSSELS = dateutil.tz.gettz('Europe/Brussels') + UTC = mdates.UTC # Create a list of timezone-aware datetime objects in UTC # Interval is 0b0.0000011 days, to prevent float rounding issues @@ -518,7 +521,7 @@ class dt_tzaware(datetime.datetime): subtraction. """ def __sub__(self, other): - r = super(dt_tzaware, self).__sub__(other) + r = super().__sub__(other) tzinfo = getattr(r, 'tzinfo', None) if tzinfo is not None: @@ -532,10 +535,10 @@ def __sub__(self, other): return r def __add__(self, other): - return self.mk_tzaware(super(dt_tzaware, self).__add__(other)) + return self.mk_tzaware(super().__add__(other)) def astimezone(self, tzinfo): - dt = super(dt_tzaware, self).astimezone(tzinfo) + dt = super().astimezone(tzinfo) return self.mk_tzaware(dt) @classmethod @@ -580,10 +583,7 @@ def tz_convert(*args): _test_date2num_dst(pd.date_range, tz_convert) -@pytest.mark.parametrize("attach_tz, get_tz", [ - (lambda dt, zi: zi.localize(dt), lambda n: pytz.timezone(n)), - (lambda dt, zi: dt.replace(tzinfo=zi), lambda n: dateutil.tz.gettz(n))]) -def test_rrulewrapper(attach_tz, get_tz): +def _test_rrulewrapper(attach_tz, get_tz): SYD = get_tz('Australia/Sydney') dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD) @@ -598,6 +598,25 @@ def test_rrulewrapper(attach_tz, get_tz): assert act == exp +def test_rrulewrapper(): + def attach_tz(dt, zi): + return dt.replace(tzinfo=zi) + + _test_rrulewrapper(attach_tz, dateutil.tz.gettz) + + +@pytest.mark.pytz +@pytest.mark.skipif(not __has_pytz(), reason="Requires pytz") +def test_rrulewrapper_pytz(): + # Test to make sure pytz zones are supported in rrules + import pytz + + def attach_tz(dt, zi): + return zi.localize(dt) + + _test_rrulewrapper(attach_tz, pytz.timezone) + + def test_DayLocator(): with pytest.raises(ValueError): mdates.DayLocator(interval=-1) diff --git a/lib/matplotlib/tests/test_dviread.py b/lib/matplotlib/tests/test_dviread.py index eb1bd10584ba..9514c0f50e86 100644 --- a/lib/matplotlib/tests/test_dviread.py +++ b/lib/matplotlib/tests/test_dviread.py @@ -1,37 +1,32 @@ -from __future__ import absolute_import, division, print_function - -import six -from matplotlib.testing.decorators import skip_if_command_unavailable +import json +from pathlib import Path +import shutil import matplotlib.dviread as dr -import os.path -import json import pytest def test_PsfontsMap(monkeypatch): monkeypatch.setattr(dr, 'find_tex_file', lambda x: x) - filename = os.path.join( - os.path.dirname(__file__), - 'baseline_images', 'dviread', 'test.map') + filename = str(Path(__file__).parent / 'baseline_images/dviread/test.map') fontmap = dr.PsfontsMap(filename) # Check all properties of a few fonts for n in [1, 2, 3, 4, 5]: - key = ('TeXfont%d' % n).encode('ascii') + key = b'TeXfont%d' % n entry = fontmap[key] assert entry.texname == key - assert entry.psname == ('PSfont%d' % n).encode('ascii') + assert entry.psname == b'PSfont%d' % n if n not in [3, 5]: - assert entry.encoding == ('font%d.enc' % n).encode('ascii') + assert entry.encoding == b'font%d.enc' % n elif n == 3: assert entry.encoding == b'enc3.foo' # We don't care about the encoding of TeXfont5, which specifies # multiple encodings. if n not in [1, 5]: - assert entry.filename == ('font%d.pfa' % n).encode('ascii') + assert entry.filename == b'font%d.pfa' % n else: - assert entry.filename == ('font%d.pfb' % n).encode('ascii') + assert entry.filename == b'font%d.pfb' % n if n == 4: assert entry.effects == {'slant': -0.1, 'extend': 2.2} else: @@ -54,18 +49,16 @@ def test_PsfontsMap(monkeypatch): assert 'no-such-font' in str(exc.value) -@skip_if_command_unavailable(["kpsewhich", "-version"]) +@pytest.mark.skipif(shutil.which("kpsewhich") is None, + reason="kpsewhich is not available") def test_dviread(): - dir = os.path.join(os.path.dirname(__file__), 'baseline_images', 'dviread') - with open(os.path.join(dir, 'test.json')) as f: + dirpath = Path(__file__).parent / 'baseline_images/dviread' + with (dirpath / 'test.json').open() as f: correct = json.load(f) - for entry in correct: - entry['text'] = [[a, b, c, d.encode('ascii'), e] - for [a, b, c, d, e] in entry['text']] - with dr.Dvi(os.path.join(dir, 'test.dvi'), None) as dvi: + with dr.Dvi(str(dirpath / 'test.dvi'), None) as dvi: data = [{'text': [[t.x, t.y, - six.unichr(t.glyph), - t.font.texname, + chr(t.glyph), + t.font.texname.decode('ascii'), round(t.font.size, 2)] for t in page.text], 'boxes': [[b.x, b.y, b.height, b.width] for b in page.boxes]} diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 69752e17d666..7cb5af621450 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1,10 +1,9 @@ -from __future__ import absolute_import, division, print_function - import sys import warnings +import platform from matplotlib import rcParams -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal from matplotlib.axes import Axes from matplotlib.ticker import AutoMinorLocator, FixedFormatter import matplotlib.pyplot as plt @@ -14,7 +13,8 @@ import pytest -@image_comparison(baseline_images=['figure_align_labels']) +@image_comparison(baseline_images=['figure_align_labels'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0)) def test_align_labels(): # Check the figure.align_labels() command fig = plt.figure(tight_layout=True) @@ -215,7 +215,7 @@ def test_iterability_axes_argument(): # This is a regression test for matplotlib/matplotlib#3196. If one of the # arguments returned by _as_mpl_axes defines __getitem__ but is not - # iterable, this would raise an execption. This is because we check + # iterable, this would raise an exception. This is because we check # whether the arguments are iterable, and if so we try and convert them # to a tuple. However, the ``iterable`` function returns True if # __getitem__ is present, but some classes can define __getitem__ without @@ -223,8 +223,7 @@ def test_iterability_axes_argument(): # case it fails. class MyAxes(Axes): - def __init__(self, *args, **kwargs): - kwargs.pop('myclass', None) + def __init__(self, *args, myclass=None, **kwargs): return Axes.__init__(self, *args, **kwargs) class MyClass(object): @@ -384,6 +383,35 @@ def test_warn_cl_plus_tl(): assert not(fig.get_constrained_layout()) +@check_figures_equal(extensions=["png", "pdf"]) +def test_add_artist(fig_test, fig_ref): + fig_test.set_dpi(100) + fig_ref.set_dpi(100) + + ax = fig_test.subplots() + l1 = plt.Line2D([.2, .7], [.7, .7], gid='l1') + l2 = plt.Line2D([.2, .7], [.8, .8], gid='l2') + r1 = plt.Circle((20, 20), 100, transform=None, gid='C1') + r2 = plt.Circle((.7, .5), .05, gid='C2') + r3 = plt.Circle((4.5, .8), .55, transform=fig_test.dpi_scale_trans, + facecolor='crimson', gid='C3') + for a in [l1, l2, r1, r2, r3]: + fig_test.add_artist(a) + l2.remove() + + ax2 = fig_ref.subplots() + l1 = plt.Line2D([.2, .7], [.7, .7], transform=fig_ref.transFigure, + gid='l1', zorder=21) + r1 = plt.Circle((20, 20), 100, transform=None, clip_on=False, zorder=20, + gid='C1') + r2 = plt.Circle((.7, .5), .05, transform=fig_ref.transFigure, gid='C2', + zorder=20) + r3 = plt.Circle((4.5, .8), .55, transform=fig_ref.dpi_scale_trans, + facecolor='crimson', clip_on=False, zorder=20, gid='C3') + for a in [l1, r1, r2, r3]: + ax2.add_artist(a) + + @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+") @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"]) def test_fspath(fmt, tmpdir): @@ -394,3 +422,28 @@ def test_fspath(fmt, tmpdir): # All the supported formats include the format name (case-insensitive) # in the first 100 bytes. assert fmt.encode("ascii") in file.read(100).lower() + + +def test_tightbbox(): + fig, ax = plt.subplots() + ax.set_xlim(0, 1) + t = ax.text(1., 0.5, 'This dangles over end') + renderer = fig.canvas.get_renderer() + x1Nom0 = 9.035 # inches + assert np.abs(t.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 + assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom0) < 0.05 + assert np.abs(fig.get_tightbbox(renderer).x0 - 0.679) < 0.05 + # now exclude t from the tight bbox so now the bbox is quite a bit + # smaller + t.set_in_layout(False) + x1Nom = 7.333 + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom * fig.dpi) < 2 + assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom) < 0.05 + + t.set_in_layout(True) + x1Nom = 7.333 + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 + # test bbox_extra_artists method... + assert np.abs(ax.get_tightbbox(renderer, + bbox_extra_artists=[]).x1 - x1Nom * fig.dpi) < 2 diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 56436cc392f4..4e75aa5199c3 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -1,8 +1,5 @@ -from __future__ import absolute_import, division, print_function - -import six - import os +import shutil import tempfile import warnings @@ -11,16 +8,10 @@ from matplotlib.font_manager import ( findfont, FontProperties, fontManager, json_dump, json_load, get_font, - get_fontconfig_fonts, is_opentype_cff_font, fontManager as fm) + get_fontconfig_fonts, is_opentype_cff_font) from matplotlib import rc_context -if six.PY2: - from distutils.spawn import find_executable - has_fclist = find_executable('fc-list') is not None -else: - # py >= 3.3 - from shutil import which - has_fclist = which('fc-list') is not None +has_fclist = shutil.which('fc-list') is not None def test_font_priority(): @@ -76,11 +67,11 @@ def test_otf(): if os.path.exists(fname): assert is_opentype_cff_font(fname) - otf_files = [f for f in fm.ttffiles if 'otf' in f] - for f in otf_files: - with open(f, 'rb') as fd: - res = fd.read(4) == b'OTTO' - assert res == is_opentype_cff_font(f) + for f in fontManager.ttflist: + if 'otf' in f.fname: + with open(f.fname, 'rb') as fd: + res = fd.read(4) == b'OTTO' + assert res == is_opentype_cff_font(f.fname) @pytest.mark.skipif(not has_fclist, reason='no fontconfig installed') diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index e6da25789f0b..893108258b65 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1,11 +1,10 @@ -from __future__ import absolute_import, division, print_function - -import six - +from contextlib import ExitStack from copy import copy import io import os import sys +import platform +import urllib.request import warnings import numpy as np @@ -23,14 +22,6 @@ import pytest -try: - from PIL import Image - HAS_PIL = True -except ImportError: - HAS_PIL = False -needs_pillow = pytest.mark.xfail(not HAS_PIL, reason='Test requires Pillow') - - @image_comparison(baseline_images=['image_interps'], style='mpl20') def test_image_interps(): 'make the basic nearest, bilinear and bicubic interps' @@ -113,17 +104,17 @@ def test_image_python_io(): plt.imread(buffer) -@needs_pillow def test_imread_pil_uint16(): + pytest.importorskip("PIL") img = plt.imread(os.path.join(os.path.dirname(__file__), 'baseline_images', 'test_image', 'uint16.tif')) - assert (img.dtype == np.uint16) + assert img.dtype == np.uint16 assert np.sum(img) == 134184960 @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+") -@needs_pillow def test_imread_fspath(): + pytest.importorskip("PIL") from pathlib import Path img = plt.imread( Path(__file__).parent / 'baseline_images/test_image/uint16.tif') @@ -208,6 +199,7 @@ def test_image_alpha(): plt.subplot(133) plt.imshow(Z, alpha=0.5, interpolation='nearest') + def test_cursor_data(): from matplotlib.backend_bases import MouseEvent @@ -289,7 +281,8 @@ def test_image_cliprect(): im = ax.imshow(d, extent=(0,5,0,5)) - rect = patches.Rectangle(xy=(1,1), width=2, height=2, transform=im.axes.transData) + rect = patches.Rectangle( + xy=(1,1), width=2, height=2, transform=im.axes.transData) im.set_clip_path(rect) @@ -305,13 +298,10 @@ def test_imshow(): @image_comparison(baseline_images=['no_interpolation_origin'], remove_text=True) def test_no_interpolation_origin(): - fig = plt.figure() - ax = fig.add_subplot(211) - ax.imshow(np.arange(100).reshape((2, 50)), origin="lower", - interpolation='none') - - ax = fig.add_subplot(212) - ax.imshow(np.arange(100).reshape((2, 50)), interpolation='none') + fig, axs = plt.subplots(2) + axs[0].imshow(np.arange(100).reshape((2, 50)), origin="lower", + interpolation='none') + axs[1].imshow(np.arange(100).reshape((2, 50)), interpolation='none') @image_comparison(baseline_images=['image_shift'], remove_text=True, @@ -319,9 +309,9 @@ def test_no_interpolation_origin(): def test_image_shift(): from matplotlib.colors import LogNorm - imgData = [[1.0/(x) + 1.0/(y) for x in range(1,100)] for y in range(1,100)] - tMin=734717.945208 - tMax=734717.946366 + imgData = [[1 / x + 1 / y for x in range(1, 100)] for y in range(1, 100)] + tMin = 734717.945208 + tMax = 734717.946366 fig, ax = plt.subplots() ax.imshow(imgData, norm=LogNorm(), interpolation='none', @@ -379,11 +369,13 @@ def test_image_composite_alpha(): fig, ax = plt.subplots() arr = np.zeros((11, 21, 4)) arr[:, :, 0] = 1 - arr[:, :, 3] = np.concatenate((np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1])) + arr[:, :, 3] = np.concatenate( + (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1])) arr2 = np.zeros((21, 11, 4)) arr2[:, :, 0] = 1 arr2[:, :, 1] = 1 - arr2[:, :, 3] = np.concatenate((np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))[:, np.newaxis] + arr2[:, :, 3] = np.concatenate( + (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))[:, np.newaxis] ax.imshow(arr, extent=[1, 2, 5, 0], alpha=0.3) ax.imshow(arr, extent=[2, 3, 5, 0], alpha=0.6) ax.imshow(arr, extent=[3, 4, 5, 0]) @@ -400,21 +392,22 @@ def test_image_composite_alpha(): remove_text=True, style='mpl20') def test_rasterize_dpi(): # This test should check rasterized rendering with high output resolution. - # It plots a rasterized line and a normal image with implot. So it will catch - # when images end up in the wrong place in case of non-standard dpi setting. - # Instead of high-res rasterization i use low-res. Therefore the fact that the - # resolution is non-standard is easily checked by image_comparison. + # It plots a rasterized line and a normal image with implot. So it will + # catch when images end up in the wrong place in case of non-standard dpi + # setting. Instead of high-res rasterization I use low-res. Therefore + # the fact that the resolution is non-standard is easily checked by + # image_comparison. img = np.asarray([[1, 2], [3, 4]]) - fig, axes = plt.subplots(1, 3, figsize = (3, 1)) + fig, axes = plt.subplots(1, 3, figsize=(3, 1)) axes[0].imshow(img) - axes[1].plot([0,1],[0,1], linewidth=20., rasterized=True) - axes[1].set(xlim = (0,1), ylim = (-1, 2)) + axes[1].plot([0,1], [0,1], linewidth=20., rasterized=True) + axes[1].set(xlim=(0, 1), ylim=(-1, 2)) - axes[2].plot([0,1],[0,1], linewidth=20.) - axes[2].set(xlim = (0,1), ylim = (-1, 2)) + axes[2].plot([0,1], [0,1], linewidth=20.) + axes[2].set(xlim=(0, 1), ylim=(-1, 2)) # Low-dpi PDF rasterization errors prevent proper image comparison tests. # Hide detailed structures like the axes spines. @@ -444,8 +437,8 @@ def test_bbox_image_inverted(): image = np.identity(10) - bbox_im = BboxImage( - TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]), ax.figure.transFigure)) + bbox_im = BboxImage(TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]), + ax.figure.transFigure)) bbox_im.set_data(image) bbox_im.set_clip_on(False) ax.add_artist(bbox_im) @@ -462,7 +455,8 @@ def test_get_window_extent_for_AxisImage(): ax.set_position([0, 0, 1, 1]) ax.set_xlim(0, 1) ax.set_ylim(0, 1) - im_obj = ax.imshow(im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest') + im_obj = ax.imshow( + im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest') fig.canvas.draw() renderer = fig.canvas.renderer @@ -497,8 +491,8 @@ def test_nonuniformimage_setnorm(): im.set_norm(plt.Normalize()) -@needs_pillow def test_jpeg_2d(): + Image = pytest.importorskip('PIL.Image') # smoke test that mode-L pillow images work. imd = np.ones((10, 10), dtype='uint8') for i in range(10): @@ -509,8 +503,9 @@ def test_jpeg_2d(): ax.imshow(im) -@needs_pillow def test_jpeg_alpha(): + Image = pytest.importorskip('PIL.Image') + plt.figure(figsize=(1, 1), dpi=300) # Create an image that is all black, with a gradient from 0-1 in # the alpha channel from left to right. @@ -575,12 +570,6 @@ def test_pcolorimage_setdata(): assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed' -def test_pcolorimage_extent(): - im = plt.hist2d([1, 2, 3], [3, 5, 6], - bins=[[0, 3, 7], [1, 2, 3]])[-1] - assert im.get_extent() == (0, 7, 1, 3) - - def test_minimized_rasterized(): # This ensures that the rasterized content in the colorbars is # only as thick as the colorbar, and doesn't extend to other parts @@ -616,9 +605,9 @@ def test_minimized_rasterized(): @pytest.mark.network def test_load_from_url(): - req = six.moves.urllib.request.urlopen( - "http://matplotlib.org/_static/logo_sidebar_horiz.png") - plt.imread(req) + url = "http://matplotlib.org/_static/logo_sidebar_horiz.png" + plt.imread(url) + plt.imread(urllib.request.urlopen(url)) @image_comparison(baseline_images=['log_scale_image'], @@ -768,6 +757,7 @@ def test_imshow_endianess(): @image_comparison(baseline_images=['imshow_masked_interpolation'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), remove_text=True, style='mpl20') def test_imshow_masked_interpolation(): @@ -779,7 +769,6 @@ def test_imshow_masked_interpolation(): N = 20 n = colors.Normalize(vmin=0, vmax=N*N-1) - # data = np.random.random((N, N))*N*N data = np.arange(N*N, dtype='float').reshape(N, N) data[5, 5] = -1 @@ -887,6 +876,10 @@ def test_empty_imshow(make_norm): def test_imshow_float128(): fig, ax = plt.subplots() ax.imshow(np.zeros((3, 3), dtype=np.longdouble)) + with (ExitStack() if np.can_cast(np.longdouble, np.float64, "equiv") + else pytest.warns(UserWarning)): + # Ensure that drawing doesn't cause crash. + fig.canvas.draw() def test_imshow_bool(): @@ -894,14 +887,6 @@ def test_imshow_bool(): ax.imshow(np.array([[True, False], [False, True]], dtype=bool)) -def test_imshow_deprecated_interd_warn(): - im = plt.imshow([[1, 2], [3, np.nan]]) - for k in ('_interpd', '_interpdr', 'iterpnames'): - with warnings.catch_warnings(record=True) as warns: - getattr(im, k) - assert len(warns) == 1 - - def test_full_invalid(): x = np.ones((10, 10)) x[:] = np.nan diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 55b8adc77745..d508d6f41fc2 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1,15 +1,11 @@ -from __future__ import absolute_import, division, print_function - -try: - # mock in python 3.3+ - from unittest import mock -except ImportError: - import mock import collections +import inspect +import platform +from unittest import mock + import numpy as np import pytest - from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt import matplotlib as mpl @@ -17,41 +13,7 @@ import matplotlib.collections as mcollections from matplotlib.legend_handler import HandlerTuple import matplotlib.legend as mlegend -import inspect - - -# test that docstrigs are the same -def get_docstring_section(func, section): - """ extract a section from the docstring of a function """ - ll = inspect.getdoc(func) - lines = ll.splitlines() - insec = False - st = '' - for ind in range(len(lines)): - if lines[ind][:len(section)] == section and lines[ind+1][:3] == '---': - insec = True - ind = ind+1 - if insec: - if len(lines[ind + 1]) > 3 and lines[ind + 1][0:3] == '---': - insec = False - break - else: - st += lines[ind] + '\n' - return st - - -def test_legend_kwdocstrings(): - stax = get_docstring_section(mpl.axes.Axes.legend, 'Parameters') - stfig = get_docstring_section(mpl.figure.Figure.legend, 'Parameters') - assert stfig == stax - - stleg = get_docstring_section(mpl.legend.Legend.__init__, - 'Other Parameters') - stax = get_docstring_section(mpl.axes.Axes.legend, 'Other Parameters') - stfig = get_docstring_section(mpl.figure.Figure.legend, 'Other Parameters') - assert stleg == stax - assert stfig == stax - assert stleg == stfig +from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning def test_legend_ordereddict(): @@ -68,7 +30,8 @@ def test_legend_ordereddict(): handles, labels = ax.get_legend_handles_labels() legend = collections.OrderedDict(zip(labels, handles)) - ax.legend(legend.values(), legend.keys(), loc=6, bbox_to_anchor=(1, .5)) + ax.legend(legend.values(), legend.keys(), + loc='center left', bbox_to_anchor=(1, .5)) @image_comparison(baseline_images=['legend_auto1'], remove_text=True) @@ -79,7 +42,7 @@ def test_legend_auto1(): x = np.arange(100) ax.plot(x, 50 - x, 'o', label='y=1') ax.plot(x, x - 50, 'o', label='y=-1') - ax.legend(loc=0) + ax.legend(loc='best') @image_comparison(baseline_images=['legend_auto2'], remove_text=True) @@ -88,9 +51,9 @@ def test_legend_auto2(): fig = plt.figure() ax = fig.add_subplot(111) x = np.arange(100) - b1 = ax.bar(x, x, color='m') - b2 = ax.bar(x, x[::-1], color='g') - ax.legend([b1[0], b2[0]], ['up', 'down'], loc=0) + b1 = ax.bar(x, x, align='edge', color='m') + b2 = ax.bar(x, x[::-1], align='edge', color='g') + ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best') @image_comparison(baseline_images=['legend_auto3']) @@ -103,7 +66,7 @@ def test_legend_auto3(): ax.plot(x, y, 'o-', label='line') ax.set_xlim(0.0, 1.0) ax.set_ylim(0.0, 1.0) - ax.legend(loc=0) + ax.legend(loc='best') @image_comparison(baseline_images=['legend_various_labels'], remove_text=True) @@ -112,9 +75,9 @@ def test_various_labels(): fig = plt.figure() ax = fig.add_subplot(121) ax.plot(np.arange(4), 'o', label=1) - ax.plot(np.linspace(4, 4.1), 'o', label=u'D\xe9velopp\xe9s') + ax.plot(np.linspace(4, 4.1), 'o', label='Développés') ax.plot(np.arange(4, 1, -1), 'o', label='__nolegend__') - ax.legend(numpoints=1, loc=0) + ax.legend(numpoints=1, loc='best') @image_comparison(baseline_images=['legend_labels_first'], extensions=['png'], @@ -126,7 +89,7 @@ def test_labels_first(): ax.plot(np.arange(10), '-o', label=1) ax.plot(np.ones(10)*5, ':x', label="x") ax.plot(np.arange(20, 10, -1), 'd', label="diamond") - ax.legend(loc=0, markerfirst=False) + ax.legend(loc='best', markerfirst=False) @image_comparison(baseline_images=['legend_multiple_keys'], extensions=['png'], @@ -145,17 +108,19 @@ def test_multiple_keys(): @image_comparison(baseline_images=['rgba_alpha'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), extensions=['png'], remove_text=True) def test_alpha_rgba(): import matplotlib.pyplot as plt fig, ax = plt.subplots(1, 1) ax.plot(range(10), lw=5) - leg = plt.legend(['Longlabel that will go away'], loc=10) + leg = plt.legend(['Longlabel that will go away'], loc='center') leg.legendPatch.set_facecolor([1, 0, 0, 0.5]) @image_comparison(baseline_images=['rcparam_alpha'], + tol={'aarch64': 0.02}.get(platform.machine(), 0.0), extensions=['png'], remove_text=True) def test_alpha_rcparam(): import matplotlib.pyplot as plt @@ -163,7 +128,7 @@ def test_alpha_rcparam(): fig, ax = plt.subplots(1, 1) ax.plot(range(10), lw=5) with mpl.rc_context(rc={'legend.framealpha': .75}): - leg = plt.legend(['Longlabel that will go away'], loc=10) + leg = plt.legend(['Longlabel that will go away'], loc='center') # this alpha is going to be over-ridden by the rcparam with # sets the alpha of the patch to be non-None which causes the alpha # value of the face color to be discarded. This behavior may not be @@ -183,7 +148,8 @@ def test_fancy(): ncol=2, shadow=True, title="My legend", numpoints=1) -@image_comparison(baseline_images=['framealpha'], remove_text=True) +@image_comparison(baseline_images=['framealpha'], remove_text=True, + tol={'aarch64': 0.02}.get(platform.machine(), 0.0)) def test_framealpha(): x = np.linspace(1, 100, 100) y = x @@ -217,12 +183,12 @@ def test_legend_expand(): x = np.arange(100) for ax, mode in zip(axes_list, legend_modes): ax.plot(x, 50 - x, 'o', label='y=1') - l1 = ax.legend(loc=2, mode=mode) + l1 = ax.legend(loc='upper left', mode=mode) ax.add_artist(l1) ax.plot(x, x - 50, 'o', label='y=-1') - l2 = ax.legend(loc=5, mode=mode) + l2 = ax.legend(loc='right', mode=mode) ax.add_artist(l2) - ax.legend(loc=3, mode=mode, ncol=2) + ax.legend(loc='lower left', mode=mode, ncol=2) @image_comparison(baseline_images=['hatching'], remove_text=True, @@ -410,7 +376,7 @@ def test_legend_stackplot(): ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3']) ax.set_xlim((0, 10)) ax.set_ylim((0, 70)) - ax.legend(loc=0) + ax.legend(loc='best') def test_cross_figure_patch_legend(): @@ -517,3 +483,83 @@ def test_shadow_framealpha(): ax.plot(range(100), label="test") leg = ax.legend(shadow=True, facecolor='w') assert leg.get_frame().get_alpha() == 1 + + +def test_legend_title_empty(): + # test that if we don't set the legend title, that + # it comes back as an empty string, and that it is not + # visible: + fig, ax = plt.subplots() + ax.plot(range(10)) + leg = ax.legend() + assert leg.get_title().get_text() == "" + assert leg.get_title().get_visible() is False + + +def test_legend_proper_window_extent(): + # test that legend returns the expected extent under various dpi... + fig, ax = plt.subplots(dpi=100) + ax.plot(range(10), label='Aardvark') + leg = ax.legend() + x01 = leg.get_window_extent(fig.canvas.get_renderer()).x0 + + fig, ax = plt.subplots(dpi=200) + ax.plot(range(10), label='Aardvark') + leg = ax.legend() + x02 = leg.get_window_extent(fig.canvas.get_renderer()).x0 + assert pytest.approx(x01*2, 0.1) == x02 + + +def test_window_extent_cached_renderer(): + fig, ax = plt.subplots(dpi=100) + ax.plot(range(10), label='Aardvark') + leg = ax.legend() + leg2 = fig.legend() + fig.canvas.draw() + # check that get_window_extent will use the cached renderer + leg.get_window_extent() + leg2.get_window_extent() + + +def test_legend_title_fontsize(): + # test the title_fontsize kwarg + fig, ax = plt.subplots() + ax.plot(range(10)) + leg = ax.legend(title='Aardvark', title_fontsize=22) + assert leg.get_title().get_fontsize() == 22 + + +def test_get_set_draggable(): + legend = plt.legend() + assert not legend.get_draggable() + legend.set_draggable(True) + assert legend.get_draggable() + legend.set_draggable(False) + assert not legend.get_draggable() + + +def test_draggable(): + legend = plt.legend() + with pytest.warns(MatplotlibDeprecationWarning): + legend.draggable(True) + assert legend.get_draggable() + with pytest.warns(MatplotlibDeprecationWarning): + legend.draggable(False) + assert not legend.get_draggable() + + # test toggle + with pytest.warns(MatplotlibDeprecationWarning): + legend.draggable() + assert legend.get_draggable() + with pytest.warns(MatplotlibDeprecationWarning): + legend.draggable() + assert not legend.get_draggable() + + +def test_alpha_handles(): + x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red') + legend = plt.legend() + for lh in legend.legendHandles: + lh.set_alpha(1.0) + assert lh.get_facecolor()[:-1] == hh[1].get_facecolor()[:-1] + assert lh.get_edgecolor()[:-1] == hh[1].get_edgecolor()[:-1] diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index e263d948c2f6..f933942d9532 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -2,19 +2,18 @@ Tests specific to the lines module. """ -from __future__ import absolute_import, division, print_function - from io import BytesIO import itertools -import matplotlib.lines as mlines -import pytest -from timeit import repeat -import numpy as np +import timeit + from cycler import cycler +import numpy as np +import pytest import matplotlib +import matplotlib.lines as mlines import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal # Runtimes on a loaded system are inherently flaky. Not so much that a rerun @@ -33,7 +32,7 @@ def test_invisible_Line_rendering(): """ # Creates big x and y data: N = 10**7 - x = np.linspace(0,1,N) + x = np.linspace(0, 1, N) y = np.random.normal(size=N) # Create a plot figure: @@ -41,13 +40,13 @@ def test_invisible_Line_rendering(): ax = plt.subplot(111) # Create a "big" Line instance: - l = mlines.Line2D(x,y) + l = mlines.Line2D(x, y) l.set_visible(False) # but don't add it to the Axis instance `ax` # [here Interactive panning and zooming is pretty responsive] # Time the canvas drawing: - t_no_line = min(repeat(fig.canvas.draw, number=1, repeat=3)) + t_no_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3)) # (gives about 25 ms) # Add the big invisible Line: @@ -55,11 +54,11 @@ def test_invisible_Line_rendering(): # [Now interactive panning and zooming is very slow] # Time the canvas drawing: - t_unvisible_line = min(repeat(fig.canvas.draw, number=1, repeat=3)) + t_unvisible_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3)) # gives about 290 ms for N = 10**7 pts slowdown_factor = (t_unvisible_line/t_no_line) - slowdown_threshold = 2 # trying to avoid false positive failures + slowdown_threshold = 2 # trying to avoid false positive failures assert slowdown_factor < slowdown_threshold @@ -145,7 +144,8 @@ def test_set_drawstyle(): assert len(line.get_path().vertices) == len(x) -@image_comparison(baseline_images=['line_collection_dashes'], remove_text=True) +@image_comparison(baseline_images=['line_collection_dashes'], + remove_text=True, style='mpl20') def test_set_line_coll_dash_image(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) @@ -201,13 +201,7 @@ def test_nan_is_sorted(): assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2]) -def test_step_markers(): - fig, ax = plt.subplots() - ax.step([0, 1], "-o") - buf1 = BytesIO() - fig.savefig(buf1) - fig, ax = plt.subplots() - ax.plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2]) - buf2 = BytesIO() - fig.savefig(buf2) - assert buf1.getvalue() == buf2.getvalue() +@check_figures_equal() +def test_step_markers(fig_test, fig_ref): + fig_test.subplots().step([0, 1], "-o") + fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2]) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index c268e4252e9a..1ef9c18c47fb 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -1,5 +1,6 @@ import numpy as np from matplotlib import markers +from matplotlib.path import Path import pytest @@ -18,3 +19,10 @@ def test_markers_invalid(): # Checking this does fail. with pytest.raises(ValueError): marker_style.set_marker(mrk_array) + + +def test_marker_path(): + marker_style = markers.MarkerStyle() + path = Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) + # Checking this doesn't fail. + marker_style.set_marker(path) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index cdc1093e1417..a05c8b3c8977 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six - import io import re @@ -59,7 +55,7 @@ '$\\alpha \\beta \\gamma \\delta \\epsilon \\zeta \\eta \\theta \\iota \\lambda \\mu \\nu \\xi \\pi \\kappa \\rho \\sigma \\tau \\upsilon \\phi \\chi \\psi$', # The examples prefixed by 'mmltt' are from the MathML torture test here: - # http://www.mozilla.org/projects/mathml/demo/texvsmml.xhtml + # http://www.mozilla.org/projects/mathml/demo/texvsmml.xhtml r'${x}^{2}{y}^{2}$', r'${}_{2}F_{3}$', r'$\frac{x+{y}^{2}}{k+1}$', @@ -86,31 +82,31 @@ # mathtex doesn't support array # 'mmltt23' : r'$\left(\begin{array}{cc}\hfill \left(\begin{array}{cc}\hfill a\hfill & \hfill b\hfill \\ \hfill c\hfill & \hfill d\hfill \end{array}\right)\hfill & \hfill \left(\begin{array}{cc}\hfill e\hfill & \hfill f\hfill \\ \hfill g\hfill & \hfill h\hfill \end{array}\right)\hfill \\ \hfill 0\hfill & \hfill \left(\begin{array}{cc}\hfill i\hfill & \hfill j\hfill \\ \hfill k\hfill & \hfill l\hfill \end{array}\right)\hfill \end{array}\right)$', # mathtex doesn't support array - # 'mmltt24' : u'$det|\\begin{array}{ccccc}\\hfill {c}_{0}\\hfill & \\hfill {c}_{1}\\hfill & \\hfill {c}_{2}\\hfill & \\hfill \\dots \\hfill & \\hfill {c}_{n}\\hfill \\\\ \\hfill {c}_{1}\\hfill & \\hfill {c}_{2}\\hfill & \\hfill {c}_{3}\\hfill & \\hfill \\dots \\hfill & \\hfill {c}_{n+1}\\hfill \\\\ \\hfill {c}_{2}\\hfill & \\hfill {c}_{3}\\hfill & \\hfill {c}_{4}\\hfill & \\hfill \\dots \\hfill & \\hfill {c}_{n+2}\\hfill \\\\ \\hfill \\u22ee\\hfill & \\hfill \\u22ee\\hfill & \\hfill \\u22ee\\hfill & \\hfill \\hfill & \\hfill \\u22ee\\hfill \\\\ \\hfill {c}_{n}\\hfill & \\hfill {c}_{n+1}\\hfill & \\hfill {c}_{n+2}\\hfill & \\hfill \\dots \\hfill & \\hfill {c}_{2n}\\hfill \\end{array}|>0$', + # 'mmltt24' : r'$det|\begin{array}{ccccc}\hfill {c}_{0}\hfill & \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill \dots \hfill & \hfill {c}_{n}\hfill \\ \hfill {c}_{1}\hfill & \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill \dots \hfill & \hfill {c}_{n+1}\hfill \\ \hfill {c}_{2}\hfill & \hfill {c}_{3}\hfill & \hfill {c}_{4}\hfill & \hfill \dots \hfill & \hfill {c}_{n+2}\hfill \\ \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \u22ee\hfill & \hfill \hfill & \hfill \u22ee\hfill \\ \hfill {c}_{n}\hfill & \hfill {c}_{n+1}\hfill & \hfill {c}_{n+2}\hfill & \hfill \dots \hfill & \hfill {c}_{2n}\hfill \end{array}|>0$', r'${y}_{{x}_{2}}$', r'${x}_{92}^{31415}+\pi $', r'${x}_{{y}_{b}^{a}}^{{z}_{c}^{d}}$', r'${y}_{3}^{\prime \prime \prime }$', - r"$\left( \xi \left( 1 - \xi \right) \right)$", # Bug 2969451 - r"$\left(2 \, a=b\right)$", # Sage bug #8125 - r"$? ! &$", # github issue #466 - r'$\operatorname{cos} x$', # github issue #553 + r"$\left( \xi \left( 1 - \xi \right) \right)$", # Bug 2969451 + r"$\left(2 \, a=b\right)$", # Sage bug #8125 + r"$? ! &$", # github issue #466 + r'$\operatorname{cos} x$', # github issue #553 r'$\sum _{\genfrac{}{}{0}{}{0\leq i\leq m}{0 M \: M \; M \ M \enspace M \quad M \qquad M \! M$', r'$\Cup$ $\Cap$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$', r'$\dotplus$ $\doteq$ $\doteqdot$ $\ddots$', - r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873 + r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873 r'${xyz}^k{x}_{k}{x}^{p}{y}^{p-2} {d}_{i}^{j}{b}_{j}{c}_{k}{d} {x}^{j}_{i}{E}^{0}{E}^0_u$', r'${\int}_x^x x\oint_x^x x\int_{X}^{X}x\int_x x \int^x x \int_{x} x\int^{x}{\int}_{x} x{\int}^{x}_{x}x$', r'testing$^{123}$', ' '.join('$\\' + p + '$' for p in sorted(mathtext.Parser._snowflake)), r'$6-2$; $-2$; $ -2$; ${-2}$; ${ -2}$; $20^{+3}_{-2}$', - r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444 - r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799 - r'$\left(X\right)_{a}^{b}$', # github issue 7615 - r'$\dfrac{\$100.00}{y}$', # github issue #1888 + r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444 + r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799 + r'$\left(X\right)_{a}^{b}$', # github issue 7615 + r'$\dfrac{\$100.00}{y}$', # github issue #1888 ] digits = "0123456789" diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index cd432f713b6c..6109d326f4a0 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function - -import six - import tempfile import warnings @@ -17,13 +13,6 @@ from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning -try: - from mpl_toolkits.natgrid import _natgrid - HAS_NATGRID = True -except ImportError: - HAS_NATGRID = False - - ''' A lot of mlab.py has been deprecated in Matplotlib 2.2 and is scheduled for removal in the future. The tests that use deprecated methods have a block @@ -212,19 +201,12 @@ def test_stride_ensure_integer_type(self): @pytest.fixture def tempcsv(): - if six.PY2: - fd = tempfile.TemporaryFile(suffix='csv', mode="wb+") - else: - fd = tempfile.TemporaryFile(suffix='csv', mode="w+", newline='') - with fd: + with tempfile.TemporaryFile(suffix='csv', mode="w+", newline='') as fd: yield fd def test_recarray_csv_roundtrip(tempcsv): - expected = np.recarray((99,), - [(str('x'), float), - (str('y'), float), - (str('t'), float)]) + expected = np.recarray((99,), [('x', float), ('y', float), ('t', float)]) # initialising all values: uninitialised memory sometimes produces # floats that do not round-trip to string and back. expected['x'][:] = np.linspace(-1e9, -1, 99) @@ -241,8 +223,7 @@ def test_recarray_csv_roundtrip(tempcsv): def test_rec2csv_bad_shape_ValueError(tempcsv): - bad = np.recarray((99, 4), [(str('x'), float), - (str('y'), float)]) + bad = np.recarray((99, 4), [('x', float), ('y', float)]) # the bad recarray should trigger a ValueError for having ndim > 1. with pytest.warns(MatplotlibDeprecationWarning): @@ -294,14 +275,12 @@ def test_csv2rec_dates(tempcsv, input, kwargs): def test_rec2txt_basic(): - # str() calls around field names necessary b/c as of numpy 1.11 - # dtype doesn't like unicode names (caused by unicode_literals import) a = np.array([(1.0, 2, 'foo', 'bing'), (2.0, 3, 'bar', 'blah')], - dtype=np.dtype([(str('x'), np.float32), - (str('y'), np.int8), - (str('s'), str, 3), - (str('s2'), str, 4)])) + dtype=np.dtype([('x', np.float32), + ('y', np.int8), + ('s', str, 3), + ('s2', str, 4)])) truth = (' x y s s2\n' ' 1.000 2 foo bing \n' ' 2.000 3 bar blah ').splitlines() @@ -1127,7 +1106,7 @@ def test_detrend_detrend_linear_1d_slope_off_axis1(self): res = mlab.detrend(input, key=mlab.detrend_linear, axis=0) assert_allclose(res, targ, atol=self.atol) - def test_detrend_str_linear_2d_slope_off_axis0(self): + def test_detrend_str_linear_2d_slope_off_axis0_notranspose(self): arri = [self.sig_off, self.sig_slope, self.sig_slope + self.sig_off] @@ -1139,7 +1118,7 @@ def test_detrend_str_linear_2d_slope_off_axis0(self): res = mlab.detrend(input, key='linear', axis=1) assert_allclose(res, targ, atol=self.atol) - def test_detrend_detrend_linear_1d_slope_off_axis1(self): + def test_detrend_detrend_linear_1d_slope_off_axis1_notranspose(self): arri = [self.sig_off, self.sig_slope, self.sig_slope + self.sig_off] @@ -2188,8 +2167,9 @@ def get_z(x, y): np.ma.getmask(correct_zi_masked)) -@pytest.mark.xfail(not HAS_NATGRID, reason='natgrid not installed') def test_griddata_nn(): + pytest.importorskip('mpl_toolkits.natgrid') + # z is a linear function of x and y. def get_z(x, y): return 3.0*x - y diff --git a/lib/matplotlib/tests/test_nbagg_01.ipynb b/lib/matplotlib/tests/test_nbagg_01.ipynb new file mode 100644 index 000000000000..c8839afe8ddd --- /dev/null +++ b/lib/matplotlib/tests/test_nbagg_01.ipynb @@ -0,0 +1,883 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib notebook\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
    ');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
    ');\n", + " var titletext = $(\n", + " '
    ');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
    ');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
    ')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('