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/scale.py
ADDED
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Various axis `~matplotlib.scale.ScaleBase` classes.
|
|
4
|
+
"""
|
|
5
|
+
import copy
|
|
6
|
+
|
|
7
|
+
import matplotlib.scale as mscale
|
|
8
|
+
import matplotlib.ticker as mticker
|
|
9
|
+
import matplotlib.transforms as mtransforms
|
|
10
|
+
import numpy as np
|
|
11
|
+
import numpy.ma as ma
|
|
12
|
+
|
|
13
|
+
from . import ticker as pticker
|
|
14
|
+
from .internals import ic # noqa: F401
|
|
15
|
+
from .internals import _not_none, _version_mpl, warnings
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"CutoffScale",
|
|
19
|
+
"ExpScale",
|
|
20
|
+
"FuncScale",
|
|
21
|
+
"InverseScale",
|
|
22
|
+
"LinearScale",
|
|
23
|
+
"LogitScale",
|
|
24
|
+
"LogScale",
|
|
25
|
+
"MercatorLatitudeScale",
|
|
26
|
+
"PowerScale",
|
|
27
|
+
"SineLatitudeScale",
|
|
28
|
+
"SymmetricalLogScale",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_logscale_args(*keys, **kwargs):
|
|
33
|
+
"""
|
|
34
|
+
Parse arguments for `LogScale` and `SymmetricalLogScale` that
|
|
35
|
+
inexplicably require ``x`` and ``y`` suffixes by default. Also
|
|
36
|
+
change the default `linthresh` to ``1``.
|
|
37
|
+
"""
|
|
38
|
+
# NOTE: Scale classes ignore unused arguments with warnings, but matplotlib 3.3
|
|
39
|
+
# version changes the keyword args. Since we can't do a try except clause, only
|
|
40
|
+
# way to avoid warnings with 3.3 upgrade is to test version string.
|
|
41
|
+
kwsuffix = "" if _version_mpl >= "3.3" else "x"
|
|
42
|
+
for key in keys:
|
|
43
|
+
# Remove duplicates
|
|
44
|
+
opts = {
|
|
45
|
+
key: kwargs.pop(key, None),
|
|
46
|
+
key + "x": kwargs.pop(key + "x", None),
|
|
47
|
+
key + "y": kwargs.pop(key + "y", None),
|
|
48
|
+
}
|
|
49
|
+
value = _not_none(**opts) # issues warning if multiple values passed
|
|
50
|
+
|
|
51
|
+
# Apply defaults and adjust
|
|
52
|
+
# NOTE: If linthresh is *exactly* on a power of the base, can end
|
|
53
|
+
# up with additional log-locator step inside the threshold, e.g. major
|
|
54
|
+
# ticks on -10, -1, -0.1, 0.1, 1, 10 for linthresh of 1. Adding slight
|
|
55
|
+
# offset to *desired* linthresh prevents this.
|
|
56
|
+
if key == "subs":
|
|
57
|
+
if value is None:
|
|
58
|
+
value = np.arange(1, 10)
|
|
59
|
+
if key == "linthresh":
|
|
60
|
+
if value is None:
|
|
61
|
+
value = 1
|
|
62
|
+
power = np.log10(value)
|
|
63
|
+
if power % 1 == 0: # exact power of 10
|
|
64
|
+
value = value + 10 ** (power - 10)
|
|
65
|
+
if value is not None: # dummy axis_name is 'x'
|
|
66
|
+
kwargs[key + kwsuffix] = value
|
|
67
|
+
|
|
68
|
+
return kwargs
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class _Scale(object):
|
|
72
|
+
"""
|
|
73
|
+
Mix-in class that standardizes the behavior of
|
|
74
|
+
`~matplotlib.scale.ScaleBase.set_default_locators_and_formatters`
|
|
75
|
+
and `~matplotlib.scale.ScaleBase.get_transform`. Also overrides
|
|
76
|
+
`__init__` so you no longer have to instantiate scales with an
|
|
77
|
+
`~matplotlib.axis.Axis` instance.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, *args, **kwargs):
|
|
81
|
+
# Pass a dummy axis to the superclass
|
|
82
|
+
axis = type("Axis", (object,), {"axis_name": "x"})()
|
|
83
|
+
super().__init__(axis, *args, **kwargs)
|
|
84
|
+
self._default_major_locator = mticker.AutoLocator()
|
|
85
|
+
self._default_minor_locator = mticker.AutoMinorLocator()
|
|
86
|
+
self._default_major_formatter = pticker.AutoFormatter()
|
|
87
|
+
self._default_minor_formatter = mticker.NullFormatter()
|
|
88
|
+
|
|
89
|
+
def set_default_locators_and_formatters(self, axis, only_if_default=False):
|
|
90
|
+
"""
|
|
91
|
+
Apply all locators and formatters defined as attributes on
|
|
92
|
+
initialization and define defaults for all scales.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
axis : `~matplotlib.axis.Axis`
|
|
97
|
+
The axis.
|
|
98
|
+
only_if_default : bool, optional
|
|
99
|
+
Whether to refrain from updating the locators and formatters if the
|
|
100
|
+
axis is currently using non-default versions. Useful if we want to
|
|
101
|
+
avoid overwriting user customization when the scale is changed.
|
|
102
|
+
"""
|
|
103
|
+
# TODO: Always use only_if_default=True? Used only for dual axes right now
|
|
104
|
+
# NOTE: We set isDefault_minloc to True when simply toggling minor ticks
|
|
105
|
+
# on and off with CartesianAxes format command.
|
|
106
|
+
from .config import rc
|
|
107
|
+
|
|
108
|
+
if not only_if_default or axis.isDefault_majloc:
|
|
109
|
+
locator = copy.copy(self._default_major_locator)
|
|
110
|
+
axis.set_major_locator(locator)
|
|
111
|
+
axis.isDefault_majloc = True
|
|
112
|
+
if not only_if_default or axis.isDefault_minloc:
|
|
113
|
+
x = axis.axis_name if axis.axis_name in "xy" else "x"
|
|
114
|
+
if rc[x + "tick.minor.visible"]:
|
|
115
|
+
locator = copy.copy(self._default_minor_locator)
|
|
116
|
+
else:
|
|
117
|
+
locator = mticker.NullLocator()
|
|
118
|
+
axis.set_minor_locator(locator)
|
|
119
|
+
axis.isDefault_minloc = True
|
|
120
|
+
if not only_if_default or axis.isDefault_majfmt:
|
|
121
|
+
formatter = copy.copy(self._default_major_formatter)
|
|
122
|
+
axis.set_major_formatter(formatter)
|
|
123
|
+
axis.isDefault_majfmt = True
|
|
124
|
+
if not only_if_default or axis.isDefault_minfmt:
|
|
125
|
+
formatter = copy.copy(self._default_minor_formatter)
|
|
126
|
+
axis.set_minor_formatter(formatter)
|
|
127
|
+
axis.isDefault_minfmt = True
|
|
128
|
+
|
|
129
|
+
def get_transform(self):
|
|
130
|
+
"""
|
|
131
|
+
Return the scale transform.
|
|
132
|
+
"""
|
|
133
|
+
return self._transform
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class LinearScale(_Scale, mscale.LinearScale):
|
|
137
|
+
"""
|
|
138
|
+
As with `~matplotlib.scale.LinearScale` but with
|
|
139
|
+
`~ultraplot.ticker.AutoFormatter` as the default major formatter.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
#: The registered scale name
|
|
143
|
+
name = "linear"
|
|
144
|
+
|
|
145
|
+
def __init__(self, **kwargs):
|
|
146
|
+
"""
|
|
147
|
+
See also
|
|
148
|
+
--------
|
|
149
|
+
ultraplot.constructor.Scale
|
|
150
|
+
"""
|
|
151
|
+
super().__init__(**kwargs)
|
|
152
|
+
self._transform = mtransforms.IdentityTransform()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class LogitScale(_Scale, mscale.LogitScale):
|
|
156
|
+
"""
|
|
157
|
+
As with `~matplotlib.scale.LogitScale` but with `~ultraplot.ticker.AutoFormatter`
|
|
158
|
+
as the default major formatter.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
#: The registered scale name
|
|
162
|
+
name = "logit"
|
|
163
|
+
|
|
164
|
+
def __init__(self, **kwargs):
|
|
165
|
+
"""
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
nonpos : {'mask', 'clip'}
|
|
169
|
+
Values outside of (0, 1) can be masked as invalid, or clipped to a
|
|
170
|
+
number very close to 0 or 1.
|
|
171
|
+
|
|
172
|
+
See also
|
|
173
|
+
--------
|
|
174
|
+
ultraplot.constructor.Scale
|
|
175
|
+
"""
|
|
176
|
+
super().__init__(**kwargs)
|
|
177
|
+
# self._default_major_formatter = mticker.LogitFormatter()
|
|
178
|
+
self._default_major_locator = mticker.LogitLocator()
|
|
179
|
+
self._default_minor_locator = mticker.LogitLocator(minor=True)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class LogScale(_Scale, mscale.LogScale):
|
|
183
|
+
"""
|
|
184
|
+
As with `~matplotlib.scale.LogScale` but with `~ultraplot.ticker.AutoFormatter`
|
|
185
|
+
as the default major formatter. ``x`` and ``y`` versions of each keyword
|
|
186
|
+
argument are no longer required.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
#: The registered scale name
|
|
190
|
+
name = "log"
|
|
191
|
+
|
|
192
|
+
def __init__(self, **kwargs):
|
|
193
|
+
"""
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
base : float, default: 10
|
|
197
|
+
The base of the logarithm.
|
|
198
|
+
nonpos : {'mask', 'clip'}, optional
|
|
199
|
+
Non-positive values in *x* or *y* can be masked as
|
|
200
|
+
invalid, or clipped to a very small positive number.
|
|
201
|
+
subs : sequence of int, default: ``[1 2 3 4 5 6 7 8 9]``
|
|
202
|
+
Default *minor* tick locations are on these multiples of each power
|
|
203
|
+
of the base. For example, ``subs=(1, 2, 5)`` draws ticks on 1, 2,
|
|
204
|
+
5, 10, 20, 50, 100, 200, 500, etc.
|
|
205
|
+
basex, basey, nonposx, nonposy, subsx, subsy
|
|
206
|
+
Aliases for the above keywords. These used to be conditional
|
|
207
|
+
on the *name* of the axis.
|
|
208
|
+
|
|
209
|
+
See also
|
|
210
|
+
--------
|
|
211
|
+
ultraplot.constructor.Scale
|
|
212
|
+
"""
|
|
213
|
+
keys = ("base", "nonpos", "subs")
|
|
214
|
+
super().__init__(**_parse_logscale_args(*keys, **kwargs))
|
|
215
|
+
self._default_major_locator = mticker.LogLocator(self.base)
|
|
216
|
+
self._default_minor_locator = mticker.LogLocator(self.base, self.subs)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class SymmetricalLogScale(_Scale, mscale.SymmetricalLogScale):
|
|
220
|
+
"""
|
|
221
|
+
As with `~matplotlib.scale.SymmetricalLogScale` but with
|
|
222
|
+
`~ultraplot.ticker.AutoFormatter` as the default major formatter.
|
|
223
|
+
``x`` and ``y`` versions of each keyword argument are no longer
|
|
224
|
+
required.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
#: The registered scale name
|
|
228
|
+
name = "symlog"
|
|
229
|
+
|
|
230
|
+
def __init__(self, **kwargs):
|
|
231
|
+
"""
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
base : float, default: 10
|
|
235
|
+
The base of the logarithm.
|
|
236
|
+
linthresh : float, default: 1
|
|
237
|
+
Defines the range ``(-linthresh, linthresh)``, within which the plot
|
|
238
|
+
is linear. This avoids having the plot go to infinity around zero.
|
|
239
|
+
linscale : float, default: 1
|
|
240
|
+
This allows the linear range ``(-linthresh, linthresh)`` to be
|
|
241
|
+
stretched relative to the logarithmic range. Its value is the
|
|
242
|
+
number of decades to use for each half of the linear range. For
|
|
243
|
+
example, when `linscale` is ``1`` (the default), the space used
|
|
244
|
+
for the positive and negative halves of the linear range will be
|
|
245
|
+
equal to one decade in the logarithmic range.
|
|
246
|
+
subs : sequence of int, default: ``[1 2 3 4 5 6 7 8 9]``
|
|
247
|
+
Default *minor* tick locations are on these multiples of each power
|
|
248
|
+
of the base. For example, ``subs=(1, 2, 5)`` draws ticks on 1, 2,
|
|
249
|
+
5, 10, 20, 50, 100, 200, 500, etc.
|
|
250
|
+
basex, basey, linthreshx, linthreshy, linscalex, linscaley, subsx, subsy
|
|
251
|
+
Aliases for the above keywords. These keywords used to be
|
|
252
|
+
conditional on the name of the axis.
|
|
253
|
+
|
|
254
|
+
See also
|
|
255
|
+
--------
|
|
256
|
+
ultraplot.constructor.Scale
|
|
257
|
+
"""
|
|
258
|
+
keys = ("base", "linthresh", "linscale", "subs")
|
|
259
|
+
super().__init__(**_parse_logscale_args(*keys, **kwargs))
|
|
260
|
+
transform = self.get_transform()
|
|
261
|
+
self._default_major_locator = mticker.SymmetricalLogLocator(transform)
|
|
262
|
+
self._default_minor_locator = mticker.SymmetricalLogLocator(
|
|
263
|
+
transform, self.subs
|
|
264
|
+
) # noqa: E501
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class FuncScale(_Scale, mscale.ScaleBase):
|
|
268
|
+
"""
|
|
269
|
+
Axis scale composed of arbitrary forward and inverse transformations.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
#: The registered scale name
|
|
273
|
+
name = "function"
|
|
274
|
+
|
|
275
|
+
def __init__(self, transform=None, invert=False, parent_scale=None, **kwargs):
|
|
276
|
+
"""
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
transform : callable, 2-tuple of callable, or scale-spec
|
|
280
|
+
The transform used to translate units from the parent axis to
|
|
281
|
+
the secondary axis. Input can be as follows:
|
|
282
|
+
|
|
283
|
+
* A single `linear <https://en.wikipedia.org/wiki/Linear_function>`__ or
|
|
284
|
+
`involutory <https://en.wikipedia.org/wiki/Involution_(mathematics)>`__
|
|
285
|
+
function that accepts a number and returns some transformation of
|
|
286
|
+
that number. For example, to convert Kelvin to Celsius, use
|
|
287
|
+
``ax.dualx(lambda x: x - 273.15)``. To convert kilometers to
|
|
288
|
+
meters, use ``ax.dualx(lambda x: x * 1e3)``.
|
|
289
|
+
* A 2-tuple of arbitrary functions. This should only be used if your
|
|
290
|
+
functions are non-linear and non-involutory. The second function must
|
|
291
|
+
be the inverse of the first. For example, to apply the square, use
|
|
292
|
+
``ax.dualx((lambda x: x ** 2, lambda x: x ** 0.5))``.
|
|
293
|
+
* A scale specification passed to the `~ultraplot.constructor.Scale`
|
|
294
|
+
constructor function. The transform and default locators and formatters
|
|
295
|
+
are borrowed from the resulting `~matplotlib.scale.ScaleBase` instance.
|
|
296
|
+
For example, to apply the inverse, use ``ax.dualx('inverse')``.
|
|
297
|
+
To apply the base-10 exponential, use ``ax.dualx(('exp', 10))``.
|
|
298
|
+
|
|
299
|
+
invert : bool, optional
|
|
300
|
+
If ``True``, the forward and inverse functions are *swapped*.
|
|
301
|
+
Used when drawing dual axes.
|
|
302
|
+
parent_scale : `~matplotlib.scale.ScaleBase`, default: `LinearScale`
|
|
303
|
+
The axis scale of the "parent" axis. Its forward transform
|
|
304
|
+
is applied to the `FuncTransform`.
|
|
305
|
+
major_locator, minor_locator : locator-spec, optional
|
|
306
|
+
The default major and minor locator. Passed to the
|
|
307
|
+
`~ultraplot.constructor.Locator` constructor function. By default, these are
|
|
308
|
+
the same as the default locators on the input transform. If the input
|
|
309
|
+
transform was not an axis scale, these are borrowed from `parent_scale`.
|
|
310
|
+
major_formatter, minor_formatter : formatter-spec, optional
|
|
311
|
+
The default major and minor formatter. Passed to the
|
|
312
|
+
`~ultraplot.constructor.Formatter` constructor function. By default, these are
|
|
313
|
+
the same as the default formatters on the input transform. If the input
|
|
314
|
+
transform was not an axis scale, these are borrowed from `parent_scale`.
|
|
315
|
+
|
|
316
|
+
See also
|
|
317
|
+
--------
|
|
318
|
+
ultraplot.constructor.Scale
|
|
319
|
+
ultraplot.axes.CartesianAxes.dualx
|
|
320
|
+
ultraplot.axes.CartesianAxes.dualy
|
|
321
|
+
"""
|
|
322
|
+
# Parse input args
|
|
323
|
+
# NOTE: Permit *arbitrary* parent axis scales and infer default locators and
|
|
324
|
+
# formatters from the input scale (if it was passed) or the parent scale. Use
|
|
325
|
+
# case for latter is e.g. logarithmic scale with linear transformation.
|
|
326
|
+
if "functions" in kwargs: # matplotlib compatibility (critical for >= 3.5)
|
|
327
|
+
functions = kwargs.pop("functions", None)
|
|
328
|
+
if transform is None:
|
|
329
|
+
transform = functions
|
|
330
|
+
else:
|
|
331
|
+
warnings._warn_ultraplot("Ignoring keyword argument 'functions'.")
|
|
332
|
+
from .constructor import Formatter, Locator, Scale
|
|
333
|
+
|
|
334
|
+
super().__init__()
|
|
335
|
+
if callable(transform):
|
|
336
|
+
forward, inverse, inherit_scale = transform, transform, None
|
|
337
|
+
elif (
|
|
338
|
+
np.iterable(transform)
|
|
339
|
+
and len(transform) == 2
|
|
340
|
+
and all(map(callable, transform))
|
|
341
|
+
): # noqa: E501
|
|
342
|
+
forward, inverse, inherit_scale = *transform, None
|
|
343
|
+
else:
|
|
344
|
+
try:
|
|
345
|
+
inherit_scale = Scale(transform)
|
|
346
|
+
except ValueError:
|
|
347
|
+
raise ValueError(
|
|
348
|
+
"Expected a function, 2-tuple of forward and inverse functions, "
|
|
349
|
+
f"or an axis scale specification. Got {transform!r}."
|
|
350
|
+
)
|
|
351
|
+
transform = inherit_scale.get_transform()
|
|
352
|
+
forward, inverse = transform.transform, transform.inverted().transform
|
|
353
|
+
|
|
354
|
+
# Create the transform
|
|
355
|
+
# NOTE: Linear scale is always identity transform (no-op).
|
|
356
|
+
# NOTE: Must transform parent scale cutoff arguments as well. Use inverse
|
|
357
|
+
# function because we are converting from some *other* axis to this one.
|
|
358
|
+
if invert: # used for dualx and dualy
|
|
359
|
+
forward, inverse = inverse, forward
|
|
360
|
+
parent_scale = _not_none(parent_scale, LinearScale())
|
|
361
|
+
if not isinstance(parent_scale, mscale.ScaleBase):
|
|
362
|
+
raise ValueError(f"Parent scale must be ScaleBase. Got {parent_scale!r}.")
|
|
363
|
+
if isinstance(parent_scale, CutoffScale):
|
|
364
|
+
args = list(parent_scale.args) # mutable copy
|
|
365
|
+
args[::2] = (inverse(arg) for arg in args[::2]) # transform cutoffs
|
|
366
|
+
parent_scale = CutoffScale(*args)
|
|
367
|
+
if isinstance(parent_scale, mscale.SymmetricalLogScale):
|
|
368
|
+
keys = ("base", "linthresh", "linscale", "subs")
|
|
369
|
+
kwsym = {key: getattr(parent_scale, key) for key in keys}
|
|
370
|
+
kwsym["linthresh"] = inverse(kwsym["linthresh"])
|
|
371
|
+
parent_scale = SymmetricalLogScale(**kwsym)
|
|
372
|
+
self.functions = (forward, inverse)
|
|
373
|
+
self._transform = parent_scale.get_transform() + FuncTransform(forward, inverse)
|
|
374
|
+
|
|
375
|
+
# Apply default locators and formatters
|
|
376
|
+
# NOTE: We pass these through contructor functions
|
|
377
|
+
scale = inherit_scale or parent_scale
|
|
378
|
+
for which in ("major", "minor"):
|
|
379
|
+
for type_, parser in (("locator", Locator), ("formatter", Formatter)):
|
|
380
|
+
key = which + "_" + type_
|
|
381
|
+
attr = "_default_" + key
|
|
382
|
+
ticker = kwargs.pop(key, None)
|
|
383
|
+
if ticker is None:
|
|
384
|
+
ticker = getattr(scale, attr, None)
|
|
385
|
+
if ticker is None: # e.g. someone used a matplotlib scale
|
|
386
|
+
continue # revert to defaults
|
|
387
|
+
ticker = parser(ticker)
|
|
388
|
+
setattr(self, attr, copy.copy(ticker))
|
|
389
|
+
if kwargs:
|
|
390
|
+
raise TypeError(f"FuncScale got unexpected arguments: {kwargs}")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class FuncTransform(mtransforms.Transform):
|
|
394
|
+
input_dims = 1
|
|
395
|
+
output_dims = 1
|
|
396
|
+
is_separable = True
|
|
397
|
+
has_inverse = True
|
|
398
|
+
|
|
399
|
+
def __init__(self, forward, inverse):
|
|
400
|
+
super().__init__()
|
|
401
|
+
if callable(forward) and callable(inverse):
|
|
402
|
+
self._forward = forward
|
|
403
|
+
self._inverse = inverse
|
|
404
|
+
else:
|
|
405
|
+
raise ValueError("arguments to FuncTransform must be functions")
|
|
406
|
+
|
|
407
|
+
def inverted(self):
|
|
408
|
+
return FuncTransform(self._inverse, self._forward)
|
|
409
|
+
|
|
410
|
+
def transform_non_affine(self, values):
|
|
411
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
412
|
+
return self._forward(values)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class PowerScale(_Scale, mscale.ScaleBase):
|
|
416
|
+
r"""
|
|
417
|
+
"Power scale" that performs the transformation
|
|
418
|
+
|
|
419
|
+
.. math::
|
|
420
|
+
|
|
421
|
+
x^{c}
|
|
422
|
+
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
#: The registered scale name
|
|
426
|
+
name = "power"
|
|
427
|
+
|
|
428
|
+
def __init__(self, power=1, inverse=False):
|
|
429
|
+
"""
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
power : float, optional
|
|
433
|
+
The power :math:`c` to which :math:`x` is raised.
|
|
434
|
+
inverse : bool, optional
|
|
435
|
+
If ``True`` this performs the inverse operation :math:`x^{1/c}`.
|
|
436
|
+
"""
|
|
437
|
+
super().__init__()
|
|
438
|
+
if not inverse:
|
|
439
|
+
self._transform = PowerTransform(power)
|
|
440
|
+
else:
|
|
441
|
+
self._transform = InvertedPowerTransform(power)
|
|
442
|
+
|
|
443
|
+
def limit_range_for_scale(self, vmin, vmax, minpos):
|
|
444
|
+
"""
|
|
445
|
+
Return the range *vmin* and *vmax* limited to positive numbers.
|
|
446
|
+
"""
|
|
447
|
+
if not np.isfinite(minpos):
|
|
448
|
+
minpos = 1e-300
|
|
449
|
+
return (
|
|
450
|
+
minpos if vmin <= 0 else vmin,
|
|
451
|
+
minpos if vmax <= 0 else vmax,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class PowerTransform(mtransforms.Transform):
|
|
456
|
+
input_dims = 1
|
|
457
|
+
output_dims = 1
|
|
458
|
+
has_inverse = True
|
|
459
|
+
is_separable = True
|
|
460
|
+
|
|
461
|
+
def __init__(self, power):
|
|
462
|
+
super().__init__()
|
|
463
|
+
self._power = power
|
|
464
|
+
|
|
465
|
+
def inverted(self):
|
|
466
|
+
return InvertedPowerTransform(self._power)
|
|
467
|
+
|
|
468
|
+
def transform_non_affine(self, a):
|
|
469
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
470
|
+
return np.power(a, self._power)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class InvertedPowerTransform(mtransforms.Transform):
|
|
474
|
+
input_dims = 1
|
|
475
|
+
output_dims = 1
|
|
476
|
+
has_inverse = True
|
|
477
|
+
is_separable = True
|
|
478
|
+
|
|
479
|
+
def __init__(self, power):
|
|
480
|
+
super().__init__()
|
|
481
|
+
self._power = power
|
|
482
|
+
|
|
483
|
+
def inverted(self):
|
|
484
|
+
return PowerTransform(self._power)
|
|
485
|
+
|
|
486
|
+
def transform_non_affine(self, a):
|
|
487
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
488
|
+
return np.power(a, 1 / self._power)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class ExpScale(_Scale, mscale.ScaleBase):
|
|
492
|
+
r"""
|
|
493
|
+
"Exponential scale" that performs either of two transformations. When
|
|
494
|
+
`inverse` is ``False`` (the default), performs the transformation
|
|
495
|
+
|
|
496
|
+
.. math::
|
|
497
|
+
|
|
498
|
+
Ca^{bx}
|
|
499
|
+
|
|
500
|
+
where the constants :math:`a`, :math:`b`, and :math:`C` are set by the
|
|
501
|
+
input (see below). When `inverse` is ``True``, this performs the inverse
|
|
502
|
+
transformation
|
|
503
|
+
|
|
504
|
+
.. math::
|
|
505
|
+
|
|
506
|
+
(\log_a(x) - \log_a(C))/b
|
|
507
|
+
|
|
508
|
+
which in appearance is equivalent to `LogScale` since it is just a linear
|
|
509
|
+
transformation of the logarithm.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
#: The registered scale name
|
|
513
|
+
name = "exp"
|
|
514
|
+
|
|
515
|
+
def __init__(self, a=np.e, b=1, c=1, inverse=False):
|
|
516
|
+
"""
|
|
517
|
+
Parameters
|
|
518
|
+
----------
|
|
519
|
+
a : float, optional
|
|
520
|
+
The base of the exponential, i.e. the :math:`a` in :math:`Ca^{bx}`.
|
|
521
|
+
b : float, optional
|
|
522
|
+
The scale for the exponent, i.e. the :math:`b` in :math:`Ca^{bx}`.
|
|
523
|
+
c : float, optional
|
|
524
|
+
The coefficient of the exponential, i.e. the :math:`C` in :math:`Ca^{bx}`.
|
|
525
|
+
inverse : bool, optional
|
|
526
|
+
If ``True``, the "forward" direction performs the inverse operation.
|
|
527
|
+
|
|
528
|
+
See also
|
|
529
|
+
--------
|
|
530
|
+
ultraplot.constructor.Scale
|
|
531
|
+
"""
|
|
532
|
+
super().__init__()
|
|
533
|
+
if not inverse:
|
|
534
|
+
self._transform = ExpTransform(a, b, c)
|
|
535
|
+
else:
|
|
536
|
+
self._transform = InvertedExpTransform(a, b, c)
|
|
537
|
+
|
|
538
|
+
def limit_range_for_scale(self, vmin, vmax, minpos):
|
|
539
|
+
"""
|
|
540
|
+
Return the range *vmin* and *vmax* limited to positive numbers.
|
|
541
|
+
"""
|
|
542
|
+
if not np.isfinite(minpos):
|
|
543
|
+
minpos = 1e-300
|
|
544
|
+
return (
|
|
545
|
+
minpos if vmin <= 0 else vmin,
|
|
546
|
+
minpos if vmax <= 0 else vmax,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class ExpTransform(mtransforms.Transform):
|
|
551
|
+
input_dims = 1
|
|
552
|
+
output_dims = 1
|
|
553
|
+
has_inverse = True
|
|
554
|
+
is_separable = True
|
|
555
|
+
|
|
556
|
+
def __init__(self, a, b, c):
|
|
557
|
+
super().__init__()
|
|
558
|
+
self._a = a
|
|
559
|
+
self._b = b
|
|
560
|
+
self._c = c
|
|
561
|
+
|
|
562
|
+
def inverted(self):
|
|
563
|
+
return InvertedExpTransform(self._a, self._b, self._c)
|
|
564
|
+
|
|
565
|
+
def transform_non_affine(self, a):
|
|
566
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
567
|
+
return self._c * np.power(self._a, self._b * np.array(a))
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class InvertedExpTransform(mtransforms.Transform):
|
|
571
|
+
input_dims = 1
|
|
572
|
+
output_dims = 1
|
|
573
|
+
has_inverse = True
|
|
574
|
+
is_separable = True
|
|
575
|
+
|
|
576
|
+
def __init__(self, a, b, c):
|
|
577
|
+
super().__init__()
|
|
578
|
+
self._a = a
|
|
579
|
+
self._b = b
|
|
580
|
+
self._c = c
|
|
581
|
+
|
|
582
|
+
def inverted(self):
|
|
583
|
+
return ExpTransform(self._a, self._b, self._c)
|
|
584
|
+
|
|
585
|
+
def transform_non_affine(self, a):
|
|
586
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
587
|
+
return np.log(a / self._c) / (self._b * np.log(self._a))
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class MercatorLatitudeScale(_Scale, mscale.ScaleBase):
|
|
591
|
+
"""
|
|
592
|
+
Axis scale that is linear in the `Mercator projection latitude \
|
|
593
|
+
<http://en.wikipedia.org/wiki/Mercator_projection>`__. Adapted from `this example \
|
|
594
|
+
<https://matplotlib.org/2.0.2/examples/api/custom_scale_example.html>`__.
|
|
595
|
+
The scale function is as follows:
|
|
596
|
+
|
|
597
|
+
.. math::
|
|
598
|
+
|
|
599
|
+
y = \\ln(\\tan(\\pi x \\,/\\, 180) + \\sec(\\pi x \\,/\\, 180))
|
|
600
|
+
|
|
601
|
+
The inverse scale function is as follows:
|
|
602
|
+
|
|
603
|
+
.. math::
|
|
604
|
+
|
|
605
|
+
x = 180\\,\\arctan(\\sinh(y)) \\,/\\, \\pi
|
|
606
|
+
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
#: The registered scale name
|
|
610
|
+
name = "mercator"
|
|
611
|
+
|
|
612
|
+
def __init__(self, thresh=85.0):
|
|
613
|
+
"""
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
thresh : float, optional
|
|
617
|
+
Threshold between 0 and 90, used to constrain axis limits
|
|
618
|
+
between ``-thresh`` and ``+thresh``.
|
|
619
|
+
|
|
620
|
+
See also
|
|
621
|
+
--------
|
|
622
|
+
ultraplot.constructor.Scale
|
|
623
|
+
"""
|
|
624
|
+
super().__init__()
|
|
625
|
+
if thresh >= 90:
|
|
626
|
+
raise ValueError("Mercator scale 'thresh' must be <= 90.")
|
|
627
|
+
self._thresh = thresh
|
|
628
|
+
self._transform = MercatorLatitudeTransform(thresh)
|
|
629
|
+
self._default_major_formatter = pticker.AutoFormatter(suffix="\N{DEGREE SIGN}")
|
|
630
|
+
|
|
631
|
+
def limit_range_for_scale(self, vmin, vmax, minpos): # noqa: U100
|
|
632
|
+
"""
|
|
633
|
+
Return the range *vmin* and *vmax* limited to within +/-90 degrees
|
|
634
|
+
(exclusive).
|
|
635
|
+
"""
|
|
636
|
+
return max(vmin, -self._thresh), min(vmax, self._thresh)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
class MercatorLatitudeTransform(mtransforms.Transform):
|
|
640
|
+
input_dims = 1
|
|
641
|
+
output_dims = 1
|
|
642
|
+
is_separable = True
|
|
643
|
+
has_inverse = True
|
|
644
|
+
|
|
645
|
+
def __init__(self, thresh):
|
|
646
|
+
super().__init__()
|
|
647
|
+
self._thresh = thresh
|
|
648
|
+
|
|
649
|
+
def inverted(self):
|
|
650
|
+
return InvertedMercatorLatitudeTransform(self._thresh)
|
|
651
|
+
|
|
652
|
+
def transform_non_affine(self, a):
|
|
653
|
+
# NOTE: Critical to truncate valid range inside transform *and*
|
|
654
|
+
# in limit_range_for_scale or get weird duplicate tick labels. This
|
|
655
|
+
# is not necessary for positive-only scales because it is harder to
|
|
656
|
+
# run up right against the scale boundaries.
|
|
657
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
658
|
+
m = ma.masked_where((a <= -90) | (a >= 90), a)
|
|
659
|
+
if m.mask.any():
|
|
660
|
+
m = np.deg2rad(m)
|
|
661
|
+
return ma.log(ma.abs(ma.tan(m) + 1 / ma.cos(m)))
|
|
662
|
+
else:
|
|
663
|
+
a = np.deg2rad(a)
|
|
664
|
+
return np.log(np.abs(np.tan(a) + 1 / np.cos(a)))
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
class InvertedMercatorLatitudeTransform(mtransforms.Transform):
|
|
668
|
+
input_dims = 1
|
|
669
|
+
output_dims = 1
|
|
670
|
+
is_separable = True
|
|
671
|
+
has_inverse = True
|
|
672
|
+
|
|
673
|
+
def __init__(self, thresh):
|
|
674
|
+
super().__init__()
|
|
675
|
+
self._thresh = thresh
|
|
676
|
+
|
|
677
|
+
def inverted(self):
|
|
678
|
+
return MercatorLatitudeTransform(self._thresh)
|
|
679
|
+
|
|
680
|
+
def transform_non_affine(self, a):
|
|
681
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
682
|
+
return np.rad2deg(np.arctan2(1, np.sinh(a)))
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class SineLatitudeScale(_Scale, mscale.ScaleBase):
|
|
686
|
+
r"""
|
|
687
|
+
Axis scale that is linear in the sine transformation of *x*. The axis
|
|
688
|
+
limits are constrained to fall between ``-90`` and ``+90`` degrees.
|
|
689
|
+
The scale function is as follows:
|
|
690
|
+
|
|
691
|
+
.. math::
|
|
692
|
+
|
|
693
|
+
y = \sin(\pi x/180)
|
|
694
|
+
|
|
695
|
+
The inverse scale function is as follows:
|
|
696
|
+
|
|
697
|
+
.. math::
|
|
698
|
+
|
|
699
|
+
x = 180\arcsin(y)/\pi
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
#: The registered scale name
|
|
703
|
+
name = "sine"
|
|
704
|
+
|
|
705
|
+
def __init__(self):
|
|
706
|
+
"""
|
|
707
|
+
See also
|
|
708
|
+
--------
|
|
709
|
+
ultraplot.constructor.Scale
|
|
710
|
+
"""
|
|
711
|
+
super().__init__()
|
|
712
|
+
self._transform = SineLatitudeTransform()
|
|
713
|
+
self._default_major_formatter = pticker.AutoFormatter(suffix="\N{DEGREE SIGN}")
|
|
714
|
+
|
|
715
|
+
def limit_range_for_scale(self, vmin, vmax, minpos): # noqa: U100
|
|
716
|
+
"""
|
|
717
|
+
Return the range *vmin* and *vmax* limited to within +/-90 degrees
|
|
718
|
+
(inclusive).
|
|
719
|
+
"""
|
|
720
|
+
return max(vmin, -90), min(vmax, 90)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
class SineLatitudeTransform(mtransforms.Transform):
|
|
724
|
+
input_dims = 1
|
|
725
|
+
output_dims = 1
|
|
726
|
+
is_separable = True
|
|
727
|
+
has_inverse = True
|
|
728
|
+
|
|
729
|
+
def __init__(self):
|
|
730
|
+
super().__init__()
|
|
731
|
+
|
|
732
|
+
def inverted(self):
|
|
733
|
+
return InvertedSineLatitudeTransform()
|
|
734
|
+
|
|
735
|
+
def transform_non_affine(self, a):
|
|
736
|
+
# NOTE: Critical to truncate valid range inside transform *and*
|
|
737
|
+
# in limit_range_for_scale or get weird duplicate tick labels. This
|
|
738
|
+
# is not necessary for positive-only scales because it is harder to
|
|
739
|
+
# run up right against the scale boundaries.
|
|
740
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
741
|
+
m = ma.masked_where((a < -90) | (a > 90), a)
|
|
742
|
+
if m.mask.any():
|
|
743
|
+
return ma.sin(np.deg2rad(m))
|
|
744
|
+
else:
|
|
745
|
+
return np.sin(np.deg2rad(a))
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
class InvertedSineLatitudeTransform(mtransforms.Transform):
|
|
749
|
+
input_dims = 1
|
|
750
|
+
output_dims = 1
|
|
751
|
+
is_separable = True
|
|
752
|
+
has_inverse = True
|
|
753
|
+
|
|
754
|
+
def __init__(self):
|
|
755
|
+
super().__init__()
|
|
756
|
+
|
|
757
|
+
def inverted(self):
|
|
758
|
+
return SineLatitudeTransform()
|
|
759
|
+
|
|
760
|
+
def transform_non_affine(self, a):
|
|
761
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
762
|
+
return np.rad2deg(np.arcsin(a))
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
class CutoffScale(_Scale, mscale.ScaleBase):
|
|
766
|
+
"""
|
|
767
|
+
Axis scale composed of arbitrary piecewise linear transformations.
|
|
768
|
+
The axis can undergo discrete jumps, "accelerations", or "decelerations"
|
|
769
|
+
between successive thresholds.
|
|
770
|
+
"""
|
|
771
|
+
|
|
772
|
+
#: The registered scale name
|
|
773
|
+
name = "cutoff"
|
|
774
|
+
|
|
775
|
+
def __init__(self, *args):
|
|
776
|
+
"""
|
|
777
|
+
Parameters
|
|
778
|
+
----------
|
|
779
|
+
*args : thresh_1, scale_1, ..., thresh_N, [scale_N], optional
|
|
780
|
+
Sequence of "thresholds" and "scales". If the final scale is
|
|
781
|
+
omitted (i.e. you passed an odd number of arguments) it is set
|
|
782
|
+
to ``1``. Each ``scale_i`` in the sequence can be interpreted
|
|
783
|
+
as follows:
|
|
784
|
+
|
|
785
|
+
* If ``scale_i < 1``, the axis is decelerated from ``thresh_i`` to
|
|
786
|
+
``thresh_i+1``. For ``scale_N``, the axis is decelerated
|
|
787
|
+
everywhere above ``thresh_N``.
|
|
788
|
+
* If ``scale_i > 1``, the axis is accelerated from ``thresh_i`` to
|
|
789
|
+
``thresh_i+1``. For ``scale_N``, the axis is accelerated
|
|
790
|
+
everywhere above ``thresh_N``.
|
|
791
|
+
* If ``scale_i == numpy.inf``, the axis *discretely jumps* from
|
|
792
|
+
``thresh_i`` to ``thresh_i+1``. The final scale ``scale_N``
|
|
793
|
+
*cannot* be ``numpy.inf``.
|
|
794
|
+
|
|
795
|
+
See also
|
|
796
|
+
--------
|
|
797
|
+
ultraplot.constructor.Scale
|
|
798
|
+
|
|
799
|
+
Example
|
|
800
|
+
-------
|
|
801
|
+
>>> import ultraplot as pplt
|
|
802
|
+
>>> import numpy as np
|
|
803
|
+
>>> scale = pplt.CutoffScale(10, 0.5) # move slower above 10
|
|
804
|
+
>>> scale = pplt.CutoffScale(10, 2, 20) # move faster between 10 and 20
|
|
805
|
+
>>> scale = pplt.CutoffScale(10, np.inf, 20) # jump from 10 to 20
|
|
806
|
+
"""
|
|
807
|
+
# NOTE: See https://stackoverflow.com/a/5669301/4970632
|
|
808
|
+
super().__init__()
|
|
809
|
+
args = list(args)
|
|
810
|
+
if len(args) % 2 == 1:
|
|
811
|
+
args.append(1)
|
|
812
|
+
self.args = args
|
|
813
|
+
self.threshs = args[::2]
|
|
814
|
+
self.scales = args[1::2]
|
|
815
|
+
self._transform = CutoffTransform(self.threshs, self.scales)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
class CutoffTransform(mtransforms.Transform):
|
|
819
|
+
input_dims = 1
|
|
820
|
+
output_dims = 1
|
|
821
|
+
has_inverse = True
|
|
822
|
+
is_separable = True
|
|
823
|
+
|
|
824
|
+
def __init__(self, threshs, scales, zero_dists=None):
|
|
825
|
+
# The zero_dists array is used to fill in distances where scales and
|
|
826
|
+
# threshold steps are zero. Used for inverting discrete transorms.
|
|
827
|
+
super().__init__()
|
|
828
|
+
dists = np.diff(threshs)
|
|
829
|
+
scales = np.asarray(scales)
|
|
830
|
+
threshs = np.asarray(threshs)
|
|
831
|
+
if len(scales) != len(threshs):
|
|
832
|
+
raise ValueError(f"Got {len(threshs)} but {len(scales)} scales.")
|
|
833
|
+
if any(scales < 0):
|
|
834
|
+
raise ValueError("Scales must be non negative.")
|
|
835
|
+
if scales[-1] in (0, np.inf):
|
|
836
|
+
raise ValueError("Final scale must be finite.")
|
|
837
|
+
if any(dists < 0):
|
|
838
|
+
raise ValueError("Thresholds must be monotonically increasing.")
|
|
839
|
+
if any((dists == 0) | (scales == 0)):
|
|
840
|
+
if zero_dists is None:
|
|
841
|
+
raise ValueError("Keyword zero_dists is required for discrete steps.")
|
|
842
|
+
if any((dists == 0) != (scales == 0)):
|
|
843
|
+
raise ValueError("Input scales disagree with discrete step locations.")
|
|
844
|
+
self._scales = scales
|
|
845
|
+
self._threshs = threshs
|
|
846
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
847
|
+
dists = np.concatenate((threshs[:1], dists / scales[:-1]))
|
|
848
|
+
if zero_dists is not None:
|
|
849
|
+
dists[scales[:-1] == 0] = zero_dists
|
|
850
|
+
self._dists = dists
|
|
851
|
+
|
|
852
|
+
def inverted(self):
|
|
853
|
+
# Use same algorithm for inversion!
|
|
854
|
+
threshs = np.cumsum(self._dists) # thresholds in transformed space
|
|
855
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
856
|
+
scales = 1.0 / self._scales # new scales are inverse
|
|
857
|
+
zero_dists = np.diff(self._threshs)[scales[:-1] == 0]
|
|
858
|
+
return CutoffTransform(threshs, scales, zero_dists=zero_dists)
|
|
859
|
+
|
|
860
|
+
def transform_non_affine(self, a):
|
|
861
|
+
# Cannot do list comprehension because this method sometimes
|
|
862
|
+
# received non-1D arrays
|
|
863
|
+
dists = self._dists
|
|
864
|
+
scales = self._scales
|
|
865
|
+
threshs = self._threshs
|
|
866
|
+
aa = np.array(a) # copy
|
|
867
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
868
|
+
for i, ai in np.ndenumerate(a):
|
|
869
|
+
j = np.searchsorted(threshs, ai)
|
|
870
|
+
if j > 0:
|
|
871
|
+
aa[i] = dists[:j].sum() + (ai - threshs[j - 1]) / scales[j - 1]
|
|
872
|
+
return aa
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
class InverseScale(_Scale, mscale.ScaleBase):
|
|
876
|
+
r"""
|
|
877
|
+
Axis scale that is linear in the *inverse* of *x*. The forward and inverse
|
|
878
|
+
scale functions are as follows:
|
|
879
|
+
|
|
880
|
+
.. math::
|
|
881
|
+
|
|
882
|
+
y = x^{-1}
|
|
883
|
+
|
|
884
|
+
"""
|
|
885
|
+
|
|
886
|
+
#: The registered scale name
|
|
887
|
+
name = "inverse"
|
|
888
|
+
|
|
889
|
+
def __init__(self):
|
|
890
|
+
"""
|
|
891
|
+
See also
|
|
892
|
+
--------
|
|
893
|
+
ultraplot.constructor.Scale
|
|
894
|
+
"""
|
|
895
|
+
super().__init__()
|
|
896
|
+
self._transform = InverseTransform()
|
|
897
|
+
self._default_major_locator = mticker.LogLocator(10)
|
|
898
|
+
self._default_minor_locator = mticker.LogLocator(10, np.arange(1, 10))
|
|
899
|
+
|
|
900
|
+
def limit_range_for_scale(self, vmin, vmax, minpos):
|
|
901
|
+
"""
|
|
902
|
+
Return the range *vmin* and *vmax* limited to positive numbers.
|
|
903
|
+
"""
|
|
904
|
+
# Unlike log-scale, we can't just warp the space between
|
|
905
|
+
# the axis limits -- have to actually change axis limits. Also this
|
|
906
|
+
# scale will invert and swap the limits you provide.
|
|
907
|
+
if not np.isfinite(minpos):
|
|
908
|
+
minpos = 1e-300
|
|
909
|
+
return (
|
|
910
|
+
minpos if vmin <= 0 else vmin,
|
|
911
|
+
minpos if vmax <= 0 else vmax,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
class InverseTransform(mtransforms.Transform):
|
|
916
|
+
# Create transform object
|
|
917
|
+
input_dims = 1
|
|
918
|
+
output_dims = 1
|
|
919
|
+
is_separable = True
|
|
920
|
+
has_inverse = True
|
|
921
|
+
|
|
922
|
+
def __init__(self):
|
|
923
|
+
super().__init__()
|
|
924
|
+
|
|
925
|
+
def inverted(self):
|
|
926
|
+
return InverseTransform()
|
|
927
|
+
|
|
928
|
+
def transform_non_affine(self, a):
|
|
929
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
930
|
+
return 1.0 / a
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _scale_factory(scale, axis, *args, **kwargs): # noqa: U100
|
|
934
|
+
"""
|
|
935
|
+
Generate an axis scale.
|
|
936
|
+
|
|
937
|
+
Parameters
|
|
938
|
+
----------
|
|
939
|
+
scale : str or `~matplotlib.scale.ScaleBase`
|
|
940
|
+
The axis scale name or scale instance.
|
|
941
|
+
axis : `~matplotlib.axis.Axis`
|
|
942
|
+
The axis instance.
|
|
943
|
+
*args, **kwargs
|
|
944
|
+
Passed to `~matplotlib.scale.ScaleBase` if `scale` is a string.
|
|
945
|
+
"""
|
|
946
|
+
mapping = mscale._scale_mapping
|
|
947
|
+
if isinstance(scale, mscale.ScaleBase):
|
|
948
|
+
if args or kwargs:
|
|
949
|
+
warnings._warn_ultraplot(f"Ignoring args {args} and keyword args {kwargs}.")
|
|
950
|
+
return scale # do nothing
|
|
951
|
+
else:
|
|
952
|
+
scale = scale.lower()
|
|
953
|
+
if scale not in mapping:
|
|
954
|
+
raise ValueError(
|
|
955
|
+
f"Unknown axis scale {scale!r}. Options are "
|
|
956
|
+
+ ", ".join(map(repr, mapping))
|
|
957
|
+
+ "."
|
|
958
|
+
)
|
|
959
|
+
return mapping[scale](*args, **kwargs)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
# Monkey patch matplotlib scale factory with version that accepts ScaleBase instances.
|
|
963
|
+
# This lets set_xscale and set_yscale accept axis scales returned by Scale constructor
|
|
964
|
+
# and makes things constistent with the other constructor functions.
|
|
965
|
+
if mscale.scale_factory is not _scale_factory:
|
|
966
|
+
mscale.scale_factory = _scale_factory
|