ultraplot 0.99.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ultraplot/__init__.py +115 -0
- ultraplot/__init__.py.rej +58 -0
- ultraplot/axes/__init__.py +42 -0
- ultraplot/axes/base.py +3240 -0
- ultraplot/axes/cartesian.py +1425 -0
- ultraplot/axes/geo.py +1675 -0
- ultraplot/axes/plot.py +4569 -0
- ultraplot/axes/polar.py +381 -0
- ultraplot/axes/shared.py +186 -0
- ultraplot/axes/three.py +34 -0
- ultraplot/cmaps/Algae.rgb +256 -0
- ultraplot/cmaps/Amp.rgb +256 -0
- ultraplot/cmaps/BR.rgb +256 -0
- ultraplot/cmaps/Balance.rgb +256 -0
- ultraplot/cmaps/Blues1_r.xml +17 -0
- ultraplot/cmaps/Blues2.xml +16 -0
- ultraplot/cmaps/Blues3.xml +25 -0
- ultraplot/cmaps/Blues4_r.xml +17 -0
- ultraplot/cmaps/Blues5.xml +16 -0
- ultraplot/cmaps/Blues6.xml +25 -0
- ultraplot/cmaps/Blues7.xml +16 -0
- ultraplot/cmaps/Blues8.xml +17 -0
- ultraplot/cmaps/Blues9.xml +1 -0
- ultraplot/cmaps/Boreal.json +53 -0
- ultraplot/cmaps/Browns1.xml +16 -0
- ultraplot/cmaps/Browns2.xml +26 -0
- ultraplot/cmaps/Browns3.xml +17 -0
- ultraplot/cmaps/Browns4.xml +17 -0
- ultraplot/cmaps/Browns5.xml +26 -0
- ultraplot/cmaps/Browns6.xml +17 -0
- ultraplot/cmaps/Browns7.xml +19 -0
- ultraplot/cmaps/Browns8.xml +11 -0
- ultraplot/cmaps/Browns9.xml +1 -0
- ultraplot/cmaps/ColdHot.rgb +229 -0
- ultraplot/cmaps/Crest.rgb +256 -0
- ultraplot/cmaps/Curl.rgb +512 -0
- ultraplot/cmaps/Deep.rgb +256 -0
- ultraplot/cmaps/Delta.rgb +512 -0
- ultraplot/cmaps/Dense.rgb +256 -0
- ultraplot/cmaps/Div.json +71 -0
- ultraplot/cmaps/DryWet.json +73 -0
- ultraplot/cmaps/Dusk.json +53 -0
- ultraplot/cmaps/Fire.json +53 -0
- ultraplot/cmaps/Flare.rgb +256 -0
- ultraplot/cmaps/Glacial.json +53 -0
- ultraplot/cmaps/Greens1_r.xml +26 -0
- ultraplot/cmaps/Greens2.xml +28 -0
- ultraplot/cmaps/Greens3_r.xml +28 -0
- ultraplot/cmaps/Greens4.xml +17 -0
- ultraplot/cmaps/Greens5.xml +16 -0
- ultraplot/cmaps/Greens6_r.xml +16 -0
- ultraplot/cmaps/Greens7.xml +16 -0
- ultraplot/cmaps/Greens8.xml +26 -0
- ultraplot/cmaps/Haline.rgb +256 -0
- ultraplot/cmaps/Ice.rgb +256 -0
- ultraplot/cmaps/IceFire.rgb +256 -0
- ultraplot/cmaps/Mako.rgb +256 -0
- ultraplot/cmaps/Marine.json +53 -0
- ultraplot/cmaps/Matter.rgb +256 -0
- ultraplot/cmaps/Mono.txt +256 -0
- ultraplot/cmaps/MonoCycle.txt +256 -0
- ultraplot/cmaps/NegPos.json +71 -0
- ultraplot/cmaps/Oranges1.xml +27 -0
- ultraplot/cmaps/Oranges2.xml +26 -0
- ultraplot/cmaps/Oranges3.xml +15 -0
- ultraplot/cmaps/Oranges4.xml +23 -0
- ultraplot/cmaps/Oxy.rgb +256 -0
- ultraplot/cmaps/Phase.rgb +256 -0
- ultraplot/cmaps/Purples1_r.xml +16 -0
- ultraplot/cmaps/Purples2.xml +17 -0
- ultraplot/cmaps/Purples3.xml +18 -0
- ultraplot/cmaps/Reds1.xml +26 -0
- ultraplot/cmaps/Reds2.xml +22 -0
- ultraplot/cmaps/Reds3.xml +23 -0
- ultraplot/cmaps/Reds4.xml +26 -0
- ultraplot/cmaps/Reds5.xml +17 -0
- ultraplot/cmaps/Rocket.rgb +256 -0
- ultraplot/cmaps/Solar.rgb +256 -0
- ultraplot/cmaps/Speed.rgb +256 -0
- ultraplot/cmaps/Stellar.json +53 -0
- ultraplot/cmaps/Sunrise.json +53 -0
- ultraplot/cmaps/Sunset.json +53 -0
- ultraplot/cmaps/Tempo.rgb +256 -0
- ultraplot/cmaps/Thermal.rgb +256 -0
- ultraplot/cmaps/Turbid.rgb +256 -0
- ultraplot/cmaps/Vivid.xml +11 -0
- ultraplot/cmaps/Vlag.rgb +256 -0
- ultraplot/cmaps/Yellows1.xml +17 -0
- ultraplot/cmaps/Yellows2.xml +17 -0
- ultraplot/cmaps/Yellows3.xml +17 -0
- ultraplot/cmaps/Yellows4.xml +17 -0
- ultraplot/cmaps/acton.txt +256 -0
- ultraplot/cmaps/bam.txt +256 -0
- ultraplot/cmaps/bamO.txt +256 -0
- ultraplot/cmaps/bamako.txt +256 -0
- ultraplot/cmaps/batlow.txt +256 -0
- ultraplot/cmaps/batlowK.txt +256 -0
- ultraplot/cmaps/batlowW.txt +256 -0
- ultraplot/cmaps/berlin.txt +256 -0
- ultraplot/cmaps/bilbao.txt +256 -0
- ultraplot/cmaps/broc.txt +256 -0
- ultraplot/cmaps/brocO.txt +256 -0
- ultraplot/cmaps/buda.txt +256 -0
- ultraplot/cmaps/bukavu.txt +256 -0
- ultraplot/cmaps/cork.txt +256 -0
- ultraplot/cmaps/corkO.txt +256 -0
- ultraplot/cmaps/davos.txt +256 -0
- ultraplot/cmaps/devon.txt +256 -0
- ultraplot/cmaps/fes.txt +256 -0
- ultraplot/cmaps/hawaii.txt +256 -0
- ultraplot/cmaps/imola.txt +256 -0
- ultraplot/cmaps/lajolla.txt +256 -0
- ultraplot/cmaps/lapaz.txt +256 -0
- ultraplot/cmaps/lisbon.txt +256 -0
- ultraplot/cmaps/nuuk.txt +256 -0
- ultraplot/cmaps/oleron.txt +256 -0
- ultraplot/cmaps/oslo.txt +256 -0
- ultraplot/cmaps/roma.txt +256 -0
- ultraplot/cmaps/romaO.txt +256 -0
- ultraplot/cmaps/tofino.txt +256 -0
- ultraplot/cmaps/tokyo.txt +256 -0
- ultraplot/cmaps/turku.txt +256 -0
- ultraplot/cmaps/vanimo.txt +256 -0
- ultraplot/cmaps/vik.txt +256 -0
- ultraplot/cmaps/vikO.txt +256 -0
- ultraplot/colors/opencolor.txt +132 -0
- ultraplot/colors/xkcd.txt +951 -0
- ultraplot/colors.py +3241 -0
- ultraplot/colors.py.rej +243 -0
- ultraplot/config.py +1809 -0
- ultraplot/constructor.py +1633 -0
- ultraplot/cycles/538.hex +2 -0
- ultraplot/cycles/FlatUI.hex +1 -0
- ultraplot/cycles/Qual1.rgb +7 -0
- ultraplot/cycles/Qual2.rgb +13 -0
- ultraplot/cycles/bmh.hex +2 -0
- ultraplot/cycles/classic.hex +2 -0
- ultraplot/cycles/colorblind.hex +2 -0
- ultraplot/cycles/colorblind10.hex +2 -0
- ultraplot/cycles/default.hex +2 -0
- ultraplot/cycles/ggplot.hex +1 -0
- ultraplot/cycles/seaborn.hex +2 -0
- ultraplot/cycles/tableau.hex +2 -0
- ultraplot/demos.py +1201 -0
- ultraplot/externals/__init__.py +5 -0
- ultraplot/externals/hsluv.py +330 -0
- ultraplot/figure.py +2102 -0
- ultraplot/fonts/FiraMath-Bold.ttf +0 -0
- ultraplot/fonts/FiraMath-ExtraLight.ttf +0 -0
- ultraplot/fonts/FiraMath-Heavy.ttf +0 -0
- ultraplot/fonts/FiraMath-Light.ttf +0 -0
- ultraplot/fonts/FiraMath-Medium.ttf +0 -0
- ultraplot/fonts/FiraMath-Regular.ttf +0 -0
- ultraplot/fonts/FiraMath-SemiBold.ttf +0 -0
- ultraplot/fonts/FiraMath-UltraLight.ttf +0 -0
- ultraplot/fonts/FiraSans-Black.ttf +0 -0
- ultraplot/fonts/FiraSans-BlackItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-Bold.ttf +0 -0
- ultraplot/fonts/FiraSans-BoldItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-ExtraBold.ttf +0 -0
- ultraplot/fonts/FiraSans-ExtraBoldItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-ExtraLight.ttf +0 -0
- ultraplot/fonts/FiraSans-ExtraLightItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-Italic.ttf +0 -0
- ultraplot/fonts/FiraSans-Light.ttf +0 -0
- ultraplot/fonts/FiraSans-LightItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-Medium.ttf +0 -0
- ultraplot/fonts/FiraSans-MediumItalic.ttf +0 -0
- ultraplot/fonts/FiraSans-Regular.ttf +0 -0
- ultraplot/fonts/FiraSans-SemiBold.ttf +0 -0
- ultraplot/fonts/FiraSans-SemiBoldItalic.ttf +0 -0
- ultraplot/fonts/LICENSE_FIRAMATH.txt +92 -0
- ultraplot/fonts/LICENSE_FIRASANS.txt +97 -0
- ultraplot/fonts/LICENSE_NOTOSANS.txt +202 -0
- ultraplot/fonts/LICENSE_NOTOSERIF.txt +93 -0
- ultraplot/fonts/LICENSE_OPENSANS.txt +202 -0
- ultraplot/fonts/LICENSE_ROBOTO.txt +202 -0
- ultraplot/fonts/LICENSE_SOURCESANS.txt +93 -0
- ultraplot/fonts/LICENSE_SOURCESERIF.txt +93 -0
- ultraplot/fonts/LICENSE_TEXGYRE.txt +29 -0
- ultraplot/fonts/LICENSE_UBUNTU.txt +96 -0
- ultraplot/fonts/NotoSans-Bold.ttf +0 -0
- ultraplot/fonts/NotoSans-BoldItalic.ttf +0 -0
- ultraplot/fonts/NotoSans-Italic.ttf +0 -0
- ultraplot/fonts/NotoSans-Regular.ttf +0 -0
- ultraplot/fonts/NotoSerif-Bold.ttf +0 -0
- ultraplot/fonts/NotoSerif-BoldItalic.ttf +0 -0
- ultraplot/fonts/NotoSerif-Italic.ttf +0 -0
- ultraplot/fonts/NotoSerif-Regular.ttf +0 -0
- ultraplot/fonts/OpenSans-Bold.ttf +0 -0
- ultraplot/fonts/OpenSans-BoldItalic.ttf +0 -0
- ultraplot/fonts/OpenSans-Italic.ttf +0 -0
- ultraplot/fonts/OpenSans-Regular.ttf +0 -0
- ultraplot/fonts/OpenSans-Semibold.ttf +0 -0
- ultraplot/fonts/OpenSans-SemiboldItalic.ttf +0 -0
- ultraplot/fonts/Roboto-Black.ttf +0 -0
- ultraplot/fonts/Roboto-BlackItalic.ttf +0 -0
- ultraplot/fonts/Roboto-Bold.ttf +0 -0
- ultraplot/fonts/Roboto-BoldItalic.ttf +0 -0
- ultraplot/fonts/Roboto-Italic.ttf +0 -0
- ultraplot/fonts/Roboto-Light.ttf +0 -0
- ultraplot/fonts/Roboto-LightItalic.ttf +0 -0
- ultraplot/fonts/Roboto-Medium.ttf +0 -0
- ultraplot/fonts/Roboto-MediumItalic.ttf +0 -0
- ultraplot/fonts/Roboto-Regular.ttf +0 -0
- ultraplot/fonts/SourceSansPro-Black.ttf +0 -0
- ultraplot/fonts/SourceSansPro-BlackItalic.ttf +0 -0
- ultraplot/fonts/SourceSansPro-Bold.ttf +0 -0
- ultraplot/fonts/SourceSansPro-BoldItalic.ttf +0 -0
- ultraplot/fonts/SourceSansPro-ExtraLight.ttf +0 -0
- ultraplot/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
- ultraplot/fonts/SourceSansPro-Italic.ttf +0 -0
- ultraplot/fonts/SourceSansPro-Light.ttf +0 -0
- ultraplot/fonts/SourceSansPro-LightItalic.ttf +0 -0
- ultraplot/fonts/SourceSansPro-Regular.ttf +0 -0
- ultraplot/fonts/SourceSansPro-SemiBold.ttf +0 -0
- ultraplot/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-Black.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-BlackItalic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-Bold.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-BoldItalic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-ExtraLight.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-ExtraLightItalic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-Italic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-Light.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-LightItalic.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-Regular.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-SemiBold.ttf +0 -0
- ultraplot/fonts/SourceSerifPro-SemiBoldItalic.ttf +0 -0
- ultraplot/fonts/Ubuntu-Bold.ttf +0 -0
- ultraplot/fonts/Ubuntu-BoldItalic.ttf +0 -0
- ultraplot/fonts/Ubuntu-Italic.ttf +0 -0
- ultraplot/fonts/Ubuntu-Light.ttf +0 -0
- ultraplot/fonts/Ubuntu-LightItalic.ttf +0 -0
- ultraplot/fonts/Ubuntu-Medium.ttf +0 -0
- ultraplot/fonts/Ubuntu-MediumItalic.ttf +0 -0
- ultraplot/fonts/Ubuntu-Regular.ttf +0 -0
- ultraplot/fonts/texgyreadventor-bold.ttf +0 -0
- ultraplot/fonts/texgyreadventor-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyreadventor-italic.ttf +0 -0
- ultraplot/fonts/texgyreadventor-regular.ttf +0 -0
- ultraplot/fonts/texgyrebonum-bold.ttf +0 -0
- ultraplot/fonts/texgyrebonum-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyrebonum-italic.ttf +0 -0
- ultraplot/fonts/texgyrebonum-regular.ttf +0 -0
- ultraplot/fonts/texgyrechorus-mediumitalic.ttf +0 -0
- ultraplot/fonts/texgyrecursor-bold.ttf +0 -0
- ultraplot/fonts/texgyrecursor-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyrecursor-italic.ttf +0 -0
- ultraplot/fonts/texgyrecursor-regular.ttf +0 -0
- ultraplot/fonts/texgyreheros-bold.ttf +0 -0
- ultraplot/fonts/texgyreheros-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyreheros-italic.ttf +0 -0
- ultraplot/fonts/texgyreheros-regular.ttf +0 -0
- ultraplot/fonts/texgyrepagella-bold.ttf +0 -0
- ultraplot/fonts/texgyrepagella-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyrepagella-italic.ttf +0 -0
- ultraplot/fonts/texgyrepagella-regular.ttf +0 -0
- ultraplot/fonts/texgyreschola-bold.ttf +0 -0
- ultraplot/fonts/texgyreschola-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyreschola-italic.ttf +0 -0
- ultraplot/fonts/texgyreschola-regular.ttf +0 -0
- ultraplot/fonts/texgyretermes-bold.ttf +0 -0
- ultraplot/fonts/texgyretermes-bolditalic.ttf +0 -0
- ultraplot/fonts/texgyretermes-italic.ttf +0 -0
- ultraplot/fonts/texgyretermes-regular.ttf +0 -0
- ultraplot/gridspec.py +1698 -0
- ultraplot/internals/__init__.py +529 -0
- ultraplot/internals/benchmarks.py +26 -0
- ultraplot/internals/context.py +44 -0
- ultraplot/internals/docstring.py +139 -0
- ultraplot/internals/fonts.py +75 -0
- ultraplot/internals/guides.py +167 -0
- ultraplot/internals/inputs.py +862 -0
- ultraplot/internals/labels.py +85 -0
- ultraplot/internals/rcsetup.py +1933 -0
- ultraplot/internals/versions.py +61 -0
- ultraplot/internals/warnings.py +122 -0
- ultraplot/proj.py +325 -0
- ultraplot/scale.py +966 -0
- ultraplot/tests/__init__.py +28 -0
- ultraplot/tests/baseline/test_align_labels.png +0 -0
- ultraplot/tests/baseline/test_aligned_outer_guides.png +0 -0
- ultraplot/tests/baseline/test_aspect_ratios.png +0 -0
- ultraplot/tests/baseline/test_auto_diverging1.png +0 -0
- ultraplot/tests/baseline/test_auto_legend.png +0 -0
- ultraplot/tests/baseline/test_auto_reverse.png +0 -0
- ultraplot/tests/baseline/test_autodiverging3.png +0 -0
- ultraplot/tests/baseline/test_autodiverging4.png +0 -0
- ultraplot/tests/baseline/test_autodiverging5.png +0 -0
- ultraplot/tests/baseline/test_axes_colors.png +0 -0
- ultraplot/tests/baseline/test_bar_vectors.png +0 -0
- ultraplot/tests/baseline/test_bar_width.png +0 -0
- ultraplot/tests/baseline/test_both_ticklabels.png +0 -0
- ultraplot/tests/baseline/test_bounds_ticks.png +0 -0
- ultraplot/tests/baseline/test_boxplot_colors.png +0 -0
- ultraplot/tests/baseline/test_boxplot_vectors.png +0 -0
- ultraplot/tests/baseline/test_cartopy_contours.png +0 -0
- ultraplot/tests/baseline/test_cartopy_labels.png +0 -0
- ultraplot/tests/baseline/test_cartopy_manual.png +0 -0
- ultraplot/tests/baseline/test_centered_legends.png +0 -0
- ultraplot/tests/baseline/test_cmap_cycles.png +0 -0
- ultraplot/tests/baseline/test_colorbar.png +0 -0
- ultraplot/tests/baseline/test_colorbar_ticks.png +0 -0
- ultraplot/tests/baseline/test_colormap_mode.png +0 -0
- ultraplot/tests/baseline/test_column_iteration.png +0 -0
- ultraplot/tests/baseline/test_complex_ticks.png +0 -0
- ultraplot/tests/baseline/test_contour_labels.png +0 -0
- ultraplot/tests/baseline/test_contour_legend_with_label.png +0 -0
- ultraplot/tests/baseline/test_contour_legend_without_label.png +0 -0
- ultraplot/tests/baseline/test_contour_negative.png +0 -0
- ultraplot/tests/baseline/test_contour_single.png +0 -0
- ultraplot/tests/baseline/test_cutoff_ticks.png +0 -0
- ultraplot/tests/baseline/test_data_keyword.png +0 -0
- ultraplot/tests/baseline/test_discrete_ticks.png +0 -0
- ultraplot/tests/baseline/test_discrete_vs_fixed.png +0 -0
- ultraplot/tests/baseline/test_drawing_in_projection_with_globe.png +0 -0
- ultraplot/tests/baseline/test_drawing_in_projection_without_globe.png +0 -0
- ultraplot/tests/baseline/test_edge_fix.png +0 -0
- ultraplot/tests/baseline/test_flow_functions.png +0 -0
- ultraplot/tests/baseline/test_font_adjustments.png +0 -0
- ultraplot/tests/baseline/test_geographic_multiple_projections.png +0 -0
- ultraplot/tests/baseline/test_geographic_single_projection.png +0 -0
- ultraplot/tests/baseline/test_gray_adjustment.png +0 -0
- ultraplot/tests/baseline/test_histogram_legend.png +0 -0
- ultraplot/tests/baseline/test_histogram_types.png +0 -0
- ultraplot/tests/baseline/test_ignore_message.png +0 -0
- ultraplot/tests/baseline/test_inbounds_data.png +0 -0
- ultraplot/tests/baseline/test_init_format.png +0 -0
- ultraplot/tests/baseline/test_inner_title_zorder.png +0 -0
- ultraplot/tests/baseline/test_inset_basic.png +0 -0
- ultraplot/tests/baseline/test_inset_colorbars.png +0 -0
- ultraplot/tests/baseline/test_inset_colors_1.png +0 -0
- ultraplot/tests/baseline/test_inset_colors_2.png +0 -0
- ultraplot/tests/baseline/test_inset_zoom_update.png +0 -0
- ultraplot/tests/baseline/test_invalid_dist.png +0 -0
- ultraplot/tests/baseline/test_invalid_plot.png +0 -0
- ultraplot/tests/baseline/test_keep_guide_labels.png +0 -0
- ultraplot/tests/baseline/test_label_settings.png +0 -0
- ultraplot/tests/baseline/test_level_restriction.png +0 -0
- ultraplot/tests/baseline/test_levels_with_vmin_vmax.png +0 -0
- ultraplot/tests/baseline/test_locale_formatting.png +0 -0
- ultraplot/tests/baseline/test_locale_formatting_en_US.UTF-8.png +0 -0
- ultraplot/tests/baseline/test_manual_labels.png +0 -0
- ultraplot/tests/baseline/test_multi_formatting.png +0 -0
- ultraplot/tests/baseline/test_multiple_calls.png +0 -0
- ultraplot/tests/baseline/test_on_the_fly_mappable.png +0 -0
- ultraplot/tests/baseline/test_outer_align.png +0 -0
- ultraplot/tests/baseline/test_panel_dist.png +0 -0
- ultraplot/tests/baseline/test_panels_suplabels_three_hor_panels.png +0 -0
- ultraplot/tests/baseline/test_panels_with_sharing.png +0 -0
- ultraplot/tests/baseline/test_panels_without_sharing_1.png +0 -0
- ultraplot/tests/baseline/test_panels_without_sharing_2.png +0 -0
- ultraplot/tests/baseline/test_parametric_colors.png +0 -0
- ultraplot/tests/baseline/test_parametric_labels.png +0 -0
- ultraplot/tests/baseline/test_patch_format.png +0 -0
- ultraplot/tests/baseline/test_pie_charts.png +0 -0
- ultraplot/tests/baseline/test_pint_quantities.png +0 -0
- ultraplot/tests/baseline/test_polar_projections.png +0 -0
- ultraplot/tests/baseline/test_projection_dicts.png +0 -0
- ultraplot/tests/baseline/test_qualitative_colormaps_1.png +0 -0
- ultraplot/tests/baseline/test_qualitative_colormaps_2.png +0 -0
- ultraplot/tests/baseline/test_reversed_levels.png +0 -0
- ultraplot/tests/baseline/test_scatter_alpha.png +0 -0
- ultraplot/tests/baseline/test_scatter_args.png +0 -0
- ultraplot/tests/baseline/test_scatter_cycle.png +0 -0
- ultraplot/tests/baseline/test_scatter_inbounds.png +0 -0
- ultraplot/tests/baseline/test_scatter_sizes.png +0 -0
- ultraplot/tests/baseline/test_seaborn_heatmap.png +0 -0
- ultraplot/tests/baseline/test_seaborn_hist.png +0 -0
- ultraplot/tests/baseline/test_seaborn_relational.png +0 -0
- ultraplot/tests/baseline/test_seaborn_swarmplot.png +0 -0
- ultraplot/tests/baseline/test_segmented_norm.png +0 -0
- ultraplot/tests/baseline/test_segmented_norm_ticks.png +0 -0
- ultraplot/tests/baseline/test_share_all_basic.png +0 -0
- ultraplot/tests/baseline/test_singleton_legend.png +0 -0
- ultraplot/tests/baseline/test_span_labels.png +0 -0
- ultraplot/tests/baseline/test_spine_offset.png +0 -0
- ultraplot/tests/baseline/test_spine_side.png +0 -0
- ultraplot/tests/baseline/test_standardized_input.png +0 -0
- ultraplot/tests/baseline/test_statistical_boxplot.png +0 -0
- ultraplot/tests/baseline/test_three_axes.png +0 -0
- ultraplot/tests/baseline/test_tick_direction.png +0 -0
- ultraplot/tests/baseline/test_tick_labels.png +0 -0
- ultraplot/tests/baseline/test_tick_length.png +0 -0
- ultraplot/tests/baseline/test_tick_width.png +0 -0
- ultraplot/tests/baseline/test_title_deflection.png +0 -0
- ultraplot/tests/baseline/test_triangular_functions.png +0 -0
- ultraplot/tests/baseline/test_tuple_handles.png +0 -0
- ultraplot/tests/baseline/test_twin_axes_1.png +0 -0
- ultraplot/tests/baseline/test_twin_axes_2.png +0 -0
- ultraplot/tests/baseline/test_twin_axes_3.png +0 -0
- ultraplot/tests/baseline/test_uneven_levels.png +0 -0
- ultraplot/tests/test_1dplots.py +373 -0
- ultraplot/tests/test_2dplots.py +354 -0
- ultraplot/tests/test_axes.py +179 -0
- ultraplot/tests/test_colorbar.py +253 -0
- ultraplot/tests/test_docs.py +78 -0
- ultraplot/tests/test_format.py +340 -0
- ultraplot/tests/test_geographic.py +116 -0
- ultraplot/tests/test_imshow.py +110 -0
- ultraplot/tests/test_inset.py +28 -0
- ultraplot/tests/test_integration.py +149 -0
- ultraplot/tests/test_legend.py +181 -0
- ultraplot/tests/test_projections.py +138 -0
- ultraplot/tests/test_statistical_plotting.py +77 -0
- ultraplot/tests/test_subplots.py +174 -0
- ultraplot/ticker.py +879 -0
- ultraplot/ui.py +233 -0
- ultraplot/utils.py +912 -0
- ultraplot-0.99.3.dist-info/LICENSE.txt +427 -0
- ultraplot-0.99.3.dist-info/METADATA +88 -0
- ultraplot-0.99.3.dist-info/RECORD +416 -0
- ultraplot-0.99.3.dist-info/WHEEL +5 -0
- ultraplot-0.99.3.dist-info/entry_points.txt +2 -0
- ultraplot-0.99.3.dist-info/top_level.txt +1 -0
ultraplot/axes/plot.py
ADDED
|
@@ -0,0 +1,4569 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
The second-level axes subclass used for all ultraplot figures.
|
|
4
|
+
Implements plotting method overrides.
|
|
5
|
+
"""
|
|
6
|
+
import contextlib
|
|
7
|
+
import inspect
|
|
8
|
+
import itertools
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from numbers import Integral, Number
|
|
12
|
+
from typing import Any, Iterable
|
|
13
|
+
|
|
14
|
+
import matplotlib.artist as martist
|
|
15
|
+
import matplotlib.axes as maxes
|
|
16
|
+
import matplotlib.cbook as cbook
|
|
17
|
+
import matplotlib.cm as mcm
|
|
18
|
+
import matplotlib.collections as mcollections
|
|
19
|
+
import matplotlib.colors as mcolors
|
|
20
|
+
import matplotlib.contour as mcontour
|
|
21
|
+
import matplotlib.image as mimage
|
|
22
|
+
import matplotlib.lines as mlines
|
|
23
|
+
import matplotlib.patches as mpatches
|
|
24
|
+
import matplotlib.ticker as mticker
|
|
25
|
+
import numpy as np
|
|
26
|
+
import numpy.ma as ma
|
|
27
|
+
|
|
28
|
+
from .. import colors as pcolors
|
|
29
|
+
from .. import constructor, utils
|
|
30
|
+
from ..config import rc
|
|
31
|
+
from ..internals import ic # noqa: F401
|
|
32
|
+
from ..internals import (
|
|
33
|
+
_get_aliases,
|
|
34
|
+
_not_none,
|
|
35
|
+
_pop_kwargs,
|
|
36
|
+
_pop_params,
|
|
37
|
+
_pop_props,
|
|
38
|
+
context,
|
|
39
|
+
docstring,
|
|
40
|
+
guides,
|
|
41
|
+
inputs,
|
|
42
|
+
warnings,
|
|
43
|
+
)
|
|
44
|
+
from . import base
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from cartopy.crs import PlateCarree
|
|
48
|
+
except ModuleNotFoundError:
|
|
49
|
+
PlateCarree = object
|
|
50
|
+
|
|
51
|
+
__all__ = ["PlotAxes"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Constants
|
|
55
|
+
# NOTE: Increased from native linewidth of 0.25 matplotlib uses for grid box edges.
|
|
56
|
+
# This is half of rc['patch.linewidth'] of 0.6. Half seems like a nice default.
|
|
57
|
+
EDGEWIDTH = 0.3
|
|
58
|
+
|
|
59
|
+
# Data argument docstrings
|
|
60
|
+
_args_1d_docstring = """
|
|
61
|
+
*args : {y} or {x}, {y}
|
|
62
|
+
The data passed as positional or keyword arguments. Interpreted as follows:
|
|
63
|
+
|
|
64
|
+
* If only `{y}` coordinates are passed, try to infer the `{x}` coordinates
|
|
65
|
+
from the `~pandas.Series` or `~pandas.DataFrame` indices or the
|
|
66
|
+
`~xarray.DataArray` coordinates. Otherwise, the `{x}` coordinates
|
|
67
|
+
are ``np.arange(0, {y}.shape[0])``.
|
|
68
|
+
* If the `{y}` coordinates are a 2D array, plot each column of data in succession
|
|
69
|
+
(except where each column of data represents a statistical distribution, as with
|
|
70
|
+
``boxplot``, ``violinplot``, or when using ``means=True`` or ``medians=True``).
|
|
71
|
+
* If any arguments are `pint.Quantity`, auto-add the pint unit registry
|
|
72
|
+
to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`.
|
|
73
|
+
A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
|
|
74
|
+
"""
|
|
75
|
+
_args_1d_multi_docstring = """
|
|
76
|
+
*args : {y}2 or {x}, {y}2, or {x}, {y}1, {y}2
|
|
77
|
+
The data passed as positional or keyword arguments. Interpreted as follows:
|
|
78
|
+
|
|
79
|
+
* If only `{y}` coordinates are passed, try to infer the `{x}` coordinates from
|
|
80
|
+
the `~pandas.Series` or `~pandas.DataFrame` indices or the `~xarray.DataArray`
|
|
81
|
+
coordinates. Otherwise, the `{x}` coordinates are ``np.arange(0, {y}2.shape[0])``.
|
|
82
|
+
* If only `{x}` and `{y}2` coordinates are passed, set the `{y}1` coordinates
|
|
83
|
+
to zero. This draws elements originating from the zero line.
|
|
84
|
+
* If both `{y}1` and `{y}2` are provided, draw elements between these points. If
|
|
85
|
+
either are 2D, draw elements by iterating over each column.
|
|
86
|
+
* If any arguments are `pint.Quantity`, auto-add the pint unit registry
|
|
87
|
+
to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`.
|
|
88
|
+
A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
|
|
89
|
+
"""
|
|
90
|
+
_args_2d_docstring = """
|
|
91
|
+
*args : {z} or x, y, {z}
|
|
92
|
+
The data passed as positional or keyword arguments. Interpreted as follows:
|
|
93
|
+
|
|
94
|
+
* If only {zvar} coordinates are passed, try to infer the `x` and `y` coordinates
|
|
95
|
+
from the `~pandas.DataFrame` indices and columns or the `~xarray.DataArray`
|
|
96
|
+
coordinates. Otherwise, the `y` coordinates are ``np.arange(0, y.shape[0])``
|
|
97
|
+
and the `x` coordinates are ``np.arange(0, y.shape[1])``.
|
|
98
|
+
* For ``pcolor`` and ``pcolormesh``, calculate coordinate *edges* using
|
|
99
|
+
`~ultraplot.utils.edges` or `~ultraplot.utils.edges2d` if *centers* were provided.
|
|
100
|
+
For all other methods, calculate coordinate *centers* if *edges* were provided.
|
|
101
|
+
* If the `x` or `y` coordinates are `pint.Quantity`, auto-add the pint unit registry
|
|
102
|
+
to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`. If the
|
|
103
|
+
{zvar} coordinates are `pint.Quantity`, pass the magnitude to the plotting
|
|
104
|
+
command. A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
|
|
105
|
+
"""
|
|
106
|
+
docstring._snippet_manager["plot.args_1d_y"] = _args_1d_docstring.format(x="x", y="y")
|
|
107
|
+
docstring._snippet_manager["plot.args_1d_x"] = _args_1d_docstring.format(x="y", y="x")
|
|
108
|
+
docstring._snippet_manager["plot.args_1d_multiy"] = _args_1d_multi_docstring.format(
|
|
109
|
+
x="x", y="y"
|
|
110
|
+
) # noqa: E501
|
|
111
|
+
docstring._snippet_manager["plot.args_1d_multix"] = _args_1d_multi_docstring.format(
|
|
112
|
+
x="y", y="x"
|
|
113
|
+
) # noqa: E501
|
|
114
|
+
docstring._snippet_manager["plot.args_2d"] = _args_2d_docstring.format(
|
|
115
|
+
z="z", zvar="`z`"
|
|
116
|
+
) # noqa: E501
|
|
117
|
+
docstring._snippet_manager["plot.args_2d_flow"] = _args_2d_docstring.format(
|
|
118
|
+
z="u, v", zvar="`u` and `v`"
|
|
119
|
+
) # noqa: E501
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Shared docstrings
|
|
123
|
+
_args_1d_shared_docstring = """
|
|
124
|
+
data : dict-like, optional
|
|
125
|
+
A dict-like dataset container (e.g., `~pandas.DataFrame` or
|
|
126
|
+
`~xarray.Dataset`). If passed, each data argument can optionally
|
|
127
|
+
be a string `key` and the arrays used for plotting are retrieved
|
|
128
|
+
with ``data[key]``. This is a `native matplotlib feature
|
|
129
|
+
<https://matplotlib.org/stable/gallery/misc/keyword_plotting.html>`__.
|
|
130
|
+
autoformat : bool, default: :rc:`autoformat`
|
|
131
|
+
Whether the `x` axis labels, `y` axis labels, axis formatters, axes titles,
|
|
132
|
+
legend titles, and colorbar labels are automatically configured when a
|
|
133
|
+
`~pandas.Series`, `~pandas.DataFrame`, `~xarray.DataArray`, or `~pint.Quantity`
|
|
134
|
+
is passed to the plotting command. Formatting of `pint.Quantity`
|
|
135
|
+
unit strings is controlled by :rc:`unitformat`.
|
|
136
|
+
"""
|
|
137
|
+
_args_2d_shared_docstring = """
|
|
138
|
+
%(plot.args_1d_shared)s
|
|
139
|
+
transpose : bool, default: False
|
|
140
|
+
Whether to transpose the input data. This should be used when
|
|
141
|
+
passing datasets with column-major dimension order ``(x, y)``.
|
|
142
|
+
Otherwise row-major dimension order ``(y, x)`` is expected.
|
|
143
|
+
order : {'C', 'F'}, default: 'C'
|
|
144
|
+
Alternative to `transpose`. ``'C'`` corresponds to the default C-cyle
|
|
145
|
+
row-major ordering (equivalent to ``transpose=False``). ``'F'`` corresponds
|
|
146
|
+
to Fortran-style column-major ordering (equivalent to ``transpose=True``).
|
|
147
|
+
globe : bool, default: False
|
|
148
|
+
For `ultraplot.axes.GeoAxes` only. Whether to enforce global
|
|
149
|
+
coverage. When set to ``True`` this does the following:
|
|
150
|
+
|
|
151
|
+
#. Interpolates input data to the North and South poles by setting the data
|
|
152
|
+
values at the poles to the mean from latitudes nearest each pole.
|
|
153
|
+
#. Makes meridional coverage "circular", i.e. the last longitude coordinate
|
|
154
|
+
equals the first longitude coordinate plus 360\N{DEGREE SIGN}.
|
|
155
|
+
#. When basemap is the backend, cycles 1D longitude vectors to fit within
|
|
156
|
+
the map edges. For example, if the central longitude is 90\N{DEGREE SIGN},
|
|
157
|
+
the data is shifted so that it spans -90\N{DEGREE SIGN} to 270\N{DEGREE SIGN}.
|
|
158
|
+
"""
|
|
159
|
+
docstring._snippet_manager["plot.args_1d_shared"] = _args_1d_shared_docstring
|
|
160
|
+
docstring._snippet_manager["plot.args_2d_shared"] = _args_2d_shared_docstring
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Auto colorbar and legend docstring
|
|
164
|
+
_guide_docstring = """
|
|
165
|
+
colorbar : bool, int, or str, optional
|
|
166
|
+
If not ``None``, this is a location specifying where to draw an
|
|
167
|
+
*inset* or *outer* colorbar from the resulting object(s). If ``True``,
|
|
168
|
+
the default :rc:`colorbar.loc` is used. If the same location is
|
|
169
|
+
used in successive plotting calls, object(s) will be added to the
|
|
170
|
+
existing colorbar in that location (valid for colorbars built from lists
|
|
171
|
+
of artists). Valid locations are shown in in `~ultraplot.axes.Axes.colorbar`.
|
|
172
|
+
colorbar_kw : dict-like, optional
|
|
173
|
+
Extra keyword args for the call to `~ultraplot.axes.Axes.colorbar`.
|
|
174
|
+
legend : bool, int, or str, optional
|
|
175
|
+
Location specifying where to draw an *inset* or *outer* legend from the
|
|
176
|
+
resulting object(s). If ``True``, the default :rc:`legend.loc` is used.
|
|
177
|
+
If the same location is used in successive plotting calls, object(s)
|
|
178
|
+
will be added to existing legend in that location. Valid locations
|
|
179
|
+
are shown in `~ultraplot.axes.Axes.legend`.
|
|
180
|
+
legend_kw : dict-like, optional
|
|
181
|
+
Extra keyword args for the call to `~ultraplot.axes.Axes.legend`.
|
|
182
|
+
"""
|
|
183
|
+
docstring._snippet_manager["plot.guide"] = _guide_docstring
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Misc shared 1D plotting docstrings
|
|
187
|
+
_inbounds_docstring = """
|
|
188
|
+
inbounds : bool, default: :rc:`axes.inbounds`
|
|
189
|
+
Whether to restrict the default `y` (`x`) axis limits to account for only
|
|
190
|
+
in-bounds data when the `x` (`y`) axis limits have been locked.
|
|
191
|
+
See also :rcraw:`axes.inbounds` and :rcraw:`cmap.inbounds`.
|
|
192
|
+
"""
|
|
193
|
+
_error_means_docstring = """
|
|
194
|
+
mean, means : bool, default: False
|
|
195
|
+
Whether to plot the means of each column for 2D `{y}` coordinates. Means
|
|
196
|
+
are calculated with `numpy.nanmean`. If no other arguments are specified,
|
|
197
|
+
this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
|
|
198
|
+
median, medians : bool, default: False
|
|
199
|
+
Whether to plot the medians of each column for 2D `{y}` coordinates. Medians
|
|
200
|
+
are calculated with `numpy.nanmedian`. If no other arguments arguments are
|
|
201
|
+
specified, this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
|
|
202
|
+
"""
|
|
203
|
+
_error_bars_docstring = """
|
|
204
|
+
bars : bool, default: None
|
|
205
|
+
Shorthand for `barstd`, `barstds`.
|
|
206
|
+
barstd, barstds : bool, float, or 2-tuple of float, optional
|
|
207
|
+
Valid only if `mean` or `median` is ``True``. Standard deviation multiples for
|
|
208
|
+
*thin error bars* with optional whiskers (i.e., caps). If scalar, then +/- that
|
|
209
|
+
multiple is used. If ``True``, the default standard deviation range of +/-3 is used.
|
|
210
|
+
barpctile, barpctiles : bool, float, or 2-tuple of float, optional
|
|
211
|
+
Valid only if `mean` or `median` is ``True``. As with `barstd`, but instead
|
|
212
|
+
using percentiles for the error bars. If scalar, that percentile range is
|
|
213
|
+
used (e.g., ``90`` shows the 5th to 95th percentiles). If ``True``, the default
|
|
214
|
+
percentile range of 0 to 100 is used.
|
|
215
|
+
bardata : array-like, optional
|
|
216
|
+
Valid only if `mean` and `median` are ``False``. If shape is 2 x N, these
|
|
217
|
+
are the lower and upper bounds for the thin error bars. If shape is N, these
|
|
218
|
+
are the absolute, symmetric deviations from the central points.
|
|
219
|
+
boxes : bool, default: None
|
|
220
|
+
Shorthand for `boxstd`, `boxstds`.
|
|
221
|
+
boxstd, boxstds, boxpctile, boxpctiles, boxdata : optional
|
|
222
|
+
As with `barstd`, `barpctile`, and `bardata`, but for *thicker error bars*
|
|
223
|
+
representing a smaller interval than the thin error bars. If `boxstds` is
|
|
224
|
+
``True``, the default standard deviation range of +/-1 is used. If `boxpctiles`
|
|
225
|
+
is ``True``, the default percentile range of 25 to 75 is used (i.e., the
|
|
226
|
+
interquartile range). When "boxes" and "bars" are combined, this has the
|
|
227
|
+
effect of drawing miniature box-and-whisker plots.
|
|
228
|
+
capsize : float, default: :rc:`errorbar.capsize`
|
|
229
|
+
The cap size for thin error bars in points.
|
|
230
|
+
barz, barzorder, boxz, boxzorder : float, default: 2.5
|
|
231
|
+
The "zorder" for the thin and thick error bars.
|
|
232
|
+
barc, barcolor, boxc, boxcolor \
|
|
233
|
+
: color-spec, default: :rc:`boxplot.whiskerprops.color`
|
|
234
|
+
Colors for the thin and thick error bars.
|
|
235
|
+
barlw, barlinewidth, boxlw, boxlinewidth \
|
|
236
|
+
: float, default: :rc:`boxplot.whiskerprops.linewidth`
|
|
237
|
+
Line widths for the thin and thick error bars, in points. The default for boxes
|
|
238
|
+
is 4 times :rcraw:`boxplot.whiskerprops.linewidth`.
|
|
239
|
+
boxm, boxmarker : bool or marker-spec, default: 'o'
|
|
240
|
+
Whether to draw a small marker in the middle of the box denoting
|
|
241
|
+
the mean or median position. Ignored if `boxes` is ``False``.
|
|
242
|
+
boxms, boxmarkersize : size-spec, default: ``(2 * boxlinewidth) ** 2``
|
|
243
|
+
The marker size for the `boxmarker` marker in points ** 2.
|
|
244
|
+
boxmc, boxmarkercolor, boxmec, boxmarkeredgecolor : color-spec, default: 'w'
|
|
245
|
+
Color, face color, and edge color for the `boxmarker` marker.
|
|
246
|
+
"""
|
|
247
|
+
_error_shading_docstring = """
|
|
248
|
+
shade : bool, default: None
|
|
249
|
+
Shorthand for `shadestd`.
|
|
250
|
+
shadestd, shadestds, shadepctile, shadepctiles, shadedata : optional
|
|
251
|
+
As with `barstd`, `barpctile`, and `bardata`, but using *shading* to indicate
|
|
252
|
+
the error range. If `shadestds` is ``True``, the default standard deviation
|
|
253
|
+
range of +/-2 is used. If `shadepctiles` is ``True``, the default
|
|
254
|
+
percentile range of 10 to 90 is used.
|
|
255
|
+
fade : bool, default: None
|
|
256
|
+
Shorthand for `fadestd`.
|
|
257
|
+
fadestd, fadestds, fadepctile, fadepctiles, fadedata : optional
|
|
258
|
+
As with `shadestd`, `shadepctile`, and `shadedata`, but for an additional,
|
|
259
|
+
more faded, *secondary* shaded region. If `fadestds` is ``True``, the default
|
|
260
|
+
standard deviation range of +/-3 is used. If `fadepctiles` is ``True``,
|
|
261
|
+
the default percentile range of 0 to 100 is used.
|
|
262
|
+
shadec, shadecolor, fadec, fadecolor : color-spec, default: None
|
|
263
|
+
Colors for the different shaded regions. The parent artist color is used by default.
|
|
264
|
+
shadez, shadezorder, fadez, fadezorder : float, default: 1.5
|
|
265
|
+
The "zorder" for the different shaded regions.
|
|
266
|
+
shadea, shadealpha, fadea, fadealpha : float, default: 0.4, 0.2
|
|
267
|
+
The opacity for the different shaded regions.
|
|
268
|
+
shadelw, shadelinewidth, fadelw, fadelinewidth : float, default: :rc:`patch.linewidth`.
|
|
269
|
+
The edge line width for the shading patches.
|
|
270
|
+
shdeec, shadeedgecolor, fadeec, fadeedgecolor : float, default: 'none'
|
|
271
|
+
The edge color for the shading patches.
|
|
272
|
+
shadelabel, fadelabel : bool or str, optional
|
|
273
|
+
Labels for the shaded regions to be used as separate legend entries. To toggle
|
|
274
|
+
labels "on" and apply a *default* label, use e.g. ``shadelabel=True``. To apply
|
|
275
|
+
a *custom* label, use e.g. ``shadelabel='label'``. Otherwise, the shading is
|
|
276
|
+
drawn underneath the line and/or marker in the legend entry.
|
|
277
|
+
"""
|
|
278
|
+
docstring._snippet_manager["plot.inbounds"] = _inbounds_docstring
|
|
279
|
+
docstring._snippet_manager["plot.error_means_y"] = _error_means_docstring.format(y="y")
|
|
280
|
+
docstring._snippet_manager["plot.error_means_x"] = _error_means_docstring.format(y="x")
|
|
281
|
+
docstring._snippet_manager["plot.error_bars"] = _error_bars_docstring
|
|
282
|
+
docstring._snippet_manager["plot.error_shading"] = _error_shading_docstring
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# Color docstrings
|
|
286
|
+
_cycle_docstring = """
|
|
287
|
+
cycle : cycle-spec, optional
|
|
288
|
+
The cycle specifer, passed to the `~ultraplot.constructor.Cycle` constructor.
|
|
289
|
+
If the returned cycler is unchanged from the current cycler, the axes
|
|
290
|
+
cycler will not be reset to its first position. To disable property cycling
|
|
291
|
+
and just use black for the default color, use ``cycle=False``, ``cycle='none'``,
|
|
292
|
+
or ``cycle=()`` (analogous to disabling ticks with e.g. ``xformatter='none'``).
|
|
293
|
+
To restore the default property cycler, use ``cycle=True``.
|
|
294
|
+
cycle_kw : dict-like, optional
|
|
295
|
+
Passed to `~ultraplot.constructor.Cycle`.
|
|
296
|
+
"""
|
|
297
|
+
_cmap_norm_docstring = """
|
|
298
|
+
cmap : colormap-spec, default: \
|
|
299
|
+
:rc:`cmap.sequential` or :rc:`cmap.diverging`
|
|
300
|
+
The colormap specifer, passed to the `~ultraplot.constructor.Colormap` constructor
|
|
301
|
+
function. If :rcraw:`cmap.autodiverging` is ``True`` and the normalization
|
|
302
|
+
range contains negative and positive values then :rcraw:`cmap.diverging` is used.
|
|
303
|
+
Otherwise :rcraw:`cmap.sequential` is used.
|
|
304
|
+
cmap_kw : dict-like, optional
|
|
305
|
+
Passed to `~ultraplot.constructor.Colormap`.
|
|
306
|
+
c, color, colors : color-spec or sequence of color-spec, optional
|
|
307
|
+
The color(s) used to create a `~ultraplot.colors.DiscreteColormap`.
|
|
308
|
+
If not passed, `cmap` is used.
|
|
309
|
+
norm : norm-spec, default: \
|
|
310
|
+
`~matplotlib.colors.Normalize` or `~ultraplot.colors.DivergingNorm`
|
|
311
|
+
The data value normalizer, passed to the `~ultraplot.constructor.Norm`
|
|
312
|
+
constructor function. If `discrete` is ``True`` then 1) this affects the default
|
|
313
|
+
level-generation algorithm (e.g. ``norm='log'`` builds levels in log-space) and
|
|
314
|
+
2) this is passed to `~ultraplot.colors.DiscreteNorm` to scale the colors before they
|
|
315
|
+
are discretized (if `norm` is not already a `~ultraplot.colors.DiscreteNorm`).
|
|
316
|
+
If :rcraw:`cmap.autodiverging` is ``True`` and the normalization range contains
|
|
317
|
+
negative and positive values then `~ultraplot.colors.DivergingNorm` is used.
|
|
318
|
+
Otherwise `~matplotlib.colors.Normalize` is used.
|
|
319
|
+
norm_kw : dict-like, optional
|
|
320
|
+
Passed to `~ultraplot.constructor.Norm`.
|
|
321
|
+
extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
|
|
322
|
+
Direction for drawing colorbar "extensions" indicating
|
|
323
|
+
out-of-bounds data on the end of the colorbar.
|
|
324
|
+
discrete : bool, default: :rc:`cmap.discrete`
|
|
325
|
+
If ``False``, then `~ultraplot.colors.DiscreteNorm` is not applied to the
|
|
326
|
+
colormap. Instead, for non-contour plots, the number of levels will be
|
|
327
|
+
roughly controlled by :rcraw:`cmap.lut`. This has a similar effect to
|
|
328
|
+
using `levels=large_number` but it may improve rendering speed. Default is
|
|
329
|
+
``True`` only for contouring commands like `~ultraplot.axes.Axes.contourf`
|
|
330
|
+
and pseudocolor commands like `~ultraplot.axes.Axes.pcolor`.
|
|
331
|
+
sequential, diverging, cyclic, qualitative : bool, default: None
|
|
332
|
+
Boolean arguments used if `cmap` is not passed. Set these to ``True``
|
|
333
|
+
to use the default :rcraw:`cmap.sequential`, :rcraw:`cmap.diverging`,
|
|
334
|
+
:rcraw:`cmap.cyclic`, and :rcraw:`cmap.qualitative` colormaps.
|
|
335
|
+
The `diverging` option also applies `~ultraplot.colors.DivergingNorm`
|
|
336
|
+
as the default continuous normalizer.
|
|
337
|
+
"""
|
|
338
|
+
docstring._snippet_manager["plot.cycle"] = _cycle_docstring
|
|
339
|
+
docstring._snippet_manager["plot.cmap_norm"] = _cmap_norm_docstring
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# Levels docstrings
|
|
343
|
+
# NOTE: In some functions we only need some components
|
|
344
|
+
_vmin_vmax_docstring = """
|
|
345
|
+
vmin, vmax : float, optional
|
|
346
|
+
The minimum and maximum color scale values used with the `norm` normalizer.
|
|
347
|
+
If `discrete` is ``False`` these are the absolute limits, and if `discrete`
|
|
348
|
+
is ``True`` these are the approximate limits used to automatically determine
|
|
349
|
+
`levels` or `values` lists at "nice" intervals. If `levels` or `values` were
|
|
350
|
+
already passed as lists, these are ignored, and `vmin` and `vmax` are set to
|
|
351
|
+
the minimum and maximum of the lists. If `robust` was passed, the default `vmin`
|
|
352
|
+
and `vmax` are some percentile range of the data values. Otherwise, the default
|
|
353
|
+
`vmin` and `vmax` are the minimum and maximum of the data values.
|
|
354
|
+
"""
|
|
355
|
+
_manual_levels_docstring = """
|
|
356
|
+
N
|
|
357
|
+
Shorthand for `levels`.
|
|
358
|
+
levels : int or sequence of float, default: :rc:`cmap.levels`
|
|
359
|
+
The number of level edges or a sequence of level edges. If the former, `locator`
|
|
360
|
+
is used to generate this many level edges at "nice" intervals. If the latter,
|
|
361
|
+
the levels should be monotonically increasing or decreasing (note decreasing
|
|
362
|
+
levels fail with ``contour`` plots).
|
|
363
|
+
values : int or sequence of float, default: None
|
|
364
|
+
The number of level centers or a sequence of level centers. If the former,
|
|
365
|
+
`locator` is used to generate this many level centers at "nice" intervals.
|
|
366
|
+
If the latter, levels are inferred using `~ultraplot.utils.edges`.
|
|
367
|
+
This will override any `levels` input.
|
|
368
|
+
"""
|
|
369
|
+
_auto_levels_docstring = """
|
|
370
|
+
robust : bool, float, or 2-tuple, default: :rc:`cmap.robust`
|
|
371
|
+
If ``True`` and `vmin` or `vmax` were not provided, they are
|
|
372
|
+
determined from the 2nd and 98th data percentiles rather than the
|
|
373
|
+
minimum and maximum. If float, this percentile range is used (for example,
|
|
374
|
+
``90`` corresponds to the 5th to 95th percentiles). If 2-tuple of float,
|
|
375
|
+
these specific percentiles should be used. This feature is useful
|
|
376
|
+
when your data has large outliers.
|
|
377
|
+
inbounds : bool, default: :rc:`cmap.inbounds`
|
|
378
|
+
If ``True`` and `vmin` or `vmax` were not provided, when axis limits
|
|
379
|
+
have been explicitly restricted with `~matplotlib.axes.Axes.set_xlim`
|
|
380
|
+
or `~matplotlib.axes.Axes.set_ylim`, out-of-bounds data is ignored.
|
|
381
|
+
See also :rcraw:`cmap.inbounds` and :rcraw:`axes.inbounds`.
|
|
382
|
+
locator : locator-spec, default: `matplotlib.ticker.MaxNLocator`
|
|
383
|
+
The locator used to determine level locations if `levels` or `values` were not
|
|
384
|
+
already passed as lists. Passed to the `~ultraplot.constructor.Locator` constructor.
|
|
385
|
+
Default is `~matplotlib.ticker.MaxNLocator` with `levels` integer levels.
|
|
386
|
+
locator_kw : dict-like, optional
|
|
387
|
+
Keyword arguments passed to `matplotlib.ticker.Locator` class.
|
|
388
|
+
symmetric : bool, default: False
|
|
389
|
+
If ``True``, the normalization range or discrete colormap levels are
|
|
390
|
+
symmetric about zero.
|
|
391
|
+
positive : bool, default: False
|
|
392
|
+
If ``True``, the normalization range or discrete colormap levels are
|
|
393
|
+
positive with a minimum at zero.
|
|
394
|
+
negative : bool, default: False
|
|
395
|
+
If ``True``, the normaliation range or discrete colormap levels are
|
|
396
|
+
negative with a minimum at zero.
|
|
397
|
+
nozero : bool, default: False
|
|
398
|
+
If ``True``, ``0`` is removed from the level list. This is mainly useful for
|
|
399
|
+
single-color `~matplotlib.axes.Axes.contour` plots.
|
|
400
|
+
"""
|
|
401
|
+
docstring._snippet_manager["plot.vmin_vmax"] = _vmin_vmax_docstring
|
|
402
|
+
docstring._snippet_manager["plot.levels_manual"] = _manual_levels_docstring
|
|
403
|
+
docstring._snippet_manager["plot.levels_auto"] = _auto_levels_docstring
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# Labels docstrings
|
|
407
|
+
_label_docstring = """
|
|
408
|
+
label, value : float or str, optional
|
|
409
|
+
The single legend label or colorbar coordinate to be used for
|
|
410
|
+
this plotted element. Can be numeric or string. This is generally
|
|
411
|
+
used with 1D positional arguments.
|
|
412
|
+
"""
|
|
413
|
+
_labels_1d_docstring = """
|
|
414
|
+
%(plot.label)s
|
|
415
|
+
labels, values : sequence of float or sequence of str, optional
|
|
416
|
+
The legend labels or colorbar coordinates used for each plotted element.
|
|
417
|
+
Can be numeric or string, and must match the number of plotted elements.
|
|
418
|
+
This is generally used with 2D positional arguments.
|
|
419
|
+
"""
|
|
420
|
+
_labels_2d_docstring = """
|
|
421
|
+
label : str, optional
|
|
422
|
+
The legend label to be used for this object. In the case of
|
|
423
|
+
contours, this is paired with the the central artist in the artist
|
|
424
|
+
list returned by `matplotlib.contour.ContourSet.legend_elements`.
|
|
425
|
+
labels : bool, optional
|
|
426
|
+
Whether to apply labels to contours and grid boxes. The text will be
|
|
427
|
+
white when the luminance of the underlying filled contour or grid box
|
|
428
|
+
is less than 50 and black otherwise.
|
|
429
|
+
labels_kw : dict-like, optional
|
|
430
|
+
Ignored if `labels` is ``False``. Extra keyword args for the labels.
|
|
431
|
+
For contour plots, this is passed to `~matplotlib.axes.Axes.clabel`.
|
|
432
|
+
Otherwise, this is passed to `~matplotlib.axes.Axes.text`.
|
|
433
|
+
formatter, fmt : formatter-spec, optional
|
|
434
|
+
The `~matplotlib.ticker.Formatter` used to format number labels.
|
|
435
|
+
Passed to the `~ultraplot.constructor.Formatter` constructor.
|
|
436
|
+
formatter_kw : dict-like, optional
|
|
437
|
+
Keyword arguments passed to `matplotlib.ticker.Formatter` class.
|
|
438
|
+
precision : int, optional
|
|
439
|
+
The maximum number of decimal places for number labels generated
|
|
440
|
+
with the default formatter `~ultraplot.ticker.Simpleformatter`.
|
|
441
|
+
"""
|
|
442
|
+
docstring._snippet_manager["plot.label"] = _label_docstring
|
|
443
|
+
docstring._snippet_manager["plot.labels_1d"] = _labels_1d_docstring
|
|
444
|
+
docstring._snippet_manager["plot.labels_2d"] = _labels_2d_docstring
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# Negative-positive colors
|
|
448
|
+
_negpos_docstring = """
|
|
449
|
+
negpos : bool, default: False
|
|
450
|
+
Whether to shade {objects} where ``{pos}`` with `poscolor`
|
|
451
|
+
and where ``{neg}`` with `negcolor`. If ``True`` this
|
|
452
|
+
function will return a length-2 silent list of handles.
|
|
453
|
+
negcolor, poscolor : color-spec, default: :rc:`negcolor`, :rc:`poscolor`
|
|
454
|
+
Colors to use for the negative and positive {objects}. Ignored if
|
|
455
|
+
`negpos` is ``False``.
|
|
456
|
+
"""
|
|
457
|
+
docstring._snippet_manager["plot.negpos_fill"] = _negpos_docstring.format(
|
|
458
|
+
objects="patches", neg="y2 < y1", pos="y2 >= y1"
|
|
459
|
+
)
|
|
460
|
+
docstring._snippet_manager["plot.negpos_lines"] = _negpos_docstring.format(
|
|
461
|
+
objects="lines", neg="ymax < ymin", pos="ymax >= ymin"
|
|
462
|
+
)
|
|
463
|
+
docstring._snippet_manager["plot.negpos_bar"] = _negpos_docstring.format(
|
|
464
|
+
objects="bars", neg="height < 0", pos="height >= 0"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
# Plot docstring
|
|
469
|
+
_plot_docstring = """
|
|
470
|
+
Plot standard lines.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
%(plot.args_1d_{y})s
|
|
475
|
+
%(plot.args_1d_shared)s
|
|
476
|
+
|
|
477
|
+
Other parameters
|
|
478
|
+
----------------
|
|
479
|
+
%(plot.cycle)s
|
|
480
|
+
%(artist.line)s
|
|
481
|
+
%(plot.error_means_{y})s
|
|
482
|
+
%(plot.error_bars)s
|
|
483
|
+
%(plot.error_shading)s
|
|
484
|
+
%(plot.inbounds)s
|
|
485
|
+
%(plot.labels_1d)s
|
|
486
|
+
%(plot.guide)s
|
|
487
|
+
**kwargs
|
|
488
|
+
Passed to `~matplotlib.axes.Axes.plot`.
|
|
489
|
+
|
|
490
|
+
See also
|
|
491
|
+
--------
|
|
492
|
+
PlotAxes.plot
|
|
493
|
+
PlotAxes.plotx
|
|
494
|
+
matplotlib.axes.Axes.plot
|
|
495
|
+
"""
|
|
496
|
+
docstring._snippet_manager["plot.plot"] = _plot_docstring.format(y="y")
|
|
497
|
+
docstring._snippet_manager["plot.plotx"] = _plot_docstring.format(y="x")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# Step docstring
|
|
501
|
+
# NOTE: Internally matplotlib implements step with thin wrapper of plot
|
|
502
|
+
_step_docstring = """
|
|
503
|
+
Plot step lines.
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
%(plot.args_1d_{y})s
|
|
508
|
+
%(plot.args_1d_shared)s
|
|
509
|
+
|
|
510
|
+
Other parameters
|
|
511
|
+
----------------
|
|
512
|
+
%(plot.cycle)s
|
|
513
|
+
%(artist.line)s
|
|
514
|
+
%(plot.inbounds)s
|
|
515
|
+
%(plot.labels_1d)s
|
|
516
|
+
%(plot.guide)s
|
|
517
|
+
**kwargs
|
|
518
|
+
Passed to `~matplotlib.axes.Axes.step`.
|
|
519
|
+
|
|
520
|
+
See also
|
|
521
|
+
--------
|
|
522
|
+
PlotAxes.step
|
|
523
|
+
PlotAxes.stepx
|
|
524
|
+
matplotlib.axes.Axes.step
|
|
525
|
+
"""
|
|
526
|
+
docstring._snippet_manager["plot.step"] = _step_docstring.format(y="y")
|
|
527
|
+
docstring._snippet_manager["plot.stepx"] = _step_docstring.format(y="x")
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# Stem docstring
|
|
531
|
+
_stem_docstring = """
|
|
532
|
+
Plot stem lines.
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
%(plot.args_1d_{y})s
|
|
537
|
+
%(plot.args_1d_shared)s
|
|
538
|
+
|
|
539
|
+
Other parameters
|
|
540
|
+
----------------
|
|
541
|
+
%(plot.cycle)s
|
|
542
|
+
%(plot.inbounds)s
|
|
543
|
+
%(plot.guide)s
|
|
544
|
+
**kwargs
|
|
545
|
+
Passed to `~matplotlib.axes.Axes.stem`.
|
|
546
|
+
"""
|
|
547
|
+
docstring._snippet_manager["plot.stem"] = _stem_docstring.format(y="x")
|
|
548
|
+
docstring._snippet_manager["plot.stemx"] = _stem_docstring.format(y="x")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# Lines docstrings
|
|
552
|
+
_lines_docstring = """
|
|
553
|
+
Plot {orientation} lines.
|
|
554
|
+
|
|
555
|
+
Parameters
|
|
556
|
+
----------
|
|
557
|
+
%(plot.args_1d_multi{y})s
|
|
558
|
+
%(plot.args_1d_shared)s
|
|
559
|
+
|
|
560
|
+
Other parameters
|
|
561
|
+
----------------
|
|
562
|
+
stack, stacked : bool, default: False
|
|
563
|
+
Whether to "stack" lines from successive columns of {y} data
|
|
564
|
+
or plot lines on top of each other.
|
|
565
|
+
%(plot.cycle)s
|
|
566
|
+
%(artist.line)s
|
|
567
|
+
%(plot.negpos_lines)s
|
|
568
|
+
%(plot.inbounds)s
|
|
569
|
+
%(plot.labels_1d)s
|
|
570
|
+
%(plot.guide)s
|
|
571
|
+
**kwargs
|
|
572
|
+
Passed to `~matplotlib.axes.Axes.{prefix}lines`.
|
|
573
|
+
|
|
574
|
+
See also
|
|
575
|
+
--------
|
|
576
|
+
PlotAxes.vlines
|
|
577
|
+
PlotAxes.hlines
|
|
578
|
+
matplotlib.axes.Axes.vlines
|
|
579
|
+
matplotlib.axes.Axes.hlines
|
|
580
|
+
"""
|
|
581
|
+
docstring._snippet_manager["plot.vlines"] = _lines_docstring.format(
|
|
582
|
+
y="y", prefix="v", orientation="vertical"
|
|
583
|
+
)
|
|
584
|
+
docstring._snippet_manager["plot.hlines"] = _lines_docstring.format(
|
|
585
|
+
y="x", prefix="h", orientation="horizontal"
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
# Scatter docstring
|
|
590
|
+
_parametric_docstring = """
|
|
591
|
+
Plot a parametric line.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
%(plot.args_1d_y)s
|
|
596
|
+
c, color, colors, values, labels : sequence of float, str, or color-spec, optional
|
|
597
|
+
The parametric coordinate(s). These can be passed as a third positional
|
|
598
|
+
argument or as a keyword argument. If they are float, the colors will be
|
|
599
|
+
determined from `norm` and `cmap`. If they are strings, the color values
|
|
600
|
+
will be ``np.arange(len(colors))`` and eventual colorbar ticks will
|
|
601
|
+
be labeled with the strings. If they are colors, they are used for the
|
|
602
|
+
line segments and `cmap` is ignored -- for example, ``colors='blue'``
|
|
603
|
+
makes a monochromatic "parametric" line.
|
|
604
|
+
interp : int, default: 0
|
|
605
|
+
Interpolate to this many additional points between the parametric
|
|
606
|
+
coordinates. This can be increased to make the color gradations
|
|
607
|
+
between a small number of coordinates appear "smooth".
|
|
608
|
+
%(plot.args_1d_shared)s
|
|
609
|
+
|
|
610
|
+
Other parameters
|
|
611
|
+
----------------
|
|
612
|
+
%(plot.cmap_norm)s
|
|
613
|
+
%(plot.vmin_vmax)s
|
|
614
|
+
%(plot.inbounds)s
|
|
615
|
+
scalex, scaley : bool, optional
|
|
616
|
+
Whether the view limits are adapted to the data limits. The values are
|
|
617
|
+
passed on to `~matplotlib.axes.Axes.autoscale_view`.
|
|
618
|
+
%(plot.label)s
|
|
619
|
+
%(plot.guide)s
|
|
620
|
+
**kwargs
|
|
621
|
+
Valid `~matplotlib.collections.LineCollection` properties.
|
|
622
|
+
|
|
623
|
+
Returns
|
|
624
|
+
-------
|
|
625
|
+
`~matplotlib.collections.LineCollection`
|
|
626
|
+
The parametric line. See `this matplotlib example \
|
|
627
|
+
<https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line>`__.
|
|
628
|
+
|
|
629
|
+
See also
|
|
630
|
+
--------
|
|
631
|
+
PlotAxes.plot
|
|
632
|
+
PlotAxes.plotx
|
|
633
|
+
matplotlib.collections.LineCollection
|
|
634
|
+
"""
|
|
635
|
+
docstring._snippet_manager["plot.parametric"] = _parametric_docstring
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
# Scatter function docstring
|
|
639
|
+
_scatter_docstring = """
|
|
640
|
+
Plot markers with flexible keyword arguments.
|
|
641
|
+
|
|
642
|
+
Parameters
|
|
643
|
+
----------
|
|
644
|
+
%(plot.args_1d_{y})s
|
|
645
|
+
s, size, ms, markersize : float or array-like or unit-spec, optional
|
|
646
|
+
The marker size area(s). If this is an array matching the shape of `x` and `y`,
|
|
647
|
+
the units are scaled by `smin` and `smax`. If this contains unit string(s), it
|
|
648
|
+
is processed by `~ultraplot.utils.units` and represents the width rather than area.
|
|
649
|
+
c, color, colors, mc, markercolor, markercolors, fc, facecolor, facecolors \
|
|
650
|
+
: array-like or color-spec, optional
|
|
651
|
+
The marker color(s). If this is an array matching the shape of `x` and `y`,
|
|
652
|
+
the colors are generated using `cmap`, `norm`, `vmin`, and `vmax`. Otherwise,
|
|
653
|
+
this should be a valid matplotlib color.
|
|
654
|
+
smin, smax : float, optional
|
|
655
|
+
The minimum and maximum marker size area in units ``points ** 2``. Ignored
|
|
656
|
+
if `absolute_size` is ``True``. Default value for `smin` is ``1`` and for
|
|
657
|
+
`smax` is the square of :rc:`lines.markersize`.
|
|
658
|
+
area_size : bool, default: True
|
|
659
|
+
Whether the marker sizes `s` are scaled by area or by radius. The default
|
|
660
|
+
``True`` is consistent with matplotlib. When `absolute_size` is ``True``,
|
|
661
|
+
the `s` units are ``points ** 2`` if `area_size` is ``True`` and ``points``
|
|
662
|
+
if `area_size` is ``False``.
|
|
663
|
+
absolute_size : bool, default: True or False
|
|
664
|
+
Whether `s` should be taken to represent "absolute" marker sizes in units
|
|
665
|
+
``points`` or ``points ** 2`` or "relative" marker sizes scaled by `smin`
|
|
666
|
+
and `smax`. Default is ``True`` if `s` is scalar and ``False`` if `s` is
|
|
667
|
+
array-like or `smin` or `smax` were passed.
|
|
668
|
+
%(plot.vmin_vmax)s
|
|
669
|
+
%(plot.args_1d_shared)s
|
|
670
|
+
|
|
671
|
+
Other parameters
|
|
672
|
+
----------------
|
|
673
|
+
%(plot.cmap_norm)s
|
|
674
|
+
%(plot.levels_manual)s
|
|
675
|
+
%(plot.levels_auto)s
|
|
676
|
+
%(plot.cycle)s
|
|
677
|
+
lw, linewidth, linewidths, mew, markeredgewidth, markeredgewidths \
|
|
678
|
+
: float or sequence, optional
|
|
679
|
+
The marker edge width(s).
|
|
680
|
+
edgecolors, markeredgecolor, markeredgecolors \
|
|
681
|
+
: color-spec or sequence, optional
|
|
682
|
+
The marker edge color(s).
|
|
683
|
+
%(plot.error_means_{y})s
|
|
684
|
+
%(plot.error_bars)s
|
|
685
|
+
%(plot.error_shading)s
|
|
686
|
+
%(plot.inbounds)s
|
|
687
|
+
%(plot.labels_1d)s
|
|
688
|
+
%(plot.guide)s
|
|
689
|
+
**kwargs
|
|
690
|
+
Passed to `~matplotlib.axes.Axes.scatter`.
|
|
691
|
+
|
|
692
|
+
See also
|
|
693
|
+
--------
|
|
694
|
+
PlotAxes.scatter
|
|
695
|
+
PlotAxes.scatterx
|
|
696
|
+
matplotlib.axes.Axes.scatter
|
|
697
|
+
"""
|
|
698
|
+
docstring._snippet_manager["plot.scatter"] = _scatter_docstring.format(y="y")
|
|
699
|
+
docstring._snippet_manager["plot.scatterx"] = _scatter_docstring.format(y="x")
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
# Bar function docstring
|
|
703
|
+
_bar_docstring = """
|
|
704
|
+
Plot individual, grouped, or stacked bars.
|
|
705
|
+
|
|
706
|
+
Parameters
|
|
707
|
+
----------
|
|
708
|
+
%(plot.args_1d_{y})s
|
|
709
|
+
width : float or array-like, default: 0.8
|
|
710
|
+
The width(s) of the bars. Can be passed as a third positional argument. If
|
|
711
|
+
`absolute_width` is ``True`` (the default) these are in units relative to the
|
|
712
|
+
{x} coordinate step size. Otherwise these are in {x} coordinate units.
|
|
713
|
+
{bottom} : float or array-like, default: 0
|
|
714
|
+
The coordinate(s) of the {bottom} edge of the bars.
|
|
715
|
+
Can be passed as a fourth positional argument.
|
|
716
|
+
absolute_width : bool, default: False
|
|
717
|
+
Whether to make the `width` units *absolute*. If ``True``,
|
|
718
|
+
this restores the default matplotlib behavior.
|
|
719
|
+
stack, stacked : bool, default: False
|
|
720
|
+
Whether to "stack" bars from successive columns of {y}
|
|
721
|
+
data or plot bars side-by-side in groups.
|
|
722
|
+
%(plot.args_1d_shared)s
|
|
723
|
+
|
|
724
|
+
Other parameters
|
|
725
|
+
----------------
|
|
726
|
+
%(plot.cycle)s
|
|
727
|
+
%(artist.patch)s
|
|
728
|
+
%(plot.negpos_bar)s
|
|
729
|
+
%(axes.edgefix)s
|
|
730
|
+
%(plot.error_means_{y})s
|
|
731
|
+
%(plot.error_bars)s
|
|
732
|
+
%(plot.inbounds)s
|
|
733
|
+
%(plot.labels_1d)s
|
|
734
|
+
%(plot.guide)s
|
|
735
|
+
**kwargs
|
|
736
|
+
Passed to `~matplotlib.axes.Axes.bar{suffix}`.
|
|
737
|
+
|
|
738
|
+
See also
|
|
739
|
+
--------
|
|
740
|
+
PlotAxes.bar
|
|
741
|
+
PlotAxes.barh
|
|
742
|
+
matplotlib.axes.Axes.bar
|
|
743
|
+
matplotlib.axes.Axes.barh
|
|
744
|
+
"""
|
|
745
|
+
docstring._snippet_manager["plot.bar"] = _bar_docstring.format(
|
|
746
|
+
x="x", y="y", bottom="bottom", suffix=""
|
|
747
|
+
)
|
|
748
|
+
docstring._snippet_manager["plot.barh"] = _bar_docstring.format(
|
|
749
|
+
x="y", y="x", bottom="left", suffix="h"
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
# Area plot docstring
|
|
754
|
+
_fill_docstring = """
|
|
755
|
+
Plot individual, grouped, or overlaid shading patches.
|
|
756
|
+
|
|
757
|
+
Parameters
|
|
758
|
+
----------
|
|
759
|
+
%(plot.args_1d_multi{y})s
|
|
760
|
+
stack, stacked : bool, default: False
|
|
761
|
+
Whether to "stack" area patches from successive columns of {y}
|
|
762
|
+
data or plot area patches on top of each other.
|
|
763
|
+
%(plot.args_1d_shared)s
|
|
764
|
+
|
|
765
|
+
Other parameters
|
|
766
|
+
----------------
|
|
767
|
+
where : ndarray, optional
|
|
768
|
+
A boolean mask for the points that should be shaded.
|
|
769
|
+
See `this matplotlib example \
|
|
770
|
+
<https://matplotlib.org/stable/gallery/pyplots/whats_new_98_4_fill_between.html>`__.
|
|
771
|
+
%(plot.cycle)s
|
|
772
|
+
%(artist.patch)s
|
|
773
|
+
%(plot.negpos_fill)s
|
|
774
|
+
%(axes.edgefix)s
|
|
775
|
+
%(plot.inbounds)s
|
|
776
|
+
%(plot.labels_1d)s
|
|
777
|
+
%(plot.guide)s
|
|
778
|
+
**kwargs
|
|
779
|
+
Passed to `~matplotlib.axes.Axes.fill_between{suffix}`.
|
|
780
|
+
|
|
781
|
+
See also
|
|
782
|
+
--------
|
|
783
|
+
PlotAxes.area
|
|
784
|
+
PlotAxes.areax
|
|
785
|
+
PlotAxes.fill_between
|
|
786
|
+
PlotAxes.fill_betweenx
|
|
787
|
+
matplotlib.axes.Axes.fill_between
|
|
788
|
+
matplotlib.axes.Axes.fill_betweenx
|
|
789
|
+
"""
|
|
790
|
+
docstring._snippet_manager["plot.fill_between"] = _fill_docstring.format(
|
|
791
|
+
x="x", y="y", suffix=""
|
|
792
|
+
)
|
|
793
|
+
docstring._snippet_manager["plot.fill_betweenx"] = _fill_docstring.format(
|
|
794
|
+
x="y", y="x", suffix="x"
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
# Box plot docstrings
|
|
799
|
+
_boxplot_docstring = """
|
|
800
|
+
Plot {orientation} boxes and whiskers with a nice default style.
|
|
801
|
+
|
|
802
|
+
Parameters
|
|
803
|
+
----------
|
|
804
|
+
%(plot.args_1d_{y})s
|
|
805
|
+
%(plot.args_1d_shared)s
|
|
806
|
+
|
|
807
|
+
Other parameters
|
|
808
|
+
----------------
|
|
809
|
+
fill : bool, default: True
|
|
810
|
+
Whether to fill the box with a color.
|
|
811
|
+
mean, means : bool, default: False
|
|
812
|
+
If ``True``, this passes ``showmeans=True`` and ``meanline=True`` to
|
|
813
|
+
`matplotlib.axes.Axes.boxplot`. Adds mean lines alongside the median.
|
|
814
|
+
%(plot.cycle)s
|
|
815
|
+
%(artist.patch_black)s
|
|
816
|
+
m, marker, ms, markersize : float or str, optional
|
|
817
|
+
Marker style and size for the 'fliers', i.e. outliers. See the
|
|
818
|
+
``boxplot.flierprops`` `~matplotlib.rcParams` settings.
|
|
819
|
+
meanls, medianls, meanlinestyle, medianlinestyle, meanlinestyles, medianlinestyles \
|
|
820
|
+
: str, optional
|
|
821
|
+
Line style for the mean and median lines drawn across the box.
|
|
822
|
+
See the ``boxplot.meanprops`` and ``boxplot.medianprops``
|
|
823
|
+
`~matplotlib.rcParams` settings.
|
|
824
|
+
boxc, capc, whiskerc, flierc, meanc, medianc, \
|
|
825
|
+
boxcolor, capcolor, whiskercolor, fliercolor, meancolor, mediancolor \
|
|
826
|
+
boxcolors, capcolors, whiskercolors, fliercolors, meancolors, mediancolors \
|
|
827
|
+
: color-spec or sequence, optional
|
|
828
|
+
Color of various boxplot components. If a sequence, should be the same length as
|
|
829
|
+
the number of boxes. These are shorthands so you don't have to pass e.g. a
|
|
830
|
+
`boxprops` dictionary keyword. See the ``boxplot.boxprops``, ``boxplot.capprops``,
|
|
831
|
+
``boxplot.whiskerprops``, ``boxplot.flierprops``, ``boxplot.meanprops``, and
|
|
832
|
+
``boxplot.medianprops`` `~matplotlib.rcParams` settings.
|
|
833
|
+
boxlw, caplw, whiskerlw, flierlw, meanlw, medianlw, boxlinewidth, caplinewidth, \
|
|
834
|
+
meanlinewidth, medianlinewidth, whiskerlinewidth, flierlinewidth, boxlinewidths, \
|
|
835
|
+
caplinewidths, meanlinewidths, medianlinewidths, whiskerlinewidths, flierlinewidths \
|
|
836
|
+
: float, optional
|
|
837
|
+
Line width of various boxplot components. These are shorthands so
|
|
838
|
+
you don't have to pass e.g. a `boxprops` dictionary keyword.
|
|
839
|
+
See the ``boxplot.boxprops``, ``boxplot.capprops``, ``boxplot.whiskerprops``,
|
|
840
|
+
``boxplot.flierprops``, ``boxplot.meanprops``, and ``boxplot.medianprops``
|
|
841
|
+
`~matplotlib.rcParams` settings.
|
|
842
|
+
%(plot.labels_1d)s
|
|
843
|
+
**kwargs
|
|
844
|
+
Passed to `matplotlib.axes.Axes.boxplot`.
|
|
845
|
+
|
|
846
|
+
See also
|
|
847
|
+
--------
|
|
848
|
+
PlotAxes.boxes
|
|
849
|
+
PlotAxes.boxesh
|
|
850
|
+
PlotAxes.boxplot
|
|
851
|
+
PlotAxes.boxploth
|
|
852
|
+
matplotlib.axes.Axes.boxplot
|
|
853
|
+
"""
|
|
854
|
+
docstring._snippet_manager["plot.boxplot"] = _boxplot_docstring.format(
|
|
855
|
+
y="y", orientation="vertical"
|
|
856
|
+
)
|
|
857
|
+
docstring._snippet_manager["plot.boxploth"] = _boxplot_docstring.format(
|
|
858
|
+
y="x", orientation="horizontal"
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
# Violin plot docstrings
|
|
863
|
+
_violinplot_docstring = """
|
|
864
|
+
Plot {orientation} violins with a nice default style matching
|
|
865
|
+
`this matplotlib example \
|
|
866
|
+
<https://matplotlib.org/stable/gallery/statistics/customized_violin.html>`__.
|
|
867
|
+
|
|
868
|
+
Parameters
|
|
869
|
+
----------
|
|
870
|
+
%(plot.args_1d_{y})s
|
|
871
|
+
%(plot.args_1d_shared)s
|
|
872
|
+
|
|
873
|
+
Other parameters
|
|
874
|
+
----------------
|
|
875
|
+
%(plot.cycle)s
|
|
876
|
+
%(artist.patch_black)s
|
|
877
|
+
%(plot.labels_1d)s
|
|
878
|
+
showmeans, showmedians : bool, optional
|
|
879
|
+
Interpreted as ``means=True`` and ``medians=True`` when passed.
|
|
880
|
+
showextrema : bool, optional
|
|
881
|
+
Interpreted as ``barpctiles=True`` when passed (i.e. shows minima and maxima).
|
|
882
|
+
%(plot.error_bars)s
|
|
883
|
+
**kwargs
|
|
884
|
+
Passed to `matplotlib.axes.Axes.violinplot`.
|
|
885
|
+
|
|
886
|
+
See also
|
|
887
|
+
--------
|
|
888
|
+
PlotAxes.violin
|
|
889
|
+
PlotAxes.violinh
|
|
890
|
+
PlotAxes.violinplot
|
|
891
|
+
PlotAxes.violinploth
|
|
892
|
+
matplotlib.axes.Axes.violinplot
|
|
893
|
+
"""
|
|
894
|
+
docstring._snippet_manager["plot.violinplot"] = _violinplot_docstring.format(
|
|
895
|
+
y="y", orientation="vertical"
|
|
896
|
+
)
|
|
897
|
+
docstring._snippet_manager["plot.violinploth"] = _violinplot_docstring.format(
|
|
898
|
+
y="x", orientation="horizontal"
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
# 1D histogram docstrings
|
|
903
|
+
_hist_docstring = """
|
|
904
|
+
Plot {orientation} histograms.
|
|
905
|
+
|
|
906
|
+
Parameters
|
|
907
|
+
----------
|
|
908
|
+
%(plot.args_1d_{y})s
|
|
909
|
+
bins : int or sequence of float, optional
|
|
910
|
+
The bin count or exact bin edges.
|
|
911
|
+
%(plot.weights)s
|
|
912
|
+
histtype : {{'bar', 'barstacked', 'step', 'stepfilled'}}, optional
|
|
913
|
+
The histogram type. See `matplotlib.axes.Axes.hist` for details.
|
|
914
|
+
width, rwidth : float, default: 0.8 or 1
|
|
915
|
+
The bar width(s) for bar-type histograms relative to the bin size. Default
|
|
916
|
+
is ``0.8`` for multiple columns of unstacked data and ``1`` otherwise.
|
|
917
|
+
stack, stacked : bool, optional
|
|
918
|
+
Whether to "stack" successive columns of {y} data for bar-type histograms
|
|
919
|
+
or show side-by-side in groups. Setting this to ``False`` is equivalent to
|
|
920
|
+
``histtype='bar'`` and to ``True`` is equivalent to ``histtype='barstacked'``.
|
|
921
|
+
fill, filled : bool, optional
|
|
922
|
+
Whether to "fill" step-type histograms or just plot the edges. Setting
|
|
923
|
+
this to ``False`` is equivalent to ``histtype='step'`` and to ``True``
|
|
924
|
+
is equivalent to ``histtype='stepfilled'``.
|
|
925
|
+
%(plot.args_1d_shared)s
|
|
926
|
+
|
|
927
|
+
Other parameters
|
|
928
|
+
----------------
|
|
929
|
+
%(plot.cycle)s
|
|
930
|
+
%(artist.patch)s
|
|
931
|
+
%(axes.edgefix)s
|
|
932
|
+
%(plot.labels_1d)s
|
|
933
|
+
%(plot.guide)s
|
|
934
|
+
**kwargs
|
|
935
|
+
Passed to `~matplotlib.axes.Axes.hist`.
|
|
936
|
+
|
|
937
|
+
See also
|
|
938
|
+
--------
|
|
939
|
+
PlotAxes.hist
|
|
940
|
+
PlotAxes.histh
|
|
941
|
+
matplotlib.axes.Axes.hist
|
|
942
|
+
"""
|
|
943
|
+
_weights_docstring = """
|
|
944
|
+
weights : array-like, optional
|
|
945
|
+
The weights associated with each point. If string this
|
|
946
|
+
can be retrieved from `data` (see below).
|
|
947
|
+
"""
|
|
948
|
+
docstring._snippet_manager["plot.weights"] = _weights_docstring
|
|
949
|
+
docstring._snippet_manager["plot.hist"] = _hist_docstring.format(
|
|
950
|
+
y="x", orientation="vertical"
|
|
951
|
+
)
|
|
952
|
+
docstring._snippet_manager["plot.histh"] = _hist_docstring.format(
|
|
953
|
+
y="x", orientation="horizontal"
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# 2D histogram docstrings
|
|
958
|
+
_hist2d_docstring = """
|
|
959
|
+
Plot a {descrip}.
|
|
960
|
+
standard 2D histogram.
|
|
961
|
+
|
|
962
|
+
Parameters
|
|
963
|
+
----------
|
|
964
|
+
%(plot.args_1d_y)s{bins}
|
|
965
|
+
%(plot.weights)s
|
|
966
|
+
%(plot.args_1d_shared)s
|
|
967
|
+
|
|
968
|
+
Other parameters
|
|
969
|
+
----------------
|
|
970
|
+
%(plot.cmap_norm)s
|
|
971
|
+
%(plot.vmin_vmax)s
|
|
972
|
+
%(plot.levels_manual)s
|
|
973
|
+
%(plot.levels_auto)s
|
|
974
|
+
%(plot.labels_2d)s
|
|
975
|
+
%(plot.guide)s
|
|
976
|
+
**kwargs
|
|
977
|
+
Passed to `~matplotlib.axes.Axes.{command}`.
|
|
978
|
+
|
|
979
|
+
See also
|
|
980
|
+
--------
|
|
981
|
+
PlotAxes.hist2d
|
|
982
|
+
PlotAxes.hexbin
|
|
983
|
+
matplotlib.axes.Axes.{command}
|
|
984
|
+
"""
|
|
985
|
+
_bins_docstring = """
|
|
986
|
+
bins : int or 2-tuple of int, or array-like or 2-tuple of array-like, optional
|
|
987
|
+
The bin count or exact bin edges for each dimension or both dimensions.
|
|
988
|
+
""".rstrip()
|
|
989
|
+
docstring._snippet_manager["plot.hist2d"] = _hist2d_docstring.format(
|
|
990
|
+
command="hist2d", descrip="standard 2D histogram", bins=_bins_docstring
|
|
991
|
+
)
|
|
992
|
+
docstring._snippet_manager["plot.hexbin"] = _hist2d_docstring.format(
|
|
993
|
+
command="hexbin", descrip="2D hexagonally binned histogram", bins=""
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
# Pie chart docstring
|
|
998
|
+
_pie_docstring = """
|
|
999
|
+
Plot a pie chart.
|
|
1000
|
+
|
|
1001
|
+
Parameters
|
|
1002
|
+
----------
|
|
1003
|
+
%(plot.args_1d_y)s
|
|
1004
|
+
%(plot.args_1d_shared)s
|
|
1005
|
+
|
|
1006
|
+
Other parameters
|
|
1007
|
+
----------------
|
|
1008
|
+
%(plot.cycle)s
|
|
1009
|
+
%(artist.patch)s
|
|
1010
|
+
%(axes.edgefix)s
|
|
1011
|
+
%(plot.labels_1d)s
|
|
1012
|
+
labelpad, labeldistance : float, optional
|
|
1013
|
+
The distance at which labels are drawn in radial coordinates.
|
|
1014
|
+
|
|
1015
|
+
See also
|
|
1016
|
+
--------
|
|
1017
|
+
matplotlib.axes.Axes.pie
|
|
1018
|
+
"""
|
|
1019
|
+
docstring._snippet_manager["plot.pie"] = _pie_docstring
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
# Contour docstrings
|
|
1023
|
+
_contour_docstring = """
|
|
1024
|
+
Plot {descrip}.
|
|
1025
|
+
|
|
1026
|
+
Parameters
|
|
1027
|
+
----------
|
|
1028
|
+
%(plot.args_2d)s
|
|
1029
|
+
|
|
1030
|
+
%(plot.args_2d_shared)s
|
|
1031
|
+
|
|
1032
|
+
Other parameters
|
|
1033
|
+
----------------
|
|
1034
|
+
%(plot.cmap_norm)s
|
|
1035
|
+
%(plot.vmin_vmax)s
|
|
1036
|
+
%(plot.levels_manual)s
|
|
1037
|
+
%(plot.levels_auto)s
|
|
1038
|
+
%(artist.collection_contour)s{edgefix}
|
|
1039
|
+
%(plot.labels_2d)s
|
|
1040
|
+
%(plot.guide)s
|
|
1041
|
+
**kwargs
|
|
1042
|
+
Passed to `matplotlib.axes.Axes.{command}`.
|
|
1043
|
+
|
|
1044
|
+
See also
|
|
1045
|
+
--------
|
|
1046
|
+
PlotAxes.contour
|
|
1047
|
+
PlotAxes.contourf
|
|
1048
|
+
PlotAxes.tricontour
|
|
1049
|
+
PlotAxes.tricontourf
|
|
1050
|
+
matplotlib.axes.Axes.{command}
|
|
1051
|
+
"""
|
|
1052
|
+
docstring._snippet_manager["plot.contour"] = _contour_docstring.format(
|
|
1053
|
+
descrip="contour lines", command="contour", edgefix=""
|
|
1054
|
+
)
|
|
1055
|
+
docstring._snippet_manager["plot.contourf"] = _contour_docstring.format(
|
|
1056
|
+
descrip="filled contours",
|
|
1057
|
+
command="contourf",
|
|
1058
|
+
edgefix="%(axes.edgefix)s\n",
|
|
1059
|
+
)
|
|
1060
|
+
docstring._snippet_manager["plot.tricontour"] = _contour_docstring.format(
|
|
1061
|
+
descrip="contour lines on a triangular grid", command="tricontour", edgefix=""
|
|
1062
|
+
)
|
|
1063
|
+
docstring._snippet_manager["plot.tricontourf"] = _contour_docstring.format(
|
|
1064
|
+
descrip="filled contours on a triangular grid",
|
|
1065
|
+
command="tricontourf",
|
|
1066
|
+
edgefix="\n%(axes.edgefix)s", # noqa: E501
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
# Pcolor docstring
|
|
1071
|
+
_pcolor_docstring = """
|
|
1072
|
+
Plot {descrip}.
|
|
1073
|
+
|
|
1074
|
+
Parameters
|
|
1075
|
+
----------
|
|
1076
|
+
%(plot.args_2d)s
|
|
1077
|
+
|
|
1078
|
+
%(plot.args_2d_shared)s{aspect}
|
|
1079
|
+
|
|
1080
|
+
Other parameters
|
|
1081
|
+
----------------
|
|
1082
|
+
%(plot.cmap_norm)s
|
|
1083
|
+
%(plot.vmin_vmax)s
|
|
1084
|
+
%(plot.levels_manual)s
|
|
1085
|
+
%(plot.levels_auto)s
|
|
1086
|
+
%(artist.collection_pcolor)s
|
|
1087
|
+
%(axes.edgefix)s
|
|
1088
|
+
%(plot.labels_2d)s
|
|
1089
|
+
%(plot.guide)s
|
|
1090
|
+
**kwargs
|
|
1091
|
+
Passed to `matplotlib.axes.Axes.{command}`.
|
|
1092
|
+
|
|
1093
|
+
See also
|
|
1094
|
+
--------
|
|
1095
|
+
PlotAxes.pcolor
|
|
1096
|
+
PlotAxes.pcolormesh
|
|
1097
|
+
PlotAxes.pcolorfast
|
|
1098
|
+
PlotAxes.heatmap
|
|
1099
|
+
PlotAxes.tripcolor
|
|
1100
|
+
matplotlib.axes.Axes.{command}
|
|
1101
|
+
"""
|
|
1102
|
+
_heatmap_descrip = """
|
|
1103
|
+
grid boxes with formatting suitable for heatmaps. Ensures square grid
|
|
1104
|
+
boxes, adds major ticks to the center of each grid box, disables minor
|
|
1105
|
+
ticks and gridlines, and sets :rcraw:`cmap.discrete` to ``False`` by default
|
|
1106
|
+
""".strip()
|
|
1107
|
+
_heatmap_aspect = """
|
|
1108
|
+
aspect : {'equal', 'auto'} or float, default: :rc:`image.aspet`
|
|
1109
|
+
Modify the axes aspect ratio. The aspect ratio is of particular relevance for
|
|
1110
|
+
heatmaps since it may lead to non-square grid boxes. This parameter is a shortcut
|
|
1111
|
+
for calling `~matplotlib.axes.set_aspect`. The options are as follows:
|
|
1112
|
+
|
|
1113
|
+
* Number: The data aspect ratio.
|
|
1114
|
+
* ``'equal'``: A data aspect ratio of 1.
|
|
1115
|
+
* ``'auto'``: Allows the data aspect ratio to change depending on
|
|
1116
|
+
the layout. In general this results in non-square grid boxes.
|
|
1117
|
+
""".rstrip()
|
|
1118
|
+
docstring._snippet_manager["plot.pcolor"] = _pcolor_docstring.format(
|
|
1119
|
+
descrip="irregular grid boxes", command="pcolor", aspect=""
|
|
1120
|
+
)
|
|
1121
|
+
docstring._snippet_manager["plot.pcolormesh"] = _pcolor_docstring.format(
|
|
1122
|
+
descrip="regular grid boxes", command="pcolormesh", aspect=""
|
|
1123
|
+
)
|
|
1124
|
+
docstring._snippet_manager["plot.pcolorfast"] = _pcolor_docstring.format(
|
|
1125
|
+
descrip="grid boxes quickly", command="pcolorfast", aspect=""
|
|
1126
|
+
)
|
|
1127
|
+
docstring._snippet_manager["plot.tripcolor"] = _pcolor_docstring.format(
|
|
1128
|
+
descrip="triangular grid boxes", command="tripcolor", aspect=""
|
|
1129
|
+
)
|
|
1130
|
+
docstring._snippet_manager["plot.heatmap"] = _pcolor_docstring.format(
|
|
1131
|
+
descrip=_heatmap_descrip, command="pcolormesh", aspect=_heatmap_aspect
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
# Image docstring
|
|
1136
|
+
_show_docstring = """
|
|
1137
|
+
Plot {descrip}.
|
|
1138
|
+
|
|
1139
|
+
Parameters
|
|
1140
|
+
----------
|
|
1141
|
+
z : array-like
|
|
1142
|
+
The data passed as a positional argument or keyword argument.
|
|
1143
|
+
%(plot.args_1d_shared)s
|
|
1144
|
+
|
|
1145
|
+
Other parameters
|
|
1146
|
+
----------------
|
|
1147
|
+
%(plot.cmap_norm)s
|
|
1148
|
+
%(plot.vmin_vmax)s
|
|
1149
|
+
%(plot.levels_manual)s
|
|
1150
|
+
%(plot.levels_auto)s
|
|
1151
|
+
%(plot.guide)s
|
|
1152
|
+
**kwargs
|
|
1153
|
+
Passed to `matplotlib.axes.Axes.{command}`.
|
|
1154
|
+
|
|
1155
|
+
See also
|
|
1156
|
+
--------
|
|
1157
|
+
ultraplot.axes.PlotAxes
|
|
1158
|
+
matplotlib.axes.Axes.{command}
|
|
1159
|
+
"""
|
|
1160
|
+
docstring._snippet_manager["plot.imshow"] = _show_docstring.format(
|
|
1161
|
+
descrip="an image", command="imshow"
|
|
1162
|
+
)
|
|
1163
|
+
docstring._snippet_manager["plot.matshow"] = _show_docstring.format(
|
|
1164
|
+
descrip="a matrix", command="matshow"
|
|
1165
|
+
)
|
|
1166
|
+
docstring._snippet_manager["plot.spy"] = _show_docstring.format(
|
|
1167
|
+
descrip="a sparcity pattern", command="spy"
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
# Flow function docstring
|
|
1172
|
+
_flow_docstring = """
|
|
1173
|
+
Plot {descrip}.
|
|
1174
|
+
|
|
1175
|
+
Parameters
|
|
1176
|
+
----------
|
|
1177
|
+
%(plot.args_2d_flow)s
|
|
1178
|
+
|
|
1179
|
+
c, color, colors : array-like or color-spec, optional
|
|
1180
|
+
The colors of the {descrip} passed as either a keyword argument
|
|
1181
|
+
or a fifth positional argument. This can be a single color or
|
|
1182
|
+
a color array to be scaled by `cmap` and `norm`.
|
|
1183
|
+
%(plot.args_2d_shared)s
|
|
1184
|
+
|
|
1185
|
+
Other parameters
|
|
1186
|
+
----------------
|
|
1187
|
+
%(plot.cmap_norm)s
|
|
1188
|
+
%(plot.vmin_vmax)s
|
|
1189
|
+
%(plot.levels_manual)s
|
|
1190
|
+
%(plot.levels_auto)s
|
|
1191
|
+
**kwargs
|
|
1192
|
+
Passed to `matplotlib.axes.Axes.{command}`
|
|
1193
|
+
|
|
1194
|
+
See also
|
|
1195
|
+
--------
|
|
1196
|
+
PlotAxes.barbs
|
|
1197
|
+
PlotAxes.quiver
|
|
1198
|
+
PlotAxes.stream
|
|
1199
|
+
PlotAxes.streamplot
|
|
1200
|
+
matplotlib.axes.Axes.{command}
|
|
1201
|
+
"""
|
|
1202
|
+
docstring._snippet_manager["plot.barbs"] = _flow_docstring.format(
|
|
1203
|
+
descrip="wind barbs", command="barbs"
|
|
1204
|
+
)
|
|
1205
|
+
docstring._snippet_manager["plot.quiver"] = _flow_docstring.format(
|
|
1206
|
+
descrip="quiver arrows", command="quiver"
|
|
1207
|
+
)
|
|
1208
|
+
docstring._snippet_manager["plot.stream"] = _flow_docstring.format(
|
|
1209
|
+
descrip="streamlines", command="streamplot"
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def _get_vert(vert=None, orientation=None, **kwargs):
|
|
1214
|
+
"""
|
|
1215
|
+
Get the orientation specified as either `vert` or `orientation`. This is
|
|
1216
|
+
used internally by various helper functions.
|
|
1217
|
+
"""
|
|
1218
|
+
if vert is not None:
|
|
1219
|
+
return kwargs, vert
|
|
1220
|
+
elif orientation is not None:
|
|
1221
|
+
return kwargs, orientation != "horizontal" # should already be validated
|
|
1222
|
+
else:
|
|
1223
|
+
return kwargs, True # fallback
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
def _parse_vert(
|
|
1227
|
+
vert=None, orientation=None, default_vert=None, default_orientation=None, **kwargs
|
|
1228
|
+
):
|
|
1229
|
+
"""
|
|
1230
|
+
Interpret both 'vert' and 'orientation' and add to outgoing keyword args
|
|
1231
|
+
if a default is provided.
|
|
1232
|
+
"""
|
|
1233
|
+
# NOTE: Users should only pass these to hist, boxplot, or violinplot. To change
|
|
1234
|
+
# the plot, scatter, area, or bar orientation users should use the differently
|
|
1235
|
+
# named functions. Internally, however, they use these keyword args.
|
|
1236
|
+
if default_vert is not None:
|
|
1237
|
+
kwargs["vert"] = _not_none(
|
|
1238
|
+
vert=vert,
|
|
1239
|
+
orientation=None if orientation is None else orientation == "vertical",
|
|
1240
|
+
default=default_vert,
|
|
1241
|
+
)
|
|
1242
|
+
if default_orientation is not None:
|
|
1243
|
+
kwargs["orientation"] = _not_none(
|
|
1244
|
+
orientation=orientation,
|
|
1245
|
+
vert=None if vert is None else "vertical" if vert else "horizontal",
|
|
1246
|
+
default=default_orientation,
|
|
1247
|
+
)
|
|
1248
|
+
if kwargs.get("orientation", None) not in (None, "horizontal", "vertical"):
|
|
1249
|
+
raise ValueError("Orientation must be either 'horizontal' or 'vertical'.")
|
|
1250
|
+
return kwargs
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
def _inside_seaborn_call():
|
|
1254
|
+
"""
|
|
1255
|
+
Try to detect `seaborn` calls to `scatter` and `bar` and then automatically
|
|
1256
|
+
apply `absolute_size` and `absolute_width`.
|
|
1257
|
+
"""
|
|
1258
|
+
frame = sys._getframe()
|
|
1259
|
+
absolute_names = (
|
|
1260
|
+
"seaborn.distributions",
|
|
1261
|
+
"seaborn.categorical",
|
|
1262
|
+
"seaborn.relational",
|
|
1263
|
+
"seaborn.regression",
|
|
1264
|
+
)
|
|
1265
|
+
while frame is not None:
|
|
1266
|
+
if frame.f_globals.get("__name__", "") in absolute_names:
|
|
1267
|
+
return True
|
|
1268
|
+
frame = frame.f_back
|
|
1269
|
+
return False
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
class PlotAxes(base.Axes):
|
|
1273
|
+
"""
|
|
1274
|
+
The second lowest-level `~matplotlib.axes.Axes` subclass used by ultraplot.
|
|
1275
|
+
Implements all plotting overrides.
|
|
1276
|
+
"""
|
|
1277
|
+
|
|
1278
|
+
def __init__(self, *args, **kwargs):
|
|
1279
|
+
"""
|
|
1280
|
+
Parameters
|
|
1281
|
+
----------
|
|
1282
|
+
*args, **kwargs
|
|
1283
|
+
Passed to `ultraplot.axes.Axes`.
|
|
1284
|
+
|
|
1285
|
+
See also
|
|
1286
|
+
--------
|
|
1287
|
+
matplotlib.axes.Axes
|
|
1288
|
+
ultraplot.axes.Axes
|
|
1289
|
+
ultraplot.axes.CartesianAxes
|
|
1290
|
+
ultraplot.axes.PolarAxes
|
|
1291
|
+
ultraplot.axes.GeoAxes
|
|
1292
|
+
"""
|
|
1293
|
+
super().__init__(*args, **kwargs)
|
|
1294
|
+
|
|
1295
|
+
def _call_native(self, name, *args, **kwargs):
|
|
1296
|
+
"""
|
|
1297
|
+
Call the plotting method and redirect internal calls to native methods.
|
|
1298
|
+
"""
|
|
1299
|
+
# NOTE: Previously allowed internal matplotlib plotting function calls to run
|
|
1300
|
+
# through ultraplot overrides then avoided awkward conflicts in piecemeal fashion.
|
|
1301
|
+
# Now prevent internal calls from running through overrides using preprocessor
|
|
1302
|
+
kwargs.pop("distribution", None) # remove stat distributions
|
|
1303
|
+
with context._state_context(self, _internal_call=True):
|
|
1304
|
+
if self._name == "basemap":
|
|
1305
|
+
obj = getattr(self.projection, name)(*args, ax=self, **kwargs)
|
|
1306
|
+
else:
|
|
1307
|
+
obj = getattr(super(), name)(*args, **kwargs)
|
|
1308
|
+
return obj
|
|
1309
|
+
|
|
1310
|
+
def _call_negpos(
|
|
1311
|
+
self,
|
|
1312
|
+
name,
|
|
1313
|
+
x,
|
|
1314
|
+
*ys,
|
|
1315
|
+
negcolor=None,
|
|
1316
|
+
poscolor=None,
|
|
1317
|
+
colorkey="facecolor",
|
|
1318
|
+
use_where=False,
|
|
1319
|
+
use_zero=False,
|
|
1320
|
+
**kwargs,
|
|
1321
|
+
):
|
|
1322
|
+
"""
|
|
1323
|
+
Call the plotting method separately for "negative" and "positive" data.
|
|
1324
|
+
"""
|
|
1325
|
+
if use_where:
|
|
1326
|
+
kwargs.setdefault("interpolate", True) # see fill_between docs
|
|
1327
|
+
for key in ("color", "colors", "facecolor", "facecolors", "where"):
|
|
1328
|
+
value = kwargs.pop(key, None)
|
|
1329
|
+
if value is not None:
|
|
1330
|
+
warnings._warn_ultraplot(
|
|
1331
|
+
f"{name}() argument {key}={value!r} is incompatible with negpos=True. Ignoring." # noqa: E501
|
|
1332
|
+
)
|
|
1333
|
+
# Negative component
|
|
1334
|
+
yneg = list(ys) # copy
|
|
1335
|
+
if use_zero: # filter bar heights
|
|
1336
|
+
yneg[0] = inputs._safe_mask(ys[0] < 0, ys[0])
|
|
1337
|
+
elif use_where: # apply fill_between mask
|
|
1338
|
+
kwargs["where"] = ys[1] < ys[0]
|
|
1339
|
+
else:
|
|
1340
|
+
yneg = inputs._safe_mask(ys[1] < ys[0], *ys)
|
|
1341
|
+
kwargs[colorkey] = _not_none(negcolor, rc["negcolor"])
|
|
1342
|
+
negobj = self._call_native(name, x, *yneg, **kwargs)
|
|
1343
|
+
# Positive component
|
|
1344
|
+
ypos = list(ys) # copy
|
|
1345
|
+
if use_zero: # filter bar heights
|
|
1346
|
+
ypos[0] = inputs._safe_mask(ys[0] >= 0, ys[0])
|
|
1347
|
+
elif use_where: # apply fill_between mask
|
|
1348
|
+
kwargs["where"] = ys[1] >= ys[0]
|
|
1349
|
+
else:
|
|
1350
|
+
ypos = inputs._safe_mask(ys[1] >= ys[0], *ys)
|
|
1351
|
+
kwargs[colorkey] = _not_none(poscolor, rc["poscolor"])
|
|
1352
|
+
posobj = self._call_native(name, x, *ypos, **kwargs)
|
|
1353
|
+
return cbook.silent_list(type(negobj).__name__, (negobj, posobj))
|
|
1354
|
+
|
|
1355
|
+
def _add_auto_labels(
|
|
1356
|
+
self,
|
|
1357
|
+
obj,
|
|
1358
|
+
cobj=None,
|
|
1359
|
+
labels=False,
|
|
1360
|
+
labels_kw=None,
|
|
1361
|
+
fmt=None,
|
|
1362
|
+
formatter=None,
|
|
1363
|
+
formatter_kw=None,
|
|
1364
|
+
precision=None,
|
|
1365
|
+
):
|
|
1366
|
+
"""
|
|
1367
|
+
Add number labels. Default formatter is `~ultraplot.ticker.SimpleFormatter`
|
|
1368
|
+
with a default maximum precision of ``3`` decimal places.
|
|
1369
|
+
"""
|
|
1370
|
+
# TODO: Add quiverkey to this!
|
|
1371
|
+
if not labels:
|
|
1372
|
+
return
|
|
1373
|
+
labels_kw = labels_kw or {}
|
|
1374
|
+
formatter_kw = formatter_kw or {}
|
|
1375
|
+
formatter = _not_none(
|
|
1376
|
+
fmt_labels_kw=labels_kw.pop("fmt", None),
|
|
1377
|
+
formatter_labels_kw=labels_kw.pop("formatter", None),
|
|
1378
|
+
fmt=fmt,
|
|
1379
|
+
formatter=formatter,
|
|
1380
|
+
default="simple",
|
|
1381
|
+
)
|
|
1382
|
+
precision = _not_none(
|
|
1383
|
+
formatter_kw_precision=formatter_kw.pop("precision", None),
|
|
1384
|
+
precision=precision,
|
|
1385
|
+
default=3, # should be lower than the default intended for tick labels
|
|
1386
|
+
)
|
|
1387
|
+
formatter = constructor.Formatter(
|
|
1388
|
+
formatter, precision=precision, **formatter_kw
|
|
1389
|
+
) # noqa: E501
|
|
1390
|
+
if isinstance(obj, mcontour.ContourSet):
|
|
1391
|
+
self._add_contour_labels(obj, cobj, formatter, **labels_kw)
|
|
1392
|
+
elif isinstance(obj, mcollections.Collection):
|
|
1393
|
+
self._add_collection_labels(obj, formatter, **labels_kw)
|
|
1394
|
+
else:
|
|
1395
|
+
raise RuntimeError(f"Not possible to add labels to object {obj!r}.")
|
|
1396
|
+
|
|
1397
|
+
def _add_collection_labels(
|
|
1398
|
+
self,
|
|
1399
|
+
obj,
|
|
1400
|
+
fmt,
|
|
1401
|
+
*,
|
|
1402
|
+
c=None,
|
|
1403
|
+
color=None,
|
|
1404
|
+
colors=None,
|
|
1405
|
+
size=None,
|
|
1406
|
+
fontsize=None,
|
|
1407
|
+
**kwargs,
|
|
1408
|
+
):
|
|
1409
|
+
"""
|
|
1410
|
+
Add labels to pcolor boxes with support for shade-dependent text colors.
|
|
1411
|
+
Values are inferred from the unnormalized grid box color.
|
|
1412
|
+
"""
|
|
1413
|
+
# Parse input args
|
|
1414
|
+
# NOTE: This function also hides grid boxes filled with NaNs to avoid ugly
|
|
1415
|
+
# issue where edge colors surround NaNs. Should maybe move this somewhere else.
|
|
1416
|
+
obj.update_scalarmappable() # update 'edgecolors' list
|
|
1417
|
+
color = _not_none(c=c, color=color, colors=colors)
|
|
1418
|
+
fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
|
|
1419
|
+
kwargs.setdefault("ha", "center")
|
|
1420
|
+
kwargs.setdefault("va", "center")
|
|
1421
|
+
|
|
1422
|
+
# Apply colors and hide edge colors for empty grids
|
|
1423
|
+
labs = []
|
|
1424
|
+
array = obj.get_array()
|
|
1425
|
+
paths = obj.get_paths()
|
|
1426
|
+
edgecolors = inputs._to_numpy_array(obj.get_edgecolors())
|
|
1427
|
+
if len(edgecolors) == 1:
|
|
1428
|
+
edgecolors = np.repeat(edgecolors, len(array), axis=0)
|
|
1429
|
+
for i, (path, value) in enumerate(zip(paths, array)):
|
|
1430
|
+
# Round to the number corresponding to the *color* rather than
|
|
1431
|
+
# the exact data value. Similar to contour label numbering.
|
|
1432
|
+
if value is ma.masked or not np.any(np.isfinite(value) == False):
|
|
1433
|
+
edgecolors[i, :] = 0
|
|
1434
|
+
continue
|
|
1435
|
+
if isinstance(obj.norm, pcolors.DiscreteNorm):
|
|
1436
|
+
value = obj.norm._norm.inverse(obj.norm(value))
|
|
1437
|
+
icolor = color
|
|
1438
|
+
if color is None:
|
|
1439
|
+
_, _, lum = utils.to_xyz(obj.cmap(obj.norm(value)), "hcl")
|
|
1440
|
+
icolor = "w" if lum < 50 else "k"
|
|
1441
|
+
bbox = path.get_extents()
|
|
1442
|
+
x = (bbox.xmin + bbox.xmax) / 2
|
|
1443
|
+
y = (bbox.ymin + bbox.ymax) / 2
|
|
1444
|
+
lab = self.text(x, y, fmt(value), color=icolor, size=fontsize, **kwargs)
|
|
1445
|
+
labs.append(lab)
|
|
1446
|
+
|
|
1447
|
+
obj.set_edgecolors(edgecolors)
|
|
1448
|
+
return labs
|
|
1449
|
+
|
|
1450
|
+
def _add_contour_labels(
|
|
1451
|
+
self,
|
|
1452
|
+
obj,
|
|
1453
|
+
cobj,
|
|
1454
|
+
fmt,
|
|
1455
|
+
*,
|
|
1456
|
+
c=None,
|
|
1457
|
+
color=None,
|
|
1458
|
+
colors=None,
|
|
1459
|
+
size=None,
|
|
1460
|
+
fontsize=None,
|
|
1461
|
+
inline_spacing=None,
|
|
1462
|
+
**kwargs,
|
|
1463
|
+
):
|
|
1464
|
+
"""
|
|
1465
|
+
Add labels to contours with support for shade-dependent filled contour labels.
|
|
1466
|
+
Text color is inferred from filled contour object and labels are always drawn
|
|
1467
|
+
on unfilled contour object (otherwise errors crop up).
|
|
1468
|
+
"""
|
|
1469
|
+
# Parse input args
|
|
1470
|
+
zorder = max((h.get_zorder() for h in obj.collections), default=3)
|
|
1471
|
+
zorder = max(3, zorder + 1)
|
|
1472
|
+
kwargs.setdefault("zorder", zorder)
|
|
1473
|
+
colors = _not_none(c=c, color=color, colors=colors)
|
|
1474
|
+
fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
|
|
1475
|
+
inline_spacing = _not_none(inline_spacing, 2.5)
|
|
1476
|
+
|
|
1477
|
+
# Separate clabel args from text Artist args
|
|
1478
|
+
text_kw = {}
|
|
1479
|
+
clabel_keys = ("levels", "inline", "manual", "rightside_up", "use_clabeltext")
|
|
1480
|
+
for key in tuple(kwargs): # allow dict to change size
|
|
1481
|
+
if key not in clabel_keys:
|
|
1482
|
+
text_kw[key] = kwargs.pop(key)
|
|
1483
|
+
|
|
1484
|
+
# Draw hidden additional contour for filled contour labels
|
|
1485
|
+
cobj = _not_none(cobj, obj)
|
|
1486
|
+
if obj.filled and colors is None:
|
|
1487
|
+
colors = []
|
|
1488
|
+
for level in obj.levels:
|
|
1489
|
+
_, _, lum = utils.to_xyz(obj.cmap(obj.norm(level)))
|
|
1490
|
+
colors.append("w" if lum < 50 else "k")
|
|
1491
|
+
|
|
1492
|
+
# Draw the labels
|
|
1493
|
+
labs = cobj.clabel(
|
|
1494
|
+
fmt=fmt,
|
|
1495
|
+
colors=colors,
|
|
1496
|
+
fontsize=fontsize,
|
|
1497
|
+
inline_spacing=inline_spacing,
|
|
1498
|
+
**kwargs,
|
|
1499
|
+
)
|
|
1500
|
+
if labs is not None: # returns None if no contours
|
|
1501
|
+
for lab in labs:
|
|
1502
|
+
lab.update(text_kw)
|
|
1503
|
+
|
|
1504
|
+
return labs
|
|
1505
|
+
|
|
1506
|
+
def _add_error_bars(
|
|
1507
|
+
self,
|
|
1508
|
+
x,
|
|
1509
|
+
y,
|
|
1510
|
+
*_,
|
|
1511
|
+
distribution=None,
|
|
1512
|
+
default_barstds=False,
|
|
1513
|
+
default_boxstds=False,
|
|
1514
|
+
default_barpctiles=False,
|
|
1515
|
+
default_boxpctiles=False,
|
|
1516
|
+
default_marker=False,
|
|
1517
|
+
bars=None,
|
|
1518
|
+
boxes=None,
|
|
1519
|
+
barstd=None,
|
|
1520
|
+
barstds=None,
|
|
1521
|
+
barpctile=None,
|
|
1522
|
+
barpctiles=None,
|
|
1523
|
+
bardata=None,
|
|
1524
|
+
boxstd=None,
|
|
1525
|
+
boxstds=None,
|
|
1526
|
+
boxpctile=None,
|
|
1527
|
+
boxpctiles=None,
|
|
1528
|
+
boxdata=None,
|
|
1529
|
+
capsize=None,
|
|
1530
|
+
**kwargs,
|
|
1531
|
+
):
|
|
1532
|
+
"""
|
|
1533
|
+
Add up to 2 error indicators: thick "boxes" and thin "bars". The ``default``
|
|
1534
|
+
keywords toggle default range indicators when distributions are passed.
|
|
1535
|
+
"""
|
|
1536
|
+
# Parse input args
|
|
1537
|
+
# NOTE: Want to keep _add_error_bars() and _add_error_shading() separate.
|
|
1538
|
+
# But also want default behavior where some default error indicator is shown
|
|
1539
|
+
# if user requests means/medians only. Result is the below kludge.
|
|
1540
|
+
kwargs, vert = _get_vert(**kwargs)
|
|
1541
|
+
barstds = _not_none(bars=bars, barstd=barstd, barstds=barstds)
|
|
1542
|
+
boxstds = _not_none(boxes=boxes, boxstd=boxstd, boxstds=boxstds)
|
|
1543
|
+
barpctiles = _not_none(barpctile=barpctile, barpctiles=barpctiles)
|
|
1544
|
+
boxpctiles = _not_none(boxpctile=boxpctile, boxpctiles=boxpctiles)
|
|
1545
|
+
if distribution is not None and not any(
|
|
1546
|
+
typ + mode in key
|
|
1547
|
+
for key in kwargs
|
|
1548
|
+
for typ in ("shade", "fade")
|
|
1549
|
+
for mode in ("", "std", "pctile", "data")
|
|
1550
|
+
): # ugly kludge to check for shading
|
|
1551
|
+
if all(_ is None for _ in (bardata, barstds, barpctiles)):
|
|
1552
|
+
barstds, barpctiles = default_barstds, default_barpctiles
|
|
1553
|
+
if all(_ is None for _ in (boxdata, boxstds, boxpctile)):
|
|
1554
|
+
boxstds, boxpctiles = default_boxstds, default_boxpctiles
|
|
1555
|
+
showbars = any(
|
|
1556
|
+
_ is not None and _ is not False for _ in (barstds, barpctiles, bardata)
|
|
1557
|
+
)
|
|
1558
|
+
showboxes = any(
|
|
1559
|
+
_ is not None and _ is not False for _ in (boxstds, boxpctiles, boxdata)
|
|
1560
|
+
)
|
|
1561
|
+
|
|
1562
|
+
# Error bar properties
|
|
1563
|
+
edgecolor = kwargs.get("edgecolor", rc["boxplot.whiskerprops.color"])
|
|
1564
|
+
barprops = _pop_props(kwargs, "line", ignore="marker", prefix="bar")
|
|
1565
|
+
barprops["capsize"] = _not_none(capsize, rc["errorbar.capsize"])
|
|
1566
|
+
barprops["linestyle"] = "none"
|
|
1567
|
+
barprops.setdefault("color", edgecolor)
|
|
1568
|
+
barprops.setdefault("zorder", 2.5)
|
|
1569
|
+
barprops.setdefault("linewidth", rc["boxplot.whiskerprops.linewidth"])
|
|
1570
|
+
|
|
1571
|
+
# Error box properties
|
|
1572
|
+
# NOTE: Includes 'markerfacecolor' and 'markeredgecolor' props
|
|
1573
|
+
boxprops = _pop_props(kwargs, "line", prefix="box")
|
|
1574
|
+
boxprops["capsize"] = 0
|
|
1575
|
+
boxprops["linestyle"] = "none"
|
|
1576
|
+
boxprops.setdefault("color", barprops["color"])
|
|
1577
|
+
boxprops.setdefault("zorder", barprops["zorder"])
|
|
1578
|
+
boxprops.setdefault("linewidth", 4 * barprops["linewidth"])
|
|
1579
|
+
|
|
1580
|
+
# Box marker properties
|
|
1581
|
+
boxmarker = {
|
|
1582
|
+
key: boxprops.pop(key) for key in tuple(boxprops) if "marker" in key
|
|
1583
|
+
} # noqa: E501
|
|
1584
|
+
boxmarker["c"] = _not_none(boxmarker.pop("markerfacecolor", None), "white")
|
|
1585
|
+
boxmarker["s"] = _not_none(
|
|
1586
|
+
boxmarker.pop("markersize", None), boxprops["linewidth"] ** 0.5
|
|
1587
|
+
) # noqa: E501
|
|
1588
|
+
boxmarker["zorder"] = boxprops["zorder"]
|
|
1589
|
+
boxmarker["edgecolor"] = boxmarker.pop("markeredgecolor", None)
|
|
1590
|
+
boxmarker["linewidth"] = boxmarker.pop("markerlinewidth", None)
|
|
1591
|
+
if boxmarker.get("marker") is True:
|
|
1592
|
+
boxmarker["marker"] = "o"
|
|
1593
|
+
elif default_marker:
|
|
1594
|
+
boxmarker.setdefault("marker", "o")
|
|
1595
|
+
|
|
1596
|
+
# Draw thin or thick error bars from distributions or explicit errdata
|
|
1597
|
+
# NOTE: Now impossible to make thin bar width different from cap width!
|
|
1598
|
+
# NOTE: Boxes must go after so scatter point can go on top
|
|
1599
|
+
sy = "y" if vert else "x" # yerr
|
|
1600
|
+
ex, ey = (x, y) if vert else (y, x)
|
|
1601
|
+
eobjs = []
|
|
1602
|
+
if showbars: # noqa: E501
|
|
1603
|
+
edata, _ = inputs._dist_range(
|
|
1604
|
+
y,
|
|
1605
|
+
distribution,
|
|
1606
|
+
stds=barstds,
|
|
1607
|
+
pctiles=barpctiles,
|
|
1608
|
+
errdata=bardata,
|
|
1609
|
+
stds_default=(-3, 3),
|
|
1610
|
+
pctiles_default=(0, 100),
|
|
1611
|
+
)
|
|
1612
|
+
if edata is not None:
|
|
1613
|
+
obj = self.errorbar(ex, ey, **barprops, **{sy + "err": edata})
|
|
1614
|
+
eobjs.append(obj)
|
|
1615
|
+
if showboxes: # noqa: E501
|
|
1616
|
+
edata, _ = inputs._dist_range(
|
|
1617
|
+
y,
|
|
1618
|
+
distribution,
|
|
1619
|
+
stds=boxstds,
|
|
1620
|
+
pctiles=boxpctiles,
|
|
1621
|
+
errdata=boxdata,
|
|
1622
|
+
stds_default=(-1, 1),
|
|
1623
|
+
pctiles_default=(25, 75),
|
|
1624
|
+
)
|
|
1625
|
+
if edata is not None:
|
|
1626
|
+
obj = self.errorbar(ex, ey, **boxprops, **{sy + "err": edata})
|
|
1627
|
+
if boxmarker.get("marker", None):
|
|
1628
|
+
self.scatter(ex, ey, **boxmarker)
|
|
1629
|
+
eobjs.append(obj)
|
|
1630
|
+
|
|
1631
|
+
kwargs["distribution"] = distribution
|
|
1632
|
+
return (*eobjs, kwargs)
|
|
1633
|
+
|
|
1634
|
+
def _add_error_shading(
|
|
1635
|
+
self,
|
|
1636
|
+
x,
|
|
1637
|
+
y,
|
|
1638
|
+
*_,
|
|
1639
|
+
distribution=None,
|
|
1640
|
+
color_key="color",
|
|
1641
|
+
shade=None,
|
|
1642
|
+
shadestd=None,
|
|
1643
|
+
shadestds=None,
|
|
1644
|
+
shadepctile=None,
|
|
1645
|
+
shadepctiles=None,
|
|
1646
|
+
shadedata=None,
|
|
1647
|
+
fade=None,
|
|
1648
|
+
fadestd=None,
|
|
1649
|
+
fadestds=None,
|
|
1650
|
+
fadepctile=None,
|
|
1651
|
+
fadepctiles=None,
|
|
1652
|
+
fadedata=None,
|
|
1653
|
+
shadelabel=False,
|
|
1654
|
+
fadelabel=False,
|
|
1655
|
+
**kwargs,
|
|
1656
|
+
):
|
|
1657
|
+
"""
|
|
1658
|
+
Add up to 2 error indicators: more opaque "shading" and less opaque "fading".
|
|
1659
|
+
"""
|
|
1660
|
+
kwargs, vert = _get_vert(**kwargs)
|
|
1661
|
+
shadestds = _not_none(shade=shade, shadestd=shadestd, shadestds=shadestds)
|
|
1662
|
+
fadestds = _not_none(fade=fade, fadestd=fadestd, fadestds=fadestds)
|
|
1663
|
+
shadepctiles = _not_none(shadepctile=shadepctile, shadepctiles=shadepctiles)
|
|
1664
|
+
fadepctiles = _not_none(fadepctile=fadepctile, fadepctiles=fadepctiles)
|
|
1665
|
+
drawshade = any(
|
|
1666
|
+
_ is not None and _ is not False
|
|
1667
|
+
for _ in (shadestds, shadepctiles, shadedata)
|
|
1668
|
+
)
|
|
1669
|
+
drawfade = any(
|
|
1670
|
+
_ is not None and _ is not False for _ in (fadestds, fadepctiles, fadedata)
|
|
1671
|
+
)
|
|
1672
|
+
|
|
1673
|
+
# Shading properties
|
|
1674
|
+
shadeprops = _pop_props(kwargs, "patch", prefix="shade")
|
|
1675
|
+
shadeprops.setdefault("alpha", 0.4)
|
|
1676
|
+
shadeprops.setdefault("zorder", 1.5)
|
|
1677
|
+
shadeprops.setdefault("linewidth", rc["patch.linewidth"])
|
|
1678
|
+
shadeprops.setdefault("edgecolor", "none")
|
|
1679
|
+
# Fading properties
|
|
1680
|
+
fadeprops = _pop_props(kwargs, "patch", prefix="fade")
|
|
1681
|
+
fadeprops.setdefault("zorder", shadeprops["zorder"])
|
|
1682
|
+
fadeprops.setdefault("alpha", 0.5 * shadeprops["alpha"])
|
|
1683
|
+
fadeprops.setdefault("linewidth", shadeprops["linewidth"])
|
|
1684
|
+
fadeprops.setdefault("edgecolor", "none")
|
|
1685
|
+
# Get default color then apply to outgoing keyword args so
|
|
1686
|
+
# that plotting function will not advance to next cycler color.
|
|
1687
|
+
# TODO: More robust treatment of 'color' vs. 'facecolor'
|
|
1688
|
+
if (
|
|
1689
|
+
drawshade
|
|
1690
|
+
and shadeprops.get("facecolor", None) is None
|
|
1691
|
+
or drawfade
|
|
1692
|
+
and fadeprops.get("facecolor", None) is None
|
|
1693
|
+
):
|
|
1694
|
+
color = kwargs.get(color_key, None)
|
|
1695
|
+
if color is None: # add to outgoing
|
|
1696
|
+
color = kwargs[color_key] = self._get_lines.get_next_color()
|
|
1697
|
+
shadeprops.setdefault("facecolor", color)
|
|
1698
|
+
fadeprops.setdefault("facecolor", color)
|
|
1699
|
+
|
|
1700
|
+
# Draw dark and light shading from distributions or explicit errdata
|
|
1701
|
+
eobjs = []
|
|
1702
|
+
fill = self.fill_between if vert else self.fill_betweenx
|
|
1703
|
+
if drawfade:
|
|
1704
|
+
edata, label = inputs._dist_range(
|
|
1705
|
+
y,
|
|
1706
|
+
distribution,
|
|
1707
|
+
stds=fadestds,
|
|
1708
|
+
pctiles=fadepctiles,
|
|
1709
|
+
errdata=fadedata,
|
|
1710
|
+
stds_default=(-3, 3),
|
|
1711
|
+
pctiles_default=(0, 100),
|
|
1712
|
+
label=fadelabel,
|
|
1713
|
+
absolute=True,
|
|
1714
|
+
)
|
|
1715
|
+
if edata is not None:
|
|
1716
|
+
eobj = fill(x, *edata, label=label, **fadeprops)
|
|
1717
|
+
eobjs.append(eobj)
|
|
1718
|
+
if drawshade:
|
|
1719
|
+
edata, label = inputs._dist_range(
|
|
1720
|
+
y,
|
|
1721
|
+
distribution,
|
|
1722
|
+
stds=shadestds,
|
|
1723
|
+
pctiles=shadepctiles,
|
|
1724
|
+
errdata=shadedata,
|
|
1725
|
+
stds_default=(-2, 2),
|
|
1726
|
+
pctiles_default=(10, 90),
|
|
1727
|
+
label=shadelabel,
|
|
1728
|
+
absolute=True,
|
|
1729
|
+
)
|
|
1730
|
+
if edata is not None:
|
|
1731
|
+
eobj = fill(x, *edata, label=label, **shadeprops)
|
|
1732
|
+
eobjs.append(eobj)
|
|
1733
|
+
|
|
1734
|
+
kwargs["distribution"] = distribution
|
|
1735
|
+
return (*eobjs, kwargs)
|
|
1736
|
+
|
|
1737
|
+
def _fix_contour_edges(self, method, *args, **kwargs):
|
|
1738
|
+
"""
|
|
1739
|
+
Fix the filled contour edges by secretly adding solid contours with
|
|
1740
|
+
the same input data.
|
|
1741
|
+
"""
|
|
1742
|
+
# NOTE: This is used to provide an object that can be used by 'clabel' for
|
|
1743
|
+
# auto-labels. Filled contours create strange artifacts.
|
|
1744
|
+
# NOTE: Make the default 'line width' identical to one used for pcolor plots
|
|
1745
|
+
# rather than rc['contour.linewidth']. See mpl pcolor() source code
|
|
1746
|
+
if not any(key in kwargs for key in ("linewidths", "linestyles", "edgecolors")):
|
|
1747
|
+
kwargs["linewidths"] = 0 # for clabel
|
|
1748
|
+
kwargs.setdefault("linewidths", EDGEWIDTH)
|
|
1749
|
+
kwargs.pop("cmap", None)
|
|
1750
|
+
kwargs["colors"] = kwargs.pop("edgecolors", "k")
|
|
1751
|
+
return self._call_native(method, *args, **kwargs)
|
|
1752
|
+
|
|
1753
|
+
def _fix_sticky_edges(self, objs, axis, *args, only=None):
|
|
1754
|
+
"""
|
|
1755
|
+
Fix sticky edges for the input artists using the minimum and maximum of the
|
|
1756
|
+
input coordinates. This is used to copy `bar` behavior to `area` and `lines`.
|
|
1757
|
+
"""
|
|
1758
|
+
for array in args:
|
|
1759
|
+
min_, max_ = inputs._safe_range(array)
|
|
1760
|
+
if min_ is None or max_ is None:
|
|
1761
|
+
continue
|
|
1762
|
+
for obj in guides._iter_iterables(objs):
|
|
1763
|
+
if only and not isinstance(obj, only):
|
|
1764
|
+
continue # e.g. ignore error bars
|
|
1765
|
+
convert = getattr(self, "convert_" + axis + "units")
|
|
1766
|
+
edges = getattr(obj.sticky_edges, axis)
|
|
1767
|
+
edges.extend(convert((min_, max_)))
|
|
1768
|
+
|
|
1769
|
+
@staticmethod
|
|
1770
|
+
def _fix_patch_edges(obj, edgefix=None, **kwargs):
|
|
1771
|
+
"""
|
|
1772
|
+
Fix white lines between between filled patches and fix issues
|
|
1773
|
+
with colormaps that are transparent. If keyword args passed by user
|
|
1774
|
+
include explicit edge properties then we skip this step.
|
|
1775
|
+
"""
|
|
1776
|
+
# NOTE: Use default edge width used for pcolor grid box edges. This is thick
|
|
1777
|
+
# enough to hide lines but thin enough to not add 'nubs' to corners of boxes.
|
|
1778
|
+
# See: https://github.com/jklymak/contourfIssues
|
|
1779
|
+
# See: https://stackoverflow.com/q/15003353/4970632
|
|
1780
|
+
edgefix = _not_none(edgefix, rc.edgefix, True)
|
|
1781
|
+
linewidth = EDGEWIDTH if edgefix is True else 0 if edgefix is False else edgefix
|
|
1782
|
+
if not linewidth:
|
|
1783
|
+
return
|
|
1784
|
+
keys = ("linewidth", "linestyle", "edgecolor") # patches and collections
|
|
1785
|
+
if any(key + suffix in kwargs for key in keys for suffix in ("", "s")):
|
|
1786
|
+
return
|
|
1787
|
+
rasterized = obj.get_rasterized() if isinstance(obj, martist.Artist) else False
|
|
1788
|
+
if rasterized:
|
|
1789
|
+
return
|
|
1790
|
+
|
|
1791
|
+
# Skip when cmap has transparency
|
|
1792
|
+
if hasattr(obj, "get_alpha"): # collections and contour sets use singular
|
|
1793
|
+
alpha = obj.get_alpha()
|
|
1794
|
+
if alpha is not None and alpha < 1:
|
|
1795
|
+
return
|
|
1796
|
+
if isinstance(obj, mcm.ScalarMappable):
|
|
1797
|
+
cmap = obj.cmap
|
|
1798
|
+
if not cmap._isinit:
|
|
1799
|
+
cmap._init()
|
|
1800
|
+
if not all(cmap._lut[:-1, 3] == 1): # skip for cmaps with transparency
|
|
1801
|
+
return
|
|
1802
|
+
|
|
1803
|
+
# Apply fixes
|
|
1804
|
+
# NOTE: This also covers TriContourSet returned by tricontour
|
|
1805
|
+
if isinstance(obj, mcontour.ContourSet):
|
|
1806
|
+
if obj.filled:
|
|
1807
|
+
obj.set_linestyle("-")
|
|
1808
|
+
obj.set_linewidth(linewidth)
|
|
1809
|
+
obj.set_edgecolor("face")
|
|
1810
|
+
|
|
1811
|
+
for contour in obj.collections:
|
|
1812
|
+
contour.set_linestyle("-")
|
|
1813
|
+
contour.set_linewidth(linewidth)
|
|
1814
|
+
contour.set_edgecolor("face")
|
|
1815
|
+
elif isinstance(obj, mcollections.Collection): # e.g. QuadMesh, PolyCollection
|
|
1816
|
+
obj.set_linewidth(linewidth)
|
|
1817
|
+
obj.set_edgecolor("face")
|
|
1818
|
+
elif isinstance(obj, mpatches.Patch): # e.g. Rectangle
|
|
1819
|
+
obj.set_linewidth(linewidth)
|
|
1820
|
+
obj.set_edgecolor(obj.get_facecolor())
|
|
1821
|
+
elif np.iterable(obj): # e.g. silent_list of BarContainer
|
|
1822
|
+
for element in obj:
|
|
1823
|
+
PlotAxes._fix_patch_edges(element, edgefix=edgefix)
|
|
1824
|
+
else:
|
|
1825
|
+
warnings._warn_ultraplot(f"Unexpected obj {obj} passed to _fix_patch_edges.")
|
|
1826
|
+
|
|
1827
|
+
@contextlib.contextmanager
|
|
1828
|
+
def _keep_grid_bools(self):
|
|
1829
|
+
"""
|
|
1830
|
+
Preserve the gridline booleans during the operation. This prevents `pcolor`
|
|
1831
|
+
methods from disabling grids (mpl < 3.5) and emitting warnings (mpl >= 3.5).
|
|
1832
|
+
"""
|
|
1833
|
+
# NOTE: Modern matplotlib uses _get_axis_list() but this is only to support
|
|
1834
|
+
# Axes3D which PlotAxes does not subclass. Safe to use xaxis and yaxis.
|
|
1835
|
+
bools = []
|
|
1836
|
+
for axis, which in itertools.product(
|
|
1837
|
+
(self.xaxis, self.yaxis), ("major", "minor")
|
|
1838
|
+
):
|
|
1839
|
+
kw = getattr(axis, f"_{which}_tick_kw", {})
|
|
1840
|
+
bools.append(kw.get("gridOn", None))
|
|
1841
|
+
kw["gridOn"] = False # prevent deprecation warning
|
|
1842
|
+
yield
|
|
1843
|
+
for b, (axis, which) in zip(bools, itertools.product("xy", ("major", "minor"))):
|
|
1844
|
+
if b is not None:
|
|
1845
|
+
self.grid(b, axis=axis, which=which)
|
|
1846
|
+
|
|
1847
|
+
def _inbounds_extent(self, *, inbounds=None, **kwargs):
|
|
1848
|
+
"""
|
|
1849
|
+
Capture the `inbounds` keyword arg and return data limit
|
|
1850
|
+
extents if it is ``True``. Otherwise return ``None``. When
|
|
1851
|
+
``_inbounds_xylim`` gets ``None`` it will silently exit.
|
|
1852
|
+
"""
|
|
1853
|
+
extents = None
|
|
1854
|
+
inbounds = _not_none(inbounds, rc["axes.inbounds"])
|
|
1855
|
+
if inbounds:
|
|
1856
|
+
extents = list(self.dataLim.extents) # ensure modifiable
|
|
1857
|
+
return kwargs, extents
|
|
1858
|
+
|
|
1859
|
+
def _inbounds_vlim(self, x, y, z, *, to_centers=False):
|
|
1860
|
+
"""
|
|
1861
|
+
Restrict the sample data used for automatic `vmin` and `vmax` selection
|
|
1862
|
+
based on the existing x and y axis limits.
|
|
1863
|
+
"""
|
|
1864
|
+
# Get masks
|
|
1865
|
+
# WARNING: Experimental, seems robust but this is not mission-critical so
|
|
1866
|
+
# keep this in a try-except clause for now. However *internally* we should
|
|
1867
|
+
# not reach this block unless everything is an array so raise that error.
|
|
1868
|
+
xmask = ymask = None
|
|
1869
|
+
if self._name != "cartesian":
|
|
1870
|
+
return z # TODO: support geographic projections when input is PlateCarree()
|
|
1871
|
+
if not all(getattr(a, "ndim", None) in (1, 2) for a in (x, y, z)):
|
|
1872
|
+
raise ValueError("Invalid input coordinates. Must be 1D or 2D arrays.")
|
|
1873
|
+
try:
|
|
1874
|
+
# Get centers and masks
|
|
1875
|
+
if to_centers and z.ndim == 2:
|
|
1876
|
+
x, y = inputs._to_centers(x, y, z)
|
|
1877
|
+
if not self.get_autoscalex_on():
|
|
1878
|
+
xlim = self.get_xlim()
|
|
1879
|
+
xmask = (x >= min(xlim)) & (x <= max(xlim))
|
|
1880
|
+
if not self.get_autoscaley_on():
|
|
1881
|
+
ylim = self.get_ylim()
|
|
1882
|
+
ymask = (y >= min(ylim)) & (y <= max(ylim))
|
|
1883
|
+
# Get subsample
|
|
1884
|
+
if xmask is not None and ymask is not None:
|
|
1885
|
+
z = (
|
|
1886
|
+
z[np.ix_(ymask, xmask)]
|
|
1887
|
+
if z.ndim == 2 and xmask.ndim == 1
|
|
1888
|
+
else z[ymask & xmask]
|
|
1889
|
+
) # noqa: E501
|
|
1890
|
+
elif xmask is not None:
|
|
1891
|
+
z = z[:, xmask] if z.ndim == 2 and xmask.ndim == 1 else z[xmask]
|
|
1892
|
+
elif ymask is not None:
|
|
1893
|
+
z = z[ymask, :] if z.ndim == 2 and ymask.ndim == 1 else z[ymask]
|
|
1894
|
+
return z
|
|
1895
|
+
except Exception as err:
|
|
1896
|
+
warnings._warn_ultraplot(
|
|
1897
|
+
"Failed to restrict automatic colormap normalization "
|
|
1898
|
+
f"to in-bounds data only. Error message: {err}"
|
|
1899
|
+
)
|
|
1900
|
+
return z
|
|
1901
|
+
|
|
1902
|
+
def _inbounds_xylim(self, extents, x, y, **kwargs):
|
|
1903
|
+
"""
|
|
1904
|
+
Restrict the `dataLim` to exclude out-of-bounds data when x (y) limits
|
|
1905
|
+
are fixed and we are determining default y (x) limits. This modifies
|
|
1906
|
+
the mutable input `extents` to support iteration over columns.
|
|
1907
|
+
"""
|
|
1908
|
+
# WARNING: This feature is still experimental. But seems obvious. Matplotlib
|
|
1909
|
+
# updates data limits in ad hoc fashion differently for each plotting command
|
|
1910
|
+
# but since ultraplot standardizes inputs we can easily use them for dataLim.
|
|
1911
|
+
if extents is None:
|
|
1912
|
+
return
|
|
1913
|
+
if self._name != "cartesian":
|
|
1914
|
+
return
|
|
1915
|
+
if not x.size or not y.size:
|
|
1916
|
+
return
|
|
1917
|
+
kwargs, vert = _get_vert(**kwargs)
|
|
1918
|
+
if not vert:
|
|
1919
|
+
x, y = y, x
|
|
1920
|
+
trans = self.dataLim
|
|
1921
|
+
autox, autoy = self.get_autoscalex_on(), self.get_autoscaley_on()
|
|
1922
|
+
try:
|
|
1923
|
+
if autoy and not autox and x.shape == y.shape:
|
|
1924
|
+
# Reset the y data limits
|
|
1925
|
+
xmin, xmax = sorted(self.get_xlim())
|
|
1926
|
+
mask = (x >= xmin) & (x <= xmax)
|
|
1927
|
+
ymin, ymax = inputs._safe_range(inputs._safe_mask(mask, y))
|
|
1928
|
+
convert = self.convert_yunits # handle datetime, pint units
|
|
1929
|
+
if ymin is not None:
|
|
1930
|
+
trans.y0 = extents[1] = min(convert(ymin), extents[1])
|
|
1931
|
+
if ymax is not None:
|
|
1932
|
+
trans.y1 = extents[3] = max(convert(ymax), extents[3])
|
|
1933
|
+
getattr(self, "_request_autoscale_view", self.autoscale_view)()
|
|
1934
|
+
if autox and not autoy and y.shape == x.shape:
|
|
1935
|
+
# Reset the x data limits
|
|
1936
|
+
ymin, ymax = sorted(self.get_ylim())
|
|
1937
|
+
mask = (y >= ymin) & (y <= ymax)
|
|
1938
|
+
xmin, xmax = inputs._safe_range(inputs._safe_mask(mask, x))
|
|
1939
|
+
convert = self.convert_xunits # handle datetime, pint units
|
|
1940
|
+
if xmin is not None:
|
|
1941
|
+
trans.x0 = extents[0] = min(convert(xmin), extents[0])
|
|
1942
|
+
if xmax is not None:
|
|
1943
|
+
trans.x1 = extents[2] = max(convert(xmax), extents[2])
|
|
1944
|
+
getattr(self, "_request_autoscale_view", self.autoscale_view)()
|
|
1945
|
+
except Exception as err:
|
|
1946
|
+
warnings._warn_ultraplot(
|
|
1947
|
+
"Failed to restrict automatic y (x) axis limit algorithm to "
|
|
1948
|
+
f"data within locked x (y) limits only. Error message: {err}"
|
|
1949
|
+
)
|
|
1950
|
+
|
|
1951
|
+
def _parse_1d_args(self, x, *ys, **kwargs):
|
|
1952
|
+
"""
|
|
1953
|
+
Interpret positional arguments for all 1D plotting commands.
|
|
1954
|
+
"""
|
|
1955
|
+
# Standardize values
|
|
1956
|
+
zerox = not ys
|
|
1957
|
+
if zerox or all(y is None for y in ys): # pad with remaining Nones
|
|
1958
|
+
x, *ys = None, x, *ys[1:]
|
|
1959
|
+
if len(ys) == 2: # 'lines' or 'fill_between'
|
|
1960
|
+
if ys[1] is None:
|
|
1961
|
+
ys = (np.array([0.0]), ys[0]) # user input 1 or 2 positional args
|
|
1962
|
+
elif ys[0] is None:
|
|
1963
|
+
ys = (np.array([0.0]), ys[1]) # user input keyword 'y2' but no y1
|
|
1964
|
+
if any(y is None for y in ys):
|
|
1965
|
+
raise ValueError("Missing required data array argument.")
|
|
1966
|
+
ys = tuple(map(inputs._to_duck_array, ys))
|
|
1967
|
+
if x is not None:
|
|
1968
|
+
x = inputs._to_duck_array(x)
|
|
1969
|
+
x, *ys, kwargs = self._parse_1d_format(x, *ys, zerox=zerox, **kwargs)
|
|
1970
|
+
|
|
1971
|
+
# Geographic corrections
|
|
1972
|
+
if self._name == "cartopy" and isinstance(
|
|
1973
|
+
kwargs.get("transform"), PlateCarree
|
|
1974
|
+
): # noqa: E501
|
|
1975
|
+
x, *ys = inputs._geo_cartopy_1d(x, *ys)
|
|
1976
|
+
elif self._name == "basemap" and kwargs.get("latlon", None):
|
|
1977
|
+
xmin, xmax = self._lonaxis.get_view_interval()
|
|
1978
|
+
x, *ys = inputs._geo_basemap_1d(x, *ys, xmin=xmin, xmax=xmax)
|
|
1979
|
+
|
|
1980
|
+
return (x, *ys, kwargs)
|
|
1981
|
+
|
|
1982
|
+
def _parse_1d_format(
|
|
1983
|
+
self,
|
|
1984
|
+
x,
|
|
1985
|
+
*ys,
|
|
1986
|
+
zerox=False,
|
|
1987
|
+
autox=True,
|
|
1988
|
+
autoy=True,
|
|
1989
|
+
autoformat=None,
|
|
1990
|
+
autoreverse=True,
|
|
1991
|
+
autolabels=True,
|
|
1992
|
+
autovalues=False,
|
|
1993
|
+
autoguide=True,
|
|
1994
|
+
label=None,
|
|
1995
|
+
labels=None,
|
|
1996
|
+
value=None,
|
|
1997
|
+
values=None,
|
|
1998
|
+
**kwargs,
|
|
1999
|
+
):
|
|
2000
|
+
"""
|
|
2001
|
+
Try to retrieve default coordinates from array-like objects and apply default
|
|
2002
|
+
formatting. Also update the keyword arguments.
|
|
2003
|
+
"""
|
|
2004
|
+
# Parse input
|
|
2005
|
+
y = max(ys, key=lambda y: y.size) # find a non-scalar y for inferring metadata
|
|
2006
|
+
autox = autox and not zerox # so far just relevant for hist()
|
|
2007
|
+
autoformat = _not_none(autoformat, rc["autoformat"])
|
|
2008
|
+
kwargs, vert = _get_vert(**kwargs)
|
|
2009
|
+
labels = _not_none(
|
|
2010
|
+
label=label,
|
|
2011
|
+
labels=labels,
|
|
2012
|
+
value=value,
|
|
2013
|
+
values=values,
|
|
2014
|
+
legend_kw_labels=kwargs.get("legend_kw", {}).pop("labels", None),
|
|
2015
|
+
colorbar_kw_values=kwargs.get("colorbar_kw", {}).pop("values", None),
|
|
2016
|
+
)
|
|
2017
|
+
|
|
2018
|
+
# Retrieve the x coords
|
|
2019
|
+
# NOTE: Where columns represent distributions, like for box and violinplot or
|
|
2020
|
+
# where we use 'means' or 'medians', columns coords (axis 1) are 'x' coords.
|
|
2021
|
+
# Otherwise, columns represent e.g. lines and row coords (axis 0) are 'x'
|
|
2022
|
+
# coords. Exception is passing "ragged arrays" to boxplot and violinplot.
|
|
2023
|
+
dists = any(kwargs.get(s) for s in ("mean", "means", "median", "medians"))
|
|
2024
|
+
raggd = any(getattr(y, "dtype", None) == "object" for y in ys)
|
|
2025
|
+
xaxis = 0 if raggd else 1 if dists or not autoy else 0
|
|
2026
|
+
if autox and x is None:
|
|
2027
|
+
x = inputs._meta_labels(y, axis=xaxis) # use the first one
|
|
2028
|
+
|
|
2029
|
+
# Retrieve the labels. We only want default legend labels if this is an
|
|
2030
|
+
# object with 'title' metadata and/or the coords are string.
|
|
2031
|
+
# WARNING: Confusing terminology differences here -- for box and violin plots
|
|
2032
|
+
# labels refer to indices along x axis.
|
|
2033
|
+
if autolabels and labels is None:
|
|
2034
|
+
laxis = 0 if not autox and not autoy else xaxis if not autoy else xaxis + 1
|
|
2035
|
+
if laxis >= y.ndim:
|
|
2036
|
+
labels = inputs._meta_title(y)
|
|
2037
|
+
else:
|
|
2038
|
+
labels = inputs._meta_labels(y, axis=laxis, always=False)
|
|
2039
|
+
notitle = not inputs._meta_title(labels)
|
|
2040
|
+
if labels is None:
|
|
2041
|
+
pass
|
|
2042
|
+
elif notitle and not any(isinstance(_, str) for _ in labels):
|
|
2043
|
+
labels = None
|
|
2044
|
+
|
|
2045
|
+
# Apply the labels or values
|
|
2046
|
+
if labels is not None:
|
|
2047
|
+
if autovalues:
|
|
2048
|
+
kwargs["values"] = inputs._to_numpy_array(labels)
|
|
2049
|
+
elif autolabels:
|
|
2050
|
+
kwargs["labels"] = inputs._to_numpy_array(labels)
|
|
2051
|
+
|
|
2052
|
+
# Apply title for legend or colorbar that uses the labels or values
|
|
2053
|
+
if autoguide and autoformat:
|
|
2054
|
+
title = inputs._meta_title(labels)
|
|
2055
|
+
if title: # safely update legend_kw and colorbar_kw
|
|
2056
|
+
guides._add_guide_kw("legend", kwargs, title=title)
|
|
2057
|
+
guides._add_guide_kw("colorbar", kwargs, title=title)
|
|
2058
|
+
|
|
2059
|
+
# Apply the basic x and y settings
|
|
2060
|
+
autox = autox and self._name == "cartesian"
|
|
2061
|
+
autoy = autoy and self._name == "cartesian"
|
|
2062
|
+
sx, sy = "xy" if vert else "yx"
|
|
2063
|
+
kw_format = {}
|
|
2064
|
+
if autox and autoformat: # 'x' axis
|
|
2065
|
+
title = inputs._meta_title(x)
|
|
2066
|
+
if title:
|
|
2067
|
+
axis = getattr(self, sx + "axis")
|
|
2068
|
+
if axis.isDefault_label:
|
|
2069
|
+
kw_format[sx + "label"] = title
|
|
2070
|
+
if autoy and autoformat: # 'y' axis
|
|
2071
|
+
sy = sx if zerox else sy # hist() 'y' values are along 'x' axis
|
|
2072
|
+
title = inputs._meta_title(y)
|
|
2073
|
+
if title:
|
|
2074
|
+
axis = getattr(self, sy + "axis")
|
|
2075
|
+
if axis.isDefault_label:
|
|
2076
|
+
kw_format[sy + "label"] = title
|
|
2077
|
+
|
|
2078
|
+
# Convert string-type coordinates to indices
|
|
2079
|
+
# NOTE: This should even allow qualitative string input to hist()
|
|
2080
|
+
if autox:
|
|
2081
|
+
x, kw_format = inputs._meta_coords(x, which=sx, **kw_format)
|
|
2082
|
+
if autoy:
|
|
2083
|
+
*ys, kw_format = inputs._meta_coords(*ys, which=sy, **kw_format)
|
|
2084
|
+
if autox and autoreverse and inputs._is_descending(x):
|
|
2085
|
+
if getattr(self, f"get_autoscale{sx}_on")():
|
|
2086
|
+
kw_format[sx + "reverse"] = True
|
|
2087
|
+
|
|
2088
|
+
# Finally apply formatting and strip metadata
|
|
2089
|
+
# WARNING: Most methods that accept 2D arrays use columns of data, but when
|
|
2090
|
+
# pandas DataFrame specifically is passed to hist, boxplot, or violinplot, rows
|
|
2091
|
+
# of data assumed! Converting to ndarray necessary.
|
|
2092
|
+
if kw_format:
|
|
2093
|
+
self.format(**kw_format)
|
|
2094
|
+
ys = tuple(map(inputs._to_numpy_array, ys))
|
|
2095
|
+
if x is not None: # pie() and hist()
|
|
2096
|
+
x = inputs._to_numpy_array(x)
|
|
2097
|
+
return (x, *ys, kwargs)
|
|
2098
|
+
|
|
2099
|
+
def _parse_2d_args(
|
|
2100
|
+
self,
|
|
2101
|
+
x,
|
|
2102
|
+
y,
|
|
2103
|
+
*zs,
|
|
2104
|
+
globe=False,
|
|
2105
|
+
edges=False,
|
|
2106
|
+
allow1d=False,
|
|
2107
|
+
transpose=None,
|
|
2108
|
+
order=None,
|
|
2109
|
+
**kwargs,
|
|
2110
|
+
):
|
|
2111
|
+
"""
|
|
2112
|
+
Interpret positional arguments for all 2D plotting commands.
|
|
2113
|
+
"""
|
|
2114
|
+
# Standardize values
|
|
2115
|
+
# NOTE: Functions pass two 'zs' at most right now
|
|
2116
|
+
if all(z is None for z in zs):
|
|
2117
|
+
x, y, zs = None, None, (x, y)[: len(zs)]
|
|
2118
|
+
if any(z is None for z in zs):
|
|
2119
|
+
raise ValueError("Missing required data array argument(s).")
|
|
2120
|
+
zs = tuple(inputs._to_duck_array(z, strip_units=True) for z in zs)
|
|
2121
|
+
if x is not None:
|
|
2122
|
+
x = inputs._to_duck_array(x)
|
|
2123
|
+
if y is not None:
|
|
2124
|
+
y = inputs._to_duck_array(y)
|
|
2125
|
+
if order is not None:
|
|
2126
|
+
if not isinstance(order, str) or order not in "CF":
|
|
2127
|
+
raise ValueError(f"Invalid order={order!r}. Options are 'C' or 'F'.")
|
|
2128
|
+
transpose = _not_none(
|
|
2129
|
+
transpose=transpose, transpose_order=bool("CF".index(order))
|
|
2130
|
+
)
|
|
2131
|
+
if transpose:
|
|
2132
|
+
zs = tuple(z.T for z in zs)
|
|
2133
|
+
if x is not None:
|
|
2134
|
+
x = x.T
|
|
2135
|
+
if y is not None:
|
|
2136
|
+
y = y.T
|
|
2137
|
+
x, y, *zs, kwargs = self._parse_2d_format(x, y, *zs, **kwargs)
|
|
2138
|
+
if edges:
|
|
2139
|
+
# NOTE: These functions quitely pass through 1D inputs, e.g. barb data
|
|
2140
|
+
x, y = inputs._to_edges(x, y, zs[0])
|
|
2141
|
+
else:
|
|
2142
|
+
x, y = inputs._to_centers(x, y, zs[0])
|
|
2143
|
+
|
|
2144
|
+
# Geographic corrections
|
|
2145
|
+
if allow1d:
|
|
2146
|
+
pass
|
|
2147
|
+
elif self._name == "cartopy" and isinstance(
|
|
2148
|
+
kwargs.get("transform"), PlateCarree
|
|
2149
|
+
): # noqa: E501
|
|
2150
|
+
x, y, *zs = inputs._geo_cartopy_2d(x, y, *zs, globe=globe)
|
|
2151
|
+
elif self._name == "basemap" and kwargs.get("latlon", None):
|
|
2152
|
+
xmin, xmax = self._lonaxis.get_view_interval()
|
|
2153
|
+
x, y, *zs = inputs._geo_basemap_2d(
|
|
2154
|
+
x, y, *zs, xmin=xmin, xmax=xmax, globe=globe
|
|
2155
|
+
) # noqa: E501
|
|
2156
|
+
x, y = np.meshgrid(x, y) # WARNING: required always
|
|
2157
|
+
|
|
2158
|
+
return (x, y, *zs, kwargs)
|
|
2159
|
+
|
|
2160
|
+
def _parse_2d_format(
|
|
2161
|
+
self, x, y, *zs, autoformat=None, autoguide=True, autoreverse=True, **kwargs
|
|
2162
|
+
):
|
|
2163
|
+
"""
|
|
2164
|
+
Try to retrieve default coordinates from array-like objects and apply default
|
|
2165
|
+
formatting. Also apply optional transpose and update the keyword arguments.
|
|
2166
|
+
"""
|
|
2167
|
+
# Retrieve coordinates
|
|
2168
|
+
autoformat = _not_none(autoformat, rc["autoformat"])
|
|
2169
|
+
if x is None and y is None:
|
|
2170
|
+
z = zs[0]
|
|
2171
|
+
if z.ndim == 1:
|
|
2172
|
+
x = inputs._meta_labels(z, axis=0)
|
|
2173
|
+
y = np.zeros(z.shape) # default barb() and quiver() behavior in mpl
|
|
2174
|
+
else:
|
|
2175
|
+
x = inputs._meta_labels(z, axis=1)
|
|
2176
|
+
y = inputs._meta_labels(z, axis=0)
|
|
2177
|
+
|
|
2178
|
+
# Apply labels and XY axis settings
|
|
2179
|
+
if self._name == "cartesian":
|
|
2180
|
+
# Apply labels
|
|
2181
|
+
# NOTE: Do not overwrite existing labels!
|
|
2182
|
+
kw_format = {}
|
|
2183
|
+
if autoformat:
|
|
2184
|
+
for s, d in zip("xy", (x, y)):
|
|
2185
|
+
title = inputs._meta_title(d)
|
|
2186
|
+
if title:
|
|
2187
|
+
axis = getattr(self, s + "axis")
|
|
2188
|
+
if axis.isDefault_label:
|
|
2189
|
+
kw_format[s + "label"] = title
|
|
2190
|
+
|
|
2191
|
+
# Handle string-type coordinates
|
|
2192
|
+
x, kw_format = inputs._meta_coords(x, which="x", **kw_format)
|
|
2193
|
+
y, kw_format = inputs._meta_coords(y, which="y", **kw_format)
|
|
2194
|
+
for s, d in zip("xy", (x, y)):
|
|
2195
|
+
if autoreverse and inputs._is_descending(d):
|
|
2196
|
+
if getattr(self, f"get_autoscale{s}_on")():
|
|
2197
|
+
kw_format[s + "reverse"] = True
|
|
2198
|
+
|
|
2199
|
+
# Apply formatting
|
|
2200
|
+
if kw_format:
|
|
2201
|
+
self.format(**kw_format)
|
|
2202
|
+
|
|
2203
|
+
# Apply title for legend or colorbar
|
|
2204
|
+
if autoguide and autoformat:
|
|
2205
|
+
title = inputs._meta_title(zs[0])
|
|
2206
|
+
if title: # safely update legend_kw and colorbar_kw
|
|
2207
|
+
guides._add_guide_kw("legend", kwargs, title=title)
|
|
2208
|
+
guides._add_guide_kw("colorbar", kwargs, title=title)
|
|
2209
|
+
|
|
2210
|
+
# Finally strip metadata
|
|
2211
|
+
x = inputs._to_numpy_array(x)
|
|
2212
|
+
y = inputs._to_numpy_array(y)
|
|
2213
|
+
zs = tuple(map(inputs._to_numpy_array, zs))
|
|
2214
|
+
return (x, y, *zs, kwargs)
|
|
2215
|
+
|
|
2216
|
+
def _parse_color(self, x, y, c, *, apply_cycle=True, infer_rgb=False, **kwargs):
|
|
2217
|
+
"""
|
|
2218
|
+
Parse either a colormap or color cycler. Colormap will be discrete and fade
|
|
2219
|
+
to subwhite luminance by default. Returns a HEX string if needed so we don't
|
|
2220
|
+
get ambiguous color warnings. Used with scatter, streamplot, quiver, barbs.
|
|
2221
|
+
"""
|
|
2222
|
+
# NOTE: This function is positioned above the _parse_cmap and _parse_cycle
|
|
2223
|
+
# functions and helper functions.
|
|
2224
|
+
parsers = (self._parse_cmap, *self._level_parsers)
|
|
2225
|
+
if c is None or mcolors.is_color_like(c):
|
|
2226
|
+
if infer_rgb and c is not None:
|
|
2227
|
+
c = pcolors.to_hex(c) # avoid scatter() ambiguous color warning
|
|
2228
|
+
if apply_cycle: # False for scatter() so we can wait to get correct 'N'
|
|
2229
|
+
kwargs = self._parse_cycle(**kwargs)
|
|
2230
|
+
else:
|
|
2231
|
+
c = np.atleast_1d(c) # should only have effect on 'scatter' input
|
|
2232
|
+
if infer_rgb and (
|
|
2233
|
+
inputs._is_categorical(c) or c.ndim == 2 and c.shape[1] in (3, 4)
|
|
2234
|
+
): # noqa: E501
|
|
2235
|
+
c = list(map(pcolors.to_hex, c)) # avoid iterating over columns
|
|
2236
|
+
else:
|
|
2237
|
+
kwargs = self._parse_cmap(
|
|
2238
|
+
x, y, c, plot_lines=True, default_discrete=False, **kwargs
|
|
2239
|
+
) # noqa: E501
|
|
2240
|
+
parsers = (self._parse_cycle,)
|
|
2241
|
+
pop = _pop_params(kwargs, *parsers, ignore_internal=True)
|
|
2242
|
+
if pop:
|
|
2243
|
+
warnings._warn_ultraplot(f"Ignoring unused keyword arg(s): {pop}")
|
|
2244
|
+
return (c, kwargs)
|
|
2245
|
+
|
|
2246
|
+
@warnings._rename_kwargs("0.6.0", centers="values")
|
|
2247
|
+
def _parse_cmap(
|
|
2248
|
+
self,
|
|
2249
|
+
*args,
|
|
2250
|
+
cmap=None,
|
|
2251
|
+
cmap_kw=None,
|
|
2252
|
+
c=None,
|
|
2253
|
+
color=None,
|
|
2254
|
+
colors=None,
|
|
2255
|
+
norm=None,
|
|
2256
|
+
norm_kw=None,
|
|
2257
|
+
extend=None,
|
|
2258
|
+
vmin=None,
|
|
2259
|
+
vmax=None,
|
|
2260
|
+
discrete=None,
|
|
2261
|
+
default_cmap=None,
|
|
2262
|
+
default_discrete=True,
|
|
2263
|
+
skip_autolev=False,
|
|
2264
|
+
min_levels=None,
|
|
2265
|
+
plot_lines=False,
|
|
2266
|
+
plot_contours=False,
|
|
2267
|
+
**kwargs,
|
|
2268
|
+
):
|
|
2269
|
+
"""
|
|
2270
|
+
Parse colormap and normalizer arguments.
|
|
2271
|
+
|
|
2272
|
+
Parameters
|
|
2273
|
+
----------
|
|
2274
|
+
c, color, colors : sequence of color-spec, optional
|
|
2275
|
+
Build a `DiscreteColormap` from the input color(s).
|
|
2276
|
+
cmap, cmap_kw : optional
|
|
2277
|
+
Colormap specs.
|
|
2278
|
+
norm, norm_kw : optional
|
|
2279
|
+
Normalize specs.
|
|
2280
|
+
extend : optional
|
|
2281
|
+
The colormap extend setting.
|
|
2282
|
+
vmin, vmax : flaot, optional
|
|
2283
|
+
The normalization range.
|
|
2284
|
+
sequential, diverging, cyclic, qualitative : bool, optional
|
|
2285
|
+
Toggle various colormap types.
|
|
2286
|
+
discrete : bool, optional
|
|
2287
|
+
Whether to apply `DiscreteNorm` to the colormap.
|
|
2288
|
+
default_discrete : bool, optional
|
|
2289
|
+
The default `discrete`. Depends on plotting method.
|
|
2290
|
+
skip_autolev : bool, optional
|
|
2291
|
+
Whether to skip automatic level generation.
|
|
2292
|
+
min_levels : int, optional
|
|
2293
|
+
The minimum number of valid levels. 1 for line contour plots 2 otherwise.
|
|
2294
|
+
plot_lines : bool, optional
|
|
2295
|
+
Whether these are lines. If so the default monochromatic luminance is 90.
|
|
2296
|
+
plot_contours : bool, optional
|
|
2297
|
+
Whether these are contours. If so then a discrete of `True` is required.
|
|
2298
|
+
"""
|
|
2299
|
+
# Parse keyword args
|
|
2300
|
+
cmap_kw = cmap_kw or {}
|
|
2301
|
+
norm_kw = norm_kw or {}
|
|
2302
|
+
vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop("vmin", None))
|
|
2303
|
+
vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop("vmax", None))
|
|
2304
|
+
extend = _not_none(extend, "neither")
|
|
2305
|
+
colors = _not_none(c=c, color=color, colors=colors) # in case untranslated
|
|
2306
|
+
modes = {
|
|
2307
|
+
key: kwargs.pop(key, None)
|
|
2308
|
+
for key in ("sequential", "diverging", "cyclic", "qualitative")
|
|
2309
|
+
} # noqa: E501
|
|
2310
|
+
trues = {key: b for key, b in modes.items() if b}
|
|
2311
|
+
if len(trues) > 1: # noqa: E501
|
|
2312
|
+
warnings._warn_ultraplot(
|
|
2313
|
+
f"Conflicting colormap arguments: {trues!r}. Using the first one."
|
|
2314
|
+
)
|
|
2315
|
+
for key in tuple(trues)[1:]:
|
|
2316
|
+
del trues[key]
|
|
2317
|
+
modes[key] = None
|
|
2318
|
+
|
|
2319
|
+
# Create user-input colormap and potentially disable autodiverging
|
|
2320
|
+
# NOTE: Let people use diverging=False with diverging cmaps because some
|
|
2321
|
+
# use them (wrongly IMO but to each their own) for increased color contrast.
|
|
2322
|
+
# WARNING: Previously 'colors' set the edgecolors. To avoid all-black
|
|
2323
|
+
# colormap make sure to ignore 'colors' if 'cmap' was also passed.
|
|
2324
|
+
# WARNING: Previously tried setting number of levels to len(colors), but this
|
|
2325
|
+
# makes single-level single-color contour plots, and since _parse_level_num is
|
|
2326
|
+
# only generates approximate level counts, the idea failed anyway. Users should
|
|
2327
|
+
# pass their own levels to avoid truncation/cycling in these very special cases.
|
|
2328
|
+
autodiverging = rc["cmap.autodiverging"]
|
|
2329
|
+
if colors is not None:
|
|
2330
|
+
if cmap is not None:
|
|
2331
|
+
warnings._warn_ultraplot(
|
|
2332
|
+
f"you specified both cmap={cmap!s} and the qualitative-colormap "
|
|
2333
|
+
f"colors={colors!r}. Ignoring 'colors'. If you meant to specify "
|
|
2334
|
+
f"the edge color please use e.g. edgecolor={colors!r} instead."
|
|
2335
|
+
)
|
|
2336
|
+
else:
|
|
2337
|
+
if mcolors.is_color_like(colors):
|
|
2338
|
+
colors = [colors] # RGB[A] tuple possibly
|
|
2339
|
+
cmap = colors = np.atleast_1d(colors)
|
|
2340
|
+
cmap_kw["listmode"] = "discrete"
|
|
2341
|
+
if cmap is not None:
|
|
2342
|
+
if plot_lines:
|
|
2343
|
+
cmap_kw["default_luminance"] = constructor.DEFAULT_CYCLE_LUMINANCE
|
|
2344
|
+
cmap = constructor.Colormap(cmap, **cmap_kw)
|
|
2345
|
+
name = re.sub(r"\A_*(.*?)(?:_r|_s|_copy)*\Z", r"\1", cmap.name.lower())
|
|
2346
|
+
if not any(name in opts for opts in pcolors.CMAPS_DIVERGING.items()):
|
|
2347
|
+
autodiverging = False # avoid auto-truncation of sequential colormaps
|
|
2348
|
+
|
|
2349
|
+
# Force default options in special cases
|
|
2350
|
+
# NOTE: Delay application of 'sequential', 'diverging', 'cyclic', 'qualitative'
|
|
2351
|
+
# until after level generation so 'diverging' can be automatically applied.
|
|
2352
|
+
if "cyclic" in trues or getattr(cmap, "_cyclic", None):
|
|
2353
|
+
if extend is not None and extend != "neither":
|
|
2354
|
+
warnings._warn_ultraplot(
|
|
2355
|
+
f"Cyclic colormaps require extend='neither'. Ignoring extend={extend!r}" # noqa: E501
|
|
2356
|
+
)
|
|
2357
|
+
extend = "neither"
|
|
2358
|
+
if "qualitative" in trues or isinstance(cmap, pcolors.DiscreteColormap):
|
|
2359
|
+
if discrete is not None and not discrete: # noqa: E501
|
|
2360
|
+
warnings._warn_ultraplot(
|
|
2361
|
+
"Qualitative colormaps require discrete=True. Ignoring discrete=False." # noqa: E501
|
|
2362
|
+
)
|
|
2363
|
+
discrete = True
|
|
2364
|
+
if plot_contours:
|
|
2365
|
+
if discrete is not None and not discrete:
|
|
2366
|
+
warnings._warn_ultraplot(
|
|
2367
|
+
"Contoured plots require discrete=True. Ignoring discrete=False."
|
|
2368
|
+
)
|
|
2369
|
+
discrete = True
|
|
2370
|
+
keys = ("levels", "values", "locator", "negative", "positive", "symmetric")
|
|
2371
|
+
if any(key in kwargs for key in keys): # override
|
|
2372
|
+
discrete = _not_none(discrete, True)
|
|
2373
|
+
else: # use global boolean rc['cmap.discrete'] or command-specific default
|
|
2374
|
+
discrete = _not_none(discrete, rc["cmap.discrete"], default_discrete)
|
|
2375
|
+
|
|
2376
|
+
# Determine the appropriate 'vmin', 'vmax', and/or 'levels'
|
|
2377
|
+
# NOTE: Unlike xarray, but like matplotlib, vmin and vmax only approximately
|
|
2378
|
+
# determine level range. Levels are selected with Locator.tick_values().
|
|
2379
|
+
levels = None # unused
|
|
2380
|
+
isdiverging = False
|
|
2381
|
+
if not discrete and not skip_autolev:
|
|
2382
|
+
vmin, vmax, kwargs = self._parse_level_lim(
|
|
2383
|
+
*args, vmin=vmin, vmax=vmax, **kwargs
|
|
2384
|
+
)
|
|
2385
|
+
if autodiverging and vmin is not None and vmax is not None:
|
|
2386
|
+
if abs(np.sign(vmax) - np.sign(vmin)) == 2:
|
|
2387
|
+
isdiverging = True
|
|
2388
|
+
if discrete:
|
|
2389
|
+
levels, vmin, vmax, norm, norm_kw, kwargs = self._parse_level_vals(
|
|
2390
|
+
*args,
|
|
2391
|
+
vmin=vmin,
|
|
2392
|
+
vmax=vmax,
|
|
2393
|
+
norm=norm,
|
|
2394
|
+
norm_kw=norm_kw,
|
|
2395
|
+
extend=extend,
|
|
2396
|
+
min_levels=min_levels,
|
|
2397
|
+
skip_autolev=skip_autolev,
|
|
2398
|
+
**kwargs,
|
|
2399
|
+
)
|
|
2400
|
+
if autodiverging and levels is not None:
|
|
2401
|
+
_, counts = np.unique(np.sign(levels), return_counts=True)
|
|
2402
|
+
if counts[counts > 1].size > 1:
|
|
2403
|
+
isdiverging = True
|
|
2404
|
+
if not trues and isdiverging and modes["diverging"] is None:
|
|
2405
|
+
trues["diverging"] = modes["diverging"] = True
|
|
2406
|
+
|
|
2407
|
+
# Create the continuous normalizer.
|
|
2408
|
+
norm = _not_none(norm, "div" if "diverging" in trues else "linear")
|
|
2409
|
+
if isinstance(norm, mcolors.Normalize):
|
|
2410
|
+
norm.vmin, norm.vmax = vmin, vmax
|
|
2411
|
+
else:
|
|
2412
|
+
norm = constructor.Norm(norm, vmin=vmin, vmax=vmax, **norm_kw)
|
|
2413
|
+
isdiverging = autodiverging and isinstance(norm, pcolors.DivergingNorm)
|
|
2414
|
+
if not trues and isdiverging and modes["diverging"] is None:
|
|
2415
|
+
trues["diverging"] = modes["diverging"] = True
|
|
2416
|
+
|
|
2417
|
+
# Create the final colormap
|
|
2418
|
+
if cmap is None:
|
|
2419
|
+
if default_cmap is not None: # used internally
|
|
2420
|
+
cmap = default_cmap
|
|
2421
|
+
elif trues:
|
|
2422
|
+
cmap = rc["cmap." + tuple(trues)[0]]
|
|
2423
|
+
else:
|
|
2424
|
+
cmap = rc["image.cmap"]
|
|
2425
|
+
cmap = constructor.Colormap(cmap, **cmap_kw)
|
|
2426
|
+
|
|
2427
|
+
# Create the discrete normalizer
|
|
2428
|
+
# Then finally warn and remove unused args
|
|
2429
|
+
if levels is not None:
|
|
2430
|
+
norm, cmap, kwargs = self._parse_level_norm(
|
|
2431
|
+
levels, norm, cmap, extend=extend, min_levels=min_levels, **kwargs
|
|
2432
|
+
)
|
|
2433
|
+
params = _pop_params(kwargs, *self._level_parsers, ignore_internal=True)
|
|
2434
|
+
if "N" in params: # use this for lookup table N instead of levels N
|
|
2435
|
+
cmap = cmap.copy(N=params.pop("N"))
|
|
2436
|
+
if params:
|
|
2437
|
+
warnings._warn_ultraplot(f"Ignoring unused keyword args(s): {params}")
|
|
2438
|
+
|
|
2439
|
+
# Update outgoing args
|
|
2440
|
+
# NOTE: ContourSet natively stores 'extend' on the result but for other
|
|
2441
|
+
# classes we need to hide it on the object.
|
|
2442
|
+
kwargs.update({"cmap": cmap, "norm": norm})
|
|
2443
|
+
if plot_contours:
|
|
2444
|
+
kwargs.update({"levels": levels, "extend": extend})
|
|
2445
|
+
else:
|
|
2446
|
+
guides._add_guide_kw("colorbar", kwargs, extend=extend)
|
|
2447
|
+
|
|
2448
|
+
return kwargs
|
|
2449
|
+
|
|
2450
|
+
def _parse_cycle(
|
|
2451
|
+
self,
|
|
2452
|
+
ncycle=None,
|
|
2453
|
+
*,
|
|
2454
|
+
cycle=None,
|
|
2455
|
+
cycle_kw=None,
|
|
2456
|
+
cycle_manually=None,
|
|
2457
|
+
return_cycle=False,
|
|
2458
|
+
**kwargs,
|
|
2459
|
+
):
|
|
2460
|
+
"""
|
|
2461
|
+
Parse property cycle-related arguments.
|
|
2462
|
+
|
|
2463
|
+
Parameters
|
|
2464
|
+
----------
|
|
2465
|
+
ncycle : int, optional
|
|
2466
|
+
The number of samples to draw for the cycle.
|
|
2467
|
+
cycle : cycle-spec, optional
|
|
2468
|
+
The property cycle specifier.
|
|
2469
|
+
cycle_kw : dict-like, optional
|
|
2470
|
+
The property cycle keyword arguments
|
|
2471
|
+
cycle_manually : dict-like, optional
|
|
2472
|
+
Mapping of property cycle keys to plotting function keys. Used
|
|
2473
|
+
to translate property cycle line properties to scatter properties.
|
|
2474
|
+
return_cycle : bool, optional
|
|
2475
|
+
Whether to simply return the property cycle or apply it. The cycle is
|
|
2476
|
+
only applied (and therefore reset) if it differs from the current one.
|
|
2477
|
+
"""
|
|
2478
|
+
# Create the property cycler and update it if necessary
|
|
2479
|
+
# NOTE: Matplotlib Cycler() objects have built-in __eq__ operator
|
|
2480
|
+
# so really easy to check if the cycler has changed!
|
|
2481
|
+
if cycle is not None or cycle_kw:
|
|
2482
|
+
cycle_kw = cycle_kw or {}
|
|
2483
|
+
if ncycle != 1: # ignore for column-by-column plotting commands
|
|
2484
|
+
cycle_kw.setdefault("N", ncycle) # if None then filled in Colormap()
|
|
2485
|
+
if isinstance(cycle, str) and cycle.lower() == "none":
|
|
2486
|
+
cycle = False
|
|
2487
|
+
if not cycle:
|
|
2488
|
+
args = ()
|
|
2489
|
+
elif cycle is True: # consistency with 'False' ('reactivate' the cycler)
|
|
2490
|
+
args = (rc["axes.prop_cycle"],)
|
|
2491
|
+
else:
|
|
2492
|
+
args = (cycle,)
|
|
2493
|
+
cycle = constructor.Cycle(*args, **cycle_kw)
|
|
2494
|
+
with warnings.catch_warnings(): # hide 'elementwise-comparison failed'
|
|
2495
|
+
warnings.simplefilter("ignore", FutureWarning)
|
|
2496
|
+
if return_cycle:
|
|
2497
|
+
pass
|
|
2498
|
+
elif cycle != self._active_cycle:
|
|
2499
|
+
self.set_prop_cycle(cycle)
|
|
2500
|
+
|
|
2501
|
+
# Manually extract and apply settings to outgoing keyword arguments
|
|
2502
|
+
# if native matplotlib function does not include desired properties
|
|
2503
|
+
cycle_manually = cycle_manually or {}
|
|
2504
|
+
parser = self._get_lines # the _process_plot_var_args instance
|
|
2505
|
+
props = {} # which keys to apply from property cycler
|
|
2506
|
+
# BREAKING in mpl3.9.1 parse has cycle items and no longer posseses _prop_keys
|
|
2507
|
+
for prop, key in cycle_manually.items():
|
|
2508
|
+
if kwargs.get(key, None) is None and any(
|
|
2509
|
+
prop in item for item in parser._cycler_items
|
|
2510
|
+
):
|
|
2511
|
+
props[prop] = key
|
|
2512
|
+
if props:
|
|
2513
|
+
for dict_ in parser._cycler_items:
|
|
2514
|
+
for prop, key in props.items():
|
|
2515
|
+
value = dict_[prop]
|
|
2516
|
+
if (
|
|
2517
|
+
key == "c"
|
|
2518
|
+
): # special case: scatter() color must be converted to hex
|
|
2519
|
+
value = pcolors.to_hex(value)
|
|
2520
|
+
kwargs[key] = value
|
|
2521
|
+
|
|
2522
|
+
if return_cycle:
|
|
2523
|
+
return cycle, kwargs # needed for stem() to apply in a context()
|
|
2524
|
+
else:
|
|
2525
|
+
return kwargs
|
|
2526
|
+
|
|
2527
|
+
def _parse_level_lim(
|
|
2528
|
+
self,
|
|
2529
|
+
*args,
|
|
2530
|
+
vmin=None,
|
|
2531
|
+
vmax=None,
|
|
2532
|
+
robust=None,
|
|
2533
|
+
inbounds=None,
|
|
2534
|
+
negative=None,
|
|
2535
|
+
positive=None,
|
|
2536
|
+
symmetric=None,
|
|
2537
|
+
to_centers=False,
|
|
2538
|
+
**kwargs,
|
|
2539
|
+
):
|
|
2540
|
+
"""
|
|
2541
|
+
Return a suitable vmin and vmax based on the input data.
|
|
2542
|
+
|
|
2543
|
+
Parameters
|
|
2544
|
+
----------
|
|
2545
|
+
*args
|
|
2546
|
+
The sample data.
|
|
2547
|
+
vmin, vmax : float, optional
|
|
2548
|
+
The user input minimum and maximum.
|
|
2549
|
+
robust : bool, optional
|
|
2550
|
+
Whether to limit the default range to exclude outliers.
|
|
2551
|
+
inbounds : bool, optional
|
|
2552
|
+
Whether to filter to in-bounds data.
|
|
2553
|
+
negative, positive, symmetric : bool, optional
|
|
2554
|
+
Whether limits should be negative, positive, or symmetric.
|
|
2555
|
+
to_centers : bool, optional
|
|
2556
|
+
Whether to convert coordinates to 'centers'.
|
|
2557
|
+
|
|
2558
|
+
Returns
|
|
2559
|
+
-------
|
|
2560
|
+
vmin, vmax : float
|
|
2561
|
+
The minimum and maximum.
|
|
2562
|
+
**kwargs
|
|
2563
|
+
Unused arguemnts.
|
|
2564
|
+
"""
|
|
2565
|
+
# Parse vmin and vmax
|
|
2566
|
+
automin = vmin is None
|
|
2567
|
+
automax = vmax is None
|
|
2568
|
+
if not automin and not automax:
|
|
2569
|
+
return vmin, vmax, kwargs
|
|
2570
|
+
|
|
2571
|
+
# Parse input args
|
|
2572
|
+
inbounds = _not_none(inbounds, rc["cmap.inbounds"])
|
|
2573
|
+
robust = _not_none(robust, rc["cmap.robust"], False)
|
|
2574
|
+
robust = 96 if robust is True else 100 if robust is False else robust
|
|
2575
|
+
robust = np.atleast_1d(robust)
|
|
2576
|
+
if robust.size == 1:
|
|
2577
|
+
pmin, pmax = 50 + 0.5 * np.array([-robust.item(), robust.item()])
|
|
2578
|
+
elif robust.size == 2:
|
|
2579
|
+
pmin, pmax = robust.flat # pull out of array
|
|
2580
|
+
else:
|
|
2581
|
+
raise ValueError(
|
|
2582
|
+
f"Unexpected robust={robust!r}. Must be bool, float, or 2-tuple."
|
|
2583
|
+
) # noqa: E501
|
|
2584
|
+
|
|
2585
|
+
# Get sample data
|
|
2586
|
+
# NOTE: Critical to use _to_numpy_array here because some
|
|
2587
|
+
# commands are unstandardized.
|
|
2588
|
+
# NOTE: Try to get reasonable *count* levels for hexbin/hist2d, but in general
|
|
2589
|
+
# have no way to select nice ones a priori (why we disable discretenorm).
|
|
2590
|
+
# NOTE: Currently we only ever use this function with *single* array input
|
|
2591
|
+
# but in future could make this public as a way for users (me) to get
|
|
2592
|
+
# automatic synced contours for a bunch of arrays in a grid.
|
|
2593
|
+
vmins, vmaxs = [], []
|
|
2594
|
+
if len(args) > 2:
|
|
2595
|
+
x, y, *zs = args
|
|
2596
|
+
else:
|
|
2597
|
+
x, y, *zs = None, None, *args
|
|
2598
|
+
for z in zs:
|
|
2599
|
+
if z is None: # e.g. empty scatter color
|
|
2600
|
+
continue
|
|
2601
|
+
if z.ndim > 2: # e.g. imshow data
|
|
2602
|
+
continue
|
|
2603
|
+
z = inputs._to_numpy_array(z)
|
|
2604
|
+
if inbounds and x is not None and y is not None: # ignore if None coords
|
|
2605
|
+
z = self._inbounds_vlim(x, y, z, to_centers=to_centers)
|
|
2606
|
+
imin, imax = inputs._safe_range(z, pmin, pmax)
|
|
2607
|
+
if automin and imin is not None:
|
|
2608
|
+
vmins.append(imin)
|
|
2609
|
+
if automax and imax is not None:
|
|
2610
|
+
vmaxs.append(imax)
|
|
2611
|
+
if automin:
|
|
2612
|
+
vmin = min(vmins, default=0)
|
|
2613
|
+
if automax:
|
|
2614
|
+
vmax = max(vmaxs, default=1)
|
|
2615
|
+
|
|
2616
|
+
# Apply modifications
|
|
2617
|
+
# NOTE: This is also applied to manual input levels lists in _parse_level_vals
|
|
2618
|
+
if negative:
|
|
2619
|
+
if automax:
|
|
2620
|
+
vmax = 0
|
|
2621
|
+
else:
|
|
2622
|
+
warnings._warn_ultraplot(
|
|
2623
|
+
f"Incompatible arguments vmax={vmax!r} and negative=True. "
|
|
2624
|
+
"Ignoring the latter."
|
|
2625
|
+
)
|
|
2626
|
+
if positive:
|
|
2627
|
+
if automin:
|
|
2628
|
+
vmin = 0
|
|
2629
|
+
else:
|
|
2630
|
+
warnings._warn_ultraplot(
|
|
2631
|
+
f"Incompatible arguments vmin={vmin!r} and positive=True. "
|
|
2632
|
+
"Ignoring the latter."
|
|
2633
|
+
)
|
|
2634
|
+
if symmetric:
|
|
2635
|
+
if automin and not automax:
|
|
2636
|
+
vmin = -vmax
|
|
2637
|
+
elif automax and not automin:
|
|
2638
|
+
vmax = -vmin
|
|
2639
|
+
elif automin and automax:
|
|
2640
|
+
vmin, vmax = -np.max(np.abs((vmin, vmax))), np.max(np.abs((vmin, vmax)))
|
|
2641
|
+
else:
|
|
2642
|
+
warnings._warn_ultraplot(
|
|
2643
|
+
f"Incompatible arguments vmin={vmin!r}, vmax={vmax!r}, and "
|
|
2644
|
+
"symmetric=True. Ignoring the latter."
|
|
2645
|
+
)
|
|
2646
|
+
|
|
2647
|
+
return vmin, vmax, kwargs
|
|
2648
|
+
|
|
2649
|
+
def _parse_level_num(
|
|
2650
|
+
self,
|
|
2651
|
+
*args,
|
|
2652
|
+
levels=None,
|
|
2653
|
+
locator=None,
|
|
2654
|
+
locator_kw=None,
|
|
2655
|
+
vmin=None,
|
|
2656
|
+
vmax=None,
|
|
2657
|
+
norm=None,
|
|
2658
|
+
norm_kw=None,
|
|
2659
|
+
extend=None,
|
|
2660
|
+
symmetric=None,
|
|
2661
|
+
**kwargs,
|
|
2662
|
+
):
|
|
2663
|
+
"""
|
|
2664
|
+
Return a suitable level list given the input data, normalizer,
|
|
2665
|
+
locator, and vmin and vmax.
|
|
2666
|
+
|
|
2667
|
+
Parameters
|
|
2668
|
+
----------
|
|
2669
|
+
*args
|
|
2670
|
+
The sample data. Passed to `_parse_level_lim`.
|
|
2671
|
+
levels : int
|
|
2672
|
+
The approximate number of levels.
|
|
2673
|
+
locator, locator_kw
|
|
2674
|
+
The tick locator used to draw levels.
|
|
2675
|
+
vmin, vmax : float, optional
|
|
2676
|
+
The minimum and maximum values passed to the tick locator.
|
|
2677
|
+
norm, norm_kw : optional
|
|
2678
|
+
The continuous normalizer. Affects the default locator used to draw levels.
|
|
2679
|
+
extend : str, optional
|
|
2680
|
+
The extend setting. Affects level trimming settings.
|
|
2681
|
+
symmetric : bool, optional
|
|
2682
|
+
Whether the resulting levels should be symmetric about zero.
|
|
2683
|
+
|
|
2684
|
+
Returns
|
|
2685
|
+
-------
|
|
2686
|
+
levels : list of float
|
|
2687
|
+
The level edges.
|
|
2688
|
+
**kwargs
|
|
2689
|
+
Unused arguments.
|
|
2690
|
+
"""
|
|
2691
|
+
# Input args
|
|
2692
|
+
# NOTE: Some of this is adapted from the hidden contour.ContourSet._autolev
|
|
2693
|
+
# NOTE: We use 'symmetric' with MaxNLocator to ensure boundaries include a
|
|
2694
|
+
# zero level but may trim many of these below.
|
|
2695
|
+
norm_kw = norm_kw or {}
|
|
2696
|
+
locator_kw = locator_kw or {}
|
|
2697
|
+
extend = _not_none(extend, "neither")
|
|
2698
|
+
levels = _not_none(levels, rc["cmap.levels"])
|
|
2699
|
+
vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop("vmin", None))
|
|
2700
|
+
vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop("vmax", None))
|
|
2701
|
+
norm = constructor.Norm(norm or "linear", **norm_kw)
|
|
2702
|
+
symmetric = _not_none(
|
|
2703
|
+
symmetric=symmetric,
|
|
2704
|
+
locator_kw_symmetric=locator_kw.pop("symmetric", None),
|
|
2705
|
+
default=False,
|
|
2706
|
+
)
|
|
2707
|
+
|
|
2708
|
+
# Get default locator from input norm
|
|
2709
|
+
# NOTE: This normalizer is only temporary for inferring level locs
|
|
2710
|
+
norm = constructor.Norm(norm or "linear", **norm_kw)
|
|
2711
|
+
if locator is not None:
|
|
2712
|
+
locator = constructor.Locator(locator, **locator_kw)
|
|
2713
|
+
elif isinstance(norm, mcolors.LogNorm):
|
|
2714
|
+
locator = mticker.LogLocator(**locator_kw)
|
|
2715
|
+
elif isinstance(norm, mcolors.SymLogNorm):
|
|
2716
|
+
for key, default in (("base", 10), ("linthresh", 1)):
|
|
2717
|
+
val = _not_none(
|
|
2718
|
+
getattr(norm, key, None), getattr(norm, "_" + key, None), default
|
|
2719
|
+
) # noqa: E501
|
|
2720
|
+
locator_kw.setdefault(key, val)
|
|
2721
|
+
locator = mticker.SymmetricalLogLocator(**locator_kw)
|
|
2722
|
+
else:
|
|
2723
|
+
locator_kw["symmetric"] = symmetric
|
|
2724
|
+
locator = mticker.MaxNLocator(levels, min_n_ticks=1, **locator_kw)
|
|
2725
|
+
|
|
2726
|
+
# Get default level locations
|
|
2727
|
+
nlevs = levels
|
|
2728
|
+
automin = vmin is None
|
|
2729
|
+
automax = vmax is None
|
|
2730
|
+
vmin, vmax, kwargs = self._parse_level_lim(
|
|
2731
|
+
*args, vmin=vmin, vmax=vmax, symmetric=symmetric, **kwargs
|
|
2732
|
+
)
|
|
2733
|
+
try:
|
|
2734
|
+
levels = locator.tick_values(vmin, vmax)
|
|
2735
|
+
except TypeError: # e.g. due to datetime arrays
|
|
2736
|
+
return None, kwargs
|
|
2737
|
+
except RuntimeError: # too-many-ticks error
|
|
2738
|
+
levels = np.linspace(vmin, vmax, levels) # TODO: _autolev used N + 1
|
|
2739
|
+
|
|
2740
|
+
# Possibly trim levels far outside of 'vmin' and 'vmax'
|
|
2741
|
+
# NOTE: This part is mostly copied from matplotlib _autolev
|
|
2742
|
+
if not symmetric:
|
|
2743
|
+
i0, i1 = 0, len(levels) # defaults
|
|
2744
|
+
(under,) = np.where(levels < vmin)
|
|
2745
|
+
if len(under):
|
|
2746
|
+
i0 = under[-1]
|
|
2747
|
+
if not automin or extend in ("min", "both"):
|
|
2748
|
+
i0 += 1 # permit out-of-bounds data
|
|
2749
|
+
(over,) = np.where(levels > vmax)
|
|
2750
|
+
if len(over):
|
|
2751
|
+
i1 = over[0] + 1 if len(over) else len(levels)
|
|
2752
|
+
if not automax or extend in ("max", "both"):
|
|
2753
|
+
i1 -= 1 # permit out-of-bounds data
|
|
2754
|
+
if i1 - i0 < 3:
|
|
2755
|
+
i0, i1 = 0, len(levels) # revert
|
|
2756
|
+
levels = levels[i0:i1]
|
|
2757
|
+
|
|
2758
|
+
# Compare the no. of levels we got (levels) to what we wanted (nlevs)
|
|
2759
|
+
# If we wanted more than 2 times the result, then add nn - 1 extra
|
|
2760
|
+
# levels in-between the returned levels in normalized space (e.g. LogNorm).
|
|
2761
|
+
nn = nlevs // len(levels)
|
|
2762
|
+
if nn >= 2:
|
|
2763
|
+
olevels = norm(levels)
|
|
2764
|
+
nlevels = []
|
|
2765
|
+
for i in range(len(levels) - 1):
|
|
2766
|
+
l1, l2 = olevels[i], olevels[i + 1]
|
|
2767
|
+
nlevels.extend(np.linspace(l1, l2, nn + 1)[:-1])
|
|
2768
|
+
nlevels.append(olevels[-1])
|
|
2769
|
+
levels = norm.inverse(nlevels)
|
|
2770
|
+
|
|
2771
|
+
return levels, kwargs
|
|
2772
|
+
|
|
2773
|
+
def _parse_level_vals(
|
|
2774
|
+
self,
|
|
2775
|
+
*args,
|
|
2776
|
+
N=None,
|
|
2777
|
+
levels=None,
|
|
2778
|
+
values=None,
|
|
2779
|
+
extend=None,
|
|
2780
|
+
positive=False,
|
|
2781
|
+
negative=False,
|
|
2782
|
+
nozero=False,
|
|
2783
|
+
norm=None,
|
|
2784
|
+
norm_kw=None,
|
|
2785
|
+
skip_autolev=False,
|
|
2786
|
+
min_levels=None,
|
|
2787
|
+
**kwargs,
|
|
2788
|
+
):
|
|
2789
|
+
"""
|
|
2790
|
+
Return levels resulting from a wide variety of keyword options.
|
|
2791
|
+
|
|
2792
|
+
Parameters
|
|
2793
|
+
----------
|
|
2794
|
+
*args
|
|
2795
|
+
The sample data. Passed to `_parse_level_lim`.
|
|
2796
|
+
N
|
|
2797
|
+
Shorthand for `levels`.
|
|
2798
|
+
levels : int or sequence of float, optional
|
|
2799
|
+
The levels list or (approximate) number of levels to create.
|
|
2800
|
+
values : int or sequence of float, optional
|
|
2801
|
+
The level center list or (approximate) number of level centers to create.
|
|
2802
|
+
positive, negative, nozero : bool, optional
|
|
2803
|
+
Whether to remove out non-positive, non-negative, and zero-valued
|
|
2804
|
+
levels. The latter is useful for single-color contour plots.
|
|
2805
|
+
norm, norm_kw : optional
|
|
2806
|
+
Passed to `Norm`. Used to possbily infer levels or to convert values.
|
|
2807
|
+
skip_autolev : bool, optional
|
|
2808
|
+
Whether to skip automatic level generation.
|
|
2809
|
+
min_levels : int, optional
|
|
2810
|
+
The minimum number of levels allowed.
|
|
2811
|
+
|
|
2812
|
+
Returns
|
|
2813
|
+
-------
|
|
2814
|
+
levels : list of float
|
|
2815
|
+
The level edges.
|
|
2816
|
+
**kwargs
|
|
2817
|
+
Unused arguments.
|
|
2818
|
+
"""
|
|
2819
|
+
|
|
2820
|
+
# Helper function that restricts levels
|
|
2821
|
+
# NOTE: This should have no effect if levels were generated automatically.
|
|
2822
|
+
# However want to apply these to manual-input levels as well.
|
|
2823
|
+
def _restrict_levels(levels):
|
|
2824
|
+
kw = {}
|
|
2825
|
+
levels = np.asarray(levels)
|
|
2826
|
+
if len(levels) > 2:
|
|
2827
|
+
kw["atol"] = 1e-5 * np.min(np.diff(levels))
|
|
2828
|
+
if nozero:
|
|
2829
|
+
levels = levels[~np.isclose(levels, 0, **kw)]
|
|
2830
|
+
if positive:
|
|
2831
|
+
levels = levels[(levels > 0) | np.isclose(levels, 0, **kw)]
|
|
2832
|
+
if negative:
|
|
2833
|
+
levels = levels[(levels < 0) | np.isclose(levels, 0, **kw)]
|
|
2834
|
+
return levels
|
|
2835
|
+
|
|
2836
|
+
# Helper function to sanitize input levels
|
|
2837
|
+
# NOTE: Include special case where color levels are referenced by string labels
|
|
2838
|
+
def _sanitize_levels(key, array, minsize):
|
|
2839
|
+
if np.iterable(array):
|
|
2840
|
+
array, _ = pcolors._sanitize_levels(array, minsize)
|
|
2841
|
+
elif isinstance(array, Integral):
|
|
2842
|
+
pass
|
|
2843
|
+
elif array is not None:
|
|
2844
|
+
raise ValueError(f"Invalid {key}={array}. Must be list or integer.")
|
|
2845
|
+
if isinstance(norm, (mcolors.BoundaryNorm, pcolors.SegmentedNorm)):
|
|
2846
|
+
if isinstance(array, Integral):
|
|
2847
|
+
warnings._warn_ultraplot(
|
|
2848
|
+
f"Ignoring {key}={array}. Using norm={norm!r} {key} instead."
|
|
2849
|
+
)
|
|
2850
|
+
array = norm.boundaries if key == "levels" else None
|
|
2851
|
+
return array
|
|
2852
|
+
|
|
2853
|
+
# Parse input arguments and resolve incompatibilities
|
|
2854
|
+
vmin = vmax = None
|
|
2855
|
+
levels = _not_none(N=N, levels=levels, norm_kw_levs=norm_kw.pop("levels", None))
|
|
2856
|
+
if positive and negative:
|
|
2857
|
+
warnings._warn_ultraplot(
|
|
2858
|
+
"Incompatible args positive=True and negative=True. Using former."
|
|
2859
|
+
)
|
|
2860
|
+
negative = False
|
|
2861
|
+
if levels is not None and values is not None:
|
|
2862
|
+
warnings._warn_ultraplot(
|
|
2863
|
+
f"Incompatible args levels={levels!r} and values={values!r}. Using former." # noqa: E501
|
|
2864
|
+
)
|
|
2865
|
+
values = None
|
|
2866
|
+
|
|
2867
|
+
# Infer level edges from level centers if possible
|
|
2868
|
+
# NOTE: The only way for user to manually impose BoundaryNorm is by
|
|
2869
|
+
# passing one -- users cannot create one using Norm constructor key.
|
|
2870
|
+
if isinstance(values, Integral):
|
|
2871
|
+
levels = values + 1
|
|
2872
|
+
values = None
|
|
2873
|
+
if values is None:
|
|
2874
|
+
levels = _sanitize_levels("levels", levels, _not_none(min_levels, 2))
|
|
2875
|
+
levels = _not_none(levels, rc["cmap.levels"])
|
|
2876
|
+
else:
|
|
2877
|
+
values = _sanitize_levels("values", values, 1)
|
|
2878
|
+
kwargs["discrete_ticks"] = values # passed to _parse_level_norm
|
|
2879
|
+
if len(values) == 1:
|
|
2880
|
+
levels = [values[0] - 1, values[0] + 1] # weird but why not
|
|
2881
|
+
elif norm is not None and norm not in ("segments", "segmented"):
|
|
2882
|
+
# Generate levels by finding in-between points in the
|
|
2883
|
+
# normalized numeric space, e.g. LogNorm space.
|
|
2884
|
+
norm_kw = norm_kw or {}
|
|
2885
|
+
convert = constructor.Norm(norm, **norm_kw)
|
|
2886
|
+
levels = convert.inverse(utils.edges(convert(values)))
|
|
2887
|
+
else:
|
|
2888
|
+
# Generate levels so that ticks will be centered between edges
|
|
2889
|
+
# Solve: (x1 + x2) / 2 = y --> x2 = 2 * y - x1 with arbitrary init x1
|
|
2890
|
+
descending = values[1] < values[0]
|
|
2891
|
+
if descending: # e.g. [100, 50, 20, 10, 5, 2, 1] successful if reversed
|
|
2892
|
+
values = values[::-1]
|
|
2893
|
+
levels = [1.5 * values[0] - 0.5 * values[1]] # arbitrary starting point
|
|
2894
|
+
for value in values:
|
|
2895
|
+
levels.append(2 * value - levels[-1])
|
|
2896
|
+
if np.any(np.diff(levels) < 0): # never happens for evenly spaced levs
|
|
2897
|
+
levels = utils.edges(values)
|
|
2898
|
+
if descending: # then revert back below
|
|
2899
|
+
levels = levels[::-1]
|
|
2900
|
+
|
|
2901
|
+
# Process level edges and infer defaults
|
|
2902
|
+
# NOTE: Matplotlib colorbar algorithm *cannot* handle descending levels so
|
|
2903
|
+
# this function reverses them and adds special attribute to the normalizer.
|
|
2904
|
+
# Then colorbar() reads this attr and flips the axis and the colormap direction
|
|
2905
|
+
if np.iterable(levels):
|
|
2906
|
+
pop = _pop_params(kwargs, self._parse_level_num, ignore_internal=True)
|
|
2907
|
+
if pop:
|
|
2908
|
+
warnings._warn_ultraplot(f"Ignoring unused keyword arg(s): {pop}")
|
|
2909
|
+
elif not skip_autolev:
|
|
2910
|
+
levels, kwargs = self._parse_level_num(
|
|
2911
|
+
*args,
|
|
2912
|
+
levels=levels,
|
|
2913
|
+
norm=norm,
|
|
2914
|
+
norm_kw=norm_kw,
|
|
2915
|
+
extend=extend,
|
|
2916
|
+
negative=negative,
|
|
2917
|
+
positive=positive,
|
|
2918
|
+
**kwargs,
|
|
2919
|
+
)
|
|
2920
|
+
else:
|
|
2921
|
+
levels = values = None
|
|
2922
|
+
|
|
2923
|
+
# Determine default colorbar locator and norm and apply filters
|
|
2924
|
+
# NOTE: DiscreteNorm does not currently support vmin and
|
|
2925
|
+
# vmax different from level list minimum and maximum.
|
|
2926
|
+
# NOTE: The level restriction should have no effect if levels were generated
|
|
2927
|
+
# automatically. However want to apply these to manual-input levels as well.
|
|
2928
|
+
if levels is not None:
|
|
2929
|
+
levels = _restrict_levels(levels)
|
|
2930
|
+
if len(levels) == 0: # skip
|
|
2931
|
+
pass
|
|
2932
|
+
elif len(levels) == 1: # use central colormap color
|
|
2933
|
+
vmin, vmax = levels[0] - 1, levels[0] + 1
|
|
2934
|
+
else: # use minimum and maximum
|
|
2935
|
+
vmin, vmax = np.min(levels), np.max(levels)
|
|
2936
|
+
if not np.allclose(levels[1] - levels[0], np.diff(levels)):
|
|
2937
|
+
norm = _not_none(norm, "segmented")
|
|
2938
|
+
if norm in ("segments", "segmented"):
|
|
2939
|
+
norm_kw["levels"] = levels
|
|
2940
|
+
|
|
2941
|
+
return levels, vmin, vmax, norm, norm_kw, kwargs
|
|
2942
|
+
|
|
2943
|
+
@staticmethod
|
|
2944
|
+
def _parse_level_norm(
|
|
2945
|
+
levels,
|
|
2946
|
+
norm,
|
|
2947
|
+
cmap,
|
|
2948
|
+
*,
|
|
2949
|
+
extend=None,
|
|
2950
|
+
min_levels=None,
|
|
2951
|
+
discrete_ticks=None,
|
|
2952
|
+
discrete_labels=None,
|
|
2953
|
+
**kwargs,
|
|
2954
|
+
):
|
|
2955
|
+
"""
|
|
2956
|
+
Create a `~ultraplot.colors.DiscreteNorm` or `~ultraplot.colors.BoundaryNorm`
|
|
2957
|
+
from the input colormap and normalizer.
|
|
2958
|
+
|
|
2959
|
+
Parameters
|
|
2960
|
+
----------
|
|
2961
|
+
levels : sequence of float
|
|
2962
|
+
The level boundaries.
|
|
2963
|
+
norm : `~matplotlib.colors.Normalize`
|
|
2964
|
+
The continuous normalizer.
|
|
2965
|
+
cmap : `~matplotlib.colors.Colormap`
|
|
2966
|
+
The colormap.
|
|
2967
|
+
extend : str, optional
|
|
2968
|
+
The extend setting.
|
|
2969
|
+
min_levels : int, optional
|
|
2970
|
+
The minimum number of levels.
|
|
2971
|
+
discrete_ticks : array-like, optional
|
|
2972
|
+
The colorbar locations to tick.
|
|
2973
|
+
discrete_labels : array-like, optional
|
|
2974
|
+
The colorbar tick labels.
|
|
2975
|
+
|
|
2976
|
+
Returns
|
|
2977
|
+
-------
|
|
2978
|
+
norm : `~ultraplot.colors.DiscreteNorm`
|
|
2979
|
+
The discrete normalizer.
|
|
2980
|
+
cmap : `~matplotlib.colors.Colormap`
|
|
2981
|
+
The possibly-modified colormap.
|
|
2982
|
+
kwargs
|
|
2983
|
+
Unused arguments.
|
|
2984
|
+
"""
|
|
2985
|
+
# Reverse the colormap if input levels or values were descending
|
|
2986
|
+
# See _parse_level_vals for details
|
|
2987
|
+
min_levels = _not_none(min_levels, 2) # 1 for contour plots
|
|
2988
|
+
unique = extend = _not_none(extend, "neither")
|
|
2989
|
+
under = cmap._rgba_under
|
|
2990
|
+
over = cmap._rgba_over
|
|
2991
|
+
cyclic = getattr(cmap, "_cyclic", None)
|
|
2992
|
+
qualitative = isinstance(cmap, pcolors.DiscreteColormap) # see _parse_cmap
|
|
2993
|
+
if len(levels) < min_levels:
|
|
2994
|
+
raise ValueError(
|
|
2995
|
+
f"Invalid levels={levels!r}. Must be at least length {min_levels}."
|
|
2996
|
+
)
|
|
2997
|
+
|
|
2998
|
+
# Ensure end colors are unique by scaling colors as if extend='both'
|
|
2999
|
+
# NOTE: Inside _parse_cmap should have enforced extend='neither'
|
|
3000
|
+
if cyclic:
|
|
3001
|
+
step = 0.5 # try to allocate space for unique end colors
|
|
3002
|
+
unique = "both"
|
|
3003
|
+
|
|
3004
|
+
# Ensure color list length matches level list length using rotation
|
|
3005
|
+
# NOTE: No harm if not enough colors, we just end up with the same
|
|
3006
|
+
# color for out-of-bounds extensions. This is a gentle failure
|
|
3007
|
+
elif qualitative:
|
|
3008
|
+
step = 0.5 # try to sample the central index for safety
|
|
3009
|
+
unique = "both"
|
|
3010
|
+
auto_under = under is None and extend in ("min", "both")
|
|
3011
|
+
auto_over = over is None and extend in ("max", "both")
|
|
3012
|
+
ncolors = len(levels) - min_levels + 1 + auto_under + auto_over
|
|
3013
|
+
colors = list(itertools.islice(itertools.cycle(cmap.colors), ncolors))
|
|
3014
|
+
if auto_under and len(colors) > 1:
|
|
3015
|
+
under, *colors = colors
|
|
3016
|
+
if auto_over and len(colors) > 1:
|
|
3017
|
+
*colors, over = colors
|
|
3018
|
+
cmap = cmap.copy(colors, N=len(colors))
|
|
3019
|
+
if under is not None:
|
|
3020
|
+
cmap.set_under(under)
|
|
3021
|
+
if over is not None:
|
|
3022
|
+
cmap.set_over(over)
|
|
3023
|
+
|
|
3024
|
+
# Ensure middle colors sample full range when extreme colors are present
|
|
3025
|
+
# by scaling colors as if extend='neither'
|
|
3026
|
+
else:
|
|
3027
|
+
step = 1.0
|
|
3028
|
+
if over is not None and under is not None:
|
|
3029
|
+
unique = "neither"
|
|
3030
|
+
elif over is not None: # turn off over-bounds unique bin
|
|
3031
|
+
if extend == "both":
|
|
3032
|
+
unique = "min"
|
|
3033
|
+
elif extend == "max":
|
|
3034
|
+
unique = "neither"
|
|
3035
|
+
elif under is not None: # turn off under-bounds unique bin
|
|
3036
|
+
if extend == "both":
|
|
3037
|
+
unique = "min"
|
|
3038
|
+
elif extend == "max":
|
|
3039
|
+
unique = "neither"
|
|
3040
|
+
|
|
3041
|
+
# Generate DiscreteNorm and update "child" norm with vmin and vmax from
|
|
3042
|
+
# levels. This lets the colorbar set tick locations properly!
|
|
3043
|
+
if not isinstance(norm, mcolors.BoundaryNorm) and len(levels) > 1:
|
|
3044
|
+
norm = pcolors.DiscreteNorm(
|
|
3045
|
+
levels,
|
|
3046
|
+
norm=norm,
|
|
3047
|
+
unique=unique,
|
|
3048
|
+
step=step,
|
|
3049
|
+
ticks=discrete_ticks,
|
|
3050
|
+
labels=discrete_labels,
|
|
3051
|
+
)
|
|
3052
|
+
|
|
3053
|
+
return norm, cmap, kwargs
|
|
3054
|
+
|
|
3055
|
+
def _apply_plot(self, *pairs, vert=True, **kwargs):
|
|
3056
|
+
"""
|
|
3057
|
+
Plot standard lines.
|
|
3058
|
+
"""
|
|
3059
|
+
# Plot the lines
|
|
3060
|
+
objs, xsides = [], []
|
|
3061
|
+
kws = kwargs.copy()
|
|
3062
|
+
kws.update(_pop_props(kws, "line"))
|
|
3063
|
+
kws, extents = self._inbounds_extent(**kws)
|
|
3064
|
+
for xs, ys, fmt in self._iter_arg_pairs(*pairs):
|
|
3065
|
+
xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, **kws)
|
|
3066
|
+
ys, kw = inputs._dist_reduce(ys, **kw)
|
|
3067
|
+
guide_kw = _pop_params(kw, self._update_guide) # after standardize
|
|
3068
|
+
for _, n, x, y, kw in self._iter_arg_cols(xs, ys, **kw):
|
|
3069
|
+
kw = self._parse_cycle(n, **kw)
|
|
3070
|
+
*eb, kw = self._add_error_bars(
|
|
3071
|
+
x, y, vert=vert, default_barstds=True, **kw
|
|
3072
|
+
) # noqa: E501
|
|
3073
|
+
*es, kw = self._add_error_shading(x, y, vert=vert, **kw)
|
|
3074
|
+
xsides.append(x)
|
|
3075
|
+
if not vert:
|
|
3076
|
+
x, y = y, x
|
|
3077
|
+
a = [x, y]
|
|
3078
|
+
if fmt is not None: # x1, y1, fmt1, x2, y2, fm2... style input
|
|
3079
|
+
a.append(fmt)
|
|
3080
|
+
(obj,) = self._call_native("plot", *a, **kw)
|
|
3081
|
+
self._inbounds_xylim(extents, x, y)
|
|
3082
|
+
objs.append((*eb, *es, obj) if eb or es else obj)
|
|
3083
|
+
|
|
3084
|
+
# Add sticky edges
|
|
3085
|
+
self._fix_sticky_edges(objs, "x" if vert else "y", *xsides, only=mlines.Line2D)
|
|
3086
|
+
self._update_guide(objs, **guide_kw)
|
|
3087
|
+
return cbook.silent_list("Line2D", objs) # always return list
|
|
3088
|
+
|
|
3089
|
+
@docstring._snippet_manager
|
|
3090
|
+
def line(self, *args, **kwargs):
|
|
3091
|
+
"""
|
|
3092
|
+
%(plot.plot)s
|
|
3093
|
+
"""
|
|
3094
|
+
return self.plot(*args, **kwargs)
|
|
3095
|
+
|
|
3096
|
+
@docstring._snippet_manager
|
|
3097
|
+
def linex(self, *args, **kwargs):
|
|
3098
|
+
"""
|
|
3099
|
+
%(plot.plotx)s
|
|
3100
|
+
"""
|
|
3101
|
+
return self.plotx(*args, **kwargs)
|
|
3102
|
+
|
|
3103
|
+
@inputs._preprocess_or_redirect("x", "y", allow_extra=True)
|
|
3104
|
+
@docstring._concatenate_inherited
|
|
3105
|
+
@docstring._snippet_manager
|
|
3106
|
+
def plot(self, *args, **kwargs):
|
|
3107
|
+
"""
|
|
3108
|
+
%(plot.plot)s
|
|
3109
|
+
"""
|
|
3110
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3111
|
+
return self._apply_plot(*args, **kwargs)
|
|
3112
|
+
|
|
3113
|
+
@inputs._preprocess_or_redirect("y", "x", allow_extra=True)
|
|
3114
|
+
@docstring._snippet_manager
|
|
3115
|
+
def plotx(self, *args, **kwargs):
|
|
3116
|
+
"""
|
|
3117
|
+
%(plot.plotx)s
|
|
3118
|
+
"""
|
|
3119
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3120
|
+
return self._apply_plot(*args, **kwargs)
|
|
3121
|
+
|
|
3122
|
+
def _apply_step(self, *pairs, vert=True, where="pre", **kwargs):
|
|
3123
|
+
"""
|
|
3124
|
+
Plot the steps.
|
|
3125
|
+
"""
|
|
3126
|
+
# Plot the steps
|
|
3127
|
+
# NOTE: Internally matplotlib plot() calls step() so we could use that
|
|
3128
|
+
# approach... but instead repeat _apply_plot internals here so we can
|
|
3129
|
+
# disable error indications that make no sense for 'step' plots.
|
|
3130
|
+
kws = kwargs.copy()
|
|
3131
|
+
opts = ("pre", "post", "mid")
|
|
3132
|
+
if where not in opts:
|
|
3133
|
+
raise ValueError(f"Invalid where={where!r}. Options are {opts!r}.")
|
|
3134
|
+
kws.update(_pop_props(kws, "line"))
|
|
3135
|
+
kws.setdefault("drawstyle", "steps-" + where)
|
|
3136
|
+
kws, extents = self._inbounds_extent(**kws)
|
|
3137
|
+
objs = []
|
|
3138
|
+
for xs, ys, fmt in self._iter_arg_pairs(*pairs):
|
|
3139
|
+
xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, **kws)
|
|
3140
|
+
guide_kw = _pop_params(kw, self._update_guide) # after standardize
|
|
3141
|
+
if fmt is not None:
|
|
3142
|
+
kw["fmt"] = fmt
|
|
3143
|
+
for _, n, x, y, *a, kw in self._iter_arg_cols(xs, ys, **kw):
|
|
3144
|
+
kw = self._parse_cycle(n, **kw)
|
|
3145
|
+
if not vert:
|
|
3146
|
+
x, y = y, x
|
|
3147
|
+
(obj,) = self._call_native("step", x, y, *a, **kw)
|
|
3148
|
+
self._inbounds_xylim(extents, x, y)
|
|
3149
|
+
objs.append(obj)
|
|
3150
|
+
|
|
3151
|
+
self._update_guide(objs, **guide_kw)
|
|
3152
|
+
return cbook.silent_list("Line2D", objs) # always return list
|
|
3153
|
+
|
|
3154
|
+
@inputs._preprocess_or_redirect("x", "y", allow_extra=True)
|
|
3155
|
+
@docstring._concatenate_inherited
|
|
3156
|
+
@docstring._snippet_manager
|
|
3157
|
+
def step(self, *args, **kwargs):
|
|
3158
|
+
"""
|
|
3159
|
+
%(plot.step)s
|
|
3160
|
+
"""
|
|
3161
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3162
|
+
return self._apply_step(*args, **kwargs)
|
|
3163
|
+
|
|
3164
|
+
@inputs._preprocess_or_redirect("y", "x", allow_extra=True)
|
|
3165
|
+
@docstring._snippet_manager
|
|
3166
|
+
def stepx(self, *args, **kwargs):
|
|
3167
|
+
"""
|
|
3168
|
+
%(plot.stepx)s
|
|
3169
|
+
"""
|
|
3170
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3171
|
+
return self._apply_step(*args, **kwargs)
|
|
3172
|
+
|
|
3173
|
+
def _apply_stem(
|
|
3174
|
+
self,
|
|
3175
|
+
x,
|
|
3176
|
+
y,
|
|
3177
|
+
*,
|
|
3178
|
+
linefmt=None,
|
|
3179
|
+
markerfmt=None,
|
|
3180
|
+
basefmt=None,
|
|
3181
|
+
orientation=None,
|
|
3182
|
+
**kwargs,
|
|
3183
|
+
):
|
|
3184
|
+
"""
|
|
3185
|
+
Plot stem lines and markers.
|
|
3186
|
+
"""
|
|
3187
|
+
# Parse input
|
|
3188
|
+
kw = kwargs.copy()
|
|
3189
|
+
kw, extents = self._inbounds_extent(**kw)
|
|
3190
|
+
x, y, kw = self._parse_1d_args(x, y, **kw)
|
|
3191
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3192
|
+
|
|
3193
|
+
# Set default colors
|
|
3194
|
+
# NOTE: 'fmt' strings can only be 2 to 3 characters and include color
|
|
3195
|
+
# shorthands like 'r' or cycle colors like 'C0'. Cannot use full color names.
|
|
3196
|
+
# NOTE: Matplotlib defaults try to make a 'reddish' color the base and 'bluish'
|
|
3197
|
+
# color the stems. To make this more robust we temporarily replace the cycler.
|
|
3198
|
+
# Bizarrely stem() only reads from the global cycler() so have to update it.
|
|
3199
|
+
fmts = (linefmt, basefmt, markerfmt)
|
|
3200
|
+
orientation = _not_none(orientation, "vertical")
|
|
3201
|
+
if not any(isinstance(fmt, str) and re.match(r"\AC[0-9]", fmt) for fmt in fmts):
|
|
3202
|
+
cycle = constructor.Cycle((rc["negcolor"], rc["poscolor"]), name="_no_name")
|
|
3203
|
+
kw.setdefault("cycle", cycle)
|
|
3204
|
+
kw["basefmt"] = _not_none(basefmt, "C1-") # red base
|
|
3205
|
+
kw["linefmt"] = linefmt = _not_none(linefmt, "C0-") # blue stems
|
|
3206
|
+
kw["markerfmt"] = _not_none(markerfmt, linefmt[:-1] + "o") # blue marker
|
|
3207
|
+
sig = inspect.signature(maxes.Axes.stem)
|
|
3208
|
+
if "use_line_collection" in sig.parameters:
|
|
3209
|
+
kw.setdefault("use_line_collection", True)
|
|
3210
|
+
|
|
3211
|
+
# Call function then restore property cycle
|
|
3212
|
+
# WARNING: Horizontal stem plots are only supported in recent versions of
|
|
3213
|
+
# matplotlib. Let matplotlib raise an error if need be.
|
|
3214
|
+
ctx = {}
|
|
3215
|
+
cycle, kw = self._parse_cycle(return_cycle=True, **kw) # allow re-application
|
|
3216
|
+
if cycle is not None:
|
|
3217
|
+
ctx["axes.prop_cycle"] = cycle
|
|
3218
|
+
if orientation == "horizontal": # may raise error
|
|
3219
|
+
kw["orientation"] = orientation
|
|
3220
|
+
with rc.context(ctx):
|
|
3221
|
+
obj = self._call_native("stem", x, y, **kw)
|
|
3222
|
+
self._inbounds_xylim(extents, x, y, orientation=orientation)
|
|
3223
|
+
self._update_guide(obj, **guide_kw)
|
|
3224
|
+
return obj
|
|
3225
|
+
|
|
3226
|
+
@inputs._preprocess_or_redirect("x", "y")
|
|
3227
|
+
@docstring._concatenate_inherited
|
|
3228
|
+
@docstring._snippet_manager
|
|
3229
|
+
def stem(self, *args, **kwargs):
|
|
3230
|
+
"""
|
|
3231
|
+
%(plot.stem)s
|
|
3232
|
+
"""
|
|
3233
|
+
kwargs = _parse_vert(default_orientation="vertical", **kwargs)
|
|
3234
|
+
return self._apply_stem(*args, **kwargs)
|
|
3235
|
+
|
|
3236
|
+
@inputs._preprocess_or_redirect("x", "y")
|
|
3237
|
+
@docstring._snippet_manager
|
|
3238
|
+
def stemx(self, *args, **kwargs):
|
|
3239
|
+
"""
|
|
3240
|
+
%(plot.stemx)s
|
|
3241
|
+
"""
|
|
3242
|
+
kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
|
|
3243
|
+
return self._apply_stem(*args, **kwargs)
|
|
3244
|
+
|
|
3245
|
+
@inputs._preprocess_or_redirect("x", "y", ("c", "color", "colors", "values"))
|
|
3246
|
+
@docstring._snippet_manager
|
|
3247
|
+
def parametric(self, x, y, c, *, interp=0, scalex=True, scaley=True, **kwargs):
|
|
3248
|
+
"""
|
|
3249
|
+
%(plot.parametric)s
|
|
3250
|
+
"""
|
|
3251
|
+
# Standardize arguments
|
|
3252
|
+
# NOTE: Values are inferred in _auto_format() the same way legend labels are
|
|
3253
|
+
# inferred. Will not always return an array like inferred coordinates do.
|
|
3254
|
+
# NOTE: We want to be able to think of 'c' as a scatter color array and
|
|
3255
|
+
# as a colormap color list. Try to support that here.
|
|
3256
|
+
kw = kwargs.copy()
|
|
3257
|
+
kw.update(_pop_props(kw, "collection"))
|
|
3258
|
+
kw, extents = self._inbounds_extent(**kw)
|
|
3259
|
+
label = _not_none(**{key: kw.pop(key, None) for key in ("label", "value")})
|
|
3260
|
+
x, y, kw = self._parse_1d_args(
|
|
3261
|
+
x, y, values=c, autovalues=True, autoreverse=False, **kw
|
|
3262
|
+
)
|
|
3263
|
+
c = kw.pop("values", None) # permits auto-inferring values
|
|
3264
|
+
c = np.arange(y.size) if c is None else inputs._to_numpy_array(c)
|
|
3265
|
+
if (
|
|
3266
|
+
c.size in (3, 4)
|
|
3267
|
+
and y.size not in (3, 4)
|
|
3268
|
+
and mcolors.is_color_like(tuple(c.flat))
|
|
3269
|
+
or all(map(mcolors.is_color_like, c))
|
|
3270
|
+
):
|
|
3271
|
+
c, kw["colors"] = np.arange(c.shape[0]), c # convert color specs
|
|
3272
|
+
|
|
3273
|
+
# Interpret color values
|
|
3274
|
+
# NOTE: This permits string label input for 'values'
|
|
3275
|
+
c, guide_kw = inputs._meta_coords(c, which="") # convert string labels
|
|
3276
|
+
if c.size == 1 and y.size != 1:
|
|
3277
|
+
c = np.arange(y.size) # convert dummy label for single color
|
|
3278
|
+
if guide_kw:
|
|
3279
|
+
guides._add_guide_kw("colorbar", kw, **guide_kw)
|
|
3280
|
+
else:
|
|
3281
|
+
guides._add_guide_kw("colorbar", kw, locator=c)
|
|
3282
|
+
|
|
3283
|
+
# Interpolate values to allow for smooth gradations between values or just
|
|
3284
|
+
# to color siwtchover halfway between points (interp True, False respectively)
|
|
3285
|
+
if interp > 0:
|
|
3286
|
+
x_orig, y_orig, v_orig = x, y, c
|
|
3287
|
+
x, y, c = [], [], []
|
|
3288
|
+
for j in range(x_orig.shape[0] - 1):
|
|
3289
|
+
idx = slice(None)
|
|
3290
|
+
if j + 1 < x_orig.shape[0] - 1:
|
|
3291
|
+
idx = slice(None, -1)
|
|
3292
|
+
x.extend(np.linspace(x_orig[j], x_orig[j + 1], interp + 2)[idx].flat)
|
|
3293
|
+
y.extend(np.linspace(y_orig[j], y_orig[j + 1], interp + 2)[idx].flat)
|
|
3294
|
+
c.extend(np.linspace(v_orig[j], v_orig[j + 1], interp + 2)[idx].flat)
|
|
3295
|
+
x, y, c = np.array(x), np.array(y), np.array(c)
|
|
3296
|
+
|
|
3297
|
+
# Get coordinates and values for points to the 'left' and 'right' of joints
|
|
3298
|
+
coords = []
|
|
3299
|
+
for i in range(y.shape[0]):
|
|
3300
|
+
icoords = np.empty((3, 2))
|
|
3301
|
+
for j, arr in enumerate((x, y)):
|
|
3302
|
+
icoords[:, j] = (
|
|
3303
|
+
arr[0] if i == 0 else 0.5 * (arr[i - 1] + arr[i]),
|
|
3304
|
+
arr[i],
|
|
3305
|
+
arr[-1] if i + 1 == y.shape[0] else 0.5 * (arr[i + 1] + arr[i]),
|
|
3306
|
+
)
|
|
3307
|
+
coords.append(icoords)
|
|
3308
|
+
coords = np.array(coords)
|
|
3309
|
+
|
|
3310
|
+
# Get the colormap accounting for 'discrete' mode
|
|
3311
|
+
discrete = kw.get("discrete", None)
|
|
3312
|
+
if discrete is not None and not discrete:
|
|
3313
|
+
a = (x, y, c) # pick levels from vmin and vmax, possibly limiting range
|
|
3314
|
+
else:
|
|
3315
|
+
a, kw["values"] = (), c
|
|
3316
|
+
kw = self._parse_cmap(*a, plot_lines=True, **kw)
|
|
3317
|
+
cmap, norm = kw.pop("cmap"), kw.pop("norm")
|
|
3318
|
+
|
|
3319
|
+
# Add collection with some custom attributes
|
|
3320
|
+
# NOTE: Modern API uses self._request_autoscale_view but this is
|
|
3321
|
+
# backwards compatible to earliest matplotlib versions.
|
|
3322
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3323
|
+
obj = mcollections.LineCollection(
|
|
3324
|
+
coords,
|
|
3325
|
+
cmap=cmap,
|
|
3326
|
+
norm=norm,
|
|
3327
|
+
label=label,
|
|
3328
|
+
linestyles="-",
|
|
3329
|
+
capstyle="butt",
|
|
3330
|
+
joinstyle="miter",
|
|
3331
|
+
)
|
|
3332
|
+
obj.set_array(c) # the ScalarMappable method
|
|
3333
|
+
obj.update({key: value for key, value in kw.items() if key not in ("color",)})
|
|
3334
|
+
self.add_collection(obj) # also adjusts label
|
|
3335
|
+
self.autoscale_view(scalex=scalex, scaley=scaley)
|
|
3336
|
+
self._update_guide(obj, **guide_kw)
|
|
3337
|
+
return obj
|
|
3338
|
+
|
|
3339
|
+
def _apply_lines(
|
|
3340
|
+
self,
|
|
3341
|
+
xs,
|
|
3342
|
+
ys1,
|
|
3343
|
+
ys2,
|
|
3344
|
+
colors,
|
|
3345
|
+
*,
|
|
3346
|
+
vert=True,
|
|
3347
|
+
stack=None,
|
|
3348
|
+
stacked=None,
|
|
3349
|
+
negpos=False,
|
|
3350
|
+
**kwargs,
|
|
3351
|
+
):
|
|
3352
|
+
"""
|
|
3353
|
+
Plot vertical or hotizontal lines at each point.
|
|
3354
|
+
"""
|
|
3355
|
+
# Parse input arguments
|
|
3356
|
+
kw = kwargs.copy()
|
|
3357
|
+
name = "vlines" if vert else "hlines"
|
|
3358
|
+
if colors is not None:
|
|
3359
|
+
kw["colors"] = colors
|
|
3360
|
+
kw.update(_pop_props(kw, "collection"))
|
|
3361
|
+
kw, extents = self._inbounds_extent(**kw)
|
|
3362
|
+
stack = _not_none(stack=stack, stacked=stacked)
|
|
3363
|
+
xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
|
|
3364
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3365
|
+
|
|
3366
|
+
# Support "negative" and "positive" lines
|
|
3367
|
+
# TODO: Ensure 'linewidths' etc. are applied! For some reason
|
|
3368
|
+
# previously thought they had to be manually applied.
|
|
3369
|
+
y0 = 0
|
|
3370
|
+
objs, sides = [], []
|
|
3371
|
+
for _, n, x, y1, y2, kw in self._iter_arg_cols(xs, ys1, ys2, **kw):
|
|
3372
|
+
kw = self._parse_cycle(n, **kw)
|
|
3373
|
+
if stack:
|
|
3374
|
+
y1 = y1 + y0 # avoid in-place modification
|
|
3375
|
+
y2 = y2 + y0
|
|
3376
|
+
y0 = y0 + y2 - y1 # irrelevant that we added y0 to both
|
|
3377
|
+
if negpos:
|
|
3378
|
+
obj = self._call_negpos(name, x, y1, y2, colorkey="colors", **kw)
|
|
3379
|
+
else:
|
|
3380
|
+
obj = self._call_native(name, x, y1, y2, **kw)
|
|
3381
|
+
for y in (y1, y2):
|
|
3382
|
+
self._inbounds_xylim(extents, x, y, vert=vert)
|
|
3383
|
+
if y.size == 1: # add sticky edges if bounds are scalar
|
|
3384
|
+
sides.append(y)
|
|
3385
|
+
objs.append(obj)
|
|
3386
|
+
|
|
3387
|
+
# Draw guide and add sticky edges
|
|
3388
|
+
self._fix_sticky_edges(objs, "y" if vert else "x", *sides)
|
|
3389
|
+
self._update_guide(objs, **guide_kw)
|
|
3390
|
+
return objs[0] if len(objs) == 1 else cbook.silent_list("LineCollection", objs)
|
|
3391
|
+
|
|
3392
|
+
# WARNING: breaking change from native 'ymin' and 'ymax'
|
|
3393
|
+
@inputs._preprocess_or_redirect("x", "y1", "y2", ("c", "color", "colors"))
|
|
3394
|
+
@docstring._snippet_manager
|
|
3395
|
+
def vlines(self, *args, **kwargs):
|
|
3396
|
+
"""
|
|
3397
|
+
%(plot.vlines)s
|
|
3398
|
+
"""
|
|
3399
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3400
|
+
return self._apply_lines(*args, **kwargs)
|
|
3401
|
+
|
|
3402
|
+
# WARNING: breaking change from native 'xmin' and 'xmax'
|
|
3403
|
+
@inputs._preprocess_or_redirect("y", "x1", "x2", ("c", "color", "colors"))
|
|
3404
|
+
@docstring._snippet_manager
|
|
3405
|
+
def hlines(self, *args, **kwargs):
|
|
3406
|
+
"""
|
|
3407
|
+
%(plot.hlines)s
|
|
3408
|
+
"""
|
|
3409
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3410
|
+
return self._apply_lines(*args, **kwargs)
|
|
3411
|
+
|
|
3412
|
+
def _parse_markersize(
|
|
3413
|
+
self, s, *, smin=None, smax=None, area_size=True, absolute_size=None, **kwargs
|
|
3414
|
+
):
|
|
3415
|
+
"""
|
|
3416
|
+
Scale the marker sizes with optional keyword args.
|
|
3417
|
+
"""
|
|
3418
|
+
if s is not None:
|
|
3419
|
+
s = inputs._to_numpy_array(s)
|
|
3420
|
+
if absolute_size is None:
|
|
3421
|
+
absolute_size = s.size == 1 or _inside_seaborn_call()
|
|
3422
|
+
if not absolute_size or smin is not None or smax is not None:
|
|
3423
|
+
smin = _not_none(smin, 1)
|
|
3424
|
+
smax = _not_none(smax, rc["lines.markersize"] ** (1, 2)[area_size])
|
|
3425
|
+
dmin, dmax = inputs._safe_range(s) # data value range
|
|
3426
|
+
if dmin is not None and dmax is not None and dmin != dmax:
|
|
3427
|
+
s = smin + (smax - smin) * (s - dmin) / (dmax - dmin)
|
|
3428
|
+
s = s ** (2, 1)[area_size]
|
|
3429
|
+
return s, kwargs
|
|
3430
|
+
|
|
3431
|
+
def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
|
|
3432
|
+
"""
|
|
3433
|
+
Apply scatter or scatterx markers.
|
|
3434
|
+
"""
|
|
3435
|
+
# Manual property cycling. Converts Line2D keywords used in property
|
|
3436
|
+
# cycle to PathCollection keywords that can be passed to scatter.
|
|
3437
|
+
# NOTE: Matplotlib uses the property cycler in _get_patches_for_fill for
|
|
3438
|
+
# scatter() plots. It only ever inherits color from that. We instead use
|
|
3439
|
+
# _get_lines to help overarching goal of unifying plot() and scatter().
|
|
3440
|
+
cycle_manually = {
|
|
3441
|
+
"alpha": "alpha",
|
|
3442
|
+
"color": "c",
|
|
3443
|
+
"markerfacecolor": "c",
|
|
3444
|
+
"markeredgecolor": "edgecolors",
|
|
3445
|
+
"marker": "marker",
|
|
3446
|
+
"markersize": "s",
|
|
3447
|
+
"markeredgewidth": "linewidths",
|
|
3448
|
+
"linestyle": "linestyles",
|
|
3449
|
+
"linewidth": "linewidths",
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
kw = kwargs.copy()
|
|
3453
|
+
inbounds = kw.pop("inbounds", None)
|
|
3454
|
+
kw.update(_pop_props(kw, "collection"))
|
|
3455
|
+
kw, extents = self._inbounds_extent(inbounds=inbounds, **kw)
|
|
3456
|
+
xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, autoreverse=False, **kw)
|
|
3457
|
+
ys, kw = inputs._dist_reduce(ys, **kw)
|
|
3458
|
+
ss, kw = self._parse_markersize(ss, **kw) # parse 's'
|
|
3459
|
+
|
|
3460
|
+
# Move _parse_cycle before _parse_color
|
|
3461
|
+
kw = self._parse_cycle(xs.shape[1] if xs.ndim > 1 else 1, **kw)
|
|
3462
|
+
|
|
3463
|
+
# Only parse color if explicitly provided
|
|
3464
|
+
if cc is not None:
|
|
3465
|
+
infer_rgb = True
|
|
3466
|
+
if not isinstance(cc, str):
|
|
3467
|
+
test = np.atleast_1d(cc)
|
|
3468
|
+
if (
|
|
3469
|
+
any(_.ndim == 2 and _.shape[1] in (3, 4) for _ in (xs, ys))
|
|
3470
|
+
and test.ndim == 2
|
|
3471
|
+
and test.shape[1] in (3, 4)
|
|
3472
|
+
):
|
|
3473
|
+
infer_rgb = False
|
|
3474
|
+
cc, kw = self._parse_color(xs, ys, cc, inbounds=inbounds, apply_cycle=False, infer_rgb=infer_rgb, **kw)
|
|
3475
|
+
|
|
3476
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3477
|
+
objs = []
|
|
3478
|
+
for _, n, x, y, s, c, kw in self._iter_arg_cols(xs, ys, ss, cc, **kw):
|
|
3479
|
+
# Don't set 'c' explicitly unless it was provided
|
|
3480
|
+
kw["s"] = s
|
|
3481
|
+
if c is not None:
|
|
3482
|
+
kw["c"] = c
|
|
3483
|
+
*eb, kw = self._add_error_bars(x, y, vert=vert, default_barstds=True, **kw)
|
|
3484
|
+
*es, kw = self._add_error_shading(x, y, vert=vert, color_key="c", **kw)
|
|
3485
|
+
if not vert:
|
|
3486
|
+
x, y = y, x
|
|
3487
|
+
obj = self._call_native("scatter", x, y, **kw)
|
|
3488
|
+
self._inbounds_xylim(extents, x, y)
|
|
3489
|
+
objs.append((*eb, *es, obj) if eb or es else obj)
|
|
3490
|
+
|
|
3491
|
+
self._update_guide(objs, queue_colorbar=False, **guide_kw)
|
|
3492
|
+
return objs[0] if len(objs) == 1 else cbook.silent_list("PathCollection", objs)
|
|
3493
|
+
|
|
3494
|
+
# NOTE: Matplotlib internally applies scatter 'c' arguments as the
|
|
3495
|
+
# 'facecolors' argument to PathCollection. So perfectly reasonable to
|
|
3496
|
+
# point both 'color' and 'facecolor' arguments to the 'c' keyword here.
|
|
3497
|
+
@inputs._preprocess_or_redirect(
|
|
3498
|
+
"x",
|
|
3499
|
+
"y",
|
|
3500
|
+
_get_aliases("collection", "sizes"),
|
|
3501
|
+
_get_aliases("collection", "colors", "facecolors"),
|
|
3502
|
+
keywords=_get_aliases("collection", "linewidths", "edgecolors"),
|
|
3503
|
+
)
|
|
3504
|
+
@docstring._concatenate_inherited
|
|
3505
|
+
@docstring._snippet_manager
|
|
3506
|
+
def scatter(self, *args, **kwargs):
|
|
3507
|
+
"""
|
|
3508
|
+
%(plot.scatter)s
|
|
3509
|
+
"""
|
|
3510
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3511
|
+
return self._apply_scatter(*args, **kwargs)
|
|
3512
|
+
|
|
3513
|
+
@inputs._preprocess_or_redirect(
|
|
3514
|
+
"y",
|
|
3515
|
+
"x",
|
|
3516
|
+
_get_aliases("collection", "sizes"),
|
|
3517
|
+
_get_aliases("collection", "colors", "facecolors"),
|
|
3518
|
+
keywords=_get_aliases("collection", "linewidths", "edgecolors"),
|
|
3519
|
+
)
|
|
3520
|
+
@docstring._snippet_manager
|
|
3521
|
+
def scatterx(self, *args, **kwargs):
|
|
3522
|
+
"""
|
|
3523
|
+
%(plot.scatterx)s
|
|
3524
|
+
"""
|
|
3525
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3526
|
+
return self._apply_scatter(*args, **kwargs)
|
|
3527
|
+
|
|
3528
|
+
def _apply_fill(
|
|
3529
|
+
self,
|
|
3530
|
+
xs,
|
|
3531
|
+
ys1,
|
|
3532
|
+
ys2,
|
|
3533
|
+
where,
|
|
3534
|
+
*,
|
|
3535
|
+
vert=True,
|
|
3536
|
+
negpos=None,
|
|
3537
|
+
stack=None,
|
|
3538
|
+
stacked=None,
|
|
3539
|
+
**kwargs,
|
|
3540
|
+
):
|
|
3541
|
+
"""
|
|
3542
|
+
Apply area shading.
|
|
3543
|
+
"""
|
|
3544
|
+
# Parse input arguments
|
|
3545
|
+
kw = kwargs.copy()
|
|
3546
|
+
kw.update(_pop_props(kw, "patch"))
|
|
3547
|
+
kw, extents = self._inbounds_extent(**kw)
|
|
3548
|
+
name = "fill_between" if vert else "fill_betweenx"
|
|
3549
|
+
stack = _not_none(stack=stack, stacked=stacked)
|
|
3550
|
+
xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
|
|
3551
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
3552
|
+
|
|
3553
|
+
# Draw patches with default edge width zero
|
|
3554
|
+
y0 = 0
|
|
3555
|
+
objs, xsides, ysides = [], [], []
|
|
3556
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3557
|
+
for _, n, x, y1, y2, w, kw in self._iter_arg_cols(xs, ys1, ys2, where, **kw):
|
|
3558
|
+
kw = self._parse_cycle(n, **kw)
|
|
3559
|
+
if stack:
|
|
3560
|
+
y1 = y1 + y0 # avoid in-place modification
|
|
3561
|
+
y2 = y2 + y0
|
|
3562
|
+
y0 = y0 + y2 - y1 # irrelevant that we added y0 to both
|
|
3563
|
+
if negpos: # NOTE: if user passes 'where' will issue a warning
|
|
3564
|
+
obj = self._call_negpos(name, x, y1, y2, where=w, use_where=True, **kw)
|
|
3565
|
+
else:
|
|
3566
|
+
obj = self._call_native(name, x, y1, y2, where=w, **kw)
|
|
3567
|
+
self._fix_patch_edges(obj, **edgefix_kw, **kw)
|
|
3568
|
+
xsides.append(x)
|
|
3569
|
+
for y in (y1, y2):
|
|
3570
|
+
self._inbounds_xylim(extents, x, y, vert=vert)
|
|
3571
|
+
if y.size == 1: # add sticky edges if bounds are scalar
|
|
3572
|
+
ysides.append(y)
|
|
3573
|
+
objs.append(obj)
|
|
3574
|
+
|
|
3575
|
+
# Draw guide and add sticky edges
|
|
3576
|
+
self._update_guide(objs, **guide_kw)
|
|
3577
|
+
for axis, sides in zip("xy" if vert else "yx", (xsides, ysides)):
|
|
3578
|
+
self._fix_sticky_edges(objs, axis, *sides)
|
|
3579
|
+
return objs[0] if len(objs) == 1 else cbook.silent_list("PolyCollection", objs)
|
|
3580
|
+
|
|
3581
|
+
@docstring._snippet_manager
|
|
3582
|
+
def area(self, *args, **kwargs):
|
|
3583
|
+
"""
|
|
3584
|
+
%(plot.fill_between)s
|
|
3585
|
+
"""
|
|
3586
|
+
return self.fill_between(*args, **kwargs)
|
|
3587
|
+
|
|
3588
|
+
@docstring._snippet_manager
|
|
3589
|
+
def areax(self, *args, **kwargs):
|
|
3590
|
+
"""
|
|
3591
|
+
%(plot.fill_betweenx)s
|
|
3592
|
+
"""
|
|
3593
|
+
return self.fill_betweenx(*args, **kwargs)
|
|
3594
|
+
|
|
3595
|
+
@inputs._preprocess_or_redirect("x", "y1", "y2", "where")
|
|
3596
|
+
@docstring._concatenate_inherited
|
|
3597
|
+
@docstring._snippet_manager
|
|
3598
|
+
def fill_between(self, *args, **kwargs):
|
|
3599
|
+
"""
|
|
3600
|
+
%(plot.fill_between)s
|
|
3601
|
+
"""
|
|
3602
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3603
|
+
return self._apply_fill(*args, **kwargs)
|
|
3604
|
+
|
|
3605
|
+
@inputs._preprocess_or_redirect("y", "x1", "x2", "where")
|
|
3606
|
+
@docstring._concatenate_inherited
|
|
3607
|
+
@docstring._snippet_manager
|
|
3608
|
+
def fill_betweenx(self, *args, **kwargs):
|
|
3609
|
+
"""
|
|
3610
|
+
%(plot.fill_betweenx)s
|
|
3611
|
+
"""
|
|
3612
|
+
# NOTE: The 'horizontal' orientation will be inferred by downstream
|
|
3613
|
+
# wrappers using the function name.
|
|
3614
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3615
|
+
return self._apply_fill(*args, **kwargs)
|
|
3616
|
+
|
|
3617
|
+
@staticmethod
|
|
3618
|
+
def _convert_bar_width(x, width=1):
|
|
3619
|
+
"""
|
|
3620
|
+
Convert bar plot widths from relative to coordinate spacing. Relative
|
|
3621
|
+
widths are much more convenient for users.
|
|
3622
|
+
"""
|
|
3623
|
+
# WARNING: This will fail for non-numeric non-datetime64 singleton
|
|
3624
|
+
# datatypes but this is good enough for vast majority of cases.
|
|
3625
|
+
x_test = inputs._to_numpy_array(x)
|
|
3626
|
+
if len(x_test) >= 2:
|
|
3627
|
+
x_step = x_test[1:] - x_test[:-1]
|
|
3628
|
+
x_step = np.concatenate((x_step, x_step[-1:]))
|
|
3629
|
+
elif x_test.dtype == np.datetime64:
|
|
3630
|
+
x_step = np.timedelta64(1, "D")
|
|
3631
|
+
else:
|
|
3632
|
+
x_step = np.array(0.5)
|
|
3633
|
+
if np.issubdtype(x_test.dtype, np.datetime64):
|
|
3634
|
+
# Avoid integer timedelta truncation
|
|
3635
|
+
x_step = x_step.astype("timedelta64[ns]")
|
|
3636
|
+
return width * x_step
|
|
3637
|
+
|
|
3638
|
+
def _apply_bar(
|
|
3639
|
+
self,
|
|
3640
|
+
xs,
|
|
3641
|
+
hs,
|
|
3642
|
+
ws,
|
|
3643
|
+
bs,
|
|
3644
|
+
*,
|
|
3645
|
+
absolute_width=None,
|
|
3646
|
+
stack=None,
|
|
3647
|
+
stacked=None,
|
|
3648
|
+
negpos=False,
|
|
3649
|
+
orientation="vertical",
|
|
3650
|
+
**kwargs,
|
|
3651
|
+
):
|
|
3652
|
+
"""
|
|
3653
|
+
Apply bar or barh command. Support default "minima" at zero.
|
|
3654
|
+
"""
|
|
3655
|
+
# Parse args
|
|
3656
|
+
kw = kwargs.copy()
|
|
3657
|
+
kw, extents = self._inbounds_extent(**kw)
|
|
3658
|
+
name = "barh" if orientation == "horizontal" else "bar"
|
|
3659
|
+
stack = _not_none(stack=stack, stacked=stacked)
|
|
3660
|
+
xs, hs, kw = self._parse_1d_args(xs, hs, orientation=orientation, **kw)
|
|
3661
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
3662
|
+
if absolute_width is None:
|
|
3663
|
+
absolute_width = _inside_seaborn_call()
|
|
3664
|
+
|
|
3665
|
+
# Call func after converting bar width
|
|
3666
|
+
b0 = 0
|
|
3667
|
+
objs = []
|
|
3668
|
+
kw.update(_pop_props(kw, "patch"))
|
|
3669
|
+
hs, kw = inputs._dist_reduce(hs, **kw)
|
|
3670
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
3671
|
+
alphas = kw.pop("alpha", None)
|
|
3672
|
+
if alphas is None:
|
|
3673
|
+
alphas = xs.size * [None]
|
|
3674
|
+
elif isinstance(alphas, Number):
|
|
3675
|
+
alphas = xs.size * [alphas]
|
|
3676
|
+
elif len(alphas) != xs.size:
|
|
3677
|
+
raise ValueError(
|
|
3678
|
+
f"Received {len(alphas)} values for alpha but needed {xs.size}"
|
|
3679
|
+
)
|
|
3680
|
+
for i, n, x, h, w, b, kw in self._iter_arg_cols(xs, hs, ws, bs, **kw):
|
|
3681
|
+
kw = self._parse_cycle(n, **kw)
|
|
3682
|
+
# Adjust x or y coordinates for grouped and stacked bars
|
|
3683
|
+
w = _not_none(w, np.array([0.8])) # same as mpl but in *relative* units
|
|
3684
|
+
b = _not_none(b, np.array([0.0])) # same as mpl
|
|
3685
|
+
if not absolute_width:
|
|
3686
|
+
w = self._convert_bar_width(x, w)
|
|
3687
|
+
if stack:
|
|
3688
|
+
b = b + b0
|
|
3689
|
+
b0 = b0 + h
|
|
3690
|
+
else: # instead "group" the bars (this is no-op if we have 1 column)
|
|
3691
|
+
w = w / n # rescaled
|
|
3692
|
+
o = 0.5 * (n - 1) # center coordinate
|
|
3693
|
+
x = x + w * (i - o) # += may cause integer/float casting issue
|
|
3694
|
+
# Draw simple bars
|
|
3695
|
+
*eb, kw = self._add_error_bars(
|
|
3696
|
+
x, b + h, default_barstds=True, orientation=orientation, **kw
|
|
3697
|
+
) # noqa: E501
|
|
3698
|
+
if negpos:
|
|
3699
|
+
obj = self._call_negpos(name, x, h, w, b, use_zero=True, **kw)
|
|
3700
|
+
else:
|
|
3701
|
+
obj = self._call_native(name, x, h, w, b, **kw)
|
|
3702
|
+
self._fix_patch_edges(obj, **edgefix_kw, **kw)
|
|
3703
|
+
for y in (b, b + h):
|
|
3704
|
+
self._inbounds_xylim(extents, x, y, orientation=orientation)
|
|
3705
|
+
|
|
3706
|
+
if alphas[i] is not None:
|
|
3707
|
+
for child in obj.get_children():
|
|
3708
|
+
child.set_alpha(alphas[i])
|
|
3709
|
+
objs.append((*eb, obj) if eb else obj)
|
|
3710
|
+
|
|
3711
|
+
self._update_guide(objs, **guide_kw)
|
|
3712
|
+
return objs[0] if len(objs) == 1 else cbook.silent_list("BarContainer", objs)
|
|
3713
|
+
|
|
3714
|
+
@inputs._preprocess_or_redirect("x", "height", "width", "bottom")
|
|
3715
|
+
@docstring._concatenate_inherited
|
|
3716
|
+
@docstring._snippet_manager
|
|
3717
|
+
def bar(self, *args, **kwargs):
|
|
3718
|
+
"""
|
|
3719
|
+
%(plot.bar)s
|
|
3720
|
+
"""
|
|
3721
|
+
kwargs = _parse_vert(default_orientation="vertical", **kwargs)
|
|
3722
|
+
return self._apply_bar(*args, **kwargs)
|
|
3723
|
+
|
|
3724
|
+
# WARNING: Swap 'height' and 'width' here so that they are always relative
|
|
3725
|
+
# to the 'tall' axis. This lets people always pass 'width' as keyword
|
|
3726
|
+
@inputs._preprocess_or_redirect("y", "height", "width", "left")
|
|
3727
|
+
@docstring._concatenate_inherited
|
|
3728
|
+
@docstring._snippet_manager
|
|
3729
|
+
def barh(self, *args, **kwargs):
|
|
3730
|
+
"""
|
|
3731
|
+
%(plot.barh)s
|
|
3732
|
+
"""
|
|
3733
|
+
kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
|
|
3734
|
+
return self._apply_bar(*args, **kwargs)
|
|
3735
|
+
|
|
3736
|
+
# WARNING: 'labels' and 'colors' no longer passed through `data` (seems like
|
|
3737
|
+
# extremely niche usage... `data` variables should be data-like)
|
|
3738
|
+
@inputs._preprocess_or_redirect("x", "explode")
|
|
3739
|
+
@docstring._concatenate_inherited
|
|
3740
|
+
@docstring._snippet_manager
|
|
3741
|
+
def pie(self, x, explode, *, labelpad=None, labeldistance=None, **kwargs):
|
|
3742
|
+
"""
|
|
3743
|
+
%(plot.pie)s
|
|
3744
|
+
"""
|
|
3745
|
+
kw = kwargs.copy()
|
|
3746
|
+
pad = _not_none(labeldistance=labeldistance, labelpad=labelpad, default=1.15)
|
|
3747
|
+
wedge_kw = kw.pop("wedgeprops", None) or {}
|
|
3748
|
+
wedge_kw.update(_pop_props(kw, "patch"))
|
|
3749
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
3750
|
+
_, x, kw = self._parse_1d_args(
|
|
3751
|
+
x, autox=False, autoy=False, autoreverse=False, **kw
|
|
3752
|
+
)
|
|
3753
|
+
kw = self._parse_cycle(x.size, **kw)
|
|
3754
|
+
objs = self._call_native(
|
|
3755
|
+
"pie", x, explode, labeldistance=pad, wedgeprops=wedge_kw, **kw
|
|
3756
|
+
)
|
|
3757
|
+
objs = tuple(cbook.silent_list(type(seq[0]).__name__, seq) for seq in objs)
|
|
3758
|
+
self._fix_patch_edges(objs[0], **edgefix_kw, **wedge_kw)
|
|
3759
|
+
return objs
|
|
3760
|
+
|
|
3761
|
+
@staticmethod
|
|
3762
|
+
def _parse_box_violin(fillcolor, fillalpha, edgecolor, **kw):
|
|
3763
|
+
"""
|
|
3764
|
+
Parse common boxplot and violinplot arguments.
|
|
3765
|
+
"""
|
|
3766
|
+
if isinstance(fillcolor, list):
|
|
3767
|
+
warnings._warn_ultraplot(
|
|
3768
|
+
"Passing lists to fillcolor was deprecated in v0.9. Please use "
|
|
3769
|
+
f"the property cycler with e.g. cycle={fillcolor!r} instead."
|
|
3770
|
+
)
|
|
3771
|
+
kw["cycle"] = _not_none(cycle=kw.get("cycle", None), fillcolor=fillcolor)
|
|
3772
|
+
fillcolor = None
|
|
3773
|
+
if isinstance(fillalpha, list):
|
|
3774
|
+
warnings._warn_ultraplot(
|
|
3775
|
+
"Passing lists to fillalpha was removed in v0.9. Please specify "
|
|
3776
|
+
"different opacities using the property cycle colors instead."
|
|
3777
|
+
)
|
|
3778
|
+
fillalpha = fillalpha[0] # too complicated to try to apply this
|
|
3779
|
+
if isinstance(edgecolor, list):
|
|
3780
|
+
warnings._warn_ultraplot(
|
|
3781
|
+
"Passing lists of edgecolors was removed in v0.9. Please call the "
|
|
3782
|
+
"plotting command multiple times with different edge colors instead."
|
|
3783
|
+
)
|
|
3784
|
+
edgecolor = edgecolor[0]
|
|
3785
|
+
return fillcolor, fillalpha, edgecolor, kw
|
|
3786
|
+
|
|
3787
|
+
def _apply_boxplot(
|
|
3788
|
+
self,
|
|
3789
|
+
x,
|
|
3790
|
+
y,
|
|
3791
|
+
*,
|
|
3792
|
+
mean=None,
|
|
3793
|
+
means=None,
|
|
3794
|
+
vert=True,
|
|
3795
|
+
fill=None,
|
|
3796
|
+
filled=None,
|
|
3797
|
+
marker=None,
|
|
3798
|
+
markersize=None,
|
|
3799
|
+
**kwargs,
|
|
3800
|
+
):
|
|
3801
|
+
"""
|
|
3802
|
+
Apply the box plot.
|
|
3803
|
+
"""
|
|
3804
|
+
# Global and fill properties
|
|
3805
|
+
kw = kwargs.copy()
|
|
3806
|
+
kw.update(_pop_props(kw, "patch"))
|
|
3807
|
+
fill = _not_none(fill=fill, filled=filled)
|
|
3808
|
+
means = _not_none(mean=mean, means=means, showmeans=kw.get("showmeans"))
|
|
3809
|
+
linewidth = kw.pop("linewidth", rc["patch.linewidth"])
|
|
3810
|
+
edgecolor = kw.pop("edgecolor", "black")
|
|
3811
|
+
fillcolor = kw.pop("facecolor", None)
|
|
3812
|
+
fillalpha = kw.pop("alpha", None)
|
|
3813
|
+
fillcolor, fillalpha, edgecolor, kw = self._parse_box_violin(
|
|
3814
|
+
fillcolor, fillalpha, edgecolor, **kw
|
|
3815
|
+
)
|
|
3816
|
+
if fill is None:
|
|
3817
|
+
fill = fillcolor is not None or fillalpha is not None
|
|
3818
|
+
fill = fill or kw.get("cycle") is not None
|
|
3819
|
+
|
|
3820
|
+
# Parse non-color properties
|
|
3821
|
+
# NOTE: Output dict keys are plural but we use singular for keyword args
|
|
3822
|
+
props = {}
|
|
3823
|
+
for key in (
|
|
3824
|
+
"boxes",
|
|
3825
|
+
"whiskers",
|
|
3826
|
+
"caps",
|
|
3827
|
+
"fliers",
|
|
3828
|
+
"medians",
|
|
3829
|
+
"means",
|
|
3830
|
+
"hatches",
|
|
3831
|
+
):
|
|
3832
|
+
prefix = key.rstrip("es") # singular form
|
|
3833
|
+
props[key] = iprops = _pop_props(kw, "line", prefix=prefix)
|
|
3834
|
+
iprops.setdefault("color", edgecolor)
|
|
3835
|
+
iprops.setdefault("linewidth", linewidth)
|
|
3836
|
+
iprops.setdefault("markeredgecolor", edgecolor)
|
|
3837
|
+
|
|
3838
|
+
# Parse color properties
|
|
3839
|
+
x, y, kw = self._parse_1d_args(
|
|
3840
|
+
x, y, autoy=False, autoguide=False, vert=vert, **kw
|
|
3841
|
+
)
|
|
3842
|
+
kw = self._parse_cycle(x.size, **kw) # possibly apply cycle
|
|
3843
|
+
if fill and fillcolor is None:
|
|
3844
|
+
parser = self._get_patches_for_fill
|
|
3845
|
+
fillcolor = [parser.get_next_color() for _ in range(x.size)]
|
|
3846
|
+
else:
|
|
3847
|
+
fillcolor = [fillcolor] * x.size
|
|
3848
|
+
|
|
3849
|
+
# Plot boxes
|
|
3850
|
+
kw.setdefault("positions", x)
|
|
3851
|
+
if means:
|
|
3852
|
+
kw["showmeans"] = kw["meanline"] = True
|
|
3853
|
+
y = inputs._dist_clean(y)
|
|
3854
|
+
# Add hatch to props as boxplot does not have a hatch but Rectangle does
|
|
3855
|
+
hatch = kw.pop("hatch", None)
|
|
3856
|
+
if hatch is None:
|
|
3857
|
+
hatch = [None for _ in range(x.size)]
|
|
3858
|
+
|
|
3859
|
+
artists = self._call_native("boxplot", y, vert=vert, **kw)
|
|
3860
|
+
artists = artists or {} # necessary?
|
|
3861
|
+
artists = {
|
|
3862
|
+
key: cbook.silent_list(type(objs[0]).__name__, objs) if objs else objs
|
|
3863
|
+
for key, objs in artists.items()
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
# Modify artist settings
|
|
3867
|
+
|
|
3868
|
+
for key, aprops in props.items():
|
|
3869
|
+
if key not in artists: # possible if not rendered
|
|
3870
|
+
continue
|
|
3871
|
+
objs = artists[key]
|
|
3872
|
+
for i, obj in enumerate(objs):
|
|
3873
|
+
# Update lines used for boxplot components
|
|
3874
|
+
# TODO: Test this thoroughly!
|
|
3875
|
+
iprops = {
|
|
3876
|
+
key: (
|
|
3877
|
+
value[i // 2 if key in ("caps", "whiskers") else i]
|
|
3878
|
+
if isinstance(value, (list, np.ndarray))
|
|
3879
|
+
else value
|
|
3880
|
+
)
|
|
3881
|
+
for key, value in aprops.items()
|
|
3882
|
+
}
|
|
3883
|
+
obj.update(iprops)
|
|
3884
|
+
# "Filled" boxplot by adding patch beneath line path
|
|
3885
|
+
if key == "boxes" and (
|
|
3886
|
+
fillcolor[i] is not None
|
|
3887
|
+
or fillalpha is not None
|
|
3888
|
+
or hatch[i] is not None
|
|
3889
|
+
):
|
|
3890
|
+
patch = mpatches.PathPatch(
|
|
3891
|
+
obj.get_path(),
|
|
3892
|
+
linewidth=0.0,
|
|
3893
|
+
facecolor=fillcolor[i],
|
|
3894
|
+
alpha=fillalpha,
|
|
3895
|
+
hatch=hatch[i],
|
|
3896
|
+
)
|
|
3897
|
+
self.add_artist(patch)
|
|
3898
|
+
# Outlier markers
|
|
3899
|
+
if key == "fliers":
|
|
3900
|
+
if marker is not None:
|
|
3901
|
+
obj.set_marker(marker)
|
|
3902
|
+
if markersize is not None:
|
|
3903
|
+
obj.set_markersize(markersize)
|
|
3904
|
+
|
|
3905
|
+
return artists
|
|
3906
|
+
|
|
3907
|
+
@docstring._snippet_manager
|
|
3908
|
+
def box(self, *args, **kwargs):
|
|
3909
|
+
"""
|
|
3910
|
+
%(plot.boxplot)s
|
|
3911
|
+
"""
|
|
3912
|
+
return self.boxplot(*args, **kwargs)
|
|
3913
|
+
|
|
3914
|
+
@docstring._snippet_manager
|
|
3915
|
+
def boxh(self, *args, **kwargs):
|
|
3916
|
+
"""
|
|
3917
|
+
%(plot.boxploth)s
|
|
3918
|
+
"""
|
|
3919
|
+
return self.boxploth(*args, **kwargs)
|
|
3920
|
+
|
|
3921
|
+
@inputs._preprocess_or_redirect("positions", "y")
|
|
3922
|
+
@docstring._concatenate_inherited
|
|
3923
|
+
@docstring._snippet_manager
|
|
3924
|
+
def boxplot(self, *args, **kwargs):
|
|
3925
|
+
"""
|
|
3926
|
+
%(plot.boxplot)s
|
|
3927
|
+
"""
|
|
3928
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
3929
|
+
return self._apply_boxplot(*args, **kwargs)
|
|
3930
|
+
|
|
3931
|
+
@inputs._preprocess_or_redirect("positions", "x")
|
|
3932
|
+
@docstring._snippet_manager
|
|
3933
|
+
def boxploth(self, *args, **kwargs):
|
|
3934
|
+
"""
|
|
3935
|
+
%(plot.boxploth)s
|
|
3936
|
+
"""
|
|
3937
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
3938
|
+
return self._apply_boxplot(*args, **kwargs)
|
|
3939
|
+
|
|
3940
|
+
def _apply_violinplot(
|
|
3941
|
+
self,
|
|
3942
|
+
x,
|
|
3943
|
+
y,
|
|
3944
|
+
vert=True,
|
|
3945
|
+
mean=None,
|
|
3946
|
+
means=None,
|
|
3947
|
+
median=None,
|
|
3948
|
+
medians=None,
|
|
3949
|
+
showmeans=None,
|
|
3950
|
+
showmedians=None,
|
|
3951
|
+
showextrema=None,
|
|
3952
|
+
**kwargs,
|
|
3953
|
+
):
|
|
3954
|
+
"""
|
|
3955
|
+
Apply the violinplot.
|
|
3956
|
+
"""
|
|
3957
|
+
# Parse keyword args
|
|
3958
|
+
kw = kwargs.copy()
|
|
3959
|
+
kw.update(_pop_props(kw, "patch"))
|
|
3960
|
+
kw.setdefault("capsize", 0) # caps are redundant for violin plots
|
|
3961
|
+
means = _not_none(mean=mean, means=means, showmeans=showmeans)
|
|
3962
|
+
medians = _not_none(median=median, medians=medians, showmedians=showmedians)
|
|
3963
|
+
if showextrema:
|
|
3964
|
+
kw["default_barpctiles"] = True
|
|
3965
|
+
if not means and not medians:
|
|
3966
|
+
medians = _not_none(medians, True)
|
|
3967
|
+
linewidth = kw.pop("linewidth", None)
|
|
3968
|
+
edgecolor = kw.pop("edgecolor", "black")
|
|
3969
|
+
fillcolor = kw.pop("facecolor", None)
|
|
3970
|
+
fillalpha = kw.pop("alpha", None)
|
|
3971
|
+
fillcolor, fillalpha, edgecolor, kw = self._parse_box_violin(
|
|
3972
|
+
fillcolor, fillalpha, edgecolor, **kw
|
|
3973
|
+
)
|
|
3974
|
+
|
|
3975
|
+
# Parse color properties
|
|
3976
|
+
x, y, kw = self._parse_1d_args(
|
|
3977
|
+
x, y, autoy=False, autoguide=False, vert=vert, **kw
|
|
3978
|
+
)
|
|
3979
|
+
kw = self._parse_cycle(x.size, **kw)
|
|
3980
|
+
if fillcolor is None:
|
|
3981
|
+
parser = self._get_patches_for_fill
|
|
3982
|
+
fillcolor = [parser.get_next_color() for _ in range(x.size)]
|
|
3983
|
+
else:
|
|
3984
|
+
fillcolor = [fillcolor] * x.size
|
|
3985
|
+
|
|
3986
|
+
# Plot violins
|
|
3987
|
+
y, kw = inputs._dist_reduce(y, means=means, medians=medians, **kw)
|
|
3988
|
+
*eb, kw = self._add_error_bars(
|
|
3989
|
+
x, y, vert=vert, default_boxstds=True, default_marker=True, **kw
|
|
3990
|
+
) # noqa: E501
|
|
3991
|
+
kw.pop("labels", None) # already applied in _parse_1d_args
|
|
3992
|
+
kw.setdefault("positions", x) # coordinates passed as keyword
|
|
3993
|
+
y = _not_none(kw.pop("distribution"), y) # i.e. was reduced
|
|
3994
|
+
y = inputs._dist_clean(y)
|
|
3995
|
+
|
|
3996
|
+
hatches = None
|
|
3997
|
+
if "hatch" in kw:
|
|
3998
|
+
hatches = kw.pop("hatch", None)
|
|
3999
|
+
if "hatches" in kw:
|
|
4000
|
+
hatches = kw.pop("hatches", None)
|
|
4001
|
+
|
|
4002
|
+
if hatches is None:
|
|
4003
|
+
hatches = len(y) * [None]
|
|
4004
|
+
elif len(hatches) != len(y):
|
|
4005
|
+
raise ValueError(f"Retrieved {len(hatches)} hatches but need {len(y)}")
|
|
4006
|
+
|
|
4007
|
+
artists = self._call_native(
|
|
4008
|
+
"violinplot",
|
|
4009
|
+
y,
|
|
4010
|
+
vert=vert,
|
|
4011
|
+
showmeans=False,
|
|
4012
|
+
showmedians=False,
|
|
4013
|
+
showextrema=False,
|
|
4014
|
+
**kw,
|
|
4015
|
+
)
|
|
4016
|
+
|
|
4017
|
+
# Modify body settings
|
|
4018
|
+
artists = artists or {} # necessary?
|
|
4019
|
+
bodies = artists.pop("bodies", ()) # should be no other entries
|
|
4020
|
+
if bodies:
|
|
4021
|
+
bodies = cbook.silent_list(type(bodies[0]).__name__, bodies)
|
|
4022
|
+
for i, body in enumerate(bodies):
|
|
4023
|
+
body.set_alpha(1.0) # change default to 1.0
|
|
4024
|
+
if fillcolor[i] is not None:
|
|
4025
|
+
body.set_facecolor(fillcolor[i])
|
|
4026
|
+
if fillalpha is not None:
|
|
4027
|
+
body.set_alpha(fillalpha[i])
|
|
4028
|
+
if edgecolor is not None:
|
|
4029
|
+
body.set_edgecolor(edgecolor)
|
|
4030
|
+
if linewidth is not None:
|
|
4031
|
+
body.set_linewidths(linewidth)
|
|
4032
|
+
if hatches[i] is not None:
|
|
4033
|
+
body.set_hatch(hatches[i])
|
|
4034
|
+
return (bodies, *eb) if eb else bodies
|
|
4035
|
+
|
|
4036
|
+
@docstring._snippet_manager
|
|
4037
|
+
def violin(self, *args, **kwargs):
|
|
4038
|
+
"""
|
|
4039
|
+
%(plot.violinplot)s
|
|
4040
|
+
"""
|
|
4041
|
+
# WARNING: This disables use of 'violin' by users but
|
|
4042
|
+
# probably very few people use this anyway.
|
|
4043
|
+
if getattr(self, "_internal_call", None):
|
|
4044
|
+
return super().violin(*args, **kwargs)
|
|
4045
|
+
else:
|
|
4046
|
+
return self.violinplot(*args, **kwargs)
|
|
4047
|
+
|
|
4048
|
+
@docstring._snippet_manager
|
|
4049
|
+
def violinh(self, *args, **kwargs):
|
|
4050
|
+
"""
|
|
4051
|
+
%(plot.violinploth)s
|
|
4052
|
+
"""
|
|
4053
|
+
return self.violinploth(*args, **kwargs)
|
|
4054
|
+
|
|
4055
|
+
@inputs._preprocess_or_redirect("positions", "y")
|
|
4056
|
+
@docstring._concatenate_inherited
|
|
4057
|
+
@docstring._snippet_manager
|
|
4058
|
+
def violinplot(self, *args, **kwargs):
|
|
4059
|
+
"""
|
|
4060
|
+
%(plot.violinplot)s
|
|
4061
|
+
"""
|
|
4062
|
+
kwargs = _parse_vert(default_vert=True, **kwargs)
|
|
4063
|
+
return self._apply_violinplot(*args, **kwargs)
|
|
4064
|
+
|
|
4065
|
+
@inputs._preprocess_or_redirect("positions", "x")
|
|
4066
|
+
@docstring._snippet_manager
|
|
4067
|
+
def violinploth(self, *args, **kwargs):
|
|
4068
|
+
"""
|
|
4069
|
+
%(plot.violinploth)s
|
|
4070
|
+
"""
|
|
4071
|
+
kwargs = _parse_vert(default_vert=False, **kwargs)
|
|
4072
|
+
return self._apply_violinplot(*args, **kwargs)
|
|
4073
|
+
|
|
4074
|
+
def _apply_hist(
|
|
4075
|
+
self,
|
|
4076
|
+
xs,
|
|
4077
|
+
bins,
|
|
4078
|
+
*,
|
|
4079
|
+
width=None,
|
|
4080
|
+
rwidth=None,
|
|
4081
|
+
stack=None,
|
|
4082
|
+
stacked=None,
|
|
4083
|
+
fill=None,
|
|
4084
|
+
filled=None,
|
|
4085
|
+
histtype=None,
|
|
4086
|
+
orientation="vertical",
|
|
4087
|
+
**kwargs,
|
|
4088
|
+
):
|
|
4089
|
+
"""
|
|
4090
|
+
Apply the histogram.
|
|
4091
|
+
"""
|
|
4092
|
+
# NOTE: While Axes.bar() adds labels to the container Axes.hist() only
|
|
4093
|
+
# adds them to the first elements in the container for each column
|
|
4094
|
+
# of the input data. Make sure that legend() will read both containers
|
|
4095
|
+
# and individual items inside those containers.
|
|
4096
|
+
_, xs, kw = self._parse_1d_args(
|
|
4097
|
+
xs, autoreverse=False, orientation=orientation, **kwargs
|
|
4098
|
+
)
|
|
4099
|
+
fill = _not_none(fill=fill, filled=filled)
|
|
4100
|
+
stack = _not_none(stack=stack, stacked=stacked)
|
|
4101
|
+
if fill is not None:
|
|
4102
|
+
histtype = _not_none(histtype, "stepfilled" if fill else "step")
|
|
4103
|
+
if stack is not None:
|
|
4104
|
+
histtype = _not_none(histtype, "barstacked" if stack else "bar")
|
|
4105
|
+
kw["bins"] = bins
|
|
4106
|
+
kw["label"] = kw.pop("labels", None) # multiple labels are natively supported
|
|
4107
|
+
kw["rwidth"] = _not_none(width=width, rwidth=rwidth) # latter is native
|
|
4108
|
+
kw["histtype"] = histtype = _not_none(histtype, "bar")
|
|
4109
|
+
kw.update(_pop_props(kw, "patch"))
|
|
4110
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4111
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4112
|
+
n = xs.shape[1] if xs.ndim > 1 else 1
|
|
4113
|
+
kw = self._parse_cycle(n, **kw)
|
|
4114
|
+
obj = self._call_native("hist", xs, orientation=orientation, **kw)
|
|
4115
|
+
if histtype.startswith("bar"):
|
|
4116
|
+
self._fix_patch_edges(obj[2], **edgefix_kw, **kw)
|
|
4117
|
+
# Revert to mpl < 3.3 behavior where silent_list was always returned for
|
|
4118
|
+
# non-bar-type histograms. Because consistency.
|
|
4119
|
+
res = obj[2]
|
|
4120
|
+
if type(res) is list: # 'step' histtype plots
|
|
4121
|
+
res = cbook.silent_list("Polygon", res)
|
|
4122
|
+
obj = (*obj[:2], res)
|
|
4123
|
+
else:
|
|
4124
|
+
for i, sub in enumerate(res):
|
|
4125
|
+
if type(sub) is list:
|
|
4126
|
+
res[i] = cbook.silent_list("Polygon", sub)
|
|
4127
|
+
self._update_guide(res, **guide_kw)
|
|
4128
|
+
return obj
|
|
4129
|
+
|
|
4130
|
+
@inputs._preprocess_or_redirect("x", "bins", keywords="weights")
|
|
4131
|
+
@docstring._concatenate_inherited
|
|
4132
|
+
@docstring._snippet_manager
|
|
4133
|
+
def hist(self, *args, **kwargs):
|
|
4134
|
+
"""
|
|
4135
|
+
%(plot.hist)s
|
|
4136
|
+
"""
|
|
4137
|
+
kwargs = _parse_vert(default_orientation="vertical", **kwargs)
|
|
4138
|
+
return self._apply_hist(*args, **kwargs)
|
|
4139
|
+
|
|
4140
|
+
@inputs._preprocess_or_redirect("y", "bins", keywords="weights")
|
|
4141
|
+
@docstring._snippet_manager
|
|
4142
|
+
def histh(self, *args, **kwargs):
|
|
4143
|
+
"""
|
|
4144
|
+
%(plot.histh)s
|
|
4145
|
+
"""
|
|
4146
|
+
kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
|
|
4147
|
+
return self._apply_hist(*args, **kwargs)
|
|
4148
|
+
|
|
4149
|
+
@inputs._preprocess_or_redirect("x", "y", "bins", keywords="weights")
|
|
4150
|
+
@docstring._concatenate_inherited
|
|
4151
|
+
@docstring._snippet_manager
|
|
4152
|
+
def hist2d(self, x, y, bins, **kwargs):
|
|
4153
|
+
"""
|
|
4154
|
+
%(plot.hist2d)s
|
|
4155
|
+
"""
|
|
4156
|
+
# Rely on the pcolormesh() override for this.
|
|
4157
|
+
if bins is not None:
|
|
4158
|
+
kwargs["bins"] = bins
|
|
4159
|
+
return super().hist2d(x, y, autoreverse=False, default_discrete=False, **kwargs)
|
|
4160
|
+
|
|
4161
|
+
# WARNING: breaking change from native 'C'
|
|
4162
|
+
@inputs._preprocess_or_redirect("x", "y", "weights")
|
|
4163
|
+
@docstring._concatenate_inherited
|
|
4164
|
+
@docstring._snippet_manager
|
|
4165
|
+
def hexbin(self, x, y, weights, **kwargs):
|
|
4166
|
+
"""
|
|
4167
|
+
%(plot.hexbin)s
|
|
4168
|
+
"""
|
|
4169
|
+
# WARNING: Cannot use automatic level generation here until counts are
|
|
4170
|
+
# estimated. Inside _parse_level_vals if no manual levels were provided then
|
|
4171
|
+
# _parse_level_num is skipped and args like levels=10 or locator=5 are ignored
|
|
4172
|
+
kw = kwargs.copy()
|
|
4173
|
+
x, y, kw = self._parse_1d_args(x, y, autoreverse=False, autovalues=True, **kw)
|
|
4174
|
+
kw.update(_pop_props(kw, "collection")) # takes LineCollection props
|
|
4175
|
+
kw = self._parse_cmap(x, y, y, skip_autolev=True, default_discrete=False, **kw)
|
|
4176
|
+
norm = kw.get("norm", None)
|
|
4177
|
+
if norm is not None and not isinstance(norm, pcolors.DiscreteNorm):
|
|
4178
|
+
norm.vmin = norm.vmax = None # remove nonsense values
|
|
4179
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4180
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4181
|
+
m = self._call_native("hexbin", x, y, weights, **kw)
|
|
4182
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4183
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4184
|
+
return m
|
|
4185
|
+
|
|
4186
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4187
|
+
@docstring._concatenate_inherited
|
|
4188
|
+
@docstring._snippet_manager
|
|
4189
|
+
def contour(self, x, y, z, **kwargs):
|
|
4190
|
+
"""
|
|
4191
|
+
%(plot.contour)s
|
|
4192
|
+
"""
|
|
4193
|
+
x, y, z, kw = self._parse_2d_args(x, y, z, **kwargs)
|
|
4194
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4195
|
+
kw = self._parse_cmap(
|
|
4196
|
+
x, y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
|
|
4197
|
+
)
|
|
4198
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4199
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4200
|
+
label = kw.pop("label", None)
|
|
4201
|
+
m = self._call_native("contour", x, y, z, **kw)
|
|
4202
|
+
m._legend_label = label
|
|
4203
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4204
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4205
|
+
return m
|
|
4206
|
+
|
|
4207
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4208
|
+
@docstring._concatenate_inherited
|
|
4209
|
+
@docstring._snippet_manager
|
|
4210
|
+
def contourf(self, x, y, z, **kwargs):
|
|
4211
|
+
"""
|
|
4212
|
+
%(plot.contourf)s
|
|
4213
|
+
"""
|
|
4214
|
+
x, y, z, kw = self._parse_2d_args(x, y, z, **kwargs)
|
|
4215
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4216
|
+
kw = self._parse_cmap(x, y, z, plot_contours=True, **kw)
|
|
4217
|
+
contour_kw = _pop_kwargs(kw, "edgecolors", "linewidths", "linestyles")
|
|
4218
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4219
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4220
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4221
|
+
label = kw.pop("label", None)
|
|
4222
|
+
m = cm = self._call_native("contourf", x, y, z, **kw)
|
|
4223
|
+
m._legend_label = label
|
|
4224
|
+
self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # no-op if not contour_kw
|
|
4225
|
+
if contour_kw or labels_kw:
|
|
4226
|
+
cm = self._fix_contour_edges("contour", x, y, z, **kw, **contour_kw)
|
|
4227
|
+
self._add_auto_labels(m, cm, **labels_kw)
|
|
4228
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4229
|
+
return m
|
|
4230
|
+
|
|
4231
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4232
|
+
@docstring._concatenate_inherited
|
|
4233
|
+
@docstring._snippet_manager
|
|
4234
|
+
def pcolor(self, x, y, z, **kwargs):
|
|
4235
|
+
"""
|
|
4236
|
+
%(plot.pcolor)s
|
|
4237
|
+
"""
|
|
4238
|
+
x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
|
|
4239
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4240
|
+
kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
|
|
4241
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4242
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4243
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4244
|
+
with self._keep_grid_bools():
|
|
4245
|
+
m = self._call_native("pcolor", x, y, z, **kw)
|
|
4246
|
+
self._fix_patch_edges(m, **edgefix_kw, **kw)
|
|
4247
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4248
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4249
|
+
return m
|
|
4250
|
+
|
|
4251
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4252
|
+
@docstring._concatenate_inherited
|
|
4253
|
+
@docstring._snippet_manager
|
|
4254
|
+
def pcolormesh(self, x, y, z, **kwargs):
|
|
4255
|
+
"""
|
|
4256
|
+
%(plot.pcolormesh)s
|
|
4257
|
+
"""
|
|
4258
|
+
x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
|
|
4259
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4260
|
+
kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
|
|
4261
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4262
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4263
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4264
|
+
with self._keep_grid_bools():
|
|
4265
|
+
m = self._call_native("pcolormesh", x, y, z, **kw)
|
|
4266
|
+
self._fix_patch_edges(m, **edgefix_kw, **kw)
|
|
4267
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4268
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4269
|
+
return m
|
|
4270
|
+
|
|
4271
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4272
|
+
@docstring._concatenate_inherited
|
|
4273
|
+
@docstring._snippet_manager
|
|
4274
|
+
def pcolorfast(self, x, y, z, **kwargs):
|
|
4275
|
+
"""
|
|
4276
|
+
%(plot.pcolorfast)s
|
|
4277
|
+
"""
|
|
4278
|
+
x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
|
|
4279
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4280
|
+
kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
|
|
4281
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4282
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4283
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4284
|
+
with self._keep_grid_bools():
|
|
4285
|
+
m = self._call_native("pcolorfast", x, y, z, **kw)
|
|
4286
|
+
if not isinstance(m, mimage.AxesImage): # NOTE: PcolorImage is derivative
|
|
4287
|
+
self._fix_patch_edges(m, **edgefix_kw, **kw)
|
|
4288
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4289
|
+
elif edgefix_kw or labels_kw:
|
|
4290
|
+
kw = {**edgefix_kw, **labels_kw}
|
|
4291
|
+
warnings._warn_ultraplot(
|
|
4292
|
+
f"Ignoring unused keyword argument(s): {kw}. These only work with "
|
|
4293
|
+
"QuadMesh, not AxesImage. Consider using pcolor() or pcolormesh()."
|
|
4294
|
+
)
|
|
4295
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4296
|
+
return m
|
|
4297
|
+
|
|
4298
|
+
@docstring._snippet_manager
|
|
4299
|
+
def heatmap(self, *args, aspect=None, **kwargs):
|
|
4300
|
+
"""
|
|
4301
|
+
%(plot.heatmap)s
|
|
4302
|
+
"""
|
|
4303
|
+
obj = self.pcolormesh(*args, default_discrete=False, **kwargs)
|
|
4304
|
+
aspect = _not_none(aspect, rc["image.aspect"])
|
|
4305
|
+
if self._name != "cartesian":
|
|
4306
|
+
warnings._warn_ultraplot(
|
|
4307
|
+
"The heatmap() command is meant for CartesianAxes "
|
|
4308
|
+
"only. Please use pcolor() or pcolormesh() instead."
|
|
4309
|
+
)
|
|
4310
|
+
return obj
|
|
4311
|
+
coords = getattr(obj, "_coordinates", None)
|
|
4312
|
+
xlocator = ylocator = None
|
|
4313
|
+
if coords is not None:
|
|
4314
|
+
coords = 0.5 * (coords[1:, ...] + coords[:-1, ...])
|
|
4315
|
+
coords = 0.5 * (coords[:, 1:, :] + coords[:, :-1, :])
|
|
4316
|
+
xlocator, ylocator = coords[0, :, 0], coords[:, 0, 1]
|
|
4317
|
+
kw = {"aspect": aspect, "xgrid": False, "ygrid": False}
|
|
4318
|
+
if xlocator is not None and self.xaxis.isDefault_majloc:
|
|
4319
|
+
kw["xlocator"] = xlocator
|
|
4320
|
+
if ylocator is not None and self.yaxis.isDefault_majloc:
|
|
4321
|
+
kw["ylocator"] = ylocator
|
|
4322
|
+
if self.xaxis.isDefault_minloc:
|
|
4323
|
+
kw["xtickminor"] = False
|
|
4324
|
+
if self.yaxis.isDefault_minloc:
|
|
4325
|
+
kw["ytickminor"] = False
|
|
4326
|
+
self.format(**kw)
|
|
4327
|
+
return obj
|
|
4328
|
+
|
|
4329
|
+
@inputs._preprocess_or_redirect("x", "y", "u", "v", ("c", "color", "colors"))
|
|
4330
|
+
@docstring._concatenate_inherited
|
|
4331
|
+
@docstring._snippet_manager
|
|
4332
|
+
def barbs(self, x, y, u, v, c, **kwargs):
|
|
4333
|
+
"""
|
|
4334
|
+
%(plot.barbs)s
|
|
4335
|
+
"""
|
|
4336
|
+
x, y, u, v, kw = self._parse_2d_args(
|
|
4337
|
+
x, y, u, v, allow1d=True, autoguide=False, **kwargs
|
|
4338
|
+
) # noqa: E501
|
|
4339
|
+
kw.update(_pop_props(kw, "line")) # applied to barbs
|
|
4340
|
+
c, kw = self._parse_color(x, y, c, **kw)
|
|
4341
|
+
if mcolors.is_color_like(c):
|
|
4342
|
+
kw["barbcolor"], c = c, None
|
|
4343
|
+
a = [x, y, u, v]
|
|
4344
|
+
if c is not None:
|
|
4345
|
+
a.append(c)
|
|
4346
|
+
kw.pop("colorbar_kw", None) # added by _parse_cmap
|
|
4347
|
+
m = self._call_native("barbs", *a, **kw)
|
|
4348
|
+
return m
|
|
4349
|
+
|
|
4350
|
+
@inputs._preprocess_or_redirect("x", "y", "u", "v", ("c", "color", "colors"))
|
|
4351
|
+
@docstring._concatenate_inherited
|
|
4352
|
+
@docstring._snippet_manager
|
|
4353
|
+
def quiver(self, x, y, u, v, c, **kwargs):
|
|
4354
|
+
"""
|
|
4355
|
+
%(plot.quiver)s
|
|
4356
|
+
"""
|
|
4357
|
+
x, y, u, v, kw = self._parse_2d_args(
|
|
4358
|
+
x, y, u, v, allow1d=True, autoguide=False, **kwargs
|
|
4359
|
+
) # noqa: E501
|
|
4360
|
+
kw.update(_pop_props(kw, "line")) # applied to arrow outline
|
|
4361
|
+
c, kw = self._parse_color(x, y, c, **kw)
|
|
4362
|
+
color = None
|
|
4363
|
+
if mcolors.is_color_like(c):
|
|
4364
|
+
color, c = c, None
|
|
4365
|
+
if color is not None:
|
|
4366
|
+
kw["color"] = color
|
|
4367
|
+
a = [x, y, u, v]
|
|
4368
|
+
if c is not None:
|
|
4369
|
+
a.append(c)
|
|
4370
|
+
kw.pop("colorbar_kw", None) # added by _parse_cmap
|
|
4371
|
+
m = self._call_native("quiver", *a, **kw)
|
|
4372
|
+
return m
|
|
4373
|
+
|
|
4374
|
+
@docstring._snippet_manager
|
|
4375
|
+
def stream(self, *args, **kwargs):
|
|
4376
|
+
"""
|
|
4377
|
+
%(plot.stream)s
|
|
4378
|
+
"""
|
|
4379
|
+
return self.streamplot(*args, **kwargs)
|
|
4380
|
+
|
|
4381
|
+
# WARNING: breaking change from native streamplot() fifth positional arg 'density'
|
|
4382
|
+
@inputs._preprocess_or_redirect(
|
|
4383
|
+
"x", "y", "u", "v", ("c", "color", "colors"), keywords="start_points"
|
|
4384
|
+
)
|
|
4385
|
+
@docstring._concatenate_inherited
|
|
4386
|
+
@docstring._snippet_manager
|
|
4387
|
+
def streamplot(self, x, y, u, v, c, **kwargs):
|
|
4388
|
+
"""
|
|
4389
|
+
%(plot.stream)s
|
|
4390
|
+
"""
|
|
4391
|
+
x, y, u, v, kw = self._parse_2d_args(x, y, u, v, **kwargs)
|
|
4392
|
+
kw.update(_pop_props(kw, "line")) # applied to lines
|
|
4393
|
+
c, kw = self._parse_color(x, y, c, **kw)
|
|
4394
|
+
if c is None: # throws an error if color not provided
|
|
4395
|
+
c = pcolors.to_hex(self._get_lines.get_next_color())
|
|
4396
|
+
kw["color"] = c # always pass this
|
|
4397
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4398
|
+
label = kw.pop("label", None)
|
|
4399
|
+
m = self._call_native("streamplot", x, y, u, v, **kw)
|
|
4400
|
+
m.lines.set_label(label) # the collection label
|
|
4401
|
+
self._update_guide(m.lines, queue_colorbar=False, **guide_kw) # use lines
|
|
4402
|
+
return m
|
|
4403
|
+
|
|
4404
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4405
|
+
@docstring._concatenate_inherited
|
|
4406
|
+
@docstring._snippet_manager
|
|
4407
|
+
def tricontour(self, x, y, z, **kwargs):
|
|
4408
|
+
"""
|
|
4409
|
+
%(plot.tricontour)s
|
|
4410
|
+
"""
|
|
4411
|
+
kw = kwargs.copy()
|
|
4412
|
+
if x is None or y is None or z is None:
|
|
4413
|
+
raise ValueError("Three input arguments are required.")
|
|
4414
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4415
|
+
kw = self._parse_cmap(
|
|
4416
|
+
x, y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
|
|
4417
|
+
)
|
|
4418
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4419
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4420
|
+
label = kw.pop("label", None)
|
|
4421
|
+
m = self._call_native("tricontour", x, y, z, **kw)
|
|
4422
|
+
m._legend_label = label
|
|
4423
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4424
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4425
|
+
return m
|
|
4426
|
+
|
|
4427
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4428
|
+
@docstring._concatenate_inherited
|
|
4429
|
+
@docstring._snippet_manager
|
|
4430
|
+
def tricontourf(self, x, y, z, **kwargs):
|
|
4431
|
+
"""
|
|
4432
|
+
%(plot.tricontourf)s
|
|
4433
|
+
"""
|
|
4434
|
+
kw = kwargs.copy()
|
|
4435
|
+
if x is None or y is None or z is None:
|
|
4436
|
+
raise ValueError("Three input arguments are required.")
|
|
4437
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4438
|
+
contour_kw = _pop_kwargs(kw, "edgecolors", "linewidths", "linestyles")
|
|
4439
|
+
kw = self._parse_cmap(x, y, z, plot_contours=True, **kw)
|
|
4440
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4441
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4442
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4443
|
+
label = kw.pop("label", None)
|
|
4444
|
+
m = cm = self._call_native("tricontourf", x, y, z, **kw)
|
|
4445
|
+
m._legend_label = label
|
|
4446
|
+
self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # no-op if not contour_kw
|
|
4447
|
+
if contour_kw or labels_kw:
|
|
4448
|
+
cm = self._fix_contour_edges("tricontour", x, y, z, **kw, **contour_kw)
|
|
4449
|
+
self._add_auto_labels(m, cm, **labels_kw)
|
|
4450
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4451
|
+
return m
|
|
4452
|
+
|
|
4453
|
+
@inputs._preprocess_or_redirect("x", "y", "z")
|
|
4454
|
+
@docstring._concatenate_inherited
|
|
4455
|
+
@docstring._snippet_manager
|
|
4456
|
+
def tripcolor(self, x, y, z, **kwargs):
|
|
4457
|
+
"""
|
|
4458
|
+
%(plot.tripcolor)s
|
|
4459
|
+
"""
|
|
4460
|
+
kw = kwargs.copy()
|
|
4461
|
+
if x is None or y is None or z is None:
|
|
4462
|
+
raise ValueError("Three input arguments are required.")
|
|
4463
|
+
kw.update(_pop_props(kw, "collection"))
|
|
4464
|
+
kw = self._parse_cmap(x, y, z, **kw)
|
|
4465
|
+
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
|
|
4466
|
+
labels_kw = _pop_params(kw, self._add_auto_labels)
|
|
4467
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4468
|
+
with self._keep_grid_bools():
|
|
4469
|
+
m = self._call_native("tripcolor", x, y, z, **kw)
|
|
4470
|
+
self._fix_patch_edges(m, **edgefix_kw, **kw)
|
|
4471
|
+
self._add_auto_labels(m, **labels_kw)
|
|
4472
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4473
|
+
return m
|
|
4474
|
+
|
|
4475
|
+
# WARNING: breaking change from native 'X'
|
|
4476
|
+
@inputs._preprocess_or_redirect("z")
|
|
4477
|
+
@docstring._concatenate_inherited
|
|
4478
|
+
@docstring._snippet_manager
|
|
4479
|
+
def imshow(self, z, **kwargs):
|
|
4480
|
+
"""
|
|
4481
|
+
%(plot.imshow)s
|
|
4482
|
+
"""
|
|
4483
|
+
kw = kwargs.copy()
|
|
4484
|
+
kw = self._parse_cmap(z, default_discrete=False, **kw)
|
|
4485
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4486
|
+
m = self._call_native("imshow", z, **kw)
|
|
4487
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4488
|
+
return m
|
|
4489
|
+
|
|
4490
|
+
# WARNING: breaking change from native 'Z'
|
|
4491
|
+
@inputs._preprocess_or_redirect("z")
|
|
4492
|
+
@docstring._concatenate_inherited
|
|
4493
|
+
@docstring._snippet_manager
|
|
4494
|
+
def matshow(self, z, **kwargs):
|
|
4495
|
+
"""
|
|
4496
|
+
%(plot.matshow)s
|
|
4497
|
+
"""
|
|
4498
|
+
# Rely on imshow() override for this.
|
|
4499
|
+
return super().matshow(z, **kwargs)
|
|
4500
|
+
|
|
4501
|
+
# WARNING: breaking change from native 'Z'
|
|
4502
|
+
@inputs._preprocess_or_redirect("z")
|
|
4503
|
+
@docstring._concatenate_inherited
|
|
4504
|
+
@docstring._snippet_manager
|
|
4505
|
+
def spy(self, z, **kwargs):
|
|
4506
|
+
"""
|
|
4507
|
+
%(plot.spy)s
|
|
4508
|
+
"""
|
|
4509
|
+
kw = kwargs.copy()
|
|
4510
|
+
kw.update(_pop_props(kw, "line")) # takes valid Line2D properties
|
|
4511
|
+
default_cmap = pcolors.DiscreteColormap(["w", "k"], "_no_name")
|
|
4512
|
+
kw = self._parse_cmap(z, default_cmap=default_cmap, **kw)
|
|
4513
|
+
guide_kw = _pop_params(kw, self._update_guide)
|
|
4514
|
+
m = self._call_native("spy", z, **kw)
|
|
4515
|
+
self._update_guide(m, queue_colorbar=False, **guide_kw)
|
|
4516
|
+
return m
|
|
4517
|
+
|
|
4518
|
+
def _iter_arg_pairs(self, *args):
|
|
4519
|
+
"""
|
|
4520
|
+
Iterate over ``[x1,] y1, [fmt1,] [x2,] y2, [fmt2,] ...`` input.
|
|
4521
|
+
"""
|
|
4522
|
+
# NOTE: This is copied from _process_plot_var_args.__call__ to avoid relying
|
|
4523
|
+
# on private API. We emulate this input style with successive plot() calls.
|
|
4524
|
+
args = list(args)
|
|
4525
|
+
while args: # this permits empty input
|
|
4526
|
+
x, y, *args = args
|
|
4527
|
+
if args and isinstance(args[0], str): # format string detected!
|
|
4528
|
+
fmt, *args = args
|
|
4529
|
+
elif isinstance(y, str): # omits some of matplotlib's rigor but whatevs
|
|
4530
|
+
x, y, fmt = None, x, y
|
|
4531
|
+
else:
|
|
4532
|
+
fmt = None
|
|
4533
|
+
yield x, y, fmt
|
|
4534
|
+
|
|
4535
|
+
def _iter_arg_cols(self, *args, label=None, labels=None, values=None, **kwargs):
|
|
4536
|
+
"""
|
|
4537
|
+
Iterate over columns of positional arguments.
|
|
4538
|
+
"""
|
|
4539
|
+
is_array = lambda data: hasattr(data, "ndim") and hasattr(data, "shape")
|
|
4540
|
+
|
|
4541
|
+
# Determine the number of columns
|
|
4542
|
+
n = max(1 if not is_array(a) or a.ndim < 2 else a.shape[-1] for a in args)
|
|
4543
|
+
|
|
4544
|
+
# Handle labels
|
|
4545
|
+
labels = _not_none(label=label, values=values, labels=labels)
|
|
4546
|
+
if not np.iterable(labels) or isinstance(labels, str):
|
|
4547
|
+
labels = n * [labels]
|
|
4548
|
+
if len(labels) != n:
|
|
4549
|
+
raise ValueError(f"Array has {n} columns but got {len(labels)} labels.")
|
|
4550
|
+
if labels is not None:
|
|
4551
|
+
labels = [
|
|
4552
|
+
str(_not_none(label, "")) for label in inputs._to_numpy_array(labels)
|
|
4553
|
+
]
|
|
4554
|
+
else:
|
|
4555
|
+
labels = n * [None]
|
|
4556
|
+
|
|
4557
|
+
# Yield successive columns
|
|
4558
|
+
for i in range(n):
|
|
4559
|
+
kw = kwargs.copy()
|
|
4560
|
+
kw["label"] = labels[i] or None
|
|
4561
|
+
a = tuple(a if not is_array(a) or a.ndim < 2 else a[..., i] for a in args)
|
|
4562
|
+
yield (i, n, *a, kw)
|
|
4563
|
+
|
|
4564
|
+
# Related parsing functions for warnings
|
|
4565
|
+
_level_parsers = (_parse_level_vals, _parse_level_num, _parse_level_lim)
|
|
4566
|
+
|
|
4567
|
+
# Rename the shorthands
|
|
4568
|
+
boxes = warnings._rename_objs("0.8.0", boxes=box)
|
|
4569
|
+
violins = warnings._rename_objs("0.8.0", violins=violin)
|