ultraplot 0.99.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. ultraplot/__init__.py +115 -0
  2. ultraplot/__init__.py.rej +58 -0
  3. ultraplot/axes/__init__.py +42 -0
  4. ultraplot/axes/base.py +3240 -0
  5. ultraplot/axes/cartesian.py +1425 -0
  6. ultraplot/axes/geo.py +1675 -0
  7. ultraplot/axes/plot.py +4569 -0
  8. ultraplot/axes/polar.py +381 -0
  9. ultraplot/axes/shared.py +186 -0
  10. ultraplot/axes/three.py +34 -0
  11. ultraplot/cmaps/Algae.rgb +256 -0
  12. ultraplot/cmaps/Amp.rgb +256 -0
  13. ultraplot/cmaps/BR.rgb +256 -0
  14. ultraplot/cmaps/Balance.rgb +256 -0
  15. ultraplot/cmaps/Blues1_r.xml +17 -0
  16. ultraplot/cmaps/Blues2.xml +16 -0
  17. ultraplot/cmaps/Blues3.xml +25 -0
  18. ultraplot/cmaps/Blues4_r.xml +17 -0
  19. ultraplot/cmaps/Blues5.xml +16 -0
  20. ultraplot/cmaps/Blues6.xml +25 -0
  21. ultraplot/cmaps/Blues7.xml +16 -0
  22. ultraplot/cmaps/Blues8.xml +17 -0
  23. ultraplot/cmaps/Blues9.xml +1 -0
  24. ultraplot/cmaps/Boreal.json +53 -0
  25. ultraplot/cmaps/Browns1.xml +16 -0
  26. ultraplot/cmaps/Browns2.xml +26 -0
  27. ultraplot/cmaps/Browns3.xml +17 -0
  28. ultraplot/cmaps/Browns4.xml +17 -0
  29. ultraplot/cmaps/Browns5.xml +26 -0
  30. ultraplot/cmaps/Browns6.xml +17 -0
  31. ultraplot/cmaps/Browns7.xml +19 -0
  32. ultraplot/cmaps/Browns8.xml +11 -0
  33. ultraplot/cmaps/Browns9.xml +1 -0
  34. ultraplot/cmaps/ColdHot.rgb +229 -0
  35. ultraplot/cmaps/Crest.rgb +256 -0
  36. ultraplot/cmaps/Curl.rgb +512 -0
  37. ultraplot/cmaps/Deep.rgb +256 -0
  38. ultraplot/cmaps/Delta.rgb +512 -0
  39. ultraplot/cmaps/Dense.rgb +256 -0
  40. ultraplot/cmaps/Div.json +71 -0
  41. ultraplot/cmaps/DryWet.json +73 -0
  42. ultraplot/cmaps/Dusk.json +53 -0
  43. ultraplot/cmaps/Fire.json +53 -0
  44. ultraplot/cmaps/Flare.rgb +256 -0
  45. ultraplot/cmaps/Glacial.json +53 -0
  46. ultraplot/cmaps/Greens1_r.xml +26 -0
  47. ultraplot/cmaps/Greens2.xml +28 -0
  48. ultraplot/cmaps/Greens3_r.xml +28 -0
  49. ultraplot/cmaps/Greens4.xml +17 -0
  50. ultraplot/cmaps/Greens5.xml +16 -0
  51. ultraplot/cmaps/Greens6_r.xml +16 -0
  52. ultraplot/cmaps/Greens7.xml +16 -0
  53. ultraplot/cmaps/Greens8.xml +26 -0
  54. ultraplot/cmaps/Haline.rgb +256 -0
  55. ultraplot/cmaps/Ice.rgb +256 -0
  56. ultraplot/cmaps/IceFire.rgb +256 -0
  57. ultraplot/cmaps/Mako.rgb +256 -0
  58. ultraplot/cmaps/Marine.json +53 -0
  59. ultraplot/cmaps/Matter.rgb +256 -0
  60. ultraplot/cmaps/Mono.txt +256 -0
  61. ultraplot/cmaps/MonoCycle.txt +256 -0
  62. ultraplot/cmaps/NegPos.json +71 -0
  63. ultraplot/cmaps/Oranges1.xml +27 -0
  64. ultraplot/cmaps/Oranges2.xml +26 -0
  65. ultraplot/cmaps/Oranges3.xml +15 -0
  66. ultraplot/cmaps/Oranges4.xml +23 -0
  67. ultraplot/cmaps/Oxy.rgb +256 -0
  68. ultraplot/cmaps/Phase.rgb +256 -0
  69. ultraplot/cmaps/Purples1_r.xml +16 -0
  70. ultraplot/cmaps/Purples2.xml +17 -0
  71. ultraplot/cmaps/Purples3.xml +18 -0
  72. ultraplot/cmaps/Reds1.xml +26 -0
  73. ultraplot/cmaps/Reds2.xml +22 -0
  74. ultraplot/cmaps/Reds3.xml +23 -0
  75. ultraplot/cmaps/Reds4.xml +26 -0
  76. ultraplot/cmaps/Reds5.xml +17 -0
  77. ultraplot/cmaps/Rocket.rgb +256 -0
  78. ultraplot/cmaps/Solar.rgb +256 -0
  79. ultraplot/cmaps/Speed.rgb +256 -0
  80. ultraplot/cmaps/Stellar.json +53 -0
  81. ultraplot/cmaps/Sunrise.json +53 -0
  82. ultraplot/cmaps/Sunset.json +53 -0
  83. ultraplot/cmaps/Tempo.rgb +256 -0
  84. ultraplot/cmaps/Thermal.rgb +256 -0
  85. ultraplot/cmaps/Turbid.rgb +256 -0
  86. ultraplot/cmaps/Vivid.xml +11 -0
  87. ultraplot/cmaps/Vlag.rgb +256 -0
  88. ultraplot/cmaps/Yellows1.xml +17 -0
  89. ultraplot/cmaps/Yellows2.xml +17 -0
  90. ultraplot/cmaps/Yellows3.xml +17 -0
  91. ultraplot/cmaps/Yellows4.xml +17 -0
  92. ultraplot/cmaps/acton.txt +256 -0
  93. ultraplot/cmaps/bam.txt +256 -0
  94. ultraplot/cmaps/bamO.txt +256 -0
  95. ultraplot/cmaps/bamako.txt +256 -0
  96. ultraplot/cmaps/batlow.txt +256 -0
  97. ultraplot/cmaps/batlowK.txt +256 -0
  98. ultraplot/cmaps/batlowW.txt +256 -0
  99. ultraplot/cmaps/berlin.txt +256 -0
  100. ultraplot/cmaps/bilbao.txt +256 -0
  101. ultraplot/cmaps/broc.txt +256 -0
  102. ultraplot/cmaps/brocO.txt +256 -0
  103. ultraplot/cmaps/buda.txt +256 -0
  104. ultraplot/cmaps/bukavu.txt +256 -0
  105. ultraplot/cmaps/cork.txt +256 -0
  106. ultraplot/cmaps/corkO.txt +256 -0
  107. ultraplot/cmaps/davos.txt +256 -0
  108. ultraplot/cmaps/devon.txt +256 -0
  109. ultraplot/cmaps/fes.txt +256 -0
  110. ultraplot/cmaps/hawaii.txt +256 -0
  111. ultraplot/cmaps/imola.txt +256 -0
  112. ultraplot/cmaps/lajolla.txt +256 -0
  113. ultraplot/cmaps/lapaz.txt +256 -0
  114. ultraplot/cmaps/lisbon.txt +256 -0
  115. ultraplot/cmaps/nuuk.txt +256 -0
  116. ultraplot/cmaps/oleron.txt +256 -0
  117. ultraplot/cmaps/oslo.txt +256 -0
  118. ultraplot/cmaps/roma.txt +256 -0
  119. ultraplot/cmaps/romaO.txt +256 -0
  120. ultraplot/cmaps/tofino.txt +256 -0
  121. ultraplot/cmaps/tokyo.txt +256 -0
  122. ultraplot/cmaps/turku.txt +256 -0
  123. ultraplot/cmaps/vanimo.txt +256 -0
  124. ultraplot/cmaps/vik.txt +256 -0
  125. ultraplot/cmaps/vikO.txt +256 -0
  126. ultraplot/colors/opencolor.txt +132 -0
  127. ultraplot/colors/xkcd.txt +951 -0
  128. ultraplot/colors.py +3241 -0
  129. ultraplot/colors.py.rej +243 -0
  130. ultraplot/config.py +1809 -0
  131. ultraplot/constructor.py +1633 -0
  132. ultraplot/cycles/538.hex +2 -0
  133. ultraplot/cycles/FlatUI.hex +1 -0
  134. ultraplot/cycles/Qual1.rgb +7 -0
  135. ultraplot/cycles/Qual2.rgb +13 -0
  136. ultraplot/cycles/bmh.hex +2 -0
  137. ultraplot/cycles/classic.hex +2 -0
  138. ultraplot/cycles/colorblind.hex +2 -0
  139. ultraplot/cycles/colorblind10.hex +2 -0
  140. ultraplot/cycles/default.hex +2 -0
  141. ultraplot/cycles/ggplot.hex +1 -0
  142. ultraplot/cycles/seaborn.hex +2 -0
  143. ultraplot/cycles/tableau.hex +2 -0
  144. ultraplot/demos.py +1201 -0
  145. ultraplot/externals/__init__.py +5 -0
  146. ultraplot/externals/hsluv.py +330 -0
  147. ultraplot/figure.py +2102 -0
  148. ultraplot/fonts/FiraMath-Bold.ttf +0 -0
  149. ultraplot/fonts/FiraMath-ExtraLight.ttf +0 -0
  150. ultraplot/fonts/FiraMath-Heavy.ttf +0 -0
  151. ultraplot/fonts/FiraMath-Light.ttf +0 -0
  152. ultraplot/fonts/FiraMath-Medium.ttf +0 -0
  153. ultraplot/fonts/FiraMath-Regular.ttf +0 -0
  154. ultraplot/fonts/FiraMath-SemiBold.ttf +0 -0
  155. ultraplot/fonts/FiraMath-UltraLight.ttf +0 -0
  156. ultraplot/fonts/FiraSans-Black.ttf +0 -0
  157. ultraplot/fonts/FiraSans-BlackItalic.ttf +0 -0
  158. ultraplot/fonts/FiraSans-Bold.ttf +0 -0
  159. ultraplot/fonts/FiraSans-BoldItalic.ttf +0 -0
  160. ultraplot/fonts/FiraSans-ExtraBold.ttf +0 -0
  161. ultraplot/fonts/FiraSans-ExtraBoldItalic.ttf +0 -0
  162. ultraplot/fonts/FiraSans-ExtraLight.ttf +0 -0
  163. ultraplot/fonts/FiraSans-ExtraLightItalic.ttf +0 -0
  164. ultraplot/fonts/FiraSans-Italic.ttf +0 -0
  165. ultraplot/fonts/FiraSans-Light.ttf +0 -0
  166. ultraplot/fonts/FiraSans-LightItalic.ttf +0 -0
  167. ultraplot/fonts/FiraSans-Medium.ttf +0 -0
  168. ultraplot/fonts/FiraSans-MediumItalic.ttf +0 -0
  169. ultraplot/fonts/FiraSans-Regular.ttf +0 -0
  170. ultraplot/fonts/FiraSans-SemiBold.ttf +0 -0
  171. ultraplot/fonts/FiraSans-SemiBoldItalic.ttf +0 -0
  172. ultraplot/fonts/LICENSE_FIRAMATH.txt +92 -0
  173. ultraplot/fonts/LICENSE_FIRASANS.txt +97 -0
  174. ultraplot/fonts/LICENSE_NOTOSANS.txt +202 -0
  175. ultraplot/fonts/LICENSE_NOTOSERIF.txt +93 -0
  176. ultraplot/fonts/LICENSE_OPENSANS.txt +202 -0
  177. ultraplot/fonts/LICENSE_ROBOTO.txt +202 -0
  178. ultraplot/fonts/LICENSE_SOURCESANS.txt +93 -0
  179. ultraplot/fonts/LICENSE_SOURCESERIF.txt +93 -0
  180. ultraplot/fonts/LICENSE_TEXGYRE.txt +29 -0
  181. ultraplot/fonts/LICENSE_UBUNTU.txt +96 -0
  182. ultraplot/fonts/NotoSans-Bold.ttf +0 -0
  183. ultraplot/fonts/NotoSans-BoldItalic.ttf +0 -0
  184. ultraplot/fonts/NotoSans-Italic.ttf +0 -0
  185. ultraplot/fonts/NotoSans-Regular.ttf +0 -0
  186. ultraplot/fonts/NotoSerif-Bold.ttf +0 -0
  187. ultraplot/fonts/NotoSerif-BoldItalic.ttf +0 -0
  188. ultraplot/fonts/NotoSerif-Italic.ttf +0 -0
  189. ultraplot/fonts/NotoSerif-Regular.ttf +0 -0
  190. ultraplot/fonts/OpenSans-Bold.ttf +0 -0
  191. ultraplot/fonts/OpenSans-BoldItalic.ttf +0 -0
  192. ultraplot/fonts/OpenSans-Italic.ttf +0 -0
  193. ultraplot/fonts/OpenSans-Regular.ttf +0 -0
  194. ultraplot/fonts/OpenSans-Semibold.ttf +0 -0
  195. ultraplot/fonts/OpenSans-SemiboldItalic.ttf +0 -0
  196. ultraplot/fonts/Roboto-Black.ttf +0 -0
  197. ultraplot/fonts/Roboto-BlackItalic.ttf +0 -0
  198. ultraplot/fonts/Roboto-Bold.ttf +0 -0
  199. ultraplot/fonts/Roboto-BoldItalic.ttf +0 -0
  200. ultraplot/fonts/Roboto-Italic.ttf +0 -0
  201. ultraplot/fonts/Roboto-Light.ttf +0 -0
  202. ultraplot/fonts/Roboto-LightItalic.ttf +0 -0
  203. ultraplot/fonts/Roboto-Medium.ttf +0 -0
  204. ultraplot/fonts/Roboto-MediumItalic.ttf +0 -0
  205. ultraplot/fonts/Roboto-Regular.ttf +0 -0
  206. ultraplot/fonts/SourceSansPro-Black.ttf +0 -0
  207. ultraplot/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  208. ultraplot/fonts/SourceSansPro-Bold.ttf +0 -0
  209. ultraplot/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  210. ultraplot/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  211. ultraplot/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  212. ultraplot/fonts/SourceSansPro-Italic.ttf +0 -0
  213. ultraplot/fonts/SourceSansPro-Light.ttf +0 -0
  214. ultraplot/fonts/SourceSansPro-LightItalic.ttf +0 -0
  215. ultraplot/fonts/SourceSansPro-Regular.ttf +0 -0
  216. ultraplot/fonts/SourceSansPro-SemiBold.ttf +0 -0
  217. ultraplot/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  218. ultraplot/fonts/SourceSerifPro-Black.ttf +0 -0
  219. ultraplot/fonts/SourceSerifPro-BlackItalic.ttf +0 -0
  220. ultraplot/fonts/SourceSerifPro-Bold.ttf +0 -0
  221. ultraplot/fonts/SourceSerifPro-BoldItalic.ttf +0 -0
  222. ultraplot/fonts/SourceSerifPro-ExtraLight.ttf +0 -0
  223. ultraplot/fonts/SourceSerifPro-ExtraLightItalic.ttf +0 -0
  224. ultraplot/fonts/SourceSerifPro-Italic.ttf +0 -0
  225. ultraplot/fonts/SourceSerifPro-Light.ttf +0 -0
  226. ultraplot/fonts/SourceSerifPro-LightItalic.ttf +0 -0
  227. ultraplot/fonts/SourceSerifPro-Regular.ttf +0 -0
  228. ultraplot/fonts/SourceSerifPro-SemiBold.ttf +0 -0
  229. ultraplot/fonts/SourceSerifPro-SemiBoldItalic.ttf +0 -0
  230. ultraplot/fonts/Ubuntu-Bold.ttf +0 -0
  231. ultraplot/fonts/Ubuntu-BoldItalic.ttf +0 -0
  232. ultraplot/fonts/Ubuntu-Italic.ttf +0 -0
  233. ultraplot/fonts/Ubuntu-Light.ttf +0 -0
  234. ultraplot/fonts/Ubuntu-LightItalic.ttf +0 -0
  235. ultraplot/fonts/Ubuntu-Medium.ttf +0 -0
  236. ultraplot/fonts/Ubuntu-MediumItalic.ttf +0 -0
  237. ultraplot/fonts/Ubuntu-Regular.ttf +0 -0
  238. ultraplot/fonts/texgyreadventor-bold.ttf +0 -0
  239. ultraplot/fonts/texgyreadventor-bolditalic.ttf +0 -0
  240. ultraplot/fonts/texgyreadventor-italic.ttf +0 -0
  241. ultraplot/fonts/texgyreadventor-regular.ttf +0 -0
  242. ultraplot/fonts/texgyrebonum-bold.ttf +0 -0
  243. ultraplot/fonts/texgyrebonum-bolditalic.ttf +0 -0
  244. ultraplot/fonts/texgyrebonum-italic.ttf +0 -0
  245. ultraplot/fonts/texgyrebonum-regular.ttf +0 -0
  246. ultraplot/fonts/texgyrechorus-mediumitalic.ttf +0 -0
  247. ultraplot/fonts/texgyrecursor-bold.ttf +0 -0
  248. ultraplot/fonts/texgyrecursor-bolditalic.ttf +0 -0
  249. ultraplot/fonts/texgyrecursor-italic.ttf +0 -0
  250. ultraplot/fonts/texgyrecursor-regular.ttf +0 -0
  251. ultraplot/fonts/texgyreheros-bold.ttf +0 -0
  252. ultraplot/fonts/texgyreheros-bolditalic.ttf +0 -0
  253. ultraplot/fonts/texgyreheros-italic.ttf +0 -0
  254. ultraplot/fonts/texgyreheros-regular.ttf +0 -0
  255. ultraplot/fonts/texgyrepagella-bold.ttf +0 -0
  256. ultraplot/fonts/texgyrepagella-bolditalic.ttf +0 -0
  257. ultraplot/fonts/texgyrepagella-italic.ttf +0 -0
  258. ultraplot/fonts/texgyrepagella-regular.ttf +0 -0
  259. ultraplot/fonts/texgyreschola-bold.ttf +0 -0
  260. ultraplot/fonts/texgyreschola-bolditalic.ttf +0 -0
  261. ultraplot/fonts/texgyreschola-italic.ttf +0 -0
  262. ultraplot/fonts/texgyreschola-regular.ttf +0 -0
  263. ultraplot/fonts/texgyretermes-bold.ttf +0 -0
  264. ultraplot/fonts/texgyretermes-bolditalic.ttf +0 -0
  265. ultraplot/fonts/texgyretermes-italic.ttf +0 -0
  266. ultraplot/fonts/texgyretermes-regular.ttf +0 -0
  267. ultraplot/gridspec.py +1698 -0
  268. ultraplot/internals/__init__.py +529 -0
  269. ultraplot/internals/benchmarks.py +26 -0
  270. ultraplot/internals/context.py +44 -0
  271. ultraplot/internals/docstring.py +139 -0
  272. ultraplot/internals/fonts.py +75 -0
  273. ultraplot/internals/guides.py +167 -0
  274. ultraplot/internals/inputs.py +862 -0
  275. ultraplot/internals/labels.py +85 -0
  276. ultraplot/internals/rcsetup.py +1933 -0
  277. ultraplot/internals/versions.py +61 -0
  278. ultraplot/internals/warnings.py +122 -0
  279. ultraplot/proj.py +325 -0
  280. ultraplot/scale.py +966 -0
  281. ultraplot/tests/__init__.py +28 -0
  282. ultraplot/tests/baseline/test_align_labels.png +0 -0
  283. ultraplot/tests/baseline/test_aligned_outer_guides.png +0 -0
  284. ultraplot/tests/baseline/test_aspect_ratios.png +0 -0
  285. ultraplot/tests/baseline/test_auto_diverging1.png +0 -0
  286. ultraplot/tests/baseline/test_auto_legend.png +0 -0
  287. ultraplot/tests/baseline/test_auto_reverse.png +0 -0
  288. ultraplot/tests/baseline/test_autodiverging3.png +0 -0
  289. ultraplot/tests/baseline/test_autodiverging4.png +0 -0
  290. ultraplot/tests/baseline/test_autodiverging5.png +0 -0
  291. ultraplot/tests/baseline/test_axes_colors.png +0 -0
  292. ultraplot/tests/baseline/test_bar_vectors.png +0 -0
  293. ultraplot/tests/baseline/test_bar_width.png +0 -0
  294. ultraplot/tests/baseline/test_both_ticklabels.png +0 -0
  295. ultraplot/tests/baseline/test_bounds_ticks.png +0 -0
  296. ultraplot/tests/baseline/test_boxplot_colors.png +0 -0
  297. ultraplot/tests/baseline/test_boxplot_vectors.png +0 -0
  298. ultraplot/tests/baseline/test_cartopy_contours.png +0 -0
  299. ultraplot/tests/baseline/test_cartopy_labels.png +0 -0
  300. ultraplot/tests/baseline/test_cartopy_manual.png +0 -0
  301. ultraplot/tests/baseline/test_centered_legends.png +0 -0
  302. ultraplot/tests/baseline/test_cmap_cycles.png +0 -0
  303. ultraplot/tests/baseline/test_colorbar.png +0 -0
  304. ultraplot/tests/baseline/test_colorbar_ticks.png +0 -0
  305. ultraplot/tests/baseline/test_colormap_mode.png +0 -0
  306. ultraplot/tests/baseline/test_column_iteration.png +0 -0
  307. ultraplot/tests/baseline/test_complex_ticks.png +0 -0
  308. ultraplot/tests/baseline/test_contour_labels.png +0 -0
  309. ultraplot/tests/baseline/test_contour_legend_with_label.png +0 -0
  310. ultraplot/tests/baseline/test_contour_legend_without_label.png +0 -0
  311. ultraplot/tests/baseline/test_contour_negative.png +0 -0
  312. ultraplot/tests/baseline/test_contour_single.png +0 -0
  313. ultraplot/tests/baseline/test_cutoff_ticks.png +0 -0
  314. ultraplot/tests/baseline/test_data_keyword.png +0 -0
  315. ultraplot/tests/baseline/test_discrete_ticks.png +0 -0
  316. ultraplot/tests/baseline/test_discrete_vs_fixed.png +0 -0
  317. ultraplot/tests/baseline/test_drawing_in_projection_with_globe.png +0 -0
  318. ultraplot/tests/baseline/test_drawing_in_projection_without_globe.png +0 -0
  319. ultraplot/tests/baseline/test_edge_fix.png +0 -0
  320. ultraplot/tests/baseline/test_flow_functions.png +0 -0
  321. ultraplot/tests/baseline/test_font_adjustments.png +0 -0
  322. ultraplot/tests/baseline/test_geographic_multiple_projections.png +0 -0
  323. ultraplot/tests/baseline/test_geographic_single_projection.png +0 -0
  324. ultraplot/tests/baseline/test_gray_adjustment.png +0 -0
  325. ultraplot/tests/baseline/test_histogram_legend.png +0 -0
  326. ultraplot/tests/baseline/test_histogram_types.png +0 -0
  327. ultraplot/tests/baseline/test_ignore_message.png +0 -0
  328. ultraplot/tests/baseline/test_inbounds_data.png +0 -0
  329. ultraplot/tests/baseline/test_init_format.png +0 -0
  330. ultraplot/tests/baseline/test_inner_title_zorder.png +0 -0
  331. ultraplot/tests/baseline/test_inset_basic.png +0 -0
  332. ultraplot/tests/baseline/test_inset_colorbars.png +0 -0
  333. ultraplot/tests/baseline/test_inset_colors_1.png +0 -0
  334. ultraplot/tests/baseline/test_inset_colors_2.png +0 -0
  335. ultraplot/tests/baseline/test_inset_zoom_update.png +0 -0
  336. ultraplot/tests/baseline/test_invalid_dist.png +0 -0
  337. ultraplot/tests/baseline/test_invalid_plot.png +0 -0
  338. ultraplot/tests/baseline/test_keep_guide_labels.png +0 -0
  339. ultraplot/tests/baseline/test_label_settings.png +0 -0
  340. ultraplot/tests/baseline/test_level_restriction.png +0 -0
  341. ultraplot/tests/baseline/test_levels_with_vmin_vmax.png +0 -0
  342. ultraplot/tests/baseline/test_locale_formatting.png +0 -0
  343. ultraplot/tests/baseline/test_locale_formatting_en_US.UTF-8.png +0 -0
  344. ultraplot/tests/baseline/test_manual_labels.png +0 -0
  345. ultraplot/tests/baseline/test_multi_formatting.png +0 -0
  346. ultraplot/tests/baseline/test_multiple_calls.png +0 -0
  347. ultraplot/tests/baseline/test_on_the_fly_mappable.png +0 -0
  348. ultraplot/tests/baseline/test_outer_align.png +0 -0
  349. ultraplot/tests/baseline/test_panel_dist.png +0 -0
  350. ultraplot/tests/baseline/test_panels_suplabels_three_hor_panels.png +0 -0
  351. ultraplot/tests/baseline/test_panels_with_sharing.png +0 -0
  352. ultraplot/tests/baseline/test_panels_without_sharing_1.png +0 -0
  353. ultraplot/tests/baseline/test_panels_without_sharing_2.png +0 -0
  354. ultraplot/tests/baseline/test_parametric_colors.png +0 -0
  355. ultraplot/tests/baseline/test_parametric_labels.png +0 -0
  356. ultraplot/tests/baseline/test_patch_format.png +0 -0
  357. ultraplot/tests/baseline/test_pie_charts.png +0 -0
  358. ultraplot/tests/baseline/test_pint_quantities.png +0 -0
  359. ultraplot/tests/baseline/test_polar_projections.png +0 -0
  360. ultraplot/tests/baseline/test_projection_dicts.png +0 -0
  361. ultraplot/tests/baseline/test_qualitative_colormaps_1.png +0 -0
  362. ultraplot/tests/baseline/test_qualitative_colormaps_2.png +0 -0
  363. ultraplot/tests/baseline/test_reversed_levels.png +0 -0
  364. ultraplot/tests/baseline/test_scatter_alpha.png +0 -0
  365. ultraplot/tests/baseline/test_scatter_args.png +0 -0
  366. ultraplot/tests/baseline/test_scatter_cycle.png +0 -0
  367. ultraplot/tests/baseline/test_scatter_inbounds.png +0 -0
  368. ultraplot/tests/baseline/test_scatter_sizes.png +0 -0
  369. ultraplot/tests/baseline/test_seaborn_heatmap.png +0 -0
  370. ultraplot/tests/baseline/test_seaborn_hist.png +0 -0
  371. ultraplot/tests/baseline/test_seaborn_relational.png +0 -0
  372. ultraplot/tests/baseline/test_seaborn_swarmplot.png +0 -0
  373. ultraplot/tests/baseline/test_segmented_norm.png +0 -0
  374. ultraplot/tests/baseline/test_segmented_norm_ticks.png +0 -0
  375. ultraplot/tests/baseline/test_share_all_basic.png +0 -0
  376. ultraplot/tests/baseline/test_singleton_legend.png +0 -0
  377. ultraplot/tests/baseline/test_span_labels.png +0 -0
  378. ultraplot/tests/baseline/test_spine_offset.png +0 -0
  379. ultraplot/tests/baseline/test_spine_side.png +0 -0
  380. ultraplot/tests/baseline/test_standardized_input.png +0 -0
  381. ultraplot/tests/baseline/test_statistical_boxplot.png +0 -0
  382. ultraplot/tests/baseline/test_three_axes.png +0 -0
  383. ultraplot/tests/baseline/test_tick_direction.png +0 -0
  384. ultraplot/tests/baseline/test_tick_labels.png +0 -0
  385. ultraplot/tests/baseline/test_tick_length.png +0 -0
  386. ultraplot/tests/baseline/test_tick_width.png +0 -0
  387. ultraplot/tests/baseline/test_title_deflection.png +0 -0
  388. ultraplot/tests/baseline/test_triangular_functions.png +0 -0
  389. ultraplot/tests/baseline/test_tuple_handles.png +0 -0
  390. ultraplot/tests/baseline/test_twin_axes_1.png +0 -0
  391. ultraplot/tests/baseline/test_twin_axes_2.png +0 -0
  392. ultraplot/tests/baseline/test_twin_axes_3.png +0 -0
  393. ultraplot/tests/baseline/test_uneven_levels.png +0 -0
  394. ultraplot/tests/test_1dplots.py +373 -0
  395. ultraplot/tests/test_2dplots.py +354 -0
  396. ultraplot/tests/test_axes.py +179 -0
  397. ultraplot/tests/test_colorbar.py +253 -0
  398. ultraplot/tests/test_docs.py +78 -0
  399. ultraplot/tests/test_format.py +340 -0
  400. ultraplot/tests/test_geographic.py +116 -0
  401. ultraplot/tests/test_imshow.py +110 -0
  402. ultraplot/tests/test_inset.py +28 -0
  403. ultraplot/tests/test_integration.py +149 -0
  404. ultraplot/tests/test_legend.py +181 -0
  405. ultraplot/tests/test_projections.py +138 -0
  406. ultraplot/tests/test_statistical_plotting.py +77 -0
  407. ultraplot/tests/test_subplots.py +174 -0
  408. ultraplot/ticker.py +879 -0
  409. ultraplot/ui.py +233 -0
  410. ultraplot/utils.py +912 -0
  411. ultraplot-0.99.3.dist-info/LICENSE.txt +427 -0
  412. ultraplot-0.99.3.dist-info/METADATA +88 -0
  413. ultraplot-0.99.3.dist-info/RECORD +416 -0
  414. ultraplot-0.99.3.dist-info/WHEEL +5 -0
  415. ultraplot-0.99.3.dist-info/entry_points.txt +2 -0
  416. ultraplot-0.99.3.dist-info/top_level.txt +1 -0
