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.
Files changed (416) hide show
  1. ultraplot/__init__.py +115 -0
  2. ultraplot/__init__.py.rej +58 -0
  3. ultraplot/axes/__init__.py +42 -0
  4. ultraplot/axes/base.py +3240 -0
  5. ultraplot/axes/cartesian.py +1425 -0
  6. ultraplot/axes/geo.py +1675 -0
  7. ultraplot/axes/plot.py +4569 -0
  8. ultraplot/axes/polar.py +381 -0
  9. ultraplot/axes/shared.py +186 -0
  10. ultraplot/axes/three.py +34 -0
  11. ultraplot/cmaps/Algae.rgb +256 -0
  12. ultraplot/cmaps/Amp.rgb +256 -0
  13. ultraplot/cmaps/BR.rgb +256 -0
  14. ultraplot/cmaps/Balance.rgb +256 -0
  15. ultraplot/cmaps/Blues1_r.xml +17 -0
  16. ultraplot/cmaps/Blues2.xml +16 -0
  17. ultraplot/cmaps/Blues3.xml +25 -0
  18. ultraplot/cmaps/Blues4_r.xml +17 -0
  19. ultraplot/cmaps/Blues5.xml +16 -0
  20. ultraplot/cmaps/Blues6.xml +25 -0
  21. ultraplot/cmaps/Blues7.xml +16 -0
  22. ultraplot/cmaps/Blues8.xml +17 -0
  23. ultraplot/cmaps/Blues9.xml +1 -0
  24. ultraplot/cmaps/Boreal.json +53 -0
  25. ultraplot/cmaps/Browns1.xml +16 -0
  26. ultraplot/cmaps/Browns2.xml +26 -0
  27. ultraplot/cmaps/Browns3.xml +17 -0
  28. ultraplot/cmaps/Browns4.xml +17 -0
  29. ultraplot/cmaps/Browns5.xml +26 -0
  30. ultraplot/cmaps/Browns6.xml +17 -0
  31. ultraplot/cmaps/Browns7.xml +19 -0
  32. ultraplot/cmaps/Browns8.xml +11 -0
  33. ultraplot/cmaps/Browns9.xml +1 -0
  34. ultraplot/cmaps/ColdHot.rgb +229 -0
  35. ultraplot/cmaps/Crest.rgb +256 -0
  36. ultraplot/cmaps/Curl.rgb +512 -0
  37. ultraplot/cmaps/Deep.rgb +256 -0
  38. ultraplot/cmaps/Delta.rgb +512 -0
  39. ultraplot/cmaps/Dense.rgb +256 -0
  40. ultraplot/cmaps/Div.json +71 -0
  41. ultraplot/cmaps/DryWet.json +73 -0
  42. ultraplot/cmaps/Dusk.json +53 -0
  43. ultraplot/cmaps/Fire.json +53 -0
  44. ultraplot/cmaps/Flare.rgb +256 -0
  45. ultraplot/cmaps/Glacial.json +53 -0
  46. ultraplot/cmaps/Greens1_r.xml +26 -0
  47. ultraplot/cmaps/Greens2.xml +28 -0
  48. ultraplot/cmaps/Greens3_r.xml +28 -0
  49. ultraplot/cmaps/Greens4.xml +17 -0
  50. ultraplot/cmaps/Greens5.xml +16 -0
  51. ultraplot/cmaps/Greens6_r.xml +16 -0
  52. ultraplot/cmaps/Greens7.xml +16 -0
  53. ultraplot/cmaps/Greens8.xml +26 -0
  54. ultraplot/cmaps/Haline.rgb +256 -0
  55. ultraplot/cmaps/Ice.rgb +256 -0
  56. ultraplot/cmaps/IceFire.rgb +256 -0
  57. ultraplot/cmaps/Mako.rgb +256 -0
  58. ultraplot/cmaps/Marine.json +53 -0
  59. ultraplot/cmaps/Matter.rgb +256 -0
  60. ultraplot/cmaps/Mono.txt +256 -0
  61. ultraplot/cmaps/MonoCycle.txt +256 -0
  62. ultraplot/cmaps/NegPos.json +71 -0
  63. ultraplot/cmaps/Oranges1.xml +27 -0
  64. ultraplot/cmaps/Oranges2.xml +26 -0
  65. ultraplot/cmaps/Oranges3.xml +15 -0
  66. ultraplot/cmaps/Oranges4.xml +23 -0
  67. ultraplot/cmaps/Oxy.rgb +256 -0
  68. ultraplot/cmaps/Phase.rgb +256 -0
  69. ultraplot/cmaps/Purples1_r.xml +16 -0
  70. ultraplot/cmaps/Purples2.xml +17 -0
  71. ultraplot/cmaps/Purples3.xml +18 -0
  72. ultraplot/cmaps/Reds1.xml +26 -0
  73. ultraplot/cmaps/Reds2.xml +22 -0
  74. ultraplot/cmaps/Reds3.xml +23 -0
  75. ultraplot/cmaps/Reds4.xml +26 -0
  76. ultraplot/cmaps/Reds5.xml +17 -0
  77. ultraplot/cmaps/Rocket.rgb +256 -0
  78. ultraplot/cmaps/Solar.rgb +256 -0
  79. ultraplot/cmaps/Speed.rgb +256 -0
  80. ultraplot/cmaps/Stellar.json +53 -0
  81. ultraplot/cmaps/Sunrise.json +53 -0
  82. ultraplot/cmaps/Sunset.json +53 -0
  83. ultraplot/cmaps/Tempo.rgb +256 -0
  84. ultraplot/cmaps/Thermal.rgb +256 -0
  85. ultraplot/cmaps/Turbid.rgb +256 -0
  86. ultraplot/cmaps/Vivid.xml +11 -0
  87. ultraplot/cmaps/Vlag.rgb +256 -0
  88. ultraplot/cmaps/Yellows1.xml +17 -0
  89. ultraplot/cmaps/Yellows2.xml +17 -0
  90. ultraplot/cmaps/Yellows3.xml +17 -0
  91. ultraplot/cmaps/Yellows4.xml +17 -0
  92. ultraplot/cmaps/acton.txt +256 -0
  93. ultraplot/cmaps/bam.txt +256 -0
  94. ultraplot/cmaps/bamO.txt +256 -0
  95. ultraplot/cmaps/bamako.txt +256 -0
  96. ultraplot/cmaps/batlow.txt +256 -0
  97. ultraplot/cmaps/batlowK.txt +256 -0
  98. ultraplot/cmaps/batlowW.txt +256 -0
  99. ultraplot/cmaps/berlin.txt +256 -0
  100. ultraplot/cmaps/bilbao.txt +256 -0
  101. ultraplot/cmaps/broc.txt +256 -0
  102. ultraplot/cmaps/brocO.txt +256 -0
  103. ultraplot/cmaps/buda.txt +256 -0
  104. ultraplot/cmaps/bukavu.txt +256 -0
  105. ultraplot/cmaps/cork.txt +256 -0
  106. ultraplot/cmaps/corkO.txt +256 -0
  107. ultraplot/cmaps/davos.txt +256 -0
  108. ultraplot/cmaps/devon.txt +256 -0
  109. ultraplot/cmaps/fes.txt +256 -0
  110. ultraplot/cmaps/hawaii.txt +256 -0
  111. ultraplot/cmaps/imola.txt +256 -0
  112. ultraplot/cmaps/lajolla.txt +256 -0
  113. ultraplot/cmaps/lapaz.txt +256 -0
  114. ultraplot/cmaps/lisbon.txt +256 -0
  115. ultraplot/cmaps/nuuk.txt +256 -0
  116. ultraplot/cmaps/oleron.txt +256 -0
  117. ultraplot/cmaps/oslo.txt +256 -0
  118. ultraplot/cmaps/roma.txt +256 -0
  119. ultraplot/cmaps/romaO.txt +256 -0
  120. ultraplot/cmaps/tofino.txt +256 -0
  121. ultraplot/cmaps/tokyo.txt +256 -0
  122. ultraplot/cmaps/turku.txt +256 -0
  123. ultraplot/cmaps/vanimo.txt +256 -0
  124. ultraplot/cmaps/vik.txt +256 -0
  125. ultraplot/cmaps/vikO.txt +256 -0
  126. ultraplot/colors/opencolor.txt +132 -0
  127. ultraplot/colors/xkcd.txt +951 -0
  128. ultraplot/colors.py +3241 -0
  129. ultraplot/colors.py.rej +243 -0
  130. ultraplot/config.py +1809 -0
  131. ultraplot/constructor.py +1633 -0
  132. ultraplot/cycles/538.hex +2 -0
  133. ultraplot/cycles/FlatUI.hex +1 -0
  134. ultraplot/cycles/Qual1.rgb +7 -0
  135. ultraplot/cycles/Qual2.rgb +13 -0
  136. ultraplot/cycles/bmh.hex +2 -0
  137. ultraplot/cycles/classic.hex +2 -0
  138. ultraplot/cycles/colorblind.hex +2 -0
  139. ultraplot/cycles/colorblind10.hex +2 -0
  140. ultraplot/cycles/default.hex +2 -0
  141. ultraplot/cycles/ggplot.hex +1 -0
  142. ultraplot/cycles/seaborn.hex +2 -0
  143. ultraplot/cycles/tableau.hex +2 -0
  144. ultraplot/demos.py +1201 -0
  145. ultraplot/externals/__init__.py +5 -0
  146. ultraplot/externals/hsluv.py +330 -0
  147. ultraplot/figure.py +2102 -0
  148. ultraplot/fonts/FiraMath-Bold.ttf +0 -0
  149. ultraplot/fonts/FiraMath-ExtraLight.ttf +0 -0
  150. ultraplot/fonts/FiraMath-Heavy.ttf +0 -0
  151. ultraplot/fonts/FiraMath-Light.ttf +0 -0
  152. ultraplot/fonts/FiraMath-Medium.ttf +0 -0
  153. ultraplot/fonts/FiraMath-Regular.ttf +0 -0
  154. ultraplot/fonts/FiraMath-SemiBold.ttf +0 -0
  155. ultraplot/fonts/FiraMath-UltraLight.ttf +0 -0
  156. ultraplot/fonts/FiraSans-Black.ttf +0 -0
  157. ultraplot/fonts/FiraSans-BlackItalic.ttf +0 -0
  158. ultraplot/fonts/FiraSans-Bold.ttf +0 -0
  159. ultraplot/fonts/FiraSans-BoldItalic.ttf +0 -0
  160. ultraplot/fonts/FiraSans-ExtraBold.ttf +0 -0
  161. ultraplot/fonts/FiraSans-ExtraBoldItalic.ttf +0 -0
  162. ultraplot/fonts/FiraSans-ExtraLight.ttf +0 -0
  163. ultraplot/fonts/FiraSans-ExtraLightItalic.ttf +0 -0
  164. ultraplot/fonts/FiraSans-Italic.ttf +0 -0
  165. ultraplot/fonts/FiraSans-Light.ttf +0 -0
  166. ultraplot/fonts/FiraSans-LightItalic.ttf +0 -0
  167. ultraplot/fonts/FiraSans-Medium.ttf +0 -0
  168. ultraplot/fonts/FiraSans-MediumItalic.ttf +0 -0
  169. ultraplot/fonts/FiraSans-Regular.ttf +0 -0
  170. ultraplot/fonts/FiraSans-SemiBold.ttf +0 -0
  171. ultraplot/fonts/FiraSans-SemiBoldItalic.ttf +0 -0
  172. ultraplot/fonts/LICENSE_FIRAMATH.txt +92 -0
  173. ultraplot/fonts/LICENSE_FIRASANS.txt +97 -0
  174. ultraplot/fonts/LICENSE_NOTOSANS.txt +202 -0
  175. ultraplot/fonts/LICENSE_NOTOSERIF.txt +93 -0
  176. ultraplot/fonts/LICENSE_OPENSANS.txt +202 -0
  177. ultraplot/fonts/LICENSE_ROBOTO.txt +202 -0
  178. ultraplot/fonts/LICENSE_SOURCESANS.txt +93 -0
  179. ultraplot/fonts/LICENSE_SOURCESERIF.txt +93 -0
  180. ultraplot/fonts/LICENSE_TEXGYRE.txt +29 -0
  181. ultraplot/fonts/LICENSE_UBUNTU.txt +96 -0
  182. ultraplot/fonts/NotoSans-Bold.ttf +0 -0
  183. ultraplot/fonts/NotoSans-BoldItalic.ttf +0 -0
  184. ultraplot/fonts/NotoSans-Italic.ttf +0 -0
  185. ultraplot/fonts/NotoSans-Regular.ttf +0 -0
  186. ultraplot/fonts/NotoSerif-Bold.ttf +0 -0
  187. ultraplot/fonts/NotoSerif-BoldItalic.ttf +0 -0
  188. ultraplot/fonts/NotoSerif-Italic.ttf +0 -0
  189. ultraplot/fonts/NotoSerif-Regular.ttf +0 -0
  190. ultraplot/fonts/OpenSans-Bold.ttf +0 -0
  191. ultraplot/fonts/OpenSans-BoldItalic.ttf +0 -0
  192. ultraplot/fonts/OpenSans-Italic.ttf +0 -0
  193. ultraplot/fonts/OpenSans-Regular.ttf +0 -0
  194. ultraplot/fonts/OpenSans-Semibold.ttf +0 -0
  195. ultraplot/fonts/OpenSans-SemiboldItalic.ttf +0 -0
  196. ultraplot/fonts/Roboto-Black.ttf +0 -0
  197. ultraplot/fonts/Roboto-BlackItalic.ttf +0 -0
  198. ultraplot/fonts/Roboto-Bold.ttf +0 -0
  199. ultraplot/fonts/Roboto-BoldItalic.ttf +0 -0
  200. ultraplot/fonts/Roboto-Italic.ttf +0 -0
  201. ultraplot/fonts/Roboto-Light.ttf +0 -0
  202. ultraplot/fonts/Roboto-LightItalic.ttf +0 -0
  203. ultraplot/fonts/Roboto-Medium.ttf +0 -0
  204. ultraplot/fonts/Roboto-MediumItalic.ttf +0 -0
  205. ultraplot/fonts/Roboto-Regular.ttf +0 -0
  206. ultraplot/fonts/SourceSansPro-Black.ttf +0 -0
  207. ultraplot/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  208. ultraplot/fonts/SourceSansPro-Bold.ttf +0 -0
  209. ultraplot/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  210. ultraplot/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  211. ultraplot/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  212. ultraplot/fonts/SourceSansPro-Italic.ttf +0 -0
  213. ultraplot/fonts/SourceSansPro-Light.ttf +0 -0
  214. ultraplot/fonts/SourceSansPro-LightItalic.ttf +0 -0
  215. ultraplot/fonts/SourceSansPro-Regular.ttf +0 -0
  216. ultraplot/fonts/SourceSansPro-SemiBold.ttf +0 -0
  217. ultraplot/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  218. ultraplot/fonts/SourceSerifPro-Black.ttf +0 -0
  219. ultraplot/fonts/SourceSerifPro-BlackItalic.ttf +0 -0
  220. ultraplot/fonts/SourceSerifPro-Bold.ttf +0 -0
  221. ultraplot/fonts/SourceSerifPro-BoldItalic.ttf +0 -0
  222. ultraplot/fonts/SourceSerifPro-ExtraLight.ttf +0 -0
  223. ultraplot/fonts/SourceSerifPro-ExtraLightItalic.ttf +0 -0
  224. ultraplot/fonts/SourceSerifPro-Italic.ttf +0 -0
  225. ultraplot/fonts/SourceSerifPro-Light.ttf +0 -0
  226. ultraplot/fonts/SourceSerifPro-LightItalic.ttf +0 -0
  227. ultraplot/fonts/SourceSerifPro-Regular.ttf +0 -0
  228. ultraplot/fonts/SourceSerifPro-SemiBold.ttf +0 -0
  229. ultraplot/fonts/SourceSerifPro-SemiBoldItalic.ttf +0 -0
  230. ultraplot/fonts/Ubuntu-Bold.ttf +0 -0
  231. ultraplot/fonts/Ubuntu-BoldItalic.ttf +0 -0
  232. ultraplot/fonts/Ubuntu-Italic.ttf +0 -0
  233. ultraplot/fonts/Ubuntu-Light.ttf +0 -0
  234. ultraplot/fonts/Ubuntu-LightItalic.ttf +0 -0
  235. ultraplot/fonts/Ubuntu-Medium.ttf +0 -0
  236. ultraplot/fonts/Ubuntu-MediumItalic.ttf +0 -0
  237. ultraplot/fonts/Ubuntu-Regular.ttf +0 -0
  238. ultraplot/fonts/texgyreadventor-bold.ttf +0 -0
  239. ultraplot/fonts/texgyreadventor-bolditalic.ttf +0 -0
  240. ultraplot/fonts/texgyreadventor-italic.ttf +0 -0
  241. ultraplot/fonts/texgyreadventor-regular.ttf +0 -0
  242. ultraplot/fonts/texgyrebonum-bold.ttf +0 -0
  243. ultraplot/fonts/texgyrebonum-bolditalic.ttf +0 -0
  244. ultraplot/fonts/texgyrebonum-italic.ttf +0 -0
  245. ultraplot/fonts/texgyrebonum-regular.ttf +0 -0
  246. ultraplot/fonts/texgyrechorus-mediumitalic.ttf +0 -0
  247. ultraplot/fonts/texgyrecursor-bold.ttf +0 -0
  248. ultraplot/fonts/texgyrecursor-bolditalic.ttf +0 -0
  249. ultraplot/fonts/texgyrecursor-italic.ttf +0 -0
  250. ultraplot/fonts/texgyrecursor-regular.ttf +0 -0
  251. ultraplot/fonts/texgyreheros-bold.ttf +0 -0
  252. ultraplot/fonts/texgyreheros-bolditalic.ttf +0 -0
  253. ultraplot/fonts/texgyreheros-italic.ttf +0 -0
  254. ultraplot/fonts/texgyreheros-regular.ttf +0 -0
  255. ultraplot/fonts/texgyrepagella-bold.ttf +0 -0
  256. ultraplot/fonts/texgyrepagella-bolditalic.ttf +0 -0
  257. ultraplot/fonts/texgyrepagella-italic.ttf +0 -0
  258. ultraplot/fonts/texgyrepagella-regular.ttf +0 -0
  259. ultraplot/fonts/texgyreschola-bold.ttf +0 -0
  260. ultraplot/fonts/texgyreschola-bolditalic.ttf +0 -0
  261. ultraplot/fonts/texgyreschola-italic.ttf +0 -0
  262. ultraplot/fonts/texgyreschola-regular.ttf +0 -0
  263. ultraplot/fonts/texgyretermes-bold.ttf +0 -0
  264. ultraplot/fonts/texgyretermes-bolditalic.ttf +0 -0
  265. ultraplot/fonts/texgyretermes-italic.ttf +0 -0
  266. ultraplot/fonts/texgyretermes-regular.ttf +0 -0
  267. ultraplot/gridspec.py +1698 -0
  268. ultraplot/internals/__init__.py +529 -0
  269. ultraplot/internals/benchmarks.py +26 -0
  270. ultraplot/internals/context.py +44 -0
  271. ultraplot/internals/docstring.py +139 -0
  272. ultraplot/internals/fonts.py +75 -0
  273. ultraplot/internals/guides.py +167 -0
  274. ultraplot/internals/inputs.py +862 -0
  275. ultraplot/internals/labels.py +85 -0
  276. ultraplot/internals/rcsetup.py +1933 -0
  277. ultraplot/internals/versions.py +61 -0
  278. ultraplot/internals/warnings.py +122 -0
  279. ultraplot/proj.py +325 -0
  280. ultraplot/scale.py +966 -0
  281. ultraplot/tests/__init__.py +28 -0
  282. ultraplot/tests/baseline/test_align_labels.png +0 -0
  283. ultraplot/tests/baseline/test_aligned_outer_guides.png +0 -0
  284. ultraplot/tests/baseline/test_aspect_ratios.png +0 -0
  285. ultraplot/tests/baseline/test_auto_diverging1.png +0 -0
  286. ultraplot/tests/baseline/test_auto_legend.png +0 -0
  287. ultraplot/tests/baseline/test_auto_reverse.png +0 -0
  288. ultraplot/tests/baseline/test_autodiverging3.png +0 -0
  289. ultraplot/tests/baseline/test_autodiverging4.png +0 -0
  290. ultraplot/tests/baseline/test_autodiverging5.png +0 -0
  291. ultraplot/tests/baseline/test_axes_colors.png +0 -0
  292. ultraplot/tests/baseline/test_bar_vectors.png +0 -0
  293. ultraplot/tests/baseline/test_bar_width.png +0 -0
  294. ultraplot/tests/baseline/test_both_ticklabels.png +0 -0
  295. ultraplot/tests/baseline/test_bounds_ticks.png +0 -0
  296. ultraplot/tests/baseline/test_boxplot_colors.png +0 -0
  297. ultraplot/tests/baseline/test_boxplot_vectors.png +0 -0
  298. ultraplot/tests/baseline/test_cartopy_contours.png +0 -0
  299. ultraplot/tests/baseline/test_cartopy_labels.png +0 -0
  300. ultraplot/tests/baseline/test_cartopy_manual.png +0 -0
  301. ultraplot/tests/baseline/test_centered_legends.png +0 -0
  302. ultraplot/tests/baseline/test_cmap_cycles.png +0 -0
  303. ultraplot/tests/baseline/test_colorbar.png +0 -0
  304. ultraplot/tests/baseline/test_colorbar_ticks.png +0 -0
  305. ultraplot/tests/baseline/test_colormap_mode.png +0 -0
  306. ultraplot/tests/baseline/test_column_iteration.png +0 -0
  307. ultraplot/tests/baseline/test_complex_ticks.png +0 -0
  308. ultraplot/tests/baseline/test_contour_labels.png +0 -0
  309. ultraplot/tests/baseline/test_contour_legend_with_label.png +0 -0
  310. ultraplot/tests/baseline/test_contour_legend_without_label.png +0 -0
  311. ultraplot/tests/baseline/test_contour_negative.png +0 -0
  312. ultraplot/tests/baseline/test_contour_single.png +0 -0
  313. ultraplot/tests/baseline/test_cutoff_ticks.png +0 -0
  314. ultraplot/tests/baseline/test_data_keyword.png +0 -0
  315. ultraplot/tests/baseline/test_discrete_ticks.png +0 -0
  316. ultraplot/tests/baseline/test_discrete_vs_fixed.png +0 -0
  317. ultraplot/tests/baseline/test_drawing_in_projection_with_globe.png +0 -0
  318. ultraplot/tests/baseline/test_drawing_in_projection_without_globe.png +0 -0
  319. ultraplot/tests/baseline/test_edge_fix.png +0 -0
  320. ultraplot/tests/baseline/test_flow_functions.png +0 -0
  321. ultraplot/tests/baseline/test_font_adjustments.png +0 -0
  322. ultraplot/tests/baseline/test_geographic_multiple_projections.png +0 -0
  323. ultraplot/tests/baseline/test_geographic_single_projection.png +0 -0
  324. ultraplot/tests/baseline/test_gray_adjustment.png +0 -0
  325. ultraplot/tests/baseline/test_histogram_legend.png +0 -0
  326. ultraplot/tests/baseline/test_histogram_types.png +0 -0
  327. ultraplot/tests/baseline/test_ignore_message.png +0 -0
  328. ultraplot/tests/baseline/test_inbounds_data.png +0 -0
  329. ultraplot/tests/baseline/test_init_format.png +0 -0
  330. ultraplot/tests/baseline/test_inner_title_zorder.png +0 -0
  331. ultraplot/tests/baseline/test_inset_basic.png +0 -0
  332. ultraplot/tests/baseline/test_inset_colorbars.png +0 -0
  333. ultraplot/tests/baseline/test_inset_colors_1.png +0 -0
  334. ultraplot/tests/baseline/test_inset_colors_2.png +0 -0
  335. ultraplot/tests/baseline/test_inset_zoom_update.png +0 -0
  336. ultraplot/tests/baseline/test_invalid_dist.png +0 -0
  337. ultraplot/tests/baseline/test_invalid_plot.png +0 -0
  338. ultraplot/tests/baseline/test_keep_guide_labels.png +0 -0
  339. ultraplot/tests/baseline/test_label_settings.png +0 -0
  340. ultraplot/tests/baseline/test_level_restriction.png +0 -0
  341. ultraplot/tests/baseline/test_levels_with_vmin_vmax.png +0 -0
  342. ultraplot/tests/baseline/test_locale_formatting.png +0 -0
  343. ultraplot/tests/baseline/test_locale_formatting_en_US.UTF-8.png +0 -0
  344. ultraplot/tests/baseline/test_manual_labels.png +0 -0
  345. ultraplot/tests/baseline/test_multi_formatting.png +0 -0
  346. ultraplot/tests/baseline/test_multiple_calls.png +0 -0
  347. ultraplot/tests/baseline/test_on_the_fly_mappable.png +0 -0
  348. ultraplot/tests/baseline/test_outer_align.png +0 -0
  349. ultraplot/tests/baseline/test_panel_dist.png +0 -0
  350. ultraplot/tests/baseline/test_panels_suplabels_three_hor_panels.png +0 -0
  351. ultraplot/tests/baseline/test_panels_with_sharing.png +0 -0
  352. ultraplot/tests/baseline/test_panels_without_sharing_1.png +0 -0
  353. ultraplot/tests/baseline/test_panels_without_sharing_2.png +0 -0
  354. ultraplot/tests/baseline/test_parametric_colors.png +0 -0
  355. ultraplot/tests/baseline/test_parametric_labels.png +0 -0
  356. ultraplot/tests/baseline/test_patch_format.png +0 -0
  357. ultraplot/tests/baseline/test_pie_charts.png +0 -0
  358. ultraplot/tests/baseline/test_pint_quantities.png +0 -0
  359. ultraplot/tests/baseline/test_polar_projections.png +0 -0
  360. ultraplot/tests/baseline/test_projection_dicts.png +0 -0
  361. ultraplot/tests/baseline/test_qualitative_colormaps_1.png +0 -0
  362. ultraplot/tests/baseline/test_qualitative_colormaps_2.png +0 -0
  363. ultraplot/tests/baseline/test_reversed_levels.png +0 -0
  364. ultraplot/tests/baseline/test_scatter_alpha.png +0 -0
  365. ultraplot/tests/baseline/test_scatter_args.png +0 -0
  366. ultraplot/tests/baseline/test_scatter_cycle.png +0 -0
  367. ultraplot/tests/baseline/test_scatter_inbounds.png +0 -0
  368. ultraplot/tests/baseline/test_scatter_sizes.png +0 -0
  369. ultraplot/tests/baseline/test_seaborn_heatmap.png +0 -0
  370. ultraplot/tests/baseline/test_seaborn_hist.png +0 -0
  371. ultraplot/tests/baseline/test_seaborn_relational.png +0 -0
  372. ultraplot/tests/baseline/test_seaborn_swarmplot.png +0 -0
  373. ultraplot/tests/baseline/test_segmented_norm.png +0 -0
  374. ultraplot/tests/baseline/test_segmented_norm_ticks.png +0 -0
  375. ultraplot/tests/baseline/test_share_all_basic.png +0 -0
  376. ultraplot/tests/baseline/test_singleton_legend.png +0 -0
  377. ultraplot/tests/baseline/test_span_labels.png +0 -0
  378. ultraplot/tests/baseline/test_spine_offset.png +0 -0
  379. ultraplot/tests/baseline/test_spine_side.png +0 -0
  380. ultraplot/tests/baseline/test_standardized_input.png +0 -0
  381. ultraplot/tests/baseline/test_statistical_boxplot.png +0 -0
  382. ultraplot/tests/baseline/test_three_axes.png +0 -0
  383. ultraplot/tests/baseline/test_tick_direction.png +0 -0
  384. ultraplot/tests/baseline/test_tick_labels.png +0 -0
  385. ultraplot/tests/baseline/test_tick_length.png +0 -0
  386. ultraplot/tests/baseline/test_tick_width.png +0 -0
  387. ultraplot/tests/baseline/test_title_deflection.png +0 -0
  388. ultraplot/tests/baseline/test_triangular_functions.png +0 -0
  389. ultraplot/tests/baseline/test_tuple_handles.png +0 -0
  390. ultraplot/tests/baseline/test_twin_axes_1.png +0 -0
  391. ultraplot/tests/baseline/test_twin_axes_2.png +0 -0
  392. ultraplot/tests/baseline/test_twin_axes_3.png +0 -0
  393. ultraplot/tests/baseline/test_uneven_levels.png +0 -0
  394. ultraplot/tests/test_1dplots.py +373 -0
  395. ultraplot/tests/test_2dplots.py +354 -0
  396. ultraplot/tests/test_axes.py +179 -0
  397. ultraplot/tests/test_colorbar.py +253 -0
  398. ultraplot/tests/test_docs.py +78 -0
  399. ultraplot/tests/test_format.py +340 -0
  400. ultraplot/tests/test_geographic.py +116 -0
  401. ultraplot/tests/test_imshow.py +110 -0
  402. ultraplot/tests/test_inset.py +28 -0
  403. ultraplot/tests/test_integration.py +149 -0
  404. ultraplot/tests/test_legend.py +181 -0
  405. ultraplot/tests/test_projections.py +138 -0
  406. ultraplot/tests/test_statistical_plotting.py +77 -0
  407. ultraplot/tests/test_subplots.py +174 -0
  408. ultraplot/ticker.py +879 -0
  409. ultraplot/ui.py +233 -0
  410. ultraplot/utils.py +912 -0
  411. ultraplot-0.99.3.dist-info/LICENSE.txt +427 -0
  412. ultraplot-0.99.3.dist-info/METADATA +88 -0
  413. ultraplot-0.99.3.dist-info/RECORD +416 -0
  414. ultraplot-0.99.3.dist-info/WHEEL +5 -0
  415. ultraplot-0.99.3.dist-info/entry_points.txt +2 -0
  416. 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