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
@@ -0,0 +1,1633 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ T"he constructor functions used to build class instances from simple shorthand arguments.
4
+ """
5
+ # NOTE: These functions used to be in separate files like crs.py and
6
+ # ticker.py but makes more sense to group them together to ensure usage is
7
+ # consistent and so online documentation is easier to understand. Also in
8
+ # future version classes will not be imported into top-level namespace. This
9
+ # change will be easier to do with all constructor functions in separate file.
10
+ # NOTE: Used to include the raw variable names that define string keys as
11
+ # part of documentation, but this is redundant and pollutes the namespace.
12
+ # User should just inspect docstrings, use trial-error, or see online tables.
13
+ import copy
14
+ import os
15
+ import re
16
+ from functools import partial
17
+ from numbers import Number
18
+
19
+ import cycler
20
+ import matplotlib.colors as mcolors
21
+ import matplotlib.dates as mdates
22
+ import matplotlib.projections.polar as mpolar
23
+ import matplotlib.scale as mscale
24
+ import matplotlib.ticker as mticker
25
+ import numpy as np
26
+
27
+ from . import colors as pcolors
28
+ from . import proj as pproj
29
+ from . import scale as pscale
30
+ from . import ticker as pticker
31
+ from .config import rc
32
+ from .internals import ic # noqa: F401
33
+ from .internals import _not_none, _pop_props, _version_cartopy, _version_mpl, warnings
34
+ from .utils import get_colors, to_hex, to_rgba
35
+
36
+ try:
37
+ from mpl_toolkits.basemap import Basemap
38
+ except ImportError:
39
+ Basemap = object
40
+ try:
41
+ import cartopy.crs as ccrs
42
+ from cartopy.crs import Projection
43
+ except ModuleNotFoundError:
44
+ ccrs = None
45
+ Projection = object
46
+
47
+ __all__ = [
48
+ "Proj",
49
+ "Locator",
50
+ "Formatter",
51
+ "Scale",
52
+ "Colormap",
53
+ "Norm",
54
+ "Cycle",
55
+ "Colors", # deprecated
56
+ ]
57
+
58
+ # Color cycle constants
59
+ # TODO: Also automatically truncate the 'bright' end of colormaps
60
+ # when building color cycles from colormaps? Or add simple option.
61
+ DEFAULT_CYCLE_SAMPLES = 10
62
+ DEFAULT_CYCLE_LUMINANCE = 90
63
+
64
+ # Normalizer registry
65
+ NORMS = {
66
+ "none": mcolors.NoNorm,
67
+ "null": mcolors.NoNorm,
68
+ "div": pcolors.DivergingNorm,
69
+ "diverging": pcolors.DivergingNorm,
70
+ "segmented": pcolors.SegmentedNorm,
71
+ "segments": pcolors.SegmentedNorm,
72
+ "log": mcolors.LogNorm,
73
+ "linear": mcolors.Normalize,
74
+ "power": mcolors.PowerNorm,
75
+ "symlog": mcolors.SymLogNorm,
76
+ }
77
+ if hasattr(mcolors, "TwoSlopeNorm"):
78
+ NORMS["twoslope"] = mcolors.TwoSlopeNorm
79
+
80
+ # Locator registry
81
+ # NOTE: Will raise error when you try to use degree-minute-second
82
+ # locators with cartopy < 0.18.
83
+ LOCATORS = {
84
+ "none": mticker.NullLocator,
85
+ "null": mticker.NullLocator,
86
+ "auto": mticker.AutoLocator,
87
+ "log": mticker.LogLocator,
88
+ "maxn": mticker.MaxNLocator,
89
+ "linear": mticker.LinearLocator,
90
+ "multiple": mticker.MultipleLocator,
91
+ "fixed": mticker.FixedLocator,
92
+ "index": pticker.IndexLocator,
93
+ "discrete": pticker.DiscreteLocator,
94
+ "discreteminor": partial(pticker.DiscreteLocator, minor=True),
95
+ "symlog": mticker.SymmetricalLogLocator,
96
+ "logit": mticker.LogitLocator,
97
+ "minor": mticker.AutoMinorLocator,
98
+ "date": mdates.AutoDateLocator,
99
+ "microsecond": mdates.MicrosecondLocator,
100
+ "second": mdates.SecondLocator,
101
+ "minute": mdates.MinuteLocator,
102
+ "hour": mdates.HourLocator,
103
+ "day": mdates.DayLocator,
104
+ "weekday": mdates.WeekdayLocator,
105
+ "month": mdates.MonthLocator,
106
+ "year": mdates.YearLocator,
107
+ "lon": partial(pticker.LongitudeLocator, dms=False),
108
+ "lat": partial(pticker.LatitudeLocator, dms=False),
109
+ "deglon": partial(pticker.LongitudeLocator, dms=False),
110
+ "deglat": partial(pticker.LatitudeLocator, dms=False),
111
+ }
112
+ if hasattr(mpolar, "ThetaLocator"):
113
+ LOCATORS["theta"] = mpolar.ThetaLocator
114
+ if _version_cartopy >= "0.18":
115
+ LOCATORS["dms"] = partial(pticker.DegreeLocator, dms=True)
116
+ LOCATORS["dmslon"] = partial(pticker.LongitudeLocator, dms=True)
117
+ LOCATORS["dmslat"] = partial(pticker.LatitudeLocator, dms=True)
118
+
119
+ # Formatter registry
120
+ # NOTE: Critical to use SimpleFormatter for cardinal formatters rather than
121
+ # AutoFormatter because latter fails with Basemap formatting.
122
+ # NOTE: Define cartopy longitude/latitude formatters with dms=True because that
123
+ # is their distinguishing feature relative to ultraplot formatter.
124
+ # NOTE: Will raise error when you try to use degree-minute-second
125
+ # formatters with cartopy < 0.18.
126
+ FORMATTERS = { # note default LogFormatter uses ugly e+00 notation
127
+ "none": mticker.NullFormatter,
128
+ "null": mticker.NullFormatter,
129
+ "auto": pticker.AutoFormatter,
130
+ "date": mdates.AutoDateFormatter,
131
+ "scalar": mticker.ScalarFormatter,
132
+ "simple": pticker.SimpleFormatter,
133
+ "fixed": mticker.FixedLocator,
134
+ "index": pticker.IndexFormatter,
135
+ "sci": pticker.SciFormatter,
136
+ "sigfig": pticker.SigFigFormatter,
137
+ "frac": pticker.FracFormatter,
138
+ "func": mticker.FuncFormatter,
139
+ "strmethod": mticker.StrMethodFormatter,
140
+ "formatstr": mticker.FormatStrFormatter,
141
+ "datestr": mdates.DateFormatter,
142
+ "log": mticker.LogFormatterSciNotation, # NOTE: this is subclass of Mathtext class
143
+ "logit": mticker.LogitFormatter,
144
+ "eng": mticker.EngFormatter,
145
+ "percent": mticker.PercentFormatter,
146
+ "e": partial(pticker.FracFormatter, symbol=r"$e$", number=np.e),
147
+ "pi": partial(pticker.FracFormatter, symbol=r"$\pi$", number=np.pi),
148
+ "tau": partial(pticker.FracFormatter, symbol=r"$\tau$", number=2 * np.pi),
149
+ "lat": partial(pticker.SimpleFormatter, negpos="SN"),
150
+ "lon": partial(pticker.SimpleFormatter, negpos="WE", wraprange=(-180, 180)),
151
+ "deg": partial(pticker.SimpleFormatter, suffix="\N{DEGREE SIGN}"),
152
+ "deglat": partial(pticker.SimpleFormatter, suffix="\N{DEGREE SIGN}", negpos="SN"),
153
+ "deglon": partial(
154
+ pticker.SimpleFormatter,
155
+ suffix="\N{DEGREE SIGN}",
156
+ negpos="WE",
157
+ wraprange=(-180, 180),
158
+ ), # noqa: E501
159
+ "math": mticker.LogFormatterMathtext, # deprecated (use SciNotation subclass)
160
+ }
161
+ if hasattr(mpolar, "ThetaFormatter"):
162
+ FORMATTERS["theta"] = mpolar.ThetaFormatter
163
+ if hasattr(mdates, "ConciseDateFormatter"):
164
+ FORMATTERS["concise"] = mdates.ConciseDateFormatter
165
+ if _version_cartopy >= "0.18":
166
+ FORMATTERS["dms"] = partial(pticker.DegreeFormatter, dms=True)
167
+ FORMATTERS["dmslon"] = partial(pticker.LongitudeFormatter, dms=True)
168
+ FORMATTERS["dmslat"] = partial(pticker.LatitudeFormatter, dms=True)
169
+
170
+ # Scale registry and presets
171
+ SCALES = mscale._scale_mapping
172
+ SCALES_PRESETS = {
173
+ "quadratic": (
174
+ "power",
175
+ 2,
176
+ ),
177
+ "cubic": (
178
+ "power",
179
+ 3,
180
+ ),
181
+ "quartic": (
182
+ "power",
183
+ 4,
184
+ ),
185
+ "height": ("exp", np.e, -1 / 7, 1013.25, True),
186
+ "pressure": ("exp", np.e, -1 / 7, 1013.25, False),
187
+ "db": ("exp", 10, 1, 0.1, True),
188
+ "idb": ("exp", 10, 1, 0.1, False),
189
+ "np": ("exp", np.e, 1, 1, True),
190
+ "inp": ("exp", np.e, 1, 1, False),
191
+ }
192
+ mscale.register_scale(pscale.CutoffScale)
193
+ mscale.register_scale(pscale.ExpScale)
194
+ mscale.register_scale(pscale.FuncScale)
195
+ mscale.register_scale(pscale.InverseScale)
196
+ mscale.register_scale(pscale.LogScale)
197
+ mscale.register_scale(pscale.LinearScale)
198
+ mscale.register_scale(pscale.LogitScale)
199
+ mscale.register_scale(pscale.MercatorLatitudeScale)
200
+ mscale.register_scale(pscale.PowerScale)
201
+ mscale.register_scale(pscale.SineLatitudeScale)
202
+ mscale.register_scale(pscale.SymmetricalLogScale)
203
+
204
+ # Cartopy projection registry and basemap default keyword args
205
+ # NOTE: Normally basemap raises error if you omit keyword args
206
+ PROJ_DEFAULTS = {
207
+ "geos": {"lon_0": 0},
208
+ "eck4": {"lon_0": 0},
209
+ "moll": {"lon_0": 0},
210
+ "hammer": {"lon_0": 0},
211
+ "kav7": {"lon_0": 0},
212
+ "sinu": {"lon_0": 0},
213
+ "vandg": {"lon_0": 0},
214
+ "mbtfpq": {"lon_0": 0},
215
+ "robin": {"lon_0": 0},
216
+ "ortho": {"lon_0": 0, "lat_0": 0},
217
+ "nsper": {"lon_0": 0, "lat_0": 0},
218
+ "aea": {"lon_0": 0, "lat_0": 90, "width": 15000e3, "height": 15000e3},
219
+ "eqdc": {"lon_0": 0, "lat_0": 90, "width": 15000e3, "height": 15000e3},
220
+ "cass": {"lon_0": 0, "lat_0": 90, "width": 15000e3, "height": 15000e3},
221
+ "gnom": {"lon_0": 0, "lat_0": 90, "width": 15000e3, "height": 15000e3},
222
+ "poly": {"lon_0": 0, "lat_0": 0, "width": 10000e3, "height": 10000e3},
223
+ "npaeqd": {"lon_0": 0, "boundinglat": 10}, # NOTE: everything breaks if you
224
+ "nplaea": {"lon_0": 0, "boundinglat": 10}, # try to set boundinglat to zero
225
+ "npstere": {"lon_0": 0, "boundinglat": 10},
226
+ "spaeqd": {"lon_0": 0, "boundinglat": -10},
227
+ "splaea": {"lon_0": 0, "boundinglat": -10},
228
+ "spstere": {"lon_0": 0, "boundinglat": -10},
229
+ "lcc": {
230
+ "lon_0": 0,
231
+ "lat_0": 40,
232
+ "lat_1": 35,
233
+ "lat_2": 45, # use cartopy defaults
234
+ "width": 20000e3,
235
+ "height": 15000e3,
236
+ },
237
+ "tmerc": {"lon_0": 0, "lat_0": 0, "width": 10000e3, "height": 10000e3},
238
+ "merc": {"llcrnrlat": -80, "urcrnrlat": 84, "llcrnrlon": -180, "urcrnrlon": 180},
239
+ "omerc": {
240
+ "lat_0": 0,
241
+ "lon_0": 0,
242
+ "lat_1": -10,
243
+ "lat_2": 10,
244
+ "lon_1": 0,
245
+ "lon_2": 0,
246
+ "width": 10000e3,
247
+ "height": 10000e3,
248
+ },
249
+ }
250
+ if ccrs is None:
251
+ PROJS = {}
252
+ else:
253
+ PROJS = {
254
+ "aitoff": pproj.Aitoff,
255
+ "hammer": pproj.Hammer,
256
+ "kav7": pproj.KavrayskiyVII,
257
+ "wintri": pproj.WinkelTripel,
258
+ "npgnom": pproj.NorthPolarGnomonic,
259
+ "spgnom": pproj.SouthPolarGnomonic,
260
+ "npaeqd": pproj.NorthPolarAzimuthalEquidistant,
261
+ "spaeqd": pproj.SouthPolarAzimuthalEquidistant,
262
+ "nplaea": pproj.NorthPolarLambertAzimuthalEqualArea,
263
+ "splaea": pproj.SouthPolarLambertAzimuthalEqualArea,
264
+ }
265
+ PROJS_MISSING = {
266
+ "aea": "AlbersEqualArea",
267
+ "aeqd": "AzimuthalEquidistant",
268
+ "cyl": "PlateCarree", # only basemap name not matching PROJ
269
+ "eck1": "EckertI",
270
+ "eck2": "EckertII",
271
+ "eck3": "EckertIII",
272
+ "eck4": "EckertIV",
273
+ "eck5": "EckertV",
274
+ "eck6": "EckertVI",
275
+ "eqc": "PlateCarree", # actual PROJ name
276
+ "eqdc": "EquidistantConic",
277
+ "eqearth": "EqualEarth", # better looking Robinson; not in basemap
278
+ "euro": "EuroPP", # Europe; not in basemap or PROJ
279
+ "geos": "Geostationary",
280
+ "gnom": "Gnomonic",
281
+ "igh": "InterruptedGoodeHomolosine", # not in basemap
282
+ "laea": "LambertAzimuthalEqualArea",
283
+ "lcc": "LambertConformal",
284
+ "lcyl": "LambertCylindrical", # not in basemap or PROJ
285
+ "merc": "Mercator",
286
+ "mill": "Miller",
287
+ "moll": "Mollweide",
288
+ "npstere": "NorthPolarStereo", # np/sp stuff not in PROJ
289
+ "nsper": "NearsidePerspective",
290
+ "ortho": "Orthographic",
291
+ "osgb": "OSGB", # UK; not in basemap or PROJ
292
+ "osni": "OSNI", # Ireland; not in basemap or PROJ
293
+ "pcarree": "PlateCarree", # common alternate name
294
+ "robin": "Robinson",
295
+ "rotpole": "RotatedPole",
296
+ "sinu": "Sinusoidal",
297
+ "spstere": "SouthPolarStereo",
298
+ "stere": "Stereographic",
299
+ "tmerc": "TransverseMercator",
300
+ "utm": "UTM", # not in basemap
301
+ }
302
+ for _key, _cls in tuple(PROJS_MISSING.items()):
303
+ if hasattr(ccrs, _cls):
304
+ PROJS[_key] = getattr(ccrs, _cls)
305
+ del PROJS_MISSING[_key]
306
+ if PROJS_MISSING:
307
+ warnings._warn_ultraplot(
308
+ "The following cartopy projection(s) are unavailable: "
309
+ + ", ".join(map(repr, PROJS_MISSING))
310
+ + " . Please consider updating cartopy."
311
+ )
312
+ PROJS_TABLE = "The known cartopy projection classes are:\n" + "\n".join(
313
+ " " + key + " " * (max(map(len, PROJS)) - len(key) + 10) + cls.__name__
314
+ for key, cls in PROJS.items()
315
+ )
316
+
317
+ # Geographic feature properties
318
+ FEATURES_CARTOPY = { # positional arguments passed to NaturalEarthFeature
319
+ "land": ("physical", "land"),
320
+ "ocean": ("physical", "ocean"),
321
+ "lakes": ("physical", "lakes"),
322
+ "coast": ("physical", "coastline"),
323
+ "rivers": ("physical", "rivers_lake_centerlines"),
324
+ "borders": ("cultural", "admin_0_boundary_lines_land"),
325
+ "innerborders": ("cultural", "admin_1_states_provinces_lakes"),
326
+ }
327
+ FEATURES_BASEMAP = { # names of relevant basemap methods
328
+ "land": "fillcontinents",
329
+ "coast": "drawcoastlines",
330
+ "rivers": "drawrivers",
331
+ "borders": "drawcountries",
332
+ "innerborders": "drawstates",
333
+ }
334
+
335
+ # Resolution names
336
+ # NOTE: Maximum basemap resolutions are much finer than cartopy
337
+ RESOS_CARTOPY = {
338
+ "lo": "110m",
339
+ "med": "50m",
340
+ "hi": "10m",
341
+ "x-hi": "10m", # extra high
342
+ "xx-hi": "10m", # extra extra high
343
+ }
344
+ RESOS_BASEMAP = {
345
+ "lo": "c", # coarse
346
+ "med": "l",
347
+ "hi": "i", # intermediate
348
+ "x-hi": "h",
349
+ "xx-hi": "f", # fine
350
+ }
351
+
352
+
353
+ def _modify_colormap(cmap, *, cut, left, right, reverse, shift, alpha, samples):
354
+ """
355
+ Modify colormap using a variety of methods.
356
+ """
357
+ if cut is not None or left is not None or right is not None:
358
+ if isinstance(cmap, pcolors.DiscreteColormap):
359
+ if cut is not None:
360
+ warnings._warn_ultraplot(
361
+ "Invalid argument 'cut' for ListedColormap. Ignoring."
362
+ )
363
+ cmap = cmap.truncate(left=left, right=right)
364
+ else:
365
+ cmap = cmap.cut(cut, left=left, right=right)
366
+ if reverse:
367
+ cmap = cmap.reversed()
368
+ if shift is not None:
369
+ cmap = cmap.shifted(shift)
370
+ if alpha is not None:
371
+ cmap = cmap.copy(alpha=alpha)
372
+ if samples is not None:
373
+ if isinstance(cmap, pcolors.DiscreteColormap):
374
+ cmap = cmap.copy(N=samples)
375
+ else:
376
+ cmap = cmap.to_discrete(samples)
377
+ return cmap
378
+
379
+
380
+ @warnings._rename_kwargs(
381
+ "0.8.0", fade="saturation", shade="luminance", to_listed="discrete"
382
+ )
383
+ def Colormap(
384
+ *args,
385
+ name=None,
386
+ listmode="perceptual",
387
+ filemode="continuous",
388
+ discrete=False,
389
+ cycle=None,
390
+ save=False,
391
+ save_kw=None,
392
+ **kwargs,
393
+ ):
394
+ """
395
+ Generate, retrieve, modify, and/or merge instances of
396
+ `~ultraplot.colors.PerceptualColormap`,
397
+ `~ultraplot.colors.ContinuousColormap`, and
398
+ `~ultraplot.colors.DiscreteColormap`.
399
+
400
+ Parameters
401
+ ----------
402
+ *args : colormap-spec
403
+ Positional arguments that individually generate colormaps. If more
404
+ than one argument is passed, the resulting colormaps are *merged* with
405
+ `~ultraplot.colors.ContinuousColormap.append`
406
+ or `~ultraplot.colors.DiscreteColormap.append`.
407
+ The arguments are interpreted as follows:
408
+
409
+ * If a registered colormap name, that colormap instance is looked up.
410
+ If colormap instance is a native matplotlib colormap class, it is
411
+ converted to a ultraplot colormap class.
412
+ * If a filename string with valid extension, the colormap data
413
+ is loaded with `ultraplot.colors.ContinuousColormap.from_file` or
414
+ `ultraplot.colors.DiscreteColormap.from_file` depending on the value of
415
+ `filemode` (see below). Default behavior is to load a
416
+ `~ultraplot.colors.ContinuousColormap`.
417
+ * If RGB tuple or color string, a `~ultraplot.colors.PerceptualColormap`
418
+ is generated with `~ultraplot.colors.PerceptualColormap.from_color`.
419
+ If the string ends in ``'_r'``, the monochromatic map will be
420
+ *reversed*, i.e. will go from dark to light instead of light to dark.
421
+ * If sequence of RGB tuples or color strings, a
422
+ `~ultraplot.colors.DiscreteColormap`, `~ultraplot.colors.PerceptualColormap`,
423
+ or `~ultraplot.colors.ContinuousColormap` is generated depending on
424
+ the value of `listmode` (see below). Default behavior is to generate a
425
+ `~ultraplot.colors.PerceptualColormap`.
426
+ * If dictionary, a `~ultraplot.colors.PerceptualColormap` is
427
+ generated with `~ultraplot.colors.PerceptualColormap.from_hsl`.
428
+ The dictionary should contain the keys ``'hue'``, ``'saturation'``,
429
+ ``'luminance'``, and optionally ``'alpha'``, or their aliases (see below).
430
+
431
+ name : str, optional
432
+ Name under which the final colormap is registered. It can
433
+ then be reused by passing ``cmap='name'`` to plotting
434
+ functions. Names with leading underscores are ignored.
435
+ filemode : {'perceptual', 'continuous', 'discrete'}, optional
436
+ Controls how colormaps are generated when you input list(s) of colors.
437
+ The options are as follows:
438
+
439
+ * If ``'perceptual'`` or ``'continuous'``, a colormap is generated using
440
+ `~ultraplot.colors.ContinuousColormap.from_file`. The resulting
441
+ colormap may be a `~ultraplot.colors.ContinuousColormap` or
442
+ `~ultraplot.colors.PerceptualColormap` depending on the data file.
443
+ * If ``'discrete'``, a `~ultraplot.colors.DiscreteColormap` is generated
444
+ using `~ultraplot.colors.ContinuousColormap.from_file`.
445
+
446
+ Default is ``'continuous'`` when calling `Colormap` directly and
447
+ ``'discrete'`` when `Colormap` is called by `Cycle`.
448
+ listmode : {'perceptual', 'continuous', 'discrete'}, optional
449
+ Controls how colormaps are generated when you input sequence(s)
450
+ of colors. The options are as follows:
451
+
452
+ * If ``'perceptual'``, a `~ultraplot.colors.PerceptualColormap`
453
+ is generated with `~ultraplot.colors.PerceptualColormap.from_list`.
454
+ * If ``'continuous'``, a `~ultraplot.colors.ContinuousColormap` is
455
+ generated with `~ultraplot.colors.ContinuousColormap.from_list`.
456
+ * If ``'discrete'``, a `~ultraplot.colors.DiscreteColormap` is generated
457
+ by simply passing the colors to the class.
458
+
459
+ Default is ``'perceptual'`` when calling `Colormap` directly and
460
+ ``'discrete'`` when `Colormap` is called by `Cycle`.
461
+ samples : int or sequence of int, optional
462
+ For `~ultraplot.colors.ContinuousColormap`\\ s, this is used to
463
+ generate `~ultraplot.colors.DiscreteColormap`\\ s with
464
+ `~ultraplot.colors.ContinuousColormap.to_discrete`. For
465
+ `~ultraplot.colors.DiscreteColormap`\\ s, this is used to updates the
466
+ number of colors in the cycle. If `samples` is integer, it applies
467
+ to the final *merged* colormap. If it is a sequence of integers,
468
+ it applies to each input colormap individually.
469
+ discrete : bool, optional
470
+ If ``True``, when the final colormap is a
471
+ `~ultraplot.colors.DiscreteColormap`, we leave it alone, but when it is a
472
+ `~ultraplot.colors.ContinuousColormap`, we always call
473
+ `~ultraplot.colors.ContinuousColormap.to_discrete` with a
474
+ default `samples` value of ``10``. This argument is not
475
+ necessary if you provide the `samples` argument.
476
+ left, right : float or sequence of float, optional
477
+ Truncate the left or right edges of the colormap.
478
+ Passed to `~ultraplot.colors.ContinuousColormap.truncate`.
479
+ If float, these apply to the final *merged* colormap. If sequence
480
+ of float, these apply to each input colormap individually.
481
+ cut : float or sequence of float, optional
482
+ Cut out the center of the colormap. Passed to
483
+ `~ultraplot.colors.ContinuousColormap.cut`. If float,
484
+ this applies to the final *merged* colormap. If sequence of
485
+ float, these apply to each input colormap individually.
486
+ reverse : bool or sequence of bool, optional
487
+ Reverse the colormap. Passed to
488
+ `~ultraplot.colors.ContinuousColormap.reversed`. If
489
+ float, this applies to the final *merged* colormap. If
490
+ sequence of float, these apply to each input colormap individually.
491
+ shift : float or sequence of float, optional
492
+ Cyclically shift the colormap.
493
+ Passed to `~ultraplot.colors.ContinuousColormap.shifted`.
494
+ If float, this applies to the final *merged* colormap. If sequence
495
+ of float, these apply to each input colormap individually.
496
+ a
497
+ Shorthand for `alpha`.
498
+ alpha : float or color-spec or sequence, optional
499
+ The opacity of the colormap or the opacity gradation. Passed to
500
+ `ultraplot.colors.ContinuousColormap.set_alpha`
501
+ or `ultraplot.colors.DiscreteColormap.set_alpha`. If float, this applies
502
+ to the final *merged* colormap. If sequence of float, these apply to
503
+ each colormap individually.
504
+ h, s, l, c
505
+ Shorthands for `hue`, `luminance`, `saturation`, and `chroma`.
506
+ hue, saturation, luminance : float or color-spec or sequence, optional
507
+ The channel value(s) used to generate colormaps with
508
+ `~ultraplot.colors.PerceptualColormap.from_hsl` and
509
+ `~ultraplot.colors.PerceptualColormap.from_color`.
510
+
511
+ * If you provided no positional arguments, these are used to create
512
+ an arbitrary perceptually uniform colormap with
513
+ `~ultraplot.colors.PerceptualColormap.from_hsl`. This
514
+ is an alternative to passing a dictionary as a positional argument
515
+ with `hue`, `saturation`, and `luminance` as dictionary keys (see `args`).
516
+ * If you did provide positional arguments, and any of them are
517
+ color specifications, these control the look of monochromatic colormaps
518
+ generated with `~ultraplot.colors.PerceptualColormap.from_color`.
519
+ To use different values for each colormap, pass a sequence of floats
520
+ instead of a single float. Note the default `luminance` is ``90`` if
521
+ `discrete` is ``True`` and ``100`` otherwise.
522
+
523
+ chroma
524
+ Alias for `saturation`.
525
+ cycle : str, optional
526
+ The registered cycle name used to interpret color strings like ``'C0'``
527
+ and ``'C2'``. Default is from the active property :rcraw:`cycle`. This lets
528
+ you make monochromatic colormaps using colors selected from arbitrary cycles.
529
+ save : bool, optional
530
+ Whether to call the colormap/color cycle save method, i.e.
531
+ `ultraplot.colors.ContinuousColormap.save` or
532
+ `ultraplot.colors.DiscreteColormap.save`.
533
+ save_kw : dict-like, optional
534
+ Ignored if `save` is ``False``. Passed to the colormap/color cycle
535
+ save method, i.e. `ultraplot.colors.ContinuousColormap.save` or
536
+ `ultraplot.colors.DiscreteColormap.save`.
537
+
538
+ Other parameters
539
+ ----------------
540
+ **kwargs
541
+ Passed to `ultraplot.colors.ContinuousColormap.copy`,
542
+ `ultraplot.colors.PerceptualColormap.copy`, or
543
+ `ultraplot.colors.DiscreteColormap.copy`.
544
+
545
+ Returns
546
+ -------
547
+ matplotlib.colors.Colormap
548
+ A `~ultraplot.colors.ContinuousColormap` or
549
+ `~ultraplot.colors.DiscreteColormap` instance.
550
+
551
+ See also
552
+ --------
553
+ matplotlib.colors.Colormap
554
+ matplotlib.colors.LinearSegmentedColormap
555
+ matplotlib.colors.ListedColormap
556
+ ultraplot.constructor.Norm
557
+ ultraplot.constructor.Cycle
558
+ ultraplot.utils.get_colors
559
+ """
560
+
561
+ # Helper function
562
+ # NOTE: Very careful here! Try to support common use cases. For example
563
+ # adding opacity gradations to colormaps with Colormap('cmap', alpha=(0.5, 1))
564
+ # or sampling maps with Colormap('cmap', samples=np.linspace(0, 1, 11)) should
565
+ # be allowable.
566
+ # If *args is singleton try to preserve it.
567
+ def _pop_modification(key):
568
+ value = kwargs.pop(key, None)
569
+ if not np.iterable(value) or isinstance(value, str):
570
+ values = (None,) * len(args)
571
+ elif len(args) == len(value):
572
+ values, value = tuple(value), None
573
+ elif len(args) == 1: # e.g. Colormap('cmap', alpha=(0.5, 1))
574
+ values = (None,)
575
+ else:
576
+ raise ValueError(
577
+ f"Got {len(args)} colormap-specs "
578
+ f"but {len(value)} values for {key!r}."
579
+ )
580
+ return value, values
581
+
582
+ # Parse keyword args that can apply to the merged colormap or each one
583
+ hsla = _pop_props(kwargs, "hsla")
584
+ if not args and hsla.keys() - {"alpha"}:
585
+ args = (hsla,)
586
+ else:
587
+ kwargs.update(hsla)
588
+ default_luminance = kwargs.pop("default_luminance", None) # used internally
589
+ cut, cuts = _pop_modification("cut")
590
+ left, lefts = _pop_modification("left")
591
+ right, rights = _pop_modification("right")
592
+ shift, shifts = _pop_modification("shift")
593
+ reverse, reverses = _pop_modification("reverse")
594
+ samples, sampless = _pop_modification("samples")
595
+ alpha, alphas = _pop_modification("alpha")
596
+ luminance, luminances = _pop_modification("luminance")
597
+ saturation, saturations = _pop_modification("saturation")
598
+ if luminance is not None:
599
+ luminances = (luminance,) * len(args)
600
+ if saturation is not None:
601
+ saturations = (saturation,) * len(args)
602
+
603
+ # Issue warnings and errors
604
+ if not args:
605
+ raise ValueError(
606
+ "Colormap() requires either positional arguments or "
607
+ "'hue', 'chroma', 'saturation', and/or 'luminance' keywords."
608
+ )
609
+ deprecated = {"listed": "discrete", "linear": "continuous"}
610
+ if listmode in deprecated:
611
+ oldmode, listmode = listmode, deprecated[listmode]
612
+ warnings._warn_ultraplot(
613
+ f"Please use listmode={listmode!r} instead of listmode={oldmode!r}."
614
+ "Option was renamed in v0.8 and will be removed in a future relase."
615
+ )
616
+ options = {"discrete", "continuous", "perceptual"}
617
+ for key, mode in zip(("listmode", "filemode"), (listmode, filemode)):
618
+ if mode not in options:
619
+ raise ValueError(
620
+ f"Invalid {key}={mode!r}. Options are: "
621
+ + ", ".join(map(repr, options))
622
+ + "."
623
+ )
624
+
625
+ # Loop through colormaps
626
+ cmaps = []
627
+ for (
628
+ arg,
629
+ icut,
630
+ ileft,
631
+ iright,
632
+ ireverse,
633
+ ishift,
634
+ isamples,
635
+ iluminance,
636
+ isaturation,
637
+ ialpha,
638
+ ) in zip( # noqa: E501
639
+ args,
640
+ cuts,
641
+ lefts,
642
+ rights,
643
+ reverses,
644
+ shifts,
645
+ sampless,
646
+ luminances,
647
+ saturations,
648
+ alphas, # noqa: E501
649
+ ):
650
+ # Load registered colormaps and maps on file
651
+ # TODO: Document how 'listmode' also affects loaded files
652
+ if isinstance(arg, str):
653
+ if "." in arg and os.path.isfile(arg):
654
+ if filemode == "discrete":
655
+ arg = pcolors.DiscreteColormap.from_file(arg)
656
+ else:
657
+ arg = pcolors.ContinuousColormap.from_file(arg)
658
+ else:
659
+ # FIXME: This error is baffling too me. Colors and colormaps
660
+ # are used interchangeable here
661
+ try:
662
+ arg = pcolors._cmap_database.get_cmap(arg)
663
+ except KeyError:
664
+ pass
665
+
666
+ # Convert matplotlib colormaps to subclasses
667
+ if isinstance(arg, mcolors.Colormap):
668
+ cmap = pcolors._translate_cmap(arg)
669
+
670
+ # Dictionary of hue/sat/luminance values or 2-tuples
671
+ elif isinstance(arg, dict):
672
+ cmap = pcolors.PerceptualColormap.from_hsl(**arg)
673
+
674
+ # List of color tuples or color strings, i.e. iterable of iterables
675
+ elif (
676
+ not isinstance(arg, str)
677
+ and np.iterable(arg)
678
+ and all(np.iterable(color) for color in arg)
679
+ ):
680
+ if listmode == "discrete":
681
+ cmap = pcolors.DiscreteColormap(arg)
682
+ elif listmode == "continuous":
683
+ cmap = pcolors.ContinuousColormap.from_list(arg)
684
+ else:
685
+ cmap = pcolors.PerceptualColormap.from_list(arg)
686
+
687
+ # Monochrome colormap from input color
688
+ # NOTE: Do not print color names in error message. Too long to be useful.
689
+ else:
690
+ jreverse = isinstance(arg, str) and arg[-2:] == "_r"
691
+ if jreverse:
692
+ arg = arg[:-2]
693
+ try:
694
+ color = to_rgba(arg, cycle=cycle)
695
+ except (ValueError, TypeError):
696
+ message = f"Invalid colormap, color cycle, or color {arg!r}."
697
+ if isinstance(arg, str) and arg[:1] != "#":
698
+ message += (
699
+ " Options include: "
700
+ + ", ".join(sorted(map(repr, pcolors._cmap_database)))
701
+ + "."
702
+ )
703
+ raise ValueError(message) from None
704
+ iluminance = _not_none(iluminance, default_luminance)
705
+ cmap = pcolors.PerceptualColormap.from_color(
706
+ color, luminance=iluminance, saturation=isaturation
707
+ )
708
+ ireverse = _not_none(ireverse, False)
709
+ ireverse = ireverse ^ jreverse # xor
710
+
711
+ # Modify the colormap
712
+ cmap = _modify_colormap(
713
+ cmap,
714
+ cut=icut,
715
+ left=ileft,
716
+ right=iright,
717
+ reverse=ireverse,
718
+ shift=ishift,
719
+ alpha=ialpha,
720
+ samples=isamples,
721
+ )
722
+ cmaps.append(cmap)
723
+
724
+ # Merge the resulting colormaps
725
+ if len(cmaps) > 1: # more than one map and modify arbitrary properties
726
+ cmap = cmaps[0].append(*cmaps[1:], **kwargs)
727
+ else:
728
+ cmap = cmaps[0].copy(**kwargs)
729
+
730
+ # Modify the colormap
731
+ if discrete and isinstance(cmap, pcolors.ContinuousColormap): # noqa: E501
732
+ samples = _not_none(samples, DEFAULT_CYCLE_SAMPLES)
733
+ cmap = _modify_colormap(
734
+ cmap,
735
+ cut=cut,
736
+ left=left,
737
+ right=right,
738
+ reverse=reverse,
739
+ shift=shift,
740
+ alpha=alpha,
741
+ samples=samples,
742
+ )
743
+
744
+ # Initialize
745
+ if not cmap._isinit:
746
+ cmap._init()
747
+
748
+ # Register the colormap
749
+ if name is None:
750
+ name = cmap.name # may have been modified by e.g. reversed()
751
+ else:
752
+ cmap.name = name
753
+ if not isinstance(name, str):
754
+ raise ValueError("The colormap name must be a string.")
755
+ pcolors._cmap_database.register(cmap, name=name)
756
+
757
+ # Save the colormap
758
+ if save:
759
+ save_kw = save_kw or {}
760
+ cmap.save(**save_kw)
761
+
762
+ return cmap
763
+
764
+
765
+ def Cycle(*args, N=None, samples=None, name=None, **kwargs):
766
+ """
767
+ Generate and merge `~cycler.Cycler` instances in a variety of ways.
768
+
769
+ Parameters
770
+ ----------
771
+ *args : colormap-spec or cycle-spec, optional
772
+ Positional arguments control the *colors* in the `~cycler.Cycler`
773
+ object. If zero arguments are passed, the single color ``'black'``
774
+ is used. If more than one argument is passed, the resulting cycles
775
+ are merged. Arguments are interpreted as follows:
776
+
777
+ * If a `~cycler.Cycler`, nothing more is done.
778
+ * If a sequence of RGB tuples or color strings, these colors are used.
779
+ * If a `~ultraplot.colors.DiscreteColormap`, colors from the ``colors``
780
+ attribute are used.
781
+ * If a string cycle name, that `~ultraplot.colors.DiscreteColormap`
782
+ is looked up and its ``colors`` are used.
783
+ * In all other cases, the argument is passed to `Colormap`, and
784
+ colors from the resulting `~ultraplot.colors.ContinuousColormap`
785
+ are used. See the `samples` argument.
786
+
787
+ If the last positional argument is numeric, it is used for the
788
+ `samples` keyword argument.
789
+ N
790
+ Shorthand for `samples`.
791
+ samples : float or sequence of float, optional
792
+ For `~ultraplot.colors.DiscreteColormap`\\ s, this is the number of
793
+ colors to select. For example, ``Cycle('538', 4)`` returns the first 4
794
+ colors of the ``'538'`` color cycle.
795
+ For `~ultraplot.colors.ContinuousColormap`\\ s, this is either a
796
+ sequence of sample coordinates used to draw colors from the colormap, or
797
+ an integer number of colors to draw. If the latter, the sample coordinates
798
+ are ``np.linspace(0, 1, samples)``. For example, ``Cycle('Reds', 5)``
799
+ divides the ``'Reds'`` colormap into five evenly spaced colors.
800
+
801
+ Other parameters
802
+ ----------------
803
+ c, color, colors : sequence of color-spec, optional
804
+ A sequence of colors passed as keyword arguments. This is equivalent
805
+ to passing a sequence of colors as the first positional argument and is
806
+ included for consistency with `~matplotlib.axes.Axes.set_prop_cycle`.
807
+ If positional arguments were passed, the colors in this list are
808
+ appended to the colors resulting from the positional arguments.
809
+ lw, ls, d, a, m, ms, mew, mec, mfc
810
+ Shorthands for the below keywords.
811
+ linewidth, linestyle, dashes, alpha, marker, markersize, markeredgewidth, \
812
+ markeredgecolor, markerfacecolor : object or sequence of object, optional
813
+ Lists of `~matplotlib.lines.Line2D` properties that can be added to the
814
+ `~cycler.Cycler` instance. If the input was already a `~cycler.Cycler`,
815
+ these are added or appended to the existing cycle keys. If the lists have
816
+ unequal length, they are repeated to their least common multiple (unlike
817
+ `~cycler.cycler`, which throws an error in this case). For more info
818
+ on cyclers see `~matplotlib.axes.Axes.set_prop_cycle`. Also see
819
+ the `line style reference \
820
+ <https://matplotlib.org/2.2.5/gallery/lines_bars_and_markers/line_styles_reference.html>`__,
821
+ the `marker reference \
822
+ <https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html>`__,
823
+ and the `custom dashes reference \
824
+ <https://matplotlib.org/stable/gallery/lines_bars_and_markers/line_demo_dash_control.html>`__.
825
+ linewidths, linestyles, dashes, alphas, markers, markersizes, markeredgewidths, \
826
+ markeredgecolors, markerfacecolors
827
+ Aliases for the above keywords.
828
+ **kwargs
829
+ If the input is not already a `~cycler.Cycler` instance, these are passed
830
+ to `Colormap` and used to build the `~ultraplot.colors.DiscreteColormap`
831
+ from which the cycler will draw its colors.
832
+
833
+ Returns
834
+ -------
835
+ cycler.Cycler
836
+ A `~cycler.Cycler` instance that can be passed
837
+ to `~matplotlib.axes.Axes.set_prop_cycle`.
838
+
839
+ See also
840
+ --------
841
+ cycler.cycler
842
+ cycler.Cycler
843
+ matplotlib.axes.Axes.set_prop_cycle
844
+ ultraplot.constructor.Colormap
845
+ ultraplot.constructor.Norm
846
+ ultraplot.utils.get_colors
847
+ """
848
+ # Parse keyword arguments that rotate through other properties
849
+ # besides color cycles.
850
+ props = _pop_props(kwargs, "line")
851
+ if "sizes" in kwargs: # special case, gets translated back by scatter()
852
+ props.setdefault("markersize", kwargs.pop("sizes"))
853
+ samples = _not_none(samples=samples, N=N) # trigger Colormap default
854
+ for key, value in tuple(props.items()): # permit in-place modification
855
+ if value is None:
856
+ return
857
+ elif not np.iterable(value) or isinstance(value, str):
858
+ value = (value,)
859
+ props[key] = list(value) # ensure mutable list
860
+
861
+ # If args is non-empty, means we want color cycle; otherwise is black
862
+ if not args:
863
+ props.setdefault("color", ["black"])
864
+ if kwargs:
865
+ warnings._warn_ultraplot(f"Ignoring Cycle() keyword arg(s) {kwargs}.")
866
+ dicts = ()
867
+
868
+ # Merge cycler objects and/or update cycler objects with input kwargs
869
+ elif all(isinstance(arg, cycler.Cycler) for arg in args):
870
+ if kwargs:
871
+ warnings._warn_ultraplot(f"Ignoring Cycle() keyword arg(s) {kwargs}.")
872
+ if len(args) == 1 and not props:
873
+ return args[0]
874
+ dicts = tuple(arg.by_key() for arg in args)
875
+
876
+ # Get a cycler from a colormap
877
+ # NOTE: Passing discrete=True does not imply default_luminance=90 because
878
+ # someone might be trying to make qualitative colormap for use in 2D plot
879
+ else:
880
+ if isinstance(args[-1], Number):
881
+ args, samples = args[:-1], _not_none(
882
+ samples_positional=args[-1], samples=samples
883
+ ) # noqa: #501
884
+ kwargs.setdefault("listmode", "discrete")
885
+ kwargs.setdefault("filemode", "discrete")
886
+ kwargs["discrete"] = True # triggers application of default 'samples'
887
+ kwargs["default_luminance"] = DEFAULT_CYCLE_LUMINANCE
888
+ cmap = Colormap(*args, name=name, samples=samples, **kwargs)
889
+ name = _not_none(name, cmap.name)
890
+ dict_ = {"color": [c if isinstance(c, str) else to_hex(c) for c in cmap.colors]}
891
+ dicts = (dict_,)
892
+
893
+ # Update the cyler property
894
+ dicts = dicts + (props,)
895
+ props = {}
896
+ for dict_ in dicts:
897
+ for key, value in dict_.items():
898
+ props.setdefault(key, []).extend(value)
899
+
900
+ # Build cycler with matching property lengths
901
+ maxlen = np.lcm.reduce([len(value) for value in props.values()])
902
+ props = {key: value * (maxlen // len(value)) for key, value in props.items()}
903
+ cycle = cycler.cycler(**props)
904
+ cycle.name = _not_none(name, "_no_name")
905
+
906
+ return cycle
907
+
908
+
909
+ def Norm(norm, *args, **kwargs):
910
+ """
911
+ Return an arbitrary `~matplotlib.colors.Normalize` instance. See this
912
+ `tutorial <https://matplotlib.org/stable/tutorials/colors/colormapnorms.html>`__
913
+ for an introduction to matplotlib normalizers.
914
+
915
+ Parameters
916
+ ----------
917
+ norm : str or `~matplotlib.colors.Normalize`
918
+ The normalizer specification. If a `~matplotlib.colors.Normalize`
919
+ instance already, a `copy.copy` of the instance is returned.
920
+ Otherwise, `norm` should be a string corresponding to one of
921
+ the "registered" colormap normalizers (see below table).
922
+
923
+ If `norm` is a list or tuple and the first element is a "registered"
924
+ normalizer name, subsequent elements are passed to the normalizer class
925
+ as positional arguments.
926
+
927
+ .. _norm_table:
928
+
929
+ =============================== =====================================
930
+ Key(s) Class
931
+ =============================== =====================================
932
+ ``'null'``, ``'none'`` `~matplotlib.colors.NoNorm`
933
+ ``'diverging'``, ``'div'`` `~ultraplot.colors.DivergingNorm`
934
+ ``'segmented'``, ``'segments'`` `~ultraplot.colors.SegmentedNorm`
935
+ ``'linear'`` `~matplotlib.colors.Normalize`
936
+ ``'log'`` `~matplotlib.colors.LogNorm`
937
+ ``'power'`` `~matplotlib.colors.PowerNorm`
938
+ ``'symlog'`` `~matplotlib.colors.SymLogNorm`
939
+ =============================== =====================================
940
+
941
+ Other parameters
942
+ ----------------
943
+ *args, **kwargs
944
+ Passed to the `~matplotlib.colors.Normalize` initializer.
945
+
946
+ Returns
947
+ -------
948
+ matplotlib.colors.Normalize
949
+ A `~matplotlib.colors.Normalize` instance.
950
+
951
+ See also
952
+ --------
953
+ matplotlib.colors.Normalize
954
+ ultraplot.colors.DiscreteNorm
955
+ ultraplot.constructor.Colormap
956
+ """
957
+ if np.iterable(norm) and not isinstance(norm, str):
958
+ norm, *args = *norm, *args
959
+ if isinstance(norm, mcolors.Normalize):
960
+ return copy.copy(norm)
961
+ if not isinstance(norm, str):
962
+ raise ValueError(f"Invalid norm name {norm!r}. Must be string.")
963
+ if norm not in NORMS:
964
+ raise ValueError(
965
+ f"Unknown normalizer {norm!r}. Options are: "
966
+ + ", ".join(map(repr, NORMS))
967
+ + "."
968
+ )
969
+ if norm == "symlog" and not args and "linthresh" not in kwargs:
970
+ kwargs["linthresh"] = 1 # special case, needs argument
971
+ return NORMS[norm](*args, **kwargs)
972
+
973
+
974
+ def Locator(locator, *args, discrete=False, **kwargs):
975
+ """
976
+ Return a `~matplotlib.ticker.Locator` instance.
977
+
978
+ Parameters
979
+ ----------
980
+ locator : `~matplotlib.ticker.Locator`, str, bool, float, or sequence
981
+ The locator specification, interpreted as follows:
982
+
983
+ * If a `~matplotlib.ticker.Locator` instance already,
984
+ a `copy.copy` of the instance is returned.
985
+ * If ``False``, a `~matplotlib.ticker.NullLocator` is used, and if
986
+ ``True``, the default `~matplotlib.ticker.AutoLocator` is used.
987
+ * If a number, this specifies the *step size* between tick locations.
988
+ Returns a `~matplotlib.ticker.MultipleLocator`.
989
+ * If a sequence of numbers, these points are ticked. Returns
990
+ a `~matplotlib.ticker.FixedLocator` by default or a
991
+ `~ultraplot.ticker.DiscreteLocator` if `discrete` is ``True``.
992
+
993
+ Otherwise, `locator` should be a string corresponding to one
994
+ of the "registered" locators (see below table). If `locator` is a
995
+ list or tuple and the first element is a "registered" locator name,
996
+ subsequent elements are passed to the locator class as positional
997
+ arguments. For example, ``pplt.Locator(('multiple', 5))`` is
998
+ equivalent to ``pplt.Locator('multiple', 5)``.
999
+
1000
+ .. _locator_table:
1001
+
1002
+ ======================= ============================================ =====================================================================================
1003
+ Key Class Description
1004
+ ======================= ============================================ =====================================================================================
1005
+ ``'null'``, ``'none'`` `~matplotlib.ticker.NullLocator` No ticks
1006
+ ``'auto'`` `~matplotlib.ticker.AutoLocator` Major ticks at sensible locations
1007
+ ``'minor'`` `~matplotlib.ticker.AutoMinorLocator` Minor ticks at sensible locations
1008
+ ``'date'`` `~matplotlib.dates.AutoDateLocator` Default tick locations for datetime axes
1009
+ ``'fixed'`` `~matplotlib.ticker.FixedLocator` Ticks at these exact locations
1010
+ ``'discrete'`` `~ultraplot.ticker.DiscreteLocator` Major ticks restricted to these locations but subsampled depending on the axis length
1011
+ ``'discreteminor'`` `~ultraplot.ticker.DiscreteLocator` Minor ticks restricted to these locations but subsampled depending on the axis length
1012
+ ``'index'`` `~ultraplot.ticker.IndexLocator` Ticks on the non-negative integers
1013
+ ``'linear'`` `~matplotlib.ticker.LinearLocator` Exactly ``N`` ticks encompassing axis limits, spaced as ``numpy.linspace(lo, hi, N)``
1014
+ ``'log'`` `~matplotlib.ticker.LogLocator` For log-scale axes
1015
+ ``'logminor'`` `~matplotlib.ticker.LogLocator` For log-scale axes on the 1st through 9th multiples of each power of the base
1016
+ ``'logit'`` `~matplotlib.ticker.LogitLocator` For logit-scale axes
1017
+ ``'logitminor'`` `~matplotlib.ticker.LogitLocator` For logit-scale axes with ``minor=True`` passed to `~matplotlib.ticker.LogitLocator`
1018
+ ``'maxn'`` `~matplotlib.ticker.MaxNLocator` No more than ``N`` ticks at sensible locations
1019
+ ``'multiple'`` `~matplotlib.ticker.MultipleLocator` Ticks every ``N`` step away from zero
1020
+ ``'symlog'`` `~matplotlib.ticker.SymmetricalLogLocator` For symlog-scale axes
1021
+ ``'symlogminor'`` `~matplotlib.ticker.SymmetricalLogLocator` For symlog-scale axes on the 1st through 9th multiples of each power of the base
1022
+ ``'theta'`` `~matplotlib.projections.polar.ThetaLocator` Like the base locator but default locations are every `numpy.pi` / 8 radians
1023
+ ``'year'`` `~matplotlib.dates.YearLocator` Ticks every ``N`` years
1024
+ ``'month'`` `~matplotlib.dates.MonthLocator` Ticks every ``N`` months
1025
+ ``'weekday'`` `~matplotlib.dates.WeekdayLocator` Ticks every ``N`` weekdays
1026
+ ``'day'`` `~matplotlib.dates.DayLocator` Ticks every ``N`` days
1027
+ ``'hour'`` `~matplotlib.dates.HourLocator` Ticks every ``N`` hours
1028
+ ``'minute'`` `~matplotlib.dates.MinuteLocator` Ticks every ``N`` minutes
1029
+ ``'second'`` `~matplotlib.dates.SecondLocator` Ticks every ``N`` seconds
1030
+ ``'microsecond'`` `~matplotlib.dates.MicrosecondLocator` Ticks every ``N`` microseconds
1031
+ ``'lon'``, ``'deglon'`` `~ultraplot.ticker.LongitudeLocator` Longitude gridlines at sensible decimal locations
1032
+ ``'lat'``, ``'deglat'`` `~ultraplot.ticker.LatitudeLocator` Latitude gridlines at sensible decimal locations
1033
+ ``'dms'`` `~ultraplot.ticker.DegreeLocator` Gridlines on nice minute and second intervals
1034
+ ``'dmslon'`` `~ultraplot.ticker.LongitudeLocator` Longitude gridlines on nice minute and second intervals
1035
+ ``'dmslat'`` `~ultraplot.ticker.LatitudeLocator` Latitude gridlines on nice minute and second intervals
1036
+ ======================= ============================================ =====================================================================================
1037
+
1038
+ Other parameters
1039
+ ----------------
1040
+ *args, **kwargs
1041
+ Passed to the `~matplotlib.ticker.Locator` class.
1042
+
1043
+ Returns
1044
+ -------
1045
+ matplotlib.ticker.Locator
1046
+ A `~matplotlib.ticker.Locator` instance.
1047
+
1048
+ See also
1049
+ --------
1050
+ matplotlib.ticker.Locator
1051
+ ultraplot.axes.CartesianAxes.format
1052
+ ultraplot.axes.PolarAxes.format
1053
+ ultraplot.axes.GeoAxes.format
1054
+ ultraplot.axes.Axes.colorbar
1055
+ ultraplot.constructor.Formatter
1056
+ """ # noqa: E501
1057
+ if (
1058
+ np.iterable(locator)
1059
+ and not isinstance(locator, str)
1060
+ and not all(isinstance(num, Number) for num in locator)
1061
+ ):
1062
+ locator, *args = *locator, *args
1063
+ if isinstance(locator, mticker.Locator):
1064
+ return copy.copy(locator)
1065
+ if isinstance(locator, str):
1066
+ if locator == "index": # defaults
1067
+ args = args or (1,)
1068
+ if len(args) == 1:
1069
+ args = (*args, 0)
1070
+ elif locator in ("logminor", "logitminor", "symlogminor"): # presets
1071
+ locator, _ = locator.split("minor")
1072
+ if locator == "logit":
1073
+ kwargs.setdefault("minor", True)
1074
+ else:
1075
+ kwargs.setdefault("subs", np.arange(1, 10))
1076
+ if locator in LOCATORS:
1077
+ locator = LOCATORS[locator](*args, **kwargs)
1078
+ else:
1079
+ raise ValueError(
1080
+ f"Unknown locator {locator!r}. Options are: "
1081
+ + ", ".join(map(repr, LOCATORS))
1082
+ + "."
1083
+ )
1084
+ elif locator is True:
1085
+ locator = mticker.AutoLocator(*args, **kwargs)
1086
+ elif locator is False:
1087
+ locator = mticker.NullLocator(*args, **kwargs)
1088
+ elif isinstance(locator, Number): # scalar variable
1089
+ locator = mticker.MultipleLocator(locator, *args, **kwargs)
1090
+ elif np.iterable(locator):
1091
+ locator = np.array(locator)
1092
+ if discrete:
1093
+ locator = pticker.DiscreteLocator(locator, *args, **kwargs)
1094
+ else:
1095
+ locator = mticker.FixedLocator(locator, *args, **kwargs)
1096
+ else:
1097
+ raise ValueError(f"Invalid locator {locator!r}.")
1098
+ return locator
1099
+
1100
+
1101
+ def Formatter(formatter, *args, date=False, index=False, **kwargs):
1102
+ """
1103
+ Return a `~matplotlib.ticker.Formatter` instance.
1104
+
1105
+ Parameters
1106
+ ----------
1107
+ formatter : `~matplotlib.ticker.Formatter`, str, bool, callable, or sequence
1108
+ The formatter specification, interpreted as follows:
1109
+
1110
+ * If a `~matplotlib.ticker.Formatter` instance already,
1111
+ a `copy.copy` of the instance is returned.
1112
+ * If ``False``, a `~matplotlib.ticker.NullFormatter` is used, and if
1113
+ ``True``, the default `~ultraplot.ticker.AutoFormatter` is used.
1114
+ * If a function, the labels will be generated using this function.
1115
+ Returns a `~matplotlib.ticker.FuncFormatter`.
1116
+ * If sequence of strings, the ticks are labeled with these strings.
1117
+ Returns a `~matplotlib.ticker.FixedFormatter` by default or
1118
+ an `~ultraplot.ticker.IndexFormatter` if `index` is ``True``.
1119
+ * If a string containing ``{x}`` or ``{x:...}``, ticks will be
1120
+ formatted by calling ``string.format(x=number)``. Returns
1121
+ a `~matplotlib.ticker.StrMethodFormatter`.
1122
+ * If a string containing ``'%'`` and `date` is ``False``, ticks
1123
+ will be formatted using the C-style ``string % number`` method. See
1124
+ `this page <https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting>`__
1125
+ for a review. Returns a `~matplotlib.ticker.FormatStrFormatter`.
1126
+ * If a string containing ``'%'`` and `date` is ``True``, ticks
1127
+ will be formatted using `~datetime.datetime.strfrtime`. See
1128
+ `this page <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes>`__
1129
+ for a review. Returns a `~matplotlib.dates.DateFormatter`.
1130
+
1131
+ Otherwise, `formatter` should be a string corresponding to one of the
1132
+ "registered" formatters or formatter presets (see below table). If
1133
+ `formatter` is a list or tuple and the first element is a "registered"
1134
+ formatter name, subsequent elements are passed to the formatter class
1135
+ as positional arguments. For example, ``pplt.Formatter(('sigfig', 3))`` is
1136
+ equivalent to ``Formatter('sigfig', 3)``.
1137
+
1138
+
1139
+ .. _tau: https://tauday.com/tau-manifesto
1140
+
1141
+ .. _formatter_table:
1142
+
1143
+ ====================== ============================================== =================================================================
1144
+ Key Class Description
1145
+ ====================== ============================================== =================================================================
1146
+ ``'null'``, ``'none'`` `~matplotlib.ticker.NullFormatter` No tick labels
1147
+ ``'auto'`` `~ultraplot.ticker.AutoFormatter` New default tick labels for axes
1148
+ ``'sci'`` `~ultraplot.ticker.SciFormatter` Format ticks with scientific notation
1149
+ ``'simple'`` `~ultraplot.ticker.SimpleFormatter` New default tick labels for e.g. contour labels
1150
+ ``'sigfig'`` `~ultraplot.ticker.SigFigFormatter` Format labels using the first ``N`` significant digits
1151
+ ``'frac'`` `~ultraplot.ticker.FracFormatter` Rational fractions
1152
+ ``'date'`` `~matplotlib.dates.AutoDateFormatter` Default tick labels for datetime axes
1153
+ ``'concise'`` `~matplotlib.dates.ConciseDateFormatter` More concise date labels introduced in matplotlib 3.1
1154
+ ``'datestr'`` `~matplotlib.dates.DateFormatter` Date formatting with C-style ``string % format`` notation
1155
+ ``'eng'`` `~matplotlib.ticker.EngFormatter` Engineering notation
1156
+ ``'fixed'`` `~matplotlib.ticker.FixedFormatter` List of strings
1157
+ ``'formatstr'`` `~matplotlib.ticker.FormatStrFormatter` From C-style ``string % format`` notation
1158
+ ``'func'`` `~matplotlib.ticker.FuncFormatter` Use an arbitrary function
1159
+ ``'index'`` `~ultraplot.ticker.IndexFormatter` List of strings corresponding to non-negative integer positions
1160
+ ``'log'`` `~matplotlib.ticker.LogFormatterSciNotation` For log-scale axes with scientific notation
1161
+ ``'logit'`` `~matplotlib.ticker.LogitFormatter` For logistic-scale axes
1162
+ ``'percent'`` `~matplotlib.ticker.PercentFormatter` Trailing percent sign
1163
+ ``'scalar'`` `~matplotlib.ticker.ScalarFormatter` The default matplotlib formatter
1164
+ ``'strmethod'`` `~matplotlib.ticker.StrMethodFormatter` From the ``string.format`` method
1165
+ ``'theta'`` `~matplotlib.projections.polar.ThetaFormatter` Formats radians as degrees, with a degree symbol
1166
+ ``'e'`` `~ultraplot.ticker.FracFormatter` preset Fractions of *e*
1167
+ ``'pi'`` `~ultraplot.ticker.FracFormatter` preset Fractions of :math:`\\pi`
1168
+ ``'tau'`` `~ultraplot.ticker.FracFormatter` preset Fractions of the `one true circle constant <tau_>`_ :math:`\\tau`
1169
+ ``'lat'`` `~ultraplot.ticker.AutoFormatter` preset Cardinal "SN" indicator
1170
+ ``'lon'`` `~ultraplot.ticker.AutoFormatter` preset Cardinal "WE" indicator
1171
+ ``'deg'`` `~ultraplot.ticker.AutoFormatter` preset Trailing degree symbol
1172
+ ``'deglat'`` `~ultraplot.ticker.AutoFormatter` preset Trailing degree symbol and cardinal "SN" indicator
1173
+ ``'deglon'`` `~ultraplot.ticker.AutoFormatter` preset Trailing degree symbol and cardinal "WE" indicator
1174
+ ``'dms'`` `~ultraplot.ticker.DegreeFormatter` Labels with degree/minute/second support
1175
+ ``'dmslon'`` `~ultraplot.ticker.LongitudeFormatter` Longitude labels with degree/minute/second support
1176
+ ``'dmslat'`` `~ultraplot.ticker.LatitudeFormatter` Latitude labels with degree/minute/second support
1177
+ ====================== ============================================== =================================================================
1178
+
1179
+ date : bool, optional
1180
+ Toggles the behavior when `formatter` contains a ``'%'`` sign
1181
+ (see above).
1182
+ index : bool, optional
1183
+ Controls the behavior when `formatter` is a sequence of strings
1184
+ (see above).
1185
+
1186
+ Other parameters
1187
+ ----------------
1188
+ *args, **kwargs
1189
+ Passed to the `~matplotlib.ticker.Formatter` class.
1190
+
1191
+ Returns
1192
+ -------
1193
+ matplotlib.ticker.Formatter
1194
+ A `~matplotlib.ticker.Formatter` instance.
1195
+
1196
+ See also
1197
+ --------
1198
+ matplotlib.ticker.Formatter
1199
+ ultraplot.axes.CartesianAxes.format
1200
+ ultraplot.axes.PolarAxes.format
1201
+ ultraplot.axes.GeoAxes.format
1202
+ ultraplot.axes.Axes.colorbar
1203
+ ultraplot.constructor.Locator
1204
+ """ # noqa: E501
1205
+ if (
1206
+ np.iterable(formatter)
1207
+ and not isinstance(formatter, str)
1208
+ and not all(isinstance(item, str) for item in formatter)
1209
+ ):
1210
+ formatter, *args = *formatter, *args
1211
+ if isinstance(formatter, mticker.Formatter):
1212
+ return copy.copy(formatter)
1213
+ if isinstance(formatter, str):
1214
+ if re.search(r"{x(:.+)?}", formatter): # str.format
1215
+ formatter = mticker.StrMethodFormatter(formatter, *args, **kwargs)
1216
+ elif "%" in formatter: # str % format
1217
+ cls = mdates.DateFormatter if date else mticker.FormatStrFormatter
1218
+ formatter = cls(formatter, *args, **kwargs)
1219
+ elif formatter in FORMATTERS:
1220
+ formatter = FORMATTERS[formatter](*args, **kwargs)
1221
+ else:
1222
+ raise ValueError(
1223
+ f"Unknown formatter {formatter!r}. Options are: "
1224
+ + ", ".join(map(repr, FORMATTERS))
1225
+ + "."
1226
+ )
1227
+ elif formatter is True:
1228
+ formatter = pticker.AutoFormatter(*args, **kwargs)
1229
+ elif formatter is False:
1230
+ formatter = mticker.NullFormatter(*args, **kwargs)
1231
+ elif np.iterable(formatter):
1232
+ formatter = (mticker.FixedFormatter, pticker.IndexFormatter)[index](formatter)
1233
+ elif callable(formatter):
1234
+ formatter = mticker.FuncFormatter(formatter, *args, **kwargs)
1235
+ else:
1236
+ raise ValueError(f"Invalid formatter {formatter!r}.")
1237
+ return formatter
1238
+
1239
+
1240
+ def Scale(scale, *args, **kwargs):
1241
+ """
1242
+ Return a `~matplotlib.scale.ScaleBase` instance.
1243
+
1244
+ Parameters
1245
+ ----------
1246
+ scale : `~matplotlib.scale.ScaleBase`, str, or tuple
1247
+ The axis scale specification. If a `~matplotlib.scale.ScaleBase` instance
1248
+ already, a `copy.copy` of the instance is returned. Otherwise, `scale`
1249
+ should be a string corresponding to one of the "registered" axis scales
1250
+ or axis scale presets (see below table).
1251
+
1252
+ If `scale` is a list or tuple and the first element is a
1253
+ "registered" scale name, subsequent elements are passed to the
1254
+ scale class as positional arguments.
1255
+
1256
+ .. _scale_table:
1257
+
1258
+ ================= ====================================== ===============================================
1259
+ Key Class Description
1260
+ ================= ====================================== ===============================================
1261
+ ``'linear'`` `~ultraplot.scale.LinearScale` Linear
1262
+ ``'log'`` `~ultraplot.scale.LogScale` Logarithmic
1263
+ ``'symlog'`` `~ultraplot.scale.SymmetricalLogScale` Logarithmic beyond finite space around zero
1264
+ ``'logit'`` `~ultraplot.scale.LogitScale` Logistic
1265
+ ``'inverse'`` `~ultraplot.scale.InverseScale` Inverse
1266
+ ``'function'`` `~ultraplot.scale.FuncScale` Arbitrary forward and backwards transformations
1267
+ ``'sine'`` `~ultraplot.scale.SineLatitudeScale` Sine function (in degrees)
1268
+ ``'mercator'`` `~ultraplot.scale.MercatorLatitudeScale` Mercator latitude function (in degrees)
1269
+ ``'exp'`` `~ultraplot.scale.ExpScale` Arbitrary exponential function
1270
+ ``'power'`` `~ultraplot.scale.PowerScale` Arbitrary power function
1271
+ ``'cutoff'`` `~ultraplot.scale.CutoffScale` Arbitrary piecewise linear transformations
1272
+ ``'quadratic'`` `~ultraplot.scale.PowerScale` (preset) Quadratic function
1273
+ ``'cubic'`` `~ultraplot.scale.PowerScale` (preset) Cubic function
1274
+ ``'quartic'`` `~ultraplot.scale.PowerScale` (preset) Quartic function
1275
+ ``'db'`` `~ultraplot.scale.ExpScale` (preset) Ratio expressed as `decibels <db_>`_
1276
+ ``'np'`` `~ultraplot.scale.ExpScale` (preset) Ratio expressed as `nepers <np_>`_
1277
+ ``'idb'`` `~ultraplot.scale.ExpScale` (preset) `Decibels <db_>`_ expressed as ratio
1278
+ ``'inp'`` `~ultraplot.scale.ExpScale` (preset) `Nepers <np_>`_ expressed as ratio
1279
+ ``'pressure'`` `~ultraplot.scale.ExpScale` (preset) Height (in km) expressed linear in pressure
1280
+ ``'height'`` `~ultraplot.scale.ExpScale` (preset) Pressure (in hPa) expressed linear in height
1281
+ ================= ====================================== ===============================================
1282
+
1283
+ .. _db: https://en.wikipedia.org/wiki/Decibel
1284
+ .. _np: https://en.wikipedia.org/wiki/Neper
1285
+
1286
+ Other parameters
1287
+ ----------------
1288
+ *args, **kwargs
1289
+ Passed to the `~matplotlib.scale.ScaleBase` class.
1290
+
1291
+ Returns
1292
+ -------
1293
+ matplotlib.scale.ScaleBase
1294
+ A `~matplotlib.scale.ScaleBase` instance.
1295
+
1296
+ See also
1297
+ --------
1298
+ matplotlib.scale.ScaleBase
1299
+ ultraplot.scale.LinearScale
1300
+ ultraplot.axes.CartesianAxes.format
1301
+ ultraplot.axes.CartesianAxes.dualx
1302
+ ultraplot.axes.CartesianAxes.dualy
1303
+ """ # noqa: E501
1304
+ # NOTE: Why not try to interpret FuncScale arguments, like when lists
1305
+ # of numbers are passed to Locator? Because FuncScale *itself* accepts
1306
+ # ScaleBase classes as arguments... but constructor functions cannot
1307
+ # do anything but return the class instance upon receiving one.
1308
+ if np.iterable(scale) and not isinstance(scale, str):
1309
+ scale, *args = *scale, *args
1310
+ if isinstance(scale, mscale.ScaleBase):
1311
+ return copy.copy(scale)
1312
+ if not isinstance(scale, str):
1313
+ raise ValueError(f"Invalid scale name {scale!r}. Must be string.")
1314
+ scale = scale.lower()
1315
+ if scale in SCALES_PRESETS:
1316
+ if args or kwargs:
1317
+ warnings._warn_ultraplot(
1318
+ f"Scale {scale!r} is a scale *preset*. Ignoring positional "
1319
+ "argument(s): {args} and keyword argument(s): {kwargs}. "
1320
+ )
1321
+ scale, *args = SCALES_PRESETS[scale]
1322
+ if scale in SCALES:
1323
+ scale = SCALES[scale]
1324
+ else:
1325
+ raise ValueError(
1326
+ f"Unknown scale or preset {scale!r}. Options are: "
1327
+ + ", ".join(map(repr, (*SCALES, *SCALES_PRESETS)))
1328
+ + "."
1329
+ )
1330
+ return scale(*args, **kwargs)
1331
+
1332
+
1333
+ def Proj(
1334
+ name,
1335
+ backend=None,
1336
+ lon0=None,
1337
+ lon_0=None,
1338
+ lat0=None,
1339
+ lat_0=None,
1340
+ lonlim=None,
1341
+ latlim=None,
1342
+ **kwargs,
1343
+ ):
1344
+ """
1345
+ Return a `cartopy.crs.Projection` or `~mpl_toolkits.basemap.Basemap` instance.
1346
+
1347
+ Parameters
1348
+ ----------
1349
+ name : str, `cartopy.crs.Projection`, or `~mpl_toolkits.basemap.Basemap`
1350
+ The projection name or projection class instance. If the latter, it
1351
+ is simply returned. If the former, it must correspond to one of the
1352
+ `PROJ <https://proj.org>`__ projection name shorthands, like in
1353
+ basemap.
1354
+
1355
+ The following table lists the valid projection name shorthands,
1356
+ their full names (with links to the relevant `PROJ documentation
1357
+ <https://proj.org/operations/projections>`__),
1358
+ and whether they are available in the cartopy and basemap packages.
1359
+ (added) indicates a projection class that ultraplot has "added" to
1360
+ cartopy using the cartopy API.
1361
+
1362
+ .. _proj_table:
1363
+
1364
+ ============= =============================================== ========= =======
1365
+ Key Name Cartopy Basemap
1366
+ ============= =============================================== ========= =======
1367
+ ``'aea'`` `Albers Equal Area <aea_>`_ ✓ ✓
1368
+ ``'aeqd'`` `Azimuthal Equidistant <aeqd_>`_ ✓ ✓
1369
+ ``'aitoff'`` `Aitoff <aitoff_>`_ ✓ (added) ✗
1370
+ ``'cass'`` `Cassini-Soldner <cass_>`_ ✗ ✓
1371
+ ``'cea'`` `Cylindrical Equal Area <cea_>`_ ✗ ✓
1372
+ ``'cyl'`` `Cylindrical Equidistant <eqc_>`_ ✓ ✓
1373
+ ``'eck1'`` `Eckert I <eck1_>`_ ✓ ✗
1374
+ ``'eck2'`` `Eckert II <eck2_>`_ ✓ ✗
1375
+ ``'eck3'`` `Eckert III <eck3_>`_ ✓ ✗
1376
+ ``'eck4'`` `Eckert IV <eck4_>`_ ✓ ✓
1377
+ ``'eck5'`` `Eckert V <eck5_>`_ ✓ ✗
1378
+ ``'eck6'`` `Eckert VI <eck6_>`_ ✓ ✗
1379
+ ``'eqdc'`` `Equidistant Conic <eqdc_>`_ ✓ ✓
1380
+ ``'eqc'`` `Cylindrical Equidistant <eqc_>`_ ✓ ✓
1381
+ ``'eqearth'`` `Equal Earth <eqearth_>`_ ✓ ✗
1382
+ ``'europp'`` Euro PP (Europe) ✓ ✗
1383
+ ``'gall'`` `Gall Stereographic Cylindrical <gall_>`_ ✗ ✓
1384
+ ``'geos'`` `Geostationary <geos_>`_ ✓ ✓
1385
+ ``'gnom'`` `Gnomonic <gnom_>`_ ✓ ✓
1386
+ ``'hammer'`` `Hammer <hammer_>`_ ✓ (added) ✓
1387
+ ``'igh'`` `Interrupted Goode Homolosine <igh_>`_ ✓ ✗
1388
+ ``'kav7'`` `Kavrayskiy VII <kav7_>`_ ✓ (added) ✓
1389
+ ``'laea'`` `Lambert Azimuthal Equal Area <laea_>`_ ✓ ✓
1390
+ ``'lcc'`` `Lambert Conformal <lcc_>`_ ✓ ✓
1391
+ ``'lcyl'`` Lambert Cylindrical ✓ ✗
1392
+ ``'mbtfpq'`` `McBryde-Thomas Flat-Polar Quartic <mbtfpq_>`_ ✗ ✓
1393
+ ``'merc'`` `Mercator <merc_>`_ ✓ ✓
1394
+ ``'mill'`` `Miller Cylindrical <mill_>`_ ✓ ✓
1395
+ ``'moll'`` `Mollweide <moll_>`_ ✓ ✓
1396
+ ``'npaeqd'`` North-Polar Azimuthal Equidistant ✓ (added) ✓
1397
+ ``'npgnom'`` North-Polar Gnomonic ✓ (added) ✗
1398
+ ``'nplaea'`` North-Polar Lambert Azimuthal ✓ (added) ✓
1399
+ ``'npstere'`` North-Polar Stereographic ✓ ✓
1400
+ ``'nsper'`` `Near-Sided Perspective <nsper_>`_ ✓ ✓
1401
+ ``'osni'`` OSNI (Ireland) ✓ ✗
1402
+ ``'osgb'`` OSGB (UK) ✓ ✗
1403
+ ``'omerc'`` `Oblique Mercator <omerc_>`_ ✗ ✓
1404
+ ``'ortho'`` `Orthographic <ortho_>`_ ✓ ✓
1405
+ ``'pcarree'`` `Cylindrical Equidistant <eqc_>`_ ✓ ✓
1406
+ ``'poly'`` `Polyconic <poly_>`_ ✗ ✓
1407
+ ``'rotpole'`` Rotated Pole ✓ ✓
1408
+ ``'sinu'`` `Sinusoidal <sinu_>`_ ✓ ✓
1409
+ ``'spaeqd'`` South-Polar Azimuthal Equidistant ✓ (added) ✓
1410
+ ``'spgnom'`` South-Polar Gnomonic ✓ (added) ✗
1411
+ ``'splaea'`` South-Polar Lambert Azimuthal ✓ (added) ✓
1412
+ ``'spstere'`` South-Polar Stereographic ✓ ✓
1413
+ ``'stere'`` `Stereographic <stere_>`_ ✓ ✓
1414
+ ``'tmerc'`` `Transverse Mercator <tmerc_>`_ ✓ ✓
1415
+ ``'utm'`` `Universal Transverse Mercator <utm_>`_ ✓ ✗
1416
+ ``'vandg'`` `van der Grinten <vandg_>`_ ✗ ✓
1417
+ ``'wintri'`` `Winkel tripel <wintri_>`_ ✓ (added) ✗
1418
+ ============= =============================================== ========= =======
1419
+
1420
+ backend : {'cartopy', 'basemap'}, default: :rc:`geo.backend`
1421
+ Whether to return a cartopy `~cartopy.crs.Projection` instance
1422
+ or a basemap `~mpl_toolkits.basemap.Basemap` instance.
1423
+ lon0, lat0 : float, optional
1424
+ The central projection longitude and latitude. These are translated to
1425
+ `central_longitude`, `central_latitude` for cartopy projections.
1426
+ lon_0, lat_0 : float, optional
1427
+ Aliases for `lon0`, `lat0`.
1428
+ lonlim : 2-tuple of float, optional
1429
+ The longitude limits. Translated to `min_longitude` and `max_longitude` for
1430
+ cartopy projections and `llcrnrlon` and `urcrnrlon` for basemap projections.
1431
+ latlim : 2-tuple of float, optional
1432
+ The latitude limits. Translated to `min_latitude` and `max_latitude` for
1433
+ cartopy projections and `llcrnrlon` and `urcrnrlon` for basemap projections.
1434
+
1435
+ Other parameters
1436
+ ----------------
1437
+ **kwargs
1438
+ Passed to the cartopy `~cartopy.crs.Projection` or
1439
+ basemap `~mpl_toolkits.basemap.Basemap` class.
1440
+
1441
+ Returns
1442
+ -------
1443
+ proj : mpl_toolkits.basemap.Basemap or cartopy.crs.Projection
1444
+ A cartopy or basemap projection instance.
1445
+
1446
+ See also
1447
+ --------
1448
+ mpl_toolkits.basemap.Basemap
1449
+ cartopy.crs.Projection
1450
+ ultraplot.ui.subplots
1451
+ ultraplot.axes.GeoAxes
1452
+
1453
+ References
1454
+ ----------
1455
+ For more information on map projections, see the
1456
+ `wikipedia page <https://en.wikipedia.org/wiki/Map_projection>`__ and the
1457
+ `PROJ <https://proj.org>`__ documentation.
1458
+
1459
+ .. _aea: https://proj.org/operations/projections/aea.html
1460
+ .. _aeqd: https://proj.org/operations/projections/aeqd.html
1461
+ .. _aitoff: https://proj.org/operations/projections/aitoff.html
1462
+ .. _cass: https://proj.org/operations/projections/cass.html
1463
+ .. _cea: https://proj.org/operations/projections/cea.html
1464
+ .. _eqc: https://proj.org/operations/projections/eqc.html
1465
+ .. _eck1: https://proj.org/operations/projections/eck1.html
1466
+ .. _eck2: https://proj.org/operations/projections/eck2.html
1467
+ .. _eck3: https://proj.org/operations/projections/eck3.html
1468
+ .. _eck4: https://proj.org/operations/projections/eck4.html
1469
+ .. _eck5: https://proj.org/operations/projections/eck5.html
1470
+ .. _eck6: https://proj.org/operations/projections/eck6.html
1471
+ .. _eqdc: https://proj.org/operations/projections/eqdc.html
1472
+ .. _eqc: https://proj.org/operations/projections/eqc.html
1473
+ .. _eqearth: https://proj.org/operations/projections/eqearth.html
1474
+ .. _gall: https://proj.org/operations/projections/gall.html
1475
+ .. _geos: https://proj.org/operations/projections/geos.html
1476
+ .. _gnom: https://proj.org/operations/projections/gnom.html
1477
+ .. _hammer: https://proj.org/operations/projections/hammer.html
1478
+ .. _igh: https://proj.org/operations/projections/igh.html
1479
+ .. _kav7: https://proj.org/operations/projections/kav7.html
1480
+ .. _laea: https://proj.org/operations/projections/laea.html
1481
+ .. _lcc: https://proj.org/operations/projections/lcc.html
1482
+ .. _mbtfpq: https://proj.org/operations/projections/mbtfpq.html
1483
+ .. _merc: https://proj.org/operations/projections/merc.html
1484
+ .. _mill: https://proj.org/operations/projections/mill.html
1485
+ .. _moll: https://proj.org/operations/projections/moll.html
1486
+ .. _nsper: https://proj.org/operations/projections/nsper.html
1487
+ .. _omerc: https://proj.org/operations/projections/omerc.html
1488
+ .. _ortho: https://proj.org/operations/projections/ortho.html
1489
+ .. _eqc: https://proj.org/operations/projections/eqc.html
1490
+ .. _poly: https://proj.org/operations/projections/poly.html
1491
+ .. _sinu: https://proj.org/operations/projections/sinu.html
1492
+ .. _stere: https://proj.org/operations/projections/stere.html
1493
+ .. _tmerc: https://proj.org/operations/projections/tmerc.html
1494
+ .. _utm: https://proj.org/operations/projections/utm.html
1495
+ .. _vandg: https://proj.org/operations/projections/vandg.html
1496
+ .. _wintri: https://proj.org/operations/projections/wintri.html
1497
+ """ # noqa: E501
1498
+ # Parse input arguments
1499
+ # NOTE: Underscores are permitted for consistency with cartopy only here.
1500
+ # In format() underscores are not allowed for constistency with reset of API.
1501
+ lon0 = _not_none(lon0=lon0, lon_0=lon_0)
1502
+ lat0 = _not_none(lat0=lat0, lat_0=lat_0)
1503
+ lonlim = _not_none(lonlim, default=(None, None))
1504
+ latlim = _not_none(latlim, default=(None, None))
1505
+ is_crs = Projection is not object and isinstance(name, Projection)
1506
+ is_basemap = Basemap is not object and isinstance(name, Basemap)
1507
+ include_axes = kwargs.pop("include_axes", False) # for error message
1508
+ if backend is not None and backend not in ("cartopy", "basemap"):
1509
+ raise ValueError(
1510
+ f"Invalid backend={backend!r}. Options are 'cartopy' or 'basemap'."
1511
+ )
1512
+ if not is_crs and not is_basemap:
1513
+ backend = _not_none(backend, rc["geo.backend"])
1514
+ if not isinstance(name, str):
1515
+ raise ValueError(
1516
+ f"Unexpected projection {name!r}. Must be PROJ string name, "
1517
+ "cartopy.crs.Projection, or mpl_toolkits.basemap.Basemap."
1518
+ )
1519
+ for key_proj, key_cartopy, value in (
1520
+ ("lon_0", "central_longitude", lon0),
1521
+ ("lat_0", "central_latitude", lat0),
1522
+ ("llcrnrlon", "min_longitude", lonlim[0]),
1523
+ ("urcrnrlon", "max_longitude", lonlim[1]),
1524
+ ("llcrnrlat", "min_latitude", latlim[0]),
1525
+ ("urcrnrlat", "max_latitude", latlim[1]),
1526
+ ):
1527
+ if value is None:
1528
+ continue
1529
+ if backend == "basemap" and key_proj == "lon_0" and value > 0:
1530
+ value -= 360 # see above comment
1531
+ kwargs[key_proj if backend == "basemap" else key_cartopy] = value
1532
+
1533
+ # Projection instances
1534
+ if is_crs or is_basemap:
1535
+ if backend is not None:
1536
+ kwargs["backend"] = backend
1537
+ if kwargs:
1538
+ warnings._warn_ultraplot(f"Ignoring Proj() keyword arg(s): {kwargs!r}.")
1539
+ proj = name
1540
+ backend = "cartopy" if is_crs else "basemap"
1541
+
1542
+ # Cartopy name
1543
+ # NOTE: Error message matches basemap invalid projection message
1544
+ elif backend == "cartopy":
1545
+ # Parse keywoard arguments
1546
+ import cartopy # ensure present # noqa: F401
1547
+
1548
+ for key in ("round", "boundinglat"):
1549
+ value = kwargs.pop(key, None)
1550
+ if value is not None:
1551
+ raise ValueError(
1552
+ "Ignoring Proj() keyword {key}={value!r}. Must be passed "
1553
+ "to GeoAxes.format() when cartopy is the backend."
1554
+ )
1555
+
1556
+ # Retrieve projection and initialize with nice error message
1557
+ try:
1558
+ crs = PROJS[name]
1559
+ except KeyError:
1560
+ message = f"{name!r} is an unknown cartopy projection class.\n"
1561
+ message += "The known cartopy projection classes are:\n"
1562
+ message += "\n".join(
1563
+ " " + key + " " * (max(map(len, PROJS)) - len(key) + 10) + cls.__name__
1564
+ for key, cls in PROJS.items()
1565
+ )
1566
+ if include_axes:
1567
+ from . import axes as paxes # avoid circular imports
1568
+
1569
+ message = message.replace("class.", "class or axes subclass.")
1570
+ message += "\nThe known axes subclasses are:\n" + paxes._cls_table
1571
+ raise ValueError(message) from None
1572
+ if name == "geos": # fix common mistake
1573
+ kwargs.pop("central_latitude", None)
1574
+ proj = crs(**kwargs)
1575
+
1576
+ # Basemap name
1577
+ # NOTE: Known issue that basemap sometimes produces backwards maps:
1578
+ # https://stackoverflow.com/q/56299971/4970632
1579
+ # NOTE: We set rsphere to fix non-conda installed basemap issue:
1580
+ # https://github.com/matplotlib/basemap/issues/361
1581
+ # NOTE: Adjust lon_0 to fix issues with Robinson (and related?) projections
1582
+ # https://stackoverflow.com/questions/56299971/ (also triggers 'no room for axes')
1583
+ # NOTE: Unlike cartopy, basemap resolution is configured
1584
+ # on initialization and controls *all* features.
1585
+ else:
1586
+ # Parse input arguments
1587
+ from mpl_toolkits import basemap # ensure present # noqa: F401
1588
+
1589
+ if name in ("eqc", "pcarree"):
1590
+ name = "cyl" # PROJ package aliases
1591
+ defaults = {"fix_aspect": True, **PROJ_DEFAULTS.get(name, {})}
1592
+ if name[:2] in ("np", "sp"):
1593
+ defaults["round"] = rc["geo.round"]
1594
+ if name == "geos":
1595
+ defaults["rsphere"] = (6378137.00, 6356752.3142)
1596
+ for key, value in defaults.items():
1597
+ if kwargs.get(key, None) is None: # allow e.g. boundinglat=None
1598
+ kwargs[key] = value
1599
+
1600
+ reso = _not_none(
1601
+ reso=kwargs.pop("reso", None),
1602
+ resolution=kwargs.pop("resolution", None),
1603
+ default=rc["reso"],
1604
+ )
1605
+ if reso in RESOS_BASEMAP:
1606
+ reso = RESOS_BASEMAP[reso]
1607
+ else:
1608
+ raise ValueError(
1609
+ f"Invalid resolution {reso!r}. Options are: "
1610
+ + ", ".join(map(repr, RESOS_BASEMAP))
1611
+ + "."
1612
+ )
1613
+ kwargs.update({"resolution": reso, "projection": name})
1614
+ try:
1615
+ proj = Basemap(**kwargs) # will raise helpful warning
1616
+ except ValueError as err:
1617
+ message = str(err)
1618
+ message = message.strip()
1619
+ message = message.replace("projection", "basemap projection")
1620
+ message = message.replace("supported", "known")
1621
+ if include_axes:
1622
+ from . import axes as paxes # avoid circular imports
1623
+
1624
+ message = message.replace("projection.", "projection or axes subclass.")
1625
+ message += "\nThe known axes subclasses are:\n" + paxes._cls_table
1626
+ raise ValueError(message) from None
1627
+
1628
+ proj._proj_backend = backend
1629
+ return proj
1630
+
1631
+
1632
+ # Deprecated
1633
+ Colors = warnings._rename_objs("0.8.0", Colors=get_colors)