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/axes/plot.py ADDED
@@ -0,0 +1,4569 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ The second-level axes subclass used for all ultraplot figures.
4
+ Implements plotting method overrides.
5
+ """
6
+ import contextlib
7
+ import inspect
8
+ import itertools
9
+ import re
10
+ import sys
11
+ from numbers import Integral, Number
12
+ from typing import Any, Iterable
13
+
14
+ import matplotlib.artist as martist
15
+ import matplotlib.axes as maxes
16
+ import matplotlib.cbook as cbook
17
+ import matplotlib.cm as mcm
18
+ import matplotlib.collections as mcollections
19
+ import matplotlib.colors as mcolors
20
+ import matplotlib.contour as mcontour
21
+ import matplotlib.image as mimage
22
+ import matplotlib.lines as mlines
23
+ import matplotlib.patches as mpatches
24
+ import matplotlib.ticker as mticker
25
+ import numpy as np
26
+ import numpy.ma as ma
27
+
28
+ from .. import colors as pcolors
29
+ from .. import constructor, utils
30
+ from ..config import rc
31
+ from ..internals import ic # noqa: F401
32
+ from ..internals import (
33
+ _get_aliases,
34
+ _not_none,
35
+ _pop_kwargs,
36
+ _pop_params,
37
+ _pop_props,
38
+ context,
39
+ docstring,
40
+ guides,
41
+ inputs,
42
+ warnings,
43
+ )
44
+ from . import base
45
+
46
+ try:
47
+ from cartopy.crs import PlateCarree
48
+ except ModuleNotFoundError:
49
+ PlateCarree = object
50
+
51
+ __all__ = ["PlotAxes"]
52
+
53
+
54
+ # Constants
55
+ # NOTE: Increased from native linewidth of 0.25 matplotlib uses for grid box edges.
56
+ # This is half of rc['patch.linewidth'] of 0.6. Half seems like a nice default.
57
+ EDGEWIDTH = 0.3
58
+
59
+ # Data argument docstrings
60
+ _args_1d_docstring = """
61
+ *args : {y} or {x}, {y}
62
+ The data passed as positional or keyword arguments. Interpreted as follows:
63
+
64
+ * If only `{y}` coordinates are passed, try to infer the `{x}` coordinates
65
+ from the `~pandas.Series` or `~pandas.DataFrame` indices or the
66
+ `~xarray.DataArray` coordinates. Otherwise, the `{x}` coordinates
67
+ are ``np.arange(0, {y}.shape[0])``.
68
+ * If the `{y}` coordinates are a 2D array, plot each column of data in succession
69
+ (except where each column of data represents a statistical distribution, as with
70
+ ``boxplot``, ``violinplot``, or when using ``means=True`` or ``medians=True``).
71
+ * If any arguments are `pint.Quantity`, auto-add the pint unit registry
72
+ to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`.
73
+ A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
74
+ """
75
+ _args_1d_multi_docstring = """
76
+ *args : {y}2 or {x}, {y}2, or {x}, {y}1, {y}2
77
+ The data passed as positional or keyword arguments. Interpreted as follows:
78
+
79
+ * If only `{y}` coordinates are passed, try to infer the `{x}` coordinates from
80
+ the `~pandas.Series` or `~pandas.DataFrame` indices or the `~xarray.DataArray`
81
+ coordinates. Otherwise, the `{x}` coordinates are ``np.arange(0, {y}2.shape[0])``.
82
+ * If only `{x}` and `{y}2` coordinates are passed, set the `{y}1` coordinates
83
+ to zero. This draws elements originating from the zero line.
84
+ * If both `{y}1` and `{y}2` are provided, draw elements between these points. If
85
+ either are 2D, draw elements by iterating over each column.
86
+ * If any arguments are `pint.Quantity`, auto-add the pint unit registry
87
+ to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`.
88
+ A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
89
+ """
90
+ _args_2d_docstring = """
91
+ *args : {z} or x, y, {z}
92
+ The data passed as positional or keyword arguments. Interpreted as follows:
93
+
94
+ * If only {zvar} coordinates are passed, try to infer the `x` and `y` coordinates
95
+ from the `~pandas.DataFrame` indices and columns or the `~xarray.DataArray`
96
+ coordinates. Otherwise, the `y` coordinates are ``np.arange(0, y.shape[0])``
97
+ and the `x` coordinates are ``np.arange(0, y.shape[1])``.
98
+ * For ``pcolor`` and ``pcolormesh``, calculate coordinate *edges* using
99
+ `~ultraplot.utils.edges` or `~ultraplot.utils.edges2d` if *centers* were provided.
100
+ For all other methods, calculate coordinate *centers* if *edges* were provided.
101
+ * If the `x` or `y` coordinates are `pint.Quantity`, auto-add the pint unit registry
102
+ to matplotlib's unit registry using `~pint.UnitRegistry.setup_matplotlib`. If the
103
+ {zvar} coordinates are `pint.Quantity`, pass the magnitude to the plotting
104
+ command. A `pint.Quantity` embedded in an `xarray.DataArray` is also supported.
105
+ """
106
+ docstring._snippet_manager["plot.args_1d_y"] = _args_1d_docstring.format(x="x", y="y")
107
+ docstring._snippet_manager["plot.args_1d_x"] = _args_1d_docstring.format(x="y", y="x")
108
+ docstring._snippet_manager["plot.args_1d_multiy"] = _args_1d_multi_docstring.format(
109
+ x="x", y="y"
110
+ ) # noqa: E501
111
+ docstring._snippet_manager["plot.args_1d_multix"] = _args_1d_multi_docstring.format(
112
+ x="y", y="x"
113
+ ) # noqa: E501
114
+ docstring._snippet_manager["plot.args_2d"] = _args_2d_docstring.format(
115
+ z="z", zvar="`z`"
116
+ ) # noqa: E501
117
+ docstring._snippet_manager["plot.args_2d_flow"] = _args_2d_docstring.format(
118
+ z="u, v", zvar="`u` and `v`"
119
+ ) # noqa: E501
120
+
121
+
122
+ # Shared docstrings
123
+ _args_1d_shared_docstring = """
124
+ data : dict-like, optional
125
+ A dict-like dataset container (e.g., `~pandas.DataFrame` or
126
+ `~xarray.Dataset`). If passed, each data argument can optionally
127
+ be a string `key` and the arrays used for plotting are retrieved
128
+ with ``data[key]``. This is a `native matplotlib feature
129
+ <https://matplotlib.org/stable/gallery/misc/keyword_plotting.html>`__.
130
+ autoformat : bool, default: :rc:`autoformat`
131
+ Whether the `x` axis labels, `y` axis labels, axis formatters, axes titles,
132
+ legend titles, and colorbar labels are automatically configured when a
133
+ `~pandas.Series`, `~pandas.DataFrame`, `~xarray.DataArray`, or `~pint.Quantity`
134
+ is passed to the plotting command. Formatting of `pint.Quantity`
135
+ unit strings is controlled by :rc:`unitformat`.
136
+ """
137
+ _args_2d_shared_docstring = """
138
+ %(plot.args_1d_shared)s
139
+ transpose : bool, default: False
140
+ Whether to transpose the input data. This should be used when
141
+ passing datasets with column-major dimension order ``(x, y)``.
142
+ Otherwise row-major dimension order ``(y, x)`` is expected.
143
+ order : {'C', 'F'}, default: 'C'
144
+ Alternative to `transpose`. ``'C'`` corresponds to the default C-cyle
145
+ row-major ordering (equivalent to ``transpose=False``). ``'F'`` corresponds
146
+ to Fortran-style column-major ordering (equivalent to ``transpose=True``).
147
+ globe : bool, default: False
148
+ For `ultraplot.axes.GeoAxes` only. Whether to enforce global
149
+ coverage. When set to ``True`` this does the following:
150
+
151
+ #. Interpolates input data to the North and South poles by setting the data
152
+ values at the poles to the mean from latitudes nearest each pole.
153
+ #. Makes meridional coverage "circular", i.e. the last longitude coordinate
154
+ equals the first longitude coordinate plus 360\N{DEGREE SIGN}.
155
+ #. When basemap is the backend, cycles 1D longitude vectors to fit within
156
+ the map edges. For example, if the central longitude is 90\N{DEGREE SIGN},
157
+ the data is shifted so that it spans -90\N{DEGREE SIGN} to 270\N{DEGREE SIGN}.
158
+ """
159
+ docstring._snippet_manager["plot.args_1d_shared"] = _args_1d_shared_docstring
160
+ docstring._snippet_manager["plot.args_2d_shared"] = _args_2d_shared_docstring
161
+
162
+
163
+ # Auto colorbar and legend docstring
164
+ _guide_docstring = """
165
+ colorbar : bool, int, or str, optional
166
+ If not ``None``, this is a location specifying where to draw an
167
+ *inset* or *outer* colorbar from the resulting object(s). If ``True``,
168
+ the default :rc:`colorbar.loc` is used. If the same location is
169
+ used in successive plotting calls, object(s) will be added to the
170
+ existing colorbar in that location (valid for colorbars built from lists
171
+ of artists). Valid locations are shown in in `~ultraplot.axes.Axes.colorbar`.
172
+ colorbar_kw : dict-like, optional
173
+ Extra keyword args for the call to `~ultraplot.axes.Axes.colorbar`.
174
+ legend : bool, int, or str, optional
175
+ Location specifying where to draw an *inset* or *outer* legend from the
176
+ resulting object(s). If ``True``, the default :rc:`legend.loc` is used.
177
+ If the same location is used in successive plotting calls, object(s)
178
+ will be added to existing legend in that location. Valid locations
179
+ are shown in `~ultraplot.axes.Axes.legend`.
180
+ legend_kw : dict-like, optional
181
+ Extra keyword args for the call to `~ultraplot.axes.Axes.legend`.
182
+ """
183
+ docstring._snippet_manager["plot.guide"] = _guide_docstring
184
+
185
+
186
+ # Misc shared 1D plotting docstrings
187
+ _inbounds_docstring = """
188
+ inbounds : bool, default: :rc:`axes.inbounds`
189
+ Whether to restrict the default `y` (`x`) axis limits to account for only
190
+ in-bounds data when the `x` (`y`) axis limits have been locked.
191
+ See also :rcraw:`axes.inbounds` and :rcraw:`cmap.inbounds`.
192
+ """
193
+ _error_means_docstring = """
194
+ mean, means : bool, default: False
195
+ Whether to plot the means of each column for 2D `{y}` coordinates. Means
196
+ are calculated with `numpy.nanmean`. If no other arguments are specified,
197
+ this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
198
+ median, medians : bool, default: False
199
+ Whether to plot the medians of each column for 2D `{y}` coordinates. Medians
200
+ are calculated with `numpy.nanmedian`. If no other arguments arguments are
201
+ specified, this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
202
+ """
203
+ _error_bars_docstring = """
204
+ bars : bool, default: None
205
+ Shorthand for `barstd`, `barstds`.
206
+ barstd, barstds : bool, float, or 2-tuple of float, optional
207
+ Valid only if `mean` or `median` is ``True``. Standard deviation multiples for
208
+ *thin error bars* with optional whiskers (i.e., caps). If scalar, then +/- that
209
+ multiple is used. If ``True``, the default standard deviation range of +/-3 is used.
210
+ barpctile, barpctiles : bool, float, or 2-tuple of float, optional
211
+ Valid only if `mean` or `median` is ``True``. As with `barstd`, but instead
212
+ using percentiles for the error bars. If scalar, that percentile range is
213
+ used (e.g., ``90`` shows the 5th to 95th percentiles). If ``True``, the default
214
+ percentile range of 0 to 100 is used.
215
+ bardata : array-like, optional
216
+ Valid only if `mean` and `median` are ``False``. If shape is 2 x N, these
217
+ are the lower and upper bounds for the thin error bars. If shape is N, these
218
+ are the absolute, symmetric deviations from the central points.
219
+ boxes : bool, default: None
220
+ Shorthand for `boxstd`, `boxstds`.
221
+ boxstd, boxstds, boxpctile, boxpctiles, boxdata : optional
222
+ As with `barstd`, `barpctile`, and `bardata`, but for *thicker error bars*
223
+ representing a smaller interval than the thin error bars. If `boxstds` is
224
+ ``True``, the default standard deviation range of +/-1 is used. If `boxpctiles`
225
+ is ``True``, the default percentile range of 25 to 75 is used (i.e., the
226
+ interquartile range). When "boxes" and "bars" are combined, this has the
227
+ effect of drawing miniature box-and-whisker plots.
228
+ capsize : float, default: :rc:`errorbar.capsize`
229
+ The cap size for thin error bars in points.
230
+ barz, barzorder, boxz, boxzorder : float, default: 2.5
231
+ The "zorder" for the thin and thick error bars.
232
+ barc, barcolor, boxc, boxcolor \
233
+ : color-spec, default: :rc:`boxplot.whiskerprops.color`
234
+ Colors for the thin and thick error bars.
235
+ barlw, barlinewidth, boxlw, boxlinewidth \
236
+ : float, default: :rc:`boxplot.whiskerprops.linewidth`
237
+ Line widths for the thin and thick error bars, in points. The default for boxes
238
+ is 4 times :rcraw:`boxplot.whiskerprops.linewidth`.
239
+ boxm, boxmarker : bool or marker-spec, default: 'o'
240
+ Whether to draw a small marker in the middle of the box denoting
241
+ the mean or median position. Ignored if `boxes` is ``False``.
242
+ boxms, boxmarkersize : size-spec, default: ``(2 * boxlinewidth) ** 2``
243
+ The marker size for the `boxmarker` marker in points ** 2.
244
+ boxmc, boxmarkercolor, boxmec, boxmarkeredgecolor : color-spec, default: 'w'
245
+ Color, face color, and edge color for the `boxmarker` marker.
246
+ """
247
+ _error_shading_docstring = """
248
+ shade : bool, default: None
249
+ Shorthand for `shadestd`.
250
+ shadestd, shadestds, shadepctile, shadepctiles, shadedata : optional
251
+ As with `barstd`, `barpctile`, and `bardata`, but using *shading* to indicate
252
+ the error range. If `shadestds` is ``True``, the default standard deviation
253
+ range of +/-2 is used. If `shadepctiles` is ``True``, the default
254
+ percentile range of 10 to 90 is used.
255
+ fade : bool, default: None
256
+ Shorthand for `fadestd`.
257
+ fadestd, fadestds, fadepctile, fadepctiles, fadedata : optional
258
+ As with `shadestd`, `shadepctile`, and `shadedata`, but for an additional,
259
+ more faded, *secondary* shaded region. If `fadestds` is ``True``, the default
260
+ standard deviation range of +/-3 is used. If `fadepctiles` is ``True``,
261
+ the default percentile range of 0 to 100 is used.
262
+ shadec, shadecolor, fadec, fadecolor : color-spec, default: None
263
+ Colors for the different shaded regions. The parent artist color is used by default.
264
+ shadez, shadezorder, fadez, fadezorder : float, default: 1.5
265
+ The "zorder" for the different shaded regions.
266
+ shadea, shadealpha, fadea, fadealpha : float, default: 0.4, 0.2
267
+ The opacity for the different shaded regions.
268
+ shadelw, shadelinewidth, fadelw, fadelinewidth : float, default: :rc:`patch.linewidth`.
269
+ The edge line width for the shading patches.
270
+ shdeec, shadeedgecolor, fadeec, fadeedgecolor : float, default: 'none'
271
+ The edge color for the shading patches.
272
+ shadelabel, fadelabel : bool or str, optional
273
+ Labels for the shaded regions to be used as separate legend entries. To toggle
274
+ labels "on" and apply a *default* label, use e.g. ``shadelabel=True``. To apply
275
+ a *custom* label, use e.g. ``shadelabel='label'``. Otherwise, the shading is
276
+ drawn underneath the line and/or marker in the legend entry.
277
+ """
278
+ docstring._snippet_manager["plot.inbounds"] = _inbounds_docstring
279
+ docstring._snippet_manager["plot.error_means_y"] = _error_means_docstring.format(y="y")
280
+ docstring._snippet_manager["plot.error_means_x"] = _error_means_docstring.format(y="x")
281
+ docstring._snippet_manager["plot.error_bars"] = _error_bars_docstring
282
+ docstring._snippet_manager["plot.error_shading"] = _error_shading_docstring
283
+
284
+
285
+ # Color docstrings
286
+ _cycle_docstring = """
287
+ cycle : cycle-spec, optional
288
+ The cycle specifer, passed to the `~ultraplot.constructor.Cycle` constructor.
289
+ If the returned cycler is unchanged from the current cycler, the axes
290
+ cycler will not be reset to its first position. To disable property cycling
291
+ and just use black for the default color, use ``cycle=False``, ``cycle='none'``,
292
+ or ``cycle=()`` (analogous to disabling ticks with e.g. ``xformatter='none'``).
293
+ To restore the default property cycler, use ``cycle=True``.
294
+ cycle_kw : dict-like, optional
295
+ Passed to `~ultraplot.constructor.Cycle`.
296
+ """
297
+ _cmap_norm_docstring = """
298
+ cmap : colormap-spec, default: \
299
+ :rc:`cmap.sequential` or :rc:`cmap.diverging`
300
+ The colormap specifer, passed to the `~ultraplot.constructor.Colormap` constructor
301
+ function. If :rcraw:`cmap.autodiverging` is ``True`` and the normalization
302
+ range contains negative and positive values then :rcraw:`cmap.diverging` is used.
303
+ Otherwise :rcraw:`cmap.sequential` is used.
304
+ cmap_kw : dict-like, optional
305
+ Passed to `~ultraplot.constructor.Colormap`.
306
+ c, color, colors : color-spec or sequence of color-spec, optional
307
+ The color(s) used to create a `~ultraplot.colors.DiscreteColormap`.
308
+ If not passed, `cmap` is used.
309
+ norm : norm-spec, default: \
310
+ `~matplotlib.colors.Normalize` or `~ultraplot.colors.DivergingNorm`
311
+ The data value normalizer, passed to the `~ultraplot.constructor.Norm`
312
+ constructor function. If `discrete` is ``True`` then 1) this affects the default
313
+ level-generation algorithm (e.g. ``norm='log'`` builds levels in log-space) and
314
+ 2) this is passed to `~ultraplot.colors.DiscreteNorm` to scale the colors before they
315
+ are discretized (if `norm` is not already a `~ultraplot.colors.DiscreteNorm`).
316
+ If :rcraw:`cmap.autodiverging` is ``True`` and the normalization range contains
317
+ negative and positive values then `~ultraplot.colors.DivergingNorm` is used.
318
+ Otherwise `~matplotlib.colors.Normalize` is used.
319
+ norm_kw : dict-like, optional
320
+ Passed to `~ultraplot.constructor.Norm`.
321
+ extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
322
+ Direction for drawing colorbar "extensions" indicating
323
+ out-of-bounds data on the end of the colorbar.
324
+ discrete : bool, default: :rc:`cmap.discrete`
325
+ If ``False``, then `~ultraplot.colors.DiscreteNorm` is not applied to the
326
+ colormap. Instead, for non-contour plots, the number of levels will be
327
+ roughly controlled by :rcraw:`cmap.lut`. This has a similar effect to
328
+ using `levels=large_number` but it may improve rendering speed. Default is
329
+ ``True`` only for contouring commands like `~ultraplot.axes.Axes.contourf`
330
+ and pseudocolor commands like `~ultraplot.axes.Axes.pcolor`.
331
+ sequential, diverging, cyclic, qualitative : bool, default: None
332
+ Boolean arguments used if `cmap` is not passed. Set these to ``True``
333
+ to use the default :rcraw:`cmap.sequential`, :rcraw:`cmap.diverging`,
334
+ :rcraw:`cmap.cyclic`, and :rcraw:`cmap.qualitative` colormaps.
335
+ The `diverging` option also applies `~ultraplot.colors.DivergingNorm`
336
+ as the default continuous normalizer.
337
+ """
338
+ docstring._snippet_manager["plot.cycle"] = _cycle_docstring
339
+ docstring._snippet_manager["plot.cmap_norm"] = _cmap_norm_docstring
340
+
341
+
342
+ # Levels docstrings
343
+ # NOTE: In some functions we only need some components
344
+ _vmin_vmax_docstring = """
345
+ vmin, vmax : float, optional
346
+ The minimum and maximum color scale values used with the `norm` normalizer.
347
+ If `discrete` is ``False`` these are the absolute limits, and if `discrete`
348
+ is ``True`` these are the approximate limits used to automatically determine
349
+ `levels` or `values` lists at "nice" intervals. If `levels` or `values` were
350
+ already passed as lists, these are ignored, and `vmin` and `vmax` are set to
351
+ the minimum and maximum of the lists. If `robust` was passed, the default `vmin`
352
+ and `vmax` are some percentile range of the data values. Otherwise, the default
353
+ `vmin` and `vmax` are the minimum and maximum of the data values.
354
+ """
355
+ _manual_levels_docstring = """
356
+ N
357
+ Shorthand for `levels`.
358
+ levels : int or sequence of float, default: :rc:`cmap.levels`
359
+ The number of level edges or a sequence of level edges. If the former, `locator`
360
+ is used to generate this many level edges at "nice" intervals. If the latter,
361
+ the levels should be monotonically increasing or decreasing (note decreasing
362
+ levels fail with ``contour`` plots).
363
+ values : int or sequence of float, default: None
364
+ The number of level centers or a sequence of level centers. If the former,
365
+ `locator` is used to generate this many level centers at "nice" intervals.
366
+ If the latter, levels are inferred using `~ultraplot.utils.edges`.
367
+ This will override any `levels` input.
368
+ """
369
+ _auto_levels_docstring = """
370
+ robust : bool, float, or 2-tuple, default: :rc:`cmap.robust`
371
+ If ``True`` and `vmin` or `vmax` were not provided, they are
372
+ determined from the 2nd and 98th data percentiles rather than the
373
+ minimum and maximum. If float, this percentile range is used (for example,
374
+ ``90`` corresponds to the 5th to 95th percentiles). If 2-tuple of float,
375
+ these specific percentiles should be used. This feature is useful
376
+ when your data has large outliers.
377
+ inbounds : bool, default: :rc:`cmap.inbounds`
378
+ If ``True`` and `vmin` or `vmax` were not provided, when axis limits
379
+ have been explicitly restricted with `~matplotlib.axes.Axes.set_xlim`
380
+ or `~matplotlib.axes.Axes.set_ylim`, out-of-bounds data is ignored.
381
+ See also :rcraw:`cmap.inbounds` and :rcraw:`axes.inbounds`.
382
+ locator : locator-spec, default: `matplotlib.ticker.MaxNLocator`
383
+ The locator used to determine level locations if `levels` or `values` were not
384
+ already passed as lists. Passed to the `~ultraplot.constructor.Locator` constructor.
385
+ Default is `~matplotlib.ticker.MaxNLocator` with `levels` integer levels.
386
+ locator_kw : dict-like, optional
387
+ Keyword arguments passed to `matplotlib.ticker.Locator` class.
388
+ symmetric : bool, default: False
389
+ If ``True``, the normalization range or discrete colormap levels are
390
+ symmetric about zero.
391
+ positive : bool, default: False
392
+ If ``True``, the normalization range or discrete colormap levels are
393
+ positive with a minimum at zero.
394
+ negative : bool, default: False
395
+ If ``True``, the normaliation range or discrete colormap levels are
396
+ negative with a minimum at zero.
397
+ nozero : bool, default: False
398
+ If ``True``, ``0`` is removed from the level list. This is mainly useful for
399
+ single-color `~matplotlib.axes.Axes.contour` plots.
400
+ """
401
+ docstring._snippet_manager["plot.vmin_vmax"] = _vmin_vmax_docstring
402
+ docstring._snippet_manager["plot.levels_manual"] = _manual_levels_docstring
403
+ docstring._snippet_manager["plot.levels_auto"] = _auto_levels_docstring
404
+
405
+
406
+ # Labels docstrings
407
+ _label_docstring = """
408
+ label, value : float or str, optional
409
+ The single legend label or colorbar coordinate to be used for
410
+ this plotted element. Can be numeric or string. This is generally
411
+ used with 1D positional arguments.
412
+ """
413
+ _labels_1d_docstring = """
414
+ %(plot.label)s
415
+ labels, values : sequence of float or sequence of str, optional
416
+ The legend labels or colorbar coordinates used for each plotted element.
417
+ Can be numeric or string, and must match the number of plotted elements.
418
+ This is generally used with 2D positional arguments.
419
+ """
420
+ _labels_2d_docstring = """
421
+ label : str, optional
422
+ The legend label to be used for this object. In the case of
423
+ contours, this is paired with the the central artist in the artist
424
+ list returned by `matplotlib.contour.ContourSet.legend_elements`.
425
+ labels : bool, optional
426
+ Whether to apply labels to contours and grid boxes. The text will be
427
+ white when the luminance of the underlying filled contour or grid box
428
+ is less than 50 and black otherwise.
429
+ labels_kw : dict-like, optional
430
+ Ignored if `labels` is ``False``. Extra keyword args for the labels.
431
+ For contour plots, this is passed to `~matplotlib.axes.Axes.clabel`.
432
+ Otherwise, this is passed to `~matplotlib.axes.Axes.text`.
433
+ formatter, fmt : formatter-spec, optional
434
+ The `~matplotlib.ticker.Formatter` used to format number labels.
435
+ Passed to the `~ultraplot.constructor.Formatter` constructor.
436
+ formatter_kw : dict-like, optional
437
+ Keyword arguments passed to `matplotlib.ticker.Formatter` class.
438
+ precision : int, optional
439
+ The maximum number of decimal places for number labels generated
440
+ with the default formatter `~ultraplot.ticker.Simpleformatter`.
441
+ """
442
+ docstring._snippet_manager["plot.label"] = _label_docstring
443
+ docstring._snippet_manager["plot.labels_1d"] = _labels_1d_docstring
444
+ docstring._snippet_manager["plot.labels_2d"] = _labels_2d_docstring
445
+
446
+
447
+ # Negative-positive colors
448
+ _negpos_docstring = """
449
+ negpos : bool, default: False
450
+ Whether to shade {objects} where ``{pos}`` with `poscolor`
451
+ and where ``{neg}`` with `negcolor`. If ``True`` this
452
+ function will return a length-2 silent list of handles.
453
+ negcolor, poscolor : color-spec, default: :rc:`negcolor`, :rc:`poscolor`
454
+ Colors to use for the negative and positive {objects}. Ignored if
455
+ `negpos` is ``False``.
456
+ """
457
+ docstring._snippet_manager["plot.negpos_fill"] = _negpos_docstring.format(
458
+ objects="patches", neg="y2 < y1", pos="y2 >= y1"
459
+ )
460
+ docstring._snippet_manager["plot.negpos_lines"] = _negpos_docstring.format(
461
+ objects="lines", neg="ymax < ymin", pos="ymax >= ymin"
462
+ )
463
+ docstring._snippet_manager["plot.negpos_bar"] = _negpos_docstring.format(
464
+ objects="bars", neg="height < 0", pos="height >= 0"
465
+ )
466
+
467
+
468
+ # Plot docstring
469
+ _plot_docstring = """
470
+ Plot standard lines.
471
+
472
+ Parameters
473
+ ----------
474
+ %(plot.args_1d_{y})s
475
+ %(plot.args_1d_shared)s
476
+
477
+ Other parameters
478
+ ----------------
479
+ %(plot.cycle)s
480
+ %(artist.line)s
481
+ %(plot.error_means_{y})s
482
+ %(plot.error_bars)s
483
+ %(plot.error_shading)s
484
+ %(plot.inbounds)s
485
+ %(plot.labels_1d)s
486
+ %(plot.guide)s
487
+ **kwargs
488
+ Passed to `~matplotlib.axes.Axes.plot`.
489
+
490
+ See also
491
+ --------
492
+ PlotAxes.plot
493
+ PlotAxes.plotx
494
+ matplotlib.axes.Axes.plot
495
+ """
496
+ docstring._snippet_manager["plot.plot"] = _plot_docstring.format(y="y")
497
+ docstring._snippet_manager["plot.plotx"] = _plot_docstring.format(y="x")
498
+
499
+
500
+ # Step docstring
501
+ # NOTE: Internally matplotlib implements step with thin wrapper of plot
502
+ _step_docstring = """
503
+ Plot step lines.
504
+
505
+ Parameters
506
+ ----------
507
+ %(plot.args_1d_{y})s
508
+ %(plot.args_1d_shared)s
509
+
510
+ Other parameters
511
+ ----------------
512
+ %(plot.cycle)s
513
+ %(artist.line)s
514
+ %(plot.inbounds)s
515
+ %(plot.labels_1d)s
516
+ %(plot.guide)s
517
+ **kwargs
518
+ Passed to `~matplotlib.axes.Axes.step`.
519
+
520
+ See also
521
+ --------
522
+ PlotAxes.step
523
+ PlotAxes.stepx
524
+ matplotlib.axes.Axes.step
525
+ """
526
+ docstring._snippet_manager["plot.step"] = _step_docstring.format(y="y")
527
+ docstring._snippet_manager["plot.stepx"] = _step_docstring.format(y="x")
528
+
529
+
530
+ # Stem docstring
531
+ _stem_docstring = """
532
+ Plot stem lines.
533
+
534
+ Parameters
535
+ ----------
536
+ %(plot.args_1d_{y})s
537
+ %(plot.args_1d_shared)s
538
+
539
+ Other parameters
540
+ ----------------
541
+ %(plot.cycle)s
542
+ %(plot.inbounds)s
543
+ %(plot.guide)s
544
+ **kwargs
545
+ Passed to `~matplotlib.axes.Axes.stem`.
546
+ """
547
+ docstring._snippet_manager["plot.stem"] = _stem_docstring.format(y="x")
548
+ docstring._snippet_manager["plot.stemx"] = _stem_docstring.format(y="x")
549
+
550
+
551
+ # Lines docstrings
552
+ _lines_docstring = """
553
+ Plot {orientation} lines.
554
+
555
+ Parameters
556
+ ----------
557
+ %(plot.args_1d_multi{y})s
558
+ %(plot.args_1d_shared)s
559
+
560
+ Other parameters
561
+ ----------------
562
+ stack, stacked : bool, default: False
563
+ Whether to "stack" lines from successive columns of {y} data
564
+ or plot lines on top of each other.
565
+ %(plot.cycle)s
566
+ %(artist.line)s
567
+ %(plot.negpos_lines)s
568
+ %(plot.inbounds)s
569
+ %(plot.labels_1d)s
570
+ %(plot.guide)s
571
+ **kwargs
572
+ Passed to `~matplotlib.axes.Axes.{prefix}lines`.
573
+
574
+ See also
575
+ --------
576
+ PlotAxes.vlines
577
+ PlotAxes.hlines
578
+ matplotlib.axes.Axes.vlines
579
+ matplotlib.axes.Axes.hlines
580
+ """
581
+ docstring._snippet_manager["plot.vlines"] = _lines_docstring.format(
582
+ y="y", prefix="v", orientation="vertical"
583
+ )
584
+ docstring._snippet_manager["plot.hlines"] = _lines_docstring.format(
585
+ y="x", prefix="h", orientation="horizontal"
586
+ )
587
+
588
+
589
+ # Scatter docstring
590
+ _parametric_docstring = """
591
+ Plot a parametric line.
592
+
593
+ Parameters
594
+ ----------
595
+ %(plot.args_1d_y)s
596
+ c, color, colors, values, labels : sequence of float, str, or color-spec, optional
597
+ The parametric coordinate(s). These can be passed as a third positional
598
+ argument or as a keyword argument. If they are float, the colors will be
599
+ determined from `norm` and `cmap`. If they are strings, the color values
600
+ will be ``np.arange(len(colors))`` and eventual colorbar ticks will
601
+ be labeled with the strings. If they are colors, they are used for the
602
+ line segments and `cmap` is ignored -- for example, ``colors='blue'``
603
+ makes a monochromatic "parametric" line.
604
+ interp : int, default: 0
605
+ Interpolate to this many additional points between the parametric
606
+ coordinates. This can be increased to make the color gradations
607
+ between a small number of coordinates appear "smooth".
608
+ %(plot.args_1d_shared)s
609
+
610
+ Other parameters
611
+ ----------------
612
+ %(plot.cmap_norm)s
613
+ %(plot.vmin_vmax)s
614
+ %(plot.inbounds)s
615
+ scalex, scaley : bool, optional
616
+ Whether the view limits are adapted to the data limits. The values are
617
+ passed on to `~matplotlib.axes.Axes.autoscale_view`.
618
+ %(plot.label)s
619
+ %(plot.guide)s
620
+ **kwargs
621
+ Valid `~matplotlib.collections.LineCollection` properties.
622
+
623
+ Returns
624
+ -------
625
+ `~matplotlib.collections.LineCollection`
626
+ The parametric line. See `this matplotlib example \
627
+ <https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line>`__.
628
+
629
+ See also
630
+ --------
631
+ PlotAxes.plot
632
+ PlotAxes.plotx
633
+ matplotlib.collections.LineCollection
634
+ """
635
+ docstring._snippet_manager["plot.parametric"] = _parametric_docstring
636
+
637
+
638
+ # Scatter function docstring
639
+ _scatter_docstring = """
640
+ Plot markers with flexible keyword arguments.
641
+
642
+ Parameters
643
+ ----------
644
+ %(plot.args_1d_{y})s
645
+ s, size, ms, markersize : float or array-like or unit-spec, optional
646
+ The marker size area(s). If this is an array matching the shape of `x` and `y`,
647
+ the units are scaled by `smin` and `smax`. If this contains unit string(s), it
648
+ is processed by `~ultraplot.utils.units` and represents the width rather than area.
649
+ c, color, colors, mc, markercolor, markercolors, fc, facecolor, facecolors \
650
+ : array-like or color-spec, optional
651
+ The marker color(s). If this is an array matching the shape of `x` and `y`,
652
+ the colors are generated using `cmap`, `norm`, `vmin`, and `vmax`. Otherwise,
653
+ this should be a valid matplotlib color.
654
+ smin, smax : float, optional
655
+ The minimum and maximum marker size area in units ``points ** 2``. Ignored
656
+ if `absolute_size` is ``True``. Default value for `smin` is ``1`` and for
657
+ `smax` is the square of :rc:`lines.markersize`.
658
+ area_size : bool, default: True
659
+ Whether the marker sizes `s` are scaled by area or by radius. The default
660
+ ``True`` is consistent with matplotlib. When `absolute_size` is ``True``,
661
+ the `s` units are ``points ** 2`` if `area_size` is ``True`` and ``points``
662
+ if `area_size` is ``False``.
663
+ absolute_size : bool, default: True or False
664
+ Whether `s` should be taken to represent "absolute" marker sizes in units
665
+ ``points`` or ``points ** 2`` or "relative" marker sizes scaled by `smin`
666
+ and `smax`. Default is ``True`` if `s` is scalar and ``False`` if `s` is
667
+ array-like or `smin` or `smax` were passed.
668
+ %(plot.vmin_vmax)s
669
+ %(plot.args_1d_shared)s
670
+
671
+ Other parameters
672
+ ----------------
673
+ %(plot.cmap_norm)s
674
+ %(plot.levels_manual)s
675
+ %(plot.levels_auto)s
676
+ %(plot.cycle)s
677
+ lw, linewidth, linewidths, mew, markeredgewidth, markeredgewidths \
678
+ : float or sequence, optional
679
+ The marker edge width(s).
680
+ edgecolors, markeredgecolor, markeredgecolors \
681
+ : color-spec or sequence, optional
682
+ The marker edge color(s).
683
+ %(plot.error_means_{y})s
684
+ %(plot.error_bars)s
685
+ %(plot.error_shading)s
686
+ %(plot.inbounds)s
687
+ %(plot.labels_1d)s
688
+ %(plot.guide)s
689
+ **kwargs
690
+ Passed to `~matplotlib.axes.Axes.scatter`.
691
+
692
+ See also
693
+ --------
694
+ PlotAxes.scatter
695
+ PlotAxes.scatterx
696
+ matplotlib.axes.Axes.scatter
697
+ """
698
+ docstring._snippet_manager["plot.scatter"] = _scatter_docstring.format(y="y")
699
+ docstring._snippet_manager["plot.scatterx"] = _scatter_docstring.format(y="x")
700
+
701
+
702
+ # Bar function docstring
703
+ _bar_docstring = """
704
+ Plot individual, grouped, or stacked bars.
705
+
706
+ Parameters
707
+ ----------
708
+ %(plot.args_1d_{y})s
709
+ width : float or array-like, default: 0.8
710
+ The width(s) of the bars. Can be passed as a third positional argument. If
711
+ `absolute_width` is ``True`` (the default) these are in units relative to the
712
+ {x} coordinate step size. Otherwise these are in {x} coordinate units.
713
+ {bottom} : float or array-like, default: 0
714
+ The coordinate(s) of the {bottom} edge of the bars.
715
+ Can be passed as a fourth positional argument.
716
+ absolute_width : bool, default: False
717
+ Whether to make the `width` units *absolute*. If ``True``,
718
+ this restores the default matplotlib behavior.
719
+ stack, stacked : bool, default: False
720
+ Whether to "stack" bars from successive columns of {y}
721
+ data or plot bars side-by-side in groups.
722
+ %(plot.args_1d_shared)s
723
+
724
+ Other parameters
725
+ ----------------
726
+ %(plot.cycle)s
727
+ %(artist.patch)s
728
+ %(plot.negpos_bar)s
729
+ %(axes.edgefix)s
730
+ %(plot.error_means_{y})s
731
+ %(plot.error_bars)s
732
+ %(plot.inbounds)s
733
+ %(plot.labels_1d)s
734
+ %(plot.guide)s
735
+ **kwargs
736
+ Passed to `~matplotlib.axes.Axes.bar{suffix}`.
737
+
738
+ See also
739
+ --------
740
+ PlotAxes.bar
741
+ PlotAxes.barh
742
+ matplotlib.axes.Axes.bar
743
+ matplotlib.axes.Axes.barh
744
+ """
745
+ docstring._snippet_manager["plot.bar"] = _bar_docstring.format(
746
+ x="x", y="y", bottom="bottom", suffix=""
747
+ )
748
+ docstring._snippet_manager["plot.barh"] = _bar_docstring.format(
749
+ x="y", y="x", bottom="left", suffix="h"
750
+ )
751
+
752
+
753
+ # Area plot docstring
754
+ _fill_docstring = """
755
+ Plot individual, grouped, or overlaid shading patches.
756
+
757
+ Parameters
758
+ ----------
759
+ %(plot.args_1d_multi{y})s
760
+ stack, stacked : bool, default: False
761
+ Whether to "stack" area patches from successive columns of {y}
762
+ data or plot area patches on top of each other.
763
+ %(plot.args_1d_shared)s
764
+
765
+ Other parameters
766
+ ----------------
767
+ where : ndarray, optional
768
+ A boolean mask for the points that should be shaded.
769
+ See `this matplotlib example \
770
+ <https://matplotlib.org/stable/gallery/pyplots/whats_new_98_4_fill_between.html>`__.
771
+ %(plot.cycle)s
772
+ %(artist.patch)s
773
+ %(plot.negpos_fill)s
774
+ %(axes.edgefix)s
775
+ %(plot.inbounds)s
776
+ %(plot.labels_1d)s
777
+ %(plot.guide)s
778
+ **kwargs
779
+ Passed to `~matplotlib.axes.Axes.fill_between{suffix}`.
780
+
781
+ See also
782
+ --------
783
+ PlotAxes.area
784
+ PlotAxes.areax
785
+ PlotAxes.fill_between
786
+ PlotAxes.fill_betweenx
787
+ matplotlib.axes.Axes.fill_between
788
+ matplotlib.axes.Axes.fill_betweenx
789
+ """
790
+ docstring._snippet_manager["plot.fill_between"] = _fill_docstring.format(
791
+ x="x", y="y", suffix=""
792
+ )
793
+ docstring._snippet_manager["plot.fill_betweenx"] = _fill_docstring.format(
794
+ x="y", y="x", suffix="x"
795
+ )
796
+
797
+
798
+ # Box plot docstrings
799
+ _boxplot_docstring = """
800
+ Plot {orientation} boxes and whiskers with a nice default style.
801
+
802
+ Parameters
803
+ ----------
804
+ %(plot.args_1d_{y})s
805
+ %(plot.args_1d_shared)s
806
+
807
+ Other parameters
808
+ ----------------
809
+ fill : bool, default: True
810
+ Whether to fill the box with a color.
811
+ mean, means : bool, default: False
812
+ If ``True``, this passes ``showmeans=True`` and ``meanline=True`` to
813
+ `matplotlib.axes.Axes.boxplot`. Adds mean lines alongside the median.
814
+ %(plot.cycle)s
815
+ %(artist.patch_black)s
816
+ m, marker, ms, markersize : float or str, optional
817
+ Marker style and size for the 'fliers', i.e. outliers. See the
818
+ ``boxplot.flierprops`` `~matplotlib.rcParams` settings.
819
+ meanls, medianls, meanlinestyle, medianlinestyle, meanlinestyles, medianlinestyles \
820
+ : str, optional
821
+ Line style for the mean and median lines drawn across the box.
822
+ See the ``boxplot.meanprops`` and ``boxplot.medianprops``
823
+ `~matplotlib.rcParams` settings.
824
+ boxc, capc, whiskerc, flierc, meanc, medianc, \
825
+ boxcolor, capcolor, whiskercolor, fliercolor, meancolor, mediancolor \
826
+ boxcolors, capcolors, whiskercolors, fliercolors, meancolors, mediancolors \
827
+ : color-spec or sequence, optional
828
+ Color of various boxplot components. If a sequence, should be the same length as
829
+ the number of boxes. These are shorthands so you don't have to pass e.g. a
830
+ `boxprops` dictionary keyword. See the ``boxplot.boxprops``, ``boxplot.capprops``,
831
+ ``boxplot.whiskerprops``, ``boxplot.flierprops``, ``boxplot.meanprops``, and
832
+ ``boxplot.medianprops`` `~matplotlib.rcParams` settings.
833
+ boxlw, caplw, whiskerlw, flierlw, meanlw, medianlw, boxlinewidth, caplinewidth, \
834
+ meanlinewidth, medianlinewidth, whiskerlinewidth, flierlinewidth, boxlinewidths, \
835
+ caplinewidths, meanlinewidths, medianlinewidths, whiskerlinewidths, flierlinewidths \
836
+ : float, optional
837
+ Line width of various boxplot components. These are shorthands so
838
+ you don't have to pass e.g. a `boxprops` dictionary keyword.
839
+ See the ``boxplot.boxprops``, ``boxplot.capprops``, ``boxplot.whiskerprops``,
840
+ ``boxplot.flierprops``, ``boxplot.meanprops``, and ``boxplot.medianprops``
841
+ `~matplotlib.rcParams` settings.
842
+ %(plot.labels_1d)s
843
+ **kwargs
844
+ Passed to `matplotlib.axes.Axes.boxplot`.
845
+
846
+ See also
847
+ --------
848
+ PlotAxes.boxes
849
+ PlotAxes.boxesh
850
+ PlotAxes.boxplot
851
+ PlotAxes.boxploth
852
+ matplotlib.axes.Axes.boxplot
853
+ """
854
+ docstring._snippet_manager["plot.boxplot"] = _boxplot_docstring.format(
855
+ y="y", orientation="vertical"
856
+ )
857
+ docstring._snippet_manager["plot.boxploth"] = _boxplot_docstring.format(
858
+ y="x", orientation="horizontal"
859
+ )
860
+
861
+
862
+ # Violin plot docstrings
863
+ _violinplot_docstring = """
864
+ Plot {orientation} violins with a nice default style matching
865
+ `this matplotlib example \
866
+ <https://matplotlib.org/stable/gallery/statistics/customized_violin.html>`__.
867
+
868
+ Parameters
869
+ ----------
870
+ %(plot.args_1d_{y})s
871
+ %(plot.args_1d_shared)s
872
+
873
+ Other parameters
874
+ ----------------
875
+ %(plot.cycle)s
876
+ %(artist.patch_black)s
877
+ %(plot.labels_1d)s
878
+ showmeans, showmedians : bool, optional
879
+ Interpreted as ``means=True`` and ``medians=True`` when passed.
880
+ showextrema : bool, optional
881
+ Interpreted as ``barpctiles=True`` when passed (i.e. shows minima and maxima).
882
+ %(plot.error_bars)s
883
+ **kwargs
884
+ Passed to `matplotlib.axes.Axes.violinplot`.
885
+
886
+ See also
887
+ --------
888
+ PlotAxes.violin
889
+ PlotAxes.violinh
890
+ PlotAxes.violinplot
891
+ PlotAxes.violinploth
892
+ matplotlib.axes.Axes.violinplot
893
+ """
894
+ docstring._snippet_manager["plot.violinplot"] = _violinplot_docstring.format(
895
+ y="y", orientation="vertical"
896
+ )
897
+ docstring._snippet_manager["plot.violinploth"] = _violinplot_docstring.format(
898
+ y="x", orientation="horizontal"
899
+ )
900
+
901
+
902
+ # 1D histogram docstrings
903
+ _hist_docstring = """
904
+ Plot {orientation} histograms.
905
+
906
+ Parameters
907
+ ----------
908
+ %(plot.args_1d_{y})s
909
+ bins : int or sequence of float, optional
910
+ The bin count or exact bin edges.
911
+ %(plot.weights)s
912
+ histtype : {{'bar', 'barstacked', 'step', 'stepfilled'}}, optional
913
+ The histogram type. See `matplotlib.axes.Axes.hist` for details.
914
+ width, rwidth : float, default: 0.8 or 1
915
+ The bar width(s) for bar-type histograms relative to the bin size. Default
916
+ is ``0.8`` for multiple columns of unstacked data and ``1`` otherwise.
917
+ stack, stacked : bool, optional
918
+ Whether to "stack" successive columns of {y} data for bar-type histograms
919
+ or show side-by-side in groups. Setting this to ``False`` is equivalent to
920
+ ``histtype='bar'`` and to ``True`` is equivalent to ``histtype='barstacked'``.
921
+ fill, filled : bool, optional
922
+ Whether to "fill" step-type histograms or just plot the edges. Setting
923
+ this to ``False`` is equivalent to ``histtype='step'`` and to ``True``
924
+ is equivalent to ``histtype='stepfilled'``.
925
+ %(plot.args_1d_shared)s
926
+
927
+ Other parameters
928
+ ----------------
929
+ %(plot.cycle)s
930
+ %(artist.patch)s
931
+ %(axes.edgefix)s
932
+ %(plot.labels_1d)s
933
+ %(plot.guide)s
934
+ **kwargs
935
+ Passed to `~matplotlib.axes.Axes.hist`.
936
+
937
+ See also
938
+ --------
939
+ PlotAxes.hist
940
+ PlotAxes.histh
941
+ matplotlib.axes.Axes.hist
942
+ """
943
+ _weights_docstring = """
944
+ weights : array-like, optional
945
+ The weights associated with each point. If string this
946
+ can be retrieved from `data` (see below).
947
+ """
948
+ docstring._snippet_manager["plot.weights"] = _weights_docstring
949
+ docstring._snippet_manager["plot.hist"] = _hist_docstring.format(
950
+ y="x", orientation="vertical"
951
+ )
952
+ docstring._snippet_manager["plot.histh"] = _hist_docstring.format(
953
+ y="x", orientation="horizontal"
954
+ )
955
+
956
+
957
+ # 2D histogram docstrings
958
+ _hist2d_docstring = """
959
+ Plot a {descrip}.
960
+ standard 2D histogram.
961
+
962
+ Parameters
963
+ ----------
964
+ %(plot.args_1d_y)s{bins}
965
+ %(plot.weights)s
966
+ %(plot.args_1d_shared)s
967
+
968
+ Other parameters
969
+ ----------------
970
+ %(plot.cmap_norm)s
971
+ %(plot.vmin_vmax)s
972
+ %(plot.levels_manual)s
973
+ %(plot.levels_auto)s
974
+ %(plot.labels_2d)s
975
+ %(plot.guide)s
976
+ **kwargs
977
+ Passed to `~matplotlib.axes.Axes.{command}`.
978
+
979
+ See also
980
+ --------
981
+ PlotAxes.hist2d
982
+ PlotAxes.hexbin
983
+ matplotlib.axes.Axes.{command}
984
+ """
985
+ _bins_docstring = """
986
+ bins : int or 2-tuple of int, or array-like or 2-tuple of array-like, optional
987
+ The bin count or exact bin edges for each dimension or both dimensions.
988
+ """.rstrip()
989
+ docstring._snippet_manager["plot.hist2d"] = _hist2d_docstring.format(
990
+ command="hist2d", descrip="standard 2D histogram", bins=_bins_docstring
991
+ )
992
+ docstring._snippet_manager["plot.hexbin"] = _hist2d_docstring.format(
993
+ command="hexbin", descrip="2D hexagonally binned histogram", bins=""
994
+ )
995
+
996
+
997
+ # Pie chart docstring
998
+ _pie_docstring = """
999
+ Plot a pie chart.
1000
+
1001
+ Parameters
1002
+ ----------
1003
+ %(plot.args_1d_y)s
1004
+ %(plot.args_1d_shared)s
1005
+
1006
+ Other parameters
1007
+ ----------------
1008
+ %(plot.cycle)s
1009
+ %(artist.patch)s
1010
+ %(axes.edgefix)s
1011
+ %(plot.labels_1d)s
1012
+ labelpad, labeldistance : float, optional
1013
+ The distance at which labels are drawn in radial coordinates.
1014
+
1015
+ See also
1016
+ --------
1017
+ matplotlib.axes.Axes.pie
1018
+ """
1019
+ docstring._snippet_manager["plot.pie"] = _pie_docstring
1020
+
1021
+
1022
+ # Contour docstrings
1023
+ _contour_docstring = """
1024
+ Plot {descrip}.
1025
+
1026
+ Parameters
1027
+ ----------
1028
+ %(plot.args_2d)s
1029
+
1030
+ %(plot.args_2d_shared)s
1031
+
1032
+ Other parameters
1033
+ ----------------
1034
+ %(plot.cmap_norm)s
1035
+ %(plot.vmin_vmax)s
1036
+ %(plot.levels_manual)s
1037
+ %(plot.levels_auto)s
1038
+ %(artist.collection_contour)s{edgefix}
1039
+ %(plot.labels_2d)s
1040
+ %(plot.guide)s
1041
+ **kwargs
1042
+ Passed to `matplotlib.axes.Axes.{command}`.
1043
+
1044
+ See also
1045
+ --------
1046
+ PlotAxes.contour
1047
+ PlotAxes.contourf
1048
+ PlotAxes.tricontour
1049
+ PlotAxes.tricontourf
1050
+ matplotlib.axes.Axes.{command}
1051
+ """
1052
+ docstring._snippet_manager["plot.contour"] = _contour_docstring.format(
1053
+ descrip="contour lines", command="contour", edgefix=""
1054
+ )
1055
+ docstring._snippet_manager["plot.contourf"] = _contour_docstring.format(
1056
+ descrip="filled contours",
1057
+ command="contourf",
1058
+ edgefix="%(axes.edgefix)s\n",
1059
+ )
1060
+ docstring._snippet_manager["plot.tricontour"] = _contour_docstring.format(
1061
+ descrip="contour lines on a triangular grid", command="tricontour", edgefix=""
1062
+ )
1063
+ docstring._snippet_manager["plot.tricontourf"] = _contour_docstring.format(
1064
+ descrip="filled contours on a triangular grid",
1065
+ command="tricontourf",
1066
+ edgefix="\n%(axes.edgefix)s", # noqa: E501
1067
+ )
1068
+
1069
+
1070
+ # Pcolor docstring
1071
+ _pcolor_docstring = """
1072
+ Plot {descrip}.
1073
+
1074
+ Parameters
1075
+ ----------
1076
+ %(plot.args_2d)s
1077
+
1078
+ %(plot.args_2d_shared)s{aspect}
1079
+
1080
+ Other parameters
1081
+ ----------------
1082
+ %(plot.cmap_norm)s
1083
+ %(plot.vmin_vmax)s
1084
+ %(plot.levels_manual)s
1085
+ %(plot.levels_auto)s
1086
+ %(artist.collection_pcolor)s
1087
+ %(axes.edgefix)s
1088
+ %(plot.labels_2d)s
1089
+ %(plot.guide)s
1090
+ **kwargs
1091
+ Passed to `matplotlib.axes.Axes.{command}`.
1092
+
1093
+ See also
1094
+ --------
1095
+ PlotAxes.pcolor
1096
+ PlotAxes.pcolormesh
1097
+ PlotAxes.pcolorfast
1098
+ PlotAxes.heatmap
1099
+ PlotAxes.tripcolor
1100
+ matplotlib.axes.Axes.{command}
1101
+ """
1102
+ _heatmap_descrip = """
1103
+ grid boxes with formatting suitable for heatmaps. Ensures square grid
1104
+ boxes, adds major ticks to the center of each grid box, disables minor
1105
+ ticks and gridlines, and sets :rcraw:`cmap.discrete` to ``False`` by default
1106
+ """.strip()
1107
+ _heatmap_aspect = """
1108
+ aspect : {'equal', 'auto'} or float, default: :rc:`image.aspet`
1109
+ Modify the axes aspect ratio. The aspect ratio is of particular relevance for
1110
+ heatmaps since it may lead to non-square grid boxes. This parameter is a shortcut
1111
+ for calling `~matplotlib.axes.set_aspect`. The options are as follows:
1112
+
1113
+ * Number: The data aspect ratio.
1114
+ * ``'equal'``: A data aspect ratio of 1.
1115
+ * ``'auto'``: Allows the data aspect ratio to change depending on
1116
+ the layout. In general this results in non-square grid boxes.
1117
+ """.rstrip()
1118
+ docstring._snippet_manager["plot.pcolor"] = _pcolor_docstring.format(
1119
+ descrip="irregular grid boxes", command="pcolor", aspect=""
1120
+ )
1121
+ docstring._snippet_manager["plot.pcolormesh"] = _pcolor_docstring.format(
1122
+ descrip="regular grid boxes", command="pcolormesh", aspect=""
1123
+ )
1124
+ docstring._snippet_manager["plot.pcolorfast"] = _pcolor_docstring.format(
1125
+ descrip="grid boxes quickly", command="pcolorfast", aspect=""
1126
+ )
1127
+ docstring._snippet_manager["plot.tripcolor"] = _pcolor_docstring.format(
1128
+ descrip="triangular grid boxes", command="tripcolor", aspect=""
1129
+ )
1130
+ docstring._snippet_manager["plot.heatmap"] = _pcolor_docstring.format(
1131
+ descrip=_heatmap_descrip, command="pcolormesh", aspect=_heatmap_aspect
1132
+ )
1133
+
1134
+
1135
+ # Image docstring
1136
+ _show_docstring = """
1137
+ Plot {descrip}.
1138
+
1139
+ Parameters
1140
+ ----------
1141
+ z : array-like
1142
+ The data passed as a positional argument or keyword argument.
1143
+ %(plot.args_1d_shared)s
1144
+
1145
+ Other parameters
1146
+ ----------------
1147
+ %(plot.cmap_norm)s
1148
+ %(plot.vmin_vmax)s
1149
+ %(plot.levels_manual)s
1150
+ %(plot.levels_auto)s
1151
+ %(plot.guide)s
1152
+ **kwargs
1153
+ Passed to `matplotlib.axes.Axes.{command}`.
1154
+
1155
+ See also
1156
+ --------
1157
+ ultraplot.axes.PlotAxes
1158
+ matplotlib.axes.Axes.{command}
1159
+ """
1160
+ docstring._snippet_manager["plot.imshow"] = _show_docstring.format(
1161
+ descrip="an image", command="imshow"
1162
+ )
1163
+ docstring._snippet_manager["plot.matshow"] = _show_docstring.format(
1164
+ descrip="a matrix", command="matshow"
1165
+ )
1166
+ docstring._snippet_manager["plot.spy"] = _show_docstring.format(
1167
+ descrip="a sparcity pattern", command="spy"
1168
+ )
1169
+
1170
+
1171
+ # Flow function docstring
1172
+ _flow_docstring = """
1173
+ Plot {descrip}.
1174
+
1175
+ Parameters
1176
+ ----------
1177
+ %(plot.args_2d_flow)s
1178
+
1179
+ c, color, colors : array-like or color-spec, optional
1180
+ The colors of the {descrip} passed as either a keyword argument
1181
+ or a fifth positional argument. This can be a single color or
1182
+ a color array to be scaled by `cmap` and `norm`.
1183
+ %(plot.args_2d_shared)s
1184
+
1185
+ Other parameters
1186
+ ----------------
1187
+ %(plot.cmap_norm)s
1188
+ %(plot.vmin_vmax)s
1189
+ %(plot.levels_manual)s
1190
+ %(plot.levels_auto)s
1191
+ **kwargs
1192
+ Passed to `matplotlib.axes.Axes.{command}`
1193
+
1194
+ See also
1195
+ --------
1196
+ PlotAxes.barbs
1197
+ PlotAxes.quiver
1198
+ PlotAxes.stream
1199
+ PlotAxes.streamplot
1200
+ matplotlib.axes.Axes.{command}
1201
+ """
1202
+ docstring._snippet_manager["plot.barbs"] = _flow_docstring.format(
1203
+ descrip="wind barbs", command="barbs"
1204
+ )
1205
+ docstring._snippet_manager["plot.quiver"] = _flow_docstring.format(
1206
+ descrip="quiver arrows", command="quiver"
1207
+ )
1208
+ docstring._snippet_manager["plot.stream"] = _flow_docstring.format(
1209
+ descrip="streamlines", command="streamplot"
1210
+ )
1211
+
1212
+
1213
+ def _get_vert(vert=None, orientation=None, **kwargs):
1214
+ """
1215
+ Get the orientation specified as either `vert` or `orientation`. This is
1216
+ used internally by various helper functions.
1217
+ """
1218
+ if vert is not None:
1219
+ return kwargs, vert
1220
+ elif orientation is not None:
1221
+ return kwargs, orientation != "horizontal" # should already be validated
1222
+ else:
1223
+ return kwargs, True # fallback
1224
+
1225
+
1226
+ def _parse_vert(
1227
+ vert=None, orientation=None, default_vert=None, default_orientation=None, **kwargs
1228
+ ):
1229
+ """
1230
+ Interpret both 'vert' and 'orientation' and add to outgoing keyword args
1231
+ if a default is provided.
1232
+ """
1233
+ # NOTE: Users should only pass these to hist, boxplot, or violinplot. To change
1234
+ # the plot, scatter, area, or bar orientation users should use the differently
1235
+ # named functions. Internally, however, they use these keyword args.
1236
+ if default_vert is not None:
1237
+ kwargs["vert"] = _not_none(
1238
+ vert=vert,
1239
+ orientation=None if orientation is None else orientation == "vertical",
1240
+ default=default_vert,
1241
+ )
1242
+ if default_orientation is not None:
1243
+ kwargs["orientation"] = _not_none(
1244
+ orientation=orientation,
1245
+ vert=None if vert is None else "vertical" if vert else "horizontal",
1246
+ default=default_orientation,
1247
+ )
1248
+ if kwargs.get("orientation", None) not in (None, "horizontal", "vertical"):
1249
+ raise ValueError("Orientation must be either 'horizontal' or 'vertical'.")
1250
+ return kwargs
1251
+
1252
+
1253
+ def _inside_seaborn_call():
1254
+ """
1255
+ Try to detect `seaborn` calls to `scatter` and `bar` and then automatically
1256
+ apply `absolute_size` and `absolute_width`.
1257
+ """
1258
+ frame = sys._getframe()
1259
+ absolute_names = (
1260
+ "seaborn.distributions",
1261
+ "seaborn.categorical",
1262
+ "seaborn.relational",
1263
+ "seaborn.regression",
1264
+ )
1265
+ while frame is not None:
1266
+ if frame.f_globals.get("__name__", "") in absolute_names:
1267
+ return True
1268
+ frame = frame.f_back
1269
+ return False
1270
+
1271
+
1272
+ class PlotAxes(base.Axes):
1273
+ """
1274
+ The second lowest-level `~matplotlib.axes.Axes` subclass used by ultraplot.
1275
+ Implements all plotting overrides.
1276
+ """
1277
+
1278
+ def __init__(self, *args, **kwargs):
1279
+ """
1280
+ Parameters
1281
+ ----------
1282
+ *args, **kwargs
1283
+ Passed to `ultraplot.axes.Axes`.
1284
+
1285
+ See also
1286
+ --------
1287
+ matplotlib.axes.Axes
1288
+ ultraplot.axes.Axes
1289
+ ultraplot.axes.CartesianAxes
1290
+ ultraplot.axes.PolarAxes
1291
+ ultraplot.axes.GeoAxes
1292
+ """
1293
+ super().__init__(*args, **kwargs)
1294
+
1295
+ def _call_native(self, name, *args, **kwargs):
1296
+ """
1297
+ Call the plotting method and redirect internal calls to native methods.
1298
+ """
1299
+ # NOTE: Previously allowed internal matplotlib plotting function calls to run
1300
+ # through ultraplot overrides then avoided awkward conflicts in piecemeal fashion.
1301
+ # Now prevent internal calls from running through overrides using preprocessor
1302
+ kwargs.pop("distribution", None) # remove stat distributions
1303
+ with context._state_context(self, _internal_call=True):
1304
+ if self._name == "basemap":
1305
+ obj = getattr(self.projection, name)(*args, ax=self, **kwargs)
1306
+ else:
1307
+ obj = getattr(super(), name)(*args, **kwargs)
1308
+ return obj
1309
+
1310
+ def _call_negpos(
1311
+ self,
1312
+ name,
1313
+ x,
1314
+ *ys,
1315
+ negcolor=None,
1316
+ poscolor=None,
1317
+ colorkey="facecolor",
1318
+ use_where=False,
1319
+ use_zero=False,
1320
+ **kwargs,
1321
+ ):
1322
+ """
1323
+ Call the plotting method separately for "negative" and "positive" data.
1324
+ """
1325
+ if use_where:
1326
+ kwargs.setdefault("interpolate", True) # see fill_between docs
1327
+ for key in ("color", "colors", "facecolor", "facecolors", "where"):
1328
+ value = kwargs.pop(key, None)
1329
+ if value is not None:
1330
+ warnings._warn_ultraplot(
1331
+ f"{name}() argument {key}={value!r} is incompatible with negpos=True. Ignoring." # noqa: E501
1332
+ )
1333
+ # Negative component
1334
+ yneg = list(ys) # copy
1335
+ if use_zero: # filter bar heights
1336
+ yneg[0] = inputs._safe_mask(ys[0] < 0, ys[0])
1337
+ elif use_where: # apply fill_between mask
1338
+ kwargs["where"] = ys[1] < ys[0]
1339
+ else:
1340
+ yneg = inputs._safe_mask(ys[1] < ys[0], *ys)
1341
+ kwargs[colorkey] = _not_none(negcolor, rc["negcolor"])
1342
+ negobj = self._call_native(name, x, *yneg, **kwargs)
1343
+ # Positive component
1344
+ ypos = list(ys) # copy
1345
+ if use_zero: # filter bar heights
1346
+ ypos[0] = inputs._safe_mask(ys[0] >= 0, ys[0])
1347
+ elif use_where: # apply fill_between mask
1348
+ kwargs["where"] = ys[1] >= ys[0]
1349
+ else:
1350
+ ypos = inputs._safe_mask(ys[1] >= ys[0], *ys)
1351
+ kwargs[colorkey] = _not_none(poscolor, rc["poscolor"])
1352
+ posobj = self._call_native(name, x, *ypos, **kwargs)
1353
+ return cbook.silent_list(type(negobj).__name__, (negobj, posobj))
1354
+
1355
+ def _add_auto_labels(
1356
+ self,
1357
+ obj,
1358
+ cobj=None,
1359
+ labels=False,
1360
+ labels_kw=None,
1361
+ fmt=None,
1362
+ formatter=None,
1363
+ formatter_kw=None,
1364
+ precision=None,
1365
+ ):
1366
+ """
1367
+ Add number labels. Default formatter is `~ultraplot.ticker.SimpleFormatter`
1368
+ with a default maximum precision of ``3`` decimal places.
1369
+ """
1370
+ # TODO: Add quiverkey to this!
1371
+ if not labels:
1372
+ return
1373
+ labels_kw = labels_kw or {}
1374
+ formatter_kw = formatter_kw or {}
1375
+ formatter = _not_none(
1376
+ fmt_labels_kw=labels_kw.pop("fmt", None),
1377
+ formatter_labels_kw=labels_kw.pop("formatter", None),
1378
+ fmt=fmt,
1379
+ formatter=formatter,
1380
+ default="simple",
1381
+ )
1382
+ precision = _not_none(
1383
+ formatter_kw_precision=formatter_kw.pop("precision", None),
1384
+ precision=precision,
1385
+ default=3, # should be lower than the default intended for tick labels
1386
+ )
1387
+ formatter = constructor.Formatter(
1388
+ formatter, precision=precision, **formatter_kw
1389
+ ) # noqa: E501
1390
+ if isinstance(obj, mcontour.ContourSet):
1391
+ self._add_contour_labels(obj, cobj, formatter, **labels_kw)
1392
+ elif isinstance(obj, mcollections.Collection):
1393
+ self._add_collection_labels(obj, formatter, **labels_kw)
1394
+ else:
1395
+ raise RuntimeError(f"Not possible to add labels to object {obj!r}.")
1396
+
1397
+ def _add_collection_labels(
1398
+ self,
1399
+ obj,
1400
+ fmt,
1401
+ *,
1402
+ c=None,
1403
+ color=None,
1404
+ colors=None,
1405
+ size=None,
1406
+ fontsize=None,
1407
+ **kwargs,
1408
+ ):
1409
+ """
1410
+ Add labels to pcolor boxes with support for shade-dependent text colors.
1411
+ Values are inferred from the unnormalized grid box color.
1412
+ """
1413
+ # Parse input args
1414
+ # NOTE: This function also hides grid boxes filled with NaNs to avoid ugly
1415
+ # issue where edge colors surround NaNs. Should maybe move this somewhere else.
1416
+ obj.update_scalarmappable() # update 'edgecolors' list
1417
+ color = _not_none(c=c, color=color, colors=colors)
1418
+ fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
1419
+ kwargs.setdefault("ha", "center")
1420
+ kwargs.setdefault("va", "center")
1421
+
1422
+ # Apply colors and hide edge colors for empty grids
1423
+ labs = []
1424
+ array = obj.get_array()
1425
+ paths = obj.get_paths()
1426
+ edgecolors = inputs._to_numpy_array(obj.get_edgecolors())
1427
+ if len(edgecolors) == 1:
1428
+ edgecolors = np.repeat(edgecolors, len(array), axis=0)
1429
+ for i, (path, value) in enumerate(zip(paths, array)):
1430
+ # Round to the number corresponding to the *color* rather than
1431
+ # the exact data value. Similar to contour label numbering.
1432
+ if value is ma.masked or not np.any(np.isfinite(value) == False):
1433
+ edgecolors[i, :] = 0
1434
+ continue
1435
+ if isinstance(obj.norm, pcolors.DiscreteNorm):
1436
+ value = obj.norm._norm.inverse(obj.norm(value))
1437
+ icolor = color
1438
+ if color is None:
1439
+ _, _, lum = utils.to_xyz(obj.cmap(obj.norm(value)), "hcl")
1440
+ icolor = "w" if lum < 50 else "k"
1441
+ bbox = path.get_extents()
1442
+ x = (bbox.xmin + bbox.xmax) / 2
1443
+ y = (bbox.ymin + bbox.ymax) / 2
1444
+ lab = self.text(x, y, fmt(value), color=icolor, size=fontsize, **kwargs)
1445
+ labs.append(lab)
1446
+
1447
+ obj.set_edgecolors(edgecolors)
1448
+ return labs
1449
+
1450
+ def _add_contour_labels(
1451
+ self,
1452
+ obj,
1453
+ cobj,
1454
+ fmt,
1455
+ *,
1456
+ c=None,
1457
+ color=None,
1458
+ colors=None,
1459
+ size=None,
1460
+ fontsize=None,
1461
+ inline_spacing=None,
1462
+ **kwargs,
1463
+ ):
1464
+ """
1465
+ Add labels to contours with support for shade-dependent filled contour labels.
1466
+ Text color is inferred from filled contour object and labels are always drawn
1467
+ on unfilled contour object (otherwise errors crop up).
1468
+ """
1469
+ # Parse input args
1470
+ zorder = max((h.get_zorder() for h in obj.collections), default=3)
1471
+ zorder = max(3, zorder + 1)
1472
+ kwargs.setdefault("zorder", zorder)
1473
+ colors = _not_none(c=c, color=color, colors=colors)
1474
+ fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
1475
+ inline_spacing = _not_none(inline_spacing, 2.5)
1476
+
1477
+ # Separate clabel args from text Artist args
1478
+ text_kw = {}
1479
+ clabel_keys = ("levels", "inline", "manual", "rightside_up", "use_clabeltext")
1480
+ for key in tuple(kwargs): # allow dict to change size
1481
+ if key not in clabel_keys:
1482
+ text_kw[key] = kwargs.pop(key)
1483
+
1484
+ # Draw hidden additional contour for filled contour labels
1485
+ cobj = _not_none(cobj, obj)
1486
+ if obj.filled and colors is None:
1487
+ colors = []
1488
+ for level in obj.levels:
1489
+ _, _, lum = utils.to_xyz(obj.cmap(obj.norm(level)))
1490
+ colors.append("w" if lum < 50 else "k")
1491
+
1492
+ # Draw the labels
1493
+ labs = cobj.clabel(
1494
+ fmt=fmt,
1495
+ colors=colors,
1496
+ fontsize=fontsize,
1497
+ inline_spacing=inline_spacing,
1498
+ **kwargs,
1499
+ )
1500
+ if labs is not None: # returns None if no contours
1501
+ for lab in labs:
1502
+ lab.update(text_kw)
1503
+
1504
+ return labs
1505
+
1506
+ def _add_error_bars(
1507
+ self,
1508
+ x,
1509
+ y,
1510
+ *_,
1511
+ distribution=None,
1512
+ default_barstds=False,
1513
+ default_boxstds=False,
1514
+ default_barpctiles=False,
1515
+ default_boxpctiles=False,
1516
+ default_marker=False,
1517
+ bars=None,
1518
+ boxes=None,
1519
+ barstd=None,
1520
+ barstds=None,
1521
+ barpctile=None,
1522
+ barpctiles=None,
1523
+ bardata=None,
1524
+ boxstd=None,
1525
+ boxstds=None,
1526
+ boxpctile=None,
1527
+ boxpctiles=None,
1528
+ boxdata=None,
1529
+ capsize=None,
1530
+ **kwargs,
1531
+ ):
1532
+ """
1533
+ Add up to 2 error indicators: thick "boxes" and thin "bars". The ``default``
1534
+ keywords toggle default range indicators when distributions are passed.
1535
+ """
1536
+ # Parse input args
1537
+ # NOTE: Want to keep _add_error_bars() and _add_error_shading() separate.
1538
+ # But also want default behavior where some default error indicator is shown
1539
+ # if user requests means/medians only. Result is the below kludge.
1540
+ kwargs, vert = _get_vert(**kwargs)
1541
+ barstds = _not_none(bars=bars, barstd=barstd, barstds=barstds)
1542
+ boxstds = _not_none(boxes=boxes, boxstd=boxstd, boxstds=boxstds)
1543
+ barpctiles = _not_none(barpctile=barpctile, barpctiles=barpctiles)
1544
+ boxpctiles = _not_none(boxpctile=boxpctile, boxpctiles=boxpctiles)
1545
+ if distribution is not None and not any(
1546
+ typ + mode in key
1547
+ for key in kwargs
1548
+ for typ in ("shade", "fade")
1549
+ for mode in ("", "std", "pctile", "data")
1550
+ ): # ugly kludge to check for shading
1551
+ if all(_ is None for _ in (bardata, barstds, barpctiles)):
1552
+ barstds, barpctiles = default_barstds, default_barpctiles
1553
+ if all(_ is None for _ in (boxdata, boxstds, boxpctile)):
1554
+ boxstds, boxpctiles = default_boxstds, default_boxpctiles
1555
+ showbars = any(
1556
+ _ is not None and _ is not False for _ in (barstds, barpctiles, bardata)
1557
+ )
1558
+ showboxes = any(
1559
+ _ is not None and _ is not False for _ in (boxstds, boxpctiles, boxdata)
1560
+ )
1561
+
1562
+ # Error bar properties
1563
+ edgecolor = kwargs.get("edgecolor", rc["boxplot.whiskerprops.color"])
1564
+ barprops = _pop_props(kwargs, "line", ignore="marker", prefix="bar")
1565
+ barprops["capsize"] = _not_none(capsize, rc["errorbar.capsize"])
1566
+ barprops["linestyle"] = "none"
1567
+ barprops.setdefault("color", edgecolor)
1568
+ barprops.setdefault("zorder", 2.5)
1569
+ barprops.setdefault("linewidth", rc["boxplot.whiskerprops.linewidth"])
1570
+
1571
+ # Error box properties
1572
+ # NOTE: Includes 'markerfacecolor' and 'markeredgecolor' props
1573
+ boxprops = _pop_props(kwargs, "line", prefix="box")
1574
+ boxprops["capsize"] = 0
1575
+ boxprops["linestyle"] = "none"
1576
+ boxprops.setdefault("color", barprops["color"])
1577
+ boxprops.setdefault("zorder", barprops["zorder"])
1578
+ boxprops.setdefault("linewidth", 4 * barprops["linewidth"])
1579
+
1580
+ # Box marker properties
1581
+ boxmarker = {
1582
+ key: boxprops.pop(key) for key in tuple(boxprops) if "marker" in key
1583
+ } # noqa: E501
1584
+ boxmarker["c"] = _not_none(boxmarker.pop("markerfacecolor", None), "white")
1585
+ boxmarker["s"] = _not_none(
1586
+ boxmarker.pop("markersize", None), boxprops["linewidth"] ** 0.5
1587
+ ) # noqa: E501
1588
+ boxmarker["zorder"] = boxprops["zorder"]
1589
+ boxmarker["edgecolor"] = boxmarker.pop("markeredgecolor", None)
1590
+ boxmarker["linewidth"] = boxmarker.pop("markerlinewidth", None)
1591
+ if boxmarker.get("marker") is True:
1592
+ boxmarker["marker"] = "o"
1593
+ elif default_marker:
1594
+ boxmarker.setdefault("marker", "o")
1595
+
1596
+ # Draw thin or thick error bars from distributions or explicit errdata
1597
+ # NOTE: Now impossible to make thin bar width different from cap width!
1598
+ # NOTE: Boxes must go after so scatter point can go on top
1599
+ sy = "y" if vert else "x" # yerr
1600
+ ex, ey = (x, y) if vert else (y, x)
1601
+ eobjs = []
1602
+ if showbars: # noqa: E501
1603
+ edata, _ = inputs._dist_range(
1604
+ y,
1605
+ distribution,
1606
+ stds=barstds,
1607
+ pctiles=barpctiles,
1608
+ errdata=bardata,
1609
+ stds_default=(-3, 3),
1610
+ pctiles_default=(0, 100),
1611
+ )
1612
+ if edata is not None:
1613
+ obj = self.errorbar(ex, ey, **barprops, **{sy + "err": edata})
1614
+ eobjs.append(obj)
1615
+ if showboxes: # noqa: E501
1616
+ edata, _ = inputs._dist_range(
1617
+ y,
1618
+ distribution,
1619
+ stds=boxstds,
1620
+ pctiles=boxpctiles,
1621
+ errdata=boxdata,
1622
+ stds_default=(-1, 1),
1623
+ pctiles_default=(25, 75),
1624
+ )
1625
+ if edata is not None:
1626
+ obj = self.errorbar(ex, ey, **boxprops, **{sy + "err": edata})
1627
+ if boxmarker.get("marker", None):
1628
+ self.scatter(ex, ey, **boxmarker)
1629
+ eobjs.append(obj)
1630
+
1631
+ kwargs["distribution"] = distribution
1632
+ return (*eobjs, kwargs)
1633
+
1634
+ def _add_error_shading(
1635
+ self,
1636
+ x,
1637
+ y,
1638
+ *_,
1639
+ distribution=None,
1640
+ color_key="color",
1641
+ shade=None,
1642
+ shadestd=None,
1643
+ shadestds=None,
1644
+ shadepctile=None,
1645
+ shadepctiles=None,
1646
+ shadedata=None,
1647
+ fade=None,
1648
+ fadestd=None,
1649
+ fadestds=None,
1650
+ fadepctile=None,
1651
+ fadepctiles=None,
1652
+ fadedata=None,
1653
+ shadelabel=False,
1654
+ fadelabel=False,
1655
+ **kwargs,
1656
+ ):
1657
+ """
1658
+ Add up to 2 error indicators: more opaque "shading" and less opaque "fading".
1659
+ """
1660
+ kwargs, vert = _get_vert(**kwargs)
1661
+ shadestds = _not_none(shade=shade, shadestd=shadestd, shadestds=shadestds)
1662
+ fadestds = _not_none(fade=fade, fadestd=fadestd, fadestds=fadestds)
1663
+ shadepctiles = _not_none(shadepctile=shadepctile, shadepctiles=shadepctiles)
1664
+ fadepctiles = _not_none(fadepctile=fadepctile, fadepctiles=fadepctiles)
1665
+ drawshade = any(
1666
+ _ is not None and _ is not False
1667
+ for _ in (shadestds, shadepctiles, shadedata)
1668
+ )
1669
+ drawfade = any(
1670
+ _ is not None and _ is not False for _ in (fadestds, fadepctiles, fadedata)
1671
+ )
1672
+
1673
+ # Shading properties
1674
+ shadeprops = _pop_props(kwargs, "patch", prefix="shade")
1675
+ shadeprops.setdefault("alpha", 0.4)
1676
+ shadeprops.setdefault("zorder", 1.5)
1677
+ shadeprops.setdefault("linewidth", rc["patch.linewidth"])
1678
+ shadeprops.setdefault("edgecolor", "none")
1679
+ # Fading properties
1680
+ fadeprops = _pop_props(kwargs, "patch", prefix="fade")
1681
+ fadeprops.setdefault("zorder", shadeprops["zorder"])
1682
+ fadeprops.setdefault("alpha", 0.5 * shadeprops["alpha"])
1683
+ fadeprops.setdefault("linewidth", shadeprops["linewidth"])
1684
+ fadeprops.setdefault("edgecolor", "none")
1685
+ # Get default color then apply to outgoing keyword args so
1686
+ # that plotting function will not advance to next cycler color.
1687
+ # TODO: More robust treatment of 'color' vs. 'facecolor'
1688
+ if (
1689
+ drawshade
1690
+ and shadeprops.get("facecolor", None) is None
1691
+ or drawfade
1692
+ and fadeprops.get("facecolor", None) is None
1693
+ ):
1694
+ color = kwargs.get(color_key, None)
1695
+ if color is None: # add to outgoing
1696
+ color = kwargs[color_key] = self._get_lines.get_next_color()
1697
+ shadeprops.setdefault("facecolor", color)
1698
+ fadeprops.setdefault("facecolor", color)
1699
+
1700
+ # Draw dark and light shading from distributions or explicit errdata
1701
+ eobjs = []
1702
+ fill = self.fill_between if vert else self.fill_betweenx
1703
+ if drawfade:
1704
+ edata, label = inputs._dist_range(
1705
+ y,
1706
+ distribution,
1707
+ stds=fadestds,
1708
+ pctiles=fadepctiles,
1709
+ errdata=fadedata,
1710
+ stds_default=(-3, 3),
1711
+ pctiles_default=(0, 100),
1712
+ label=fadelabel,
1713
+ absolute=True,
1714
+ )
1715
+ if edata is not None:
1716
+ eobj = fill(x, *edata, label=label, **fadeprops)
1717
+ eobjs.append(eobj)
1718
+ if drawshade:
1719
+ edata, label = inputs._dist_range(
1720
+ y,
1721
+ distribution,
1722
+ stds=shadestds,
1723
+ pctiles=shadepctiles,
1724
+ errdata=shadedata,
1725
+ stds_default=(-2, 2),
1726
+ pctiles_default=(10, 90),
1727
+ label=shadelabel,
1728
+ absolute=True,
1729
+ )
1730
+ if edata is not None:
1731
+ eobj = fill(x, *edata, label=label, **shadeprops)
1732
+ eobjs.append(eobj)
1733
+
1734
+ kwargs["distribution"] = distribution
1735
+ return (*eobjs, kwargs)
1736
+
1737
+ def _fix_contour_edges(self, method, *args, **kwargs):
1738
+ """
1739
+ Fix the filled contour edges by secretly adding solid contours with
1740
+ the same input data.
1741
+ """
1742
+ # NOTE: This is used to provide an object that can be used by 'clabel' for
1743
+ # auto-labels. Filled contours create strange artifacts.
1744
+ # NOTE: Make the default 'line width' identical to one used for pcolor plots
1745
+ # rather than rc['contour.linewidth']. See mpl pcolor() source code
1746
+ if not any(key in kwargs for key in ("linewidths", "linestyles", "edgecolors")):
1747
+ kwargs["linewidths"] = 0 # for clabel
1748
+ kwargs.setdefault("linewidths", EDGEWIDTH)
1749
+ kwargs.pop("cmap", None)
1750
+ kwargs["colors"] = kwargs.pop("edgecolors", "k")
1751
+ return self._call_native(method, *args, **kwargs)
1752
+
1753
+ def _fix_sticky_edges(self, objs, axis, *args, only=None):
1754
+ """
1755
+ Fix sticky edges for the input artists using the minimum and maximum of the
1756
+ input coordinates. This is used to copy `bar` behavior to `area` and `lines`.
1757
+ """
1758
+ for array in args:
1759
+ min_, max_ = inputs._safe_range(array)
1760
+ if min_ is None or max_ is None:
1761
+ continue
1762
+ for obj in guides._iter_iterables(objs):
1763
+ if only and not isinstance(obj, only):
1764
+ continue # e.g. ignore error bars
1765
+ convert = getattr(self, "convert_" + axis + "units")
1766
+ edges = getattr(obj.sticky_edges, axis)
1767
+ edges.extend(convert((min_, max_)))
1768
+
1769
+ @staticmethod
1770
+ def _fix_patch_edges(obj, edgefix=None, **kwargs):
1771
+ """
1772
+ Fix white lines between between filled patches and fix issues
1773
+ with colormaps that are transparent. If keyword args passed by user
1774
+ include explicit edge properties then we skip this step.
1775
+ """
1776
+ # NOTE: Use default edge width used for pcolor grid box edges. This is thick
1777
+ # enough to hide lines but thin enough to not add 'nubs' to corners of boxes.
1778
+ # See: https://github.com/jklymak/contourfIssues
1779
+ # See: https://stackoverflow.com/q/15003353/4970632
1780
+ edgefix = _not_none(edgefix, rc.edgefix, True)
1781
+ linewidth = EDGEWIDTH if edgefix is True else 0 if edgefix is False else edgefix
1782
+ if not linewidth:
1783
+ return
1784
+ keys = ("linewidth", "linestyle", "edgecolor") # patches and collections
1785
+ if any(key + suffix in kwargs for key in keys for suffix in ("", "s")):
1786
+ return
1787
+ rasterized = obj.get_rasterized() if isinstance(obj, martist.Artist) else False
1788
+ if rasterized:
1789
+ return
1790
+
1791
+ # Skip when cmap has transparency
1792
+ if hasattr(obj, "get_alpha"): # collections and contour sets use singular
1793
+ alpha = obj.get_alpha()
1794
+ if alpha is not None and alpha < 1:
1795
+ return
1796
+ if isinstance(obj, mcm.ScalarMappable):
1797
+ cmap = obj.cmap
1798
+ if not cmap._isinit:
1799
+ cmap._init()
1800
+ if not all(cmap._lut[:-1, 3] == 1): # skip for cmaps with transparency
1801
+ return
1802
+
1803
+ # Apply fixes
1804
+ # NOTE: This also covers TriContourSet returned by tricontour
1805
+ if isinstance(obj, mcontour.ContourSet):
1806
+ if obj.filled:
1807
+ obj.set_linestyle("-")
1808
+ obj.set_linewidth(linewidth)
1809
+ obj.set_edgecolor("face")
1810
+
1811
+ for contour in obj.collections:
1812
+ contour.set_linestyle("-")
1813
+ contour.set_linewidth(linewidth)
1814
+ contour.set_edgecolor("face")
1815
+ elif isinstance(obj, mcollections.Collection): # e.g. QuadMesh, PolyCollection
1816
+ obj.set_linewidth(linewidth)
1817
+ obj.set_edgecolor("face")
1818
+ elif isinstance(obj, mpatches.Patch): # e.g. Rectangle
1819
+ obj.set_linewidth(linewidth)
1820
+ obj.set_edgecolor(obj.get_facecolor())
1821
+ elif np.iterable(obj): # e.g. silent_list of BarContainer
1822
+ for element in obj:
1823
+ PlotAxes._fix_patch_edges(element, edgefix=edgefix)
1824
+ else:
1825
+ warnings._warn_ultraplot(f"Unexpected obj {obj} passed to _fix_patch_edges.")
1826
+
1827
+ @contextlib.contextmanager
1828
+ def _keep_grid_bools(self):
1829
+ """
1830
+ Preserve the gridline booleans during the operation. This prevents `pcolor`
1831
+ methods from disabling grids (mpl < 3.5) and emitting warnings (mpl >= 3.5).
1832
+ """
1833
+ # NOTE: Modern matplotlib uses _get_axis_list() but this is only to support
1834
+ # Axes3D which PlotAxes does not subclass. Safe to use xaxis and yaxis.
1835
+ bools = []
1836
+ for axis, which in itertools.product(
1837
+ (self.xaxis, self.yaxis), ("major", "minor")
1838
+ ):
1839
+ kw = getattr(axis, f"_{which}_tick_kw", {})
1840
+ bools.append(kw.get("gridOn", None))
1841
+ kw["gridOn"] = False # prevent deprecation warning
1842
+ yield
1843
+ for b, (axis, which) in zip(bools, itertools.product("xy", ("major", "minor"))):
1844
+ if b is not None:
1845
+ self.grid(b, axis=axis, which=which)
1846
+
1847
+ def _inbounds_extent(self, *, inbounds=None, **kwargs):
1848
+ """
1849
+ Capture the `inbounds` keyword arg and return data limit
1850
+ extents if it is ``True``. Otherwise return ``None``. When
1851
+ ``_inbounds_xylim`` gets ``None`` it will silently exit.
1852
+ """
1853
+ extents = None
1854
+ inbounds = _not_none(inbounds, rc["axes.inbounds"])
1855
+ if inbounds:
1856
+ extents = list(self.dataLim.extents) # ensure modifiable
1857
+ return kwargs, extents
1858
+
1859
+ def _inbounds_vlim(self, x, y, z, *, to_centers=False):
1860
+ """
1861
+ Restrict the sample data used for automatic `vmin` and `vmax` selection
1862
+ based on the existing x and y axis limits.
1863
+ """
1864
+ # Get masks
1865
+ # WARNING: Experimental, seems robust but this is not mission-critical so
1866
+ # keep this in a try-except clause for now. However *internally* we should
1867
+ # not reach this block unless everything is an array so raise that error.
1868
+ xmask = ymask = None
1869
+ if self._name != "cartesian":
1870
+ return z # TODO: support geographic projections when input is PlateCarree()
1871
+ if not all(getattr(a, "ndim", None) in (1, 2) for a in (x, y, z)):
1872
+ raise ValueError("Invalid input coordinates. Must be 1D or 2D arrays.")
1873
+ try:
1874
+ # Get centers and masks
1875
+ if to_centers and z.ndim == 2:
1876
+ x, y = inputs._to_centers(x, y, z)
1877
+ if not self.get_autoscalex_on():
1878
+ xlim = self.get_xlim()
1879
+ xmask = (x >= min(xlim)) & (x <= max(xlim))
1880
+ if not self.get_autoscaley_on():
1881
+ ylim = self.get_ylim()
1882
+ ymask = (y >= min(ylim)) & (y <= max(ylim))
1883
+ # Get subsample
1884
+ if xmask is not None and ymask is not None:
1885
+ z = (
1886
+ z[np.ix_(ymask, xmask)]
1887
+ if z.ndim == 2 and xmask.ndim == 1
1888
+ else z[ymask & xmask]
1889
+ ) # noqa: E501
1890
+ elif xmask is not None:
1891
+ z = z[:, xmask] if z.ndim == 2 and xmask.ndim == 1 else z[xmask]
1892
+ elif ymask is not None:
1893
+ z = z[ymask, :] if z.ndim == 2 and ymask.ndim == 1 else z[ymask]
1894
+ return z
1895
+ except Exception as err:
1896
+ warnings._warn_ultraplot(
1897
+ "Failed to restrict automatic colormap normalization "
1898
+ f"to in-bounds data only. Error message: {err}"
1899
+ )
1900
+ return z
1901
+
1902
+ def _inbounds_xylim(self, extents, x, y, **kwargs):
1903
+ """
1904
+ Restrict the `dataLim` to exclude out-of-bounds data when x (y) limits
1905
+ are fixed and we are determining default y (x) limits. This modifies
1906
+ the mutable input `extents` to support iteration over columns.
1907
+ """
1908
+ # WARNING: This feature is still experimental. But seems obvious. Matplotlib
1909
+ # updates data limits in ad hoc fashion differently for each plotting command
1910
+ # but since ultraplot standardizes inputs we can easily use them for dataLim.
1911
+ if extents is None:
1912
+ return
1913
+ if self._name != "cartesian":
1914
+ return
1915
+ if not x.size or not y.size:
1916
+ return
1917
+ kwargs, vert = _get_vert(**kwargs)
1918
+ if not vert:
1919
+ x, y = y, x
1920
+ trans = self.dataLim
1921
+ autox, autoy = self.get_autoscalex_on(), self.get_autoscaley_on()
1922
+ try:
1923
+ if autoy and not autox and x.shape == y.shape:
1924
+ # Reset the y data limits
1925
+ xmin, xmax = sorted(self.get_xlim())
1926
+ mask = (x >= xmin) & (x <= xmax)
1927
+ ymin, ymax = inputs._safe_range(inputs._safe_mask(mask, y))
1928
+ convert = self.convert_yunits # handle datetime, pint units
1929
+ if ymin is not None:
1930
+ trans.y0 = extents[1] = min(convert(ymin), extents[1])
1931
+ if ymax is not None:
1932
+ trans.y1 = extents[3] = max(convert(ymax), extents[3])
1933
+ getattr(self, "_request_autoscale_view", self.autoscale_view)()
1934
+ if autox and not autoy and y.shape == x.shape:
1935
+ # Reset the x data limits
1936
+ ymin, ymax = sorted(self.get_ylim())
1937
+ mask = (y >= ymin) & (y <= ymax)
1938
+ xmin, xmax = inputs._safe_range(inputs._safe_mask(mask, x))
1939
+ convert = self.convert_xunits # handle datetime, pint units
1940
+ if xmin is not None:
1941
+ trans.x0 = extents[0] = min(convert(xmin), extents[0])
1942
+ if xmax is not None:
1943
+ trans.x1 = extents[2] = max(convert(xmax), extents[2])
1944
+ getattr(self, "_request_autoscale_view", self.autoscale_view)()
1945
+ except Exception as err:
1946
+ warnings._warn_ultraplot(
1947
+ "Failed to restrict automatic y (x) axis limit algorithm to "
1948
+ f"data within locked x (y) limits only. Error message: {err}"
1949
+ )
1950
+
1951
+ def _parse_1d_args(self, x, *ys, **kwargs):
1952
+ """
1953
+ Interpret positional arguments for all 1D plotting commands.
1954
+ """
1955
+ # Standardize values
1956
+ zerox = not ys
1957
+ if zerox or all(y is None for y in ys): # pad with remaining Nones
1958
+ x, *ys = None, x, *ys[1:]
1959
+ if len(ys) == 2: # 'lines' or 'fill_between'
1960
+ if ys[1] is None:
1961
+ ys = (np.array([0.0]), ys[0]) # user input 1 or 2 positional args
1962
+ elif ys[0] is None:
1963
+ ys = (np.array([0.0]), ys[1]) # user input keyword 'y2' but no y1
1964
+ if any(y is None for y in ys):
1965
+ raise ValueError("Missing required data array argument.")
1966
+ ys = tuple(map(inputs._to_duck_array, ys))
1967
+ if x is not None:
1968
+ x = inputs._to_duck_array(x)
1969
+ x, *ys, kwargs = self._parse_1d_format(x, *ys, zerox=zerox, **kwargs)
1970
+
1971
+ # Geographic corrections
1972
+ if self._name == "cartopy" and isinstance(
1973
+ kwargs.get("transform"), PlateCarree
1974
+ ): # noqa: E501
1975
+ x, *ys = inputs._geo_cartopy_1d(x, *ys)
1976
+ elif self._name == "basemap" and kwargs.get("latlon", None):
1977
+ xmin, xmax = self._lonaxis.get_view_interval()
1978
+ x, *ys = inputs._geo_basemap_1d(x, *ys, xmin=xmin, xmax=xmax)
1979
+
1980
+ return (x, *ys, kwargs)
1981
+
1982
+ def _parse_1d_format(
1983
+ self,
1984
+ x,
1985
+ *ys,
1986
+ zerox=False,
1987
+ autox=True,
1988
+ autoy=True,
1989
+ autoformat=None,
1990
+ autoreverse=True,
1991
+ autolabels=True,
1992
+ autovalues=False,
1993
+ autoguide=True,
1994
+ label=None,
1995
+ labels=None,
1996
+ value=None,
1997
+ values=None,
1998
+ **kwargs,
1999
+ ):
2000
+ """
2001
+ Try to retrieve default coordinates from array-like objects and apply default
2002
+ formatting. Also update the keyword arguments.
2003
+ """
2004
+ # Parse input
2005
+ y = max(ys, key=lambda y: y.size) # find a non-scalar y for inferring metadata
2006
+ autox = autox and not zerox # so far just relevant for hist()
2007
+ autoformat = _not_none(autoformat, rc["autoformat"])
2008
+ kwargs, vert = _get_vert(**kwargs)
2009
+ labels = _not_none(
2010
+ label=label,
2011
+ labels=labels,
2012
+ value=value,
2013
+ values=values,
2014
+ legend_kw_labels=kwargs.get("legend_kw", {}).pop("labels", None),
2015
+ colorbar_kw_values=kwargs.get("colorbar_kw", {}).pop("values", None),
2016
+ )
2017
+
2018
+ # Retrieve the x coords
2019
+ # NOTE: Where columns represent distributions, like for box and violinplot or
2020
+ # where we use 'means' or 'medians', columns coords (axis 1) are 'x' coords.
2021
+ # Otherwise, columns represent e.g. lines and row coords (axis 0) are 'x'
2022
+ # coords. Exception is passing "ragged arrays" to boxplot and violinplot.
2023
+ dists = any(kwargs.get(s) for s in ("mean", "means", "median", "medians"))
2024
+ raggd = any(getattr(y, "dtype", None) == "object" for y in ys)
2025
+ xaxis = 0 if raggd else 1 if dists or not autoy else 0
2026
+ if autox and x is None:
2027
+ x = inputs._meta_labels(y, axis=xaxis) # use the first one
2028
+
2029
+ # Retrieve the labels. We only want default legend labels if this is an
2030
+ # object with 'title' metadata and/or the coords are string.
2031
+ # WARNING: Confusing terminology differences here -- for box and violin plots
2032
+ # labels refer to indices along x axis.
2033
+ if autolabels and labels is None:
2034
+ laxis = 0 if not autox and not autoy else xaxis if not autoy else xaxis + 1
2035
+ if laxis >= y.ndim:
2036
+ labels = inputs._meta_title(y)
2037
+ else:
2038
+ labels = inputs._meta_labels(y, axis=laxis, always=False)
2039
+ notitle = not inputs._meta_title(labels)
2040
+ if labels is None:
2041
+ pass
2042
+ elif notitle and not any(isinstance(_, str) for _ in labels):
2043
+ labels = None
2044
+
2045
+ # Apply the labels or values
2046
+ if labels is not None:
2047
+ if autovalues:
2048
+ kwargs["values"] = inputs._to_numpy_array(labels)
2049
+ elif autolabels:
2050
+ kwargs["labels"] = inputs._to_numpy_array(labels)
2051
+
2052
+ # Apply title for legend or colorbar that uses the labels or values
2053
+ if autoguide and autoformat:
2054
+ title = inputs._meta_title(labels)
2055
+ if title: # safely update legend_kw and colorbar_kw
2056
+ guides._add_guide_kw("legend", kwargs, title=title)
2057
+ guides._add_guide_kw("colorbar", kwargs, title=title)
2058
+
2059
+ # Apply the basic x and y settings
2060
+ autox = autox and self._name == "cartesian"
2061
+ autoy = autoy and self._name == "cartesian"
2062
+ sx, sy = "xy" if vert else "yx"
2063
+ kw_format = {}
2064
+ if autox and autoformat: # 'x' axis
2065
+ title = inputs._meta_title(x)
2066
+ if title:
2067
+ axis = getattr(self, sx + "axis")
2068
+ if axis.isDefault_label:
2069
+ kw_format[sx + "label"] = title
2070
+ if autoy and autoformat: # 'y' axis
2071
+ sy = sx if zerox else sy # hist() 'y' values are along 'x' axis
2072
+ title = inputs._meta_title(y)
2073
+ if title:
2074
+ axis = getattr(self, sy + "axis")
2075
+ if axis.isDefault_label:
2076
+ kw_format[sy + "label"] = title
2077
+
2078
+ # Convert string-type coordinates to indices
2079
+ # NOTE: This should even allow qualitative string input to hist()
2080
+ if autox:
2081
+ x, kw_format = inputs._meta_coords(x, which=sx, **kw_format)
2082
+ if autoy:
2083
+ *ys, kw_format = inputs._meta_coords(*ys, which=sy, **kw_format)
2084
+ if autox and autoreverse and inputs._is_descending(x):
2085
+ if getattr(self, f"get_autoscale{sx}_on")():
2086
+ kw_format[sx + "reverse"] = True
2087
+
2088
+ # Finally apply formatting and strip metadata
2089
+ # WARNING: Most methods that accept 2D arrays use columns of data, but when
2090
+ # pandas DataFrame specifically is passed to hist, boxplot, or violinplot, rows
2091
+ # of data assumed! Converting to ndarray necessary.
2092
+ if kw_format:
2093
+ self.format(**kw_format)
2094
+ ys = tuple(map(inputs._to_numpy_array, ys))
2095
+ if x is not None: # pie() and hist()
2096
+ x = inputs._to_numpy_array(x)
2097
+ return (x, *ys, kwargs)
2098
+
2099
+ def _parse_2d_args(
2100
+ self,
2101
+ x,
2102
+ y,
2103
+ *zs,
2104
+ globe=False,
2105
+ edges=False,
2106
+ allow1d=False,
2107
+ transpose=None,
2108
+ order=None,
2109
+ **kwargs,
2110
+ ):
2111
+ """
2112
+ Interpret positional arguments for all 2D plotting commands.
2113
+ """
2114
+ # Standardize values
2115
+ # NOTE: Functions pass two 'zs' at most right now
2116
+ if all(z is None for z in zs):
2117
+ x, y, zs = None, None, (x, y)[: len(zs)]
2118
+ if any(z is None for z in zs):
2119
+ raise ValueError("Missing required data array argument(s).")
2120
+ zs = tuple(inputs._to_duck_array(z, strip_units=True) for z in zs)
2121
+ if x is not None:
2122
+ x = inputs._to_duck_array(x)
2123
+ if y is not None:
2124
+ y = inputs._to_duck_array(y)
2125
+ if order is not None:
2126
+ if not isinstance(order, str) or order not in "CF":
2127
+ raise ValueError(f"Invalid order={order!r}. Options are 'C' or 'F'.")
2128
+ transpose = _not_none(
2129
+ transpose=transpose, transpose_order=bool("CF".index(order))
2130
+ )
2131
+ if transpose:
2132
+ zs = tuple(z.T for z in zs)
2133
+ if x is not None:
2134
+ x = x.T
2135
+ if y is not None:
2136
+ y = y.T
2137
+ x, y, *zs, kwargs = self._parse_2d_format(x, y, *zs, **kwargs)
2138
+ if edges:
2139
+ # NOTE: These functions quitely pass through 1D inputs, e.g. barb data
2140
+ x, y = inputs._to_edges(x, y, zs[0])
2141
+ else:
2142
+ x, y = inputs._to_centers(x, y, zs[0])
2143
+
2144
+ # Geographic corrections
2145
+ if allow1d:
2146
+ pass
2147
+ elif self._name == "cartopy" and isinstance(
2148
+ kwargs.get("transform"), PlateCarree
2149
+ ): # noqa: E501
2150
+ x, y, *zs = inputs._geo_cartopy_2d(x, y, *zs, globe=globe)
2151
+ elif self._name == "basemap" and kwargs.get("latlon", None):
2152
+ xmin, xmax = self._lonaxis.get_view_interval()
2153
+ x, y, *zs = inputs._geo_basemap_2d(
2154
+ x, y, *zs, xmin=xmin, xmax=xmax, globe=globe
2155
+ ) # noqa: E501
2156
+ x, y = np.meshgrid(x, y) # WARNING: required always
2157
+
2158
+ return (x, y, *zs, kwargs)
2159
+
2160
+ def _parse_2d_format(
2161
+ self, x, y, *zs, autoformat=None, autoguide=True, autoreverse=True, **kwargs
2162
+ ):
2163
+ """
2164
+ Try to retrieve default coordinates from array-like objects and apply default
2165
+ formatting. Also apply optional transpose and update the keyword arguments.
2166
+ """
2167
+ # Retrieve coordinates
2168
+ autoformat = _not_none(autoformat, rc["autoformat"])
2169
+ if x is None and y is None:
2170
+ z = zs[0]
2171
+ if z.ndim == 1:
2172
+ x = inputs._meta_labels(z, axis=0)
2173
+ y = np.zeros(z.shape) # default barb() and quiver() behavior in mpl
2174
+ else:
2175
+ x = inputs._meta_labels(z, axis=1)
2176
+ y = inputs._meta_labels(z, axis=0)
2177
+
2178
+ # Apply labels and XY axis settings
2179
+ if self._name == "cartesian":
2180
+ # Apply labels
2181
+ # NOTE: Do not overwrite existing labels!
2182
+ kw_format = {}
2183
+ if autoformat:
2184
+ for s, d in zip("xy", (x, y)):
2185
+ title = inputs._meta_title(d)
2186
+ if title:
2187
+ axis = getattr(self, s + "axis")
2188
+ if axis.isDefault_label:
2189
+ kw_format[s + "label"] = title
2190
+
2191
+ # Handle string-type coordinates
2192
+ x, kw_format = inputs._meta_coords(x, which="x", **kw_format)
2193
+ y, kw_format = inputs._meta_coords(y, which="y", **kw_format)
2194
+ for s, d in zip("xy", (x, y)):
2195
+ if autoreverse and inputs._is_descending(d):
2196
+ if getattr(self, f"get_autoscale{s}_on")():
2197
+ kw_format[s + "reverse"] = True
2198
+
2199
+ # Apply formatting
2200
+ if kw_format:
2201
+ self.format(**kw_format)
2202
+
2203
+ # Apply title for legend or colorbar
2204
+ if autoguide and autoformat:
2205
+ title = inputs._meta_title(zs[0])
2206
+ if title: # safely update legend_kw and colorbar_kw
2207
+ guides._add_guide_kw("legend", kwargs, title=title)
2208
+ guides._add_guide_kw("colorbar", kwargs, title=title)
2209
+
2210
+ # Finally strip metadata
2211
+ x = inputs._to_numpy_array(x)
2212
+ y = inputs._to_numpy_array(y)
2213
+ zs = tuple(map(inputs._to_numpy_array, zs))
2214
+ return (x, y, *zs, kwargs)
2215
+
2216
+ def _parse_color(self, x, y, c, *, apply_cycle=True, infer_rgb=False, **kwargs):
2217
+ """
2218
+ Parse either a colormap or color cycler. Colormap will be discrete and fade
2219
+ to subwhite luminance by default. Returns a HEX string if needed so we don't
2220
+ get ambiguous color warnings. Used with scatter, streamplot, quiver, barbs.
2221
+ """
2222
+ # NOTE: This function is positioned above the _parse_cmap and _parse_cycle
2223
+ # functions and helper functions.
2224
+ parsers = (self._parse_cmap, *self._level_parsers)
2225
+ if c is None or mcolors.is_color_like(c):
2226
+ if infer_rgb and c is not None:
2227
+ c = pcolors.to_hex(c) # avoid scatter() ambiguous color warning
2228
+ if apply_cycle: # False for scatter() so we can wait to get correct 'N'
2229
+ kwargs = self._parse_cycle(**kwargs)
2230
+ else:
2231
+ c = np.atleast_1d(c) # should only have effect on 'scatter' input
2232
+ if infer_rgb and (
2233
+ inputs._is_categorical(c) or c.ndim == 2 and c.shape[1] in (3, 4)
2234
+ ): # noqa: E501
2235
+ c = list(map(pcolors.to_hex, c)) # avoid iterating over columns
2236
+ else:
2237
+ kwargs = self._parse_cmap(
2238
+ x, y, c, plot_lines=True, default_discrete=False, **kwargs
2239
+ ) # noqa: E501
2240
+ parsers = (self._parse_cycle,)
2241
+ pop = _pop_params(kwargs, *parsers, ignore_internal=True)
2242
+ if pop:
2243
+ warnings._warn_ultraplot(f"Ignoring unused keyword arg(s): {pop}")
2244
+ return (c, kwargs)
2245
+
2246
+ @warnings._rename_kwargs("0.6.0", centers="values")
2247
+ def _parse_cmap(
2248
+ self,
2249
+ *args,
2250
+ cmap=None,
2251
+ cmap_kw=None,
2252
+ c=None,
2253
+ color=None,
2254
+ colors=None,
2255
+ norm=None,
2256
+ norm_kw=None,
2257
+ extend=None,
2258
+ vmin=None,
2259
+ vmax=None,
2260
+ discrete=None,
2261
+ default_cmap=None,
2262
+ default_discrete=True,
2263
+ skip_autolev=False,
2264
+ min_levels=None,
2265
+ plot_lines=False,
2266
+ plot_contours=False,
2267
+ **kwargs,
2268
+ ):
2269
+ """
2270
+ Parse colormap and normalizer arguments.
2271
+
2272
+ Parameters
2273
+ ----------
2274
+ c, color, colors : sequence of color-spec, optional
2275
+ Build a `DiscreteColormap` from the input color(s).
2276
+ cmap, cmap_kw : optional
2277
+ Colormap specs.
2278
+ norm, norm_kw : optional
2279
+ Normalize specs.
2280
+ extend : optional
2281
+ The colormap extend setting.
2282
+ vmin, vmax : flaot, optional
2283
+ The normalization range.
2284
+ sequential, diverging, cyclic, qualitative : bool, optional
2285
+ Toggle various colormap types.
2286
+ discrete : bool, optional
2287
+ Whether to apply `DiscreteNorm` to the colormap.
2288
+ default_discrete : bool, optional
2289
+ The default `discrete`. Depends on plotting method.
2290
+ skip_autolev : bool, optional
2291
+ Whether to skip automatic level generation.
2292
+ min_levels : int, optional
2293
+ The minimum number of valid levels. 1 for line contour plots 2 otherwise.
2294
+ plot_lines : bool, optional
2295
+ Whether these are lines. If so the default monochromatic luminance is 90.
2296
+ plot_contours : bool, optional
2297
+ Whether these are contours. If so then a discrete of `True` is required.
2298
+ """
2299
+ # Parse keyword args
2300
+ cmap_kw = cmap_kw or {}
2301
+ norm_kw = norm_kw or {}
2302
+ vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop("vmin", None))
2303
+ vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop("vmax", None))
2304
+ extend = _not_none(extend, "neither")
2305
+ colors = _not_none(c=c, color=color, colors=colors) # in case untranslated
2306
+ modes = {
2307
+ key: kwargs.pop(key, None)
2308
+ for key in ("sequential", "diverging", "cyclic", "qualitative")
2309
+ } # noqa: E501
2310
+ trues = {key: b for key, b in modes.items() if b}
2311
+ if len(trues) > 1: # noqa: E501
2312
+ warnings._warn_ultraplot(
2313
+ f"Conflicting colormap arguments: {trues!r}. Using the first one."
2314
+ )
2315
+ for key in tuple(trues)[1:]:
2316
+ del trues[key]
2317
+ modes[key] = None
2318
+
2319
+ # Create user-input colormap and potentially disable autodiverging
2320
+ # NOTE: Let people use diverging=False with diverging cmaps because some
2321
+ # use them (wrongly IMO but to each their own) for increased color contrast.
2322
+ # WARNING: Previously 'colors' set the edgecolors. To avoid all-black
2323
+ # colormap make sure to ignore 'colors' if 'cmap' was also passed.
2324
+ # WARNING: Previously tried setting number of levels to len(colors), but this
2325
+ # makes single-level single-color contour plots, and since _parse_level_num is
2326
+ # only generates approximate level counts, the idea failed anyway. Users should
2327
+ # pass their own levels to avoid truncation/cycling in these very special cases.
2328
+ autodiverging = rc["cmap.autodiverging"]
2329
+ if colors is not None:
2330
+ if cmap is not None:
2331
+ warnings._warn_ultraplot(
2332
+ f"you specified both cmap={cmap!s} and the qualitative-colormap "
2333
+ f"colors={colors!r}. Ignoring 'colors'. If you meant to specify "
2334
+ f"the edge color please use e.g. edgecolor={colors!r} instead."
2335
+ )
2336
+ else:
2337
+ if mcolors.is_color_like(colors):
2338
+ colors = [colors] # RGB[A] tuple possibly
2339
+ cmap = colors = np.atleast_1d(colors)
2340
+ cmap_kw["listmode"] = "discrete"
2341
+ if cmap is not None:
2342
+ if plot_lines:
2343
+ cmap_kw["default_luminance"] = constructor.DEFAULT_CYCLE_LUMINANCE
2344
+ cmap = constructor.Colormap(cmap, **cmap_kw)
2345
+ name = re.sub(r"\A_*(.*?)(?:_r|_s|_copy)*\Z", r"\1", cmap.name.lower())
2346
+ if not any(name in opts for opts in pcolors.CMAPS_DIVERGING.items()):
2347
+ autodiverging = False # avoid auto-truncation of sequential colormaps
2348
+
2349
+ # Force default options in special cases
2350
+ # NOTE: Delay application of 'sequential', 'diverging', 'cyclic', 'qualitative'
2351
+ # until after level generation so 'diverging' can be automatically applied.
2352
+ if "cyclic" in trues or getattr(cmap, "_cyclic", None):
2353
+ if extend is not None and extend != "neither":
2354
+ warnings._warn_ultraplot(
2355
+ f"Cyclic colormaps require extend='neither'. Ignoring extend={extend!r}" # noqa: E501
2356
+ )
2357
+ extend = "neither"
2358
+ if "qualitative" in trues or isinstance(cmap, pcolors.DiscreteColormap):
2359
+ if discrete is not None and not discrete: # noqa: E501
2360
+ warnings._warn_ultraplot(
2361
+ "Qualitative colormaps require discrete=True. Ignoring discrete=False." # noqa: E501
2362
+ )
2363
+ discrete = True
2364
+ if plot_contours:
2365
+ if discrete is not None and not discrete:
2366
+ warnings._warn_ultraplot(
2367
+ "Contoured plots require discrete=True. Ignoring discrete=False."
2368
+ )
2369
+ discrete = True
2370
+ keys = ("levels", "values", "locator", "negative", "positive", "symmetric")
2371
+ if any(key in kwargs for key in keys): # override
2372
+ discrete = _not_none(discrete, True)
2373
+ else: # use global boolean rc['cmap.discrete'] or command-specific default
2374
+ discrete = _not_none(discrete, rc["cmap.discrete"], default_discrete)
2375
+
2376
+ # Determine the appropriate 'vmin', 'vmax', and/or 'levels'
2377
+ # NOTE: Unlike xarray, but like matplotlib, vmin and vmax only approximately
2378
+ # determine level range. Levels are selected with Locator.tick_values().
2379
+ levels = None # unused
2380
+ isdiverging = False
2381
+ if not discrete and not skip_autolev:
2382
+ vmin, vmax, kwargs = self._parse_level_lim(
2383
+ *args, vmin=vmin, vmax=vmax, **kwargs
2384
+ )
2385
+ if autodiverging and vmin is not None and vmax is not None:
2386
+ if abs(np.sign(vmax) - np.sign(vmin)) == 2:
2387
+ isdiverging = True
2388
+ if discrete:
2389
+ levels, vmin, vmax, norm, norm_kw, kwargs = self._parse_level_vals(
2390
+ *args,
2391
+ vmin=vmin,
2392
+ vmax=vmax,
2393
+ norm=norm,
2394
+ norm_kw=norm_kw,
2395
+ extend=extend,
2396
+ min_levels=min_levels,
2397
+ skip_autolev=skip_autolev,
2398
+ **kwargs,
2399
+ )
2400
+ if autodiverging and levels is not None:
2401
+ _, counts = np.unique(np.sign(levels), return_counts=True)
2402
+ if counts[counts > 1].size > 1:
2403
+ isdiverging = True
2404
+ if not trues and isdiverging and modes["diverging"] is None:
2405
+ trues["diverging"] = modes["diverging"] = True
2406
+
2407
+ # Create the continuous normalizer.
2408
+ norm = _not_none(norm, "div" if "diverging" in trues else "linear")
2409
+ if isinstance(norm, mcolors.Normalize):
2410
+ norm.vmin, norm.vmax = vmin, vmax
2411
+ else:
2412
+ norm = constructor.Norm(norm, vmin=vmin, vmax=vmax, **norm_kw)
2413
+ isdiverging = autodiverging and isinstance(norm, pcolors.DivergingNorm)
2414
+ if not trues and isdiverging and modes["diverging"] is None:
2415
+ trues["diverging"] = modes["diverging"] = True
2416
+
2417
+ # Create the final colormap
2418
+ if cmap is None:
2419
+ if default_cmap is not None: # used internally
2420
+ cmap = default_cmap
2421
+ elif trues:
2422
+ cmap = rc["cmap." + tuple(trues)[0]]
2423
+ else:
2424
+ cmap = rc["image.cmap"]
2425
+ cmap = constructor.Colormap(cmap, **cmap_kw)
2426
+
2427
+ # Create the discrete normalizer
2428
+ # Then finally warn and remove unused args
2429
+ if levels is not None:
2430
+ norm, cmap, kwargs = self._parse_level_norm(
2431
+ levels, norm, cmap, extend=extend, min_levels=min_levels, **kwargs
2432
+ )
2433
+ params = _pop_params(kwargs, *self._level_parsers, ignore_internal=True)
2434
+ if "N" in params: # use this for lookup table N instead of levels N
2435
+ cmap = cmap.copy(N=params.pop("N"))
2436
+ if params:
2437
+ warnings._warn_ultraplot(f"Ignoring unused keyword args(s): {params}")
2438
+
2439
+ # Update outgoing args
2440
+ # NOTE: ContourSet natively stores 'extend' on the result but for other
2441
+ # classes we need to hide it on the object.
2442
+ kwargs.update({"cmap": cmap, "norm": norm})
2443
+ if plot_contours:
2444
+ kwargs.update({"levels": levels, "extend": extend})
2445
+ else:
2446
+ guides._add_guide_kw("colorbar", kwargs, extend=extend)
2447
+
2448
+ return kwargs
2449
+
2450
+ def _parse_cycle(
2451
+ self,
2452
+ ncycle=None,
2453
+ *,
2454
+ cycle=None,
2455
+ cycle_kw=None,
2456
+ cycle_manually=None,
2457
+ return_cycle=False,
2458
+ **kwargs,
2459
+ ):
2460
+ """
2461
+ Parse property cycle-related arguments.
2462
+
2463
+ Parameters
2464
+ ----------
2465
+ ncycle : int, optional
2466
+ The number of samples to draw for the cycle.
2467
+ cycle : cycle-spec, optional
2468
+ The property cycle specifier.
2469
+ cycle_kw : dict-like, optional
2470
+ The property cycle keyword arguments
2471
+ cycle_manually : dict-like, optional
2472
+ Mapping of property cycle keys to plotting function keys. Used
2473
+ to translate property cycle line properties to scatter properties.
2474
+ return_cycle : bool, optional
2475
+ Whether to simply return the property cycle or apply it. The cycle is
2476
+ only applied (and therefore reset) if it differs from the current one.
2477
+ """
2478
+ # Create the property cycler and update it if necessary
2479
+ # NOTE: Matplotlib Cycler() objects have built-in __eq__ operator
2480
+ # so really easy to check if the cycler has changed!
2481
+ if cycle is not None or cycle_kw:
2482
+ cycle_kw = cycle_kw or {}
2483
+ if ncycle != 1: # ignore for column-by-column plotting commands
2484
+ cycle_kw.setdefault("N", ncycle) # if None then filled in Colormap()
2485
+ if isinstance(cycle, str) and cycle.lower() == "none":
2486
+ cycle = False
2487
+ if not cycle:
2488
+ args = ()
2489
+ elif cycle is True: # consistency with 'False' ('reactivate' the cycler)
2490
+ args = (rc["axes.prop_cycle"],)
2491
+ else:
2492
+ args = (cycle,)
2493
+ cycle = constructor.Cycle(*args, **cycle_kw)
2494
+ with warnings.catch_warnings(): # hide 'elementwise-comparison failed'
2495
+ warnings.simplefilter("ignore", FutureWarning)
2496
+ if return_cycle:
2497
+ pass
2498
+ elif cycle != self._active_cycle:
2499
+ self.set_prop_cycle(cycle)
2500
+
2501
+ # Manually extract and apply settings to outgoing keyword arguments
2502
+ # if native matplotlib function does not include desired properties
2503
+ cycle_manually = cycle_manually or {}
2504
+ parser = self._get_lines # the _process_plot_var_args instance
2505
+ props = {} # which keys to apply from property cycler
2506
+ # BREAKING in mpl3.9.1 parse has cycle items and no longer posseses _prop_keys
2507
+ for prop, key in cycle_manually.items():
2508
+ if kwargs.get(key, None) is None and any(
2509
+ prop in item for item in parser._cycler_items
2510
+ ):
2511
+ props[prop] = key
2512
+ if props:
2513
+ for dict_ in parser._cycler_items:
2514
+ for prop, key in props.items():
2515
+ value = dict_[prop]
2516
+ if (
2517
+ key == "c"
2518
+ ): # special case: scatter() color must be converted to hex
2519
+ value = pcolors.to_hex(value)
2520
+ kwargs[key] = value
2521
+
2522
+ if return_cycle:
2523
+ return cycle, kwargs # needed for stem() to apply in a context()
2524
+ else:
2525
+ return kwargs
2526
+
2527
+ def _parse_level_lim(
2528
+ self,
2529
+ *args,
2530
+ vmin=None,
2531
+ vmax=None,
2532
+ robust=None,
2533
+ inbounds=None,
2534
+ negative=None,
2535
+ positive=None,
2536
+ symmetric=None,
2537
+ to_centers=False,
2538
+ **kwargs,
2539
+ ):
2540
+ """
2541
+ Return a suitable vmin and vmax based on the input data.
2542
+
2543
+ Parameters
2544
+ ----------
2545
+ *args
2546
+ The sample data.
2547
+ vmin, vmax : float, optional
2548
+ The user input minimum and maximum.
2549
+ robust : bool, optional
2550
+ Whether to limit the default range to exclude outliers.
2551
+ inbounds : bool, optional
2552
+ Whether to filter to in-bounds data.
2553
+ negative, positive, symmetric : bool, optional
2554
+ Whether limits should be negative, positive, or symmetric.
2555
+ to_centers : bool, optional
2556
+ Whether to convert coordinates to 'centers'.
2557
+
2558
+ Returns
2559
+ -------
2560
+ vmin, vmax : float
2561
+ The minimum and maximum.
2562
+ **kwargs
2563
+ Unused arguemnts.
2564
+ """
2565
+ # Parse vmin and vmax
2566
+ automin = vmin is None
2567
+ automax = vmax is None
2568
+ if not automin and not automax:
2569
+ return vmin, vmax, kwargs
2570
+
2571
+ # Parse input args
2572
+ inbounds = _not_none(inbounds, rc["cmap.inbounds"])
2573
+ robust = _not_none(robust, rc["cmap.robust"], False)
2574
+ robust = 96 if robust is True else 100 if robust is False else robust
2575
+ robust = np.atleast_1d(robust)
2576
+ if robust.size == 1:
2577
+ pmin, pmax = 50 + 0.5 * np.array([-robust.item(), robust.item()])
2578
+ elif robust.size == 2:
2579
+ pmin, pmax = robust.flat # pull out of array
2580
+ else:
2581
+ raise ValueError(
2582
+ f"Unexpected robust={robust!r}. Must be bool, float, or 2-tuple."
2583
+ ) # noqa: E501
2584
+
2585
+ # Get sample data
2586
+ # NOTE: Critical to use _to_numpy_array here because some
2587
+ # commands are unstandardized.
2588
+ # NOTE: Try to get reasonable *count* levels for hexbin/hist2d, but in general
2589
+ # have no way to select nice ones a priori (why we disable discretenorm).
2590
+ # NOTE: Currently we only ever use this function with *single* array input
2591
+ # but in future could make this public as a way for users (me) to get
2592
+ # automatic synced contours for a bunch of arrays in a grid.
2593
+ vmins, vmaxs = [], []
2594
+ if len(args) > 2:
2595
+ x, y, *zs = args
2596
+ else:
2597
+ x, y, *zs = None, None, *args
2598
+ for z in zs:
2599
+ if z is None: # e.g. empty scatter color
2600
+ continue
2601
+ if z.ndim > 2: # e.g. imshow data
2602
+ continue
2603
+ z = inputs._to_numpy_array(z)
2604
+ if inbounds and x is not None and y is not None: # ignore if None coords
2605
+ z = self._inbounds_vlim(x, y, z, to_centers=to_centers)
2606
+ imin, imax = inputs._safe_range(z, pmin, pmax)
2607
+ if automin and imin is not None:
2608
+ vmins.append(imin)
2609
+ if automax and imax is not None:
2610
+ vmaxs.append(imax)
2611
+ if automin:
2612
+ vmin = min(vmins, default=0)
2613
+ if automax:
2614
+ vmax = max(vmaxs, default=1)
2615
+
2616
+ # Apply modifications
2617
+ # NOTE: This is also applied to manual input levels lists in _parse_level_vals
2618
+ if negative:
2619
+ if automax:
2620
+ vmax = 0
2621
+ else:
2622
+ warnings._warn_ultraplot(
2623
+ f"Incompatible arguments vmax={vmax!r} and negative=True. "
2624
+ "Ignoring the latter."
2625
+ )
2626
+ if positive:
2627
+ if automin:
2628
+ vmin = 0
2629
+ else:
2630
+ warnings._warn_ultraplot(
2631
+ f"Incompatible arguments vmin={vmin!r} and positive=True. "
2632
+ "Ignoring the latter."
2633
+ )
2634
+ if symmetric:
2635
+ if automin and not automax:
2636
+ vmin = -vmax
2637
+ elif automax and not automin:
2638
+ vmax = -vmin
2639
+ elif automin and automax:
2640
+ vmin, vmax = -np.max(np.abs((vmin, vmax))), np.max(np.abs((vmin, vmax)))
2641
+ else:
2642
+ warnings._warn_ultraplot(
2643
+ f"Incompatible arguments vmin={vmin!r}, vmax={vmax!r}, and "
2644
+ "symmetric=True. Ignoring the latter."
2645
+ )
2646
+
2647
+ return vmin, vmax, kwargs
2648
+
2649
+ def _parse_level_num(
2650
+ self,
2651
+ *args,
2652
+ levels=None,
2653
+ locator=None,
2654
+ locator_kw=None,
2655
+ vmin=None,
2656
+ vmax=None,
2657
+ norm=None,
2658
+ norm_kw=None,
2659
+ extend=None,
2660
+ symmetric=None,
2661
+ **kwargs,
2662
+ ):
2663
+ """
2664
+ Return a suitable level list given the input data, normalizer,
2665
+ locator, and vmin and vmax.
2666
+
2667
+ Parameters
2668
+ ----------
2669
+ *args
2670
+ The sample data. Passed to `_parse_level_lim`.
2671
+ levels : int
2672
+ The approximate number of levels.
2673
+ locator, locator_kw
2674
+ The tick locator used to draw levels.
2675
+ vmin, vmax : float, optional
2676
+ The minimum and maximum values passed to the tick locator.
2677
+ norm, norm_kw : optional
2678
+ The continuous normalizer. Affects the default locator used to draw levels.
2679
+ extend : str, optional
2680
+ The extend setting. Affects level trimming settings.
2681
+ symmetric : bool, optional
2682
+ Whether the resulting levels should be symmetric about zero.
2683
+
2684
+ Returns
2685
+ -------
2686
+ levels : list of float
2687
+ The level edges.
2688
+ **kwargs
2689
+ Unused arguments.
2690
+ """
2691
+ # Input args
2692
+ # NOTE: Some of this is adapted from the hidden contour.ContourSet._autolev
2693
+ # NOTE: We use 'symmetric' with MaxNLocator to ensure boundaries include a
2694
+ # zero level but may trim many of these below.
2695
+ norm_kw = norm_kw or {}
2696
+ locator_kw = locator_kw or {}
2697
+ extend = _not_none(extend, "neither")
2698
+ levels = _not_none(levels, rc["cmap.levels"])
2699
+ vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop("vmin", None))
2700
+ vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop("vmax", None))
2701
+ norm = constructor.Norm(norm or "linear", **norm_kw)
2702
+ symmetric = _not_none(
2703
+ symmetric=symmetric,
2704
+ locator_kw_symmetric=locator_kw.pop("symmetric", None),
2705
+ default=False,
2706
+ )
2707
+
2708
+ # Get default locator from input norm
2709
+ # NOTE: This normalizer is only temporary for inferring level locs
2710
+ norm = constructor.Norm(norm or "linear", **norm_kw)
2711
+ if locator is not None:
2712
+ locator = constructor.Locator(locator, **locator_kw)
2713
+ elif isinstance(norm, mcolors.LogNorm):
2714
+ locator = mticker.LogLocator(**locator_kw)
2715
+ elif isinstance(norm, mcolors.SymLogNorm):
2716
+ for key, default in (("base", 10), ("linthresh", 1)):
2717
+ val = _not_none(
2718
+ getattr(norm, key, None), getattr(norm, "_" + key, None), default
2719
+ ) # noqa: E501
2720
+ locator_kw.setdefault(key, val)
2721
+ locator = mticker.SymmetricalLogLocator(**locator_kw)
2722
+ else:
2723
+ locator_kw["symmetric"] = symmetric
2724
+ locator = mticker.MaxNLocator(levels, min_n_ticks=1, **locator_kw)
2725
+
2726
+ # Get default level locations
2727
+ nlevs = levels
2728
+ automin = vmin is None
2729
+ automax = vmax is None
2730
+ vmin, vmax, kwargs = self._parse_level_lim(
2731
+ *args, vmin=vmin, vmax=vmax, symmetric=symmetric, **kwargs
2732
+ )
2733
+ try:
2734
+ levels = locator.tick_values(vmin, vmax)
2735
+ except TypeError: # e.g. due to datetime arrays
2736
+ return None, kwargs
2737
+ except RuntimeError: # too-many-ticks error
2738
+ levels = np.linspace(vmin, vmax, levels) # TODO: _autolev used N + 1
2739
+
2740
+ # Possibly trim levels far outside of 'vmin' and 'vmax'
2741
+ # NOTE: This part is mostly copied from matplotlib _autolev
2742
+ if not symmetric:
2743
+ i0, i1 = 0, len(levels) # defaults
2744
+ (under,) = np.where(levels < vmin)
2745
+ if len(under):
2746
+ i0 = under[-1]
2747
+ if not automin or extend in ("min", "both"):
2748
+ i0 += 1 # permit out-of-bounds data
2749
+ (over,) = np.where(levels > vmax)
2750
+ if len(over):
2751
+ i1 = over[0] + 1 if len(over) else len(levels)
2752
+ if not automax or extend in ("max", "both"):
2753
+ i1 -= 1 # permit out-of-bounds data
2754
+ if i1 - i0 < 3:
2755
+ i0, i1 = 0, len(levels) # revert
2756
+ levels = levels[i0:i1]
2757
+
2758
+ # Compare the no. of levels we got (levels) to what we wanted (nlevs)
2759
+ # If we wanted more than 2 times the result, then add nn - 1 extra
2760
+ # levels in-between the returned levels in normalized space (e.g. LogNorm).
2761
+ nn = nlevs // len(levels)
2762
+ if nn >= 2:
2763
+ olevels = norm(levels)
2764
+ nlevels = []
2765
+ for i in range(len(levels) - 1):
2766
+ l1, l2 = olevels[i], olevels[i + 1]
2767
+ nlevels.extend(np.linspace(l1, l2, nn + 1)[:-1])
2768
+ nlevels.append(olevels[-1])
2769
+ levels = norm.inverse(nlevels)
2770
+
2771
+ return levels, kwargs
2772
+
2773
+ def _parse_level_vals(
2774
+ self,
2775
+ *args,
2776
+ N=None,
2777
+ levels=None,
2778
+ values=None,
2779
+ extend=None,
2780
+ positive=False,
2781
+ negative=False,
2782
+ nozero=False,
2783
+ norm=None,
2784
+ norm_kw=None,
2785
+ skip_autolev=False,
2786
+ min_levels=None,
2787
+ **kwargs,
2788
+ ):
2789
+ """
2790
+ Return levels resulting from a wide variety of keyword options.
2791
+
2792
+ Parameters
2793
+ ----------
2794
+ *args
2795
+ The sample data. Passed to `_parse_level_lim`.
2796
+ N
2797
+ Shorthand for `levels`.
2798
+ levels : int or sequence of float, optional
2799
+ The levels list or (approximate) number of levels to create.
2800
+ values : int or sequence of float, optional
2801
+ The level center list or (approximate) number of level centers to create.
2802
+ positive, negative, nozero : bool, optional
2803
+ Whether to remove out non-positive, non-negative, and zero-valued
2804
+ levels. The latter is useful for single-color contour plots.
2805
+ norm, norm_kw : optional
2806
+ Passed to `Norm`. Used to possbily infer levels or to convert values.
2807
+ skip_autolev : bool, optional
2808
+ Whether to skip automatic level generation.
2809
+ min_levels : int, optional
2810
+ The minimum number of levels allowed.
2811
+
2812
+ Returns
2813
+ -------
2814
+ levels : list of float
2815
+ The level edges.
2816
+ **kwargs
2817
+ Unused arguments.
2818
+ """
2819
+
2820
+ # Helper function that restricts levels
2821
+ # NOTE: This should have no effect if levels were generated automatically.
2822
+ # However want to apply these to manual-input levels as well.
2823
+ def _restrict_levels(levels):
2824
+ kw = {}
2825
+ levels = np.asarray(levels)
2826
+ if len(levels) > 2:
2827
+ kw["atol"] = 1e-5 * np.min(np.diff(levels))
2828
+ if nozero:
2829
+ levels = levels[~np.isclose(levels, 0, **kw)]
2830
+ if positive:
2831
+ levels = levels[(levels > 0) | np.isclose(levels, 0, **kw)]
2832
+ if negative:
2833
+ levels = levels[(levels < 0) | np.isclose(levels, 0, **kw)]
2834
+ return levels
2835
+
2836
+ # Helper function to sanitize input levels
2837
+ # NOTE: Include special case where color levels are referenced by string labels
2838
+ def _sanitize_levels(key, array, minsize):
2839
+ if np.iterable(array):
2840
+ array, _ = pcolors._sanitize_levels(array, minsize)
2841
+ elif isinstance(array, Integral):
2842
+ pass
2843
+ elif array is not None:
2844
+ raise ValueError(f"Invalid {key}={array}. Must be list or integer.")
2845
+ if isinstance(norm, (mcolors.BoundaryNorm, pcolors.SegmentedNorm)):
2846
+ if isinstance(array, Integral):
2847
+ warnings._warn_ultraplot(
2848
+ f"Ignoring {key}={array}. Using norm={norm!r} {key} instead."
2849
+ )
2850
+ array = norm.boundaries if key == "levels" else None
2851
+ return array
2852
+
2853
+ # Parse input arguments and resolve incompatibilities
2854
+ vmin = vmax = None
2855
+ levels = _not_none(N=N, levels=levels, norm_kw_levs=norm_kw.pop("levels", None))
2856
+ if positive and negative:
2857
+ warnings._warn_ultraplot(
2858
+ "Incompatible args positive=True and negative=True. Using former."
2859
+ )
2860
+ negative = False
2861
+ if levels is not None and values is not None:
2862
+ warnings._warn_ultraplot(
2863
+ f"Incompatible args levels={levels!r} and values={values!r}. Using former." # noqa: E501
2864
+ )
2865
+ values = None
2866
+
2867
+ # Infer level edges from level centers if possible
2868
+ # NOTE: The only way for user to manually impose BoundaryNorm is by
2869
+ # passing one -- users cannot create one using Norm constructor key.
2870
+ if isinstance(values, Integral):
2871
+ levels = values + 1
2872
+ values = None
2873
+ if values is None:
2874
+ levels = _sanitize_levels("levels", levels, _not_none(min_levels, 2))
2875
+ levels = _not_none(levels, rc["cmap.levels"])
2876
+ else:
2877
+ values = _sanitize_levels("values", values, 1)
2878
+ kwargs["discrete_ticks"] = values # passed to _parse_level_norm
2879
+ if len(values) == 1:
2880
+ levels = [values[0] - 1, values[0] + 1] # weird but why not
2881
+ elif norm is not None and norm not in ("segments", "segmented"):
2882
+ # Generate levels by finding in-between points in the
2883
+ # normalized numeric space, e.g. LogNorm space.
2884
+ norm_kw = norm_kw or {}
2885
+ convert = constructor.Norm(norm, **norm_kw)
2886
+ levels = convert.inverse(utils.edges(convert(values)))
2887
+ else:
2888
+ # Generate levels so that ticks will be centered between edges
2889
+ # Solve: (x1 + x2) / 2 = y --> x2 = 2 * y - x1 with arbitrary init x1
2890
+ descending = values[1] < values[0]
2891
+ if descending: # e.g. [100, 50, 20, 10, 5, 2, 1] successful if reversed
2892
+ values = values[::-1]
2893
+ levels = [1.5 * values[0] - 0.5 * values[1]] # arbitrary starting point
2894
+ for value in values:
2895
+ levels.append(2 * value - levels[-1])
2896
+ if np.any(np.diff(levels) < 0): # never happens for evenly spaced levs
2897
+ levels = utils.edges(values)
2898
+ if descending: # then revert back below
2899
+ levels = levels[::-1]
2900
+
2901
+ # Process level edges and infer defaults
2902
+ # NOTE: Matplotlib colorbar algorithm *cannot* handle descending levels so
2903
+ # this function reverses them and adds special attribute to the normalizer.
2904
+ # Then colorbar() reads this attr and flips the axis and the colormap direction
2905
+ if np.iterable(levels):
2906
+ pop = _pop_params(kwargs, self._parse_level_num, ignore_internal=True)
2907
+ if pop:
2908
+ warnings._warn_ultraplot(f"Ignoring unused keyword arg(s): {pop}")
2909
+ elif not skip_autolev:
2910
+ levels, kwargs = self._parse_level_num(
2911
+ *args,
2912
+ levels=levels,
2913
+ norm=norm,
2914
+ norm_kw=norm_kw,
2915
+ extend=extend,
2916
+ negative=negative,
2917
+ positive=positive,
2918
+ **kwargs,
2919
+ )
2920
+ else:
2921
+ levels = values = None
2922
+
2923
+ # Determine default colorbar locator and norm and apply filters
2924
+ # NOTE: DiscreteNorm does not currently support vmin and
2925
+ # vmax different from level list minimum and maximum.
2926
+ # NOTE: The level restriction should have no effect if levels were generated
2927
+ # automatically. However want to apply these to manual-input levels as well.
2928
+ if levels is not None:
2929
+ levels = _restrict_levels(levels)
2930
+ if len(levels) == 0: # skip
2931
+ pass
2932
+ elif len(levels) == 1: # use central colormap color
2933
+ vmin, vmax = levels[0] - 1, levels[0] + 1
2934
+ else: # use minimum and maximum
2935
+ vmin, vmax = np.min(levels), np.max(levels)
2936
+ if not np.allclose(levels[1] - levels[0], np.diff(levels)):
2937
+ norm = _not_none(norm, "segmented")
2938
+ if norm in ("segments", "segmented"):
2939
+ norm_kw["levels"] = levels
2940
+
2941
+ return levels, vmin, vmax, norm, norm_kw, kwargs
2942
+
2943
+ @staticmethod
2944
+ def _parse_level_norm(
2945
+ levels,
2946
+ norm,
2947
+ cmap,
2948
+ *,
2949
+ extend=None,
2950
+ min_levels=None,
2951
+ discrete_ticks=None,
2952
+ discrete_labels=None,
2953
+ **kwargs,
2954
+ ):
2955
+ """
2956
+ Create a `~ultraplot.colors.DiscreteNorm` or `~ultraplot.colors.BoundaryNorm`
2957
+ from the input colormap and normalizer.
2958
+
2959
+ Parameters
2960
+ ----------
2961
+ levels : sequence of float
2962
+ The level boundaries.
2963
+ norm : `~matplotlib.colors.Normalize`
2964
+ The continuous normalizer.
2965
+ cmap : `~matplotlib.colors.Colormap`
2966
+ The colormap.
2967
+ extend : str, optional
2968
+ The extend setting.
2969
+ min_levels : int, optional
2970
+ The minimum number of levels.
2971
+ discrete_ticks : array-like, optional
2972
+ The colorbar locations to tick.
2973
+ discrete_labels : array-like, optional
2974
+ The colorbar tick labels.
2975
+
2976
+ Returns
2977
+ -------
2978
+ norm : `~ultraplot.colors.DiscreteNorm`
2979
+ The discrete normalizer.
2980
+ cmap : `~matplotlib.colors.Colormap`
2981
+ The possibly-modified colormap.
2982
+ kwargs
2983
+ Unused arguments.
2984
+ """
2985
+ # Reverse the colormap if input levels or values were descending
2986
+ # See _parse_level_vals for details
2987
+ min_levels = _not_none(min_levels, 2) # 1 for contour plots
2988
+ unique = extend = _not_none(extend, "neither")
2989
+ under = cmap._rgba_under
2990
+ over = cmap._rgba_over
2991
+ cyclic = getattr(cmap, "_cyclic", None)
2992
+ qualitative = isinstance(cmap, pcolors.DiscreteColormap) # see _parse_cmap
2993
+ if len(levels) < min_levels:
2994
+ raise ValueError(
2995
+ f"Invalid levels={levels!r}. Must be at least length {min_levels}."
2996
+ )
2997
+
2998
+ # Ensure end colors are unique by scaling colors as if extend='both'
2999
+ # NOTE: Inside _parse_cmap should have enforced extend='neither'
3000
+ if cyclic:
3001
+ step = 0.5 # try to allocate space for unique end colors
3002
+ unique = "both"
3003
+
3004
+ # Ensure color list length matches level list length using rotation
3005
+ # NOTE: No harm if not enough colors, we just end up with the same
3006
+ # color for out-of-bounds extensions. This is a gentle failure
3007
+ elif qualitative:
3008
+ step = 0.5 # try to sample the central index for safety
3009
+ unique = "both"
3010
+ auto_under = under is None and extend in ("min", "both")
3011
+ auto_over = over is None and extend in ("max", "both")
3012
+ ncolors = len(levels) - min_levels + 1 + auto_under + auto_over
3013
+ colors = list(itertools.islice(itertools.cycle(cmap.colors), ncolors))
3014
+ if auto_under and len(colors) > 1:
3015
+ under, *colors = colors
3016
+ if auto_over and len(colors) > 1:
3017
+ *colors, over = colors
3018
+ cmap = cmap.copy(colors, N=len(colors))
3019
+ if under is not None:
3020
+ cmap.set_under(under)
3021
+ if over is not None:
3022
+ cmap.set_over(over)
3023
+
3024
+ # Ensure middle colors sample full range when extreme colors are present
3025
+ # by scaling colors as if extend='neither'
3026
+ else:
3027
+ step = 1.0
3028
+ if over is not None and under is not None:
3029
+ unique = "neither"
3030
+ elif over is not None: # turn off over-bounds unique bin
3031
+ if extend == "both":
3032
+ unique = "min"
3033
+ elif extend == "max":
3034
+ unique = "neither"
3035
+ elif under is not None: # turn off under-bounds unique bin
3036
+ if extend == "both":
3037
+ unique = "min"
3038
+ elif extend == "max":
3039
+ unique = "neither"
3040
+
3041
+ # Generate DiscreteNorm and update "child" norm with vmin and vmax from
3042
+ # levels. This lets the colorbar set tick locations properly!
3043
+ if not isinstance(norm, mcolors.BoundaryNorm) and len(levels) > 1:
3044
+ norm = pcolors.DiscreteNorm(
3045
+ levels,
3046
+ norm=norm,
3047
+ unique=unique,
3048
+ step=step,
3049
+ ticks=discrete_ticks,
3050
+ labels=discrete_labels,
3051
+ )
3052
+
3053
+ return norm, cmap, kwargs
3054
+
3055
+ def _apply_plot(self, *pairs, vert=True, **kwargs):
3056
+ """
3057
+ Plot standard lines.
3058
+ """
3059
+ # Plot the lines
3060
+ objs, xsides = [], []
3061
+ kws = kwargs.copy()
3062
+ kws.update(_pop_props(kws, "line"))
3063
+ kws, extents = self._inbounds_extent(**kws)
3064
+ for xs, ys, fmt in self._iter_arg_pairs(*pairs):
3065
+ xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, **kws)
3066
+ ys, kw = inputs._dist_reduce(ys, **kw)
3067
+ guide_kw = _pop_params(kw, self._update_guide) # after standardize
3068
+ for _, n, x, y, kw in self._iter_arg_cols(xs, ys, **kw):
3069
+ kw = self._parse_cycle(n, **kw)
3070
+ *eb, kw = self._add_error_bars(
3071
+ x, y, vert=vert, default_barstds=True, **kw
3072
+ ) # noqa: E501
3073
+ *es, kw = self._add_error_shading(x, y, vert=vert, **kw)
3074
+ xsides.append(x)
3075
+ if not vert:
3076
+ x, y = y, x
3077
+ a = [x, y]
3078
+ if fmt is not None: # x1, y1, fmt1, x2, y2, fm2... style input
3079
+ a.append(fmt)
3080
+ (obj,) = self._call_native("plot", *a, **kw)
3081
+ self._inbounds_xylim(extents, x, y)
3082
+ objs.append((*eb, *es, obj) if eb or es else obj)
3083
+
3084
+ # Add sticky edges
3085
+ self._fix_sticky_edges(objs, "x" if vert else "y", *xsides, only=mlines.Line2D)
3086
+ self._update_guide(objs, **guide_kw)
3087
+ return cbook.silent_list("Line2D", objs) # always return list
3088
+
3089
+ @docstring._snippet_manager
3090
+ def line(self, *args, **kwargs):
3091
+ """
3092
+ %(plot.plot)s
3093
+ """
3094
+ return self.plot(*args, **kwargs)
3095
+
3096
+ @docstring._snippet_manager
3097
+ def linex(self, *args, **kwargs):
3098
+ """
3099
+ %(plot.plotx)s
3100
+ """
3101
+ return self.plotx(*args, **kwargs)
3102
+
3103
+ @inputs._preprocess_or_redirect("x", "y", allow_extra=True)
3104
+ @docstring._concatenate_inherited
3105
+ @docstring._snippet_manager
3106
+ def plot(self, *args, **kwargs):
3107
+ """
3108
+ %(plot.plot)s
3109
+ """
3110
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3111
+ return self._apply_plot(*args, **kwargs)
3112
+
3113
+ @inputs._preprocess_or_redirect("y", "x", allow_extra=True)
3114
+ @docstring._snippet_manager
3115
+ def plotx(self, *args, **kwargs):
3116
+ """
3117
+ %(plot.plotx)s
3118
+ """
3119
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3120
+ return self._apply_plot(*args, **kwargs)
3121
+
3122
+ def _apply_step(self, *pairs, vert=True, where="pre", **kwargs):
3123
+ """
3124
+ Plot the steps.
3125
+ """
3126
+ # Plot the steps
3127
+ # NOTE: Internally matplotlib plot() calls step() so we could use that
3128
+ # approach... but instead repeat _apply_plot internals here so we can
3129
+ # disable error indications that make no sense for 'step' plots.
3130
+ kws = kwargs.copy()
3131
+ opts = ("pre", "post", "mid")
3132
+ if where not in opts:
3133
+ raise ValueError(f"Invalid where={where!r}. Options are {opts!r}.")
3134
+ kws.update(_pop_props(kws, "line"))
3135
+ kws.setdefault("drawstyle", "steps-" + where)
3136
+ kws, extents = self._inbounds_extent(**kws)
3137
+ objs = []
3138
+ for xs, ys, fmt in self._iter_arg_pairs(*pairs):
3139
+ xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, **kws)
3140
+ guide_kw = _pop_params(kw, self._update_guide) # after standardize
3141
+ if fmt is not None:
3142
+ kw["fmt"] = fmt
3143
+ for _, n, x, y, *a, kw in self._iter_arg_cols(xs, ys, **kw):
3144
+ kw = self._parse_cycle(n, **kw)
3145
+ if not vert:
3146
+ x, y = y, x
3147
+ (obj,) = self._call_native("step", x, y, *a, **kw)
3148
+ self._inbounds_xylim(extents, x, y)
3149
+ objs.append(obj)
3150
+
3151
+ self._update_guide(objs, **guide_kw)
3152
+ return cbook.silent_list("Line2D", objs) # always return list
3153
+
3154
+ @inputs._preprocess_or_redirect("x", "y", allow_extra=True)
3155
+ @docstring._concatenate_inherited
3156
+ @docstring._snippet_manager
3157
+ def step(self, *args, **kwargs):
3158
+ """
3159
+ %(plot.step)s
3160
+ """
3161
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3162
+ return self._apply_step(*args, **kwargs)
3163
+
3164
+ @inputs._preprocess_or_redirect("y", "x", allow_extra=True)
3165
+ @docstring._snippet_manager
3166
+ def stepx(self, *args, **kwargs):
3167
+ """
3168
+ %(plot.stepx)s
3169
+ """
3170
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3171
+ return self._apply_step(*args, **kwargs)
3172
+
3173
+ def _apply_stem(
3174
+ self,
3175
+ x,
3176
+ y,
3177
+ *,
3178
+ linefmt=None,
3179
+ markerfmt=None,
3180
+ basefmt=None,
3181
+ orientation=None,
3182
+ **kwargs,
3183
+ ):
3184
+ """
3185
+ Plot stem lines and markers.
3186
+ """
3187
+ # Parse input
3188
+ kw = kwargs.copy()
3189
+ kw, extents = self._inbounds_extent(**kw)
3190
+ x, y, kw = self._parse_1d_args(x, y, **kw)
3191
+ guide_kw = _pop_params(kw, self._update_guide)
3192
+
3193
+ # Set default colors
3194
+ # NOTE: 'fmt' strings can only be 2 to 3 characters and include color
3195
+ # shorthands like 'r' or cycle colors like 'C0'. Cannot use full color names.
3196
+ # NOTE: Matplotlib defaults try to make a 'reddish' color the base and 'bluish'
3197
+ # color the stems. To make this more robust we temporarily replace the cycler.
3198
+ # Bizarrely stem() only reads from the global cycler() so have to update it.
3199
+ fmts = (linefmt, basefmt, markerfmt)
3200
+ orientation = _not_none(orientation, "vertical")
3201
+ if not any(isinstance(fmt, str) and re.match(r"\AC[0-9]", fmt) for fmt in fmts):
3202
+ cycle = constructor.Cycle((rc["negcolor"], rc["poscolor"]), name="_no_name")
3203
+ kw.setdefault("cycle", cycle)
3204
+ kw["basefmt"] = _not_none(basefmt, "C1-") # red base
3205
+ kw["linefmt"] = linefmt = _not_none(linefmt, "C0-") # blue stems
3206
+ kw["markerfmt"] = _not_none(markerfmt, linefmt[:-1] + "o") # blue marker
3207
+ sig = inspect.signature(maxes.Axes.stem)
3208
+ if "use_line_collection" in sig.parameters:
3209
+ kw.setdefault("use_line_collection", True)
3210
+
3211
+ # Call function then restore property cycle
3212
+ # WARNING: Horizontal stem plots are only supported in recent versions of
3213
+ # matplotlib. Let matplotlib raise an error if need be.
3214
+ ctx = {}
3215
+ cycle, kw = self._parse_cycle(return_cycle=True, **kw) # allow re-application
3216
+ if cycle is not None:
3217
+ ctx["axes.prop_cycle"] = cycle
3218
+ if orientation == "horizontal": # may raise error
3219
+ kw["orientation"] = orientation
3220
+ with rc.context(ctx):
3221
+ obj = self._call_native("stem", x, y, **kw)
3222
+ self._inbounds_xylim(extents, x, y, orientation=orientation)
3223
+ self._update_guide(obj, **guide_kw)
3224
+ return obj
3225
+
3226
+ @inputs._preprocess_or_redirect("x", "y")
3227
+ @docstring._concatenate_inherited
3228
+ @docstring._snippet_manager
3229
+ def stem(self, *args, **kwargs):
3230
+ """
3231
+ %(plot.stem)s
3232
+ """
3233
+ kwargs = _parse_vert(default_orientation="vertical", **kwargs)
3234
+ return self._apply_stem(*args, **kwargs)
3235
+
3236
+ @inputs._preprocess_or_redirect("x", "y")
3237
+ @docstring._snippet_manager
3238
+ def stemx(self, *args, **kwargs):
3239
+ """
3240
+ %(plot.stemx)s
3241
+ """
3242
+ kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
3243
+ return self._apply_stem(*args, **kwargs)
3244
+
3245
+ @inputs._preprocess_or_redirect("x", "y", ("c", "color", "colors", "values"))
3246
+ @docstring._snippet_manager
3247
+ def parametric(self, x, y, c, *, interp=0, scalex=True, scaley=True, **kwargs):
3248
+ """
3249
+ %(plot.parametric)s
3250
+ """
3251
+ # Standardize arguments
3252
+ # NOTE: Values are inferred in _auto_format() the same way legend labels are
3253
+ # inferred. Will not always return an array like inferred coordinates do.
3254
+ # NOTE: We want to be able to think of 'c' as a scatter color array and
3255
+ # as a colormap color list. Try to support that here.
3256
+ kw = kwargs.copy()
3257
+ kw.update(_pop_props(kw, "collection"))
3258
+ kw, extents = self._inbounds_extent(**kw)
3259
+ label = _not_none(**{key: kw.pop(key, None) for key in ("label", "value")})
3260
+ x, y, kw = self._parse_1d_args(
3261
+ x, y, values=c, autovalues=True, autoreverse=False, **kw
3262
+ )
3263
+ c = kw.pop("values", None) # permits auto-inferring values
3264
+ c = np.arange(y.size) if c is None else inputs._to_numpy_array(c)
3265
+ if (
3266
+ c.size in (3, 4)
3267
+ and y.size not in (3, 4)
3268
+ and mcolors.is_color_like(tuple(c.flat))
3269
+ or all(map(mcolors.is_color_like, c))
3270
+ ):
3271
+ c, kw["colors"] = np.arange(c.shape[0]), c # convert color specs
3272
+
3273
+ # Interpret color values
3274
+ # NOTE: This permits string label input for 'values'
3275
+ c, guide_kw = inputs._meta_coords(c, which="") # convert string labels
3276
+ if c.size == 1 and y.size != 1:
3277
+ c = np.arange(y.size) # convert dummy label for single color
3278
+ if guide_kw:
3279
+ guides._add_guide_kw("colorbar", kw, **guide_kw)
3280
+ else:
3281
+ guides._add_guide_kw("colorbar", kw, locator=c)
3282
+
3283
+ # Interpolate values to allow for smooth gradations between values or just
3284
+ # to color siwtchover halfway between points (interp True, False respectively)
3285
+ if interp > 0:
3286
+ x_orig, y_orig, v_orig = x, y, c
3287
+ x, y, c = [], [], []
3288
+ for j in range(x_orig.shape[0] - 1):
3289
+ idx = slice(None)
3290
+ if j + 1 < x_orig.shape[0] - 1:
3291
+ idx = slice(None, -1)
3292
+ x.extend(np.linspace(x_orig[j], x_orig[j + 1], interp + 2)[idx].flat)
3293
+ y.extend(np.linspace(y_orig[j], y_orig[j + 1], interp + 2)[idx].flat)
3294
+ c.extend(np.linspace(v_orig[j], v_orig[j + 1], interp + 2)[idx].flat)
3295
+ x, y, c = np.array(x), np.array(y), np.array(c)
3296
+
3297
+ # Get coordinates and values for points to the 'left' and 'right' of joints
3298
+ coords = []
3299
+ for i in range(y.shape[0]):
3300
+ icoords = np.empty((3, 2))
3301
+ for j, arr in enumerate((x, y)):
3302
+ icoords[:, j] = (
3303
+ arr[0] if i == 0 else 0.5 * (arr[i - 1] + arr[i]),
3304
+ arr[i],
3305
+ arr[-1] if i + 1 == y.shape[0] else 0.5 * (arr[i + 1] + arr[i]),
3306
+ )
3307
+ coords.append(icoords)
3308
+ coords = np.array(coords)
3309
+
3310
+ # Get the colormap accounting for 'discrete' mode
3311
+ discrete = kw.get("discrete", None)
3312
+ if discrete is not None and not discrete:
3313
+ a = (x, y, c) # pick levels from vmin and vmax, possibly limiting range
3314
+ else:
3315
+ a, kw["values"] = (), c
3316
+ kw = self._parse_cmap(*a, plot_lines=True, **kw)
3317
+ cmap, norm = kw.pop("cmap"), kw.pop("norm")
3318
+
3319
+ # Add collection with some custom attributes
3320
+ # NOTE: Modern API uses self._request_autoscale_view but this is
3321
+ # backwards compatible to earliest matplotlib versions.
3322
+ guide_kw = _pop_params(kw, self._update_guide)
3323
+ obj = mcollections.LineCollection(
3324
+ coords,
3325
+ cmap=cmap,
3326
+ norm=norm,
3327
+ label=label,
3328
+ linestyles="-",
3329
+ capstyle="butt",
3330
+ joinstyle="miter",
3331
+ )
3332
+ obj.set_array(c) # the ScalarMappable method
3333
+ obj.update({key: value for key, value in kw.items() if key not in ("color",)})
3334
+ self.add_collection(obj) # also adjusts label
3335
+ self.autoscale_view(scalex=scalex, scaley=scaley)
3336
+ self._update_guide(obj, **guide_kw)
3337
+ return obj
3338
+
3339
+ def _apply_lines(
3340
+ self,
3341
+ xs,
3342
+ ys1,
3343
+ ys2,
3344
+ colors,
3345
+ *,
3346
+ vert=True,
3347
+ stack=None,
3348
+ stacked=None,
3349
+ negpos=False,
3350
+ **kwargs,
3351
+ ):
3352
+ """
3353
+ Plot vertical or hotizontal lines at each point.
3354
+ """
3355
+ # Parse input arguments
3356
+ kw = kwargs.copy()
3357
+ name = "vlines" if vert else "hlines"
3358
+ if colors is not None:
3359
+ kw["colors"] = colors
3360
+ kw.update(_pop_props(kw, "collection"))
3361
+ kw, extents = self._inbounds_extent(**kw)
3362
+ stack = _not_none(stack=stack, stacked=stacked)
3363
+ xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
3364
+ guide_kw = _pop_params(kw, self._update_guide)
3365
+
3366
+ # Support "negative" and "positive" lines
3367
+ # TODO: Ensure 'linewidths' etc. are applied! For some reason
3368
+ # previously thought they had to be manually applied.
3369
+ y0 = 0
3370
+ objs, sides = [], []
3371
+ for _, n, x, y1, y2, kw in self._iter_arg_cols(xs, ys1, ys2, **kw):
3372
+ kw = self._parse_cycle(n, **kw)
3373
+ if stack:
3374
+ y1 = y1 + y0 # avoid in-place modification
3375
+ y2 = y2 + y0
3376
+ y0 = y0 + y2 - y1 # irrelevant that we added y0 to both
3377
+ if negpos:
3378
+ obj = self._call_negpos(name, x, y1, y2, colorkey="colors", **kw)
3379
+ else:
3380
+ obj = self._call_native(name, x, y1, y2, **kw)
3381
+ for y in (y1, y2):
3382
+ self._inbounds_xylim(extents, x, y, vert=vert)
3383
+ if y.size == 1: # add sticky edges if bounds are scalar
3384
+ sides.append(y)
3385
+ objs.append(obj)
3386
+
3387
+ # Draw guide and add sticky edges
3388
+ self._fix_sticky_edges(objs, "y" if vert else "x", *sides)
3389
+ self._update_guide(objs, **guide_kw)
3390
+ return objs[0] if len(objs) == 1 else cbook.silent_list("LineCollection", objs)
3391
+
3392
+ # WARNING: breaking change from native 'ymin' and 'ymax'
3393
+ @inputs._preprocess_or_redirect("x", "y1", "y2", ("c", "color", "colors"))
3394
+ @docstring._snippet_manager
3395
+ def vlines(self, *args, **kwargs):
3396
+ """
3397
+ %(plot.vlines)s
3398
+ """
3399
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3400
+ return self._apply_lines(*args, **kwargs)
3401
+
3402
+ # WARNING: breaking change from native 'xmin' and 'xmax'
3403
+ @inputs._preprocess_or_redirect("y", "x1", "x2", ("c", "color", "colors"))
3404
+ @docstring._snippet_manager
3405
+ def hlines(self, *args, **kwargs):
3406
+ """
3407
+ %(plot.hlines)s
3408
+ """
3409
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3410
+ return self._apply_lines(*args, **kwargs)
3411
+
3412
+ def _parse_markersize(
3413
+ self, s, *, smin=None, smax=None, area_size=True, absolute_size=None, **kwargs
3414
+ ):
3415
+ """
3416
+ Scale the marker sizes with optional keyword args.
3417
+ """
3418
+ if s is not None:
3419
+ s = inputs._to_numpy_array(s)
3420
+ if absolute_size is None:
3421
+ absolute_size = s.size == 1 or _inside_seaborn_call()
3422
+ if not absolute_size or smin is not None or smax is not None:
3423
+ smin = _not_none(smin, 1)
3424
+ smax = _not_none(smax, rc["lines.markersize"] ** (1, 2)[area_size])
3425
+ dmin, dmax = inputs._safe_range(s) # data value range
3426
+ if dmin is not None and dmax is not None and dmin != dmax:
3427
+ s = smin + (smax - smin) * (s - dmin) / (dmax - dmin)
3428
+ s = s ** (2, 1)[area_size]
3429
+ return s, kwargs
3430
+
3431
+ def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
3432
+ """
3433
+ Apply scatter or scatterx markers.
3434
+ """
3435
+ # Manual property cycling. Converts Line2D keywords used in property
3436
+ # cycle to PathCollection keywords that can be passed to scatter.
3437
+ # NOTE: Matplotlib uses the property cycler in _get_patches_for_fill for
3438
+ # scatter() plots. It only ever inherits color from that. We instead use
3439
+ # _get_lines to help overarching goal of unifying plot() and scatter().
3440
+ cycle_manually = {
3441
+ "alpha": "alpha",
3442
+ "color": "c",
3443
+ "markerfacecolor": "c",
3444
+ "markeredgecolor": "edgecolors",
3445
+ "marker": "marker",
3446
+ "markersize": "s",
3447
+ "markeredgewidth": "linewidths",
3448
+ "linestyle": "linestyles",
3449
+ "linewidth": "linewidths",
3450
+ }
3451
+
3452
+ kw = kwargs.copy()
3453
+ inbounds = kw.pop("inbounds", None)
3454
+ kw.update(_pop_props(kw, "collection"))
3455
+ kw, extents = self._inbounds_extent(inbounds=inbounds, **kw)
3456
+ xs, ys, kw = self._parse_1d_args(xs, ys, vert=vert, autoreverse=False, **kw)
3457
+ ys, kw = inputs._dist_reduce(ys, **kw)
3458
+ ss, kw = self._parse_markersize(ss, **kw) # parse 's'
3459
+
3460
+ # Move _parse_cycle before _parse_color
3461
+ kw = self._parse_cycle(xs.shape[1] if xs.ndim > 1 else 1, **kw)
3462
+
3463
+ # Only parse color if explicitly provided
3464
+ if cc is not None:
3465
+ infer_rgb = True
3466
+ if not isinstance(cc, str):
3467
+ test = np.atleast_1d(cc)
3468
+ if (
3469
+ any(_.ndim == 2 and _.shape[1] in (3, 4) for _ in (xs, ys))
3470
+ and test.ndim == 2
3471
+ and test.shape[1] in (3, 4)
3472
+ ):
3473
+ infer_rgb = False
3474
+ cc, kw = self._parse_color(xs, ys, cc, inbounds=inbounds, apply_cycle=False, infer_rgb=infer_rgb, **kw)
3475
+
3476
+ guide_kw = _pop_params(kw, self._update_guide)
3477
+ objs = []
3478
+ for _, n, x, y, s, c, kw in self._iter_arg_cols(xs, ys, ss, cc, **kw):
3479
+ # Don't set 'c' explicitly unless it was provided
3480
+ kw["s"] = s
3481
+ if c is not None:
3482
+ kw["c"] = c
3483
+ *eb, kw = self._add_error_bars(x, y, vert=vert, default_barstds=True, **kw)
3484
+ *es, kw = self._add_error_shading(x, y, vert=vert, color_key="c", **kw)
3485
+ if not vert:
3486
+ x, y = y, x
3487
+ obj = self._call_native("scatter", x, y, **kw)
3488
+ self._inbounds_xylim(extents, x, y)
3489
+ objs.append((*eb, *es, obj) if eb or es else obj)
3490
+
3491
+ self._update_guide(objs, queue_colorbar=False, **guide_kw)
3492
+ return objs[0] if len(objs) == 1 else cbook.silent_list("PathCollection", objs)
3493
+
3494
+ # NOTE: Matplotlib internally applies scatter 'c' arguments as the
3495
+ # 'facecolors' argument to PathCollection. So perfectly reasonable to
3496
+ # point both 'color' and 'facecolor' arguments to the 'c' keyword here.
3497
+ @inputs._preprocess_or_redirect(
3498
+ "x",
3499
+ "y",
3500
+ _get_aliases("collection", "sizes"),
3501
+ _get_aliases("collection", "colors", "facecolors"),
3502
+ keywords=_get_aliases("collection", "linewidths", "edgecolors"),
3503
+ )
3504
+ @docstring._concatenate_inherited
3505
+ @docstring._snippet_manager
3506
+ def scatter(self, *args, **kwargs):
3507
+ """
3508
+ %(plot.scatter)s
3509
+ """
3510
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3511
+ return self._apply_scatter(*args, **kwargs)
3512
+
3513
+ @inputs._preprocess_or_redirect(
3514
+ "y",
3515
+ "x",
3516
+ _get_aliases("collection", "sizes"),
3517
+ _get_aliases("collection", "colors", "facecolors"),
3518
+ keywords=_get_aliases("collection", "linewidths", "edgecolors"),
3519
+ )
3520
+ @docstring._snippet_manager
3521
+ def scatterx(self, *args, **kwargs):
3522
+ """
3523
+ %(plot.scatterx)s
3524
+ """
3525
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3526
+ return self._apply_scatter(*args, **kwargs)
3527
+
3528
+ def _apply_fill(
3529
+ self,
3530
+ xs,
3531
+ ys1,
3532
+ ys2,
3533
+ where,
3534
+ *,
3535
+ vert=True,
3536
+ negpos=None,
3537
+ stack=None,
3538
+ stacked=None,
3539
+ **kwargs,
3540
+ ):
3541
+ """
3542
+ Apply area shading.
3543
+ """
3544
+ # Parse input arguments
3545
+ kw = kwargs.copy()
3546
+ kw.update(_pop_props(kw, "patch"))
3547
+ kw, extents = self._inbounds_extent(**kw)
3548
+ name = "fill_between" if vert else "fill_betweenx"
3549
+ stack = _not_none(stack=stack, stacked=stacked)
3550
+ xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
3551
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
3552
+
3553
+ # Draw patches with default edge width zero
3554
+ y0 = 0
3555
+ objs, xsides, ysides = [], [], []
3556
+ guide_kw = _pop_params(kw, self._update_guide)
3557
+ for _, n, x, y1, y2, w, kw in self._iter_arg_cols(xs, ys1, ys2, where, **kw):
3558
+ kw = self._parse_cycle(n, **kw)
3559
+ if stack:
3560
+ y1 = y1 + y0 # avoid in-place modification
3561
+ y2 = y2 + y0
3562
+ y0 = y0 + y2 - y1 # irrelevant that we added y0 to both
3563
+ if negpos: # NOTE: if user passes 'where' will issue a warning
3564
+ obj = self._call_negpos(name, x, y1, y2, where=w, use_where=True, **kw)
3565
+ else:
3566
+ obj = self._call_native(name, x, y1, y2, where=w, **kw)
3567
+ self._fix_patch_edges(obj, **edgefix_kw, **kw)
3568
+ xsides.append(x)
3569
+ for y in (y1, y2):
3570
+ self._inbounds_xylim(extents, x, y, vert=vert)
3571
+ if y.size == 1: # add sticky edges if bounds are scalar
3572
+ ysides.append(y)
3573
+ objs.append(obj)
3574
+
3575
+ # Draw guide and add sticky edges
3576
+ self._update_guide(objs, **guide_kw)
3577
+ for axis, sides in zip("xy" if vert else "yx", (xsides, ysides)):
3578
+ self._fix_sticky_edges(objs, axis, *sides)
3579
+ return objs[0] if len(objs) == 1 else cbook.silent_list("PolyCollection", objs)
3580
+
3581
+ @docstring._snippet_manager
3582
+ def area(self, *args, **kwargs):
3583
+ """
3584
+ %(plot.fill_between)s
3585
+ """
3586
+ return self.fill_between(*args, **kwargs)
3587
+
3588
+ @docstring._snippet_manager
3589
+ def areax(self, *args, **kwargs):
3590
+ """
3591
+ %(plot.fill_betweenx)s
3592
+ """
3593
+ return self.fill_betweenx(*args, **kwargs)
3594
+
3595
+ @inputs._preprocess_or_redirect("x", "y1", "y2", "where")
3596
+ @docstring._concatenate_inherited
3597
+ @docstring._snippet_manager
3598
+ def fill_between(self, *args, **kwargs):
3599
+ """
3600
+ %(plot.fill_between)s
3601
+ """
3602
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3603
+ return self._apply_fill(*args, **kwargs)
3604
+
3605
+ @inputs._preprocess_or_redirect("y", "x1", "x2", "where")
3606
+ @docstring._concatenate_inherited
3607
+ @docstring._snippet_manager
3608
+ def fill_betweenx(self, *args, **kwargs):
3609
+ """
3610
+ %(plot.fill_betweenx)s
3611
+ """
3612
+ # NOTE: The 'horizontal' orientation will be inferred by downstream
3613
+ # wrappers using the function name.
3614
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3615
+ return self._apply_fill(*args, **kwargs)
3616
+
3617
+ @staticmethod
3618
+ def _convert_bar_width(x, width=1):
3619
+ """
3620
+ Convert bar plot widths from relative to coordinate spacing. Relative
3621
+ widths are much more convenient for users.
3622
+ """
3623
+ # WARNING: This will fail for non-numeric non-datetime64 singleton
3624
+ # datatypes but this is good enough for vast majority of cases.
3625
+ x_test = inputs._to_numpy_array(x)
3626
+ if len(x_test) >= 2:
3627
+ x_step = x_test[1:] - x_test[:-1]
3628
+ x_step = np.concatenate((x_step, x_step[-1:]))
3629
+ elif x_test.dtype == np.datetime64:
3630
+ x_step = np.timedelta64(1, "D")
3631
+ else:
3632
+ x_step = np.array(0.5)
3633
+ if np.issubdtype(x_test.dtype, np.datetime64):
3634
+ # Avoid integer timedelta truncation
3635
+ x_step = x_step.astype("timedelta64[ns]")
3636
+ return width * x_step
3637
+
3638
+ def _apply_bar(
3639
+ self,
3640
+ xs,
3641
+ hs,
3642
+ ws,
3643
+ bs,
3644
+ *,
3645
+ absolute_width=None,
3646
+ stack=None,
3647
+ stacked=None,
3648
+ negpos=False,
3649
+ orientation="vertical",
3650
+ **kwargs,
3651
+ ):
3652
+ """
3653
+ Apply bar or barh command. Support default "minima" at zero.
3654
+ """
3655
+ # Parse args
3656
+ kw = kwargs.copy()
3657
+ kw, extents = self._inbounds_extent(**kw)
3658
+ name = "barh" if orientation == "horizontal" else "bar"
3659
+ stack = _not_none(stack=stack, stacked=stacked)
3660
+ xs, hs, kw = self._parse_1d_args(xs, hs, orientation=orientation, **kw)
3661
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
3662
+ if absolute_width is None:
3663
+ absolute_width = _inside_seaborn_call()
3664
+
3665
+ # Call func after converting bar width
3666
+ b0 = 0
3667
+ objs = []
3668
+ kw.update(_pop_props(kw, "patch"))
3669
+ hs, kw = inputs._dist_reduce(hs, **kw)
3670
+ guide_kw = _pop_params(kw, self._update_guide)
3671
+ alphas = kw.pop("alpha", None)
3672
+ if alphas is None:
3673
+ alphas = xs.size * [None]
3674
+ elif isinstance(alphas, Number):
3675
+ alphas = xs.size * [alphas]
3676
+ elif len(alphas) != xs.size:
3677
+ raise ValueError(
3678
+ f"Received {len(alphas)} values for alpha but needed {xs.size}"
3679
+ )
3680
+ for i, n, x, h, w, b, kw in self._iter_arg_cols(xs, hs, ws, bs, **kw):
3681
+ kw = self._parse_cycle(n, **kw)
3682
+ # Adjust x or y coordinates for grouped and stacked bars
3683
+ w = _not_none(w, np.array([0.8])) # same as mpl but in *relative* units
3684
+ b = _not_none(b, np.array([0.0])) # same as mpl
3685
+ if not absolute_width:
3686
+ w = self._convert_bar_width(x, w)
3687
+ if stack:
3688
+ b = b + b0
3689
+ b0 = b0 + h
3690
+ else: # instead "group" the bars (this is no-op if we have 1 column)
3691
+ w = w / n # rescaled
3692
+ o = 0.5 * (n - 1) # center coordinate
3693
+ x = x + w * (i - o) # += may cause integer/float casting issue
3694
+ # Draw simple bars
3695
+ *eb, kw = self._add_error_bars(
3696
+ x, b + h, default_barstds=True, orientation=orientation, **kw
3697
+ ) # noqa: E501
3698
+ if negpos:
3699
+ obj = self._call_negpos(name, x, h, w, b, use_zero=True, **kw)
3700
+ else:
3701
+ obj = self._call_native(name, x, h, w, b, **kw)
3702
+ self._fix_patch_edges(obj, **edgefix_kw, **kw)
3703
+ for y in (b, b + h):
3704
+ self._inbounds_xylim(extents, x, y, orientation=orientation)
3705
+
3706
+ if alphas[i] is not None:
3707
+ for child in obj.get_children():
3708
+ child.set_alpha(alphas[i])
3709
+ objs.append((*eb, obj) if eb else obj)
3710
+
3711
+ self._update_guide(objs, **guide_kw)
3712
+ return objs[0] if len(objs) == 1 else cbook.silent_list("BarContainer", objs)
3713
+
3714
+ @inputs._preprocess_or_redirect("x", "height", "width", "bottom")
3715
+ @docstring._concatenate_inherited
3716
+ @docstring._snippet_manager
3717
+ def bar(self, *args, **kwargs):
3718
+ """
3719
+ %(plot.bar)s
3720
+ """
3721
+ kwargs = _parse_vert(default_orientation="vertical", **kwargs)
3722
+ return self._apply_bar(*args, **kwargs)
3723
+
3724
+ # WARNING: Swap 'height' and 'width' here so that they are always relative
3725
+ # to the 'tall' axis. This lets people always pass 'width' as keyword
3726
+ @inputs._preprocess_or_redirect("y", "height", "width", "left")
3727
+ @docstring._concatenate_inherited
3728
+ @docstring._snippet_manager
3729
+ def barh(self, *args, **kwargs):
3730
+ """
3731
+ %(plot.barh)s
3732
+ """
3733
+ kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
3734
+ return self._apply_bar(*args, **kwargs)
3735
+
3736
+ # WARNING: 'labels' and 'colors' no longer passed through `data` (seems like
3737
+ # extremely niche usage... `data` variables should be data-like)
3738
+ @inputs._preprocess_or_redirect("x", "explode")
3739
+ @docstring._concatenate_inherited
3740
+ @docstring._snippet_manager
3741
+ def pie(self, x, explode, *, labelpad=None, labeldistance=None, **kwargs):
3742
+ """
3743
+ %(plot.pie)s
3744
+ """
3745
+ kw = kwargs.copy()
3746
+ pad = _not_none(labeldistance=labeldistance, labelpad=labelpad, default=1.15)
3747
+ wedge_kw = kw.pop("wedgeprops", None) or {}
3748
+ wedge_kw.update(_pop_props(kw, "patch"))
3749
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
3750
+ _, x, kw = self._parse_1d_args(
3751
+ x, autox=False, autoy=False, autoreverse=False, **kw
3752
+ )
3753
+ kw = self._parse_cycle(x.size, **kw)
3754
+ objs = self._call_native(
3755
+ "pie", x, explode, labeldistance=pad, wedgeprops=wedge_kw, **kw
3756
+ )
3757
+ objs = tuple(cbook.silent_list(type(seq[0]).__name__, seq) for seq in objs)
3758
+ self._fix_patch_edges(objs[0], **edgefix_kw, **wedge_kw)
3759
+ return objs
3760
+
3761
+ @staticmethod
3762
+ def _parse_box_violin(fillcolor, fillalpha, edgecolor, **kw):
3763
+ """
3764
+ Parse common boxplot and violinplot arguments.
3765
+ """
3766
+ if isinstance(fillcolor, list):
3767
+ warnings._warn_ultraplot(
3768
+ "Passing lists to fillcolor was deprecated in v0.9. Please use "
3769
+ f"the property cycler with e.g. cycle={fillcolor!r} instead."
3770
+ )
3771
+ kw["cycle"] = _not_none(cycle=kw.get("cycle", None), fillcolor=fillcolor)
3772
+ fillcolor = None
3773
+ if isinstance(fillalpha, list):
3774
+ warnings._warn_ultraplot(
3775
+ "Passing lists to fillalpha was removed in v0.9. Please specify "
3776
+ "different opacities using the property cycle colors instead."
3777
+ )
3778
+ fillalpha = fillalpha[0] # too complicated to try to apply this
3779
+ if isinstance(edgecolor, list):
3780
+ warnings._warn_ultraplot(
3781
+ "Passing lists of edgecolors was removed in v0.9. Please call the "
3782
+ "plotting command multiple times with different edge colors instead."
3783
+ )
3784
+ edgecolor = edgecolor[0]
3785
+ return fillcolor, fillalpha, edgecolor, kw
3786
+
3787
+ def _apply_boxplot(
3788
+ self,
3789
+ x,
3790
+ y,
3791
+ *,
3792
+ mean=None,
3793
+ means=None,
3794
+ vert=True,
3795
+ fill=None,
3796
+ filled=None,
3797
+ marker=None,
3798
+ markersize=None,
3799
+ **kwargs,
3800
+ ):
3801
+ """
3802
+ Apply the box plot.
3803
+ """
3804
+ # Global and fill properties
3805
+ kw = kwargs.copy()
3806
+ kw.update(_pop_props(kw, "patch"))
3807
+ fill = _not_none(fill=fill, filled=filled)
3808
+ means = _not_none(mean=mean, means=means, showmeans=kw.get("showmeans"))
3809
+ linewidth = kw.pop("linewidth", rc["patch.linewidth"])
3810
+ edgecolor = kw.pop("edgecolor", "black")
3811
+ fillcolor = kw.pop("facecolor", None)
3812
+ fillalpha = kw.pop("alpha", None)
3813
+ fillcolor, fillalpha, edgecolor, kw = self._parse_box_violin(
3814
+ fillcolor, fillalpha, edgecolor, **kw
3815
+ )
3816
+ if fill is None:
3817
+ fill = fillcolor is not None or fillalpha is not None
3818
+ fill = fill or kw.get("cycle") is not None
3819
+
3820
+ # Parse non-color properties
3821
+ # NOTE: Output dict keys are plural but we use singular for keyword args
3822
+ props = {}
3823
+ for key in (
3824
+ "boxes",
3825
+ "whiskers",
3826
+ "caps",
3827
+ "fliers",
3828
+ "medians",
3829
+ "means",
3830
+ "hatches",
3831
+ ):
3832
+ prefix = key.rstrip("es") # singular form
3833
+ props[key] = iprops = _pop_props(kw, "line", prefix=prefix)
3834
+ iprops.setdefault("color", edgecolor)
3835
+ iprops.setdefault("linewidth", linewidth)
3836
+ iprops.setdefault("markeredgecolor", edgecolor)
3837
+
3838
+ # Parse color properties
3839
+ x, y, kw = self._parse_1d_args(
3840
+ x, y, autoy=False, autoguide=False, vert=vert, **kw
3841
+ )
3842
+ kw = self._parse_cycle(x.size, **kw) # possibly apply cycle
3843
+ if fill and fillcolor is None:
3844
+ parser = self._get_patches_for_fill
3845
+ fillcolor = [parser.get_next_color() for _ in range(x.size)]
3846
+ else:
3847
+ fillcolor = [fillcolor] * x.size
3848
+
3849
+ # Plot boxes
3850
+ kw.setdefault("positions", x)
3851
+ if means:
3852
+ kw["showmeans"] = kw["meanline"] = True
3853
+ y = inputs._dist_clean(y)
3854
+ # Add hatch to props as boxplot does not have a hatch but Rectangle does
3855
+ hatch = kw.pop("hatch", None)
3856
+ if hatch is None:
3857
+ hatch = [None for _ in range(x.size)]
3858
+
3859
+ artists = self._call_native("boxplot", y, vert=vert, **kw)
3860
+ artists = artists or {} # necessary?
3861
+ artists = {
3862
+ key: cbook.silent_list(type(objs[0]).__name__, objs) if objs else objs
3863
+ for key, objs in artists.items()
3864
+ }
3865
+
3866
+ # Modify artist settings
3867
+
3868
+ for key, aprops in props.items():
3869
+ if key not in artists: # possible if not rendered
3870
+ continue
3871
+ objs = artists[key]
3872
+ for i, obj in enumerate(objs):
3873
+ # Update lines used for boxplot components
3874
+ # TODO: Test this thoroughly!
3875
+ iprops = {
3876
+ key: (
3877
+ value[i // 2 if key in ("caps", "whiskers") else i]
3878
+ if isinstance(value, (list, np.ndarray))
3879
+ else value
3880
+ )
3881
+ for key, value in aprops.items()
3882
+ }
3883
+ obj.update(iprops)
3884
+ # "Filled" boxplot by adding patch beneath line path
3885
+ if key == "boxes" and (
3886
+ fillcolor[i] is not None
3887
+ or fillalpha is not None
3888
+ or hatch[i] is not None
3889
+ ):
3890
+ patch = mpatches.PathPatch(
3891
+ obj.get_path(),
3892
+ linewidth=0.0,
3893
+ facecolor=fillcolor[i],
3894
+ alpha=fillalpha,
3895
+ hatch=hatch[i],
3896
+ )
3897
+ self.add_artist(patch)
3898
+ # Outlier markers
3899
+ if key == "fliers":
3900
+ if marker is not None:
3901
+ obj.set_marker(marker)
3902
+ if markersize is not None:
3903
+ obj.set_markersize(markersize)
3904
+
3905
+ return artists
3906
+
3907
+ @docstring._snippet_manager
3908
+ def box(self, *args, **kwargs):
3909
+ """
3910
+ %(plot.boxplot)s
3911
+ """
3912
+ return self.boxplot(*args, **kwargs)
3913
+
3914
+ @docstring._snippet_manager
3915
+ def boxh(self, *args, **kwargs):
3916
+ """
3917
+ %(plot.boxploth)s
3918
+ """
3919
+ return self.boxploth(*args, **kwargs)
3920
+
3921
+ @inputs._preprocess_or_redirect("positions", "y")
3922
+ @docstring._concatenate_inherited
3923
+ @docstring._snippet_manager
3924
+ def boxplot(self, *args, **kwargs):
3925
+ """
3926
+ %(plot.boxplot)s
3927
+ """
3928
+ kwargs = _parse_vert(default_vert=True, **kwargs)
3929
+ return self._apply_boxplot(*args, **kwargs)
3930
+
3931
+ @inputs._preprocess_or_redirect("positions", "x")
3932
+ @docstring._snippet_manager
3933
+ def boxploth(self, *args, **kwargs):
3934
+ """
3935
+ %(plot.boxploth)s
3936
+ """
3937
+ kwargs = _parse_vert(default_vert=False, **kwargs)
3938
+ return self._apply_boxplot(*args, **kwargs)
3939
+
3940
+ def _apply_violinplot(
3941
+ self,
3942
+ x,
3943
+ y,
3944
+ vert=True,
3945
+ mean=None,
3946
+ means=None,
3947
+ median=None,
3948
+ medians=None,
3949
+ showmeans=None,
3950
+ showmedians=None,
3951
+ showextrema=None,
3952
+ **kwargs,
3953
+ ):
3954
+ """
3955
+ Apply the violinplot.
3956
+ """
3957
+ # Parse keyword args
3958
+ kw = kwargs.copy()
3959
+ kw.update(_pop_props(kw, "patch"))
3960
+ kw.setdefault("capsize", 0) # caps are redundant for violin plots
3961
+ means = _not_none(mean=mean, means=means, showmeans=showmeans)
3962
+ medians = _not_none(median=median, medians=medians, showmedians=showmedians)
3963
+ if showextrema:
3964
+ kw["default_barpctiles"] = True
3965
+ if not means and not medians:
3966
+ medians = _not_none(medians, True)
3967
+ linewidth = kw.pop("linewidth", None)
3968
+ edgecolor = kw.pop("edgecolor", "black")
3969
+ fillcolor = kw.pop("facecolor", None)
3970
+ fillalpha = kw.pop("alpha", None)
3971
+ fillcolor, fillalpha, edgecolor, kw = self._parse_box_violin(
3972
+ fillcolor, fillalpha, edgecolor, **kw
3973
+ )
3974
+
3975
+ # Parse color properties
3976
+ x, y, kw = self._parse_1d_args(
3977
+ x, y, autoy=False, autoguide=False, vert=vert, **kw
3978
+ )
3979
+ kw = self._parse_cycle(x.size, **kw)
3980
+ if fillcolor is None:
3981
+ parser = self._get_patches_for_fill
3982
+ fillcolor = [parser.get_next_color() for _ in range(x.size)]
3983
+ else:
3984
+ fillcolor = [fillcolor] * x.size
3985
+
3986
+ # Plot violins
3987
+ y, kw = inputs._dist_reduce(y, means=means, medians=medians, **kw)
3988
+ *eb, kw = self._add_error_bars(
3989
+ x, y, vert=vert, default_boxstds=True, default_marker=True, **kw
3990
+ ) # noqa: E501
3991
+ kw.pop("labels", None) # already applied in _parse_1d_args
3992
+ kw.setdefault("positions", x) # coordinates passed as keyword
3993
+ y = _not_none(kw.pop("distribution"), y) # i.e. was reduced
3994
+ y = inputs._dist_clean(y)
3995
+
3996
+ hatches = None
3997
+ if "hatch" in kw:
3998
+ hatches = kw.pop("hatch", None)
3999
+ if "hatches" in kw:
4000
+ hatches = kw.pop("hatches", None)
4001
+
4002
+ if hatches is None:
4003
+ hatches = len(y) * [None]
4004
+ elif len(hatches) != len(y):
4005
+ raise ValueError(f"Retrieved {len(hatches)} hatches but need {len(y)}")
4006
+
4007
+ artists = self._call_native(
4008
+ "violinplot",
4009
+ y,
4010
+ vert=vert,
4011
+ showmeans=False,
4012
+ showmedians=False,
4013
+ showextrema=False,
4014
+ **kw,
4015
+ )
4016
+
4017
+ # Modify body settings
4018
+ artists = artists or {} # necessary?
4019
+ bodies = artists.pop("bodies", ()) # should be no other entries
4020
+ if bodies:
4021
+ bodies = cbook.silent_list(type(bodies[0]).__name__, bodies)
4022
+ for i, body in enumerate(bodies):
4023
+ body.set_alpha(1.0) # change default to 1.0
4024
+ if fillcolor[i] is not None:
4025
+ body.set_facecolor(fillcolor[i])
4026
+ if fillalpha is not None:
4027
+ body.set_alpha(fillalpha[i])
4028
+ if edgecolor is not None:
4029
+ body.set_edgecolor(edgecolor)
4030
+ if linewidth is not None:
4031
+ body.set_linewidths(linewidth)
4032
+ if hatches[i] is not None:
4033
+ body.set_hatch(hatches[i])
4034
+ return (bodies, *eb) if eb else bodies
4035
+
4036
+ @docstring._snippet_manager
4037
+ def violin(self, *args, **kwargs):
4038
+ """
4039
+ %(plot.violinplot)s
4040
+ """
4041
+ # WARNING: This disables use of 'violin' by users but
4042
+ # probably very few people use this anyway.
4043
+ if getattr(self, "_internal_call", None):
4044
+ return super().violin(*args, **kwargs)
4045
+ else:
4046
+ return self.violinplot(*args, **kwargs)
4047
+
4048
+ @docstring._snippet_manager
4049
+ def violinh(self, *args, **kwargs):
4050
+ """
4051
+ %(plot.violinploth)s
4052
+ """
4053
+ return self.violinploth(*args, **kwargs)
4054
+
4055
+ @inputs._preprocess_or_redirect("positions", "y")
4056
+ @docstring._concatenate_inherited
4057
+ @docstring._snippet_manager
4058
+ def violinplot(self, *args, **kwargs):
4059
+ """
4060
+ %(plot.violinplot)s
4061
+ """
4062
+ kwargs = _parse_vert(default_vert=True, **kwargs)
4063
+ return self._apply_violinplot(*args, **kwargs)
4064
+
4065
+ @inputs._preprocess_or_redirect("positions", "x")
4066
+ @docstring._snippet_manager
4067
+ def violinploth(self, *args, **kwargs):
4068
+ """
4069
+ %(plot.violinploth)s
4070
+ """
4071
+ kwargs = _parse_vert(default_vert=False, **kwargs)
4072
+ return self._apply_violinplot(*args, **kwargs)
4073
+
4074
+ def _apply_hist(
4075
+ self,
4076
+ xs,
4077
+ bins,
4078
+ *,
4079
+ width=None,
4080
+ rwidth=None,
4081
+ stack=None,
4082
+ stacked=None,
4083
+ fill=None,
4084
+ filled=None,
4085
+ histtype=None,
4086
+ orientation="vertical",
4087
+ **kwargs,
4088
+ ):
4089
+ """
4090
+ Apply the histogram.
4091
+ """
4092
+ # NOTE: While Axes.bar() adds labels to the container Axes.hist() only
4093
+ # adds them to the first elements in the container for each column
4094
+ # of the input data. Make sure that legend() will read both containers
4095
+ # and individual items inside those containers.
4096
+ _, xs, kw = self._parse_1d_args(
4097
+ xs, autoreverse=False, orientation=orientation, **kwargs
4098
+ )
4099
+ fill = _not_none(fill=fill, filled=filled)
4100
+ stack = _not_none(stack=stack, stacked=stacked)
4101
+ if fill is not None:
4102
+ histtype = _not_none(histtype, "stepfilled" if fill else "step")
4103
+ if stack is not None:
4104
+ histtype = _not_none(histtype, "barstacked" if stack else "bar")
4105
+ kw["bins"] = bins
4106
+ kw["label"] = kw.pop("labels", None) # multiple labels are natively supported
4107
+ kw["rwidth"] = _not_none(width=width, rwidth=rwidth) # latter is native
4108
+ kw["histtype"] = histtype = _not_none(histtype, "bar")
4109
+ kw.update(_pop_props(kw, "patch"))
4110
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4111
+ guide_kw = _pop_params(kw, self._update_guide)
4112
+ n = xs.shape[1] if xs.ndim > 1 else 1
4113
+ kw = self._parse_cycle(n, **kw)
4114
+ obj = self._call_native("hist", xs, orientation=orientation, **kw)
4115
+ if histtype.startswith("bar"):
4116
+ self._fix_patch_edges(obj[2], **edgefix_kw, **kw)
4117
+ # Revert to mpl < 3.3 behavior where silent_list was always returned for
4118
+ # non-bar-type histograms. Because consistency.
4119
+ res = obj[2]
4120
+ if type(res) is list: # 'step' histtype plots
4121
+ res = cbook.silent_list("Polygon", res)
4122
+ obj = (*obj[:2], res)
4123
+ else:
4124
+ for i, sub in enumerate(res):
4125
+ if type(sub) is list:
4126
+ res[i] = cbook.silent_list("Polygon", sub)
4127
+ self._update_guide(res, **guide_kw)
4128
+ return obj
4129
+
4130
+ @inputs._preprocess_or_redirect("x", "bins", keywords="weights")
4131
+ @docstring._concatenate_inherited
4132
+ @docstring._snippet_manager
4133
+ def hist(self, *args, **kwargs):
4134
+ """
4135
+ %(plot.hist)s
4136
+ """
4137
+ kwargs = _parse_vert(default_orientation="vertical", **kwargs)
4138
+ return self._apply_hist(*args, **kwargs)
4139
+
4140
+ @inputs._preprocess_or_redirect("y", "bins", keywords="weights")
4141
+ @docstring._snippet_manager
4142
+ def histh(self, *args, **kwargs):
4143
+ """
4144
+ %(plot.histh)s
4145
+ """
4146
+ kwargs = _parse_vert(default_orientation="horizontal", **kwargs)
4147
+ return self._apply_hist(*args, **kwargs)
4148
+
4149
+ @inputs._preprocess_or_redirect("x", "y", "bins", keywords="weights")
4150
+ @docstring._concatenate_inherited
4151
+ @docstring._snippet_manager
4152
+ def hist2d(self, x, y, bins, **kwargs):
4153
+ """
4154
+ %(plot.hist2d)s
4155
+ """
4156
+ # Rely on the pcolormesh() override for this.
4157
+ if bins is not None:
4158
+ kwargs["bins"] = bins
4159
+ return super().hist2d(x, y, autoreverse=False, default_discrete=False, **kwargs)
4160
+
4161
+ # WARNING: breaking change from native 'C'
4162
+ @inputs._preprocess_or_redirect("x", "y", "weights")
4163
+ @docstring._concatenate_inherited
4164
+ @docstring._snippet_manager
4165
+ def hexbin(self, x, y, weights, **kwargs):
4166
+ """
4167
+ %(plot.hexbin)s
4168
+ """
4169
+ # WARNING: Cannot use automatic level generation here until counts are
4170
+ # estimated. Inside _parse_level_vals if no manual levels were provided then
4171
+ # _parse_level_num is skipped and args like levels=10 or locator=5 are ignored
4172
+ kw = kwargs.copy()
4173
+ x, y, kw = self._parse_1d_args(x, y, autoreverse=False, autovalues=True, **kw)
4174
+ kw.update(_pop_props(kw, "collection")) # takes LineCollection props
4175
+ kw = self._parse_cmap(x, y, y, skip_autolev=True, default_discrete=False, **kw)
4176
+ norm = kw.get("norm", None)
4177
+ if norm is not None and not isinstance(norm, pcolors.DiscreteNorm):
4178
+ norm.vmin = norm.vmax = None # remove nonsense values
4179
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4180
+ guide_kw = _pop_params(kw, self._update_guide)
4181
+ m = self._call_native("hexbin", x, y, weights, **kw)
4182
+ self._add_auto_labels(m, **labels_kw)
4183
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4184
+ return m
4185
+
4186
+ @inputs._preprocess_or_redirect("x", "y", "z")
4187
+ @docstring._concatenate_inherited
4188
+ @docstring._snippet_manager
4189
+ def contour(self, x, y, z, **kwargs):
4190
+ """
4191
+ %(plot.contour)s
4192
+ """
4193
+ x, y, z, kw = self._parse_2d_args(x, y, z, **kwargs)
4194
+ kw.update(_pop_props(kw, "collection"))
4195
+ kw = self._parse_cmap(
4196
+ x, y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
4197
+ )
4198
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4199
+ guide_kw = _pop_params(kw, self._update_guide)
4200
+ label = kw.pop("label", None)
4201
+ m = self._call_native("contour", x, y, z, **kw)
4202
+ m._legend_label = label
4203
+ self._add_auto_labels(m, **labels_kw)
4204
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4205
+ return m
4206
+
4207
+ @inputs._preprocess_or_redirect("x", "y", "z")
4208
+ @docstring._concatenate_inherited
4209
+ @docstring._snippet_manager
4210
+ def contourf(self, x, y, z, **kwargs):
4211
+ """
4212
+ %(plot.contourf)s
4213
+ """
4214
+ x, y, z, kw = self._parse_2d_args(x, y, z, **kwargs)
4215
+ kw.update(_pop_props(kw, "collection"))
4216
+ kw = self._parse_cmap(x, y, z, plot_contours=True, **kw)
4217
+ contour_kw = _pop_kwargs(kw, "edgecolors", "linewidths", "linestyles")
4218
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4219
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4220
+ guide_kw = _pop_params(kw, self._update_guide)
4221
+ label = kw.pop("label", None)
4222
+ m = cm = self._call_native("contourf", x, y, z, **kw)
4223
+ m._legend_label = label
4224
+ self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # no-op if not contour_kw
4225
+ if contour_kw or labels_kw:
4226
+ cm = self._fix_contour_edges("contour", x, y, z, **kw, **contour_kw)
4227
+ self._add_auto_labels(m, cm, **labels_kw)
4228
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4229
+ return m
4230
+
4231
+ @inputs._preprocess_or_redirect("x", "y", "z")
4232
+ @docstring._concatenate_inherited
4233
+ @docstring._snippet_manager
4234
+ def pcolor(self, x, y, z, **kwargs):
4235
+ """
4236
+ %(plot.pcolor)s
4237
+ """
4238
+ x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
4239
+ kw.update(_pop_props(kw, "collection"))
4240
+ kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
4241
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4242
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4243
+ guide_kw = _pop_params(kw, self._update_guide)
4244
+ with self._keep_grid_bools():
4245
+ m = self._call_native("pcolor", x, y, z, **kw)
4246
+ self._fix_patch_edges(m, **edgefix_kw, **kw)
4247
+ self._add_auto_labels(m, **labels_kw)
4248
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4249
+ return m
4250
+
4251
+ @inputs._preprocess_or_redirect("x", "y", "z")
4252
+ @docstring._concatenate_inherited
4253
+ @docstring._snippet_manager
4254
+ def pcolormesh(self, x, y, z, **kwargs):
4255
+ """
4256
+ %(plot.pcolormesh)s
4257
+ """
4258
+ x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
4259
+ kw.update(_pop_props(kw, "collection"))
4260
+ kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
4261
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4262
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4263
+ guide_kw = _pop_params(kw, self._update_guide)
4264
+ with self._keep_grid_bools():
4265
+ m = self._call_native("pcolormesh", x, y, z, **kw)
4266
+ self._fix_patch_edges(m, **edgefix_kw, **kw)
4267
+ self._add_auto_labels(m, **labels_kw)
4268
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4269
+ return m
4270
+
4271
+ @inputs._preprocess_or_redirect("x", "y", "z")
4272
+ @docstring._concatenate_inherited
4273
+ @docstring._snippet_manager
4274
+ def pcolorfast(self, x, y, z, **kwargs):
4275
+ """
4276
+ %(plot.pcolorfast)s
4277
+ """
4278
+ x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs)
4279
+ kw.update(_pop_props(kw, "collection"))
4280
+ kw = self._parse_cmap(x, y, z, to_centers=True, **kw)
4281
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4282
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4283
+ guide_kw = _pop_params(kw, self._update_guide)
4284
+ with self._keep_grid_bools():
4285
+ m = self._call_native("pcolorfast", x, y, z, **kw)
4286
+ if not isinstance(m, mimage.AxesImage): # NOTE: PcolorImage is derivative
4287
+ self._fix_patch_edges(m, **edgefix_kw, **kw)
4288
+ self._add_auto_labels(m, **labels_kw)
4289
+ elif edgefix_kw or labels_kw:
4290
+ kw = {**edgefix_kw, **labels_kw}
4291
+ warnings._warn_ultraplot(
4292
+ f"Ignoring unused keyword argument(s): {kw}. These only work with "
4293
+ "QuadMesh, not AxesImage. Consider using pcolor() or pcolormesh()."
4294
+ )
4295
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4296
+ return m
4297
+
4298
+ @docstring._snippet_manager
4299
+ def heatmap(self, *args, aspect=None, **kwargs):
4300
+ """
4301
+ %(plot.heatmap)s
4302
+ """
4303
+ obj = self.pcolormesh(*args, default_discrete=False, **kwargs)
4304
+ aspect = _not_none(aspect, rc["image.aspect"])
4305
+ if self._name != "cartesian":
4306
+ warnings._warn_ultraplot(
4307
+ "The heatmap() command is meant for CartesianAxes "
4308
+ "only. Please use pcolor() or pcolormesh() instead."
4309
+ )
4310
+ return obj
4311
+ coords = getattr(obj, "_coordinates", None)
4312
+ xlocator = ylocator = None
4313
+ if coords is not None:
4314
+ coords = 0.5 * (coords[1:, ...] + coords[:-1, ...])
4315
+ coords = 0.5 * (coords[:, 1:, :] + coords[:, :-1, :])
4316
+ xlocator, ylocator = coords[0, :, 0], coords[:, 0, 1]
4317
+ kw = {"aspect": aspect, "xgrid": False, "ygrid": False}
4318
+ if xlocator is not None and self.xaxis.isDefault_majloc:
4319
+ kw["xlocator"] = xlocator
4320
+ if ylocator is not None and self.yaxis.isDefault_majloc:
4321
+ kw["ylocator"] = ylocator
4322
+ if self.xaxis.isDefault_minloc:
4323
+ kw["xtickminor"] = False
4324
+ if self.yaxis.isDefault_minloc:
4325
+ kw["ytickminor"] = False
4326
+ self.format(**kw)
4327
+ return obj
4328
+
4329
+ @inputs._preprocess_or_redirect("x", "y", "u", "v", ("c", "color", "colors"))
4330
+ @docstring._concatenate_inherited
4331
+ @docstring._snippet_manager
4332
+ def barbs(self, x, y, u, v, c, **kwargs):
4333
+ """
4334
+ %(plot.barbs)s
4335
+ """
4336
+ x, y, u, v, kw = self._parse_2d_args(
4337
+ x, y, u, v, allow1d=True, autoguide=False, **kwargs
4338
+ ) # noqa: E501
4339
+ kw.update(_pop_props(kw, "line")) # applied to barbs
4340
+ c, kw = self._parse_color(x, y, c, **kw)
4341
+ if mcolors.is_color_like(c):
4342
+ kw["barbcolor"], c = c, None
4343
+ a = [x, y, u, v]
4344
+ if c is not None:
4345
+ a.append(c)
4346
+ kw.pop("colorbar_kw", None) # added by _parse_cmap
4347
+ m = self._call_native("barbs", *a, **kw)
4348
+ return m
4349
+
4350
+ @inputs._preprocess_or_redirect("x", "y", "u", "v", ("c", "color", "colors"))
4351
+ @docstring._concatenate_inherited
4352
+ @docstring._snippet_manager
4353
+ def quiver(self, x, y, u, v, c, **kwargs):
4354
+ """
4355
+ %(plot.quiver)s
4356
+ """
4357
+ x, y, u, v, kw = self._parse_2d_args(
4358
+ x, y, u, v, allow1d=True, autoguide=False, **kwargs
4359
+ ) # noqa: E501
4360
+ kw.update(_pop_props(kw, "line")) # applied to arrow outline
4361
+ c, kw = self._parse_color(x, y, c, **kw)
4362
+ color = None
4363
+ if mcolors.is_color_like(c):
4364
+ color, c = c, None
4365
+ if color is not None:
4366
+ kw["color"] = color
4367
+ a = [x, y, u, v]
4368
+ if c is not None:
4369
+ a.append(c)
4370
+ kw.pop("colorbar_kw", None) # added by _parse_cmap
4371
+ m = self._call_native("quiver", *a, **kw)
4372
+ return m
4373
+
4374
+ @docstring._snippet_manager
4375
+ def stream(self, *args, **kwargs):
4376
+ """
4377
+ %(plot.stream)s
4378
+ """
4379
+ return self.streamplot(*args, **kwargs)
4380
+
4381
+ # WARNING: breaking change from native streamplot() fifth positional arg 'density'
4382
+ @inputs._preprocess_or_redirect(
4383
+ "x", "y", "u", "v", ("c", "color", "colors"), keywords="start_points"
4384
+ )
4385
+ @docstring._concatenate_inherited
4386
+ @docstring._snippet_manager
4387
+ def streamplot(self, x, y, u, v, c, **kwargs):
4388
+ """
4389
+ %(plot.stream)s
4390
+ """
4391
+ x, y, u, v, kw = self._parse_2d_args(x, y, u, v, **kwargs)
4392
+ kw.update(_pop_props(kw, "line")) # applied to lines
4393
+ c, kw = self._parse_color(x, y, c, **kw)
4394
+ if c is None: # throws an error if color not provided
4395
+ c = pcolors.to_hex(self._get_lines.get_next_color())
4396
+ kw["color"] = c # always pass this
4397
+ guide_kw = _pop_params(kw, self._update_guide)
4398
+ label = kw.pop("label", None)
4399
+ m = self._call_native("streamplot", x, y, u, v, **kw)
4400
+ m.lines.set_label(label) # the collection label
4401
+ self._update_guide(m.lines, queue_colorbar=False, **guide_kw) # use lines
4402
+ return m
4403
+
4404
+ @inputs._preprocess_or_redirect("x", "y", "z")
4405
+ @docstring._concatenate_inherited
4406
+ @docstring._snippet_manager
4407
+ def tricontour(self, x, y, z, **kwargs):
4408
+ """
4409
+ %(plot.tricontour)s
4410
+ """
4411
+ kw = kwargs.copy()
4412
+ if x is None or y is None or z is None:
4413
+ raise ValueError("Three input arguments are required.")
4414
+ kw.update(_pop_props(kw, "collection"))
4415
+ kw = self._parse_cmap(
4416
+ x, y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
4417
+ )
4418
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4419
+ guide_kw = _pop_params(kw, self._update_guide)
4420
+ label = kw.pop("label", None)
4421
+ m = self._call_native("tricontour", x, y, z, **kw)
4422
+ m._legend_label = label
4423
+ self._add_auto_labels(m, **labels_kw)
4424
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4425
+ return m
4426
+
4427
+ @inputs._preprocess_or_redirect("x", "y", "z")
4428
+ @docstring._concatenate_inherited
4429
+ @docstring._snippet_manager
4430
+ def tricontourf(self, x, y, z, **kwargs):
4431
+ """
4432
+ %(plot.tricontourf)s
4433
+ """
4434
+ kw = kwargs.copy()
4435
+ if x is None or y is None or z is None:
4436
+ raise ValueError("Three input arguments are required.")
4437
+ kw.update(_pop_props(kw, "collection"))
4438
+ contour_kw = _pop_kwargs(kw, "edgecolors", "linewidths", "linestyles")
4439
+ kw = self._parse_cmap(x, y, z, plot_contours=True, **kw)
4440
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4441
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4442
+ guide_kw = _pop_params(kw, self._update_guide)
4443
+ label = kw.pop("label", None)
4444
+ m = cm = self._call_native("tricontourf", x, y, z, **kw)
4445
+ m._legend_label = label
4446
+ self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # no-op if not contour_kw
4447
+ if contour_kw or labels_kw:
4448
+ cm = self._fix_contour_edges("tricontour", x, y, z, **kw, **contour_kw)
4449
+ self._add_auto_labels(m, cm, **labels_kw)
4450
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4451
+ return m
4452
+
4453
+ @inputs._preprocess_or_redirect("x", "y", "z")
4454
+ @docstring._concatenate_inherited
4455
+ @docstring._snippet_manager
4456
+ def tripcolor(self, x, y, z, **kwargs):
4457
+ """
4458
+ %(plot.tripcolor)s
4459
+ """
4460
+ kw = kwargs.copy()
4461
+ if x is None or y is None or z is None:
4462
+ raise ValueError("Three input arguments are required.")
4463
+ kw.update(_pop_props(kw, "collection"))
4464
+ kw = self._parse_cmap(x, y, z, **kw)
4465
+ edgefix_kw = _pop_params(kw, self._fix_patch_edges)
4466
+ labels_kw = _pop_params(kw, self._add_auto_labels)
4467
+ guide_kw = _pop_params(kw, self._update_guide)
4468
+ with self._keep_grid_bools():
4469
+ m = self._call_native("tripcolor", x, y, z, **kw)
4470
+ self._fix_patch_edges(m, **edgefix_kw, **kw)
4471
+ self._add_auto_labels(m, **labels_kw)
4472
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4473
+ return m
4474
+
4475
+ # WARNING: breaking change from native 'X'
4476
+ @inputs._preprocess_or_redirect("z")
4477
+ @docstring._concatenate_inherited
4478
+ @docstring._snippet_manager
4479
+ def imshow(self, z, **kwargs):
4480
+ """
4481
+ %(plot.imshow)s
4482
+ """
4483
+ kw = kwargs.copy()
4484
+ kw = self._parse_cmap(z, default_discrete=False, **kw)
4485
+ guide_kw = _pop_params(kw, self._update_guide)
4486
+ m = self._call_native("imshow", z, **kw)
4487
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4488
+ return m
4489
+
4490
+ # WARNING: breaking change from native 'Z'
4491
+ @inputs._preprocess_or_redirect("z")
4492
+ @docstring._concatenate_inherited
4493
+ @docstring._snippet_manager
4494
+ def matshow(self, z, **kwargs):
4495
+ """
4496
+ %(plot.matshow)s
4497
+ """
4498
+ # Rely on imshow() override for this.
4499
+ return super().matshow(z, **kwargs)
4500
+
4501
+ # WARNING: breaking change from native 'Z'
4502
+ @inputs._preprocess_or_redirect("z")
4503
+ @docstring._concatenate_inherited
4504
+ @docstring._snippet_manager
4505
+ def spy(self, z, **kwargs):
4506
+ """
4507
+ %(plot.spy)s
4508
+ """
4509
+ kw = kwargs.copy()
4510
+ kw.update(_pop_props(kw, "line")) # takes valid Line2D properties
4511
+ default_cmap = pcolors.DiscreteColormap(["w", "k"], "_no_name")
4512
+ kw = self._parse_cmap(z, default_cmap=default_cmap, **kw)
4513
+ guide_kw = _pop_params(kw, self._update_guide)
4514
+ m = self._call_native("spy", z, **kw)
4515
+ self._update_guide(m, queue_colorbar=False, **guide_kw)
4516
+ return m
4517
+
4518
+ def _iter_arg_pairs(self, *args):
4519
+ """
4520
+ Iterate over ``[x1,] y1, [fmt1,] [x2,] y2, [fmt2,] ...`` input.
4521
+ """
4522
+ # NOTE: This is copied from _process_plot_var_args.__call__ to avoid relying
4523
+ # on private API. We emulate this input style with successive plot() calls.
4524
+ args = list(args)
4525
+ while args: # this permits empty input
4526
+ x, y, *args = args
4527
+ if args and isinstance(args[0], str): # format string detected!
4528
+ fmt, *args = args
4529
+ elif isinstance(y, str): # omits some of matplotlib's rigor but whatevs
4530
+ x, y, fmt = None, x, y
4531
+ else:
4532
+ fmt = None
4533
+ yield x, y, fmt
4534
+
4535
+ def _iter_arg_cols(self, *args, label=None, labels=None, values=None, **kwargs):
4536
+ """
4537
+ Iterate over columns of positional arguments.
4538
+ """
4539
+ is_array = lambda data: hasattr(data, "ndim") and hasattr(data, "shape")
4540
+
4541
+ # Determine the number of columns
4542
+ n = max(1 if not is_array(a) or a.ndim < 2 else a.shape[-1] for a in args)
4543
+
4544
+ # Handle labels
4545
+ labels = _not_none(label=label, values=values, labels=labels)
4546
+ if not np.iterable(labels) or isinstance(labels, str):
4547
+ labels = n * [labels]
4548
+ if len(labels) != n:
4549
+ raise ValueError(f"Array has {n} columns but got {len(labels)} labels.")
4550
+ if labels is not None:
4551
+ labels = [
4552
+ str(_not_none(label, "")) for label in inputs._to_numpy_array(labels)
4553
+ ]
4554
+ else:
4555
+ labels = n * [None]
4556
+
4557
+ # Yield successive columns
4558
+ for i in range(n):
4559
+ kw = kwargs.copy()
4560
+ kw["label"] = labels[i] or None
4561
+ a = tuple(a if not is_array(a) or a.ndim < 2 else a[..., i] for a in args)
4562
+ yield (i, n, *a, kw)
4563
+
4564
+ # Related parsing functions for warnings
4565
+ _level_parsers = (_parse_level_vals, _parse_level_num, _parse_level_lim)
4566
+
4567
+ # Rename the shorthands
4568
+ boxes = warnings._rename_objs("0.8.0", boxes=box)
4569
+ violins = warnings._rename_objs("0.8.0", violins=violin)