ultraplot/ticker.py ADDED
@@ -0,0 +1,879 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Various `~matplotlib.ticker.Locator` and `~matplotlib.ticker.Formatter` classes.
4
+ """
5
+ import locale
6
+ import re
7
+ from fractions import Fraction
8
+
9
+ import matplotlib.axis as maxis
10
+ import matplotlib.ticker as mticker
11
+ import numpy as np
12
+
13
+ from .config import rc
14
+ from .internals import ic # noqa: F401
15
+ from .internals import _not_none, context, docstring
16
+
17
+ try:
18
+ import cartopy.crs as ccrs
19
+ from cartopy.mpl.ticker import (
20
+ LatitudeFormatter,
21
+ LongitudeFormatter,
22
+ _PlateCarreeFormatter,
23
+ )
24
+ except ModuleNotFoundError:
25
+ ccrs = None
26
+ LatitudeFormatter = LongitudeFormatter = _PlateCarreeFormatter = object
27
+
28
+ __all__ = [
29
+ "IndexLocator",
30
+ "DiscreteLocator",
31
+ "DegreeLocator",
32
+ "LongitudeLocator",
33
+ "LatitudeLocator",
34
+ "AutoFormatter",
35
+ "SimpleFormatter",
36
+ "IndexFormatter",
37
+ "SciFormatter",
38
+ "SigFigFormatter",
39
+ "FracFormatter",
40
+ "DegreeFormatter",
41
+ "LongitudeFormatter",
42
+ "LatitudeFormatter",
43
+ ]
44
+
45
+ REGEX_ZERO = re.compile("\\A[-\N{MINUS SIGN}]?0(.0*)?\\Z")
46
+ REGEX_MINUS = re.compile("\\A[-\N{MINUS SIGN}]\\Z")
47
+ REGEX_MINUS_ZERO = re.compile("\\A[-\N{MINUS SIGN}]0(.0*)?\\Z")
48
+
49
+ _precision_docstring = """
50
+ precision : int, default: {6, 2}
51
+ The maximum number of digits after the decimal point. Default is ``6``
52
+ when `zerotrim` is ``True`` and ``2`` otherwise.
53
+ """
54
+ _zerotrim_docstring = """
55
+ zerotrim : bool, default: :rc:`format.zerotrim`
56
+ Whether to trim trailing decimal zeros.
57
+ """
58
+ _auto_docstring = """
59
+ tickrange : 2-tuple of float, optional
60
+ Range within which major tick marks are labeled.
61
+ All ticks are labeled by default.
62
+ wraprange : 2-tuple of float, optional
63
+ Range outside of which tick values are wrapped. For example,
64
+ ``(-180, 180)`` will format a value of ``200`` as ``-160``.
65
+ prefix, suffix : str, optional
66
+ Prefix and suffix for all tick strings. The suffix is added before
67
+ the optional `negpos` suffix.
68
+ negpos : str, optional
69
+ Length-2 string indicating the suffix for "negative" and "positive"
70
+ numbers, meant to replace the minus sign.
71
+ """
72
+ _formatter_call = """
73
+ Convert number to a string.
74
+
75
+ Parameters
76
+ ----------
77
+ x : float
78
+ The value.
79
+ pos : float, optional
80
+ The position.
81
+ """
82
+ docstring._snippet_manager["ticker.precision"] = _precision_docstring
83
+ docstring._snippet_manager["ticker.zerotrim"] = _zerotrim_docstring
84
+ docstring._snippet_manager["ticker.auto"] = _auto_docstring
85
+ docstring._snippet_manager["ticker.call"] = _formatter_call
86
+
87
+ _dms_docstring = """
88
+ Parameters
89
+ ----------
90
+ dms : bool, default: False
91
+ Locate the ticks on clean degree-minute-second intervals and format the
92
+ ticks with minutes and seconds instead of decimals.
93
+ """
94
+ docstring._snippet_manager["ticker.dms"] = _dms_docstring
95
+
96
+
97
+ def _default_precision_zerotrim(precision=None, zerotrim=None):
98
+ """
99
+ Return the default zerotrim and precision. Shared by several formatters.
100
+ """
101
+ zerotrim = _not_none(zerotrim, rc["formatter.zerotrim"])
102
+ if precision is None:
103
+ precision = 6 if zerotrim else 2
104
+ return precision, zerotrim
105
+
106
+
107
+ class IndexLocator(mticker.Locator):
108
+ """
109
+ Format numbers by assigning fixed strings to non-negative indices. The ticks
110
+ are restricted to the extent of plotted content when content is present.
111
+ """
112
+
113
+ def __init__(self, base=1, offset=0):
114
+ self._base = base
115
+ self._offset = offset
116
+
117
+ def set_params(self, base=None, offset=None):
118
+ if base is not None:
119
+ self._base = base
120
+ if offset is not None:
121
+ self._offset = offset
122
+
123
+ def __call__(self):
124
+ # NOTE: We adapt matplotlib IndexLocator to support case where
125
+ # the data interval is empty. Only restrict after data is plotted.
126
+ dmin, dmax = self.axis.get_data_interval()
127
+ vmin, vmax = self.axis.get_view_interval()
128
+ min_ = max(dmin, vmin)
129
+ max_ = min(dmax, vmax)
130
+ return self.tick_values(min_, max_)
131
+
132
+ def tick_values(self, vmin, vmax):
133
+ base, offset = self._base, self._offset
134
+ vmin = max(base * np.ceil(vmin / base), offset)
135
+ vmax = max(base * np.floor(vmax / base), offset)
136
+ locs = np.arange(vmin, vmax + 0.5 * base, base)
137
+ return self.raise_if_exceeds(locs)
138
+
139
+
140
+ class DiscreteLocator(mticker.Locator):
141
+ """
142
+ A tick locator suitable for discretized colorbars. Adds ticks to some
143
+ subset of the location list depending on the available space determined from
144
+ `~matplotlib.axis.Axis.get_tick_space`. Zero will be used if it appears in the
145
+ location list, and step sizes along the location list are restricted to "nice"
146
+ intervals by default.
147
+ """
148
+
149
+ default_params = {
150
+ "nbins": None,
151
+ "minor": False,
152
+ "steps": np.array([1, 2, 3, 4, 5, 6, 8, 10]),
153
+ "min_n_ticks": 2,
154
+ }
155
+
156
+ @docstring._snippet_manager
157
+ def __init__(self, locs, **kwargs):
158
+ """
159
+ Parameters
160
+ ----------
161
+ locs : array-like
162
+ The tick location list.
163
+ nbins : int, optional
164
+ Maximum number of ticks to select. By default this is automatically
165
+ determined based on the the axis length and tick label font size.
166
+ minor : bool, default: False
167
+ Whether this is for "minor" ticks. Setting to ``True`` will select more
168
+ ticks with an index step that divides the index step used for "major" ticks.
169
+ steps : array-like of int, default: ``[1 2 3 4 5 6 8]``
170
+ Valid integer index steps when selecting from the tick list. Must fall
171
+ between 1 and 9. Powers of 10 of these step sizes will also be permitted.
172
+ min_n_ticks : int, default: 1
173
+ The minimum number of ticks to select. See also `nbins`.
174
+ """
175
+ self.locs = np.array(locs)
176
+ self._nbins = None # otherwise unset
177
+ self.set_params(**{**self.default_params, **kwargs})
178
+
179
+ def __call__(self):
180
+ """
181
+ Return the locations of the ticks.
182
+ """
183
+ return self.tick_values(None, None)
184
+
185
+ def set_params(self, steps=None, nbins=None, minor=None, min_n_ticks=None):
186
+ """
187
+ Set the parameters for this locator. See `DiscreteLocator` for details.
188
+ """
189
+ if steps is not None:
190
+ steps = np.unique(np.array(steps, dtype=int)) # also sorts, makes 1D
191
+ if np.any(steps < 1) or np.any(steps > 10):
192
+ raise ValueError("Steps must fall between one and ten (inclusive).")
193
+ if steps[0] != 1:
194
+ steps = np.concatenate([[1], steps])
195
+ if steps[-1] != 10:
196
+ steps = np.concatenate([steps, [10]])
197
+ self._steps = steps
198
+ if nbins is not None:
199
+ self._nbins = nbins
200
+ if minor is not None:
201
+ self._minor = bool(minor) # needed to scale tick space
202
+ if min_n_ticks is not None:
203
+ self._min_n_ticks = int(min_n_ticks) # compare to MaxNLocator
204
+
205
+ def tick_values(self, vmin, vmax): # noqa: U100
206
+ """
207
+ Return the locations of the ticks.
208
+ """
209
+ # NOTE: Critical that minor tick interval evenly divides major tick
210
+ # interval. Otherwise get misaligned major and minor tick steps.
211
+ # NOTE: This tries to select ticks that are integer steps away from zero (like
212
+ # AutoLocator). The list minimum is used if this fails (like FixedLocator)
213
+ # NOTE: This avoids awkward steps like '7' or '13' that produce awkward
214
+ # jumps and have no integer divisors (and therefore eliminate minor ticks)
215
+ # NOTE: We virtually always want to subsample the level list rather than
216
+ # using continuous minor locators (e.g. LogLocator or SymLogLocator) because
217
+ # _parse_autolev interpolates evenly in the norm-space (e.g. 1, 3.16, 10, 31.6
218
+ # for a LogNorm) rather than in linear-space (e.g. 1, 5, 10, 15, 20).
219
+ locs = self.locs
220
+ axis = self.axis
221
+ if axis is None:
222
+ return locs
223
+ nbins = self._nbins
224
+ steps = self._steps
225
+ if nbins is None:
226
+ nbins = axis.get_tick_space()
227
+ nbins = max((1, self._min_n_ticks - 1, nbins))
228
+ step = max(1, int(np.ceil(locs.size / nbins)))
229
+ fact = 10 ** max(0, -AutoFormatter._decimal_place(step)) # e.g. 2 for 100
230
+ idx = min(len(steps) - 1, np.searchsorted(steps, step / fact))
231
+ step = int(np.round(steps[idx] * fact))
232
+ if self._minor: # tick every half font size
233
+ if isinstance(axis, maxis.XAxis):
234
+ fact = 6 # unscale heuristic scaling of 3 em-widths
235
+ elif isinstance(axis, maxis.YAxis):
236
+ fact = 4 # unscale standard scaling of 2 em-widths
237
+ else:
238
+ fact = 2 # fall back to just one em-width
239
+ for i in range(fact, 0, -1):
240
+ if step % i == 0:
241
+ step = step // i
242
+ break
243
+ diff = np.abs(np.diff(locs[: step + 1 : step]))
244
+ (offset,) = np.where(np.isclose(locs % diff if diff.size else 0.0, 0.0))
245
+ offset = offset[0] if offset.size else np.argmin(np.abs(locs))
246
+ return locs[offset % step :: step] # even multiples from zero or zero-close
247
+
248
+
249
+ class DegreeLocator(mticker.MaxNLocator):
250
+ """
251
+ Locate geographic gridlines with degree-minute-second support.
252
+ Adapted from cartopy.
253
+ """
254
+
255
+ # NOTE: This is identical to cartopy except they only define LongitutdeLocator
256
+ # for common methods whereas we use DegreeLocator. More intuitive this way in
257
+ # case users need degree-minute-seconds for non-specific degree axis.
258
+ # NOTE: Locator implementation is weird AF. __init__ just calls set_params with all
259
+ # keyword args and fills in missing params with default_params class attribute.
260
+ # Unknown params result in warning instead of error.
261
+ default_params = mticker.MaxNLocator.default_params.copy()
262
+ default_params.update(nbins=8, dms=False)
263
+
264
+ @docstring._snippet_manager
265
+ def __init__(self, *args, **kwargs):
266
+ """
267
+ %(ticker.dms)s
268
+ """
269
+ super().__init__(*args, **kwargs)
270
+
271
+ def set_params(self, **kwargs):
272
+ if "dms" in kwargs:
273
+ self._dms = kwargs.pop("dms")
274
+ super().set_params(**kwargs)
275
+
276
+ def _guess_steps(self, vmin, vmax):
277
+ dv = abs(vmax - vmin)
278
+ if dv > 180:
279
+ dv -= 180
280
+ if dv > 50:
281
+ steps = np.array([1, 2, 3, 6, 10])
282
+ elif not self._dms or dv > 3.0:
283
+ steps = np.array([1, 1.5, 2, 2.5, 3, 5, 10])
284
+ else:
285
+ steps = np.array([1, 10 / 6.0, 15 / 6.0, 20 / 6.0, 30 / 6.0, 10])
286
+ self.set_params(steps=np.array(steps))
287
+
288
+ def _raw_ticks(self, vmin, vmax):
289
+ self._guess_steps(vmin, vmax)
290
+ return super()._raw_ticks(vmin, vmax)
291
+
292
+ def bin_boundaries(self, vmin, vmax): # matplotlib < 2.2.0
293
+ return self._raw_ticks(vmin, vmax) # may call Latitude/Longitude Locator copies
294
+
295
+
296
+ class LongitudeLocator(DegreeLocator):
297
+ """
298
+ Locate longitude gridlines with degree-minute-second support.
299
+ Adapted from cartopy.
300
+ """
301
+
302
+ @docstring._snippet_manager
303
+ def __init__(self, *args, **kwargs):
304
+ """
305
+ %(ticker.dms)s
306
+ """
307
+ super().__init__(*args, **kwargs)
308
+
309
+
310
+ class LatitudeLocator(DegreeLocator):
311
+ """
312
+ Locate latitude gridlines with degree-minute-second support.
313
+ Adapted from cartopy.
314
+ """
315
+
316
+ @docstring._snippet_manager
317
+ def __init__(self, *args, **kwargs):
318
+ """
319
+ %(ticker.dms)s
320
+ """
321
+ super().__init__(*args, **kwargs)
322
+
323
+ def tick_values(self, vmin, vmax):
324
+ vmin = max(vmin, -90)
325
+ vmax = min(vmax, 90)
326
+ return super().tick_values(vmin, vmax)
327
+
328
+ def _guess_steps(self, vmin, vmax):
329
+ vmin = max(vmin, -90)
330
+ vmax = min(vmax, 90)
331
+ super()._guess_steps(vmin, vmax)
332
+
333
+ def _raw_ticks(self, vmin, vmax):
334
+ ticks = super()._raw_ticks(vmin, vmax)
335
+ return [t for t in ticks if -90 <= t <= 90]
336
+
337
+
338
+ class AutoFormatter(mticker.ScalarFormatter):
339
+ """
340
+ The default formatter used for ultraplot tick labels.
341
+ Replaces `~matplotlib.ticker.ScalarFormatter`.
342
+ """
343
+
344
+ @docstring._snippet_manager
345
+ def __init__(
346
+ self,
347
+ zerotrim=None,
348
+ tickrange=None,
349
+ wraprange=None,
350
+ prefix=None,
351
+ suffix=None,
352
+ negpos=None,
353
+ **kwargs,
354
+ ):
355
+ """
356
+ Parameters
357
+ ----------
358
+ %(ticker.zerotrim)s
359
+ %(ticker.auto)s
360
+
361
+ Other parameters
362
+ ----------------
363
+ **kwargs
364
+ Passed to `matplotlib.ticker.ScalarFormatter`.
365
+
366
+ See also
367
+ --------
368
+ ultraplot.constructor.Formatter
369
+ ultraplot.ticker.SimpleFormatter
370
+
371
+ Note
372
+ ----
373
+ `matplotlib.ticker.ScalarFormatter` determines the number of
374
+ significant digits based on the axis limits, and therefore may
375
+ truncate digits while formatting ticks on highly non-linear axis
376
+ scales like `~ultraplot.scale.LogScale`. `AutoFormatter` corrects
377
+ this behavior, making it suitable for arbitrary axis scales. We
378
+ therefore use `AutoFormatter` with every axis scale by default.
379
+ """
380
+ tickrange = tickrange or (-np.inf, np.inf)
381
+ super().__init__(**kwargs)
382
+ zerotrim = _not_none(zerotrim, rc["formatter.zerotrim"])
383
+ self._zerotrim = zerotrim
384
+ self._tickrange = tickrange
385
+ self._wraprange = wraprange
386
+ self._prefix = prefix or ""
387
+ self._suffix = suffix or ""
388
+ self._negpos = negpos or ""
389
+
390
+ @docstring._snippet_manager
391
+ def __call__(self, x, pos=None):
392
+ """
393
+ %(ticker.call)s
394
+ """
395
+ # Tick range limitation
396
+ x = self._wrap_tick_range(x, self._wraprange)
397
+ if self._outside_tick_range(x, self._tickrange):
398
+ return ""
399
+
400
+ # Negative positive handling
401
+ x, tail = self._neg_pos_format(x, self._negpos, wraprange=self._wraprange)
402
+
403
+ # Default string formatting
404
+ string = super().__call__(x, pos)
405
+
406
+ # Fix issue where non-zero string is formatted as zero
407
+ string = self._fix_small_number(x, string)
408
+
409
+ # Custom string formatting
410
+ string = self._minus_format(string)
411
+ if self._zerotrim:
412
+ string = self._trim_trailing_zeros(string, self._get_decimal_point())
413
+
414
+ # Prefix and suffix
415
+ string = self._add_prefix_suffix(string, self._prefix, self._suffix)
416
+ string = string + tail # add negative-positive indicator
417
+ return string
418
+
419
+ def get_offset(self):
420
+ """
421
+ Get the offset but *always* use math text.
422
+ """
423
+ with context._state_context(self, _useMathText=True):
424
+ return super().get_offset()
425
+
426
+ @staticmethod
427
+ def _add_prefix_suffix(string, prefix=None, suffix=None):
428
+ """
429
+ Add prefix and suffix to string.
430
+ """
431
+ sign = ""
432
+ prefix = prefix or ""
433
+ suffix = suffix or ""
434
+ if string and REGEX_MINUS.match(string[0]):
435
+ sign, string = string[0], string[1:]
436
+ return sign + prefix + string + suffix
437
+
438
+ def _fix_small_number(self, x, string, precision_offset=2):
439
+ """
440
+ Fix formatting for non-zero formatted as zero. The `offset` controls the offset
441
+ from true floating point precision at which we want to limit string precision.
442
+ """
443
+ # Add just enough precision for small numbers. Default formatter is
444
+ # only meant to be used for linear scales and cannot handle the wide
445
+ # range of magnitudes in e.g. log scales. To correct this, we only
446
+ # truncate if value is within `offset` order of magnitude of the float
447
+ # precision. Common issue is e.g. levels=pplt.arange(-1, 1, 0.1).
448
+ # This choice satisfies even 1000 additions of 0.1 to -100.
449
+ m = REGEX_ZERO.match(string)
450
+ decimal_point = self._get_decimal_point()
451
+
452
+ if m and x != 0:
453
+ # Get initial precision spit out by algorithm
454
+ (decimals,) = m.groups()
455
+ precision_init = len(decimals.lstrip(decimal_point)) if decimals else 0
456
+
457
+ # Format with precision below floating point error
458
+ x -= getattr(self, "offset", 0) # guard against API change
459
+ x /= 10 ** getattr(self, "orderOfMagnitude", 0) # guard against API change
460
+ precision_true = max(0, self._decimal_place(x))
461
+ precision_max = max(0, np.finfo(type(x)).precision - precision_offset)
462
+ precision = min(precision_true, precision_max)
463
+ string = ("{:.%df}" % precision).format(x)
464
+
465
+ # If zero ignoring floating point error then match original precision
466
+ if REGEX_ZERO.match(string):
467
+ string = ("{:.%df}" % precision_init).format(0)
468
+
469
+ # Fix decimal point
470
+ string = string.replace(".", decimal_point)
471
+
472
+ return string
473
+
474
+ def _get_decimal_point(self, use_locale=None):
475
+ """
476
+ Get decimal point symbol for current locale (e.g. in Europe will be comma).
477
+ """
478
+ use_locale = _not_none(use_locale, self.get_useLocale())
479
+ return self._get_default_decimal_point(use_locale)
480
+
481
+ @staticmethod
482
+ def _get_default_decimal_point(use_locale=None):
483
+ """
484
+ Get decimal point symbol for current locale. Called externally.
485
+ """
486
+ use_locale = _not_none(use_locale, rc["formatter.use_locale"])
487
+ return locale.localeconv()["decimal_point"] if use_locale else "."
488
+
489
+ @staticmethod
490
+ def _decimal_place(x):
491
+ """
492
+ Return the decimal place of the number (e.g., 100 is -2 and 0.01 is 2).
493
+ """
494
+ if x == 0:
495
+ digits = 0
496
+ else:
497
+ digits = -int(np.log10(abs(x)) // 1)
498
+ return digits
499
+
500
+ @staticmethod
501
+ def _minus_format(string):
502
+ """
503
+ Format the minus sign and avoid "negative zero," e.g. ``-0.000``.
504
+ """
505
+ if rc["axes.unicode_minus"] and not rc["text.usetex"]:
506
+ string = string.replace("-", "\N{MINUS SIGN}")
507
+ if REGEX_MINUS_ZERO.match(string):
508
+ string = string[1:]
509
+ return string
510
+
511
+ @staticmethod
512
+ def _neg_pos_format(x, negpos, wraprange=None):
513
+ """
514
+ Permit suffixes indicators for "negative" and "positive" numbers.
515
+ """
516
+ # NOTE: If input is a symmetric wraprange, the value conceptually has
517
+ # no "sign", so trim tail and format as absolute value.
518
+ if not negpos or x == 0:
519
+ tail = ""
520
+ elif (
521
+ wraprange is not None
522
+ and np.isclose(-wraprange[0], wraprange[1])
523
+ and np.any(np.isclose(x, wraprange))
524
+ ):
525
+ x = abs(x)
526
+ tail = ""
527
+ elif x > 0:
528
+ tail = negpos[1]
529
+ else:
530
+ x *= -1
531
+ tail = negpos[0]
532
+ return x, tail
533
+
534
+ @staticmethod
535
+ def _outside_tick_range(x, tickrange):
536
+ """
537
+ Return whether point is outside tick range up to some precision.
538
+ """
539
+ eps = abs(x) / 1000
540
+ return (x + eps) < tickrange[0] or (x - eps) > tickrange[1]
541
+
542
+ @staticmethod
543
+ def _trim_trailing_zeros(string, decimal_point="."):
544
+ """
545
+ Sanitize tick label strings.
546
+ """
547
+ if decimal_point in string:
548
+ string = string.rstrip("0").rstrip(decimal_point)
549
+ return string
550
+
551
+ @staticmethod
552
+ def _wrap_tick_range(x, wraprange):
553
+ """
554
+ Wrap the tick range to within these values.
555
+ """
556
+ if wraprange is None:
557
+ return x
558
+ base = wraprange[0]
559
+ modulus = wraprange[1] - wraprange[0]
560
+ return (x - base) % modulus + base
561
+
562
+
563
+ class SimpleFormatter(mticker.Formatter):
564
+ """
565
+ A general purpose number formatter. This is similar to `AutoFormatter`
566
+ but suitable for arbitrary formatting not necessarily associated with
567
+ an `~matplotlib.axis.Axis` instance.
568
+ """
569
+
570
+ @docstring._snippet_manager
571
+ def __init__(
572
+ self,
573
+ precision=None,
574
+ zerotrim=None,
575
+ tickrange=None,
576
+ wraprange=None,
577
+ prefix=None,
578
+ suffix=None,
579
+ negpos=None,
580
+ ):
581
+ """
582
+ Parameters
583
+ ----------
584
+ %(ticker.precision)s
585
+ %(ticker.zerotrim)s
586
+ %(ticker.auto)s
587
+
588
+ See also
589
+ --------
590
+ ultraplot.constructor.Formatter
591
+ ultraplot.ticker.AutoFormatter
592
+ """
593
+ precision, zerotrim = _default_precision_zerotrim(precision, zerotrim)
594
+ self._precision = precision
595
+ self._prefix = prefix or ""
596
+ self._suffix = suffix or ""
597
+ self._negpos = negpos or ""
598
+ self._tickrange = tickrange or (-np.inf, np.inf)
599
+ self._wraprange = wraprange
600
+ self._zerotrim = zerotrim
601
+
602
+ @docstring._snippet_manager
603
+ def __call__(self, x, pos=None): # noqa: U100
604
+ """
605
+ %(ticker.call)s
606
+ """
607
+ # Tick range limitation
608
+ x = AutoFormatter._wrap_tick_range(x, self._wraprange)
609
+ if AutoFormatter._outside_tick_range(x, self._tickrange):
610
+ return ""
611
+
612
+ # Negative positive handling
613
+ x, tail = AutoFormatter._neg_pos_format(
614
+ x, self._negpos, wraprange=self._wraprange
615
+ )
616
+
617
+ # Default string formatting
618
+ decimal_point = AutoFormatter._get_default_decimal_point()
619
+ string = ("{:.%df}" % self._precision).format(x)
620
+ string = string.replace(".", decimal_point)
621
+
622
+ # Custom string formatting
623
+ string = AutoFormatter._minus_format(string)
624
+ if self._zerotrim:
625
+ string = AutoFormatter._trim_trailing_zeros(string, decimal_point)
626
+
627
+ # Prefix and suffix
628
+ string = AutoFormatter._add_prefix_suffix(string, self._prefix, self._suffix)
629
+ string = string + tail # add negative-positive indicator
630
+ return string
631
+
632
+
633
+ class IndexFormatter(mticker.Formatter):
634
+ """
635
+ Format numbers by assigning fixed strings to non-negative indices. Generally
636
+ paired with `IndexLocator` or `~matplotlib.ticker.FixedLocator`.
637
+ """
638
+
639
+ # NOTE: This was deprecated in matplotlib 3.3. For details check out
640
+ # https://github.com/matplotlib/matplotlib/issues/16631 and bring some popcorn.
641
+ def __init__(self, labels):
642
+ self.labels = labels
643
+ self.n = len(labels)
644
+
645
+ def __call__(self, x, pos=None): # noqa: U100
646
+ i = int(round(x))
647
+ if i < 0 or i >= self.n:
648
+ return ""
649
+ else:
650
+ return self.labels[i]
651
+
652
+
653
+ class SciFormatter(mticker.Formatter):
654
+ """
655
+ Format numbers with scientific notation.
656
+ """
657
+
658
+ @docstring._snippet_manager
659
+ def __init__(self, precision=None, zerotrim=None):
660
+ """
661
+ Parameters
662
+ ----------
663
+ %(ticker.precision)s
664
+ %(ticker.zerotrim)s
665
+
666
+ See also
667
+ --------
668
+ ultraplot.constructor.Formatter
669
+ ultraplot.ticker.AutoFormatter
670
+ """
671
+ precision, zerotrim = _default_precision_zerotrim(precision, zerotrim)
672
+ self._precision = precision
673
+ self._zerotrim = zerotrim
674
+
675
+ @docstring._snippet_manager
676
+ def __call__(self, x, pos=None): # noqa: U100
677
+ """
678
+ %(ticker.call)s
679
+ """
680
+ # Get string
681
+ decimal_point = AutoFormatter._get_default_decimal_point()
682
+ string = ("{:.%de}" % self._precision).format(x)
683
+ parts = string.split("e")
684
+
685
+ # Trim trailing zeros
686
+ significand = parts[0].rstrip(decimal_point)
687
+ if self._zerotrim:
688
+ significand = AutoFormatter._trim_trailing_zeros(significand, decimal_point)
689
+
690
+ # Get sign and exponent
691
+ sign = parts[1][0].replace("+", "")
692
+ exponent = parts[1][1:].lstrip("0")
693
+ if exponent:
694
+ exponent = f"10^{{{sign}{exponent}}}"
695
+ if significand and exponent:
696
+ string = rf"{significand}{{\times}}{exponent}"
697
+ else:
698
+ string = rf"{significand}{exponent}"
699
+
700
+ # Ensure unicode minus sign
701
+ string = AutoFormatter._minus_format(string)
702
+
703
+ # Return TeX string
704
+ return f"${string}$"
705
+
706
+
707
+ class SigFigFormatter(mticker.Formatter):
708
+ """
709
+ Format numbers by retaining the specified number of significant digits.
710
+ """
711
+
712
+ @docstring._snippet_manager
713
+ def __init__(self, sigfig=None, zerotrim=None, base=None):
714
+ """
715
+ Parameters
716
+ ----------
717
+ sigfig : float, default: 3
718
+ The number of significant digits.
719
+ %(ticker.zerotrim)s
720
+ base : float, default: 1
721
+ The base unit for rounding. For example ``SigFigFormatter(2, base=5)``
722
+ rounds to the nearest 5 with up to 2 digits (e.g., 87 --> 85, 8.7 --> 8.5).
723
+
724
+ See also
725
+ --------
726
+ ultraplot.constructor.Formatter
727
+ ultraplot.ticker.AutoFormatter
728
+ """
729
+ self._sigfig = _not_none(sigfig, 3)
730
+ self._zerotrim = _not_none(zerotrim, rc["formatter.zerotrim"])
731
+ self._base = _not_none(base, 1)
732
+
733
+ @docstring._snippet_manager
734
+ def __call__(self, x, pos=None): # noqa: U100
735
+ """
736
+ %(ticker.call)s
737
+ """
738
+ # Limit to significant figures
739
+ digits = AutoFormatter._decimal_place(x) + self._sigfig - 1
740
+ scale = self._base * 10**-digits
741
+ x = scale * round(x / scale)
742
+
743
+ # Create the string
744
+ decimal_point = AutoFormatter._get_default_decimal_point()
745
+ precision = max(0, digits) + max(0, AutoFormatter._decimal_place(self._base))
746
+ string = ("{:.%df}" % precision).format(x)
747
+ string = string.replace(".", decimal_point)
748
+
749
+ # Custom string formatting
750
+ string = AutoFormatter._minus_format(string)
751
+ if self._zerotrim:
752
+ string = AutoFormatter._trim_trailing_zeros(string, decimal_point)
753
+ return string
754
+
755
+
756
+ class FracFormatter(mticker.Formatter):
757
+ r"""
758
+ Format numbers as integers or integer fractions. Optionally express the
759
+ values relative to some constant like `numpy.pi`.
760
+ """
761
+
762
+ def __init__(self, symbol="", number=1):
763
+ r"""
764
+ Parameters
765
+ ----------
766
+ symbol : str, default: ''
767
+ The constant symbol, e.g. ``r'$\pi$'``.
768
+ number : float, default: 1
769
+ The constant value, e.g. `numpy.pi`.
770
+
771
+ Note
772
+ ----
773
+ The fractions shown by this formatter are resolved using the builtin
774
+ `fractions.Fraction` class and `fractions.Fraction.limit_denominator`.
775
+
776
+ See also
777
+ --------
778
+ ultraplot.constructor.Formatter
779
+ ultraplot.ticker.AutoFormatter
780
+ """
781
+ self._symbol = symbol
782
+ self._number = number
783
+ super().__init__()
784
+
785
+ @docstring._snippet_manager
786
+ def __call__(self, x, pos=None): # noqa: U100
787
+ """
788
+ %(ticker.call)s
789
+ """
790
+ frac = Fraction(x / self._number).limit_denominator()
791
+ symbol = self._symbol
792
+ if x == 0:
793
+ string = "0"
794
+ elif frac.denominator == 1: # denominator is one
795
+ if frac.numerator == 1 and symbol:
796
+ string = f"{symbol:s}"
797
+ elif frac.numerator == -1 and symbol:
798
+ string = f"-{symbol:s}"
799
+ else:
800
+ string = f"{frac.numerator:d}{symbol:s}"
801
+ else:
802
+ if frac.numerator == 1 and symbol: # numerator is +/-1
803
+ string = f"{symbol:s}/{frac.denominator:d}"
804
+ elif frac.numerator == -1 and symbol:
805
+ string = f"-{symbol:s}/{frac.denominator:d}"
806
+ else: # and again make sure we use unicode minus!
807
+ string = f"{frac.numerator:d}{symbol:s}/{frac.denominator:d}"
808
+ string = AutoFormatter._minus_format(string)
809
+ return string
810
+
811
+
812
+ class _CartopyFormatter(object):
813
+ """
814
+ Mixin class for cartopy formatters.
815
+ """
816
+
817
+ # NOTE: Cartopy formatters pre 0.18 required axis, and *always* translated
818
+ # input values from map projection coordinates to Plate Carrée coordinates.
819
+ # After 0.18 you can avoid this behavior by not setting axis but really
820
+ # dislike that inconsistency. Solution is temporarily assign PlateCarre().
821
+ def __init__(self, *args, **kwargs):
822
+ import cartopy # noqa: F401 (ensure available)
823
+
824
+ super().__init__(*args, **kwargs)
825
+
826
+ def __call__(self, value, pos=None):
827
+ ctx = context._empty_context()
828
+ if self.axis is not None:
829
+ ctx = context._state_context(self.axis.axes, projection=ccrs.PlateCarree())
830
+ with ctx:
831
+ return super().__call__(value, pos)
832
+
833
+
834
+ class DegreeFormatter(_CartopyFormatter, _PlateCarreeFormatter):
835
+ """
836
+ Formatter for longitude and latitude gridline labels.
837
+ Adapted from cartopy.
838
+ """
839
+
840
+ @docstring._snippet_manager
841
+ def __init__(self, *args, **kwargs):
842
+ """
843
+ %(ticker.dms)s
844
+ """
845
+ super().__init__(*args, **kwargs)
846
+
847
+ def _apply_transform(self, value, *args, **kwargs): # noqa: U100
848
+ return value
849
+
850
+ def _hemisphere(self, value, *args, **kwargs): # noqa: U100
851
+ return ""
852
+
853
+
854
+ class LongitudeFormatter(_CartopyFormatter, LongitudeFormatter):
855
+ """
856
+ Format longitude gridline labels. Adapted from
857
+ `cartopy.mpl.ticker.LongitudeFormatter`.
858
+ """
859
+
860
+ @docstring._snippet_manager
861
+ def __init__(self, *args, **kwargs):
862
+ """
863
+ %(ticker.dms)s
864
+ """
865
+ super().__init__(*args, **kwargs)
866
+
867
+
868
+ class LatitudeFormatter(_CartopyFormatter, LatitudeFormatter):
869
+ """
870
+ Format latitude gridline labels. Adapted from
871
+ `cartopy.mpl.ticker.LatitudeFormatter`.
872
+ """
873
+
874
+ @docstring._snippet_manager
875
+ def __init__(self, *args, **kwargs):
876
+ """
877
+ %(ticker.dms)s
878
+ """
879
+ super().__init__(*args, **kwargs)