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/config.py
ADDED
|
@@ -0,0 +1,1809 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tools for setting up ultraplot and configuring global settings.
|
|
4
|
+
See the :ref:`configuration guide <ug_config>` for details.
|
|
5
|
+
"""
|
|
6
|
+
# NOTE: The matplotlib analogue to this file is actually __init__.py
|
|
7
|
+
# but it makes more sense to have all the setup actions in a separate file
|
|
8
|
+
# so the namespace of the top-level module is unpolluted.
|
|
9
|
+
# NOTE: Why also load colormaps and cycles in this file and not colors.py?
|
|
10
|
+
# Because I think it makes sense to have all the code that "runs" (i.e. not
|
|
11
|
+
# just definitions) in the same place, and I was having issues with circular
|
|
12
|
+
# dependencies and where import order of __init__.py was affecting behavior.
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from collections import namedtuple
|
|
18
|
+
from collections.abc import MutableMapping
|
|
19
|
+
from numbers import Real
|
|
20
|
+
|
|
21
|
+
import cycler
|
|
22
|
+
import matplotlib as mpl
|
|
23
|
+
import matplotlib.colors as mcolors
|
|
24
|
+
import matplotlib.font_manager as mfonts
|
|
25
|
+
import matplotlib.mathtext # noqa: F401
|
|
26
|
+
import matplotlib.style.core as mstyle
|
|
27
|
+
import numpy as np
|
|
28
|
+
from matplotlib import RcParams
|
|
29
|
+
|
|
30
|
+
from .internals import ic # noqa: F401
|
|
31
|
+
from .internals import (
|
|
32
|
+
_not_none,
|
|
33
|
+
_pop_kwargs,
|
|
34
|
+
_pop_props,
|
|
35
|
+
_translate_grid,
|
|
36
|
+
_version_mpl,
|
|
37
|
+
docstring,
|
|
38
|
+
rcsetup,
|
|
39
|
+
warnings,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from IPython import get_ipython
|
|
44
|
+
except ImportError:
|
|
45
|
+
|
|
46
|
+
def get_ipython():
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Suppress warnings emitted by mathtext.py (_mathtext.py in recent versions)
|
|
51
|
+
# when when substituting dummy unavailable glyph due to fallback disabled.
|
|
52
|
+
logging.getLogger("matplotlib.mathtext").setLevel(logging.ERROR)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"Configurator",
|
|
56
|
+
"rc",
|
|
57
|
+
"rc_ultraplot",
|
|
58
|
+
"rc_matplotlib",
|
|
59
|
+
"use_style",
|
|
60
|
+
"config_inline_backend",
|
|
61
|
+
"register_cmaps",
|
|
62
|
+
"register_cycles",
|
|
63
|
+
"register_colors",
|
|
64
|
+
"register_fonts",
|
|
65
|
+
"RcConfigurator", # deprecated
|
|
66
|
+
"inline_backend_fmt", # deprecated
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# Constants
|
|
70
|
+
COLORS_KEEP = ("red", "green", "blue", "cyan", "yellow", "magenta", "white", "black")
|
|
71
|
+
|
|
72
|
+
# Configurator docstrings
|
|
73
|
+
_rc_docstring = """
|
|
74
|
+
local : bool, default: True
|
|
75
|
+
Whether to load settings from the `~Configurator.local_files` file.
|
|
76
|
+
user : bool, default: True
|
|
77
|
+
Whether to load settings from the `~Configurator.user_file` file.
|
|
78
|
+
default : bool, default: True
|
|
79
|
+
Whether to reload built-in default ultraplot settings.
|
|
80
|
+
"""
|
|
81
|
+
docstring._snippet_manager["rc.params"] = _rc_docstring
|
|
82
|
+
|
|
83
|
+
# Registration docstrings
|
|
84
|
+
_shared_docstring = """
|
|
85
|
+
*args : path-spec or `~ultraplot.colors.{type}Colormap`, optional
|
|
86
|
+
The {objects} to register. These can be file paths containing
|
|
87
|
+
RGB data or `~ultraplot.colors.{type}Colormap` instances. By default,
|
|
88
|
+
if positional arguments are passed, then `user` is set to ``False``.
|
|
89
|
+
|
|
90
|
+
Valid file extensions are listed in the below table. Note that {objects}
|
|
91
|
+
are registered according to their filenames -- for example, ``name.xyz``
|
|
92
|
+
will be registered as ``'name'``.
|
|
93
|
+
""" # noqa: E501
|
|
94
|
+
_cmap_exts_docstring = """
|
|
95
|
+
=================== ==========================================
|
|
96
|
+
Extension Description
|
|
97
|
+
=================== ==========================================
|
|
98
|
+
``.json`` JSON database of the channel segment data.
|
|
99
|
+
``.hex`` Comma-delimited list of HEX strings.
|
|
100
|
+
``.rgb``, ``.txt`` 3-4 column table of channel values.
|
|
101
|
+
=================== ==========================================
|
|
102
|
+
"""
|
|
103
|
+
_cycle_exts_docstring = """
|
|
104
|
+
================== ==========================================
|
|
105
|
+
Extension Description
|
|
106
|
+
================== ==========================================
|
|
107
|
+
``.hex`` Comma-delimited list of HEX strings.
|
|
108
|
+
``.rgb``, ``.txt`` 3-4 column table of channel values.
|
|
109
|
+
================== ==========================================
|
|
110
|
+
"""
|
|
111
|
+
_color_docstring = """
|
|
112
|
+
*args : path-like or dict, optional
|
|
113
|
+
The colors to register. These can be file paths containing RGB data or
|
|
114
|
+
dictionary mappings of names to RGB values. By default, if positional
|
|
115
|
+
arguments are passed, then `user` is set to ``False``. Files must have
|
|
116
|
+
the extension ``.txt`` and should contain one line per color in the
|
|
117
|
+
format ``name : hex``. Whitespace is ignored.
|
|
118
|
+
"""
|
|
119
|
+
_font_docstring = """
|
|
120
|
+
*args : path-like, optional
|
|
121
|
+
The font files to add. By default, if positional arguments are passed, then
|
|
122
|
+
`user` is set to ``False``. Files must have the extensions ``.ttf`` or ``.otf``.
|
|
123
|
+
See `this link \
|
|
124
|
+
<https://gree2.github.io/python/2015/04/27/python-change-matplotlib-font-on-mac>`__
|
|
125
|
+
for a guide on converting other font files to ``.ttf`` and ``.otf``.
|
|
126
|
+
"""
|
|
127
|
+
_register_docstring = """
|
|
128
|
+
user : bool, optional
|
|
129
|
+
Whether to reload {objects} from `~Configurator.user_folder`. Default is
|
|
130
|
+
``False`` if positional arguments were passed and ``True`` otherwise.
|
|
131
|
+
local : bool, optional
|
|
132
|
+
Whether to reload {objects} from `~Configurator.local_folders`. Default is
|
|
133
|
+
``False`` if positional arguments were passed and ``True`` otherwise.
|
|
134
|
+
default : bool, default: False
|
|
135
|
+
Whether to reload the default {objects} packaged with ultraplot.
|
|
136
|
+
Default is always ``False``.
|
|
137
|
+
"""
|
|
138
|
+
docstring._snippet_manager["rc.cmap_params"] = _register_docstring.format(
|
|
139
|
+
objects="colormaps"
|
|
140
|
+
) # noqa: E501
|
|
141
|
+
docstring._snippet_manager["rc.cycle_params"] = _register_docstring.format(
|
|
142
|
+
objects="color cycles"
|
|
143
|
+
) # noqa: E501
|
|
144
|
+
docstring._snippet_manager["rc.color_params"] = _register_docstring.format(
|
|
145
|
+
objects="colors"
|
|
146
|
+
) # noqa: E501
|
|
147
|
+
docstring._snippet_manager["rc.font_params"] = _register_docstring.format(
|
|
148
|
+
objects="fonts"
|
|
149
|
+
) # noqa: E501
|
|
150
|
+
docstring._snippet_manager["rc.cmap_args"] = _shared_docstring.format(
|
|
151
|
+
objects="colormaps", type="Continuous"
|
|
152
|
+
) # noqa: E501
|
|
153
|
+
docstring._snippet_manager["rc.cycle_args"] = _shared_docstring.format(
|
|
154
|
+
objects="color cycles", type="Discrete"
|
|
155
|
+
) # noqa: E501
|
|
156
|
+
docstring._snippet_manager["rc.color_args"] = _color_docstring
|
|
157
|
+
docstring._snippet_manager["rc.font_args"] = _font_docstring
|
|
158
|
+
docstring._snippet_manager["rc.cmap_exts"] = _cmap_exts_docstring
|
|
159
|
+
docstring._snippet_manager["rc.cycle_exts"] = _cycle_exts_docstring
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _init_user_file():
|
|
163
|
+
"""
|
|
164
|
+
Initialize .ultraplotrc file.
|
|
165
|
+
"""
|
|
166
|
+
file = Configurator.user_file()
|
|
167
|
+
if not os.path.exists(file):
|
|
168
|
+
Configurator._save_yaml(file, comment=True)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _init_user_folders():
|
|
172
|
+
"""
|
|
173
|
+
Initialize .ultraplot folder.
|
|
174
|
+
"""
|
|
175
|
+
for subfolder in ("", "cmaps", "cycles", "colors", "fonts"):
|
|
176
|
+
folder = Configurator.user_folder(subfolder)
|
|
177
|
+
if not os.path.isdir(folder):
|
|
178
|
+
os.mkdir(folder)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _get_data_folders(folder, user=True, local=True, default=True, reverse=False):
|
|
182
|
+
"""
|
|
183
|
+
Return data folder paths in reverse order of precedence.
|
|
184
|
+
"""
|
|
185
|
+
# When loading colormaps, cycles, and colors, files in the latter
|
|
186
|
+
# directories overwrite files in the former directories. When loading
|
|
187
|
+
# fonts, the resulting paths need to be *reversed*.
|
|
188
|
+
paths = []
|
|
189
|
+
if default:
|
|
190
|
+
paths.append(os.path.join(os.path.dirname(__file__), folder))
|
|
191
|
+
if user:
|
|
192
|
+
paths.append(Configurator.user_folder(folder))
|
|
193
|
+
if local:
|
|
194
|
+
paths.extend(Configurator.local_folders(folder))
|
|
195
|
+
if reverse:
|
|
196
|
+
paths = paths[::-1]
|
|
197
|
+
return paths
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _iter_data_objects(folder, *args, **kwargs):
|
|
201
|
+
"""
|
|
202
|
+
Iterate over input objects and files in the data folders that should be
|
|
203
|
+
registered. Also yield an index indicating whether these are user files.
|
|
204
|
+
"""
|
|
205
|
+
i = 0 # default files
|
|
206
|
+
for i, path in enumerate(_get_data_folders(folder, **kwargs)):
|
|
207
|
+
for dirname, dirnames, filenames in os.walk(path):
|
|
208
|
+
for filename in filenames:
|
|
209
|
+
if filename[0] == ".": # UNIX-style hidden files
|
|
210
|
+
continue
|
|
211
|
+
path = os.path.join(dirname, filename)
|
|
212
|
+
yield i, path
|
|
213
|
+
i += 1 # user files
|
|
214
|
+
for path in args:
|
|
215
|
+
path = os.path.expanduser(path)
|
|
216
|
+
if os.path.isfile(path):
|
|
217
|
+
yield i, path
|
|
218
|
+
else:
|
|
219
|
+
raise FileNotFoundError(f"Invalid file path {path!r}.")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _filter_style_dict(rcdict, warn=True):
|
|
223
|
+
"""
|
|
224
|
+
Filter out blacklisted style parameters.
|
|
225
|
+
"""
|
|
226
|
+
# NOTE: This implements bugfix: https://github.com/matplotlib/matplotlib/pull/17252
|
|
227
|
+
# This fix is *critical* for ultraplot because we always run style.use()
|
|
228
|
+
# when the configurator is made. Without fix backend is reset every time
|
|
229
|
+
# you import ultraplot in jupyter notebooks. So apply retroactively.
|
|
230
|
+
rcdict_filtered = {}
|
|
231
|
+
for key in rcdict:
|
|
232
|
+
if key in mstyle.STYLE_BLACKLIST:
|
|
233
|
+
if warn:
|
|
234
|
+
warnings._warn_ultraplot(
|
|
235
|
+
f"Dictionary includes a parameter, {key!r}, that is not related "
|
|
236
|
+
"to style. Ignoring."
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
rcdict_filtered[key] = rcdict[key]
|
|
240
|
+
return rcdict_filtered
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _get_default_style_dict():
|
|
244
|
+
"""
|
|
245
|
+
Get the default rc parameters dictionary with deprecated parameters filtered.
|
|
246
|
+
"""
|
|
247
|
+
# NOTE: Use RcParams update to filter and translate deprecated settings
|
|
248
|
+
# before actually applying them to rcParams down pipeline. This way we can
|
|
249
|
+
# suppress warnings for deprecated default params but still issue warnings
|
|
250
|
+
# when user-supplied stylesheets have deprecated params.
|
|
251
|
+
# WARNING: Some deprecated rc params remain in dictionary as None so we
|
|
252
|
+
# filter them out. Beware if hidden attribute changes.
|
|
253
|
+
# WARNING: The examples.directory deprecation was handled specially inside
|
|
254
|
+
# RcParams in early versions. Manually pop it out here.
|
|
255
|
+
rcdict = _filter_style_dict(mpl.rcParamsDefault, warn=False)
|
|
256
|
+
with warnings.catch_warnings():
|
|
257
|
+
warnings.simplefilter("ignore", mpl.MatplotlibDeprecationWarning)
|
|
258
|
+
rcdict = dict(RcParams(rcdict))
|
|
259
|
+
for attr in ("_deprecated_set", "_deprecated_remain_as_none"):
|
|
260
|
+
deprecated = getattr(mpl, attr, ())
|
|
261
|
+
for key in deprecated: # _deprecated_set is in matplotlib < 3.4
|
|
262
|
+
rcdict.pop(key, None)
|
|
263
|
+
rcdict.pop("examples.directory", None) # special case for matplotlib < 3.2
|
|
264
|
+
return rcdict
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _get_style_dict(style, filter=True):
|
|
268
|
+
"""
|
|
269
|
+
Return a dictionary of settings belonging to the requested style(s). If `filter`
|
|
270
|
+
is ``True``, invalid style parameters like `backend` are filtered out.
|
|
271
|
+
"""
|
|
272
|
+
# NOTE: This is adapted from matplotlib source for the following changes:
|
|
273
|
+
# 1. Add an 'original' pseudo style. Like rcParamsOrig except we also reload
|
|
274
|
+
# from the user matplotlibrc file.
|
|
275
|
+
# 2. When the style is changed we reset to the default state ignoring matplotlibrc.
|
|
276
|
+
# By contrast matplotlib applies styles on top of current state (including
|
|
277
|
+
# matplotlibrc changes and runtime rcParams changes) but the word 'style'
|
|
278
|
+
# implies a rigid static format. This makes more sense.
|
|
279
|
+
# 3. Add a separate function that returns lists of style dictionaries so that
|
|
280
|
+
# we can modify the active style in a context block. ultraplot context is more
|
|
281
|
+
# conservative than matplotlib's rc_context because it gets called a lot
|
|
282
|
+
# (e.g. every time you make an axes and every format() call). Instead of
|
|
283
|
+
# copying the entire rcParams dict we just track the keys that were changed.
|
|
284
|
+
style_aliases = {
|
|
285
|
+
"538": "fivethirtyeight",
|
|
286
|
+
"mpl20": "default",
|
|
287
|
+
"mpl15": "classic",
|
|
288
|
+
"original": mpl.matplotlib_fname(),
|
|
289
|
+
"seaborn": "seaborn-v0_8",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Always apply the default style *first* so styles are rigid
|
|
293
|
+
kw_matplotlib = _get_default_style_dict()
|
|
294
|
+
if style == "default" or style is mpl.rcParamsDefault:
|
|
295
|
+
return kw_matplotlib
|
|
296
|
+
|
|
297
|
+
# Apply limited deviations from the matplotlib style that we want to propagate to
|
|
298
|
+
# other styles. Want users selecting stylesheets to have few surprises, so
|
|
299
|
+
# currently just enforce the new aesthetically pleasing fonts.
|
|
300
|
+
kw_matplotlib["font.family"] = "sans-serif"
|
|
301
|
+
for fmly in ("serif", "sans-serif", "monospace", "cursive", "fantasy"):
|
|
302
|
+
kw_matplotlib["font." + fmly] = rcsetup._rc_matplotlib_default["font." + fmly]
|
|
303
|
+
|
|
304
|
+
# Apply user input style(s) one by one
|
|
305
|
+
if isinstance(style, str) or isinstance(style, dict):
|
|
306
|
+
styles = [style]
|
|
307
|
+
else:
|
|
308
|
+
styles = style
|
|
309
|
+
for style in styles:
|
|
310
|
+
if isinstance(style, dict):
|
|
311
|
+
kw = style
|
|
312
|
+
elif isinstance(style, str):
|
|
313
|
+
style = style_aliases.get(style, style)
|
|
314
|
+
if style in mstyle.library:
|
|
315
|
+
kw = mstyle.library[style]
|
|
316
|
+
else:
|
|
317
|
+
try:
|
|
318
|
+
kw = mpl.rc_params_from_file(style, use_default_template=False)
|
|
319
|
+
except IOError:
|
|
320
|
+
raise IOError(
|
|
321
|
+
f"Style {style!r} not found in the style library and input "
|
|
322
|
+
"is not a valid URL or file path. Available styles are: "
|
|
323
|
+
+ ", ".join(map(repr, mstyle.available))
|
|
324
|
+
+ "."
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
raise ValueError(f"Invalid style {style!r}. Must be string or dictionary.")
|
|
328
|
+
if filter:
|
|
329
|
+
kw = _filter_style_dict(kw, warn=True)
|
|
330
|
+
kw_matplotlib.update(kw)
|
|
331
|
+
|
|
332
|
+
return kw_matplotlib
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _infer_ultraplot_dict(kw_params):
|
|
336
|
+
"""
|
|
337
|
+
Infer values for ultraplot's "added" parameters from stylesheet parameters.
|
|
338
|
+
"""
|
|
339
|
+
kw_ultraplot = {}
|
|
340
|
+
mpl_to_ultraplot = {
|
|
341
|
+
"xtick.labelsize": (
|
|
342
|
+
"tick.labelsize",
|
|
343
|
+
"grid.labelsize",
|
|
344
|
+
),
|
|
345
|
+
"ytick.labelsize": (
|
|
346
|
+
"tick.labelsize",
|
|
347
|
+
"grid.labelsize",
|
|
348
|
+
),
|
|
349
|
+
"axes.titlesize": (
|
|
350
|
+
"abc.size",
|
|
351
|
+
"suptitle.size",
|
|
352
|
+
"title.size",
|
|
353
|
+
"leftlabel.size",
|
|
354
|
+
"rightlabel.size",
|
|
355
|
+
"toplabel.size",
|
|
356
|
+
"bottomlabel.size",
|
|
357
|
+
),
|
|
358
|
+
"text.color": (
|
|
359
|
+
"abc.color",
|
|
360
|
+
"suptitle.color",
|
|
361
|
+
"title.color",
|
|
362
|
+
"tick.labelcolor",
|
|
363
|
+
"grid.labelcolor",
|
|
364
|
+
"leftlabel.color",
|
|
365
|
+
"rightlabel.color",
|
|
366
|
+
"toplabel.color",
|
|
367
|
+
"bottomlabel.color",
|
|
368
|
+
),
|
|
369
|
+
}
|
|
370
|
+
for key, params in mpl_to_ultraplot.items():
|
|
371
|
+
if key in kw_params:
|
|
372
|
+
value = kw_params[key]
|
|
373
|
+
for param in params:
|
|
374
|
+
kw_ultraplot[param] = value
|
|
375
|
+
return kw_ultraplot
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def config_inline_backend(fmt=None):
|
|
379
|
+
"""
|
|
380
|
+
Set up the ipython `inline backend display format \
|
|
381
|
+
<https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-matplotlib>`__
|
|
382
|
+
and ensure that inline figures always look the same as saved figures.
|
|
383
|
+
This runs the following ipython magic commands:
|
|
384
|
+
|
|
385
|
+
.. code-block:: ipython
|
|
386
|
+
|
|
387
|
+
%config InlineBackend.figure_formats = rc['inlineformat']
|
|
388
|
+
%config InlineBackend.rc = {} # never override rc settings
|
|
389
|
+
%config InlineBackend.close_figures = True # cells start with no active figures
|
|
390
|
+
%config InlineBackend.print_figure_kwargs = {'bbox_inches': None}
|
|
391
|
+
|
|
392
|
+
When the inline backend is inactive or unavailable, this has no effect.
|
|
393
|
+
This function is called when you modify the :rcraw:`inlineformat` property.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
fmt : str or sequence, default: :rc:`inlineformat`
|
|
398
|
+
The inline backend file format or a list thereof. Valid formats
|
|
399
|
+
include ``'jpg'``, ``'png'``, ``'svg'``, ``'pdf'``, and ``'retina'``.
|
|
400
|
+
|
|
401
|
+
See also
|
|
402
|
+
--------
|
|
403
|
+
Configurator
|
|
404
|
+
"""
|
|
405
|
+
# Note if inline backend is unavailable this will fail silently
|
|
406
|
+
ipython = get_ipython()
|
|
407
|
+
if ipython is None:
|
|
408
|
+
return
|
|
409
|
+
fmt = _not_none(fmt, rc_ultraplot["inlineformat"])
|
|
410
|
+
if isinstance(fmt, str):
|
|
411
|
+
fmt = [fmt]
|
|
412
|
+
elif np.iterable(fmt):
|
|
413
|
+
fmt = list(fmt)
|
|
414
|
+
else:
|
|
415
|
+
raise ValueError(f"Invalid inline backend format {fmt!r}. Must be string.")
|
|
416
|
+
ipython.magic("config InlineBackend.figure_formats = " + repr(fmt))
|
|
417
|
+
ipython.magic("config InlineBackend.rc = {}")
|
|
418
|
+
ipython.magic("config InlineBackend.close_figures = True")
|
|
419
|
+
ipython.magic("config InlineBackend.print_figure_kwargs = {'bbox_inches': None}")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def use_style(style):
|
|
423
|
+
"""
|
|
424
|
+
Apply the `matplotlib style(s) \
|
|
425
|
+
<https://matplotlib.org/stable/tutorials/introductory/customizing.html>`__
|
|
426
|
+
with `matplotlib.style.use`. This function is
|
|
427
|
+
called when you modify the :rcraw:`style` property.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
style : str or sequence or dict-like
|
|
432
|
+
The matplotlib style name(s) or stylesheet filename(s), or dictionary(s)
|
|
433
|
+
of settings. Use ``'default'`` to apply matplotlib default settings and
|
|
434
|
+
``'original'`` to include settings from your user ``matplotlibrc`` file.
|
|
435
|
+
|
|
436
|
+
See also
|
|
437
|
+
--------
|
|
438
|
+
Configurator
|
|
439
|
+
matplotlib.style.use
|
|
440
|
+
"""
|
|
441
|
+
# NOTE: This function is not really necessary but makes ultraplot's
|
|
442
|
+
# stylesheet-supporting features obvious. Plus changing the style does
|
|
443
|
+
# so much *more* than changing rc params or quick settings, so it is
|
|
444
|
+
# nice to have dedicated function instead of just another rc_param name.
|
|
445
|
+
kw_matplotlib = _get_style_dict(style)
|
|
446
|
+
rc_matplotlib.update(kw_matplotlib)
|
|
447
|
+
rc_ultraplot.update(_infer_ultraplot_dict(kw_matplotlib))
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@docstring._snippet_manager
|
|
451
|
+
def register_cmaps(*args, user=None, local=None, default=False):
|
|
452
|
+
"""
|
|
453
|
+
Register named colormaps. This is called on import.
|
|
454
|
+
|
|
455
|
+
Parameters
|
|
456
|
+
----------
|
|
457
|
+
%(rc.cmap_args)s
|
|
458
|
+
|
|
459
|
+
%(rc.cmap_exts)s
|
|
460
|
+
|
|
461
|
+
%(rc.cmap_params)s
|
|
462
|
+
|
|
463
|
+
See also
|
|
464
|
+
--------
|
|
465
|
+
register_cycles
|
|
466
|
+
register_colors
|
|
467
|
+
register_fonts
|
|
468
|
+
ultraplot.demos.show_cmaps
|
|
469
|
+
"""
|
|
470
|
+
# Register input colormaps
|
|
471
|
+
from . import colors as pcolors
|
|
472
|
+
|
|
473
|
+
user = _not_none(user, not bool(args)) # skip user folder if input args passed
|
|
474
|
+
local = _not_none(local, not bool(args))
|
|
475
|
+
paths = []
|
|
476
|
+
for arg in args:
|
|
477
|
+
if isinstance(arg, mcolors.Colormap):
|
|
478
|
+
pcolors._cmap_database.register(arg, name=arg.name)
|
|
479
|
+
else:
|
|
480
|
+
paths.append(arg)
|
|
481
|
+
|
|
482
|
+
# Register data files
|
|
483
|
+
for i, path in _iter_data_objects(
|
|
484
|
+
"cmaps", *paths, user=user, local=local, default=default
|
|
485
|
+
):
|
|
486
|
+
cmap = pcolors.ContinuousColormap.from_file(path, warn_on_failure=True)
|
|
487
|
+
if not cmap:
|
|
488
|
+
continue
|
|
489
|
+
if i == 0 and cmap.name.lower() in pcolors.CMAPS_CYCLIC:
|
|
490
|
+
cmap.set_cyclic(True)
|
|
491
|
+
pcolors._cmap_database.register(cmap, name=cmap.name)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@docstring._snippet_manager
|
|
495
|
+
def register_cycles(*args, user=None, local=None, default=False):
|
|
496
|
+
"""
|
|
497
|
+
Register named color cycles. This is called on import.
|
|
498
|
+
|
|
499
|
+
Parameters
|
|
500
|
+
----------
|
|
501
|
+
%(rc.cycle_args)s
|
|
502
|
+
|
|
503
|
+
%(rc.cycle_exts)s
|
|
504
|
+
|
|
505
|
+
%(rc.cycle_params)s
|
|
506
|
+
|
|
507
|
+
See also
|
|
508
|
+
--------
|
|
509
|
+
register_cmaps
|
|
510
|
+
register_colors
|
|
511
|
+
register_fonts
|
|
512
|
+
ultraplot.demos.show_cycles
|
|
513
|
+
"""
|
|
514
|
+
# Register input color cycles
|
|
515
|
+
from . import colors as pcolors
|
|
516
|
+
|
|
517
|
+
user = _not_none(user, not bool(args)) # skip user folder if input args passed
|
|
518
|
+
local = _not_none(local, not bool(args))
|
|
519
|
+
paths = []
|
|
520
|
+
for arg in args:
|
|
521
|
+
if isinstance(arg, mcolors.Colormap):
|
|
522
|
+
pcolors._cmap_database.register(arg, name=arg.name)
|
|
523
|
+
else:
|
|
524
|
+
paths.append(arg)
|
|
525
|
+
|
|
526
|
+
# Register data files
|
|
527
|
+
for _, path in _iter_data_objects(
|
|
528
|
+
"cycles", *paths, user=user, local=local, default=default
|
|
529
|
+
):
|
|
530
|
+
cmap = pcolors.DiscreteColormap.from_file(path, warn_on_failure=True)
|
|
531
|
+
if not cmap:
|
|
532
|
+
continue
|
|
533
|
+
pcolors._cmap_database.register(cmap, name=cmap.name)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@docstring._snippet_manager
|
|
537
|
+
def register_colors(
|
|
538
|
+
*args, user=None, local=None, default=False, space=None, margin=None, **kwargs
|
|
539
|
+
):
|
|
540
|
+
"""
|
|
541
|
+
Register named colors. This is called on import.
|
|
542
|
+
|
|
543
|
+
Parameters
|
|
544
|
+
----------
|
|
545
|
+
%(rc.color_args)s
|
|
546
|
+
%(rc.color_params)s
|
|
547
|
+
space : {'hcl', 'hsl', 'hpl'}, optional
|
|
548
|
+
The colorspace used to pick "perceptually distinct" colors from
|
|
549
|
+
the `XKCD color survey <https://xkcd.com/color/rgb/>`__.
|
|
550
|
+
If passed then `default` is set to ``True``.
|
|
551
|
+
margin : float, default: 0.1
|
|
552
|
+
The margin used to pick "perceptually distinct" colors from the
|
|
553
|
+
`XKCD color survey <https://xkcd.com/color/rgb/>`__. The normalized hue,
|
|
554
|
+
saturation, and luminance of each color must differ from the channel
|
|
555
|
+
values of the prededing colors by `margin` in order to be registered.
|
|
556
|
+
Must fall between ``0`` and ``1`` (``0`` will register all colors).
|
|
557
|
+
If passed then `default` is set to ``True``.
|
|
558
|
+
**kwargs
|
|
559
|
+
Additional color name specifications passed as keyword arguments rather
|
|
560
|
+
than positional argument dictionaries.
|
|
561
|
+
|
|
562
|
+
See also
|
|
563
|
+
--------
|
|
564
|
+
register_cmaps
|
|
565
|
+
register_cycles
|
|
566
|
+
register_fonts
|
|
567
|
+
ultraplot.demos.show_colors
|
|
568
|
+
"""
|
|
569
|
+
from . import colors as pcolors
|
|
570
|
+
|
|
571
|
+
default = default or space is not None or margin is not None
|
|
572
|
+
margin = _not_none(margin, 0.1)
|
|
573
|
+
space = _not_none(space, "hcl")
|
|
574
|
+
|
|
575
|
+
# Remove previously registered colors
|
|
576
|
+
# NOTE: Try not to touch matplotlib colors for compatibility
|
|
577
|
+
srcs = {"xkcd": pcolors.COLORS_XKCD, "opencolor": pcolors.COLORS_OPEN}
|
|
578
|
+
if default: # possibly slow but not these dicts are empty on startup
|
|
579
|
+
for src in srcs.values():
|
|
580
|
+
for key in src:
|
|
581
|
+
if key not in COLORS_KEEP:
|
|
582
|
+
pcolors._color_database.pop(key, None) # this also clears cache
|
|
583
|
+
src.clear()
|
|
584
|
+
|
|
585
|
+
# Register input colors
|
|
586
|
+
user = _not_none(user, not bool(args) and not bool(kwargs)) # skip if args passed
|
|
587
|
+
local = _not_none(local, not bool(args) and not bool(kwargs))
|
|
588
|
+
paths = []
|
|
589
|
+
for arg in args:
|
|
590
|
+
if isinstance(arg, dict):
|
|
591
|
+
kwargs.update(arg)
|
|
592
|
+
else:
|
|
593
|
+
paths.append(arg)
|
|
594
|
+
for key, color in kwargs.items():
|
|
595
|
+
if mcolors.is_color_like(color):
|
|
596
|
+
pcolors._color_database[key] = mcolors.to_rgba(color)
|
|
597
|
+
else:
|
|
598
|
+
raise ValueError(f"Invalid color {key}={color!r}.")
|
|
599
|
+
|
|
600
|
+
# Load colors from file and get their HCL values
|
|
601
|
+
# NOTE: Colors that come *later* overwrite colors that come earlier.
|
|
602
|
+
for i, path in _iter_data_objects(
|
|
603
|
+
"colors", *paths, user=user, local=local, default=default
|
|
604
|
+
):
|
|
605
|
+
loaded = pcolors._load_colors(path, warn_on_failure=True)
|
|
606
|
+
if i == 0:
|
|
607
|
+
cat, _ = os.path.splitext(os.path.basename(path))
|
|
608
|
+
if cat not in srcs:
|
|
609
|
+
raise RuntimeError(f"Unknown ultraplot color database {path!r}.")
|
|
610
|
+
src = srcs[cat]
|
|
611
|
+
if cat == "xkcd":
|
|
612
|
+
for key in COLORS_KEEP:
|
|
613
|
+
loaded[key] = pcolors._color_database[key] # keep the same
|
|
614
|
+
loaded = pcolors._standardize_colors(loaded, space, margin)
|
|
615
|
+
src.clear()
|
|
616
|
+
src.update(loaded) # needed for demos.show_colors()
|
|
617
|
+
pcolors._color_database.update(loaded)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@docstring._snippet_manager
|
|
621
|
+
def register_fonts(*args, user=True, local=True, default=False):
|
|
622
|
+
"""
|
|
623
|
+
Register font families. This is called on import.
|
|
624
|
+
|
|
625
|
+
Parameters
|
|
626
|
+
----------
|
|
627
|
+
%(rc.font_args)s
|
|
628
|
+
%(rc.font_params)s
|
|
629
|
+
|
|
630
|
+
See also
|
|
631
|
+
--------
|
|
632
|
+
register_cmaps
|
|
633
|
+
register_cycles
|
|
634
|
+
register_colors
|
|
635
|
+
ultraplot.demos.show_fonts
|
|
636
|
+
"""
|
|
637
|
+
# Find ultraplot fonts
|
|
638
|
+
# WARNING: Must search data files in reverse because font manager will
|
|
639
|
+
# not overwrite existing fonts with user-input fonts.
|
|
640
|
+
# WARNING: If you include a font file with an unrecognized style,
|
|
641
|
+
# matplotlib may use that font instead of the 'normal' one! Valid styles:
|
|
642
|
+
# 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman',
|
|
643
|
+
# 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black'
|
|
644
|
+
# https://matplotlib.org/api/font_manager_api.html
|
|
645
|
+
# For macOS the only fonts with 'Thin' in one of the .ttf file names
|
|
646
|
+
# are Helvetica Neue and .SF NS Display Condensed. Never try to use these!
|
|
647
|
+
paths_ultraplot = _get_data_folders(
|
|
648
|
+
"fonts", user=user, local=local, default=default, reverse=True
|
|
649
|
+
)
|
|
650
|
+
fnames_ultraplot = set(mfonts.findSystemFonts(paths_ultraplot))
|
|
651
|
+
for path in args:
|
|
652
|
+
path = os.path.expanduser(path)
|
|
653
|
+
if os.path.isfile(path):
|
|
654
|
+
fnames_ultraplot.add(path)
|
|
655
|
+
else:
|
|
656
|
+
raise FileNotFoundError(f"Invalid font file path {path!r}.")
|
|
657
|
+
|
|
658
|
+
# Detect user-input ttc fonts and issue warning
|
|
659
|
+
fnames_ultraplot_ttc = {
|
|
660
|
+
file for file in fnames_ultraplot if os.path.splitext(file)[1] == ".ttc"
|
|
661
|
+
}
|
|
662
|
+
if fnames_ultraplot_ttc:
|
|
663
|
+
warnings._warn_ultraplot(
|
|
664
|
+
"Ignoring the following .ttc fonts because they cannot be "
|
|
665
|
+
"saved into PDF or EPS files (see matplotlib issue #3135): "
|
|
666
|
+
+ ", ".join(map(repr, sorted(fnames_ultraplot_ttc)))
|
|
667
|
+
+ ". Please consider expanding them into separate .ttf files."
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
# Rebuild font cache only if necessary! Can be >50% of total import time!
|
|
671
|
+
fnames_all = {font.fname for font in mfonts.fontManager.ttflist}
|
|
672
|
+
fnames_ultraplot -= fnames_ultraplot_ttc
|
|
673
|
+
if not fnames_all >= fnames_ultraplot:
|
|
674
|
+
warnings._warn_ultraplot(
|
|
675
|
+
"Rebuilding font cache. This usually happens "
|
|
676
|
+
"after installing or updating ultraplot."
|
|
677
|
+
)
|
|
678
|
+
if hasattr(mfonts.fontManager, "addfont"):
|
|
679
|
+
# Newer API lets us add font files manually and deprecates TTFPATH. However
|
|
680
|
+
# to cache fonts added this way, we must call json_dump explicitly.
|
|
681
|
+
# NOTE: Previously, cache filename was specified as _fmcache variable, but
|
|
682
|
+
# recently became inaccessible. Must reproduce mpl code instead.
|
|
683
|
+
# NOTE: Older mpl versions used fontList.json as the cache, but these
|
|
684
|
+
# versions also did not have 'addfont', so makes no difference.
|
|
685
|
+
for fname in fnames_ultraplot:
|
|
686
|
+
mfonts.fontManager.addfont(fname)
|
|
687
|
+
cache = os.path.join(
|
|
688
|
+
mpl.get_cachedir(), f"fontlist-v{mfonts.FontManager.__version__}.json"
|
|
689
|
+
)
|
|
690
|
+
mfonts.json_dump(mfonts.fontManager, cache)
|
|
691
|
+
else:
|
|
692
|
+
# Older API requires us to modify TTFPATH
|
|
693
|
+
# NOTE: Previously we tried to modify TTFPATH before importing
|
|
694
|
+
# font manager with hope that it would load ultraplot fonts on
|
|
695
|
+
# initialization. But 99% of the time font manager just imports
|
|
696
|
+
# the FontManager from cache, so we would have to rebuild anyway.
|
|
697
|
+
paths = ":".join(paths_ultraplot)
|
|
698
|
+
if "TTFPATH" not in os.environ:
|
|
699
|
+
os.environ["TTFPATH"] = paths
|
|
700
|
+
elif paths not in os.environ["TTFPATH"]:
|
|
701
|
+
os.environ["TTFPATH"] += ":" + paths
|
|
702
|
+
mfonts._rebuild()
|
|
703
|
+
|
|
704
|
+
# Remove ttc files and 'Thin' fonts *after* rebuild
|
|
705
|
+
# NOTE: 'Thin' filter is ugly kludge but without this matplotlib picks up on
|
|
706
|
+
# Roboto thin ttf files installed on the RTD server when compiling docs.
|
|
707
|
+
mfonts.fontManager.ttflist = [
|
|
708
|
+
font
|
|
709
|
+
for font in mfonts.fontManager.ttflist
|
|
710
|
+
if os.path.splitext(font.fname)[1] != ".ttc"
|
|
711
|
+
and (_version_mpl >= "3.3" or "Thin" not in os.path.basename(font.fname))
|
|
712
|
+
]
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
class Configurator(MutableMapping, dict):
|
|
716
|
+
"""
|
|
717
|
+
A dictionary-like class for managing `matplotlib settings
|
|
718
|
+
<https://matplotlib.org/stable/tutorials/introductory/customizing.html>`__
|
|
719
|
+
stored in `rc_matplotlib` and :ref:`ultraplot settings <ug_rcultraplot>`
|
|
720
|
+
stored in `rc_ultraplot`. This class is instantiated as the `rc` object
|
|
721
|
+
on import. See the :ref:`user guide <ug_config>` for details.
|
|
722
|
+
"""
|
|
723
|
+
|
|
724
|
+
def __repr__(self):
|
|
725
|
+
cls = type("rc", (dict,), {}) # temporary class with short name
|
|
726
|
+
src = cls({key: val for key, val in rc_ultraplot.items() if "." not in key})
|
|
727
|
+
return type(rc_matplotlib).__repr__(src).strip()[:-1] + ",\n ...\n })"
|
|
728
|
+
|
|
729
|
+
def __str__(self):
|
|
730
|
+
cls = type("rc", (dict,), {}) # temporary class with short name
|
|
731
|
+
src = cls({key: val for key, val in rc_ultraplot.items() if "." not in key})
|
|
732
|
+
return type(rc_matplotlib).__str__(src) + "\n..."
|
|
733
|
+
|
|
734
|
+
def __iter__(self):
|
|
735
|
+
yield from rc_ultraplot # sorted ultraplot settings, ignoring deprecations
|
|
736
|
+
yield from rc_matplotlib # sorted matplotlib settings, ignoring deprecations
|
|
737
|
+
|
|
738
|
+
def __len__(self):
|
|
739
|
+
return len(tuple(iter(self)))
|
|
740
|
+
|
|
741
|
+
def __delitem__(self, key): # noqa: U100
|
|
742
|
+
raise RuntimeError("rc settings cannot be deleted.")
|
|
743
|
+
|
|
744
|
+
def __delattr__(self, attr): # noqa: U100
|
|
745
|
+
raise RuntimeError("rc settings cannot be deleted.")
|
|
746
|
+
|
|
747
|
+
@docstring._snippet_manager
|
|
748
|
+
def __init__(self, local=True, user=True, default=True, **kwargs):
|
|
749
|
+
"""
|
|
750
|
+
Parameters
|
|
751
|
+
----------
|
|
752
|
+
%(rc.params)s
|
|
753
|
+
"""
|
|
754
|
+
self._context = []
|
|
755
|
+
self._init(local=local, user=user, default=default, **kwargs)
|
|
756
|
+
|
|
757
|
+
def __getitem__(self, key):
|
|
758
|
+
"""
|
|
759
|
+
Return an `rc_matplotlib` or `rc_ultraplot` setting using dictionary notation
|
|
760
|
+
(e.g., ``value = pplt.rc[name]``).
|
|
761
|
+
"""
|
|
762
|
+
key, _ = self._validate_key(key) # might issue ultraplot removed/renamed error
|
|
763
|
+
try:
|
|
764
|
+
return rc_ultraplot[key]
|
|
765
|
+
except KeyError:
|
|
766
|
+
pass
|
|
767
|
+
return rc_matplotlib[key] # might issue matplotlib removed/renamed error
|
|
768
|
+
|
|
769
|
+
def __setitem__(self, key, value):
|
|
770
|
+
"""
|
|
771
|
+
Modify an `rc_matplotlib` or `rc_ultraplot` setting using dictionary notation
|
|
772
|
+
(e.g., ``pplt.rc[name] = value``).
|
|
773
|
+
"""
|
|
774
|
+
kw_ultraplot, kw_matplotlib = self._get_item_dicts(key, value)
|
|
775
|
+
rc_ultraplot.update(kw_ultraplot)
|
|
776
|
+
rc_matplotlib.update(kw_matplotlib)
|
|
777
|
+
|
|
778
|
+
def __getattr__(self, attr):
|
|
779
|
+
"""
|
|
780
|
+
Return an `rc_matplotlib` or `rc_ultraplot` setting using "dot" notation
|
|
781
|
+
(e.g., ``value = pplt.rc.name``).
|
|
782
|
+
"""
|
|
783
|
+
if attr[:1] == "_":
|
|
784
|
+
return super().__getattribute__(attr) # raise built-in error
|
|
785
|
+
else:
|
|
786
|
+
return self.__getitem__(attr)
|
|
787
|
+
|
|
788
|
+
def __setattr__(self, attr, value):
|
|
789
|
+
"""
|
|
790
|
+
Modify an `rc_matplotlib` or `rc_ultraplot` setting using "dot" notation
|
|
791
|
+
(e.g., ``pplt.rc.name = value``).
|
|
792
|
+
"""
|
|
793
|
+
if attr[:1] == "_":
|
|
794
|
+
super().__setattr__(attr, value)
|
|
795
|
+
else:
|
|
796
|
+
self.__setitem__(attr, value)
|
|
797
|
+
|
|
798
|
+
def __enter__(self):
|
|
799
|
+
"""
|
|
800
|
+
Apply settings from the most recent context block.
|
|
801
|
+
"""
|
|
802
|
+
if not self._context:
|
|
803
|
+
raise RuntimeError(
|
|
804
|
+
"rc object must be initialized for context block using rc.context()."
|
|
805
|
+
)
|
|
806
|
+
context = self._context[-1]
|
|
807
|
+
kwargs = context.kwargs
|
|
808
|
+
rc_new = context.rc_new # used for context-based _get_item_context
|
|
809
|
+
rc_old = context.rc_old # used to re-apply settings without copying whole dict
|
|
810
|
+
for key, value in kwargs.items():
|
|
811
|
+
kw_ultraplot, kw_matplotlib = self._get_item_dicts(key, value)
|
|
812
|
+
for rc_dict, kw_new in zip(
|
|
813
|
+
(rc_ultraplot, rc_matplotlib),
|
|
814
|
+
(kw_ultraplot, kw_matplotlib),
|
|
815
|
+
):
|
|
816
|
+
for key, value in kw_new.items():
|
|
817
|
+
rc_old[key] = rc_dict[key]
|
|
818
|
+
rc_new[key] = rc_dict[key] = value
|
|
819
|
+
|
|
820
|
+
def __exit__(self, *args): # noqa: U100
|
|
821
|
+
"""
|
|
822
|
+
Restore settings from the most recent context block.
|
|
823
|
+
"""
|
|
824
|
+
if not self._context:
|
|
825
|
+
raise RuntimeError(
|
|
826
|
+
"rc object must be initialized for context block using rc.context()."
|
|
827
|
+
)
|
|
828
|
+
context = self._context[-1]
|
|
829
|
+
for key, value in context.rc_old.items():
|
|
830
|
+
kw_ultraplot, kw_matplotlib = self._get_item_dicts(key, value)
|
|
831
|
+
rc_ultraplot.update(kw_ultraplot)
|
|
832
|
+
rc_matplotlib.update(kw_matplotlib)
|
|
833
|
+
del self._context[-1]
|
|
834
|
+
|
|
835
|
+
def _init(self, *, local, user, default, skip_cycle=False):
|
|
836
|
+
"""
|
|
837
|
+
Initialize the configurator.
|
|
838
|
+
"""
|
|
839
|
+
# Always remove context objects
|
|
840
|
+
self._context.clear()
|
|
841
|
+
|
|
842
|
+
# Update from default settings
|
|
843
|
+
# NOTE: see _remove_blacklisted_style_params bugfix
|
|
844
|
+
if default:
|
|
845
|
+
rc_matplotlib.update(_get_style_dict("original", filter=False))
|
|
846
|
+
rc_matplotlib.update(rcsetup._rc_matplotlib_default)
|
|
847
|
+
rc_ultraplot.update(rcsetup._rc_ultraplot_default)
|
|
848
|
+
for key, value in rc_ultraplot.items():
|
|
849
|
+
kw_ultraplot, kw_matplotlib = self._get_item_dicts(
|
|
850
|
+
key, value, skip_cycle=skip_cycle
|
|
851
|
+
)
|
|
852
|
+
rc_matplotlib.update(kw_matplotlib)
|
|
853
|
+
rc_ultraplot.update(kw_ultraplot)
|
|
854
|
+
|
|
855
|
+
# Update from user home
|
|
856
|
+
user_path = None
|
|
857
|
+
if user:
|
|
858
|
+
user_path = self.user_file()
|
|
859
|
+
if os.path.isfile(user_path):
|
|
860
|
+
self.load(user_path)
|
|
861
|
+
|
|
862
|
+
# Update from local paths
|
|
863
|
+
if local:
|
|
864
|
+
local_paths = self.local_files()
|
|
865
|
+
for path in local_paths:
|
|
866
|
+
if path == user_path: # local files always have precedence
|
|
867
|
+
continue
|
|
868
|
+
self.load(path)
|
|
869
|
+
|
|
870
|
+
@staticmethod
|
|
871
|
+
def _validate_key(key, value=None):
|
|
872
|
+
"""
|
|
873
|
+
Validate setting names and handle `rc_ultraplot` deprecations.
|
|
874
|
+
"""
|
|
875
|
+
# NOTE: Not necessary to check matplotlib key here because... not sure why.
|
|
876
|
+
# Think deprecated matplotlib keys are not involved in any synced settings.
|
|
877
|
+
# Also note _check_key includes special handling for some renamed keys.
|
|
878
|
+
if not isinstance(key, str):
|
|
879
|
+
raise KeyError(f"Invalid key {key!r}. Must be string.")
|
|
880
|
+
key = key.lower()
|
|
881
|
+
if "." not in key:
|
|
882
|
+
key = rcsetup._rc_nodots.get(key, key)
|
|
883
|
+
key, value = rc_ultraplot._check_key(key, value) # may issue deprecation warning
|
|
884
|
+
return key, value
|
|
885
|
+
|
|
886
|
+
@staticmethod
|
|
887
|
+
def _validate_value(key, value):
|
|
888
|
+
"""
|
|
889
|
+
Validate setting values and convert numpy ndarray to list if possible.
|
|
890
|
+
"""
|
|
891
|
+
# NOTE: Ideally would implicitly validate on subsequent assignment to rc
|
|
892
|
+
# dictionaries, but must explicitly do it here, so _get_item_dicts can
|
|
893
|
+
# work with e.g. 'tick.lenratio', so _get_item_dicts does not have to include
|
|
894
|
+
# deprecated name handling in its if statements, and so _load_file can
|
|
895
|
+
# catch errors and emit warnings with line number indications as files
|
|
896
|
+
# are being read rather than after the end of the file reading.
|
|
897
|
+
if isinstance(value, np.ndarray):
|
|
898
|
+
value = value.item() if value.size == 1 else value.tolist()
|
|
899
|
+
validate_matplotlib = getattr(rc_matplotlib, "validate", None)
|
|
900
|
+
validate_ultraplot = rc_ultraplot._validate
|
|
901
|
+
if validate_matplotlib is not None and key in validate_matplotlib:
|
|
902
|
+
value = validate_matplotlib[key](value)
|
|
903
|
+
elif key in validate_ultraplot:
|
|
904
|
+
value = validate_ultraplot[key](value)
|
|
905
|
+
return value
|
|
906
|
+
|
|
907
|
+
def _get_item_context(self, key, mode=None):
|
|
908
|
+
"""
|
|
909
|
+
As with `~Configurator.__getitem__` but the search is limited based
|
|
910
|
+
on the context mode and ``None`` is returned if the key is not found.
|
|
911
|
+
"""
|
|
912
|
+
key, _ = self._validate_key(key)
|
|
913
|
+
if mode is None:
|
|
914
|
+
mode = self._context_mode
|
|
915
|
+
cache = tuple(context.rc_new for context in self._context)
|
|
916
|
+
if mode == 0:
|
|
917
|
+
rcdicts = (*cache, rc_ultraplot, rc_matplotlib)
|
|
918
|
+
elif mode == 1:
|
|
919
|
+
rcdicts = (*cache, rc_ultraplot) # added settings only!
|
|
920
|
+
elif mode == 2:
|
|
921
|
+
rcdicts = (*cache,)
|
|
922
|
+
else:
|
|
923
|
+
raise ValueError(f"Invalid caching mode {mode!r}.")
|
|
924
|
+
for rcdict in rcdicts:
|
|
925
|
+
if not rcdict:
|
|
926
|
+
continue
|
|
927
|
+
try:
|
|
928
|
+
return rcdict[key]
|
|
929
|
+
except KeyError:
|
|
930
|
+
continue
|
|
931
|
+
if mode == 0: # otherwise return None
|
|
932
|
+
raise KeyError(f"Invalid rc setting {key!r}.")
|
|
933
|
+
|
|
934
|
+
def _get_item_dicts(self, key, value, skip_cycle=False):
|
|
935
|
+
"""
|
|
936
|
+
Return dictionaries for updating the `rc_ultraplot` and `rc_matplotlib`
|
|
937
|
+
properties associated with this key. Used when setting items, entering
|
|
938
|
+
context blocks, or loading files.
|
|
939
|
+
"""
|
|
940
|
+
# Get validated key, value, and child keys
|
|
941
|
+
key, value = self._validate_key(key, value)
|
|
942
|
+
value = self._validate_value(key, value)
|
|
943
|
+
keys = (key,) + rcsetup._rc_children.get(key, ()) # settings to change
|
|
944
|
+
contains = lambda *args: any(arg in keys for arg in args) # noqa: E731
|
|
945
|
+
|
|
946
|
+
# Fill dictionaries of matplotlib and ultraplot settings
|
|
947
|
+
# NOTE: Raise key error right away so it can be caught by _load_file().
|
|
948
|
+
# Also ignore deprecation warnings so we only get them *once* on assignment
|
|
949
|
+
kw_ultraplot = {} # custom properties
|
|
950
|
+
kw_matplotlib = {} # builtin properties
|
|
951
|
+
with warnings.catch_warnings():
|
|
952
|
+
warnings.simplefilter("ignore", mpl.MatplotlibDeprecationWarning)
|
|
953
|
+
warnings.simplefilter("ignore", warnings.UltraplotWarning)
|
|
954
|
+
for key in keys:
|
|
955
|
+
if key in rc_matplotlib:
|
|
956
|
+
kw_matplotlib[key] = value
|
|
957
|
+
elif key in rc_ultraplot:
|
|
958
|
+
kw_ultraplot[key] = value
|
|
959
|
+
else:
|
|
960
|
+
raise KeyError(f"Invalid rc setting {key!r}.")
|
|
961
|
+
|
|
962
|
+
# Special key: configure inline backend
|
|
963
|
+
if contains("inlineformat"):
|
|
964
|
+
config_inline_backend(value)
|
|
965
|
+
|
|
966
|
+
# Special key: apply stylesheet
|
|
967
|
+
elif contains("style"):
|
|
968
|
+
if value is not None:
|
|
969
|
+
ikw_matplotlib = _get_style_dict(value)
|
|
970
|
+
kw_matplotlib.update(ikw_matplotlib)
|
|
971
|
+
kw_ultraplot.update(_infer_ultraplot_dict(ikw_matplotlib))
|
|
972
|
+
|
|
973
|
+
# Cycler
|
|
974
|
+
# NOTE: Have to skip this step during initial ultraplot import
|
|
975
|
+
elif contains("cycle") and not skip_cycle:
|
|
976
|
+
from .colors import _get_cmap_subtype
|
|
977
|
+
|
|
978
|
+
cmap = _get_cmap_subtype(value, "discrete")
|
|
979
|
+
kw_matplotlib["axes.prop_cycle"] = cycler.cycler("color", cmap.colors)
|
|
980
|
+
kw_matplotlib["patch.facecolor"] = "C0"
|
|
981
|
+
|
|
982
|
+
# Turning bounding box on should turn border off and vice versa
|
|
983
|
+
elif contains("abc.bbox", "title.bbox", "abc.border", "title.border"):
|
|
984
|
+
if value:
|
|
985
|
+
name, this = key.split(".")
|
|
986
|
+
other = "border" if this == "bbox" else "bbox"
|
|
987
|
+
kw_ultraplot[name + "." + other] = False
|
|
988
|
+
|
|
989
|
+
# Fontsize
|
|
990
|
+
# NOTE: Re-application of e.g. size='small' uses the updated 'font.size'
|
|
991
|
+
elif contains("font.size"):
|
|
992
|
+
kw_ultraplot.update(
|
|
993
|
+
{
|
|
994
|
+
key: value
|
|
995
|
+
for key, value in rc_ultraplot.items()
|
|
996
|
+
if key in rcsetup.FONT_KEYS and value in mfonts.font_scalings
|
|
997
|
+
}
|
|
998
|
+
)
|
|
999
|
+
kw_matplotlib.update(
|
|
1000
|
+
{
|
|
1001
|
+
key: value
|
|
1002
|
+
for key, value in rc_matplotlib.items()
|
|
1003
|
+
if key in rcsetup.FONT_KEYS and value in mfonts.font_scalings
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
# Tick length/major-minor tick length ratio
|
|
1008
|
+
elif contains("tick.len", "tick.lenratio"):
|
|
1009
|
+
if contains("tick.len"):
|
|
1010
|
+
ticklen = value
|
|
1011
|
+
ratio = rc_ultraplot["tick.lenratio"]
|
|
1012
|
+
else:
|
|
1013
|
+
ticklen = rc_ultraplot["tick.len"]
|
|
1014
|
+
ratio = value
|
|
1015
|
+
kw_matplotlib["xtick.minor.size"] = ticklen * ratio
|
|
1016
|
+
kw_matplotlib["ytick.minor.size"] = ticklen * ratio
|
|
1017
|
+
|
|
1018
|
+
# Spine width/major-minor tick width ratio
|
|
1019
|
+
elif contains("tick.width", "tick.widthratio"):
|
|
1020
|
+
if contains("tick.width"):
|
|
1021
|
+
tickwidth = value
|
|
1022
|
+
ratio = rc_ultraplot["tick.widthratio"]
|
|
1023
|
+
else:
|
|
1024
|
+
tickwidth = rc_ultraplot["tick.width"]
|
|
1025
|
+
ratio = value
|
|
1026
|
+
kw_matplotlib["xtick.minor.width"] = tickwidth * ratio
|
|
1027
|
+
kw_matplotlib["ytick.minor.width"] = tickwidth * ratio
|
|
1028
|
+
|
|
1029
|
+
# Gridline width
|
|
1030
|
+
elif contains("grid.width", "grid.widthratio"):
|
|
1031
|
+
if contains("grid.width"):
|
|
1032
|
+
gridwidth = value
|
|
1033
|
+
ratio = rc_ultraplot["grid.widthratio"]
|
|
1034
|
+
else:
|
|
1035
|
+
gridwidth = rc_ultraplot["grid.width"]
|
|
1036
|
+
ratio = value
|
|
1037
|
+
kw_ultraplot["gridminor.linewidth"] = gridwidth * ratio
|
|
1038
|
+
kw_ultraplot["gridminor.width"] = gridwidth * ratio
|
|
1039
|
+
|
|
1040
|
+
# Gridline toggling
|
|
1041
|
+
elif contains("grid", "gridminor"):
|
|
1042
|
+
b, which = _translate_grid(
|
|
1043
|
+
value, "gridminor" if contains("gridminor") else "grid"
|
|
1044
|
+
)
|
|
1045
|
+
kw_matplotlib["axes.grid"] = b
|
|
1046
|
+
kw_matplotlib["axes.grid.which"] = which
|
|
1047
|
+
|
|
1048
|
+
return kw_ultraplot, kw_matplotlib
|
|
1049
|
+
|
|
1050
|
+
@staticmethod
|
|
1051
|
+
def _get_axisbelow_zorder(axisbelow):
|
|
1052
|
+
"""
|
|
1053
|
+
Convert the `axisbelow` string to its corresponding `zorder`.
|
|
1054
|
+
"""
|
|
1055
|
+
if axisbelow is True:
|
|
1056
|
+
zorder = 0.5
|
|
1057
|
+
elif axisbelow is False:
|
|
1058
|
+
zorder = 2.5
|
|
1059
|
+
elif axisbelow in ("line", "lines"):
|
|
1060
|
+
zorder = 1.5
|
|
1061
|
+
else:
|
|
1062
|
+
raise ValueError(f"Unexpected axisbelow value {axisbelow!r}.")
|
|
1063
|
+
return zorder
|
|
1064
|
+
|
|
1065
|
+
def _get_background_props(self, patch_kw=None, native=True, **kwargs):
|
|
1066
|
+
"""
|
|
1067
|
+
Return background properties, optionally filtering the output dictionary
|
|
1068
|
+
based on the context.
|
|
1069
|
+
"""
|
|
1070
|
+
# Deprecated behavior
|
|
1071
|
+
context = native or self._context_mode == 2
|
|
1072
|
+
if patch_kw:
|
|
1073
|
+
warnings._warn_ultraplot(
|
|
1074
|
+
"'patch_kw' is no longer necessary as of ultraplot v0.8. "
|
|
1075
|
+
"Pass the parameters as keyword arguments instead."
|
|
1076
|
+
)
|
|
1077
|
+
kwargs.update(patch_kw)
|
|
1078
|
+
|
|
1079
|
+
# Get user-input properties and changed rc settings
|
|
1080
|
+
# NOTE: Here we use 'color' as an alias for just 'edgecolor' rather than
|
|
1081
|
+
# both 'edgecolor' and 'facecolor' to match 'xcolor' and 'ycolor' arguments.
|
|
1082
|
+
props = _pop_props(kwargs, "patch")
|
|
1083
|
+
if "color" in props:
|
|
1084
|
+
props.setdefault("edgecolor", props.pop("color"))
|
|
1085
|
+
for key in ("alpha", "facecolor", "linewidth", "edgecolor"):
|
|
1086
|
+
value = self.find("axes." + key, context=context)
|
|
1087
|
+
if value is not None:
|
|
1088
|
+
props.setdefault(key, value)
|
|
1089
|
+
|
|
1090
|
+
# Partition properties into face and edge
|
|
1091
|
+
kw_face = _pop_kwargs(props, "alpha", "facecolor")
|
|
1092
|
+
kw_edge = _pop_kwargs(props, "edgecolor", "linewidth", "linestyle")
|
|
1093
|
+
kw_edge["capstyle"] = "projecting" # NOTE: needed to fix cartopy bounds
|
|
1094
|
+
if "color" in props:
|
|
1095
|
+
kw_edge.setdefault("edgecolor", props.pop("color"))
|
|
1096
|
+
if kwargs:
|
|
1097
|
+
raise TypeError(f"Unexpected keyword argument(s): {kwargs!r}")
|
|
1098
|
+
|
|
1099
|
+
return kw_face, kw_edge
|
|
1100
|
+
|
|
1101
|
+
def _get_gridline_bool(self, grid=None, axis=None, which="major", native=True):
|
|
1102
|
+
"""
|
|
1103
|
+
Return major and minor gridline toggles from ``axes.grid``, ``axes.grid.which``,
|
|
1104
|
+
and ``axes.grid.axis``, optionally returning `None` based on the context.
|
|
1105
|
+
"""
|
|
1106
|
+
# NOTE: If you pass 'grid' or 'gridminor' the native args are updated
|
|
1107
|
+
# NOTE: Very careful to return not None only if setting was changed.
|
|
1108
|
+
# Avoid unnecessarily triggering grid redraws (esp. bad for geo.py)
|
|
1109
|
+
context = native or self._context_mode == 2
|
|
1110
|
+
grid_on = self.find("axes.grid", context=context)
|
|
1111
|
+
which_on = self.find("axes.grid.which", context=context)
|
|
1112
|
+
if grid_on is not None or which_on is not None: # if *one* was changed
|
|
1113
|
+
axis_on = self["axes.grid.axis"] # always need this property
|
|
1114
|
+
grid_on = _not_none(grid_on, self["axes.grid"])
|
|
1115
|
+
which_on = _not_none(which_on, self["axes.grid.which"])
|
|
1116
|
+
axis = _not_none(axis, "x")
|
|
1117
|
+
axis_on = axis is None or axis_on in (axis, "both")
|
|
1118
|
+
which_on = which_on in (which, "both")
|
|
1119
|
+
grid = _not_none(grid, grid_on and axis_on and which_on)
|
|
1120
|
+
return grid
|
|
1121
|
+
|
|
1122
|
+
def _get_gridline_props(self, which="major", native=True, rebuild=False):
|
|
1123
|
+
"""
|
|
1124
|
+
Return gridline properties, optionally filtering the output dictionary
|
|
1125
|
+
based on the context.
|
|
1126
|
+
"""
|
|
1127
|
+
# Line properties
|
|
1128
|
+
# NOTE: Gridline zorder is controlled automatically by matplotlib but
|
|
1129
|
+
# must be controlled manually for geographic projections
|
|
1130
|
+
key = "grid" if which == "major" else "gridminor"
|
|
1131
|
+
prefix = "grid_" if native else "" # for native gridlines use this prefix
|
|
1132
|
+
context = not rebuild and (native or self._context_mode == 2)
|
|
1133
|
+
kwlines = self.fill(
|
|
1134
|
+
{
|
|
1135
|
+
f"{prefix}alpha": f"{key}.alpha",
|
|
1136
|
+
f"{prefix}color": f"{key}.color",
|
|
1137
|
+
f"{prefix}linewidth": f"{key}.linewidth",
|
|
1138
|
+
f"{prefix}linestyle": f"{key}.linestyle",
|
|
1139
|
+
},
|
|
1140
|
+
context=context,
|
|
1141
|
+
)
|
|
1142
|
+
axisbelow = self.find("axes.axisbelow", context=context)
|
|
1143
|
+
if axisbelow is not None:
|
|
1144
|
+
if native: # this is a native plot so use set_axisbelow() down the line
|
|
1145
|
+
kwlines["axisbelow"] = axisbelow
|
|
1146
|
+
else: # this is a geographic plot so apply with zorder
|
|
1147
|
+
kwlines["zorder"] = self._get_axisbelow_zorder(axisbelow)
|
|
1148
|
+
return kwlines
|
|
1149
|
+
|
|
1150
|
+
def _get_label_props(self, native=True, **kwargs):
|
|
1151
|
+
"""
|
|
1152
|
+
Return the axis label properties, optionally filtering the output dictionary
|
|
1153
|
+
based on the context.
|
|
1154
|
+
"""
|
|
1155
|
+
# Get the label settings
|
|
1156
|
+
# NOTE: This permits passing arbitrary additional args to set_[xy]label()
|
|
1157
|
+
context = native or self._context_mode == 2
|
|
1158
|
+
kw = self.fill(
|
|
1159
|
+
{
|
|
1160
|
+
"color": "axes.labelcolor",
|
|
1161
|
+
"weight": "axes.labelweight",
|
|
1162
|
+
"size": "axes.labelsize",
|
|
1163
|
+
"family": "font.family",
|
|
1164
|
+
"labelpad": "axes.labelpad", # read by set_xlabel/set_ylabel
|
|
1165
|
+
},
|
|
1166
|
+
context=context,
|
|
1167
|
+
)
|
|
1168
|
+
for key, value in kwargs.items():
|
|
1169
|
+
if value is not None: # allow e.g. color=None
|
|
1170
|
+
kw[key] = value
|
|
1171
|
+
return kw
|
|
1172
|
+
|
|
1173
|
+
def _get_loc_string(self, string, axis=None, native=True):
|
|
1174
|
+
"""
|
|
1175
|
+
Return `tickloc` and `spineloc` location strings from the `rc` boolean toggles,
|
|
1176
|
+
optionally returning `None` based on the context.
|
|
1177
|
+
"""
|
|
1178
|
+
context = native or self._context_mode == 2
|
|
1179
|
+
axis = _not_none(axis, "x")
|
|
1180
|
+
opt1, opt2 = ("top", "bottom") if axis == "x" else ("left", "right")
|
|
1181
|
+
b1 = self.find(f"{string}.{opt1}", context=context)
|
|
1182
|
+
b2 = self.find(f"{string}.{opt2}", context=context)
|
|
1183
|
+
if b1 is None and b2 is None:
|
|
1184
|
+
return None
|
|
1185
|
+
elif b1 and b2:
|
|
1186
|
+
return "both"
|
|
1187
|
+
elif b1:
|
|
1188
|
+
return opt1
|
|
1189
|
+
elif b2:
|
|
1190
|
+
return opt2
|
|
1191
|
+
else:
|
|
1192
|
+
return "neither"
|
|
1193
|
+
|
|
1194
|
+
def _get_tickline_props(self, axis=None, which="major", native=True, rebuild=False):
|
|
1195
|
+
"""
|
|
1196
|
+
Return the tick line properties, optionally filtering the output dictionary
|
|
1197
|
+
based on the context.
|
|
1198
|
+
"""
|
|
1199
|
+
# Tick properties obtained with rc.category
|
|
1200
|
+
# NOTE: This loads 'size', 'width', 'pad', 'bottom', and 'top'
|
|
1201
|
+
axis = _not_none(axis, "x")
|
|
1202
|
+
context = not rebuild and (native or self._context_mode == 2)
|
|
1203
|
+
kwticks = self.category(f"{axis}tick.{which}", context=context)
|
|
1204
|
+
kwticks.pop("visible", None)
|
|
1205
|
+
for key in ("color", "direction"):
|
|
1206
|
+
value = self.find(f"{axis}tick.{key}", context=context)
|
|
1207
|
+
if value is not None:
|
|
1208
|
+
kwticks[key] = value
|
|
1209
|
+
return kwticks
|
|
1210
|
+
|
|
1211
|
+
def _get_ticklabel_props(self, axis=None, native=True, rebuild=False):
|
|
1212
|
+
"""
|
|
1213
|
+
Return the tick label properties, optionally filtering the output dictionary
|
|
1214
|
+
based on the context.
|
|
1215
|
+
"""
|
|
1216
|
+
# NOTE: 'tick.label' properties are now synonyms of 'grid.label' properties
|
|
1217
|
+
sprefix = axis or ""
|
|
1218
|
+
cprefix = sprefix if _version_mpl >= "3.4" else "" # new settings
|
|
1219
|
+
context = not rebuild and (native or self._context_mode == 2)
|
|
1220
|
+
kwtext = self.fill(
|
|
1221
|
+
{
|
|
1222
|
+
"color": f"{cprefix}tick.labelcolor", # native setting sometimes avail
|
|
1223
|
+
"size": f"{sprefix}tick.labelsize", # native setting always avail
|
|
1224
|
+
"weight": "tick.labelweight", # native setting never avail
|
|
1225
|
+
"family": "font.family", # apply manually
|
|
1226
|
+
},
|
|
1227
|
+
context=context,
|
|
1228
|
+
)
|
|
1229
|
+
if kwtext.get("color", None) == "inherit":
|
|
1230
|
+
# Inheritence is not automatic for geographic
|
|
1231
|
+
# gridline labels so we apply inheritence here.
|
|
1232
|
+
kwtext["color"] = self[f"{sprefix}tick.color"]
|
|
1233
|
+
return kwtext
|
|
1234
|
+
|
|
1235
|
+
@staticmethod
|
|
1236
|
+
def local_files():
|
|
1237
|
+
"""
|
|
1238
|
+
Return locations of files named ``ultraplotrc`` in this directory and in parent
|
|
1239
|
+
directories. "Hidden" files with a leading dot are also recognized. These are
|
|
1240
|
+
automatically loaded when ultraplot is imported.
|
|
1241
|
+
|
|
1242
|
+
See also
|
|
1243
|
+
--------
|
|
1244
|
+
Configurator.user_file
|
|
1245
|
+
Configurator.local_folders
|
|
1246
|
+
"""
|
|
1247
|
+
cdir = os.getcwd()
|
|
1248
|
+
paths = []
|
|
1249
|
+
while cdir: # i.e. not root
|
|
1250
|
+
for name in ("ultraplotrc", ".ultraplotrc"):
|
|
1251
|
+
path = os.path.join(cdir, name)
|
|
1252
|
+
if os.path.isfile(path):
|
|
1253
|
+
paths.append(path)
|
|
1254
|
+
ndir = os.path.dirname(cdir)
|
|
1255
|
+
if ndir == cdir: # root
|
|
1256
|
+
break
|
|
1257
|
+
cdir = ndir
|
|
1258
|
+
return paths[::-1] # sort from decreasing to increasing importantce
|
|
1259
|
+
|
|
1260
|
+
@staticmethod
|
|
1261
|
+
def local_folders(subfolder=None):
|
|
1262
|
+
"""
|
|
1263
|
+
Return locations of folders named ``ultraplot_cmaps``, ``ultraplot_cycles``,
|
|
1264
|
+
``ultraplot_colors``, and ``ultraplot_fonts`` in this directory and in parent
|
|
1265
|
+
directories. "Hidden" folders with a leading dot are also recognized. Files
|
|
1266
|
+
in these directories are automatically loaded when ultraplot is imported.
|
|
1267
|
+
|
|
1268
|
+
See also
|
|
1269
|
+
--------
|
|
1270
|
+
Configurator.user_folder
|
|
1271
|
+
Configurator.local_files
|
|
1272
|
+
"""
|
|
1273
|
+
cdir = os.getcwd()
|
|
1274
|
+
paths = []
|
|
1275
|
+
if subfolder is None:
|
|
1276
|
+
subfolder = ("cmaps", "cycles", "colors", "fonts")
|
|
1277
|
+
if isinstance(subfolder, str):
|
|
1278
|
+
subfolder = (subfolder,)
|
|
1279
|
+
while cdir: # i.e. not root
|
|
1280
|
+
for prefix in ("ultraplot", ".ultraplot"):
|
|
1281
|
+
for suffix in subfolder:
|
|
1282
|
+
path = os.path.join(cdir, "_".join((prefix, suffix)))
|
|
1283
|
+
if os.path.isdir(path):
|
|
1284
|
+
paths.append(path)
|
|
1285
|
+
ndir = os.path.dirname(cdir)
|
|
1286
|
+
if ndir == cdir: # root
|
|
1287
|
+
break
|
|
1288
|
+
cdir = ndir
|
|
1289
|
+
return paths[::-1]
|
|
1290
|
+
|
|
1291
|
+
@staticmethod
|
|
1292
|
+
def _config_folder():
|
|
1293
|
+
"""
|
|
1294
|
+
Get the XDG ultraplot folder.
|
|
1295
|
+
"""
|
|
1296
|
+
home = os.path.expanduser("~")
|
|
1297
|
+
base = os.environ.get("XDG_CONFIG_HOME")
|
|
1298
|
+
if not base:
|
|
1299
|
+
base = os.path.join(home, ".config")
|
|
1300
|
+
if sys.platform.startswith(("linux", "freebsd")) and os.path.isdir(base):
|
|
1301
|
+
configdir = os.path.join(base, "ultraplot")
|
|
1302
|
+
else:
|
|
1303
|
+
configdir = os.path.join(home, ".ultraplot")
|
|
1304
|
+
return configdir
|
|
1305
|
+
|
|
1306
|
+
@staticmethod
|
|
1307
|
+
def user_file():
|
|
1308
|
+
"""
|
|
1309
|
+
Return location of the default ultraplotrc file. On Linux, this is either
|
|
1310
|
+
``$XDG_CONFIG_HOME/ultraplot/ultraplotrc`` or ``~/.config/ultraplot/ultraplotrc``
|
|
1311
|
+
if the `XDG directory <https://wiki.archlinux.org/title/XDG_Base_Directory>`__
|
|
1312
|
+
is unset. On other operating systems, this is ``~/.ultraplot/ultraplotrc``. The
|
|
1313
|
+
location ``~/.ultraplotrc`` or ``~/.ultraplot/ultraplotrc`` is always returned if the
|
|
1314
|
+
file exists, regardless of the operating system. If multiple valid locations
|
|
1315
|
+
are found, a warning is raised.
|
|
1316
|
+
|
|
1317
|
+
See also
|
|
1318
|
+
--------
|
|
1319
|
+
Configurator.user_folder
|
|
1320
|
+
Configurator.local_files
|
|
1321
|
+
"""
|
|
1322
|
+
# Support both loose files and files inside .ultraplot
|
|
1323
|
+
file = os.path.join(Configurator.user_folder(), "ultraplotrc")
|
|
1324
|
+
universal = os.path.join(os.path.expanduser("~"), ".ultraplotrc")
|
|
1325
|
+
if os.path.isfile(universal):
|
|
1326
|
+
if file != universal and os.path.isfile(file):
|
|
1327
|
+
warnings._warn_ultraplot(
|
|
1328
|
+
"Found conflicting default user ultraplotrc files at "
|
|
1329
|
+
f"{universal!r} and {file!r}. Ignoring the second one."
|
|
1330
|
+
)
|
|
1331
|
+
file = universal
|
|
1332
|
+
return file
|
|
1333
|
+
|
|
1334
|
+
@staticmethod
|
|
1335
|
+
def user_folder(subfolder=None):
|
|
1336
|
+
"""
|
|
1337
|
+
Return location of the default ultraplot folder. On Linux, this
|
|
1338
|
+
is either ``$XDG_CONFIG_HOME/ultraplot`` or ``~/.config/ultraplot``
|
|
1339
|
+
if the `XDG directory <https://wiki.archlinux.org/title/XDG_Base_Directory>`__
|
|
1340
|
+
is unset. On other operating systems, this is ``~/.ultraplot``. The location
|
|
1341
|
+
``~/.ultraplot`` is always returned if the folder exists, regardless of the
|
|
1342
|
+
operating system. If multiple valid locations are found, a warning is raised.
|
|
1343
|
+
|
|
1344
|
+
See also
|
|
1345
|
+
--------
|
|
1346
|
+
Configurator.user_file
|
|
1347
|
+
Configurator.local_folders
|
|
1348
|
+
"""
|
|
1349
|
+
# Try the XDG standard location
|
|
1350
|
+
# NOTE: This is borrowed from matplotlib.get_configdir
|
|
1351
|
+
home = os.path.expanduser("~")
|
|
1352
|
+
universal = folder = os.path.join(home, ".ultraplot")
|
|
1353
|
+
if sys.platform.startswith(("linux", "freebsd")):
|
|
1354
|
+
xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
1355
|
+
xdg = xdg or os.path.join(home, ".config")
|
|
1356
|
+
folder = os.path.join(xdg, "ultraplot")
|
|
1357
|
+
# Fallback to the loose ~/.ultraplot if it is present
|
|
1358
|
+
# NOTE: This is critical or we might ignore previously stored settings!
|
|
1359
|
+
if os.path.isdir(universal):
|
|
1360
|
+
if folder != universal and os.path.isdir(folder):
|
|
1361
|
+
warnings._warn_ultraplot(
|
|
1362
|
+
"Found conflicting default user ultraplot folders at "
|
|
1363
|
+
f"{universal!r} and {folder!r}. Ignoring the second one."
|
|
1364
|
+
)
|
|
1365
|
+
folder = universal
|
|
1366
|
+
# Return the folder
|
|
1367
|
+
if subfolder:
|
|
1368
|
+
folder = os.path.join(folder, subfolder)
|
|
1369
|
+
return folder
|
|
1370
|
+
|
|
1371
|
+
def context(self, *args, mode=0, file=None, **kwargs):
|
|
1372
|
+
"""
|
|
1373
|
+
Temporarily modify the rc settings in a "with as" block.
|
|
1374
|
+
|
|
1375
|
+
Parameters
|
|
1376
|
+
----------
|
|
1377
|
+
*args
|
|
1378
|
+
Dictionaries of `rc` keys and values.
|
|
1379
|
+
file : path-like, optional
|
|
1380
|
+
Filename from which settings should be loaded.
|
|
1381
|
+
**kwargs
|
|
1382
|
+
`rc` names and values passed as keyword arguments.
|
|
1383
|
+
If the name has dots, simply omit them.
|
|
1384
|
+
|
|
1385
|
+
Other parameters
|
|
1386
|
+
----------------
|
|
1387
|
+
mode : {0, 1, 2}, optional
|
|
1388
|
+
The context mode. Dictates the behavior of `~Configurator.find`,
|
|
1389
|
+
`~Configurator.fill`, and `~Configurator.category` within a
|
|
1390
|
+
"with as" block when called with ``context=True``.
|
|
1391
|
+
|
|
1392
|
+
The options are as follows:
|
|
1393
|
+
|
|
1394
|
+
* ``mode=0``: Matplotlib's `rc_matplotlib` settings
|
|
1395
|
+
and ultraplot's `rc_ultraplot` settings are all returned,
|
|
1396
|
+
whether or not they are local to the "with as" block.
|
|
1397
|
+
* ``mode=1``: Matplotlib's `rc_matplotlib` settings are only
|
|
1398
|
+
returned if they are local to the "with as" block. For example,
|
|
1399
|
+
if :rcraw:`axes.titlesize` was passed to `~Configurator.context`,
|
|
1400
|
+
then ``pplt.rc.find('axes.titlesize', context=True)`` will return
|
|
1401
|
+
this value, but ``pplt.rc.find('axes.titleweight', context=True)`` will
|
|
1402
|
+
return ``None``. This is used internally when instantiating axes.
|
|
1403
|
+
* ``mode=2``: Matplotlib's `rc_matplotlib` settings and ultraplot's
|
|
1404
|
+
`rc_ultraplot` settings are only returned if they are local to the
|
|
1405
|
+
"with as" block. This is used internally when formatting axes.
|
|
1406
|
+
|
|
1407
|
+
Note
|
|
1408
|
+
----
|
|
1409
|
+
Context "modes" are primarily used internally but may also be useful for power
|
|
1410
|
+
users. Mode ``1`` is used when `~ultraplot.axes.Axes.format` is called during
|
|
1411
|
+
axes instantiation, and mode ``2`` is used when `~ultraplot.axes.Axes.format`
|
|
1412
|
+
is manually called by users. The latter prevents successive calls to
|
|
1413
|
+
`~ultraplot.axes.Axes.format` from constantly looking up and re-applying
|
|
1414
|
+
unchanged settings and significantly increasing the runtime.
|
|
1415
|
+
|
|
1416
|
+
Example
|
|
1417
|
+
-------
|
|
1418
|
+
The below applies settings to axes in a specific figure using
|
|
1419
|
+
`~Configurator.context`.
|
|
1420
|
+
|
|
1421
|
+
>>> import ultraplot as pplt
|
|
1422
|
+
>>> with pplt.rc.context(ticklen=5, metalinewidth=2):
|
|
1423
|
+
>>> fig, ax = pplt.subplots()
|
|
1424
|
+
>>> ax.plot(data)
|
|
1425
|
+
|
|
1426
|
+
The below applies settings to a specific axes using
|
|
1427
|
+
`~ultraplot.axes.Axes.format`, which uses `~Configurator.context`
|
|
1428
|
+
internally.
|
|
1429
|
+
|
|
1430
|
+
>>> import ultraplot as pplt
|
|
1431
|
+
>>> fig, ax = pplt.subplots()
|
|
1432
|
+
>>> ax.format(ticklen=5, metalinewidth=2)
|
|
1433
|
+
"""
|
|
1434
|
+
# Add input dictionaries
|
|
1435
|
+
for arg in args:
|
|
1436
|
+
if not isinstance(arg, dict):
|
|
1437
|
+
raise ValueError(f"Non-dictionary argument {arg!r}.")
|
|
1438
|
+
kwargs.update(arg)
|
|
1439
|
+
|
|
1440
|
+
# Add settings from file
|
|
1441
|
+
if file is not None:
|
|
1442
|
+
kw = self._load_file(file)
|
|
1443
|
+
kw = {key: value for key, value in kw.items() if key not in kwargs}
|
|
1444
|
+
kwargs.update(kw)
|
|
1445
|
+
|
|
1446
|
+
# Activate context object
|
|
1447
|
+
if mode not in range(3):
|
|
1448
|
+
raise ValueError(f"Invalid mode {mode!r}.")
|
|
1449
|
+
cls = namedtuple("RcContext", ("mode", "kwargs", "rc_new", "rc_old"))
|
|
1450
|
+
context = cls(mode=mode, kwargs=kwargs, rc_new={}, rc_old={})
|
|
1451
|
+
self._context.append(context)
|
|
1452
|
+
return self
|
|
1453
|
+
|
|
1454
|
+
def category(self, cat, *, trimcat=True, context=False):
|
|
1455
|
+
"""
|
|
1456
|
+
Return a dictionary of settings beginning with the substring ``cat + '.'``.
|
|
1457
|
+
Optionally limit the search to the context level.
|
|
1458
|
+
|
|
1459
|
+
Parameters
|
|
1460
|
+
----------
|
|
1461
|
+
cat : str, optional
|
|
1462
|
+
The `rc` setting category.
|
|
1463
|
+
trimcat : bool, default: True
|
|
1464
|
+
Whether to trim ``cat`` from the key names in the output dictionary.
|
|
1465
|
+
context : bool, default: False
|
|
1466
|
+
If ``True``, then settings not found in the context dictionaries
|
|
1467
|
+
are omitted from the output dictionary. See `~Configurator.context`.
|
|
1468
|
+
|
|
1469
|
+
See also
|
|
1470
|
+
--------
|
|
1471
|
+
Configurator.find
|
|
1472
|
+
Configurator.fill
|
|
1473
|
+
"""
|
|
1474
|
+
kw = {}
|
|
1475
|
+
if cat not in rcsetup._rc_categories:
|
|
1476
|
+
raise ValueError(
|
|
1477
|
+
f"Invalid rc category {cat!r}. Valid categories are: "
|
|
1478
|
+
+ ", ".join(map(repr, rcsetup._rc_categories))
|
|
1479
|
+
+ "."
|
|
1480
|
+
)
|
|
1481
|
+
for key in self:
|
|
1482
|
+
if not re.match(rf"\A{cat}\.[^.]+\Z", key):
|
|
1483
|
+
continue
|
|
1484
|
+
value = self._get_item_context(key, None if context else 0)
|
|
1485
|
+
if value is None:
|
|
1486
|
+
continue
|
|
1487
|
+
if trimcat:
|
|
1488
|
+
key = re.sub(rf"\A{cat}\.", "", key)
|
|
1489
|
+
kw[key] = value
|
|
1490
|
+
return kw
|
|
1491
|
+
|
|
1492
|
+
def fill(self, props, *, context=False):
|
|
1493
|
+
"""
|
|
1494
|
+
Return a dictionary filled with settings whose names match the string values
|
|
1495
|
+
in the input dictionary. Optionally limit the search to the context level.
|
|
1496
|
+
|
|
1497
|
+
Parameters
|
|
1498
|
+
----------
|
|
1499
|
+
props : dict-like
|
|
1500
|
+
Dictionary whose values are setting names -- for example
|
|
1501
|
+
``rc.fill({'edgecolor': 'axes.edgecolor', 'facecolor': 'axes.facecolor'})``.
|
|
1502
|
+
context : bool, default: False
|
|
1503
|
+
If ``True``, then settings not found in the context dictionaries
|
|
1504
|
+
are omitted from the output dictionary. See `~Configurator.context`.
|
|
1505
|
+
|
|
1506
|
+
See also
|
|
1507
|
+
--------
|
|
1508
|
+
Configurator.category
|
|
1509
|
+
Configurator.find
|
|
1510
|
+
"""
|
|
1511
|
+
kw = {}
|
|
1512
|
+
for key, value in props.items():
|
|
1513
|
+
item = self._get_item_context(value, None if context else 0)
|
|
1514
|
+
if item is not None:
|
|
1515
|
+
kw[key] = item
|
|
1516
|
+
return kw
|
|
1517
|
+
|
|
1518
|
+
def find(self, key, *, context=False):
|
|
1519
|
+
"""
|
|
1520
|
+
Return a single setting. Optionally limit the search to the context level.
|
|
1521
|
+
|
|
1522
|
+
Parameters
|
|
1523
|
+
----------
|
|
1524
|
+
key : str
|
|
1525
|
+
The single setting name.
|
|
1526
|
+
context : bool, default: False
|
|
1527
|
+
If ``True``, then ``None`` is returned if the setting is not found
|
|
1528
|
+
in the context dictionaries. See `~Configurator.context`.
|
|
1529
|
+
|
|
1530
|
+
See also
|
|
1531
|
+
--------
|
|
1532
|
+
Configurator.category
|
|
1533
|
+
Configurator.fill
|
|
1534
|
+
"""
|
|
1535
|
+
return self._get_item_context(key, None if context else 0)
|
|
1536
|
+
|
|
1537
|
+
def update(self, *args, **kwargs):
|
|
1538
|
+
"""
|
|
1539
|
+
Update several settings at once.
|
|
1540
|
+
|
|
1541
|
+
Parameters
|
|
1542
|
+
----------
|
|
1543
|
+
*args : str or dict-like, optional
|
|
1544
|
+
A dictionary containing `rc` keys and values. You can also pass
|
|
1545
|
+
a "category" name as the first argument, in which case all
|
|
1546
|
+
settings are prepended with ``'category.'``. For example,
|
|
1547
|
+
``rc.update('axes', labelsize=20, titlesize=20)`` changes the
|
|
1548
|
+
:rcraw:`axes.labelsize` and :rcraw:`axes.titlesize` settings.
|
|
1549
|
+
**kwargs
|
|
1550
|
+
`rc` keys and values passed as keyword arguments.
|
|
1551
|
+
If the name has dots, simply omit them.
|
|
1552
|
+
|
|
1553
|
+
See also
|
|
1554
|
+
--------
|
|
1555
|
+
Configurator.category
|
|
1556
|
+
Configurator.fill
|
|
1557
|
+
"""
|
|
1558
|
+
prefix, kw = "", {}
|
|
1559
|
+
if not args:
|
|
1560
|
+
pass
|
|
1561
|
+
elif len(args) == 1 and isinstance(args[0], str):
|
|
1562
|
+
prefix = args[0]
|
|
1563
|
+
elif len(args) == 1 and isinstance(args[0], dict):
|
|
1564
|
+
kw = args[0]
|
|
1565
|
+
elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], dict):
|
|
1566
|
+
prefix, kw = args
|
|
1567
|
+
else:
|
|
1568
|
+
raise ValueError(
|
|
1569
|
+
f"Invalid arguments {args!r}. Usage is either "
|
|
1570
|
+
"rc.update(dict), rc.update(kwy=value, ...), "
|
|
1571
|
+
"rc.update(category, dict), or rc.update(category, key=value, ...)."
|
|
1572
|
+
)
|
|
1573
|
+
prefix = prefix and prefix + "."
|
|
1574
|
+
kw.update(kwargs)
|
|
1575
|
+
for key, value in kw.items():
|
|
1576
|
+
self.__setitem__(prefix + key, value)
|
|
1577
|
+
|
|
1578
|
+
@docstring._snippet_manager
|
|
1579
|
+
def reset(self, local=True, user=True, default=True, **kwargs):
|
|
1580
|
+
"""
|
|
1581
|
+
Reset the configurator to its initial state.
|
|
1582
|
+
|
|
1583
|
+
Parameters
|
|
1584
|
+
----------
|
|
1585
|
+
%(rc.params)s
|
|
1586
|
+
"""
|
|
1587
|
+
self._init(local=local, user=user, default=default, **kwargs)
|
|
1588
|
+
|
|
1589
|
+
def _load_file(self, path):
|
|
1590
|
+
"""
|
|
1591
|
+
Return dictionaries of ultraplot and matplotlib settings loaded from the file.
|
|
1592
|
+
"""
|
|
1593
|
+
# WARNING: Critical to not yet apply _get_item_dicts() syncing or else we
|
|
1594
|
+
# can overwrite input settings (e.g. label.size followed by font.size).
|
|
1595
|
+
path = os.path.expanduser(path)
|
|
1596
|
+
added = set()
|
|
1597
|
+
rcdict = {}
|
|
1598
|
+
with open(path, "r") as fh:
|
|
1599
|
+
for idx, line in enumerate(fh):
|
|
1600
|
+
# Strip comments
|
|
1601
|
+
message = f"line #{idx + 1} in file {path!r}"
|
|
1602
|
+
stripped = line.split("#", 1)[0].strip()
|
|
1603
|
+
if not stripped:
|
|
1604
|
+
pass # no warning
|
|
1605
|
+
continue
|
|
1606
|
+
# Parse the pair
|
|
1607
|
+
pair = stripped.split(":", 1)
|
|
1608
|
+
if len(pair) != 2:
|
|
1609
|
+
warnings._warn_ultraplot(f'Illegal {message}:\n{line}"')
|
|
1610
|
+
continue
|
|
1611
|
+
# Detect duplicates
|
|
1612
|
+
key, value = map(str.strip, pair)
|
|
1613
|
+
if key in added:
|
|
1614
|
+
warnings._warn_ultraplot(f"Duplicate rc key {key!r} on {message}.")
|
|
1615
|
+
added.add(key)
|
|
1616
|
+
# Get child dictionaries. Careful to have informative messages
|
|
1617
|
+
with warnings.catch_warnings():
|
|
1618
|
+
warnings.simplefilter("error", warnings.UltraplotWarning)
|
|
1619
|
+
try:
|
|
1620
|
+
key, value = self._validate_key(key, value)
|
|
1621
|
+
value = self._validate_value(key, value)
|
|
1622
|
+
except KeyError:
|
|
1623
|
+
warnings.simplefilter("default", warnings.UltraplotWarning)
|
|
1624
|
+
warnings._warn_ultraplot(f"Invalid rc key {key!r} on {message}.")
|
|
1625
|
+
continue
|
|
1626
|
+
except ValueError as err:
|
|
1627
|
+
warnings.simplefilter("default", warnings.UltraplotWarning)
|
|
1628
|
+
warnings._warn_ultraplot(
|
|
1629
|
+
f"Invalid rc value {value!r} for key {key!r} on {message}: {err}"
|
|
1630
|
+
) # noqa: E501
|
|
1631
|
+
continue
|
|
1632
|
+
except warnings.UltraplotWarning as err:
|
|
1633
|
+
warnings.simplefilter("default", warnings.UltraplotWarning)
|
|
1634
|
+
warnings._warn_ultraplot(
|
|
1635
|
+
f"Outdated rc key {key!r} on {message}: {err}"
|
|
1636
|
+
) # noqa: E501
|
|
1637
|
+
warnings.simplefilter("ignore", warnings.UltraplotWarning)
|
|
1638
|
+
key, value = self._validate_key(key, value)
|
|
1639
|
+
value = self._validate_value(key, value)
|
|
1640
|
+
# Update the settings
|
|
1641
|
+
rcdict[key] = value
|
|
1642
|
+
|
|
1643
|
+
return rcdict
|
|
1644
|
+
|
|
1645
|
+
def load(self, path):
|
|
1646
|
+
"""
|
|
1647
|
+
Load settings from the specified file.
|
|
1648
|
+
|
|
1649
|
+
Parameters
|
|
1650
|
+
----------
|
|
1651
|
+
path : path-like
|
|
1652
|
+
The file path.
|
|
1653
|
+
|
|
1654
|
+
See also
|
|
1655
|
+
--------
|
|
1656
|
+
Configurator.save
|
|
1657
|
+
"""
|
|
1658
|
+
rcdict = self._load_file(path)
|
|
1659
|
+
for key, value in rcdict.items():
|
|
1660
|
+
self.__setitem__(key, value)
|
|
1661
|
+
|
|
1662
|
+
@staticmethod
|
|
1663
|
+
def _save_rst(path):
|
|
1664
|
+
"""
|
|
1665
|
+
Create an RST table file. Used for online docs.
|
|
1666
|
+
"""
|
|
1667
|
+
string = rcsetup._rst_table()
|
|
1668
|
+
with open(path, "w") as fh:
|
|
1669
|
+
fh.write(string)
|
|
1670
|
+
|
|
1671
|
+
@staticmethod
|
|
1672
|
+
def _save_yaml(path, user_dict=None, *, comment=False, description=False):
|
|
1673
|
+
"""
|
|
1674
|
+
Create a YAML file. Used for online docs and default and user-generated
|
|
1675
|
+
ultraplotrc files. Extra settings can be passed with the input dictionary.
|
|
1676
|
+
"""
|
|
1677
|
+
user_table = ()
|
|
1678
|
+
if user_dict: # add always-uncommented user settings
|
|
1679
|
+
user_table = rcsetup._yaml_table(user_dict, comment=False)
|
|
1680
|
+
user_table = ("# Changed settings", user_table, "")
|
|
1681
|
+
ultraplot_dict = (
|
|
1682
|
+
rcsetup._rc_ultraplot_table if description else rcsetup._rc_ultraplot_default
|
|
1683
|
+
) # noqa: E501
|
|
1684
|
+
ultraplot_table = rcsetup._yaml_table(
|
|
1685
|
+
ultraplot_dict, comment=comment, description=description
|
|
1686
|
+
) # noqa: E501
|
|
1687
|
+
ultraplot_table = ("# ultraplot settings", ultraplot_table, "")
|
|
1688
|
+
matplotlib_dict = rcsetup._rc_matplotlib_default
|
|
1689
|
+
matplotlib_table = rcsetup._yaml_table(matplotlib_dict, comment=comment)
|
|
1690
|
+
matplotlib_table = ("# Matplotlib settings", matplotlib_table)
|
|
1691
|
+
parts = (
|
|
1692
|
+
"#--------------------------------------------------------------------",
|
|
1693
|
+
"# Use this file to change the default ultraplot and matplotlib settings.",
|
|
1694
|
+
"# The syntax is identical to matplotlibrc syntax. For details see:",
|
|
1695
|
+
"# https://ultraplot.readthedocs.io/en/latest/configuration.html",
|
|
1696
|
+
"# https://matplotlib.org/stable/tutorials/introductory/customizing.html",
|
|
1697
|
+
"#--------------------------------------------------------------------",
|
|
1698
|
+
*user_table, # empty if nothing passed
|
|
1699
|
+
*ultraplot_table,
|
|
1700
|
+
*matplotlib_table,
|
|
1701
|
+
)
|
|
1702
|
+
with open(path, "w") as fh:
|
|
1703
|
+
fh.write("\n".join(parts))
|
|
1704
|
+
|
|
1705
|
+
def save(self, path=None, user=True, comment=None, backup=True, description=False):
|
|
1706
|
+
"""
|
|
1707
|
+
Save the current settings to a ``ultraplotrc`` file. This writes
|
|
1708
|
+
the default values commented out plus the values that *differ*
|
|
1709
|
+
from the defaults at the top of the file.
|
|
1710
|
+
|
|
1711
|
+
Parameters
|
|
1712
|
+
----------
|
|
1713
|
+
path : path-like, default: 'ultraplotrc'
|
|
1714
|
+
The file name and/or directory. The default file name is ``ultraplotrc``
|
|
1715
|
+
and the default directory is the current directory.
|
|
1716
|
+
user : bool, default: True
|
|
1717
|
+
If ``True`` then settings that have been `~Configurator.changed` from
|
|
1718
|
+
the ultraplot defaults are shown uncommented at the top of the file.
|
|
1719
|
+
backup : bool, default: True
|
|
1720
|
+
Whether to "backup" an existing file by renaming with the suffix ``.bak``
|
|
1721
|
+
or overwrite an existing file.
|
|
1722
|
+
comment : bool, optional
|
|
1723
|
+
Whether to comment out the default settings. If not passed
|
|
1724
|
+
this takes the same value as `user`.
|
|
1725
|
+
description : bool, default: False
|
|
1726
|
+
Whether to include descriptions of each setting (as seen in the
|
|
1727
|
+
:ref:`user guide table <ug_rctable>`) as comments.
|
|
1728
|
+
|
|
1729
|
+
See also
|
|
1730
|
+
--------
|
|
1731
|
+
Configurator.load
|
|
1732
|
+
Configurator.changed
|
|
1733
|
+
"""
|
|
1734
|
+
path = os.path.expanduser(path or ".")
|
|
1735
|
+
if os.path.isdir(path): # includes ''
|
|
1736
|
+
path = os.path.join(path, "ultraplotrc")
|
|
1737
|
+
if os.path.isfile(path) and backup:
|
|
1738
|
+
backup = path + ".bak"
|
|
1739
|
+
os.rename(path, backup)
|
|
1740
|
+
warnings._warn_ultraplot(f"Existing file {path!r} was moved to {backup!r}.")
|
|
1741
|
+
comment = _not_none(comment, user)
|
|
1742
|
+
user_dict = self.changed if user else None
|
|
1743
|
+
self._save_yaml(path, user_dict, comment=comment, description=description)
|
|
1744
|
+
|
|
1745
|
+
@property
|
|
1746
|
+
def _context_mode(self):
|
|
1747
|
+
"""
|
|
1748
|
+
Return the highest (least permissive) context mode.
|
|
1749
|
+
"""
|
|
1750
|
+
return max((context.mode for context in self._context), default=0)
|
|
1751
|
+
|
|
1752
|
+
@property
|
|
1753
|
+
def changed(self):
|
|
1754
|
+
"""
|
|
1755
|
+
A dictionary of settings that have changed from the ultraplot defaults.
|
|
1756
|
+
|
|
1757
|
+
See also
|
|
1758
|
+
--------
|
|
1759
|
+
Configurator.save
|
|
1760
|
+
"""
|
|
1761
|
+
# Carefully detect changed settings
|
|
1762
|
+
rcdict = {}
|
|
1763
|
+
for key, value in self.items():
|
|
1764
|
+
default = rcsetup._get_default_param(key)
|
|
1765
|
+
if (
|
|
1766
|
+
isinstance(value, Real)
|
|
1767
|
+
and isinstance(default, Real)
|
|
1768
|
+
and np.isclose(value, default)
|
|
1769
|
+
): # noqa: E501
|
|
1770
|
+
pass
|
|
1771
|
+
elif value == default:
|
|
1772
|
+
pass
|
|
1773
|
+
else:
|
|
1774
|
+
rcdict[key] = value
|
|
1775
|
+
# Ignore non-style-related settings. See mstyle.STYLE_BLACKLIST
|
|
1776
|
+
# TODO: For now not sure how to detect if prop cycle changed since
|
|
1777
|
+
# we cannot load it from _cmap_database in rcsetup.
|
|
1778
|
+
rcdict.pop("interactive", None) # changed by backend
|
|
1779
|
+
rcdict.pop("axes.prop_cycle", None)
|
|
1780
|
+
return _filter_style_dict(rcdict, warn=False)
|
|
1781
|
+
|
|
1782
|
+
# Renamed methods
|
|
1783
|
+
load_file = warnings._rename_objs("0.8.0", load_file=load)
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
# Initialize locations
|
|
1787
|
+
_init_user_folders()
|
|
1788
|
+
_init_user_file()
|
|
1789
|
+
|
|
1790
|
+
#: A dictionary-like container of matplotlib settings. Assignments are
|
|
1791
|
+
#: validated and restricted to recognized setting names.
|
|
1792
|
+
rc_matplotlib = mpl.rcParams # PEP8 4 lyfe
|
|
1793
|
+
|
|
1794
|
+
#: A dictionary-like container of ultraplot settings. Assignments are
|
|
1795
|
+
#: validated and restricted to recognized setting names.
|
|
1796
|
+
rc_ultraplot = rcsetup._rc_ultraplot_default.copy() # a validated rcParams-style dict
|
|
1797
|
+
|
|
1798
|
+
#: Instance of `Configurator`. This controls both `rc_matplotlib` and `rc_ultraplot`
|
|
1799
|
+
#: settings. See the :ref:`configuration guide <ug_config>` for details.
|
|
1800
|
+
rc = Configurator(skip_cycle=True)
|
|
1801
|
+
|
|
1802
|
+
# Deprecated
|
|
1803
|
+
RcConfigurator = warnings._rename_objs(
|
|
1804
|
+
"0.8.0",
|
|
1805
|
+
RcConfigurator=Configurator,
|
|
1806
|
+
)
|
|
1807
|
+
inline_backend_fmt = warnings._rename_objs(
|
|
1808
|
+
"0.6.0", inline_backend_fmt=config_inline_backend
|
|
1809
|
+
)
|