anhtld commited on
Commit
13bbbbc
·
verified ·
1 Parent(s): 2e2846f

Auto-sync: 2026-06-25 22:43:25 (part 6)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +17 -0
  2. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py +2573 -0
  3. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi +262 -0
  4. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py +1563 -0
  5. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi +139 -0
  6. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py +703 -0
  7. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi +102 -0
  8. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py +0 -0
  9. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi +449 -0
  10. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py +141 -0
  11. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi +56 -0
  12. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py +1702 -0
  13. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi +140 -0
  14. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py +1840 -0
  15. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py +1139 -0
  16. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi +89 -0
  17. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py +0 -0
  18. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi +428 -0
  19. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py +1645 -0
  20. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi +136 -0
  21. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so +3 -0
  22. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi +312 -0
  23. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py +788 -0
  24. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi +160 -0
  25. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py +225 -0
  26. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi +68 -0
  27. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py +1786 -0
  28. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.pyi +215 -0
  29. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py +269 -0
  30. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi +25 -0
  31. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py +309 -0
  32. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi +62 -0
  33. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py +1371 -0
  34. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi +152 -0
  35. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py +812 -0
  36. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi +294 -0
  37. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py +1720 -0
  38. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi +153 -0
  39. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py +908 -0
  40. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi +51 -0
  41. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py +140 -0
  42. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi +33 -0
  43. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py +913 -0
  44. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi +100 -0
  45. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm +220 -0
  46. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm +326 -0
  47. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm +343 -0
  48. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm +195 -0
  49. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm +156 -0
  50. outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagd8a.afm +576 -0
.gitattributes CHANGED
@@ -52,3 +52,20 @@ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/_qhull.cpython-311-x8
52
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/_tri.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
53
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_backend_agg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
54
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_tkagg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/_tri.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
53
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_backend_agg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
54
  outputs/audit_venv/lib/python3.11/site-packages/matplotlib/backends/_tkagg.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
55
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
56
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf filter=lfs diff=lfs merge=lfs -text
57
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf filter=lfs diff=lfs merge=lfs -text
58
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf filter=lfs diff=lfs merge=lfs -text
59
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf filter=lfs diff=lfs merge=lfs -text
60
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf filter=lfs diff=lfs merge=lfs -text
61
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf filter=lfs diff=lfs merge=lfs -text
62
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf filter=lfs diff=lfs merge=lfs -text
63
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf filter=lfs diff=lfs merge=lfs -text
64
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf filter=lfs diff=lfs merge=lfs -text
65
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf filter=lfs diff=lfs merge=lfs -text
66
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf filter=lfs diff=lfs merge=lfs -text
67
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf filter=lfs diff=lfs merge=lfs -text
68
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf filter=lfs diff=lfs merge=lfs -text
69
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf filter=lfs diff=lfs merge=lfs -text
70
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf filter=lfs diff=lfs merge=lfs -text
71
+ outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf filter=lfs diff=lfs merge=lfs -text
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py ADDED
@@ -0,0 +1,2573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Classes for the efficient drawing of large collections of objects that
3
+ share most properties, e.g., a large number of line segments or
4
+ polygons.
5
+
6
+ The classes are not meant to be as flexible as their single element
7
+ counterparts (e.g., you may not be able to select all line styles) but
8
+ they are meant to be fast for common use cases (e.g., a large set of solid
9
+ line segments).
10
+ """
11
+
12
+ import itertools
13
+ import functools
14
+ import math
15
+ from numbers import Number, Real
16
+ import warnings
17
+
18
+ import numpy as np
19
+
20
+ import matplotlib as mpl
21
+ from . import (_api, _path, artist, cbook, colorizer as mcolorizer, colors as mcolors,
22
+ _docstring, hatch as mhatch, lines as mlines, path as mpath, transforms)
23
+ from ._enums import JoinStyle, CapStyle
24
+
25
+
26
+ # "color" is excluded; it is a compound setter, and its docstring differs
27
+ # in LineCollection.
28
+ @_api.define_aliases({
29
+ "antialiased": ["antialiaseds", "aa"],
30
+ "edgecolor": ["edgecolors", "ec"],
31
+ "facecolor": ["facecolors", "fc"],
32
+ "linestyle": ["linestyles", "dashes", "ls"],
33
+ "linewidth": ["linewidths", "lw"],
34
+ "offset_transform": ["transOffset"],
35
+ })
36
+ class Collection(mcolorizer.ColorizingArtist):
37
+ r"""
38
+ Base class for Collections. Must be subclassed to be usable.
39
+
40
+ A Collection represents a sequence of `.Patch`\es that can be drawn
41
+ more efficiently together than individually. For example, when a single
42
+ path is being drawn repeatedly at different offsets, the renderer can
43
+ typically execute a ``draw_marker()`` call much more efficiently than a
44
+ series of repeated calls to ``draw_path()`` with the offsets put in
45
+ one-by-one.
46
+
47
+ Most properties of a collection can be configured per-element. Therefore,
48
+ Collections have "plural" versions of many of the properties of a `.Patch`
49
+ (e.g. `.Collection.get_paths` instead of `.Patch.get_path`). Exceptions are
50
+ the *zorder*, *hatch*, *pickradius*, *capstyle* and *joinstyle* properties,
51
+ which can only be set globally for the whole collection.
52
+
53
+ Besides these exceptions, all properties can be specified as single values
54
+ (applying to all elements) or sequences of values. The property of the
55
+ ``i``\th element of the collection is::
56
+
57
+ prop[i % len(prop)]
58
+
59
+ Each Collection can optionally be used as its own `.ScalarMappable` by
60
+ passing the *norm* and *cmap* parameters to its constructor. If the
61
+ Collection's `.ScalarMappable` matrix ``_A`` has been set (via a call
62
+ to `.Collection.set_array`), then at draw time this internal scalar
63
+ mappable will be used to set the ``facecolors`` and ``edgecolors``,
64
+ ignoring those that were manually passed in.
65
+ """
66
+ #: Either a list of 3x3 arrays or an Nx3x3 array (representing N
67
+ #: transforms), suitable for the `all_transforms` argument to
68
+ #: `~matplotlib.backend_bases.RendererBase.draw_path_collection`;
69
+ #: each 3x3 array is used to initialize an
70
+ #: `~matplotlib.transforms.Affine2D` object.
71
+ #: Each kind of collection defines this based on its arguments.
72
+ _transforms = np.empty((0, 3, 3))
73
+
74
+ # Whether to draw an edge by default. Set on a
75
+ # subclass-by-subclass basis.
76
+ _edge_default = False
77
+
78
+ @_docstring.interpd
79
+ def __init__(self, *,
80
+ edgecolors=None,
81
+ facecolors=None,
82
+ linewidths=None,
83
+ linestyles='solid',
84
+ capstyle=None,
85
+ joinstyle=None,
86
+ antialiaseds=None,
87
+ offsets=None,
88
+ offset_transform=None,
89
+ norm=None, # optional for ScalarMappable
90
+ cmap=None, # ditto
91
+ colorizer=None,
92
+ pickradius=5.0,
93
+ hatch=None,
94
+ urls=None,
95
+ zorder=1,
96
+ **kwargs
97
+ ):
98
+ """
99
+ Parameters
100
+ ----------
101
+ edgecolors : :mpltype:`color` or list of colors, default: :rc:`patch.edgecolor`
102
+ Edge color for each patch making up the collection. The special
103
+ value 'face' can be passed to make the edgecolor match the
104
+ facecolor.
105
+ facecolors : :mpltype:`color` or list of colors, default: :rc:`patch.facecolor`
106
+ Face color for each patch making up the collection.
107
+ linewidths : float or list of floats, default: :rc:`patch.linewidth`
108
+ Line width for each patch making up the collection.
109
+ linestyles : str or tuple or list thereof, default: 'solid'
110
+ Valid strings are ['solid', 'dashed', 'dashdot', 'dotted', '-',
111
+ '--', '-.', ':']. Dash tuples should be of the form::
112
+
113
+ (offset, onoffseq),
114
+
115
+ where *onoffseq* is an even length tuple of on and off ink lengths
116
+ in points. For examples, see
117
+ :doc:`/gallery/lines_bars_and_markers/linestyles`.
118
+ capstyle : `.CapStyle`-like, default: 'butt'
119
+ Style to use for capping lines for all paths in the collection.
120
+ Allowed values are %(CapStyle)s.
121
+ joinstyle : `.JoinStyle`-like, default: 'round'
122
+ Style to use for joining lines for all paths in the collection.
123
+ Allowed values are %(JoinStyle)s.
124
+ antialiaseds : bool or list of bool, default: :rc:`patch.antialiased`
125
+ Whether each patch in the collection should be drawn with
126
+ antialiasing.
127
+ offsets : (float, float) or list thereof, default: (0, 0)
128
+ A vector by which to translate each patch after rendering (default
129
+ is no translation). The translation is performed in screen (pixel)
130
+ coordinates (i.e. after the Artist's transform is applied).
131
+ offset_transform : `~.Transform`, default: `.IdentityTransform`
132
+ A single transform which will be applied to each *offsets* vector
133
+ before it is used.
134
+ cmap, norm
135
+ Data normalization and colormapping parameters. See
136
+ `.ScalarMappable` for a detailed description.
137
+ hatch : str, optional
138
+ Hatching pattern to use in filled paths, if any. Valid strings are
139
+ ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*']. See
140
+ :doc:`/gallery/shapes_and_collections/hatch_style_reference` for
141
+ the meaning of each hatch type.
142
+ pickradius : float, default: 5.0
143
+ If ``pickradius <= 0``, then `.Collection.contains` will return
144
+ ``True`` whenever the test point is inside of one of the polygons
145
+ formed by the control points of a Path in the Collection. On the
146
+ other hand, if it is greater than 0, then we instead check if the
147
+ test point is contained in a stroke of width ``2*pickradius``
148
+ following any of the Paths in the Collection.
149
+ urls : list of str, default: None
150
+ A URL for each patch to link to once drawn. Currently only works
151
+ for the SVG backend. See :doc:`/gallery/misc/hyperlinks_sgskip` for
152
+ examples.
153
+ zorder : float, default: 1
154
+ The drawing order, shared by all Patches in the Collection. See
155
+ :doc:`/gallery/misc/zorder_demo` for all defaults and examples.
156
+ **kwargs
157
+ Remaining keyword arguments will be used to set properties as
158
+ ``Collection.set_{key}(val)`` for each key-value pair in *kwargs*.
159
+ """
160
+
161
+ super().__init__(self._get_colorizer(cmap, norm, colorizer))
162
+ # list of un-scaled dash patterns
163
+ # this is needed scaling the dash pattern by linewidth
164
+ self._us_linestyles = [(0, None)]
165
+ # list of dash patterns
166
+ self._linestyles = [(0, None)]
167
+ # list of unbroadcast/scaled linewidths
168
+ self._us_lw = [0]
169
+ self._linewidths = [0]
170
+
171
+ self._gapcolor = None # Currently only used by LineCollection.
172
+
173
+ # Flags set by _set_mappable_flags: are colors from mapping an array?
174
+ self._face_is_mapped = None
175
+ self._edge_is_mapped = None
176
+ self._mapped_colors = None # calculated in update_scalarmappable
177
+ self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
178
+ self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
179
+ self.set_facecolor(facecolors)
180
+ self.set_edgecolor(edgecolors)
181
+ self.set_linewidth(linewidths)
182
+ self.set_linestyle(linestyles)
183
+ self.set_antialiased(antialiaseds)
184
+ self.set_pickradius(pickradius)
185
+ self.set_urls(urls)
186
+ self.set_hatch(hatch)
187
+ self.set_zorder(zorder)
188
+
189
+ if capstyle:
190
+ self.set_capstyle(capstyle)
191
+ else:
192
+ self._capstyle = None
193
+
194
+ if joinstyle:
195
+ self.set_joinstyle(joinstyle)
196
+ else:
197
+ self._joinstyle = None
198
+
199
+ if offsets is not None:
200
+ offsets = np.asanyarray(offsets, float)
201
+ # Broadcast (2,) -> (1, 2) but nothing else.
202
+ if offsets.shape == (2,):
203
+ offsets = offsets[None, :]
204
+
205
+ self._offsets = offsets
206
+ self._offset_transform = offset_transform
207
+
208
+ self._path_effects = None
209
+ self._internal_update(kwargs)
210
+ self._paths = None
211
+
212
+ def get_paths(self):
213
+ return self._paths
214
+
215
+ def set_paths(self, paths):
216
+ self._paths = paths
217
+ self.stale = True
218
+
219
+ def get_transforms(self):
220
+ return self._transforms
221
+
222
+ def get_offset_transform(self):
223
+ """Return the `.Transform` instance used by this artist offset."""
224
+ if self._offset_transform is None:
225
+ self._offset_transform = transforms.IdentityTransform()
226
+ elif (not isinstance(self._offset_transform, transforms.Transform)
227
+ and hasattr(self._offset_transform, '_as_mpl_transform')):
228
+ self._offset_transform = \
229
+ self._offset_transform._as_mpl_transform(self.axes)
230
+ return self._offset_transform
231
+
232
+ def set_offset_transform(self, offset_transform):
233
+ """
234
+ Set the artist offset transform.
235
+
236
+ Parameters
237
+ ----------
238
+ offset_transform : `.Transform`
239
+ """
240
+ self._offset_transform = offset_transform
241
+
242
+ def get_datalim(self, transData):
243
+ # Calculate the data limits and return them as a `.Bbox`.
244
+ #
245
+ # This operation depends on the transforms for the data in the
246
+ # collection and whether the collection has offsets:
247
+ #
248
+ # 1. offsets = None, transform child of transData: use the paths for
249
+ # the automatic limits (i.e. for LineCollection in streamline).
250
+ # 2. offsets != None: offset_transform is child of transData:
251
+ #
252
+ # a. transform is child of transData: use the path + offset for
253
+ # limits (i.e for bar).
254
+ # b. transform is not a child of transData: just use the offsets
255
+ # for the limits (i.e. for scatter)
256
+ #
257
+ # 3. otherwise return a null Bbox.
258
+
259
+ transform = self.get_transform()
260
+ offset_trf = self.get_offset_transform()
261
+ if not (isinstance(offset_trf, transforms.IdentityTransform)
262
+ or offset_trf.contains_branch(transData)):
263
+ # if the offsets are in some coords other than data,
264
+ # then don't use them for autoscaling.
265
+ return transforms.Bbox.null()
266
+
267
+ paths = self.get_paths()
268
+ if not len(paths):
269
+ # No paths to transform
270
+ return transforms.Bbox.null()
271
+
272
+ if not transform.is_affine:
273
+ paths = [transform.transform_path_non_affine(p) for p in paths]
274
+ # Don't convert transform to transform.get_affine() here because
275
+ # we may have transform.contains_branch(transData) but not
276
+ # transforms.get_affine().contains_branch(transData). But later,
277
+ # be careful to only apply the affine part that remains.
278
+
279
+ offsets = self.get_offsets()
280
+
281
+ if any(transform.contains_branch_seperately(transData)):
282
+ # collections that are just in data units (like quiver)
283
+ # can properly have the axes limits set by their shape +
284
+ # offset. LineCollections that have no offsets can
285
+ # also use this algorithm (like streamplot).
286
+ if isinstance(offsets, np.ma.MaskedArray):
287
+ offsets = offsets.filled(np.nan)
288
+ # get_path_collection_extents handles nan but not masked arrays
289
+ return mpath.get_path_collection_extents(
290
+ transform.get_affine() - transData, paths,
291
+ self.get_transforms(),
292
+ offset_trf.transform_non_affine(offsets),
293
+ offset_trf.get_affine().frozen())
294
+
295
+ # NOTE: None is the default case where no offsets were passed in
296
+ if self._offsets is not None:
297
+ # this is for collections that have their paths (shapes)
298
+ # in physical, axes-relative, or figure-relative units
299
+ # (i.e. like scatter). We can't uniquely set limits based on
300
+ # those shapes, so we just set the limits based on their
301
+ # location.
302
+ offsets = (offset_trf - transData).transform(offsets)
303
+ # note A-B means A B^{-1}
304
+ offsets = np.ma.masked_invalid(offsets)
305
+ if not offsets.mask.all():
306
+ bbox = transforms.Bbox.null()
307
+ bbox.update_from_data_xy(offsets)
308
+ return bbox
309
+ return transforms.Bbox.null()
310
+
311
+ def get_window_extent(self, renderer=None):
312
+ # TODO: check to ensure that this does not fail for
313
+ # cases other than scatter plot legend
314
+ return self.get_datalim(transforms.IdentityTransform())
315
+
316
+ def _prepare_points(self):
317
+ # Helper for drawing and hit testing.
318
+
319
+ transform = self.get_transform()
320
+ offset_trf = self.get_offset_transform()
321
+ offsets = self.get_offsets()
322
+ paths = self.get_paths()
323
+
324
+ if self.have_units():
325
+ paths = []
326
+ for path in self.get_paths():
327
+ vertices = path.vertices
328
+ xs, ys = vertices[:, 0], vertices[:, 1]
329
+ xs = self.convert_xunits(xs)
330
+ ys = self.convert_yunits(ys)
331
+ paths.append(mpath.Path(np.column_stack([xs, ys]), path.codes))
332
+ xs = self.convert_xunits(offsets[:, 0])
333
+ ys = self.convert_yunits(offsets[:, 1])
334
+ offsets = np.ma.column_stack([xs, ys])
335
+
336
+ if not transform.is_affine:
337
+ paths = [transform.transform_path_non_affine(path)
338
+ for path in paths]
339
+ transform = transform.get_affine()
340
+ if not offset_trf.is_affine:
341
+ offsets = offset_trf.transform_non_affine(offsets)
342
+ # This might have changed an ndarray into a masked array.
343
+ offset_trf = offset_trf.get_affine()
344
+
345
+ if isinstance(offsets, np.ma.MaskedArray):
346
+ offsets = offsets.filled(np.nan)
347
+ # Changing from a masked array to nan-filled ndarray
348
+ # is probably most efficient at this point.
349
+
350
+ return transform, offset_trf, offsets, paths
351
+
352
+ @artist.allow_rasterization
353
+ def draw(self, renderer):
354
+ if not self.get_visible():
355
+ return
356
+ renderer.open_group(self.__class__.__name__, self.get_gid())
357
+
358
+ self.update_scalarmappable()
359
+
360
+ transform, offset_trf, offsets, paths = self._prepare_points()
361
+
362
+ gc = renderer.new_gc()
363
+ self._set_gc_clip(gc)
364
+ gc.set_snap(self.get_snap())
365
+
366
+ if self._hatch:
367
+ gc.set_hatch(self._hatch)
368
+ gc.set_hatch_color(self._hatch_color)
369
+ gc.set_hatch_linewidth(self._hatch_linewidth)
370
+
371
+ if self.get_sketch_params() is not None:
372
+ gc.set_sketch_params(*self.get_sketch_params())
373
+
374
+ if self.get_path_effects():
375
+ from matplotlib.patheffects import PathEffectRenderer
376
+ renderer = PathEffectRenderer(self.get_path_effects(), renderer)
377
+
378
+ # If the collection is made up of a single shape/color/stroke,
379
+ # it can be rendered once and blitted multiple times, using
380
+ # `draw_markers` rather than `draw_path_collection`. This is
381
+ # *much* faster for Agg, and results in smaller file sizes in
382
+ # PDF/SVG/PS.
383
+
384
+ trans = self.get_transforms()
385
+ facecolors = self.get_facecolor()
386
+ edgecolors = self.get_edgecolor()
387
+ do_single_path_optimization = False
388
+ if (len(paths) == 1 and len(trans) <= 1 and
389
+ len(facecolors) == 1 and len(edgecolors) == 1 and
390
+ len(self._linewidths) == 1 and
391
+ all(ls[1] is None for ls in self._linestyles) and
392
+ len(self._antialiaseds) == 1 and len(self._urls) == 1 and
393
+ self.get_hatch() is None):
394
+ if len(trans):
395
+ combined_transform = transforms.Affine2D(trans[0]) + transform
396
+ else:
397
+ combined_transform = transform
398
+ extents = paths[0].get_extents(combined_transform)
399
+ if (extents.width < self.get_figure(root=True).bbox.width
400
+ and extents.height < self.get_figure(root=True).bbox.height):
401
+ do_single_path_optimization = True
402
+
403
+ if self._joinstyle:
404
+ gc.set_joinstyle(self._joinstyle)
405
+
406
+ if self._capstyle:
407
+ gc.set_capstyle(self._capstyle)
408
+
409
+ if do_single_path_optimization:
410
+ gc.set_foreground(tuple(edgecolors[0]))
411
+ gc.set_linewidth(self._linewidths[0])
412
+ gc.set_dashes(*self._linestyles[0])
413
+ gc.set_antialiased(self._antialiaseds[0])
414
+ gc.set_url(self._urls[0])
415
+ renderer.draw_markers(
416
+ gc, paths[0], combined_transform.frozen(),
417
+ mpath.Path(offsets), offset_trf, tuple(facecolors[0]))
418
+ else:
419
+ if self._gapcolor is not None:
420
+ # First draw paths within the gaps.
421
+ ipaths, ilinestyles = self._get_inverse_paths_linestyles()
422
+ renderer.draw_path_collection(
423
+ gc, transform.frozen(), ipaths,
424
+ self.get_transforms(), offsets, offset_trf,
425
+ [mcolors.to_rgba("none")], self._gapcolor,
426
+ self._linewidths, ilinestyles,
427
+ self._antialiaseds, self._urls,
428
+ "screen")
429
+
430
+ renderer.draw_path_collection(
431
+ gc, transform.frozen(), paths,
432
+ self.get_transforms(), offsets, offset_trf,
433
+ self.get_facecolor(), self.get_edgecolor(),
434
+ self._linewidths, self._linestyles,
435
+ self._antialiaseds, self._urls,
436
+ "screen") # offset_position, kept for backcompat.
437
+
438
+ gc.restore()
439
+ renderer.close_group(self.__class__.__name__)
440
+ self.stale = False
441
+
442
+ def set_pickradius(self, pickradius):
443
+ """
444
+ Set the pick radius used for containment tests.
445
+
446
+ Parameters
447
+ ----------
448
+ pickradius : float
449
+ Pick radius, in points.
450
+ """
451
+ if not isinstance(pickradius, Real):
452
+ raise ValueError(
453
+ f"pickradius must be a real-valued number, not {pickradius!r}")
454
+ self._pickradius = pickradius
455
+
456
+ def get_pickradius(self):
457
+ return self._pickradius
458
+
459
+ def contains(self, mouseevent):
460
+ """
461
+ Test whether the mouse event occurred in the collection.
462
+
463
+ Returns ``bool, dict(ind=itemlist)``, where every item in itemlist
464
+ contains the event.
465
+ """
466
+ if self._different_canvas(mouseevent) or not self.get_visible():
467
+ return False, {}
468
+ pickradius = (
469
+ float(self._picker)
470
+ if isinstance(self._picker, Number) and
471
+ self._picker is not True # the bool, not just nonzero or 1
472
+ else self._pickradius)
473
+ if self.axes:
474
+ self.axes._unstale_viewLim()
475
+ transform, offset_trf, offsets, paths = self._prepare_points()
476
+ # Tests if the point is contained on one of the polygons formed
477
+ # by the control points of each of the paths. A point is considered
478
+ # "on" a path if it would lie within a stroke of width 2*pickradius
479
+ # following the path. If pickradius <= 0, then we instead simply check
480
+ # if the point is *inside* of the path instead.
481
+ ind = _path.point_in_path_collection(
482
+ mouseevent.x, mouseevent.y, pickradius,
483
+ transform.frozen(), paths, self.get_transforms(),
484
+ offsets, offset_trf, pickradius <= 0)
485
+ return len(ind) > 0, dict(ind=ind)
486
+
487
+ def set_urls(self, urls):
488
+ """
489
+ Parameters
490
+ ----------
491
+ urls : list of str or None
492
+
493
+ Notes
494
+ -----
495
+ URLs are currently only implemented by the SVG backend. They are
496
+ ignored by all other backends.
497
+ """
498
+ self._urls = urls if urls is not None else [None]
499
+ self.stale = True
500
+
501
+ def get_urls(self):
502
+ """
503
+ Return a list of URLs, one for each element of the collection.
504
+
505
+ The list contains *None* for elements without a URL. See
506
+ :doc:`/gallery/misc/hyperlinks_sgskip` for an example.
507
+ """
508
+ return self._urls
509
+
510
+ def set_hatch(self, hatch):
511
+ r"""
512
+ Set the hatching pattern
513
+
514
+ *hatch* can be one of::
515
+
516
+ / - diagonal hatching
517
+ \ - back diagonal
518
+ | - vertical
519
+ - - horizontal
520
+ + - crossed
521
+ x - crossed diagonal
522
+ o - small circle
523
+ O - large circle
524
+ . - dots
525
+ * - stars
526
+
527
+ Letters can be combined, in which case all the specified
528
+ hatchings are done. If same letter repeats, it increases the
529
+ density of hatching of that pattern.
530
+
531
+ Unlike other properties such as linewidth and colors, hatching
532
+ can only be specified for the collection as a whole, not separately
533
+ for each member.
534
+
535
+ Parameters
536
+ ----------
537
+ hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
538
+ """
539
+ # Use validate_hatch(list) after deprecation.
540
+ mhatch._validate_hatch_pattern(hatch)
541
+ self._hatch = hatch
542
+ self.stale = True
543
+
544
+ def get_hatch(self):
545
+ """Return the current hatching pattern."""
546
+ return self._hatch
547
+
548
+ def set_hatch_linewidth(self, lw):
549
+ """Set the hatch linewidth."""
550
+ self._hatch_linewidth = lw
551
+
552
+ def get_hatch_linewidth(self):
553
+ """Return the hatch linewidth."""
554
+ return self._hatch_linewidth
555
+
556
+ def set_offsets(self, offsets):
557
+ """
558
+ Set the offsets for the collection.
559
+
560
+ Parameters
561
+ ----------
562
+ offsets : (N, 2) or (2,) array-like
563
+ """
564
+ offsets = np.asanyarray(offsets)
565
+ if offsets.shape == (2,): # Broadcast (2,) -> (1, 2) but nothing else.
566
+ offsets = offsets[None, :]
567
+ cstack = (np.ma.column_stack if isinstance(offsets, np.ma.MaskedArray)
568
+ else np.column_stack)
569
+ self._offsets = cstack(
570
+ (np.asanyarray(self.convert_xunits(offsets[:, 0]), float),
571
+ np.asanyarray(self.convert_yunits(offsets[:, 1]), float)))
572
+ self.stale = True
573
+
574
+ def get_offsets(self):
575
+ """Return the offsets for the collection."""
576
+ # Default to zeros in the no-offset (None) case
577
+ return np.zeros((1, 2)) if self._offsets is None else self._offsets
578
+
579
+ def _get_default_linewidth(self):
580
+ # This may be overridden in a subclass.
581
+ return mpl.rcParams['patch.linewidth'] # validated as float
582
+
583
+ def set_linewidth(self, lw):
584
+ """
585
+ Set the linewidth(s) for the collection. *lw* can be a scalar
586
+ or a sequence; if it is a sequence the patches will cycle
587
+ through the sequence
588
+
589
+ Parameters
590
+ ----------
591
+ lw : float or list of floats
592
+ """
593
+ if lw is None:
594
+ lw = self._get_default_linewidth()
595
+ # get the un-scaled/broadcast lw
596
+ self._us_lw = np.atleast_1d(lw)
597
+
598
+ # scale all of the dash patterns.
599
+ self._linewidths, self._linestyles = self._bcast_lwls(
600
+ self._us_lw, self._us_linestyles)
601
+ self.stale = True
602
+
603
+ def set_linestyle(self, ls):
604
+ """
605
+ Set the linestyle(s) for the collection.
606
+
607
+ =========================== =================
608
+ linestyle description
609
+ =========================== =================
610
+ ``'-'`` or ``'solid'`` solid line
611
+ ``'--'`` or ``'dashed'`` dashed line
612
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
613
+ ``':'`` or ``'dotted'`` dotted line
614
+ =========================== =================
615
+
616
+ Alternatively a dash tuple of the following form can be provided::
617
+
618
+ (offset, onoffseq),
619
+
620
+ where ``onoffseq`` is an even length tuple of on and off ink in points.
621
+
622
+ Parameters
623
+ ----------
624
+ ls : str or tuple or list thereof
625
+ Valid values for individual linestyles include {'-', '--', '-.',
626
+ ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a
627
+ complete description.
628
+ """
629
+ # get the list of raw 'unscaled' dash patterns
630
+ self._us_linestyles = mlines._get_dash_patterns(ls)
631
+
632
+ # broadcast and scale the lw and dash patterns
633
+ self._linewidths, self._linestyles = self._bcast_lwls(
634
+ self._us_lw, self._us_linestyles)
635
+
636
+ @_docstring.interpd
637
+ def set_capstyle(self, cs):
638
+ """
639
+ Set the `.CapStyle` for the collection (for all its elements).
640
+
641
+ Parameters
642
+ ----------
643
+ cs : `.CapStyle` or %(CapStyle)s
644
+ """
645
+ self._capstyle = CapStyle(cs)
646
+
647
+ @_docstring.interpd
648
+ def get_capstyle(self):
649
+ """
650
+ Return the cap style for the collection (for all its elements).
651
+
652
+ Returns
653
+ -------
654
+ %(CapStyle)s or None
655
+ """
656
+ return self._capstyle.name if self._capstyle else None
657
+
658
+ @_docstring.interpd
659
+ def set_joinstyle(self, js):
660
+ """
661
+ Set the `.JoinStyle` for the collection (for all its elements).
662
+
663
+ Parameters
664
+ ----------
665
+ js : `.JoinStyle` or %(JoinStyle)s
666
+ """
667
+ self._joinstyle = JoinStyle(js)
668
+
669
+ @_docstring.interpd
670
+ def get_joinstyle(self):
671
+ """
672
+ Return the join style for the collection (for all its elements).
673
+
674
+ Returns
675
+ -------
676
+ %(JoinStyle)s or None
677
+ """
678
+ return self._joinstyle.name if self._joinstyle else None
679
+
680
+ @staticmethod
681
+ def _bcast_lwls(linewidths, dashes):
682
+ """
683
+ Internal helper function to broadcast + scale ls/lw
684
+
685
+ In the collection drawing code, the linewidth and linestyle are cycled
686
+ through as circular buffers (via ``v[i % len(v)]``). Thus, if we are
687
+ going to scale the dash pattern at set time (not draw time) we need to
688
+ do the broadcasting now and expand both lists to be the same length.
689
+
690
+ Parameters
691
+ ----------
692
+ linewidths : list
693
+ line widths of collection
694
+ dashes : list
695
+ dash specification (offset, (dash pattern tuple))
696
+
697
+ Returns
698
+ -------
699
+ linewidths, dashes : list
700
+ Will be the same length, dashes are scaled by paired linewidth
701
+ """
702
+ if mpl.rcParams['_internal.classic_mode']:
703
+ return linewidths, dashes
704
+ # make sure they are the same length so we can zip them
705
+ if len(dashes) != len(linewidths):
706
+ l_dashes = len(dashes)
707
+ l_lw = len(linewidths)
708
+ gcd = math.gcd(l_dashes, l_lw)
709
+ dashes = list(dashes) * (l_lw // gcd)
710
+ linewidths = list(linewidths) * (l_dashes // gcd)
711
+
712
+ # scale the dash patterns
713
+ dashes = [mlines._scale_dashes(o, d, lw)
714
+ for (o, d), lw in zip(dashes, linewidths)]
715
+
716
+ return linewidths, dashes
717
+
718
+ def get_antialiased(self):
719
+ """
720
+ Get the antialiasing state for rendering.
721
+
722
+ Returns
723
+ -------
724
+ array of bools
725
+ """
726
+ return self._antialiaseds
727
+
728
+ def set_antialiased(self, aa):
729
+ """
730
+ Set the antialiasing state for rendering.
731
+
732
+ Parameters
733
+ ----------
734
+ aa : bool or list of bools
735
+ """
736
+ if aa is None:
737
+ aa = self._get_default_antialiased()
738
+ self._antialiaseds = np.atleast_1d(np.asarray(aa, bool))
739
+ self.stale = True
740
+
741
+ def _get_default_antialiased(self):
742
+ # This may be overridden in a subclass.
743
+ return mpl.rcParams['patch.antialiased']
744
+
745
+ def set_color(self, c):
746
+ """
747
+ Set both the edgecolor and the facecolor.
748
+
749
+ Parameters
750
+ ----------
751
+ c : :mpltype:`color` or list of RGBA tuples
752
+
753
+ See Also
754
+ --------
755
+ Collection.set_facecolor, Collection.set_edgecolor
756
+ For setting the edge or face color individually.
757
+ """
758
+ self.set_facecolor(c)
759
+ self.set_edgecolor(c)
760
+
761
+ def _get_default_facecolor(self):
762
+ # This may be overridden in a subclass.
763
+ return mpl.rcParams['patch.facecolor']
764
+
765
+ def _set_facecolor(self, c):
766
+ if c is None:
767
+ c = self._get_default_facecolor()
768
+
769
+ self._facecolors = mcolors.to_rgba_array(c, self._alpha)
770
+ self.stale = True
771
+
772
+ def set_facecolor(self, c):
773
+ """
774
+ Set the facecolor(s) of the collection. *c* can be a color (all patches
775
+ have same color), or a sequence of colors; if it is a sequence the
776
+ patches will cycle through the sequence.
777
+
778
+ If *c* is 'none', the patch will not be filled.
779
+
780
+ Parameters
781
+ ----------
782
+ c : :mpltype:`color` or list of :mpltype:`color`
783
+ """
784
+ if isinstance(c, str) and c.lower() in ("none", "face"):
785
+ c = c.lower()
786
+ self._original_facecolor = c
787
+ self._set_facecolor(c)
788
+
789
+ def get_facecolor(self):
790
+ return self._facecolors
791
+
792
+ def get_edgecolor(self):
793
+ if cbook._str_equal(self._edgecolors, 'face'):
794
+ return self.get_facecolor()
795
+ else:
796
+ return self._edgecolors
797
+
798
+ def _get_default_edgecolor(self):
799
+ # This may be overridden in a subclass.
800
+ return mpl.rcParams['patch.edgecolor']
801
+
802
+ def _set_edgecolor(self, c):
803
+ set_hatch_color = True
804
+ if c is None:
805
+ if (mpl.rcParams['patch.force_edgecolor']
806
+ or self._edge_default
807
+ or cbook._str_equal(self._original_facecolor, 'none')):
808
+ c = self._get_default_edgecolor()
809
+ else:
810
+ c = 'none'
811
+ set_hatch_color = False
812
+ if cbook._str_lower_equal(c, 'face'):
813
+ self._edgecolors = 'face'
814
+ self.stale = True
815
+ return
816
+ self._edgecolors = mcolors.to_rgba_array(c, self._alpha)
817
+ if set_hatch_color and len(self._edgecolors):
818
+ self._hatch_color = tuple(self._edgecolors[0])
819
+ self.stale = True
820
+
821
+ def set_edgecolor(self, c):
822
+ """
823
+ Set the edgecolor(s) of the collection.
824
+
825
+ Parameters
826
+ ----------
827
+ c : :mpltype:`color` or list of :mpltype:`color` or 'face'
828
+ The collection edgecolor(s). If a sequence, the patches cycle
829
+ through it. If 'face', match the facecolor.
830
+ """
831
+ # We pass through a default value for use in LineCollection.
832
+ # This allows us to maintain None as the default indicator in
833
+ # _original_edgecolor.
834
+ if isinstance(c, str) and c.lower() in ("none", "face"):
835
+ c = c.lower()
836
+ self._original_edgecolor = c
837
+ self._set_edgecolor(c)
838
+
839
+ def set_alpha(self, alpha):
840
+ """
841
+ Set the transparency of the collection.
842
+
843
+ Parameters
844
+ ----------
845
+ alpha : float or array of float or None
846
+ If not None, *alpha* values must be between 0 and 1, inclusive.
847
+ If an array is provided, its length must match the number of
848
+ elements in the collection. Masked values and nans are not
849
+ supported.
850
+ """
851
+ artist.Artist._set_alpha_for_array(self, alpha)
852
+ self._set_facecolor(self._original_facecolor)
853
+ self._set_edgecolor(self._original_edgecolor)
854
+
855
+ set_alpha.__doc__ = artist.Artist._set_alpha_for_array.__doc__
856
+
857
+ def get_linewidth(self):
858
+ return self._linewidths
859
+
860
+ def get_linestyle(self):
861
+ return self._linestyles
862
+
863
+ def _set_mappable_flags(self):
864
+ """
865
+ Determine whether edges and/or faces are color-mapped.
866
+
867
+ This is a helper for update_scalarmappable.
868
+ It sets Boolean flags '_edge_is_mapped' and '_face_is_mapped'.
869
+
870
+ Returns
871
+ -------
872
+ mapping_change : bool
873
+ True if either flag is True, or if a flag has changed.
874
+ """
875
+ # The flags are initialized to None to ensure this returns True
876
+ # the first time it is called.
877
+ edge0 = self._edge_is_mapped
878
+ face0 = self._face_is_mapped
879
+ # After returning, the flags must be Booleans, not None.
880
+ self._edge_is_mapped = False
881
+ self._face_is_mapped = False
882
+ if self._A is not None:
883
+ if not cbook._str_equal(self._original_facecolor, 'none'):
884
+ self._face_is_mapped = True
885
+ if cbook._str_equal(self._original_edgecolor, 'face'):
886
+ self._edge_is_mapped = True
887
+ else:
888
+ if self._original_edgecolor is None:
889
+ self._edge_is_mapped = True
890
+
891
+ mapped = self._face_is_mapped or self._edge_is_mapped
892
+ changed = (edge0 is None or face0 is None
893
+ or self._edge_is_mapped != edge0
894
+ or self._face_is_mapped != face0)
895
+ return mapped or changed
896
+
897
+ def update_scalarmappable(self):
898
+ """
899
+ Update colors from the scalar mappable array, if any.
900
+
901
+ Assign colors to edges and faces based on the array and/or
902
+ colors that were directly set, as appropriate.
903
+ """
904
+ if not self._set_mappable_flags():
905
+ return
906
+ # Allow possibility to call 'self.set_array(None)'.
907
+ if self._A is not None:
908
+ # QuadMesh can map 2d arrays (but pcolormesh supplies 1d array)
909
+ if self._A.ndim > 1 and not isinstance(self, _MeshData):
910
+ raise ValueError('Collections can only map rank 1 arrays')
911
+ if np.iterable(self._alpha):
912
+ if self._alpha.size != self._A.size:
913
+ raise ValueError(
914
+ f'Data array shape, {self._A.shape} '
915
+ 'is incompatible with alpha array shape, '
916
+ f'{self._alpha.shape}. '
917
+ 'This can occur with the deprecated '
918
+ 'behavior of the "flat" shading option, '
919
+ 'in which a row and/or column of the data '
920
+ 'array is dropped.')
921
+ # pcolormesh, scatter, maybe others flatten their _A
922
+ self._alpha = self._alpha.reshape(self._A.shape)
923
+ self._mapped_colors = self.to_rgba(self._A, self._alpha)
924
+
925
+ if self._face_is_mapped:
926
+ self._facecolors = self._mapped_colors
927
+ else:
928
+ self._set_facecolor(self._original_facecolor)
929
+ if self._edge_is_mapped:
930
+ self._edgecolors = self._mapped_colors
931
+ else:
932
+ self._set_edgecolor(self._original_edgecolor)
933
+ self.stale = True
934
+
935
+ def get_fill(self):
936
+ """Return whether face is colored."""
937
+ return not cbook._str_lower_equal(self._original_facecolor, "none")
938
+
939
+ def update_from(self, other):
940
+ """Copy properties from other to self."""
941
+
942
+ artist.Artist.update_from(self, other)
943
+ self._antialiaseds = other._antialiaseds
944
+ self._mapped_colors = other._mapped_colors
945
+ self._edge_is_mapped = other._edge_is_mapped
946
+ self._original_edgecolor = other._original_edgecolor
947
+ self._edgecolors = other._edgecolors
948
+ self._face_is_mapped = other._face_is_mapped
949
+ self._original_facecolor = other._original_facecolor
950
+ self._facecolors = other._facecolors
951
+ self._linewidths = other._linewidths
952
+ self._linestyles = other._linestyles
953
+ self._us_linestyles = other._us_linestyles
954
+ self._pickradius = other._pickradius
955
+ self._hatch = other._hatch
956
+
957
+ # update_from for scalarmappable
958
+ self._A = other._A
959
+ self.norm = other.norm
960
+ self.cmap = other.cmap
961
+ self.stale = True
962
+
963
+
964
+ class _CollectionWithSizes(Collection):
965
+ """
966
+ Base class for collections that have an array of sizes.
967
+ """
968
+ _factor = 1.0
969
+
970
+ def get_sizes(self):
971
+ """
972
+ Return the sizes ('areas') of the elements in the collection.
973
+
974
+ Returns
975
+ -------
976
+ array
977
+ The 'area' of each element.
978
+ """
979
+ return self._sizes
980
+
981
+ def set_sizes(self, sizes, dpi=72.0):
982
+ """
983
+ Set the sizes of each member of the collection.
984
+
985
+ Parameters
986
+ ----------
987
+ sizes : `numpy.ndarray` or None
988
+ The size to set for each element of the collection. The
989
+ value is the 'area' of the element.
990
+ dpi : float, default: 72
991
+ The dpi of the canvas.
992
+ """
993
+ if sizes is None:
994
+ self._sizes = np.array([])
995
+ self._transforms = np.empty((0, 3, 3))
996
+ else:
997
+ self._sizes = np.asarray(sizes)
998
+ self._transforms = np.zeros((len(self._sizes), 3, 3))
999
+ scale = np.sqrt(self._sizes) * dpi / 72.0 * self._factor
1000
+ self._transforms[:, 0, 0] = scale
1001
+ self._transforms[:, 1, 1] = scale
1002
+ self._transforms[:, 2, 2] = 1.0
1003
+ self.stale = True
1004
+
1005
+ @artist.allow_rasterization
1006
+ def draw(self, renderer):
1007
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
1008
+ super().draw(renderer)
1009
+
1010
+
1011
+ class PathCollection(_CollectionWithSizes):
1012
+ r"""
1013
+ A collection of `~.path.Path`\s, as created by e.g. `~.Axes.scatter`.
1014
+ """
1015
+
1016
+ def __init__(self, paths, sizes=None, **kwargs):
1017
+ """
1018
+ Parameters
1019
+ ----------
1020
+ paths : list of `.path.Path`
1021
+ The paths that will make up the `.Collection`.
1022
+ sizes : array-like
1023
+ The factor by which to scale each drawn `~.path.Path`. One unit
1024
+ squared in the Path's data space is scaled to be ``sizes**2``
1025
+ points when rendered.
1026
+ **kwargs
1027
+ Forwarded to `.Collection`.
1028
+ """
1029
+
1030
+ super().__init__(**kwargs)
1031
+ self.set_paths(paths)
1032
+ self.set_sizes(sizes)
1033
+ self.stale = True
1034
+
1035
+ def get_paths(self):
1036
+ return self._paths
1037
+
1038
+ def legend_elements(self, prop="colors", num="auto",
1039
+ fmt=None, func=lambda x: x, **kwargs):
1040
+ """
1041
+ Create legend handles and labels for a PathCollection.
1042
+
1043
+ Each legend handle is a `.Line2D` representing the Path that was drawn,
1044
+ and each label is a string that represents the Path.
1045
+
1046
+ This is useful for obtaining a legend for a `~.Axes.scatter` plot;
1047
+ e.g.::
1048
+
1049
+ scatter = plt.scatter([1, 2, 3], [4, 5, 6], c=[7, 2, 3], num=None)
1050
+ plt.legend(*scatter.legend_elements())
1051
+
1052
+ creates three legend elements, one for each color with the numerical
1053
+ values passed to *c* as the labels.
1054
+
1055
+ Also see the :ref:`automatedlegendcreation` example.
1056
+
1057
+ Parameters
1058
+ ----------
1059
+ prop : {"colors", "sizes"}, default: "colors"
1060
+ If "colors", the legend handles will show the different colors of
1061
+ the collection. If "sizes", the legend will show the different
1062
+ sizes. To set both, use *kwargs* to directly edit the `.Line2D`
1063
+ properties.
1064
+ num : int, None, "auto" (default), array-like, or `~.ticker.Locator`
1065
+ Target number of elements to create.
1066
+ If None, use all unique elements of the mappable array. If an
1067
+ integer, target to use *num* elements in the normed range.
1068
+ If *"auto"*, try to determine which option better suits the nature
1069
+ of the data.
1070
+ The number of created elements may slightly deviate from *num* due
1071
+ to a `~.ticker.Locator` being used to find useful locations.
1072
+ If a list or array, use exactly those elements for the legend.
1073
+ Finally, a `~.ticker.Locator` can be provided.
1074
+ fmt : str, `~matplotlib.ticker.Formatter`, or None (default)
1075
+ The format or formatter to use for the labels. If a string must be
1076
+ a valid input for a `.StrMethodFormatter`. If None (the default),
1077
+ use a `.ScalarFormatter`.
1078
+ func : function, default: ``lambda x: x``
1079
+ Function to calculate the labels. Often the size (or color)
1080
+ argument to `~.Axes.scatter` will have been pre-processed by the
1081
+ user using a function ``s = f(x)`` to make the markers visible;
1082
+ e.g. ``size = np.log10(x)``. Providing the inverse of this
1083
+ function here allows that pre-processing to be inverted, so that
1084
+ the legend labels have the correct values; e.g. ``func = lambda
1085
+ x: 10**x``.
1086
+ **kwargs
1087
+ Allowed keyword arguments are *color* and *size*. E.g. it may be
1088
+ useful to set the color of the markers if *prop="sizes"* is used;
1089
+ similarly to set the size of the markers if *prop="colors"* is
1090
+ used. Any further parameters are passed onto the `.Line2D`
1091
+ instance. This may be useful to e.g. specify a different
1092
+ *markeredgecolor* or *alpha* for the legend handles.
1093
+
1094
+ Returns
1095
+ -------
1096
+ handles : list of `.Line2D`
1097
+ Visual representation of each element of the legend.
1098
+ labels : list of str
1099
+ The string labels for elements of the legend.
1100
+ """
1101
+ handles = []
1102
+ labels = []
1103
+ hasarray = self.get_array() is not None
1104
+ if fmt is None:
1105
+ fmt = mpl.ticker.ScalarFormatter(useOffset=False, useMathText=True)
1106
+ elif isinstance(fmt, str):
1107
+ fmt = mpl.ticker.StrMethodFormatter(fmt)
1108
+ fmt.create_dummy_axis()
1109
+
1110
+ if prop == "colors":
1111
+ if not hasarray:
1112
+ warnings.warn("Collection without array used. Make sure to "
1113
+ "specify the values to be colormapped via the "
1114
+ "`c` argument.")
1115
+ return handles, labels
1116
+ u = np.unique(self.get_array())
1117
+ size = kwargs.pop("size", mpl.rcParams["lines.markersize"])
1118
+ elif prop == "sizes":
1119
+ u = np.unique(self.get_sizes())
1120
+ color = kwargs.pop("color", "k")
1121
+ else:
1122
+ raise ValueError("Valid values for `prop` are 'colors' or "
1123
+ f"'sizes'. You supplied '{prop}' instead.")
1124
+
1125
+ fu = func(u)
1126
+ fmt.axis.set_view_interval(fu.min(), fu.max())
1127
+ fmt.axis.set_data_interval(fu.min(), fu.max())
1128
+ if num == "auto":
1129
+ num = 9
1130
+ if len(u) <= num:
1131
+ num = None
1132
+ if num is None:
1133
+ values = u
1134
+ label_values = func(values)
1135
+ else:
1136
+ if prop == "colors":
1137
+ arr = self.get_array()
1138
+ elif prop == "sizes":
1139
+ arr = self.get_sizes()
1140
+ if isinstance(num, mpl.ticker.Locator):
1141
+ loc = num
1142
+ elif np.iterable(num):
1143
+ loc = mpl.ticker.FixedLocator(num)
1144
+ else:
1145
+ num = int(num)
1146
+ loc = mpl.ticker.MaxNLocator(nbins=num, min_n_ticks=num-1,
1147
+ steps=[1, 2, 2.5, 3, 5, 6, 8, 10])
1148
+ label_values = loc.tick_values(func(arr).min(), func(arr).max())
1149
+ cond = ((label_values >= func(arr).min()) &
1150
+ (label_values <= func(arr).max()))
1151
+ label_values = label_values[cond]
1152
+ yarr = np.linspace(arr.min(), arr.max(), 256)
1153
+ xarr = func(yarr)
1154
+ ix = np.argsort(xarr)
1155
+ values = np.interp(label_values, xarr[ix], yarr[ix])
1156
+
1157
+ kw = {"markeredgewidth": self.get_linewidths()[0],
1158
+ "alpha": self.get_alpha(),
1159
+ **kwargs}
1160
+
1161
+ for val, lab in zip(values, label_values):
1162
+ if prop == "colors":
1163
+ color = self.cmap(self.norm(val))
1164
+ elif prop == "sizes":
1165
+ size = np.sqrt(val)
1166
+ if np.isclose(size, 0.0):
1167
+ continue
1168
+ h = mlines.Line2D([0], [0], ls="", color=color, ms=size,
1169
+ marker=self.get_paths()[0], **kw)
1170
+ handles.append(h)
1171
+ if hasattr(fmt, "set_locs"):
1172
+ fmt.set_locs(label_values)
1173
+ l = fmt(lab)
1174
+ labels.append(l)
1175
+
1176
+ return handles, labels
1177
+
1178
+
1179
+ class PolyCollection(_CollectionWithSizes):
1180
+
1181
+ def __init__(self, verts, sizes=None, *, closed=True, **kwargs):
1182
+ """
1183
+ Parameters
1184
+ ----------
1185
+ verts : list of array-like
1186
+ The sequence of polygons [*verts0*, *verts1*, ...] where each
1187
+ element *verts_i* defines the vertices of polygon *i* as a 2D
1188
+ array-like of shape (M, 2).
1189
+ sizes : array-like, default: None
1190
+ Squared scaling factors for the polygons. The coordinates of each
1191
+ polygon *verts_i* are multiplied by the square-root of the
1192
+ corresponding entry in *sizes* (i.e., *sizes* specify the scaling
1193
+ of areas). The scaling is applied before the Artist master
1194
+ transform.
1195
+ closed : bool, default: True
1196
+ Whether the polygon should be closed by adding a CLOSEPOLY
1197
+ connection at the end.
1198
+ **kwargs
1199
+ Forwarded to `.Collection`.
1200
+ """
1201
+ super().__init__(**kwargs)
1202
+ self.set_sizes(sizes)
1203
+ self.set_verts(verts, closed)
1204
+ self.stale = True
1205
+
1206
+ def set_verts(self, verts, closed=True):
1207
+ """
1208
+ Set the vertices of the polygons.
1209
+
1210
+ Parameters
1211
+ ----------
1212
+ verts : list of array-like
1213
+ The sequence of polygons [*verts0*, *verts1*, ...] where each
1214
+ element *verts_i* defines the vertices of polygon *i* as a 2D
1215
+ array-like of shape (M, 2).
1216
+ closed : bool, default: True
1217
+ Whether the polygon should be closed by adding a CLOSEPOLY
1218
+ connection at the end.
1219
+ """
1220
+ self.stale = True
1221
+ if isinstance(verts, np.ma.MaskedArray):
1222
+ verts = verts.astype(float).filled(np.nan)
1223
+
1224
+ # No need to do anything fancy if the path isn't closed.
1225
+ if not closed:
1226
+ self._paths = [mpath.Path(xy) for xy in verts]
1227
+ return
1228
+
1229
+ # Fast path for arrays
1230
+ if isinstance(verts, np.ndarray) and len(verts.shape) == 3:
1231
+ verts_pad = np.concatenate((verts, verts[:, :1]), axis=1)
1232
+ # Creating the codes once is much faster than having Path do it
1233
+ # separately each time by passing closed=True.
1234
+ codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type)
1235
+ codes[:] = mpath.Path.LINETO
1236
+ codes[0] = mpath.Path.MOVETO
1237
+ codes[-1] = mpath.Path.CLOSEPOLY
1238
+ self._paths = [mpath.Path(xy, codes) for xy in verts_pad]
1239
+ return
1240
+
1241
+ self._paths = []
1242
+ for xy in verts:
1243
+ if len(xy):
1244
+ self._paths.append(mpath.Path._create_closed(xy))
1245
+ else:
1246
+ self._paths.append(mpath.Path(xy))
1247
+
1248
+ set_paths = set_verts
1249
+
1250
+ def set_verts_and_codes(self, verts, codes):
1251
+ """Initialize vertices with path codes."""
1252
+ if len(verts) != len(codes):
1253
+ raise ValueError("'codes' must be a 1D list or array "
1254
+ "with the same length of 'verts'")
1255
+ self._paths = [mpath.Path(xy, cds) if len(xy) else mpath.Path(xy)
1256
+ for xy, cds in zip(verts, codes)]
1257
+ self.stale = True
1258
+
1259
+
1260
+ class FillBetweenPolyCollection(PolyCollection):
1261
+ """
1262
+ `.PolyCollection` that fills the area between two x- or y-curves.
1263
+ """
1264
+ def __init__(
1265
+ self, t_direction, t, f1, f2, *,
1266
+ where=None, interpolate=False, step=None, **kwargs):
1267
+ """
1268
+ Parameters
1269
+ ----------
1270
+ t_direction : {{'x', 'y'}}
1271
+ The axes on which the variable lies.
1272
+
1273
+ - 'x': the curves are ``(t, f1)`` and ``(t, f2)``.
1274
+ - 'y': the curves are ``(f1, t)`` and ``(f2, t)``.
1275
+
1276
+ t : array-like
1277
+ The ``t_direction`` coordinates of the nodes defining the curves.
1278
+
1279
+ f1 : array-like or float
1280
+ The other coordinates of the nodes defining the first curve.
1281
+
1282
+ f2 : array-like or float
1283
+ The other coordinates of the nodes defining the second curve.
1284
+
1285
+ where : array-like of bool, optional
1286
+ Define *where* to exclude some {dir} regions from being filled.
1287
+ The filled regions are defined by the coordinates ``t[where]``.
1288
+ More precisely, fill between ``t[i]`` and ``t[i+1]`` if
1289
+ ``where[i] and where[i+1]``. Note that this definition implies
1290
+ that an isolated *True* value between two *False* values in *where*
1291
+ will not result in filling. Both sides of the *True* position
1292
+ remain unfilled due to the adjacent *False* values.
1293
+
1294
+ interpolate : bool, default: False
1295
+ This option is only relevant if *where* is used and the two curves
1296
+ are crossing each other.
1297
+
1298
+ Semantically, *where* is often used for *f1* > *f2* or
1299
+ similar. By default, the nodes of the polygon defining the filled
1300
+ region will only be placed at the positions in the *t* array.
1301
+ Such a polygon cannot describe the above semantics close to the
1302
+ intersection. The t-sections containing the intersection are
1303
+ simply clipped.
1304
+
1305
+ Setting *interpolate* to *True* will calculate the actual
1306
+ intersection point and extend the filled region up to this point.
1307
+
1308
+ step : {{'pre', 'post', 'mid'}}, optional
1309
+ Define *step* if the filling should be a step function,
1310
+ i.e. constant in between *t*. The value determines where the
1311
+ step will occur:
1312
+
1313
+ - 'pre': The f value is continued constantly to the left from
1314
+ every *t* position, i.e. the interval ``(t[i-1], t[i]]`` has the
1315
+ value ``f[i]``.
1316
+ - 'post': The y value is continued constantly to the right from
1317
+ every *x* position, i.e. the interval ``[t[i], t[i+1])`` has the
1318
+ value ``f[i]``.
1319
+ - 'mid': Steps occur half-way between the *t* positions.
1320
+
1321
+ **kwargs
1322
+ Forwarded to `.PolyCollection`.
1323
+
1324
+ See Also
1325
+ --------
1326
+ .Axes.fill_between, .Axes.fill_betweenx
1327
+ """
1328
+ self.t_direction = t_direction
1329
+ self._interpolate = interpolate
1330
+ self._step = step
1331
+ verts = self._make_verts(t, f1, f2, where)
1332
+ super().__init__(verts, **kwargs)
1333
+
1334
+ @staticmethod
1335
+ def _f_dir_from_t(t_direction):
1336
+ """The direction that is other than `t_direction`."""
1337
+ if t_direction == "x":
1338
+ return "y"
1339
+ elif t_direction == "y":
1340
+ return "x"
1341
+ else:
1342
+ msg = f"t_direction must be 'x' or 'y', got {t_direction!r}"
1343
+ raise ValueError(msg)
1344
+
1345
+ @property
1346
+ def _f_direction(self):
1347
+ """The direction that is other than `self.t_direction`."""
1348
+ return self._f_dir_from_t(self.t_direction)
1349
+
1350
+ def set_data(self, t, f1, f2, *, where=None):
1351
+ """
1352
+ Set new values for the two bounding curves.
1353
+
1354
+ Parameters
1355
+ ----------
1356
+ t : array-like
1357
+ The ``self.t_direction`` coordinates of the nodes defining the curves.
1358
+
1359
+ f1 : array-like or float
1360
+ The other coordinates of the nodes defining the first curve.
1361
+
1362
+ f2 : array-like or float
1363
+ The other coordinates of the nodes defining the second curve.
1364
+
1365
+ where : array-like of bool, optional
1366
+ Define *where* to exclude some {dir} regions from being filled.
1367
+ The filled regions are defined by the coordinates ``t[where]``.
1368
+ More precisely, fill between ``t[i]`` and ``t[i+1]`` if
1369
+ ``where[i] and where[i+1]``. Note that this definition implies
1370
+ that an isolated *True* value between two *False* values in *where*
1371
+ will not result in filling. Both sides of the *True* position
1372
+ remain unfilled due to the adjacent *False* values.
1373
+
1374
+ See Also
1375
+ --------
1376
+ .PolyCollection.set_verts, .Line2D.set_data
1377
+ """
1378
+ t, f1, f2 = self.axes._fill_between_process_units(
1379
+ self.t_direction, self._f_direction, t, f1, f2)
1380
+
1381
+ verts = self._make_verts(t, f1, f2, where)
1382
+ self.set_verts(verts)
1383
+
1384
+ def get_datalim(self, transData):
1385
+ """Calculate the data limits and return them as a `.Bbox`."""
1386
+ datalim = transforms.Bbox.null()
1387
+ datalim.update_from_data_xy((self.get_transform() - transData).transform(
1388
+ np.concatenate([self._bbox, [self._bbox.minpos]])))
1389
+ return datalim
1390
+
1391
+ def _make_verts(self, t, f1, f2, where):
1392
+ """
1393
+ Make verts that can be forwarded to `.PolyCollection`.
1394
+ """
1395
+ self._validate_shapes(self.t_direction, self._f_direction, t, f1, f2)
1396
+
1397
+ where = self._get_data_mask(t, f1, f2, where)
1398
+ t, f1, f2 = np.broadcast_arrays(np.atleast_1d(t), f1, f2, subok=True)
1399
+
1400
+ self._bbox = transforms.Bbox.null()
1401
+ self._bbox.update_from_data_xy(self._fix_pts_xy_order(np.concatenate([
1402
+ np.stack((t[where], f[where]), axis=-1) for f in (f1, f2)])))
1403
+
1404
+ return [
1405
+ self._make_verts_for_region(t, f1, f2, idx0, idx1)
1406
+ for idx0, idx1 in cbook.contiguous_regions(where)
1407
+ ]
1408
+
1409
+ def _get_data_mask(self, t, f1, f2, where):
1410
+ """
1411
+ Return a bool array, with True at all points that should eventually be rendered.
1412
+
1413
+ The array is True at a point if none of the data inputs
1414
+ *t*, *f1*, *f2* is masked and if the input *where* is true at that point.
1415
+ """
1416
+ if where is None:
1417
+ where = True
1418
+ else:
1419
+ where = np.asarray(where, dtype=bool)
1420
+ if where.size != t.size:
1421
+ msg = "where size ({}) does not match {!r} size ({})".format(
1422
+ where.size, self.t_direction, t.size)
1423
+ raise ValueError(msg)
1424
+ return where & ~functools.reduce(
1425
+ np.logical_or, map(np.ma.getmaskarray, [t, f1, f2]))
1426
+
1427
+ @staticmethod
1428
+ def _validate_shapes(t_dir, f_dir, t, f1, f2):
1429
+ """Validate that t, f1 and f2 are 1-dimensional and have the same length."""
1430
+ names = (d + s for d, s in zip((t_dir, f_dir, f_dir), ("", "1", "2")))
1431
+ for name, array in zip(names, [t, f1, f2]):
1432
+ if array.ndim > 1:
1433
+ raise ValueError(f"{name!r} is not 1-dimensional")
1434
+ if t.size > 1 and array.size > 1 and t.size != array.size:
1435
+ msg = "{!r} has size {}, but {!r} has an unequal size of {}".format(
1436
+ t_dir, t.size, name, array.size)
1437
+ raise ValueError(msg)
1438
+
1439
+ def _make_verts_for_region(self, t, f1, f2, idx0, idx1):
1440
+ """
1441
+ Make ``verts`` for a contiguous region between ``idx0`` and ``idx1``, taking
1442
+ into account ``step`` and ``interpolate``.
1443
+ """
1444
+ t_slice = t[idx0:idx1]
1445
+ f1_slice = f1[idx0:idx1]
1446
+ f2_slice = f2[idx0:idx1]
1447
+ if self._step is not None:
1448
+ step_func = cbook.STEP_LOOKUP_MAP["steps-" + self._step]
1449
+ t_slice, f1_slice, f2_slice = step_func(t_slice, f1_slice, f2_slice)
1450
+
1451
+ if self._interpolate:
1452
+ start = self._get_interpolating_points(t, f1, f2, idx0)
1453
+ end = self._get_interpolating_points(t, f1, f2, idx1)
1454
+ else:
1455
+ # Handle scalar f2 (e.g. 0): the fill should go all
1456
+ # the way down to 0 even if none of the dep1 sample points do.
1457
+ start = t_slice[0], f2_slice[0]
1458
+ end = t_slice[-1], f2_slice[-1]
1459
+
1460
+ pts = np.concatenate((
1461
+ np.asarray([start]),
1462
+ np.stack((t_slice, f1_slice), axis=-1),
1463
+ np.asarray([end]),
1464
+ np.stack((t_slice, f2_slice), axis=-1)[::-1]))
1465
+
1466
+ return self._fix_pts_xy_order(pts)
1467
+
1468
+ @classmethod
1469
+ def _get_interpolating_points(cls, t, f1, f2, idx):
1470
+ """Calculate interpolating points."""
1471
+ im1 = max(idx - 1, 0)
1472
+ t_values = t[im1:idx+1]
1473
+ diff_values = f1[im1:idx+1] - f2[im1:idx+1]
1474
+ f1_values = f1[im1:idx+1]
1475
+
1476
+ if len(diff_values) == 2:
1477
+ if np.ma.is_masked(diff_values[1]):
1478
+ return t[im1], f1[im1]
1479
+ elif np.ma.is_masked(diff_values[0]):
1480
+ return t[idx], f1[idx]
1481
+
1482
+ diff_root_t = cls._get_diff_root(0, diff_values, t_values)
1483
+ diff_root_f = cls._get_diff_root(diff_root_t, t_values, f1_values)
1484
+ return diff_root_t, diff_root_f
1485
+
1486
+ @staticmethod
1487
+ def _get_diff_root(x, xp, fp):
1488
+ """Calculate diff root."""
1489
+ order = xp.argsort()
1490
+ return np.interp(x, xp[order], fp[order])
1491
+
1492
+ def _fix_pts_xy_order(self, pts):
1493
+ """
1494
+ Fix pts calculation results with `self.t_direction`.
1495
+
1496
+ In the workflow, it is assumed that `self.t_direction` is 'x'. If this
1497
+ is not true, we need to exchange the coordinates.
1498
+ """
1499
+ return pts[:, ::-1] if self.t_direction == "y" else pts
1500
+
1501
+
1502
+ class RegularPolyCollection(_CollectionWithSizes):
1503
+ """A collection of n-sided regular polygons."""
1504
+
1505
+ _path_generator = mpath.Path.unit_regular_polygon
1506
+ _factor = np.pi ** (-1/2)
1507
+
1508
+ def __init__(self,
1509
+ numsides,
1510
+ *,
1511
+ rotation=0,
1512
+ sizes=(1,),
1513
+ **kwargs):
1514
+ """
1515
+ Parameters
1516
+ ----------
1517
+ numsides : int
1518
+ The number of sides of the polygon.
1519
+ rotation : float
1520
+ The rotation of the polygon in radians.
1521
+ sizes : tuple of float
1522
+ The area of the circle circumscribing the polygon in points^2.
1523
+ **kwargs
1524
+ Forwarded to `.Collection`.
1525
+
1526
+ Examples
1527
+ --------
1528
+ See :doc:`/gallery/event_handling/lasso_demo` for a complete example::
1529
+
1530
+ offsets = np.random.rand(20, 2)
1531
+ facecolors = [cm.jet(x) for x in np.random.rand(20)]
1532
+
1533
+ collection = RegularPolyCollection(
1534
+ numsides=5, # a pentagon
1535
+ rotation=0, sizes=(50,),
1536
+ facecolors=facecolors,
1537
+ edgecolors=("black",),
1538
+ linewidths=(1,),
1539
+ offsets=offsets,
1540
+ offset_transform=ax.transData,
1541
+ )
1542
+ """
1543
+ super().__init__(**kwargs)
1544
+ self.set_sizes(sizes)
1545
+ self._numsides = numsides
1546
+ self._paths = [self._path_generator(numsides)]
1547
+ self._rotation = rotation
1548
+ self.set_transform(transforms.IdentityTransform())
1549
+
1550
+ def get_numsides(self):
1551
+ return self._numsides
1552
+
1553
+ def get_rotation(self):
1554
+ return self._rotation
1555
+
1556
+ @artist.allow_rasterization
1557
+ def draw(self, renderer):
1558
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
1559
+ self._transforms = [
1560
+ transforms.Affine2D(x).rotate(-self._rotation).get_matrix()
1561
+ for x in self._transforms
1562
+ ]
1563
+ # Explicitly not super().draw, because set_sizes must be called before
1564
+ # updating self._transforms.
1565
+ Collection.draw(self, renderer)
1566
+
1567
+
1568
+ class StarPolygonCollection(RegularPolyCollection):
1569
+ """Draw a collection of regular stars with *numsides* points."""
1570
+ _path_generator = mpath.Path.unit_regular_star
1571
+
1572
+
1573
+ class AsteriskPolygonCollection(RegularPolyCollection):
1574
+ """Draw a collection of regular asterisks with *numsides* points."""
1575
+ _path_generator = mpath.Path.unit_regular_asterisk
1576
+
1577
+
1578
+ class LineCollection(Collection):
1579
+ r"""
1580
+ Represents a sequence of `.Line2D`\s that should be drawn together.
1581
+
1582
+ This class extends `.Collection` to represent a sequence of
1583
+ `.Line2D`\s instead of just a sequence of `.Patch`\s.
1584
+ Just as in `.Collection`, each property of a *LineCollection* may be either
1585
+ a single value or a list of values. This list is then used cyclically for
1586
+ each element of the LineCollection, so the property of the ``i``\th element
1587
+ of the collection is::
1588
+
1589
+ prop[i % len(prop)]
1590
+
1591
+ The properties of each member of a *LineCollection* default to their values
1592
+ in :rc:`lines.*` instead of :rc:`patch.*`, and the property *colors* is
1593
+ added in place of *edgecolors*.
1594
+ """
1595
+
1596
+ _edge_default = True
1597
+
1598
+ def __init__(self, segments, # Can be None.
1599
+ *,
1600
+ zorder=2, # Collection.zorder is 1
1601
+ **kwargs
1602
+ ):
1603
+ """
1604
+ Parameters
1605
+ ----------
1606
+ segments : list of (N, 2) array-like
1607
+ A sequence ``[line0, line1, ...]`` where each line is a (N, 2)-shape
1608
+ array-like containing points::
1609
+
1610
+ line0 = [(x0, y0), (x1, y1), ...]
1611
+
1612
+ Each line can contain a different number of points.
1613
+ linewidths : float or list of float, default: :rc:`lines.linewidth`
1614
+ The width of each line in points.
1615
+ colors : :mpltype:`color` or list of color, default: :rc:`lines.color`
1616
+ A sequence of RGBA tuples (e.g., arbitrary color strings, etc, not
1617
+ allowed).
1618
+ antialiaseds : bool or list of bool, default: :rc:`lines.antialiased`
1619
+ Whether to use antialiasing for each line.
1620
+ zorder : float, default: 2
1621
+ zorder of the lines once drawn.
1622
+
1623
+ facecolors : :mpltype:`color` or list of :mpltype:`color`, default: 'none'
1624
+ When setting *facecolors*, each line is interpreted as a boundary
1625
+ for an area, implicitly closing the path from the last point to the
1626
+ first point. The enclosed area is filled with *facecolor*.
1627
+ In order to manually specify what should count as the "interior" of
1628
+ each line, please use `.PathCollection` instead, where the
1629
+ "interior" can be specified by appropriate usage of
1630
+ `~.path.Path.CLOSEPOLY`.
1631
+
1632
+ **kwargs
1633
+ Forwarded to `.Collection`.
1634
+ """
1635
+ # Unfortunately, mplot3d needs this explicit setting of 'facecolors'.
1636
+ kwargs.setdefault('facecolors', 'none')
1637
+ super().__init__(
1638
+ zorder=zorder,
1639
+ **kwargs)
1640
+ self.set_segments(segments)
1641
+
1642
+ def set_segments(self, segments):
1643
+ if segments is None:
1644
+ return
1645
+
1646
+ self._paths = [mpath.Path(seg) if isinstance(seg, np.ma.MaskedArray)
1647
+ else mpath.Path(np.asarray(seg, float))
1648
+ for seg in segments]
1649
+ self.stale = True
1650
+
1651
+ set_verts = set_segments # for compatibility with PolyCollection
1652
+ set_paths = set_segments
1653
+
1654
+ def get_segments(self):
1655
+ """
1656
+ Returns
1657
+ -------
1658
+ list
1659
+ List of segments in the LineCollection. Each list item contains an
1660
+ array of vertices.
1661
+ """
1662
+ segments = []
1663
+
1664
+ for path in self._paths:
1665
+ vertices = [
1666
+ vertex
1667
+ for vertex, _
1668
+ # Never simplify here, we want to get the data-space values
1669
+ # back and there in no way to know the "right" simplification
1670
+ # threshold so never try.
1671
+ in path.iter_segments(simplify=False)
1672
+ ]
1673
+ vertices = np.asarray(vertices)
1674
+ segments.append(vertices)
1675
+
1676
+ return segments
1677
+
1678
+ def _get_default_linewidth(self):
1679
+ return mpl.rcParams['lines.linewidth']
1680
+
1681
+ def _get_default_antialiased(self):
1682
+ return mpl.rcParams['lines.antialiased']
1683
+
1684
+ def _get_default_edgecolor(self):
1685
+ return mpl.rcParams['lines.color']
1686
+
1687
+ def _get_default_facecolor(self):
1688
+ return 'none'
1689
+
1690
+ def set_alpha(self, alpha):
1691
+ # docstring inherited
1692
+ super().set_alpha(alpha)
1693
+ if self._gapcolor is not None:
1694
+ self.set_gapcolor(self._original_gapcolor)
1695
+
1696
+ def set_color(self, c):
1697
+ """
1698
+ Set the edgecolor(s) of the LineCollection.
1699
+
1700
+ Parameters
1701
+ ----------
1702
+ c : :mpltype:`color` or list of :mpltype:`color`
1703
+ Single color (all lines have same color), or a
1704
+ sequence of RGBA tuples; if it is a sequence the lines will
1705
+ cycle through the sequence.
1706
+ """
1707
+ self.set_edgecolor(c)
1708
+
1709
+ set_colors = set_color
1710
+
1711
+ def get_color(self):
1712
+ return self._edgecolors
1713
+
1714
+ get_colors = get_color # for compatibility with old versions
1715
+
1716
+ def set_gapcolor(self, gapcolor):
1717
+ """
1718
+ Set a color to fill the gaps in the dashed line style.
1719
+
1720
+ .. note::
1721
+
1722
+ Striped lines are created by drawing two interleaved dashed lines.
1723
+ There can be overlaps between those two, which may result in
1724
+ artifacts when using transparency.
1725
+
1726
+ This functionality is experimental and may change.
1727
+
1728
+ Parameters
1729
+ ----------
1730
+ gapcolor : :mpltype:`color` or list of :mpltype:`color` or None
1731
+ The color with which to fill the gaps. If None, the gaps are
1732
+ unfilled.
1733
+ """
1734
+ self._original_gapcolor = gapcolor
1735
+ self._set_gapcolor(gapcolor)
1736
+
1737
+ def _set_gapcolor(self, gapcolor):
1738
+ if gapcolor is not None:
1739
+ gapcolor = mcolors.to_rgba_array(gapcolor, self._alpha)
1740
+ self._gapcolor = gapcolor
1741
+ self.stale = True
1742
+
1743
+ def get_gapcolor(self):
1744
+ return self._gapcolor
1745
+
1746
+ def _get_inverse_paths_linestyles(self):
1747
+ """
1748
+ Returns the path and pattern for the gaps in the non-solid lines.
1749
+
1750
+ This path and pattern is the inverse of the path and pattern used to
1751
+ construct the non-solid lines. For solid lines, we set the inverse path
1752
+ to nans to prevent drawing an inverse line.
1753
+ """
1754
+ path_patterns = [
1755
+ (mpath.Path(np.full((1, 2), np.nan)), ls)
1756
+ if ls == (0, None) else
1757
+ (path, mlines._get_inverse_dash_pattern(*ls))
1758
+ for (path, ls) in
1759
+ zip(self._paths, itertools.cycle(self._linestyles))]
1760
+
1761
+ return zip(*path_patterns)
1762
+
1763
+
1764
+ class EventCollection(LineCollection):
1765
+ """
1766
+ A collection of locations along a single axis at which an "event" occurred.
1767
+
1768
+ The events are given by a 1-dimensional array. They do not have an
1769
+ amplitude and are displayed as parallel lines.
1770
+ """
1771
+
1772
+ _edge_default = True
1773
+
1774
+ def __init__(self,
1775
+ positions, # Cannot be None.
1776
+ orientation='horizontal',
1777
+ *,
1778
+ lineoffset=0,
1779
+ linelength=1,
1780
+ linewidth=None,
1781
+ color=None,
1782
+ linestyle='solid',
1783
+ antialiased=None,
1784
+ **kwargs
1785
+ ):
1786
+ """
1787
+ Parameters
1788
+ ----------
1789
+ positions : 1D array-like
1790
+ Each value is an event.
1791
+ orientation : {'horizontal', 'vertical'}, default: 'horizontal'
1792
+ The sequence of events is plotted along this direction.
1793
+ The marker lines of the single events are along the orthogonal
1794
+ direction.
1795
+ lineoffset : float, default: 0
1796
+ The offset of the center of the markers from the origin, in the
1797
+ direction orthogonal to *orientation*.
1798
+ linelength : float, default: 1
1799
+ The total height of the marker (i.e. the marker stretches from
1800
+ ``lineoffset - linelength/2`` to ``lineoffset + linelength/2``).
1801
+ linewidth : float or list thereof, default: :rc:`lines.linewidth`
1802
+ The line width of the event lines, in points.
1803
+ color : :mpltype:`color` or list of :mpltype:`color`, default: :rc:`lines.color`
1804
+ The color of the event lines.
1805
+ linestyle : str or tuple or list thereof, default: 'solid'
1806
+ Valid strings are ['solid', 'dashed', 'dashdot', 'dotted',
1807
+ '-', '--', '-.', ':']. Dash tuples should be of the form::
1808
+
1809
+ (offset, onoffseq),
1810
+
1811
+ where *onoffseq* is an even length tuple of on and off ink
1812
+ in points.
1813
+ antialiased : bool or list thereof, default: :rc:`lines.antialiased`
1814
+ Whether to use antialiasing for drawing the lines.
1815
+ **kwargs
1816
+ Forwarded to `.LineCollection`.
1817
+
1818
+ Examples
1819
+ --------
1820
+ .. plot:: gallery/lines_bars_and_markers/eventcollection_demo.py
1821
+ """
1822
+ super().__init__([],
1823
+ linewidths=linewidth, linestyles=linestyle,
1824
+ colors=color, antialiaseds=antialiased,
1825
+ **kwargs)
1826
+ self._is_horizontal = True # Initial value, may be switched below.
1827
+ self._linelength = linelength
1828
+ self._lineoffset = lineoffset
1829
+ self.set_orientation(orientation)
1830
+ self.set_positions(positions)
1831
+
1832
+ def get_positions(self):
1833
+ """
1834
+ Return an array containing the floating-point values of the positions.
1835
+ """
1836
+ pos = 0 if self.is_horizontal() else 1
1837
+ return [segment[0, pos] for segment in self.get_segments()]
1838
+
1839
+ def set_positions(self, positions):
1840
+ """Set the positions of the events."""
1841
+ if positions is None:
1842
+ positions = []
1843
+ if np.ndim(positions) != 1:
1844
+ raise ValueError('positions must be one-dimensional')
1845
+ lineoffset = self.get_lineoffset()
1846
+ linelength = self.get_linelength()
1847
+ pos_idx = 0 if self.is_horizontal() else 1
1848
+ segments = np.empty((len(positions), 2, 2))
1849
+ segments[:, :, pos_idx] = np.sort(positions)[:, None]
1850
+ segments[:, 0, 1 - pos_idx] = lineoffset + linelength / 2
1851
+ segments[:, 1, 1 - pos_idx] = lineoffset - linelength / 2
1852
+ self.set_segments(segments)
1853
+
1854
+ def add_positions(self, position):
1855
+ """Add one or more events at the specified positions."""
1856
+ if position is None or (hasattr(position, 'len') and
1857
+ len(position) == 0):
1858
+ return
1859
+ positions = self.get_positions()
1860
+ positions = np.hstack([positions, np.asanyarray(position)])
1861
+ self.set_positions(positions)
1862
+ extend_positions = append_positions = add_positions
1863
+
1864
+ def is_horizontal(self):
1865
+ """True if the eventcollection is horizontal, False if vertical."""
1866
+ return self._is_horizontal
1867
+
1868
+ def get_orientation(self):
1869
+ """
1870
+ Return the orientation of the event line ('horizontal' or 'vertical').
1871
+ """
1872
+ return 'horizontal' if self.is_horizontal() else 'vertical'
1873
+
1874
+ def switch_orientation(self):
1875
+ """
1876
+ Switch the orientation of the event line, either from vertical to
1877
+ horizontal or vice versus.
1878
+ """
1879
+ segments = self.get_segments()
1880
+ for i, segment in enumerate(segments):
1881
+ segments[i] = np.fliplr(segment)
1882
+ self.set_segments(segments)
1883
+ self._is_horizontal = not self.is_horizontal()
1884
+ self.stale = True
1885
+
1886
+ def set_orientation(self, orientation):
1887
+ """
1888
+ Set the orientation of the event line.
1889
+
1890
+ Parameters
1891
+ ----------
1892
+ orientation : {'horizontal', 'vertical'}
1893
+ """
1894
+ is_horizontal = _api.check_getitem(
1895
+ {"horizontal": True, "vertical": False},
1896
+ orientation=orientation)
1897
+ if is_horizontal == self.is_horizontal():
1898
+ return
1899
+ self.switch_orientation()
1900
+
1901
+ def get_linelength(self):
1902
+ """Return the length of the lines used to mark each event."""
1903
+ return self._linelength
1904
+
1905
+ def set_linelength(self, linelength):
1906
+ """Set the length of the lines used to mark each event."""
1907
+ if linelength == self.get_linelength():
1908
+ return
1909
+ lineoffset = self.get_lineoffset()
1910
+ segments = self.get_segments()
1911
+ pos = 1 if self.is_horizontal() else 0
1912
+ for segment in segments:
1913
+ segment[0, pos] = lineoffset + linelength / 2.
1914
+ segment[1, pos] = lineoffset - linelength / 2.
1915
+ self.set_segments(segments)
1916
+ self._linelength = linelength
1917
+
1918
+ def get_lineoffset(self):
1919
+ """Return the offset of the lines used to mark each event."""
1920
+ return self._lineoffset
1921
+
1922
+ def set_lineoffset(self, lineoffset):
1923
+ """Set the offset of the lines used to mark each event."""
1924
+ if lineoffset == self.get_lineoffset():
1925
+ return
1926
+ linelength = self.get_linelength()
1927
+ segments = self.get_segments()
1928
+ pos = 1 if self.is_horizontal() else 0
1929
+ for segment in segments:
1930
+ segment[0, pos] = lineoffset + linelength / 2.
1931
+ segment[1, pos] = lineoffset - linelength / 2.
1932
+ self.set_segments(segments)
1933
+ self._lineoffset = lineoffset
1934
+
1935
+ def get_linewidth(self):
1936
+ """Get the width of the lines used to mark each event."""
1937
+ return super().get_linewidth()[0]
1938
+
1939
+ def get_linewidths(self):
1940
+ return super().get_linewidth()
1941
+
1942
+ def get_color(self):
1943
+ """Return the color of the lines used to mark each event."""
1944
+ return self.get_colors()[0]
1945
+
1946
+
1947
+ class CircleCollection(_CollectionWithSizes):
1948
+ """A collection of circles, drawn using splines."""
1949
+
1950
+ _factor = np.pi ** (-1/2)
1951
+
1952
+ def __init__(self, sizes, **kwargs):
1953
+ """
1954
+ Parameters
1955
+ ----------
1956
+ sizes : float or array-like
1957
+ The area of each circle in points^2.
1958
+ **kwargs
1959
+ Forwarded to `.Collection`.
1960
+ """
1961
+ super().__init__(**kwargs)
1962
+ self.set_sizes(sizes)
1963
+ self.set_transform(transforms.IdentityTransform())
1964
+ self._paths = [mpath.Path.unit_circle()]
1965
+
1966
+
1967
+ class EllipseCollection(Collection):
1968
+ """A collection of ellipses, drawn using splines."""
1969
+
1970
+ def __init__(self, widths, heights, angles, *, units='points', **kwargs):
1971
+ """
1972
+ Parameters
1973
+ ----------
1974
+ widths : array-like
1975
+ The lengths of the first axes (e.g., major axis lengths).
1976
+ heights : array-like
1977
+ The lengths of second axes.
1978
+ angles : array-like
1979
+ The angles of the first axes, degrees CCW from the x-axis.
1980
+ units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'}
1981
+ The units in which majors and minors are given; 'width' and
1982
+ 'height' refer to the dimensions of the axes, while 'x' and 'y'
1983
+ refer to the *offsets* data units. 'xy' differs from all others in
1984
+ that the angle as plotted varies with the aspect ratio, and equals
1985
+ the specified angle only when the aspect ratio is unity. Hence
1986
+ it behaves the same as the `~.patches.Ellipse` with
1987
+ ``axes.transData`` as its transform.
1988
+ **kwargs
1989
+ Forwarded to `Collection`.
1990
+ """
1991
+ super().__init__(**kwargs)
1992
+ self.set_widths(widths)
1993
+ self.set_heights(heights)
1994
+ self.set_angles(angles)
1995
+ self._units = units
1996
+ self.set_transform(transforms.IdentityTransform())
1997
+ self._transforms = np.empty((0, 3, 3))
1998
+ self._paths = [mpath.Path.unit_circle()]
1999
+
2000
+ def _set_transforms(self):
2001
+ """Calculate transforms immediately before drawing."""
2002
+
2003
+ ax = self.axes
2004
+ fig = self.get_figure(root=False)
2005
+
2006
+ if self._units == 'xy':
2007
+ sc = 1
2008
+ elif self._units == 'x':
2009
+ sc = ax.bbox.width / ax.viewLim.width
2010
+ elif self._units == 'y':
2011
+ sc = ax.bbox.height / ax.viewLim.height
2012
+ elif self._units == 'inches':
2013
+ sc = fig.dpi
2014
+ elif self._units == 'points':
2015
+ sc = fig.dpi / 72.0
2016
+ elif self._units == 'width':
2017
+ sc = ax.bbox.width
2018
+ elif self._units == 'height':
2019
+ sc = ax.bbox.height
2020
+ elif self._units == 'dots':
2021
+ sc = 1.0
2022
+ else:
2023
+ raise ValueError(f'Unrecognized units: {self._units!r}')
2024
+
2025
+ self._transforms = np.zeros((len(self._widths), 3, 3))
2026
+ widths = self._widths * sc
2027
+ heights = self._heights * sc
2028
+ sin_angle = np.sin(self._angles)
2029
+ cos_angle = np.cos(self._angles)
2030
+ self._transforms[:, 0, 0] = widths * cos_angle
2031
+ self._transforms[:, 0, 1] = heights * -sin_angle
2032
+ self._transforms[:, 1, 0] = widths * sin_angle
2033
+ self._transforms[:, 1, 1] = heights * cos_angle
2034
+ self._transforms[:, 2, 2] = 1.0
2035
+
2036
+ _affine = transforms.Affine2D
2037
+ if self._units == 'xy':
2038
+ m = ax.transData.get_affine().get_matrix().copy()
2039
+ m[:2, 2:] = 0
2040
+ self.set_transform(_affine(m))
2041
+
2042
+ def set_widths(self, widths):
2043
+ """Set the lengths of the first axes (e.g., major axis)."""
2044
+ self._widths = 0.5 * np.asarray(widths).ravel()
2045
+ self.stale = True
2046
+
2047
+ def set_heights(self, heights):
2048
+ """Set the lengths of second axes (e.g., minor axes)."""
2049
+ self._heights = 0.5 * np.asarray(heights).ravel()
2050
+ self.stale = True
2051
+
2052
+ def set_angles(self, angles):
2053
+ """Set the angles of the first axes, degrees CCW from the x-axis."""
2054
+ self._angles = np.deg2rad(angles).ravel()
2055
+ self.stale = True
2056
+
2057
+ def get_widths(self):
2058
+ """Get the lengths of the first axes (e.g., major axis)."""
2059
+ return self._widths * 2
2060
+
2061
+ def get_heights(self):
2062
+ """Set the lengths of second axes (e.g., minor axes)."""
2063
+ return self._heights * 2
2064
+
2065
+ def get_angles(self):
2066
+ """Get the angles of the first axes, degrees CCW from the x-axis."""
2067
+ return np.rad2deg(self._angles)
2068
+
2069
+ @artist.allow_rasterization
2070
+ def draw(self, renderer):
2071
+ self._set_transforms()
2072
+ super().draw(renderer)
2073
+
2074
+
2075
+ class PatchCollection(Collection):
2076
+ """
2077
+ A generic collection of patches.
2078
+
2079
+ PatchCollection draws faster than a large number of equivalent individual
2080
+ Patches. It also makes it easier to assign a colormap to a heterogeneous
2081
+ collection of patches.
2082
+ """
2083
+
2084
+ def __init__(self, patches, *, match_original=False, **kwargs):
2085
+ """
2086
+ Parameters
2087
+ ----------
2088
+ patches : list of `.Patch`
2089
+ A sequence of Patch objects. This list may include
2090
+ a heterogeneous assortment of different patch types.
2091
+
2092
+ match_original : bool, default: False
2093
+ If True, use the colors and linewidths of the original
2094
+ patches. If False, new colors may be assigned by
2095
+ providing the standard collection arguments, facecolor,
2096
+ edgecolor, linewidths, norm or cmap.
2097
+
2098
+ **kwargs
2099
+ All other parameters are forwarded to `.Collection`.
2100
+
2101
+ If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds*
2102
+ are None, they default to their `.rcParams` patch setting, in
2103
+ sequence form.
2104
+
2105
+ Notes
2106
+ -----
2107
+ The use of `~matplotlib.cm.ScalarMappable` functionality is optional.
2108
+ If the `~matplotlib.cm.ScalarMappable` matrix ``_A`` has been set (via
2109
+ a call to `~.ScalarMappable.set_array`), at draw time a call to scalar
2110
+ mappable will be made to set the face colors.
2111
+ """
2112
+
2113
+ if match_original:
2114
+ def determine_facecolor(patch):
2115
+ if patch.get_fill():
2116
+ return patch.get_facecolor()
2117
+ return [0, 0, 0, 0]
2118
+
2119
+ kwargs['facecolors'] = [determine_facecolor(p) for p in patches]
2120
+ kwargs['edgecolors'] = [p.get_edgecolor() for p in patches]
2121
+ kwargs['linewidths'] = [p.get_linewidth() for p in patches]
2122
+ kwargs['linestyles'] = [p.get_linestyle() for p in patches]
2123
+ kwargs['antialiaseds'] = [p.get_antialiased() for p in patches]
2124
+
2125
+ super().__init__(**kwargs)
2126
+
2127
+ self.set_paths(patches)
2128
+
2129
+ def set_paths(self, patches):
2130
+ paths = [p.get_transform().transform_path(p.get_path())
2131
+ for p in patches]
2132
+ self._paths = paths
2133
+
2134
+
2135
+ class TriMesh(Collection):
2136
+ """
2137
+ Class for the efficient drawing of a triangular mesh using Gouraud shading.
2138
+
2139
+ A triangular mesh is a `~matplotlib.tri.Triangulation` object.
2140
+ """
2141
+ def __init__(self, triangulation, **kwargs):
2142
+ super().__init__(**kwargs)
2143
+ self._triangulation = triangulation
2144
+ self._shading = 'gouraud'
2145
+
2146
+ self._bbox = transforms.Bbox.unit()
2147
+
2148
+ # Unfortunately this requires a copy, unless Triangulation
2149
+ # was rewritten.
2150
+ xy = np.hstack((triangulation.x.reshape(-1, 1),
2151
+ triangulation.y.reshape(-1, 1)))
2152
+ self._bbox.update_from_data_xy(xy)
2153
+
2154
+ def get_paths(self):
2155
+ if self._paths is None:
2156
+ self.set_paths()
2157
+ return self._paths
2158
+
2159
+ def set_paths(self):
2160
+ self._paths = self.convert_mesh_to_paths(self._triangulation)
2161
+
2162
+ @staticmethod
2163
+ def convert_mesh_to_paths(tri):
2164
+ """
2165
+ Convert a given mesh into a sequence of `.Path` objects.
2166
+
2167
+ This function is primarily of use to implementers of backends that do
2168
+ not directly support meshes.
2169
+ """
2170
+ triangles = tri.get_masked_triangles()
2171
+ verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1)
2172
+ return [mpath.Path(x) for x in verts]
2173
+
2174
+ @artist.allow_rasterization
2175
+ def draw(self, renderer):
2176
+ if not self.get_visible():
2177
+ return
2178
+ renderer.open_group(self.__class__.__name__, gid=self.get_gid())
2179
+ transform = self.get_transform()
2180
+
2181
+ # Get a list of triangles and the color at each vertex.
2182
+ tri = self._triangulation
2183
+ triangles = tri.get_masked_triangles()
2184
+
2185
+ verts = np.stack((tri.x[triangles], tri.y[triangles]), axis=-1)
2186
+
2187
+ self.update_scalarmappable()
2188
+ colors = self._facecolors[triangles]
2189
+
2190
+ gc = renderer.new_gc()
2191
+ self._set_gc_clip(gc)
2192
+ gc.set_linewidth(self.get_linewidth()[0])
2193
+ renderer.draw_gouraud_triangles(gc, verts, colors, transform.frozen())
2194
+ gc.restore()
2195
+ renderer.close_group(self.__class__.__name__)
2196
+
2197
+
2198
+ class _MeshData:
2199
+ r"""
2200
+ Class for managing the two dimensional coordinates of Quadrilateral meshes
2201
+ and the associated data with them. This class is a mixin and is intended to
2202
+ be used with another collection that will implement the draw separately.
2203
+
2204
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
2205
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
2206
+ defined by the vertices ::
2207
+
2208
+ (m+1, n) ----------- (m+1, n+1)
2209
+ / /
2210
+ / /
2211
+ / /
2212
+ (m, n) -------- (m, n+1)
2213
+
2214
+ The mesh need not be regular and the polygons need not be convex.
2215
+
2216
+ Parameters
2217
+ ----------
2218
+ coordinates : (M+1, N+1, 2) array-like
2219
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
2220
+ of vertex (m, n).
2221
+
2222
+ shading : {'flat', 'gouraud'}, default: 'flat'
2223
+ """
2224
+ def __init__(self, coordinates, *, shading='flat'):
2225
+ _api.check_shape((None, None, 2), coordinates=coordinates)
2226
+ self._coordinates = coordinates
2227
+ self._shading = shading
2228
+
2229
+ def set_array(self, A):
2230
+ """
2231
+ Set the data values.
2232
+
2233
+ Parameters
2234
+ ----------
2235
+ A : array-like
2236
+ The mesh data. Supported array shapes are:
2237
+
2238
+ - (M, N) or (M*N,): a mesh with scalar data. The values are mapped
2239
+ to colors using normalization and a colormap. See parameters
2240
+ *norm*, *cmap*, *vmin*, *vmax*.
2241
+ - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
2242
+ - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
2243
+ i.e. including transparency.
2244
+
2245
+ If the values are provided as a 2D grid, the shape must match the
2246
+ coordinates grid. If the values are 1D, they are reshaped to 2D.
2247
+ M, N follow from the coordinates grid, where the coordinates grid
2248
+ shape is (M, N) for 'gouraud' *shading* and (M+1, N+1) for 'flat'
2249
+ shading.
2250
+ """
2251
+ height, width = self._coordinates.shape[0:-1]
2252
+ if self._shading == 'flat':
2253
+ h, w = height - 1, width - 1
2254
+ else:
2255
+ h, w = height, width
2256
+ ok_shapes = [(h, w, 3), (h, w, 4), (h, w), (h * w,)]
2257
+ if A is not None:
2258
+ shape = np.shape(A)
2259
+ if shape not in ok_shapes:
2260
+ raise ValueError(
2261
+ f"For X ({width}) and Y ({height}) with {self._shading} "
2262
+ f"shading, A should have shape "
2263
+ f"{' or '.join(map(str, ok_shapes))}, not {A.shape}")
2264
+ return super().set_array(A)
2265
+
2266
+ def get_coordinates(self):
2267
+ """
2268
+ Return the vertices of the mesh as an (M+1, N+1, 2) array.
2269
+
2270
+ M, N are the number of quadrilaterals in the rows / columns of the
2271
+ mesh, corresponding to (M+1, N+1) vertices.
2272
+ The last dimension specifies the components (x, y).
2273
+ """
2274
+ return self._coordinates
2275
+
2276
+ def get_edgecolor(self):
2277
+ # docstring inherited
2278
+ # Note that we want to return an array of shape (N*M, 4)
2279
+ # a flattened RGBA collection
2280
+ return super().get_edgecolor().reshape(-1, 4)
2281
+
2282
+ def get_facecolor(self):
2283
+ # docstring inherited
2284
+ # Note that we want to return an array of shape (N*M, 4)
2285
+ # a flattened RGBA collection
2286
+ return super().get_facecolor().reshape(-1, 4)
2287
+
2288
+ @staticmethod
2289
+ def _convert_mesh_to_paths(coordinates):
2290
+ """
2291
+ Convert a given mesh into a sequence of `.Path` objects.
2292
+
2293
+ This function is primarily of use to implementers of backends that do
2294
+ not directly support quadmeshes.
2295
+ """
2296
+ if isinstance(coordinates, np.ma.MaskedArray):
2297
+ c = coordinates.data
2298
+ else:
2299
+ c = coordinates
2300
+ points = np.concatenate([
2301
+ c[:-1, :-1],
2302
+ c[:-1, 1:],
2303
+ c[1:, 1:],
2304
+ c[1:, :-1],
2305
+ c[:-1, :-1]
2306
+ ], axis=2).reshape((-1, 5, 2))
2307
+ return [mpath.Path(x) for x in points]
2308
+
2309
+ def _convert_mesh_to_triangles(self, coordinates):
2310
+ """
2311
+ Convert a given mesh into a sequence of triangles, each point
2312
+ with its own color. The result can be used to construct a call to
2313
+ `~.RendererBase.draw_gouraud_triangles`.
2314
+ """
2315
+ if isinstance(coordinates, np.ma.MaskedArray):
2316
+ p = coordinates.data
2317
+ else:
2318
+ p = coordinates
2319
+
2320
+ p_a = p[:-1, :-1]
2321
+ p_b = p[:-1, 1:]
2322
+ p_c = p[1:, 1:]
2323
+ p_d = p[1:, :-1]
2324
+ p_center = (p_a + p_b + p_c + p_d) / 4.0
2325
+ triangles = np.concatenate([
2326
+ p_a, p_b, p_center,
2327
+ p_b, p_c, p_center,
2328
+ p_c, p_d, p_center,
2329
+ p_d, p_a, p_center,
2330
+ ], axis=2).reshape((-1, 3, 2))
2331
+
2332
+ c = self.get_facecolor().reshape((*coordinates.shape[:2], 4))
2333
+ z = self.get_array()
2334
+ mask = z.mask if np.ma.is_masked(z) else None
2335
+ if mask is not None:
2336
+ c[mask, 3] = np.nan
2337
+ c_a = c[:-1, :-1]
2338
+ c_b = c[:-1, 1:]
2339
+ c_c = c[1:, 1:]
2340
+ c_d = c[1:, :-1]
2341
+ c_center = (c_a + c_b + c_c + c_d) / 4.0
2342
+ colors = np.concatenate([
2343
+ c_a, c_b, c_center,
2344
+ c_b, c_c, c_center,
2345
+ c_c, c_d, c_center,
2346
+ c_d, c_a, c_center,
2347
+ ], axis=2).reshape((-1, 3, 4))
2348
+ tmask = np.isnan(colors[..., 2, 3])
2349
+ return triangles[~tmask], colors[~tmask]
2350
+
2351
+
2352
+ class QuadMesh(_MeshData, Collection):
2353
+ r"""
2354
+ Class for the efficient drawing of a quadrilateral mesh.
2355
+
2356
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
2357
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
2358
+ defined by the vertices ::
2359
+
2360
+ (m+1, n) ----------- (m+1, n+1)
2361
+ / /
2362
+ / /
2363
+ / /
2364
+ (m, n) -------- (m, n+1)
2365
+
2366
+ The mesh need not be regular and the polygons need not be convex.
2367
+
2368
+ Parameters
2369
+ ----------
2370
+ coordinates : (M+1, N+1, 2) array-like
2371
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
2372
+ of vertex (m, n).
2373
+
2374
+ antialiased : bool, default: True
2375
+
2376
+ shading : {'flat', 'gouraud'}, default: 'flat'
2377
+
2378
+ Notes
2379
+ -----
2380
+ Unlike other `.Collection`\s, the default *pickradius* of `.QuadMesh` is 0,
2381
+ i.e. `~.Artist.contains` checks whether the test point is within any of the
2382
+ mesh quadrilaterals.
2383
+
2384
+ """
2385
+
2386
+ def __init__(self, coordinates, *, antialiased=True, shading='flat',
2387
+ **kwargs):
2388
+ kwargs.setdefault("pickradius", 0)
2389
+ super().__init__(coordinates=coordinates, shading=shading)
2390
+ Collection.__init__(self, **kwargs)
2391
+
2392
+ self._antialiased = antialiased
2393
+ self._bbox = transforms.Bbox.unit()
2394
+ self._bbox.update_from_data_xy(self._coordinates.reshape(-1, 2))
2395
+ self.set_mouseover(False)
2396
+
2397
+ def get_paths(self):
2398
+ if self._paths is None:
2399
+ self.set_paths()
2400
+ return self._paths
2401
+
2402
+ def set_paths(self):
2403
+ self._paths = self._convert_mesh_to_paths(self._coordinates)
2404
+ self.stale = True
2405
+
2406
+ def get_datalim(self, transData):
2407
+ return (self.get_transform() - transData).transform_bbox(self._bbox)
2408
+
2409
+ @artist.allow_rasterization
2410
+ def draw(self, renderer):
2411
+ if not self.get_visible():
2412
+ return
2413
+ renderer.open_group(self.__class__.__name__, self.get_gid())
2414
+ transform = self.get_transform()
2415
+ offset_trf = self.get_offset_transform()
2416
+ offsets = self.get_offsets()
2417
+
2418
+ if self.have_units():
2419
+ xs = self.convert_xunits(offsets[:, 0])
2420
+ ys = self.convert_yunits(offsets[:, 1])
2421
+ offsets = np.column_stack([xs, ys])
2422
+
2423
+ self.update_scalarmappable()
2424
+
2425
+ if not transform.is_affine:
2426
+ coordinates = self._coordinates.reshape((-1, 2))
2427
+ coordinates = transform.transform(coordinates)
2428
+ coordinates = coordinates.reshape(self._coordinates.shape)
2429
+ transform = transforms.IdentityTransform()
2430
+ else:
2431
+ coordinates = self._coordinates
2432
+
2433
+ if not offset_trf.is_affine:
2434
+ offsets = offset_trf.transform_non_affine(offsets)
2435
+ offset_trf = offset_trf.get_affine()
2436
+
2437
+ gc = renderer.new_gc()
2438
+ gc.set_snap(self.get_snap())
2439
+ self._set_gc_clip(gc)
2440
+ gc.set_linewidth(self.get_linewidth()[0])
2441
+
2442
+ if self._shading == 'gouraud':
2443
+ triangles, colors = self._convert_mesh_to_triangles(coordinates)
2444
+ renderer.draw_gouraud_triangles(
2445
+ gc, triangles, colors, transform.frozen())
2446
+ else:
2447
+ renderer.draw_quad_mesh(
2448
+ gc, transform.frozen(),
2449
+ coordinates.shape[1] - 1, coordinates.shape[0] - 1,
2450
+ coordinates, offsets, offset_trf,
2451
+ # Backends expect flattened rgba arrays (n*m, 4) for fc and ec
2452
+ self.get_facecolor().reshape((-1, 4)),
2453
+ self._antialiased, self.get_edgecolors().reshape((-1, 4)))
2454
+ gc.restore()
2455
+ renderer.close_group(self.__class__.__name__)
2456
+ self.stale = False
2457
+
2458
+ def get_cursor_data(self, event):
2459
+ contained, info = self.contains(event)
2460
+ if contained and self.get_array() is not None:
2461
+ return self.get_array().ravel()[info["ind"]]
2462
+ return None
2463
+
2464
+
2465
+ class PolyQuadMesh(_MeshData, PolyCollection):
2466
+ """
2467
+ Class for drawing a quadrilateral mesh as individual Polygons.
2468
+
2469
+ A quadrilateral mesh is a grid of M by N adjacent quadrilaterals that are
2470
+ defined via a (M+1, N+1) grid of vertices. The quadrilateral (m, n) is
2471
+ defined by the vertices ::
2472
+
2473
+ (m+1, n) ----------- (m+1, n+1)
2474
+ / /
2475
+ / /
2476
+ / /
2477
+ (m, n) -------- (m, n+1)
2478
+
2479
+ The mesh need not be regular and the polygons need not be convex.
2480
+
2481
+ Parameters
2482
+ ----------
2483
+ coordinates : (M+1, N+1, 2) array-like
2484
+ The vertices. ``coordinates[m, n]`` specifies the (x, y) coordinates
2485
+ of vertex (m, n).
2486
+
2487
+ Notes
2488
+ -----
2489
+ Unlike `.QuadMesh`, this class will draw each cell as an individual Polygon.
2490
+ This is significantly slower, but allows for more flexibility when wanting
2491
+ to add additional properties to the cells, such as hatching.
2492
+
2493
+ Another difference from `.QuadMesh` is that if any of the vertices or data
2494
+ of a cell are masked, that Polygon will **not** be drawn and it won't be in
2495
+ the list of paths returned.
2496
+ """
2497
+
2498
+ def __init__(self, coordinates, **kwargs):
2499
+ super().__init__(coordinates=coordinates)
2500
+ PolyCollection.__init__(self, verts=[], **kwargs)
2501
+ # Setting the verts updates the paths of the PolyCollection
2502
+ # This is called after the initializers to make sure the kwargs
2503
+ # have all been processed and available for the masking calculations
2504
+ self._set_unmasked_verts()
2505
+
2506
+ def _get_unmasked_polys(self):
2507
+ """Get the unmasked regions using the coordinates and array"""
2508
+ # mask(X) | mask(Y)
2509
+ mask = np.any(np.ma.getmaskarray(self._coordinates), axis=-1)
2510
+
2511
+ # We want the shape of the polygon, which is the corner of each X/Y array
2512
+ mask = (mask[0:-1, 0:-1] | mask[1:, 1:] | mask[0:-1, 1:] | mask[1:, 0:-1])
2513
+ arr = self.get_array()
2514
+ if arr is not None:
2515
+ arr = np.ma.getmaskarray(arr)
2516
+ if arr.ndim == 3:
2517
+ # RGB(A) case
2518
+ mask |= np.any(arr, axis=-1)
2519
+ elif arr.ndim == 2:
2520
+ mask |= arr
2521
+ else:
2522
+ mask |= arr.reshape(self._coordinates[:-1, :-1, :].shape[:2])
2523
+ return ~mask
2524
+
2525
+ def _set_unmasked_verts(self):
2526
+ X = self._coordinates[..., 0]
2527
+ Y = self._coordinates[..., 1]
2528
+
2529
+ unmask = self._get_unmasked_polys()
2530
+ X1 = np.ma.filled(X[:-1, :-1])[unmask]
2531
+ Y1 = np.ma.filled(Y[:-1, :-1])[unmask]
2532
+ X2 = np.ma.filled(X[1:, :-1])[unmask]
2533
+ Y2 = np.ma.filled(Y[1:, :-1])[unmask]
2534
+ X3 = np.ma.filled(X[1:, 1:])[unmask]
2535
+ Y3 = np.ma.filled(Y[1:, 1:])[unmask]
2536
+ X4 = np.ma.filled(X[:-1, 1:])[unmask]
2537
+ Y4 = np.ma.filled(Y[:-1, 1:])[unmask]
2538
+ npoly = len(X1)
2539
+
2540
+ xy = np.ma.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4, X1, Y1], axis=-1)
2541
+ verts = xy.reshape((npoly, 5, 2))
2542
+ self.set_verts(verts)
2543
+
2544
+ def get_edgecolor(self):
2545
+ # docstring inherited
2546
+ # We only want to return the facecolors of the polygons
2547
+ # that were drawn.
2548
+ ec = super().get_edgecolor()
2549
+ unmasked_polys = self._get_unmasked_polys().ravel()
2550
+ if len(ec) != len(unmasked_polys):
2551
+ # Mapping is off
2552
+ return ec
2553
+ return ec[unmasked_polys, :]
2554
+
2555
+ def get_facecolor(self):
2556
+ # docstring inherited
2557
+ # We only want to return the facecolors of the polygons
2558
+ # that were drawn.
2559
+ fc = super().get_facecolor()
2560
+ unmasked_polys = self._get_unmasked_polys().ravel()
2561
+ if len(fc) != len(unmasked_polys):
2562
+ # Mapping is off
2563
+ return fc
2564
+ return fc[unmasked_polys, :]
2565
+
2566
+ def set_array(self, A):
2567
+ # docstring inherited
2568
+ prev_unmask = self._get_unmasked_polys()
2569
+ super().set_array(A)
2570
+ # If the mask has changed at all we need to update
2571
+ # the set of Polys that we are drawing
2572
+ if not np.array_equal(prev_unmask, self._get_unmasked_polys()):
2573
+ self._set_unmasked_verts()
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable, Iterable, Sequence
2
+ from typing import Literal
3
+
4
+ import numpy as np
5
+ from numpy.typing import ArrayLike, NDArray
6
+
7
+ from . import colorizer, transforms
8
+ from .backend_bases import MouseEvent
9
+ from .artist import Artist
10
+ from .colors import Normalize, Colormap
11
+ from .lines import Line2D
12
+ from .path import Path
13
+ from .patches import Patch
14
+ from .ticker import Locator, Formatter
15
+ from .tri import Triangulation
16
+ from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType
17
+
18
+ class Collection(colorizer.ColorizingArtist):
19
+ def __init__(
20
+ self,
21
+ *,
22
+ edgecolors: ColorType | Sequence[ColorType] | None = ...,
23
+ facecolors: ColorType | Sequence[ColorType] | None = ...,
24
+ linewidths: float | Sequence[float] | None = ...,
25
+ linestyles: LineStyleType | Sequence[LineStyleType] = ...,
26
+ capstyle: CapStyleType | None = ...,
27
+ joinstyle: JoinStyleType | None = ...,
28
+ antialiaseds: bool | Sequence[bool] | None = ...,
29
+ offsets: tuple[float, float] | Sequence[tuple[float, float]] | None = ...,
30
+ offset_transform: transforms.Transform | None = ...,
31
+ norm: Normalize | None = ...,
32
+ cmap: Colormap | None = ...,
33
+ colorizer: colorizer.Colorizer | None = ...,
34
+ pickradius: float = ...,
35
+ hatch: str | None = ...,
36
+ urls: Sequence[str] | None = ...,
37
+ zorder: float = ...,
38
+ **kwargs
39
+ ) -> None: ...
40
+ def get_paths(self) -> Sequence[Path]: ...
41
+ def set_paths(self, paths: Sequence[Path]) -> None: ...
42
+ def get_transforms(self) -> Sequence[transforms.Transform]: ...
43
+ def get_offset_transform(self) -> transforms.Transform: ...
44
+ def set_offset_transform(self, offset_transform: transforms.Transform) -> None: ...
45
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
46
+ def set_pickradius(self, pickradius: float) -> None: ...
47
+ def get_pickradius(self) -> float: ...
48
+ def set_urls(self, urls: Sequence[str | None]) -> None: ...
49
+ def get_urls(self) -> Sequence[str | None]: ...
50
+ def set_hatch(self, hatch: str) -> None: ...
51
+ def get_hatch(self) -> str: ...
52
+ def set_hatch_linewidth(self, lw: float) -> None: ...
53
+ def get_hatch_linewidth(self) -> float: ...
54
+ def set_offsets(self, offsets: ArrayLike) -> None: ...
55
+ def get_offsets(self) -> ArrayLike: ...
56
+ def set_linewidth(self, lw: float | Sequence[float]) -> None: ...
57
+ def set_linestyle(self, ls: LineStyleType | Sequence[LineStyleType]) -> None: ...
58
+ def set_capstyle(self, cs: CapStyleType) -> None: ...
59
+ def get_capstyle(self) -> Literal["butt", "projecting", "round"] | None: ...
60
+ def set_joinstyle(self, js: JoinStyleType) -> None: ...
61
+ def get_joinstyle(self) -> Literal["miter", "round", "bevel"] | None: ...
62
+ def set_antialiased(self, aa: bool | Sequence[bool]) -> None: ...
63
+ def get_antialiased(self) -> NDArray[np.bool_]: ...
64
+ def set_color(self, c: ColorType | Sequence[ColorType]) -> None: ...
65
+ def set_facecolor(self, c: ColorType | Sequence[ColorType]) -> None: ...
66
+ def get_facecolor(self) -> ColorType | Sequence[ColorType]: ...
67
+ def get_edgecolor(self) -> ColorType | Sequence[ColorType]: ...
68
+ def set_edgecolor(self, c: ColorType | Sequence[ColorType]) -> None: ...
69
+ def set_alpha(self, alpha: float | Sequence[float] | None) -> None: ...
70
+ def get_linewidth(self) -> float | Sequence[float]: ...
71
+ def get_linestyle(self) -> LineStyleType | Sequence[LineStyleType]: ...
72
+ def update_scalarmappable(self) -> None: ...
73
+ def get_fill(self) -> bool: ...
74
+ def update_from(self, other: Artist) -> None: ...
75
+
76
+ class _CollectionWithSizes(Collection):
77
+ def get_sizes(self) -> np.ndarray: ...
78
+ def set_sizes(self, sizes: ArrayLike | None, dpi: float = ...) -> None: ...
79
+
80
+ class PathCollection(_CollectionWithSizes):
81
+ def __init__(
82
+ self, paths: Sequence[Path], sizes: ArrayLike | None = ..., **kwargs
83
+ ) -> None: ...
84
+ def set_paths(self, paths: Sequence[Path]) -> None: ...
85
+ def get_paths(self) -> Sequence[Path]: ...
86
+ def legend_elements(
87
+ self,
88
+ prop: Literal["colors", "sizes"] = ...,
89
+ num: int | Literal["auto"] | ArrayLike | Locator = ...,
90
+ fmt: str | Formatter | None = ...,
91
+ func: Callable[[ArrayLike], ArrayLike] = ...,
92
+ **kwargs,
93
+ ) -> tuple[list[Line2D], list[str]]: ...
94
+
95
+ class PolyCollection(_CollectionWithSizes):
96
+ def __init__(
97
+ self,
98
+ verts: Sequence[ArrayLike],
99
+ sizes: ArrayLike | None = ...,
100
+ *,
101
+ closed: bool = ...,
102
+ **kwargs
103
+ ) -> None: ...
104
+ def set_verts(
105
+ self, verts: Sequence[ArrayLike | Path], closed: bool = ...
106
+ ) -> None: ...
107
+ def set_paths(self, verts: Sequence[Path], closed: bool = ...) -> None: ...
108
+ def set_verts_and_codes(
109
+ self, verts: Sequence[ArrayLike | Path], codes: Sequence[int]
110
+ ) -> None: ...
111
+
112
+ class FillBetweenPolyCollection(PolyCollection):
113
+ def __init__(
114
+ self,
115
+ t_direction: Literal["x", "y"],
116
+ t: ArrayLike,
117
+ f1: ArrayLike,
118
+ f2: ArrayLike,
119
+ *,
120
+ where: Sequence[bool] | None = ...,
121
+ interpolate: bool = ...,
122
+ step: Literal["pre", "post", "mid"] | None = ...,
123
+ **kwargs,
124
+ ) -> None: ...
125
+ def set_data(
126
+ self,
127
+ t: ArrayLike,
128
+ f1: ArrayLike,
129
+ f2: ArrayLike,
130
+ *,
131
+ where: Sequence[bool] | None = ...,
132
+ ) -> None: ...
133
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
134
+
135
+ class RegularPolyCollection(_CollectionWithSizes):
136
+ def __init__(
137
+ self, numsides: int, *, rotation: float = ..., sizes: ArrayLike = ..., **kwargs
138
+ ) -> None: ...
139
+ def get_numsides(self) -> int: ...
140
+ def get_rotation(self) -> float: ...
141
+
142
+ class StarPolygonCollection(RegularPolyCollection): ...
143
+ class AsteriskPolygonCollection(RegularPolyCollection): ...
144
+
145
+ class LineCollection(Collection):
146
+ def __init__(
147
+ self, segments: Sequence[ArrayLike], *, zorder: float = ..., **kwargs
148
+ ) -> None: ...
149
+ def set_segments(self, segments: Sequence[ArrayLike] | None) -> None: ...
150
+ def set_verts(self, segments: Sequence[ArrayLike] | None) -> None: ...
151
+ def set_paths(self, segments: Sequence[ArrayLike] | None) -> None: ... # type: ignore[override]
152
+ def get_segments(self) -> list[np.ndarray]: ...
153
+ def set_color(self, c: ColorType | Sequence[ColorType]) -> None: ...
154
+ def set_colors(self, c: ColorType | Sequence[ColorType]) -> None: ...
155
+ def set_gapcolor(self, gapcolor: ColorType | Sequence[ColorType] | None) -> None: ...
156
+ def get_color(self) -> ColorType | Sequence[ColorType]: ...
157
+ def get_colors(self) -> ColorType | Sequence[ColorType]: ...
158
+ def get_gapcolor(self) -> ColorType | Sequence[ColorType] | None: ...
159
+
160
+
161
+ class EventCollection(LineCollection):
162
+ def __init__(
163
+ self,
164
+ positions: ArrayLike,
165
+ orientation: Literal["horizontal", "vertical"] = ...,
166
+ *,
167
+ lineoffset: float = ...,
168
+ linelength: float = ...,
169
+ linewidth: float | Sequence[float] | None = ...,
170
+ color: ColorType | Sequence[ColorType] | None = ...,
171
+ linestyle: LineStyleType | Sequence[LineStyleType] = ...,
172
+ antialiased: bool | Sequence[bool] | None = ...,
173
+ **kwargs
174
+ ) -> None: ...
175
+ def get_positions(self) -> list[float]: ...
176
+ def set_positions(self, positions: Sequence[float] | None) -> None: ...
177
+ def add_positions(self, position: Sequence[float] | None) -> None: ...
178
+ def extend_positions(self, position: Sequence[float] | None) -> None: ...
179
+ def append_positions(self, position: Sequence[float] | None) -> None: ...
180
+ def is_horizontal(self) -> bool: ...
181
+ def get_orientation(self) -> Literal["horizontal", "vertical"]: ...
182
+ def switch_orientation(self) -> None: ...
183
+ def set_orientation(
184
+ self, orientation: Literal["horizontal", "vertical"]
185
+ ) -> None: ...
186
+ def get_linelength(self) -> float | Sequence[float]: ...
187
+ def set_linelength(self, linelength: float | Sequence[float]) -> None: ...
188
+ def get_lineoffset(self) -> float: ...
189
+ def set_lineoffset(self, lineoffset: float) -> None: ...
190
+ def get_linewidth(self) -> float: ...
191
+ def get_linewidths(self) -> Sequence[float]: ...
192
+ def get_color(self) -> ColorType: ...
193
+
194
+ class CircleCollection(_CollectionWithSizes):
195
+ def __init__(self, sizes: float | ArrayLike, **kwargs) -> None: ...
196
+
197
+ class EllipseCollection(Collection):
198
+ def __init__(
199
+ self,
200
+ widths: ArrayLike,
201
+ heights: ArrayLike,
202
+ angles: ArrayLike,
203
+ *,
204
+ units: Literal[
205
+ "points", "inches", "dots", "width", "height", "x", "y", "xy"
206
+ ] = ...,
207
+ **kwargs
208
+ ) -> None: ...
209
+ def set_widths(self, widths: ArrayLike) -> None: ...
210
+ def set_heights(self, heights: ArrayLike) -> None: ...
211
+ def set_angles(self, angles: ArrayLike) -> None: ...
212
+ def get_widths(self) -> ArrayLike: ...
213
+ def get_heights(self) -> ArrayLike: ...
214
+ def get_angles(self) -> ArrayLike: ...
215
+
216
+ class PatchCollection(Collection):
217
+ def __init__(
218
+ self, patches: Iterable[Patch], *, match_original: bool = ..., **kwargs
219
+ ) -> None: ...
220
+ def set_paths(self, patches: Iterable[Patch]) -> None: ... # type: ignore[override]
221
+
222
+ class TriMesh(Collection):
223
+ def __init__(self, triangulation: Triangulation, **kwargs) -> None: ...
224
+ def get_paths(self) -> list[Path]: ...
225
+ # Parent class has an argument, perhaps add a noop arg?
226
+ def set_paths(self) -> None: ... # type: ignore[override]
227
+ @staticmethod
228
+ def convert_mesh_to_paths(tri: Triangulation) -> list[Path]: ...
229
+
230
+ class _MeshData:
231
+ def __init__(
232
+ self,
233
+ coordinates: ArrayLike,
234
+ *,
235
+ shading: Literal["flat", "gouraud"] = ...,
236
+ ) -> None: ...
237
+ def set_array(self, A: ArrayLike | None) -> None: ...
238
+ def get_coordinates(self) -> ArrayLike: ...
239
+ def get_facecolor(self) -> ColorType | Sequence[ColorType]: ...
240
+ def get_edgecolor(self) -> ColorType | Sequence[ColorType]: ...
241
+
242
+ class QuadMesh(_MeshData, Collection):
243
+ def __init__(
244
+ self,
245
+ coordinates: ArrayLike,
246
+ *,
247
+ antialiased: bool = ...,
248
+ shading: Literal["flat", "gouraud"] = ...,
249
+ **kwargs
250
+ ) -> None: ...
251
+ def get_paths(self) -> list[Path]: ...
252
+ # Parent class has an argument, perhaps add a noop arg?
253
+ def set_paths(self) -> None: ... # type: ignore[override]
254
+ def get_datalim(self, transData: transforms.Transform) -> transforms.Bbox: ...
255
+ def get_cursor_data(self, event: MouseEvent) -> float: ...
256
+
257
+ class PolyQuadMesh(_MeshData, PolyCollection):
258
+ def __init__(
259
+ self,
260
+ coordinates: ArrayLike,
261
+ **kwargs
262
+ ) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py ADDED
@@ -0,0 +1,1563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Colorbars are a visualization of the mapping from scalar values to colors.
3
+ In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
4
+
5
+ .. note::
6
+ Colorbars are typically created through `.Figure.colorbar` or its pyplot
7
+ wrapper `.pyplot.colorbar`, which internally use `.Colorbar` together with
8
+ `.make_axes_gridspec` (for `.GridSpec`-positioned Axes) or `.make_axes` (for
9
+ non-`.GridSpec`-positioned Axes).
10
+
11
+ End-users most likely won't need to directly use this module's API.
12
+ """
13
+
14
+ import logging
15
+
16
+ import numpy as np
17
+
18
+ import matplotlib as mpl
19
+ from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
20
+ import matplotlib.artist as martist
21
+ import matplotlib.patches as mpatches
22
+ import matplotlib.path as mpath
23
+ import matplotlib.spines as mspines
24
+ import matplotlib.transforms as mtransforms
25
+ from matplotlib import _docstring
26
+
27
+ _log = logging.getLogger(__name__)
28
+
29
+ _docstring.interpd.register(
30
+ _make_axes_kw_doc="""
31
+ location : None or {'left', 'right', 'top', 'bottom'}
32
+ The location, relative to the parent Axes, where the colorbar Axes
33
+ is created. It also determines the *orientation* of the colorbar
34
+ (colorbars on the left and right are vertical, colorbars at the top
35
+ and bottom are horizontal). If None, the location will come from the
36
+ *orientation* if it is set (vertical colorbars on the right, horizontal
37
+ ones at the bottom), or default to 'right' if *orientation* is unset.
38
+
39
+ orientation : None or {'vertical', 'horizontal'}
40
+ The orientation of the colorbar. It is preferable to set the *location*
41
+ of the colorbar, as that also determines the *orientation*; passing
42
+ incompatible values for *location* and *orientation* raises an exception.
43
+
44
+ fraction : float, default: 0.15
45
+ Fraction of original Axes to use for colorbar.
46
+
47
+ shrink : float, default: 1.0
48
+ Fraction by which to multiply the size of the colorbar.
49
+
50
+ aspect : float, default: 20
51
+ Ratio of long to short dimensions.
52
+
53
+ pad : float, default: 0.05 if vertical, 0.15 if horizontal
54
+ Fraction of original Axes between colorbar and new image Axes.
55
+
56
+ anchor : (float, float), optional
57
+ The anchor point of the colorbar Axes.
58
+ Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
59
+
60
+ panchor : (float, float), or *False*, optional
61
+ The anchor point of the colorbar parent Axes. If *False*, the parent
62
+ axes' anchor will be unchanged.
63
+ Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.""",
64
+ _colormap_kw_doc="""
65
+ extend : {'neither', 'both', 'min', 'max'}
66
+ Make pointed end(s) for out-of-range values (unless 'neither'). These are
67
+ set for a given colormap using the colormap set_under and set_over methods.
68
+
69
+ extendfrac : {*None*, 'auto', length, lengths}
70
+ If set to *None*, both the minimum and maximum triangular colorbar
71
+ extensions will have a length of 5% of the interior colorbar length (this
72
+ is the default setting).
73
+
74
+ If set to 'auto', makes the triangular colorbar extensions the same lengths
75
+ as the interior boxes (when *spacing* is set to 'uniform') or the same
76
+ lengths as the respective adjacent interior boxes (when *spacing* is set to
77
+ 'proportional').
78
+
79
+ If a scalar, indicates the length of both the minimum and maximum
80
+ triangular colorbar extensions as a fraction of the interior colorbar
81
+ length. A two-element sequence of fractions may also be given, indicating
82
+ the lengths of the minimum and maximum colorbar extensions respectively as
83
+ a fraction of the interior colorbar length.
84
+
85
+ extendrect : bool
86
+ If *False* the minimum and maximum colorbar extensions will be triangular
87
+ (the default). If *True* the extensions will be rectangular.
88
+
89
+ ticks : None or list of ticks or Locator
90
+ If None, ticks are determined automatically from the input.
91
+
92
+ format : None or str or Formatter
93
+ If None, `~.ticker.ScalarFormatter` is used.
94
+ Format strings, e.g., ``"%4.2e"`` or ``"{x:.2e}"``, are supported.
95
+ An alternative `~.ticker.Formatter` may be given instead.
96
+
97
+ drawedges : bool
98
+ Whether to draw lines at color boundaries.
99
+
100
+ label : str
101
+ The label on the colorbar's long axis.
102
+
103
+ boundaries, values : None or a sequence
104
+ If unset, the colormap will be displayed on a 0-1 scale.
105
+ If sequences, *values* must have a length 1 less than *boundaries*. For
106
+ each region delimited by adjacent entries in *boundaries*, the color mapped
107
+ to the corresponding value in *values* will be used. The size of each
108
+ region is determined by the *spacing* parameter.
109
+ Normally only useful for indexed colors (i.e. ``norm=NoNorm()``) or other
110
+ unusual circumstances.
111
+
112
+ spacing : {'uniform', 'proportional'}
113
+ For discrete colorbars (`.BoundaryNorm` or contours), 'uniform' gives each
114
+ color the same space; 'proportional' makes the space proportional to the
115
+ data interval.""")
116
+
117
+
118
+ def _set_ticks_on_axis_warn(*args, **kwargs):
119
+ # a top level function which gets put in at the axes'
120
+ # set_xticks and set_yticks by Colorbar.__init__.
121
+ _api.warn_external("Use the colorbar set_ticks() method instead.")
122
+
123
+
124
+ class _ColorbarSpine(mspines.Spine):
125
+ def __init__(self, axes):
126
+ self._ax = axes
127
+ super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2))))
128
+ mpatches.Patch.set_transform(self, axes.transAxes)
129
+
130
+ def get_window_extent(self, renderer=None):
131
+ # This Spine has no Axis associated with it, and doesn't need to adjust
132
+ # its location, so we can directly get the window extent from the
133
+ # super-super-class.
134
+ return mpatches.Patch.get_window_extent(self, renderer=renderer)
135
+
136
+ def set_xy(self, xy):
137
+ self._path = mpath.Path(xy, closed=True)
138
+ self._xy = xy
139
+ self.stale = True
140
+
141
+ def draw(self, renderer):
142
+ ret = mpatches.Patch.draw(self, renderer)
143
+ self.stale = False
144
+ return ret
145
+
146
+
147
+ class _ColorbarAxesLocator:
148
+ """
149
+ Shrink the Axes if there are triangular or rectangular extends.
150
+ """
151
+ def __init__(self, cbar):
152
+ self._cbar = cbar
153
+ self._orig_locator = cbar.ax._axes_locator
154
+
155
+ def __call__(self, ax, renderer):
156
+ if self._orig_locator is not None:
157
+ pos = self._orig_locator(ax, renderer)
158
+ else:
159
+ pos = ax.get_position(original=True)
160
+ if self._cbar.extend == 'neither':
161
+ return pos
162
+
163
+ y, extendlen = self._cbar._proportional_y()
164
+ if not self._cbar._extend_lower():
165
+ extendlen[0] = 0
166
+ if not self._cbar._extend_upper():
167
+ extendlen[1] = 0
168
+ len = sum(extendlen) + 1
169
+ shrink = 1 / len
170
+ offset = extendlen[0] / len
171
+ # we need to reset the aspect ratio of the axes to account
172
+ # of the extends...
173
+ if hasattr(ax, '_colorbar_info'):
174
+ aspect = ax._colorbar_info['aspect']
175
+ else:
176
+ aspect = False
177
+ # now shrink and/or offset to take into account the
178
+ # extend tri/rectangles.
179
+ if self._cbar.orientation == 'vertical':
180
+ if aspect:
181
+ self._cbar.ax.set_box_aspect(aspect*shrink)
182
+ pos = pos.shrunk(1, shrink).translated(0, offset * pos.height)
183
+ else:
184
+ if aspect:
185
+ self._cbar.ax.set_box_aspect(1/(aspect * shrink))
186
+ pos = pos.shrunk(shrink, 1).translated(offset * pos.width, 0)
187
+ return pos
188
+
189
+ def get_subplotspec(self):
190
+ # make tight_layout happy..
191
+ return (
192
+ self._cbar.ax.get_subplotspec()
193
+ or getattr(self._orig_locator, "get_subplotspec", lambda: None)())
194
+
195
+
196
+ @_docstring.interpd
197
+ class Colorbar:
198
+ r"""
199
+ Draw a colorbar in an existing Axes.
200
+
201
+ Typically, colorbars are created using `.Figure.colorbar` or
202
+ `.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an
203
+ `.AxesImage` generated via `~.axes.Axes.imshow`).
204
+
205
+ In order to draw a colorbar not associated with other elements in the
206
+ figure, e.g. when showing a colormap by itself, one can create an empty
207
+ `.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable*
208
+ to `Colorbar`.
209
+
210
+ Useful public methods are :meth:`set_label` and :meth:`add_lines`.
211
+
212
+ Attributes
213
+ ----------
214
+ ax : `~matplotlib.axes.Axes`
215
+ The `~.axes.Axes` instance in which the colorbar is drawn.
216
+ lines : list
217
+ A list of `.LineCollection` (empty if no lines were drawn).
218
+ dividers : `.LineCollection`
219
+ A LineCollection (empty if *drawedges* is ``False``).
220
+ """
221
+
222
+ n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
223
+
224
+ def __init__(
225
+ self, ax, mappable=None, *,
226
+ alpha=None,
227
+ location=None,
228
+ extend=None,
229
+ extendfrac=None,
230
+ extendrect=False,
231
+ ticks=None,
232
+ format=None,
233
+ values=None,
234
+ boundaries=None,
235
+ spacing='uniform',
236
+ drawedges=False,
237
+ label='',
238
+ cmap=None, norm=None, # redundant with *mappable*
239
+ orientation=None, ticklocation='auto', # redundant with *location*
240
+ ):
241
+ """
242
+ Parameters
243
+ ----------
244
+ ax : `~matplotlib.axes.Axes`
245
+ The `~.axes.Axes` instance in which the colorbar is drawn.
246
+
247
+ mappable : `.ScalarMappable`
248
+ The mappable whose colormap and norm will be used.
249
+
250
+ To show the colors versus index instead of on a 0-1 scale, set the
251
+ mappable's norm to ``colors.NoNorm()``.
252
+
253
+ alpha : float
254
+ The colorbar transparency between 0 (transparent) and 1 (opaque).
255
+
256
+ location : None or {'left', 'right', 'top', 'bottom'}
257
+ Set the colorbar's *orientation* and *ticklocation*. Colorbars on
258
+ the left and right are vertical, colorbars at the top and bottom
259
+ are horizontal. The *ticklocation* is the same as *location*, so if
260
+ *location* is 'top', the ticks are on the top. *orientation* and/or
261
+ *ticklocation* can be provided as well and overrides the value set by
262
+ *location*, but there will be an error for incompatible combinations.
263
+
264
+ .. versionadded:: 3.7
265
+
266
+ %(_colormap_kw_doc)s
267
+
268
+ Other Parameters
269
+ ----------------
270
+ cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
271
+ The colormap to use. This parameter is ignored, unless *mappable* is
272
+ None.
273
+
274
+ norm : `~matplotlib.colors.Normalize`
275
+ The normalization to use. This parameter is ignored, unless *mappable*
276
+ is None.
277
+
278
+ orientation : None or {'vertical', 'horizontal'}
279
+ If None, use the value determined by *location*. If both
280
+ *orientation* and *location* are None then defaults to 'vertical'.
281
+
282
+ ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
283
+ The location of the colorbar ticks. The *ticklocation* must match
284
+ *orientation*. For example, a horizontal colorbar can only have ticks
285
+ at the top or the bottom. If 'auto', the ticks will be the same as
286
+ *location*, so a colorbar to the left will have ticks to the left. If
287
+ *location* is None, the ticks will be at the bottom for a horizontal
288
+ colorbar and at the right for a vertical.
289
+ """
290
+ if mappable is None:
291
+ mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
292
+
293
+ self.mappable = mappable
294
+ cmap = mappable.cmap
295
+ norm = mappable.norm
296
+
297
+ filled = True
298
+ if isinstance(mappable, contour.ContourSet):
299
+ cs = mappable
300
+ alpha = cs.get_alpha()
301
+ boundaries = cs._levels
302
+ values = cs.cvalues
303
+ extend = cs.extend
304
+ filled = cs.filled
305
+ if ticks is None:
306
+ ticks = ticker.FixedLocator(cs.levels, nbins=10)
307
+ elif isinstance(mappable, martist.Artist):
308
+ alpha = mappable.get_alpha()
309
+
310
+ mappable.colorbar = self
311
+ mappable.colorbar_cid = mappable.callbacks.connect(
312
+ 'changed', self.update_normal)
313
+
314
+ location_orientation = _get_orientation_from_location(location)
315
+
316
+ _api.check_in_list(
317
+ [None, 'vertical', 'horizontal'], orientation=orientation)
318
+ _api.check_in_list(
319
+ ['auto', 'left', 'right', 'top', 'bottom'],
320
+ ticklocation=ticklocation)
321
+ _api.check_in_list(
322
+ ['uniform', 'proportional'], spacing=spacing)
323
+
324
+ if location_orientation is not None and orientation is not None:
325
+ if location_orientation != orientation:
326
+ raise TypeError(
327
+ "location and orientation are mutually exclusive")
328
+ else:
329
+ orientation = orientation or location_orientation or "vertical"
330
+
331
+ self.ax = ax
332
+ self.ax._axes_locator = _ColorbarAxesLocator(self)
333
+
334
+ if extend is None:
335
+ if (not isinstance(mappable, contour.ContourSet)
336
+ and getattr(cmap, 'colorbar_extend', False) is not False):
337
+ extend = cmap.colorbar_extend
338
+ elif hasattr(norm, 'extend'):
339
+ extend = norm.extend
340
+ else:
341
+ extend = 'neither'
342
+ self.alpha = None
343
+ # Call set_alpha to handle array-like alphas properly
344
+ self.set_alpha(alpha)
345
+ self.cmap = cmap
346
+ self.norm = norm
347
+ self.values = values
348
+ self.boundaries = boundaries
349
+ self.extend = extend
350
+ self._inside = _api.check_getitem(
351
+ {'neither': slice(0, None), 'both': slice(1, -1),
352
+ 'min': slice(1, None), 'max': slice(0, -1)},
353
+ extend=extend)
354
+ self.spacing = spacing
355
+ self.orientation = orientation
356
+ self.drawedges = drawedges
357
+ self._filled = filled
358
+ self.extendfrac = extendfrac
359
+ self.extendrect = extendrect
360
+ self._extend_patches = []
361
+ self.solids = None
362
+ self.solids_patches = []
363
+ self.lines = []
364
+
365
+ for spine in self.ax.spines.values():
366
+ spine.set_visible(False)
367
+ self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax)
368
+
369
+ self.dividers = collections.LineCollection(
370
+ [],
371
+ colors=[mpl.rcParams['axes.edgecolor']],
372
+ linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
373
+ clip_on=False)
374
+ self.ax.add_collection(self.dividers)
375
+
376
+ self._locator = None
377
+ self._minorlocator = None
378
+ self._formatter = None
379
+ self._minorformatter = None
380
+
381
+ if ticklocation == 'auto':
382
+ ticklocation = _get_ticklocation_from_orientation(
383
+ orientation) if location is None else location
384
+ self.ticklocation = ticklocation
385
+
386
+ self.set_label(label)
387
+ self._reset_locator_formatter_scale()
388
+
389
+ if np.iterable(ticks):
390
+ self._locator = ticker.FixedLocator(ticks, nbins=len(ticks))
391
+ else:
392
+ self._locator = ticks
393
+
394
+ if isinstance(format, str):
395
+ # Check format between FormatStrFormatter and StrMethodFormatter
396
+ try:
397
+ self._formatter = ticker.FormatStrFormatter(format)
398
+ _ = self._formatter(0)
399
+ except (TypeError, ValueError):
400
+ self._formatter = ticker.StrMethodFormatter(format)
401
+ else:
402
+ self._formatter = format # Assume it is a Formatter or None
403
+ self._draw_all()
404
+
405
+ if isinstance(mappable, contour.ContourSet) and not mappable.filled:
406
+ self.add_lines(mappable)
407
+
408
+ # Link the Axes and Colorbar for interactive use
409
+ self.ax._colorbar = self
410
+ # Don't navigate on any of these types of mappables
411
+ if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) or
412
+ isinstance(self.mappable, contour.ContourSet)):
413
+ self.ax.set_navigate(False)
414
+
415
+ # These are the functions that set up interactivity on this colorbar
416
+ self._interactive_funcs = ["_get_view", "_set_view",
417
+ "_set_view_from_bbox", "drag_pan"]
418
+ for x in self._interactive_funcs:
419
+ setattr(self.ax, x, getattr(self, x))
420
+ # Set the cla function to the cbar's method to override it
421
+ self.ax.cla = self._cbar_cla
422
+ # Callbacks for the extend calculations to handle inverting the axis
423
+ self._extend_cid1 = self.ax.callbacks.connect(
424
+ "xlim_changed", self._do_extends)
425
+ self._extend_cid2 = self.ax.callbacks.connect(
426
+ "ylim_changed", self._do_extends)
427
+
428
+ @property
429
+ def long_axis(self):
430
+ """Axis that has decorations (ticks, etc) on it."""
431
+ if self.orientation == 'vertical':
432
+ return self.ax.yaxis
433
+ return self.ax.xaxis
434
+
435
+ @property
436
+ def locator(self):
437
+ """Major tick `.Locator` for the colorbar."""
438
+ return self.long_axis.get_major_locator()
439
+
440
+ @locator.setter
441
+ def locator(self, loc):
442
+ self.long_axis.set_major_locator(loc)
443
+ self._locator = loc
444
+
445
+ @property
446
+ def minorlocator(self):
447
+ """Minor tick `.Locator` for the colorbar."""
448
+ return self.long_axis.get_minor_locator()
449
+
450
+ @minorlocator.setter
451
+ def minorlocator(self, loc):
452
+ self.long_axis.set_minor_locator(loc)
453
+ self._minorlocator = loc
454
+
455
+ @property
456
+ def formatter(self):
457
+ """Major tick label `.Formatter` for the colorbar."""
458
+ return self.long_axis.get_major_formatter()
459
+
460
+ @formatter.setter
461
+ def formatter(self, fmt):
462
+ self.long_axis.set_major_formatter(fmt)
463
+ self._formatter = fmt
464
+
465
+ @property
466
+ def minorformatter(self):
467
+ """Minor tick `.Formatter` for the colorbar."""
468
+ return self.long_axis.get_minor_formatter()
469
+
470
+ @minorformatter.setter
471
+ def minorformatter(self, fmt):
472
+ self.long_axis.set_minor_formatter(fmt)
473
+ self._minorformatter = fmt
474
+
475
+ def _cbar_cla(self):
476
+ """Function to clear the interactive colorbar state."""
477
+ for x in self._interactive_funcs:
478
+ delattr(self.ax, x)
479
+ # We now restore the old cla() back and can call it directly
480
+ del self.ax.cla
481
+ self.ax.cla()
482
+
483
+ def update_normal(self, mappable=None):
484
+ """
485
+ Update solid patches, lines, etc.
486
+
487
+ This is meant to be called when the norm of the image or contour plot
488
+ to which this colorbar belongs changes.
489
+
490
+ If the norm on the mappable is different than before, this resets the
491
+ locator and formatter for the axis, so if these have been customized,
492
+ they will need to be customized again. However, if the norm only
493
+ changes values of *vmin*, *vmax* or *cmap* then the old formatter
494
+ and locator will be preserved.
495
+ """
496
+ if mappable:
497
+ # The mappable keyword argument exists because
498
+ # ScalarMappable.changed() emits self.callbacks.process('changed', self)
499
+ # in contrast, ColorizingArtist (and Colorizer) does not use this keyword.
500
+ # [ColorizingArtist.changed() emits self.callbacks.process('changed')]
501
+ # Also, there is no test where self.mappable == mappable is not True
502
+ # and possibly no use case.
503
+ # Therefore, the mappable keyword can be deprecated if cm.ScalarMappable
504
+ # is removed.
505
+ self.mappable = mappable
506
+ _log.debug('colorbar update normal %r %r', self.mappable.norm, self.norm)
507
+ self.set_alpha(self.mappable.get_alpha())
508
+ self.cmap = self.mappable.cmap
509
+ if self.mappable.norm != self.norm:
510
+ self.norm = self.mappable.norm
511
+ self._reset_locator_formatter_scale()
512
+
513
+ self._draw_all()
514
+ if isinstance(self.mappable, contour.ContourSet):
515
+ CS = self.mappable
516
+ if not CS.filled:
517
+ self.add_lines(CS)
518
+ self.stale = True
519
+
520
+ def _draw_all(self):
521
+ """
522
+ Calculate any free parameters based on the current cmap and norm,
523
+ and do all the drawing.
524
+ """
525
+ if self.orientation == 'vertical':
526
+ if mpl.rcParams['ytick.minor.visible']:
527
+ self.minorticks_on()
528
+ else:
529
+ if mpl.rcParams['xtick.minor.visible']:
530
+ self.minorticks_on()
531
+ self.long_axis.set(label_position=self.ticklocation,
532
+ ticks_position=self.ticklocation)
533
+ self._short_axis().set_ticks([])
534
+ self._short_axis().set_ticks([], minor=True)
535
+
536
+ # Set self._boundaries and self._values, including extensions.
537
+ # self._boundaries are the edges of each square of color, and
538
+ # self._values are the value to map into the norm to get the
539
+ # color:
540
+ self._process_values()
541
+ # Set self.vmin and self.vmax to first and last boundary, excluding
542
+ # extensions:
543
+ self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
544
+ # Compute the X/Y mesh.
545
+ X, Y = self._mesh()
546
+ # draw the extend triangles, and shrink the inner Axes to accommodate.
547
+ # also adds the outline path to self.outline spine:
548
+ self._do_extends()
549
+ lower, upper = self.vmin, self.vmax
550
+ if self.long_axis.get_inverted():
551
+ # If the axis is inverted, we need to swap the vmin/vmax
552
+ lower, upper = upper, lower
553
+ if self.orientation == 'vertical':
554
+ self.ax.set_xlim(0, 1)
555
+ self.ax.set_ylim(lower, upper)
556
+ else:
557
+ self.ax.set_ylim(0, 1)
558
+ self.ax.set_xlim(lower, upper)
559
+
560
+ # set up the tick locators and formatters. A bit complicated because
561
+ # boundary norms + uniform spacing requires a manual locator.
562
+ self.update_ticks()
563
+
564
+ if self._filled:
565
+ ind = np.arange(len(self._values))
566
+ if self._extend_lower():
567
+ ind = ind[1:]
568
+ if self._extend_upper():
569
+ ind = ind[:-1]
570
+ self._add_solids(X, Y, self._values[ind, np.newaxis])
571
+
572
+ def _add_solids(self, X, Y, C):
573
+ """Draw the colors; optionally add separators."""
574
+ # Cleanup previously set artists.
575
+ if self.solids is not None:
576
+ self.solids.remove()
577
+ for solid in self.solids_patches:
578
+ solid.remove()
579
+ # Add new artist(s), based on mappable type. Use individual patches if
580
+ # hatching is needed, pcolormesh otherwise.
581
+ mappable = getattr(self, 'mappable', None)
582
+ if (isinstance(mappable, contour.ContourSet)
583
+ and any(hatch is not None for hatch in mappable.hatches)):
584
+ self._add_solids_patches(X, Y, C, mappable)
585
+ else:
586
+ self.solids = self.ax.pcolormesh(
587
+ X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha,
588
+ edgecolors='none', shading='flat')
589
+ if not self.drawedges:
590
+ if len(self._y) >= self.n_rasterize:
591
+ self.solids.set_rasterized(True)
592
+ self._update_dividers()
593
+
594
+ def _update_dividers(self):
595
+ if not self.drawedges:
596
+ self.dividers.set_segments([])
597
+ return
598
+ # Place all *internal* dividers.
599
+ if self.orientation == 'vertical':
600
+ lims = self.ax.get_ylim()
601
+ bounds = (lims[0] < self._y) & (self._y < lims[1])
602
+ else:
603
+ lims = self.ax.get_xlim()
604
+ bounds = (lims[0] < self._y) & (self._y < lims[1])
605
+ y = self._y[bounds]
606
+ # And then add outer dividers if extensions are on.
607
+ if self._extend_lower():
608
+ y = np.insert(y, 0, lims[0])
609
+ if self._extend_upper():
610
+ y = np.append(y, lims[1])
611
+ X, Y = np.meshgrid([0, 1], y)
612
+ if self.orientation == 'vertical':
613
+ segments = np.dstack([X, Y])
614
+ else:
615
+ segments = np.dstack([Y, X])
616
+ self.dividers.set_segments(segments)
617
+
618
+ def _add_solids_patches(self, X, Y, C, mappable):
619
+ hatches = mappable.hatches * (len(C) + 1) # Have enough hatches.
620
+ if self._extend_lower():
621
+ # remove first hatch that goes into the extend patch
622
+ hatches = hatches[1:]
623
+ patches = []
624
+ for i in range(len(X) - 1):
625
+ xy = np.array([[X[i, 0], Y[i, 1]],
626
+ [X[i, 1], Y[i, 0]],
627
+ [X[i + 1, 1], Y[i + 1, 0]],
628
+ [X[i + 1, 0], Y[i + 1, 1]]])
629
+ patch = mpatches.PathPatch(mpath.Path(xy),
630
+ facecolor=self.cmap(self.norm(C[i][0])),
631
+ hatch=hatches[i], linewidth=0,
632
+ antialiased=False, alpha=self.alpha)
633
+ self.ax.add_patch(patch)
634
+ patches.append(patch)
635
+ self.solids_patches = patches
636
+
637
+ def _do_extends(self, ax=None):
638
+ """
639
+ Add the extend tri/rectangles on the outside of the Axes.
640
+
641
+ ax is unused, but required due to the callbacks on xlim/ylim changed
642
+ """
643
+ # Clean up any previous extend patches
644
+ for patch in self._extend_patches:
645
+ patch.remove()
646
+ self._extend_patches = []
647
+ # extend lengths are fraction of the *inner* part of colorbar,
648
+ # not the total colorbar:
649
+ _, extendlen = self._proportional_y()
650
+ bot = 0 - (extendlen[0] if self._extend_lower() else 0)
651
+ top = 1 + (extendlen[1] if self._extend_upper() else 0)
652
+
653
+ # xyout is the outline of the colorbar including the extend patches:
654
+ if not self.extendrect:
655
+ # triangle:
656
+ xyout = np.array([[0, 0], [0.5, bot], [1, 0],
657
+ [1, 1], [0.5, top], [0, 1], [0, 0]])
658
+ else:
659
+ # rectangle:
660
+ xyout = np.array([[0, 0], [0, bot], [1, bot], [1, 0],
661
+ [1, 1], [1, top], [0, top], [0, 1],
662
+ [0, 0]])
663
+
664
+ if self.orientation == 'horizontal':
665
+ xyout = xyout[:, ::-1]
666
+
667
+ # xyout is the path for the spine:
668
+ self.outline.set_xy(xyout)
669
+ if not self._filled:
670
+ return
671
+
672
+ # Make extend triangles or rectangles filled patches. These are
673
+ # defined in the outer parent axes' coordinates:
674
+ mappable = getattr(self, 'mappable', None)
675
+ if (isinstance(mappable, contour.ContourSet)
676
+ and any(hatch is not None for hatch in mappable.hatches)):
677
+ hatches = mappable.hatches * (len(self._y) + 1)
678
+ else:
679
+ hatches = [None] * (len(self._y) + 1)
680
+
681
+ if self._extend_lower():
682
+ if not self.extendrect:
683
+ # triangle
684
+ xy = np.array([[0, 0], [0.5, bot], [1, 0]])
685
+ else:
686
+ # rectangle
687
+ xy = np.array([[0, 0], [0, bot], [1., bot], [1, 0]])
688
+ if self.orientation == 'horizontal':
689
+ xy = xy[:, ::-1]
690
+ # add the patch
691
+ val = -1 if self.long_axis.get_inverted() else 0
692
+ color = self.cmap(self.norm(self._values[val]))
693
+ patch = mpatches.PathPatch(
694
+ mpath.Path(xy), facecolor=color, alpha=self.alpha,
695
+ linewidth=0, antialiased=False,
696
+ transform=self.ax.transAxes,
697
+ hatch=hatches[0], clip_on=False,
698
+ # Place it right behind the standard patches, which is
699
+ # needed if we updated the extends
700
+ zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
701
+ self.ax.add_patch(patch)
702
+ self._extend_patches.append(patch)
703
+ # remove first hatch that goes into the extend patch
704
+ hatches = hatches[1:]
705
+ if self._extend_upper():
706
+ if not self.extendrect:
707
+ # triangle
708
+ xy = np.array([[0, 1], [0.5, top], [1, 1]])
709
+ else:
710
+ # rectangle
711
+ xy = np.array([[0, 1], [0, top], [1, top], [1, 1]])
712
+ if self.orientation == 'horizontal':
713
+ xy = xy[:, ::-1]
714
+ # add the patch
715
+ val = 0 if self.long_axis.get_inverted() else -1
716
+ color = self.cmap(self.norm(self._values[val]))
717
+ hatch_idx = len(self._y) - 1
718
+ patch = mpatches.PathPatch(
719
+ mpath.Path(xy), facecolor=color, alpha=self.alpha,
720
+ linewidth=0, antialiased=False,
721
+ transform=self.ax.transAxes, hatch=hatches[hatch_idx],
722
+ clip_on=False,
723
+ # Place it right behind the standard patches, which is
724
+ # needed if we updated the extends
725
+ zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
726
+ self.ax.add_patch(patch)
727
+ self._extend_patches.append(patch)
728
+
729
+ self._update_dividers()
730
+
731
+ def add_lines(self, *args, **kwargs):
732
+ """
733
+ Draw lines on the colorbar.
734
+
735
+ The lines are appended to the list :attr:`lines`.
736
+
737
+ Parameters
738
+ ----------
739
+ levels : array-like
740
+ The positions of the lines.
741
+ colors : :mpltype:`color` or list of :mpltype:`color`
742
+ Either a single color applying to all lines or one color value for
743
+ each line.
744
+ linewidths : float or array-like
745
+ Either a single linewidth applying to all lines or one linewidth
746
+ for each line.
747
+ erase : bool, default: True
748
+ Whether to remove any previously added lines.
749
+
750
+ Notes
751
+ -----
752
+ Alternatively, this method can also be called with the signature
753
+ ``colorbar.add_lines(contour_set, erase=True)``, in which case
754
+ *levels*, *colors*, and *linewidths* are taken from *contour_set*.
755
+ """
756
+ params = _api.select_matching_signature(
757
+ [lambda self, CS, erase=True: locals(),
758
+ lambda self, levels, colors, linewidths, erase=True: locals()],
759
+ self, *args, **kwargs)
760
+ if "CS" in params:
761
+ self, cs, erase = params.values()
762
+ if not isinstance(cs, contour.ContourSet) or cs.filled:
763
+ raise ValueError("If a single artist is passed to add_lines, "
764
+ "it must be a ContourSet of lines")
765
+ # TODO: Make colorbar lines auto-follow changes in contour lines.
766
+ return self.add_lines(
767
+ cs.levels,
768
+ cs.to_rgba(cs.cvalues, cs.alpha),
769
+ cs.get_linewidths(),
770
+ erase=erase)
771
+ else:
772
+ self, levels, colors, linewidths, erase = params.values()
773
+
774
+ y = self._locate(levels)
775
+ rtol = (self._y[-1] - self._y[0]) * 1e-10
776
+ igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
777
+ y = y[igood]
778
+ if np.iterable(colors):
779
+ colors = np.asarray(colors)[igood]
780
+ if np.iterable(linewidths):
781
+ linewidths = np.asarray(linewidths)[igood]
782
+ X, Y = np.meshgrid([0, 1], y)
783
+ if self.orientation == 'vertical':
784
+ xy = np.stack([X, Y], axis=-1)
785
+ else:
786
+ xy = np.stack([Y, X], axis=-1)
787
+ col = collections.LineCollection(xy, linewidths=linewidths,
788
+ colors=colors)
789
+
790
+ if erase and self.lines:
791
+ for lc in self.lines:
792
+ lc.remove()
793
+ self.lines = []
794
+ self.lines.append(col)
795
+
796
+ # make a clip path that is just a linewidth bigger than the Axes...
797
+ fac = np.max(linewidths) / 72
798
+ xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
799
+ inches = self.ax.get_figure().dpi_scale_trans
800
+ # do in inches:
801
+ xy = inches.inverted().transform(self.ax.transAxes.transform(xy))
802
+ xy[[0, 1, 4], 1] -= fac
803
+ xy[[2, 3], 1] += fac
804
+ # back to axes units...
805
+ xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
806
+ col.set_clip_path(mpath.Path(xy, closed=True),
807
+ self.ax.transAxes)
808
+ self.ax.add_collection(col)
809
+ self.stale = True
810
+
811
+ def update_ticks(self):
812
+ """
813
+ Set up the ticks and ticklabels. This should not be needed by users.
814
+ """
815
+ # Get the locator and formatter; defaults to self._locator if not None.
816
+ self._get_ticker_locator_formatter()
817
+ self.long_axis.set_major_locator(self._locator)
818
+ self.long_axis.set_minor_locator(self._minorlocator)
819
+ self.long_axis.set_major_formatter(self._formatter)
820
+
821
+ def _get_ticker_locator_formatter(self):
822
+ """
823
+ Return the ``locator`` and ``formatter`` of the colorbar.
824
+
825
+ If they have not been defined (i.e. are *None*), the formatter and
826
+ locator are retrieved from the axis, or from the value of the
827
+ boundaries for a boundary norm.
828
+
829
+ Called by update_ticks...
830
+ """
831
+ locator = self._locator
832
+ formatter = self._formatter
833
+ minorlocator = self._minorlocator
834
+ if isinstance(self.norm, colors.BoundaryNorm):
835
+ b = self.norm.boundaries
836
+ if locator is None:
837
+ locator = ticker.FixedLocator(b, nbins=10)
838
+ if minorlocator is None:
839
+ minorlocator = ticker.FixedLocator(b)
840
+ elif isinstance(self.norm, colors.NoNorm):
841
+ if locator is None:
842
+ # put ticks on integers between the boundaries of NoNorm
843
+ nv = len(self._values)
844
+ base = 1 + int(nv / 10)
845
+ locator = ticker.IndexLocator(base=base, offset=.5)
846
+ elif self.boundaries is not None:
847
+ b = self._boundaries[self._inside]
848
+ if locator is None:
849
+ locator = ticker.FixedLocator(b, nbins=10)
850
+ else: # most cases:
851
+ if locator is None:
852
+ # we haven't set the locator explicitly, so use the default
853
+ # for this axis:
854
+ locator = self.long_axis.get_major_locator()
855
+ if minorlocator is None:
856
+ minorlocator = self.long_axis.get_minor_locator()
857
+
858
+ if minorlocator is None:
859
+ minorlocator = ticker.NullLocator()
860
+
861
+ if formatter is None:
862
+ formatter = self.long_axis.get_major_formatter()
863
+
864
+ self._locator = locator
865
+ self._formatter = formatter
866
+ self._minorlocator = minorlocator
867
+ _log.debug('locator: %r', locator)
868
+
869
+ def set_ticks(self, ticks, *, labels=None, minor=False, **kwargs):
870
+ """
871
+ Set tick locations.
872
+
873
+ Parameters
874
+ ----------
875
+ ticks : 1D array-like
876
+ List of tick locations.
877
+ labels : list of str, optional
878
+ List of tick labels. If not set, the labels show the data value.
879
+ minor : bool, default: False
880
+ If ``False``, set the major ticks; if ``True``, the minor ticks.
881
+ **kwargs
882
+ `.Text` properties for the labels. These take effect only if you
883
+ pass *labels*. In other cases, please use `~.Axes.tick_params`.
884
+ """
885
+ if np.iterable(ticks):
886
+ self.long_axis.set_ticks(ticks, labels=labels, minor=minor,
887
+ **kwargs)
888
+ self._locator = self.long_axis.get_major_locator()
889
+ else:
890
+ self._locator = ticks
891
+ self.long_axis.set_major_locator(self._locator)
892
+ self.stale = True
893
+
894
+ def get_ticks(self, minor=False):
895
+ """
896
+ Return the ticks as a list of locations.
897
+
898
+ Parameters
899
+ ----------
900
+ minor : boolean, default: False
901
+ if True return the minor ticks.
902
+ """
903
+ if minor:
904
+ return self.long_axis.get_minorticklocs()
905
+ else:
906
+ return self.long_axis.get_majorticklocs()
907
+
908
+ def set_ticklabels(self, ticklabels, *, minor=False, **kwargs):
909
+ """
910
+ [*Discouraged*] Set tick labels.
911
+
912
+ .. admonition:: Discouraged
913
+
914
+ The use of this method is discouraged, because of the dependency
915
+ on tick positions. In most cases, you'll want to use
916
+ ``set_ticks(positions, labels=labels)`` instead.
917
+
918
+ If you are using this method, you should always fix the tick
919
+ positions before, e.g. by using `.Colorbar.set_ticks` or by
920
+ explicitly setting a `~.ticker.FixedLocator` on the long axis
921
+ of the colorbar. Otherwise, ticks are free to move and the
922
+ labels may end up in unexpected positions.
923
+
924
+ Parameters
925
+ ----------
926
+ ticklabels : sequence of str or of `.Text`
927
+ Texts for labeling each tick location in the sequence set by
928
+ `.Colorbar.set_ticks`; the number of labels must match the number
929
+ of locations.
930
+
931
+ update_ticks : bool, default: True
932
+ This keyword argument is ignored and will be removed.
933
+ Deprecated
934
+
935
+ minor : bool
936
+ If True, set minor ticks instead of major ticks.
937
+
938
+ **kwargs
939
+ `.Text` properties for the labels.
940
+ """
941
+ self.long_axis.set_ticklabels(ticklabels, minor=minor, **kwargs)
942
+
943
+ def minorticks_on(self):
944
+ """
945
+ Turn on colorbar minor ticks.
946
+ """
947
+ self.ax.minorticks_on()
948
+ self._short_axis().set_minor_locator(ticker.NullLocator())
949
+
950
+ def minorticks_off(self):
951
+ """Turn the minor ticks of the colorbar off."""
952
+ self._minorlocator = ticker.NullLocator()
953
+ self.long_axis.set_minor_locator(self._minorlocator)
954
+
955
+ def set_label(self, label, *, loc=None, **kwargs):
956
+ """
957
+ Add a label to the long axis of the colorbar.
958
+
959
+ Parameters
960
+ ----------
961
+ label : str
962
+ The label text.
963
+ loc : str, optional
964
+ The location of the label.
965
+
966
+ - For horizontal orientation one of {'left', 'center', 'right'}
967
+ - For vertical orientation one of {'bottom', 'center', 'top'}
968
+
969
+ Defaults to :rc:`xaxis.labellocation` or :rc:`yaxis.labellocation`
970
+ depending on the orientation.
971
+ **kwargs
972
+ Keyword arguments are passed to `~.Axes.set_xlabel` /
973
+ `~.Axes.set_ylabel`.
974
+ Supported keywords are *labelpad* and `.Text` properties.
975
+ """
976
+ if self.orientation == "vertical":
977
+ self.ax.set_ylabel(label, loc=loc, **kwargs)
978
+ else:
979
+ self.ax.set_xlabel(label, loc=loc, **kwargs)
980
+ self.stale = True
981
+
982
+ def set_alpha(self, alpha):
983
+ """
984
+ Set the transparency between 0 (transparent) and 1 (opaque).
985
+
986
+ If an array is provided, *alpha* will be set to None to use the
987
+ transparency values associated with the colormap.
988
+ """
989
+ self.alpha = None if isinstance(alpha, np.ndarray) else alpha
990
+
991
+ def _set_scale(self, scale, **kwargs):
992
+ """
993
+ Set the colorbar long axis scale.
994
+
995
+ Parameters
996
+ ----------
997
+ scale : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
998
+ The axis scale type to apply.
999
+
1000
+ **kwargs
1001
+ Different keyword arguments are accepted, depending on the scale.
1002
+ See the respective class keyword arguments:
1003
+
1004
+ - `matplotlib.scale.LinearScale`
1005
+ - `matplotlib.scale.LogScale`
1006
+ - `matplotlib.scale.SymmetricalLogScale`
1007
+ - `matplotlib.scale.LogitScale`
1008
+ - `matplotlib.scale.FuncScale`
1009
+ - `matplotlib.scale.AsinhScale`
1010
+
1011
+ Notes
1012
+ -----
1013
+ By default, Matplotlib supports the above-mentioned scales.
1014
+ Additionally, custom scales may be registered using
1015
+ `matplotlib.scale.register_scale`. These scales can then also
1016
+ be used here.
1017
+ """
1018
+ self.long_axis._set_axes_scale(scale, **kwargs)
1019
+
1020
+ def remove(self):
1021
+ """
1022
+ Remove this colorbar from the figure.
1023
+
1024
+ If the colorbar was created with ``use_gridspec=True`` the previous
1025
+ gridspec is restored.
1026
+ """
1027
+ if hasattr(self.ax, '_colorbar_info'):
1028
+ parents = self.ax._colorbar_info['parents']
1029
+ for a in parents:
1030
+ if self.ax in a._colorbars:
1031
+ a._colorbars.remove(self.ax)
1032
+
1033
+ self.ax.remove()
1034
+
1035
+ self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
1036
+ self.mappable.colorbar = None
1037
+ self.mappable.colorbar_cid = None
1038
+ # Remove the extension callbacks
1039
+ self.ax.callbacks.disconnect(self._extend_cid1)
1040
+ self.ax.callbacks.disconnect(self._extend_cid2)
1041
+
1042
+ try:
1043
+ ax = self.mappable.axes
1044
+ except AttributeError:
1045
+ return
1046
+ try:
1047
+ subplotspec = self.ax.get_subplotspec().get_gridspec()._subplot_spec
1048
+ except AttributeError: # use_gridspec was False
1049
+ pos = ax.get_position(original=True)
1050
+ ax._set_position(pos)
1051
+ else: # use_gridspec was True
1052
+ ax.set_subplotspec(subplotspec)
1053
+
1054
+ def _process_values(self):
1055
+ """
1056
+ Set `_boundaries` and `_values` based on the self.boundaries and
1057
+ self.values if not None, or based on the size of the colormap and
1058
+ the vmin/vmax of the norm.
1059
+ """
1060
+ if self.values is not None:
1061
+ # set self._boundaries from the values...
1062
+ self._values = np.array(self.values)
1063
+ if self.boundaries is None:
1064
+ # bracket values by 1/2 dv:
1065
+ b = np.zeros(len(self.values) + 1)
1066
+ b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
1067
+ b[0] = 2.0 * b[1] - b[2]
1068
+ b[-1] = 2.0 * b[-2] - b[-3]
1069
+ self._boundaries = b
1070
+ return
1071
+ self._boundaries = np.array(self.boundaries)
1072
+ return
1073
+
1074
+ # otherwise values are set from the boundaries
1075
+ if isinstance(self.norm, colors.BoundaryNorm):
1076
+ b = self.norm.boundaries
1077
+ elif isinstance(self.norm, colors.NoNorm):
1078
+ # NoNorm has N blocks, so N+1 boundaries, centered on integers:
1079
+ b = np.arange(self.cmap.N + 1) - .5
1080
+ elif self.boundaries is not None:
1081
+ b = self.boundaries
1082
+ else:
1083
+ # otherwise make the boundaries from the size of the cmap:
1084
+ N = self.cmap.N + 1
1085
+ b, _ = self._uniform_y(N)
1086
+ # add extra boundaries if needed:
1087
+ if self._extend_lower():
1088
+ b = np.hstack((b[0] - 1, b))
1089
+ if self._extend_upper():
1090
+ b = np.hstack((b, b[-1] + 1))
1091
+
1092
+ # transform from 0-1 to vmin-vmax:
1093
+ if self.mappable.get_array() is not None:
1094
+ self.mappable.autoscale_None()
1095
+ if not self.norm.scaled():
1096
+ # If we still aren't scaled after autoscaling, use 0, 1 as default
1097
+ self.norm.vmin = 0
1098
+ self.norm.vmax = 1
1099
+ self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
1100
+ self.norm.vmin, self.norm.vmax, expander=0.1)
1101
+ if (not isinstance(self.norm, colors.BoundaryNorm) and
1102
+ (self.boundaries is None)):
1103
+ b = self.norm.inverse(b)
1104
+
1105
+ self._boundaries = np.asarray(b, dtype=float)
1106
+ self._values = 0.5 * (self._boundaries[:-1] + self._boundaries[1:])
1107
+ if isinstance(self.norm, colors.NoNorm):
1108
+ self._values = (self._values + 0.00001).astype(np.int16)
1109
+
1110
+ def _mesh(self):
1111
+ """
1112
+ Return the coordinate arrays for the colorbar pcolormesh/patches.
1113
+
1114
+ These are scaled between vmin and vmax, and already handle colorbar
1115
+ orientation.
1116
+ """
1117
+ y, _ = self._proportional_y()
1118
+ # Use the vmin and vmax of the colorbar, which may not be the same
1119
+ # as the norm. There are situations where the colormap has a
1120
+ # narrower range than the colorbar and we want to accommodate the
1121
+ # extra contours.
1122
+ if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm))
1123
+ or self.boundaries is not None):
1124
+ # not using a norm.
1125
+ y = y * (self.vmax - self.vmin) + self.vmin
1126
+ else:
1127
+ # Update the norm values in a context manager as it is only
1128
+ # a temporary change and we don't want to propagate any signals
1129
+ # attached to the norm (callbacks.blocked).
1130
+ with (self.norm.callbacks.blocked(),
1131
+ cbook._setattr_cm(self.norm, vmin=self.vmin, vmax=self.vmax)):
1132
+ y = self.norm.inverse(y)
1133
+ self._y = y
1134
+ X, Y = np.meshgrid([0., 1.], y)
1135
+ if self.orientation == 'vertical':
1136
+ return (X, Y)
1137
+ else:
1138
+ return (Y, X)
1139
+
1140
+ def _forward_boundaries(self, x):
1141
+ # map boundaries equally between 0 and 1...
1142
+ b = self._boundaries
1143
+ y = np.interp(x, b, np.linspace(0, 1, len(b)))
1144
+ # the following avoids ticks in the extends:
1145
+ eps = (b[-1] - b[0]) * 1e-6
1146
+ # map these _well_ out of bounds to keep any ticks out
1147
+ # of the extends region...
1148
+ y[x < b[0]-eps] = -1
1149
+ y[x > b[-1]+eps] = 2
1150
+ return y
1151
+
1152
+ def _inverse_boundaries(self, x):
1153
+ # invert the above...
1154
+ b = self._boundaries
1155
+ return np.interp(x, np.linspace(0, 1, len(b)), b)
1156
+
1157
+ def _reset_locator_formatter_scale(self):
1158
+ """
1159
+ Reset the locator et al to defaults. Any user-hardcoded changes
1160
+ need to be re-entered if this gets called (either at init, or when
1161
+ the mappable normal gets changed: Colorbar.update_normal)
1162
+ """
1163
+ self._process_values()
1164
+ self._locator = None
1165
+ self._minorlocator = None
1166
+ self._formatter = None
1167
+ self._minorformatter = None
1168
+ if (isinstance(self.mappable, contour.ContourSet) and
1169
+ isinstance(self.norm, colors.LogNorm)):
1170
+ # if contours have lognorm, give them a log scale...
1171
+ self._set_scale('log')
1172
+ elif (self.boundaries is not None or
1173
+ isinstance(self.norm, colors.BoundaryNorm)):
1174
+ if self.spacing == 'uniform':
1175
+ funcs = (self._forward_boundaries, self._inverse_boundaries)
1176
+ self._set_scale('function', functions=funcs)
1177
+ elif self.spacing == 'proportional':
1178
+ self._set_scale('linear')
1179
+ elif getattr(self.norm, '_scale', None):
1180
+ # use the norm's scale (if it exists and is not None):
1181
+ self._set_scale(self.norm._scale)
1182
+ elif type(self.norm) is colors.Normalize:
1183
+ # plain Normalize:
1184
+ self._set_scale('linear')
1185
+ else:
1186
+ # norm._scale is None or not an attr: derive the scale from
1187
+ # the Norm:
1188
+ funcs = (self.norm, self.norm.inverse)
1189
+ self._set_scale('function', functions=funcs)
1190
+
1191
+ def _locate(self, x):
1192
+ """
1193
+ Given a set of color data values, return their
1194
+ corresponding colorbar data coordinates.
1195
+ """
1196
+ if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
1197
+ b = self._boundaries
1198
+ xn = x
1199
+ else:
1200
+ # Do calculations using normalized coordinates so
1201
+ # as to make the interpolation more accurate.
1202
+ b = self.norm(self._boundaries, clip=False).filled()
1203
+ xn = self.norm(x, clip=False).filled()
1204
+
1205
+ bunique = b[self._inside]
1206
+ yunique = self._y
1207
+
1208
+ z = np.interp(xn, bunique, yunique)
1209
+ return z
1210
+
1211
+ # trivial helpers
1212
+
1213
+ def _uniform_y(self, N):
1214
+ """
1215
+ Return colorbar data coordinates for *N* uniformly
1216
+ spaced boundaries, plus extension lengths if required.
1217
+ """
1218
+ automin = automax = 1. / (N - 1.)
1219
+ extendlength = self._get_extension_lengths(self.extendfrac,
1220
+ automin, automax,
1221
+ default=0.05)
1222
+ y = np.linspace(0, 1, N)
1223
+ return y, extendlength
1224
+
1225
+ def _proportional_y(self):
1226
+ """
1227
+ Return colorbar data coordinates for the boundaries of
1228
+ a proportional colorbar, plus extension lengths if required:
1229
+ """
1230
+ if (isinstance(self.norm, colors.BoundaryNorm) or
1231
+ self.boundaries is not None):
1232
+ y = (self._boundaries - self._boundaries[self._inside][0])
1233
+ y = y / (self._boundaries[self._inside][-1] -
1234
+ self._boundaries[self._inside][0])
1235
+ # need yscaled the same as the axes scale to get
1236
+ # the extend lengths.
1237
+ if self.spacing == 'uniform':
1238
+ yscaled = self._forward_boundaries(self._boundaries)
1239
+ else:
1240
+ yscaled = y
1241
+ else:
1242
+ y = self.norm(self._boundaries.copy())
1243
+ y = np.ma.filled(y, np.nan)
1244
+ # the norm and the scale should be the same...
1245
+ yscaled = y
1246
+ y = y[self._inside]
1247
+ yscaled = yscaled[self._inside]
1248
+ # normalize from 0..1:
1249
+ norm = colors.Normalize(y[0], y[-1])
1250
+ y = np.ma.filled(norm(y), np.nan)
1251
+ norm = colors.Normalize(yscaled[0], yscaled[-1])
1252
+ yscaled = np.ma.filled(norm(yscaled), np.nan)
1253
+ # make the lower and upper extend lengths proportional to the lengths
1254
+ # of the first and last boundary spacing (if extendfrac='auto'):
1255
+ automin = yscaled[1] - yscaled[0]
1256
+ automax = yscaled[-1] - yscaled[-2]
1257
+ extendlength = [0, 0]
1258
+ if self._extend_lower() or self._extend_upper():
1259
+ extendlength = self._get_extension_lengths(
1260
+ self.extendfrac, automin, automax, default=0.05)
1261
+ return y, extendlength
1262
+
1263
+ def _get_extension_lengths(self, frac, automin, automax, default=0.05):
1264
+ """
1265
+ Return the lengths of colorbar extensions.
1266
+
1267
+ This is a helper method for _uniform_y and _proportional_y.
1268
+ """
1269
+ # Set the default value.
1270
+ extendlength = np.array([default, default])
1271
+ if isinstance(frac, str):
1272
+ _api.check_in_list(['auto'], extendfrac=frac.lower())
1273
+ # Use the provided values when 'auto' is required.
1274
+ extendlength[:] = [automin, automax]
1275
+ elif frac is not None:
1276
+ try:
1277
+ # Try to set min and max extension fractions directly.
1278
+ extendlength[:] = frac
1279
+ # If frac is a sequence containing None then NaN may
1280
+ # be encountered. This is an error.
1281
+ if np.isnan(extendlength).any():
1282
+ raise ValueError()
1283
+ except (TypeError, ValueError) as err:
1284
+ # Raise an error on encountering an invalid value for frac.
1285
+ raise ValueError('invalid value for extendfrac') from err
1286
+ return extendlength
1287
+
1288
+ def _extend_lower(self):
1289
+ """Return whether the lower limit is open ended."""
1290
+ minmax = "max" if self.long_axis.get_inverted() else "min"
1291
+ return self.extend in ('both', minmax)
1292
+
1293
+ def _extend_upper(self):
1294
+ """Return whether the upper limit is open ended."""
1295
+ minmax = "min" if self.long_axis.get_inverted() else "max"
1296
+ return self.extend in ('both', minmax)
1297
+
1298
+ def _short_axis(self):
1299
+ """Return the short axis"""
1300
+ if self.orientation == 'vertical':
1301
+ return self.ax.xaxis
1302
+ return self.ax.yaxis
1303
+
1304
+ def _get_view(self):
1305
+ # docstring inherited
1306
+ # An interactive view for a colorbar is the norm's vmin/vmax
1307
+ return self.norm.vmin, self.norm.vmax
1308
+
1309
+ def _set_view(self, view):
1310
+ # docstring inherited
1311
+ # An interactive view for a colorbar is the norm's vmin/vmax
1312
+ self.norm.vmin, self.norm.vmax = view
1313
+
1314
+ def _set_view_from_bbox(self, bbox, direction='in',
1315
+ mode=None, twinx=False, twiny=False):
1316
+ # docstring inherited
1317
+ # For colorbars, we use the zoom bbox to scale the norm's vmin/vmax
1318
+ new_xbound, new_ybound = self.ax._prepare_view_from_bbox(
1319
+ bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny)
1320
+ if self.orientation == 'horizontal':
1321
+ self.norm.vmin, self.norm.vmax = new_xbound
1322
+ elif self.orientation == 'vertical':
1323
+ self.norm.vmin, self.norm.vmax = new_ybound
1324
+
1325
+ def drag_pan(self, button, key, x, y):
1326
+ # docstring inherited
1327
+ points = self.ax._get_pan_points(button, key, x, y)
1328
+ if points is not None:
1329
+ if self.orientation == 'horizontal':
1330
+ self.norm.vmin, self.norm.vmax = points[:, 0]
1331
+ elif self.orientation == 'vertical':
1332
+ self.norm.vmin, self.norm.vmax = points[:, 1]
1333
+
1334
+
1335
+ ColorbarBase = Colorbar # Backcompat API
1336
+
1337
+
1338
+ def _normalize_location_orientation(location, orientation):
1339
+ if location is None:
1340
+ location = _get_ticklocation_from_orientation(orientation)
1341
+ loc_settings = _api.check_getitem({
1342
+ "left": {"location": "left", "anchor": (1.0, 0.5),
1343
+ "panchor": (0.0, 0.5), "pad": 0.10},
1344
+ "right": {"location": "right", "anchor": (0.0, 0.5),
1345
+ "panchor": (1.0, 0.5), "pad": 0.05},
1346
+ "top": {"location": "top", "anchor": (0.5, 0.0),
1347
+ "panchor": (0.5, 1.0), "pad": 0.05},
1348
+ "bottom": {"location": "bottom", "anchor": (0.5, 1.0),
1349
+ "panchor": (0.5, 0.0), "pad": 0.15},
1350
+ }, location=location)
1351
+ loc_settings["orientation"] = _get_orientation_from_location(location)
1352
+ if orientation is not None and orientation != loc_settings["orientation"]:
1353
+ # Allow the user to pass both if they are consistent.
1354
+ raise TypeError("location and orientation are mutually exclusive")
1355
+ return loc_settings
1356
+
1357
+
1358
+ def _get_orientation_from_location(location):
1359
+ return _api.check_getitem(
1360
+ {None: None, "left": "vertical", "right": "vertical",
1361
+ "top": "horizontal", "bottom": "horizontal"}, location=location)
1362
+
1363
+
1364
+ def _get_ticklocation_from_orientation(orientation):
1365
+ return _api.check_getitem(
1366
+ {None: "right", "vertical": "right", "horizontal": "bottom"},
1367
+ orientation=orientation)
1368
+
1369
+
1370
+ @_docstring.interpd
1371
+ def make_axes(parents, location=None, orientation=None, fraction=0.15,
1372
+ shrink=1.0, aspect=20, **kwargs):
1373
+ """
1374
+ Create an `~.axes.Axes` suitable for a colorbar.
1375
+
1376
+ The Axes is placed in the figure of the *parents* Axes, by resizing and
1377
+ repositioning *parents*.
1378
+
1379
+ Parameters
1380
+ ----------
1381
+ parents : `~matplotlib.axes.Axes` or iterable or `numpy.ndarray` of `~.axes.Axes`
1382
+ The Axes to use as parents for placing the colorbar.
1383
+ %(_make_axes_kw_doc)s
1384
+
1385
+ Returns
1386
+ -------
1387
+ cax : `~matplotlib.axes.Axes`
1388
+ The child Axes.
1389
+ kwargs : dict
1390
+ The reduced keyword dictionary to be passed when creating the colorbar
1391
+ instance.
1392
+ """
1393
+ loc_settings = _normalize_location_orientation(location, orientation)
1394
+ # put appropriate values into the kwargs dict for passing back to
1395
+ # the Colorbar class
1396
+ kwargs['orientation'] = loc_settings['orientation']
1397
+ location = kwargs['ticklocation'] = loc_settings['location']
1398
+
1399
+ anchor = kwargs.pop('anchor', loc_settings['anchor'])
1400
+ panchor = kwargs.pop('panchor', loc_settings['panchor'])
1401
+ aspect0 = aspect
1402
+ # turn parents into a list if it is not already. Note we cannot
1403
+ # use .flatten or .ravel as these copy the references rather than
1404
+ # reuse them, leading to a memory leak
1405
+ if isinstance(parents, np.ndarray):
1406
+ parents = list(parents.flat)
1407
+ elif np.iterable(parents):
1408
+ parents = list(parents)
1409
+ else:
1410
+ parents = [parents]
1411
+
1412
+ fig = parents[0].get_figure()
1413
+
1414
+ pad0 = 0.05 if fig.get_constrained_layout() else loc_settings['pad']
1415
+ pad = kwargs.pop('pad', pad0)
1416
+
1417
+ if not all(fig is ax.get_figure() for ax in parents):
1418
+ raise ValueError('Unable to create a colorbar Axes as not all '
1419
+ 'parents share the same figure.')
1420
+
1421
+ # take a bounding box around all of the given Axes
1422
+ parents_bbox = mtransforms.Bbox.union(
1423
+ [ax.get_position(original=True).frozen() for ax in parents])
1424
+
1425
+ pb = parents_bbox
1426
+ if location in ('left', 'right'):
1427
+ if location == 'left':
1428
+ pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
1429
+ else:
1430
+ pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
1431
+ pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
1432
+ else:
1433
+ if location == 'bottom':
1434
+ pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
1435
+ else:
1436
+ pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
1437
+ pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
1438
+
1439
+ # define the aspect ratio in terms of y's per x rather than x's per y
1440
+ aspect = 1.0 / aspect
1441
+
1442
+ # define a transform which takes us from old axes coordinates to
1443
+ # new axes coordinates
1444
+ shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
1445
+
1446
+ # transform each of the Axes in parents using the new transform
1447
+ for ax in parents:
1448
+ new_posn = shrinking_trans.transform(ax.get_position(original=True))
1449
+ new_posn = mtransforms.Bbox(new_posn)
1450
+ ax._set_position(new_posn)
1451
+ if panchor is not False:
1452
+ ax.set_anchor(panchor)
1453
+
1454
+ cax = fig.add_axes(pbcb, label="<colorbar>")
1455
+ for a in parents:
1456
+ a._colorbars.append(cax) # tell the parent it has a colorbar
1457
+ cax._colorbar_info = dict(
1458
+ parents=parents,
1459
+ location=location,
1460
+ shrink=shrink,
1461
+ anchor=anchor,
1462
+ panchor=panchor,
1463
+ fraction=fraction,
1464
+ aspect=aspect0,
1465
+ pad=pad)
1466
+ # and we need to set the aspect ratio by hand...
1467
+ cax.set_anchor(anchor)
1468
+ cax.set_box_aspect(aspect)
1469
+ cax.set_aspect('auto')
1470
+
1471
+ return cax, kwargs
1472
+
1473
+
1474
+ @_docstring.interpd
1475
+ def make_axes_gridspec(parent, *, location=None, orientation=None,
1476
+ fraction=0.15, shrink=1.0, aspect=20, **kwargs):
1477
+ """
1478
+ Create an `~.axes.Axes` suitable for a colorbar.
1479
+
1480
+ The Axes is placed in the figure of the *parent* Axes, by resizing and
1481
+ repositioning *parent*.
1482
+
1483
+ This function is similar to `.make_axes` and mostly compatible with it.
1484
+ Primary differences are
1485
+
1486
+ - `.make_axes_gridspec` requires the *parent* to have a subplotspec.
1487
+ - `.make_axes` positions the Axes in figure coordinates;
1488
+ `.make_axes_gridspec` positions it using a subplotspec.
1489
+ - `.make_axes` updates the position of the parent. `.make_axes_gridspec`
1490
+ replaces the parent gridspec with a new one.
1491
+
1492
+ Parameters
1493
+ ----------
1494
+ parent : `~matplotlib.axes.Axes`
1495
+ The Axes to use as parent for placing the colorbar.
1496
+ %(_make_axes_kw_doc)s
1497
+
1498
+ Returns
1499
+ -------
1500
+ cax : `~matplotlib.axes.Axes`
1501
+ The child Axes.
1502
+ kwargs : dict
1503
+ The reduced keyword dictionary to be passed when creating the colorbar
1504
+ instance.
1505
+ """
1506
+
1507
+ loc_settings = _normalize_location_orientation(location, orientation)
1508
+ kwargs['orientation'] = loc_settings['orientation']
1509
+ location = kwargs['ticklocation'] = loc_settings['location']
1510
+
1511
+ aspect0 = aspect
1512
+ anchor = kwargs.pop('anchor', loc_settings['anchor'])
1513
+ panchor = kwargs.pop('panchor', loc_settings['panchor'])
1514
+ pad = kwargs.pop('pad', loc_settings["pad"])
1515
+ wh_space = 2 * pad / (1 - pad)
1516
+
1517
+ if location in ('left', 'right'):
1518
+ gs = parent.get_subplotspec().subgridspec(
1519
+ 3, 2, wspace=wh_space, hspace=0,
1520
+ height_ratios=[(1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)])
1521
+ if location == 'left':
1522
+ gs.set_width_ratios([fraction, 1 - fraction - pad])
1523
+ ss_main = gs[:, 1]
1524
+ ss_cb = gs[1, 0]
1525
+ else:
1526
+ gs.set_width_ratios([1 - fraction - pad, fraction])
1527
+ ss_main = gs[:, 0]
1528
+ ss_cb = gs[1, 1]
1529
+ else:
1530
+ gs = parent.get_subplotspec().subgridspec(
1531
+ 2, 3, hspace=wh_space, wspace=0,
1532
+ width_ratios=[anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)])
1533
+ if location == 'top':
1534
+ gs.set_height_ratios([fraction, 1 - fraction - pad])
1535
+ ss_main = gs[1, :]
1536
+ ss_cb = gs[0, 1]
1537
+ else:
1538
+ gs.set_height_ratios([1 - fraction - pad, fraction])
1539
+ ss_main = gs[0, :]
1540
+ ss_cb = gs[1, 1]
1541
+ aspect = 1 / aspect
1542
+
1543
+ parent.set_subplotspec(ss_main)
1544
+ if panchor is not False:
1545
+ parent.set_anchor(panchor)
1546
+
1547
+ fig = parent.get_figure()
1548
+ cax = fig.add_subplot(ss_cb, label="<colorbar>")
1549
+ parent._colorbars.append(cax) # tell the parent it has a colorbar
1550
+ cax.set_anchor(anchor)
1551
+ cax.set_box_aspect(aspect)
1552
+ cax.set_aspect('auto')
1553
+ cax._colorbar_info = dict(
1554
+ location=location,
1555
+ parents=[parent],
1556
+ shrink=shrink,
1557
+ anchor=anchor,
1558
+ panchor=panchor,
1559
+ fraction=fraction,
1560
+ aspect=aspect0,
1561
+ pad=pad)
1562
+
1563
+ return cax, kwargs
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.spines as mspines
2
+ from matplotlib import cm, collections, colors, contour, colorizer
3
+ from matplotlib.axes import Axes
4
+ from matplotlib.axis import Axis
5
+ from matplotlib.backend_bases import RendererBase
6
+ from matplotlib.patches import Patch
7
+ from matplotlib.ticker import Locator, Formatter
8
+ from matplotlib.transforms import Bbox
9
+
10
+ import numpy as np
11
+ from numpy.typing import ArrayLike
12
+ from collections.abc import Sequence
13
+ from typing import Any, Literal, overload
14
+ from .typing import ColorType
15
+
16
+ class _ColorbarSpine(mspines.Spines):
17
+ def __init__(self, axes: Axes): ...
18
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox:...
19
+ def set_xy(self, xy: ArrayLike) -> None: ...
20
+ def draw(self, renderer: RendererBase | None) -> None:...
21
+
22
+
23
+ class Colorbar:
24
+ n_rasterize: int
25
+ mappable: cm.ScalarMappable | colorizer.ColorizingArtist
26
+ ax: Axes
27
+ alpha: float | None
28
+ cmap: colors.Colormap
29
+ norm: colors.Normalize
30
+ values: Sequence[float] | None
31
+ boundaries: Sequence[float] | None
32
+ extend: Literal["neither", "both", "min", "max"]
33
+ spacing: Literal["uniform", "proportional"]
34
+ orientation: Literal["vertical", "horizontal"]
35
+ drawedges: bool
36
+ extendfrac: Literal["auto"] | float | Sequence[float] | None
37
+ extendrect: bool
38
+ solids: None | collections.QuadMesh
39
+ solids_patches: list[Patch]
40
+ lines: list[collections.LineCollection]
41
+ outline: _ColorbarSpine
42
+ dividers: collections.LineCollection
43
+ ticklocation: Literal["left", "right", "top", "bottom"]
44
+ def __init__(
45
+ self,
46
+ ax: Axes,
47
+ mappable: cm.ScalarMappable | colorizer.ColorizingArtist | None = ...,
48
+ *,
49
+ cmap: str | colors.Colormap | None = ...,
50
+ norm: colors.Normalize | None = ...,
51
+ alpha: float | None = ...,
52
+ values: Sequence[float] | None = ...,
53
+ boundaries: Sequence[float] | None = ...,
54
+ orientation: Literal["vertical", "horizontal"] | None = ...,
55
+ ticklocation: Literal["auto", "left", "right", "top", "bottom"] = ...,
56
+ extend: Literal["neither", "both", "min", "max"] | None = ...,
57
+ spacing: Literal["uniform", "proportional"] = ...,
58
+ ticks: Sequence[float] | Locator | None = ...,
59
+ format: str | Formatter | None = ...,
60
+ drawedges: bool = ...,
61
+ extendfrac: Literal["auto"] | float | Sequence[float] | None = ...,
62
+ extendrect: bool = ...,
63
+ label: str = ...,
64
+ location: Literal["left", "right", "top", "bottom"] | None = ...
65
+ ) -> None: ...
66
+ @property
67
+ def long_axis(self) -> Axis: ...
68
+ @property
69
+ def locator(self) -> Locator: ...
70
+ @locator.setter
71
+ def locator(self, loc: Locator) -> None: ...
72
+ @property
73
+ def minorlocator(self) -> Locator: ...
74
+ @minorlocator.setter
75
+ def minorlocator(self, loc: Locator) -> None: ...
76
+ @property
77
+ def formatter(self) -> Formatter: ...
78
+ @formatter.setter
79
+ def formatter(self, fmt: Formatter) -> None: ...
80
+ @property
81
+ def minorformatter(self) -> Formatter: ...
82
+ @minorformatter.setter
83
+ def minorformatter(self, fmt: Formatter) -> None: ...
84
+ def update_normal(self, mappable: cm.ScalarMappable | None = ...) -> None: ...
85
+ @overload
86
+ def add_lines(self, CS: contour.ContourSet, erase: bool = ...) -> None: ...
87
+ @overload
88
+ def add_lines(
89
+ self,
90
+ levels: ArrayLike,
91
+ colors: ColorType | Sequence[ColorType],
92
+ linewidths: float | ArrayLike,
93
+ erase: bool = ...,
94
+ ) -> None: ...
95
+ def update_ticks(self) -> None: ...
96
+ def set_ticks(
97
+ self,
98
+ ticks: Sequence[float] | Locator,
99
+ *,
100
+ labels: Sequence[str] | None = ...,
101
+ minor: bool = ...,
102
+ **kwargs
103
+ ) -> None: ...
104
+ def get_ticks(self, minor: bool = ...) -> np.ndarray: ...
105
+ def set_ticklabels(
106
+ self,
107
+ ticklabels: Sequence[str],
108
+ *,
109
+ minor: bool = ...,
110
+ **kwargs
111
+ ) -> None: ...
112
+ def minorticks_on(self) -> None: ...
113
+ def minorticks_off(self) -> None: ...
114
+ def set_label(self, label: str, *, loc: str | None = ..., **kwargs) -> None: ...
115
+ def set_alpha(self, alpha: float | np.ndarray) -> None: ...
116
+ def remove(self) -> None: ...
117
+ def drag_pan(self, button: Any, key: Any, x: float, y: float) -> None: ...
118
+
119
+ ColorbarBase = Colorbar
120
+
121
+ def make_axes(
122
+ parents: Axes | list[Axes] | np.ndarray,
123
+ location: Literal["left", "right", "top", "bottom"] | None = ...,
124
+ orientation: Literal["vertical", "horizontal"] | None = ...,
125
+ fraction: float = ...,
126
+ shrink: float = ...,
127
+ aspect: float = ...,
128
+ **kwargs
129
+ ) -> tuple[Axes, dict[str, Any]]: ...
130
+ def make_axes_gridspec(
131
+ parent: Axes,
132
+ *,
133
+ location: Literal["left", "right", "top", "bottom"] | None = ...,
134
+ orientation: Literal["vertical", "horizontal"] | None = ...,
135
+ fraction: float = ...,
136
+ shrink: float = ...,
137
+ aspect: float = ...,
138
+ **kwargs
139
+ ) -> tuple[Axes, dict[str, Any]]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py ADDED
@@ -0,0 +1,703 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The Colorizer class which handles the data to color pipeline via a
3
+ normalization and a colormap.
4
+
5
+ .. admonition:: Provisional status of colorizer
6
+
7
+ The ``colorizer`` module and classes in this file are considered
8
+ provisional and may change at any time without a deprecation period.
9
+
10
+ .. seealso::
11
+
12
+ :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps.
13
+
14
+ :ref:`colormap-manipulation` for examples of how to make colormaps.
15
+
16
+ :ref:`colormaps` for an in-depth discussion of choosing colormaps.
17
+
18
+ :ref:`colormapnorms` for more details about data normalization.
19
+
20
+ """
21
+
22
+ import functools
23
+
24
+ import numpy as np
25
+ from numpy import ma
26
+
27
+ from matplotlib import _api, colors, cbook, scale, artist
28
+ import matplotlib as mpl
29
+
30
+ mpl._docstring.interpd.register(
31
+ colorizer_doc="""\
32
+ colorizer : `~matplotlib.colorizer.Colorizer` or None, default: None
33
+ The Colorizer object used to map color to data. If None, a Colorizer
34
+ object is created from a *norm* and *cmap*.""",
35
+ )
36
+
37
+
38
+ class Colorizer:
39
+ """
40
+ Data to color pipeline.
41
+
42
+ This pipeline is accessible via `.Colorizer.to_rgba` and executed via
43
+ the `.Colorizer.norm` and `.Colorizer.cmap` attributes.
44
+
45
+ Parameters
46
+ ----------
47
+ cmap: colorbar.Colorbar or str or None, default: None
48
+ The colormap used to color data.
49
+
50
+ norm: colors.Normalize or str or None, default: None
51
+ The normalization used to normalize the data
52
+ """
53
+ def __init__(self, cmap=None, norm=None):
54
+
55
+ self._cmap = None
56
+ self._set_cmap(cmap)
57
+
58
+ self._id_norm = None
59
+ self._norm = None
60
+ self.norm = norm
61
+
62
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
63
+ self.colorbar = None
64
+
65
+ def _scale_norm(self, norm, vmin, vmax, A):
66
+ """
67
+ Helper for initial scaling.
68
+
69
+ Used by public functions that create a ScalarMappable and support
70
+ parameters *vmin*, *vmax* and *norm*. This makes sure that a *norm*
71
+ will take precedence over *vmin*, *vmax*.
72
+
73
+ Note that this method does not set the norm.
74
+ """
75
+ if vmin is not None or vmax is not None:
76
+ self.set_clim(vmin, vmax)
77
+ if isinstance(norm, colors.Normalize):
78
+ raise ValueError(
79
+ "Passing a Normalize instance simultaneously with "
80
+ "vmin/vmax is not supported. Please pass vmin/vmax "
81
+ "directly to the norm when creating it.")
82
+
83
+ # always resolve the autoscaling so we have concrete limits
84
+ # rather than deferring to draw time.
85
+ self.autoscale_None(A)
86
+
87
+ @property
88
+ def norm(self):
89
+ return self._norm
90
+
91
+ @norm.setter
92
+ def norm(self, norm):
93
+ _api.check_isinstance((colors.Normalize, str, None), norm=norm)
94
+ if norm is None:
95
+ norm = colors.Normalize()
96
+ elif isinstance(norm, str):
97
+ try:
98
+ scale_cls = scale._scale_mapping[norm]
99
+ except KeyError:
100
+ raise ValueError(
101
+ "Invalid norm str name; the following values are "
102
+ f"supported: {', '.join(scale._scale_mapping)}"
103
+ ) from None
104
+ norm = _auto_norm_from_scale(scale_cls)()
105
+
106
+ if norm is self.norm:
107
+ # We aren't updating anything
108
+ return
109
+
110
+ in_init = self.norm is None
111
+ # Remove the current callback and connect to the new one
112
+ if not in_init:
113
+ self.norm.callbacks.disconnect(self._id_norm)
114
+ self._norm = norm
115
+ self._id_norm = self.norm.callbacks.connect('changed',
116
+ self.changed)
117
+ if not in_init:
118
+ self.changed()
119
+
120
+ def to_rgba(self, x, alpha=None, bytes=False, norm=True):
121
+ """
122
+ Return a normalized RGBA array corresponding to *x*.
123
+
124
+ In the normal case, *x* is a 1D or 2D sequence of scalars, and
125
+ the corresponding `~numpy.ndarray` of RGBA values will be returned,
126
+ based on the norm and colormap set for this Colorizer.
127
+
128
+ There is one special case, for handling images that are already
129
+ RGB or RGBA, such as might have been read from an image file.
130
+ If *x* is an `~numpy.ndarray` with 3 dimensions,
131
+ and the last dimension is either 3 or 4, then it will be
132
+ treated as an RGB or RGBA array, and no mapping will be done.
133
+ The array can be `~numpy.uint8`, or it can be floats with
134
+ values in the 0-1 range; otherwise a ValueError will be raised.
135
+ Any NaNs or masked elements will be set to 0 alpha.
136
+ If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
137
+ will be used to fill in the transparency. If the last dimension
138
+ is 4, the *alpha* kwarg is ignored; it does not
139
+ replace the preexisting alpha. A ValueError will be raised
140
+ if the third dimension is other than 3 or 4.
141
+
142
+ In either case, if *bytes* is *False* (default), the RGBA
143
+ array will be floats in the 0-1 range; if it is *True*,
144
+ the returned RGBA array will be `~numpy.uint8` in the 0 to 255 range.
145
+
146
+ If norm is False, no normalization of the input data is
147
+ performed, and it is assumed to be in the range (0-1).
148
+
149
+ """
150
+ # First check for special case, image input:
151
+ if isinstance(x, np.ndarray) and x.ndim == 3:
152
+ return self._pass_image_data(x, alpha, bytes, norm)
153
+
154
+ # Otherwise run norm -> colormap pipeline
155
+ x = ma.asarray(x)
156
+ if norm:
157
+ x = self.norm(x)
158
+ rgba = self.cmap(x, alpha=alpha, bytes=bytes)
159
+ return rgba
160
+
161
+ @staticmethod
162
+ def _pass_image_data(x, alpha=None, bytes=False, norm=True):
163
+ """
164
+ Helper function to pass ndarray of shape (...,3) or (..., 4)
165
+ through `to_rgba()`, see `to_rgba()` for docstring.
166
+ """
167
+ if x.shape[2] == 3:
168
+ if alpha is None:
169
+ alpha = 1
170
+ if x.dtype == np.uint8:
171
+ alpha = np.uint8(alpha * 255)
172
+ m, n = x.shape[:2]
173
+ xx = np.empty(shape=(m, n, 4), dtype=x.dtype)
174
+ xx[:, :, :3] = x
175
+ xx[:, :, 3] = alpha
176
+ elif x.shape[2] == 4:
177
+ xx = x
178
+ else:
179
+ raise ValueError("Third dimension must be 3 or 4")
180
+ if xx.dtype.kind == 'f':
181
+ # If any of R, G, B, or A is nan, set to 0
182
+ if np.any(nans := np.isnan(x)):
183
+ if x.shape[2] == 4:
184
+ xx = xx.copy()
185
+ xx[np.any(nans, axis=2), :] = 0
186
+
187
+ if norm and (xx.max() > 1 or xx.min() < 0):
188
+ raise ValueError("Floating point image RGB values "
189
+ "must be in the 0..1 range.")
190
+ if bytes:
191
+ xx = (xx * 255).astype(np.uint8)
192
+ elif xx.dtype == np.uint8:
193
+ if not bytes:
194
+ xx = xx.astype(np.float32) / 255
195
+ else:
196
+ raise ValueError("Image RGB array must be uint8 or "
197
+ "floating point; found %s" % xx.dtype)
198
+ # Account for any masked entries in the original array
199
+ # If any of R, G, B, or A are masked for an entry, we set alpha to 0
200
+ if np.ma.is_masked(x):
201
+ xx[np.any(np.ma.getmaskarray(x), axis=2), 3] = 0
202
+ return xx
203
+
204
+ def autoscale(self, A):
205
+ """
206
+ Autoscale the scalar limits on the norm instance using the
207
+ current array
208
+ """
209
+ if A is None:
210
+ raise TypeError('You must first set_array for mappable')
211
+ # If the norm's limits are updated self.changed() will be called
212
+ # through the callbacks attached to the norm
213
+ self.norm.autoscale(A)
214
+
215
+ def autoscale_None(self, A):
216
+ """
217
+ Autoscale the scalar limits on the norm instance using the
218
+ current array, changing only limits that are None
219
+ """
220
+ if A is None:
221
+ raise TypeError('You must first set_array for mappable')
222
+ # If the norm's limits are updated self.changed() will be called
223
+ # through the callbacks attached to the norm
224
+ self.norm.autoscale_None(A)
225
+
226
+ def _set_cmap(self, cmap):
227
+ """
228
+ Set the colormap for luminance data.
229
+
230
+ Parameters
231
+ ----------
232
+ cmap : `.Colormap` or str or None
233
+ """
234
+ # bury import to avoid circular imports
235
+ from matplotlib import cm
236
+ in_init = self._cmap is None
237
+ self._cmap = cm._ensure_cmap(cmap)
238
+ if not in_init:
239
+ self.changed() # Things are not set up properly yet.
240
+
241
+ @property
242
+ def cmap(self):
243
+ return self._cmap
244
+
245
+ @cmap.setter
246
+ def cmap(self, cmap):
247
+ self._set_cmap(cmap)
248
+
249
+ def set_clim(self, vmin=None, vmax=None):
250
+ """
251
+ Set the norm limits for image scaling.
252
+
253
+ Parameters
254
+ ----------
255
+ vmin, vmax : float
256
+ The limits.
257
+
258
+ The limits may also be passed as a tuple (*vmin*, *vmax*) as a
259
+ single positional argument.
260
+
261
+ .. ACCEPTS: (vmin: float, vmax: float)
262
+ """
263
+ # If the norm's limits are updated self.changed() will be called
264
+ # through the callbacks attached to the norm, this causes an inconsistent
265
+ # state, to prevent this blocked context manager is used
266
+ if vmax is None:
267
+ try:
268
+ vmin, vmax = vmin
269
+ except (TypeError, ValueError):
270
+ pass
271
+
272
+ orig_vmin_vmax = self.norm.vmin, self.norm.vmax
273
+
274
+ # Blocked context manager prevents callbacks from being triggered
275
+ # until both vmin and vmax are updated
276
+ with self.norm.callbacks.blocked(signal='changed'):
277
+ if vmin is not None:
278
+ self.norm.vmin = colors._sanitize_extrema(vmin)
279
+ if vmax is not None:
280
+ self.norm.vmax = colors._sanitize_extrema(vmax)
281
+
282
+ # emit a update signal if the limits are changed
283
+ if orig_vmin_vmax != (self.norm.vmin, self.norm.vmax):
284
+ self.norm.callbacks.process('changed')
285
+
286
+ def get_clim(self):
287
+ """
288
+ Return the values (min, max) that are mapped to the colormap limits.
289
+ """
290
+ return self.norm.vmin, self.norm.vmax
291
+
292
+ def changed(self):
293
+ """
294
+ Call this whenever the mappable is changed to notify all the
295
+ callbackSM listeners to the 'changed' signal.
296
+ """
297
+ self.callbacks.process('changed')
298
+ self.stale = True
299
+
300
+ @property
301
+ def vmin(self):
302
+ return self.get_clim()[0]
303
+
304
+ @vmin.setter
305
+ def vmin(self, vmin):
306
+ self.set_clim(vmin=vmin)
307
+
308
+ @property
309
+ def vmax(self):
310
+ return self.get_clim()[1]
311
+
312
+ @vmax.setter
313
+ def vmax(self, vmax):
314
+ self.set_clim(vmax=vmax)
315
+
316
+ @property
317
+ def clip(self):
318
+ return self.norm.clip
319
+
320
+ @clip.setter
321
+ def clip(self, clip):
322
+ self.norm.clip = clip
323
+
324
+
325
+ class _ColorizerInterface:
326
+ """
327
+ Base class that contains the interface to `Colorizer` objects from
328
+ a `ColorizingArtist` or `.cm.ScalarMappable`.
329
+
330
+ Note: This class only contain functions that interface the .colorizer
331
+ attribute. Other functions that as shared between `.ColorizingArtist`
332
+ and `.cm.ScalarMappable` are not included.
333
+ """
334
+ def _scale_norm(self, norm, vmin, vmax):
335
+ self._colorizer._scale_norm(norm, vmin, vmax, self._A)
336
+
337
+ def to_rgba(self, x, alpha=None, bytes=False, norm=True):
338
+ """
339
+ Return a normalized RGBA array corresponding to *x*.
340
+
341
+ In the normal case, *x* is a 1D or 2D sequence of scalars, and
342
+ the corresponding `~numpy.ndarray` of RGBA values will be returned,
343
+ based on the norm and colormap set for this Colorizer.
344
+
345
+ There is one special case, for handling images that are already
346
+ RGB or RGBA, such as might have been read from an image file.
347
+ If *x* is an `~numpy.ndarray` with 3 dimensions,
348
+ and the last dimension is either 3 or 4, then it will be
349
+ treated as an RGB or RGBA array, and no mapping will be done.
350
+ The array can be `~numpy.uint8`, or it can be floats with
351
+ values in the 0-1 range; otherwise a ValueError will be raised.
352
+ Any NaNs or masked elements will be set to 0 alpha.
353
+ If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
354
+ will be used to fill in the transparency. If the last dimension
355
+ is 4, the *alpha* kwarg is ignored; it does not
356
+ replace the preexisting alpha. A ValueError will be raised
357
+ if the third dimension is other than 3 or 4.
358
+
359
+ In either case, if *bytes* is *False* (default), the RGBA
360
+ array will be floats in the 0-1 range; if it is *True*,
361
+ the returned RGBA array will be `~numpy.uint8` in the 0 to 255 range.
362
+
363
+ If norm is False, no normalization of the input data is
364
+ performed, and it is assumed to be in the range (0-1).
365
+
366
+ """
367
+ return self._colorizer.to_rgba(x, alpha=alpha, bytes=bytes, norm=norm)
368
+
369
+ def get_clim(self):
370
+ """
371
+ Return the values (min, max) that are mapped to the colormap limits.
372
+ """
373
+ return self._colorizer.get_clim()
374
+
375
+ def set_clim(self, vmin=None, vmax=None):
376
+ """
377
+ Set the norm limits for image scaling.
378
+
379
+ Parameters
380
+ ----------
381
+ vmin, vmax : float
382
+ The limits.
383
+
384
+ For scalar data, the limits may also be passed as a
385
+ tuple (*vmin*, *vmax*) as a single positional argument.
386
+
387
+ .. ACCEPTS: (vmin: float, vmax: float)
388
+ """
389
+ # If the norm's limits are updated self.changed() will be called
390
+ # through the callbacks attached to the norm
391
+ self._colorizer.set_clim(vmin, vmax)
392
+
393
+ def get_alpha(self):
394
+ try:
395
+ return super().get_alpha()
396
+ except AttributeError:
397
+ return 1
398
+
399
+ @property
400
+ def cmap(self):
401
+ return self._colorizer.cmap
402
+
403
+ @cmap.setter
404
+ def cmap(self, cmap):
405
+ self._colorizer.cmap = cmap
406
+
407
+ def get_cmap(self):
408
+ """Return the `.Colormap` instance."""
409
+ return self._colorizer.cmap
410
+
411
+ def set_cmap(self, cmap):
412
+ """
413
+ Set the colormap for luminance data.
414
+
415
+ Parameters
416
+ ----------
417
+ cmap : `.Colormap` or str or None
418
+ """
419
+ self.cmap = cmap
420
+
421
+ @property
422
+ def norm(self):
423
+ return self._colorizer.norm
424
+
425
+ @norm.setter
426
+ def norm(self, norm):
427
+ self._colorizer.norm = norm
428
+
429
+ def set_norm(self, norm):
430
+ """
431
+ Set the normalization instance.
432
+
433
+ Parameters
434
+ ----------
435
+ norm : `.Normalize` or str or None
436
+
437
+ Notes
438
+ -----
439
+ If there are any colorbars using the mappable for this norm, setting
440
+ the norm of the mappable will reset the norm, locator, and formatters
441
+ on the colorbar to default.
442
+ """
443
+ self.norm = norm
444
+
445
+ def autoscale(self):
446
+ """
447
+ Autoscale the scalar limits on the norm instance using the
448
+ current array
449
+ """
450
+ self._colorizer.autoscale(self._A)
451
+
452
+ def autoscale_None(self):
453
+ """
454
+ Autoscale the scalar limits on the norm instance using the
455
+ current array, changing only limits that are None
456
+ """
457
+ self._colorizer.autoscale_None(self._A)
458
+
459
+ @property
460
+ def colorbar(self):
461
+ """
462
+ The last colorbar associated with this object. May be None
463
+ """
464
+ return self._colorizer.colorbar
465
+
466
+ @colorbar.setter
467
+ def colorbar(self, colorbar):
468
+ self._colorizer.colorbar = colorbar
469
+
470
+ def _format_cursor_data_override(self, data):
471
+ # This function overwrites Artist.format_cursor_data(). We cannot
472
+ # implement cm.ScalarMappable.format_cursor_data() directly, because
473
+ # most cm.ScalarMappable subclasses inherit from Artist first and from
474
+ # cm.ScalarMappable second, so Artist.format_cursor_data would always
475
+ # have precedence over cm.ScalarMappable.format_cursor_data.
476
+
477
+ # Note if cm.ScalarMappable is depreciated, this functionality should be
478
+ # implemented as format_cursor_data() on ColorizingArtist.
479
+ n = self.cmap.N
480
+ if np.ma.getmask(data):
481
+ return "[]"
482
+ normed = self.norm(data)
483
+ if np.isfinite(normed):
484
+ if isinstance(self.norm, colors.BoundaryNorm):
485
+ # not an invertible normalization mapping
486
+ cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
487
+ neigh_idx = max(0, cur_idx - 1)
488
+ # use max diff to prevent delta == 0
489
+ delta = np.diff(
490
+ self.norm.boundaries[neigh_idx:cur_idx + 2]
491
+ ).max()
492
+ elif self.norm.vmin == self.norm.vmax:
493
+ # singular norms, use delta of 10% of only value
494
+ delta = np.abs(self.norm.vmin * .1)
495
+ else:
496
+ # Midpoints of neighboring color intervals.
497
+ neighbors = self.norm.inverse(
498
+ (int(normed * n) + np.array([0, 1])) / n)
499
+ delta = abs(neighbors - data).max()
500
+ g_sig_digits = cbook._g_sig_digits(data, delta)
501
+ else:
502
+ g_sig_digits = 3 # Consistent with default below.
503
+ return f"[{data:-#.{g_sig_digits}g}]"
504
+
505
+
506
+ class _ScalarMappable(_ColorizerInterface):
507
+ """
508
+ A mixin class to map one or multiple sets of scalar data to RGBA.
509
+
510
+ The ScalarMappable applies data normalization before returning RGBA colors from
511
+ the given `~matplotlib.colors.Colormap`.
512
+ """
513
+
514
+ # _ScalarMappable exists for compatibility with
515
+ # code written before the introduction of the Colorizer
516
+ # and ColorizingArtist classes.
517
+
518
+ # _ScalarMappable can be depreciated so that ColorizingArtist
519
+ # inherits directly from _ColorizerInterface.
520
+ # in this case, the following changes should occur:
521
+ # __init__() has its functionality moved to ColorizingArtist.
522
+ # set_array(), get_array(), _get_colorizer() and
523
+ # _check_exclusionary_keywords() are moved to ColorizingArtist.
524
+ # changed() can be removed so long as colorbar.Colorbar
525
+ # is changed to connect to the colorizer instead of the
526
+ # ScalarMappable/ColorizingArtist,
527
+ # otherwise changed() can be moved to ColorizingArtist.
528
+ def __init__(self, norm=None, cmap=None, *, colorizer=None, **kwargs):
529
+ """
530
+ Parameters
531
+ ----------
532
+ norm : `.Normalize` (or subclass thereof) or str or None
533
+ The normalizing object which scales data, typically into the
534
+ interval ``[0, 1]``.
535
+ If a `str`, a `.Normalize` subclass is dynamically generated based
536
+ on the scale with the corresponding name.
537
+ If *None*, *norm* defaults to a *colors.Normalize* object which
538
+ initializes its scaling based on the first data processed.
539
+ cmap : str or `~matplotlib.colors.Colormap`
540
+ The colormap used to map normalized data values to RGBA colors.
541
+ """
542
+ super().__init__(**kwargs)
543
+ self._A = None
544
+ self._colorizer = self._get_colorizer(colorizer=colorizer, norm=norm, cmap=cmap)
545
+
546
+ self.colorbar = None
547
+ self._id_colorizer = self._colorizer.callbacks.connect('changed', self.changed)
548
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
549
+
550
+ def set_array(self, A):
551
+ """
552
+ Set the value array from array-like *A*.
553
+
554
+ Parameters
555
+ ----------
556
+ A : array-like or None
557
+ The values that are mapped to colors.
558
+
559
+ The base class `.ScalarMappable` does not make any assumptions on
560
+ the dimensionality and shape of the value array *A*.
561
+ """
562
+ if A is None:
563
+ self._A = None
564
+ return
565
+
566
+ A = cbook.safe_masked_invalid(A, copy=True)
567
+ if not np.can_cast(A.dtype, float, "same_kind"):
568
+ raise TypeError(f"Image data of dtype {A.dtype} cannot be "
569
+ "converted to float")
570
+
571
+ self._A = A
572
+ if not self.norm.scaled():
573
+ self._colorizer.autoscale_None(A)
574
+
575
+ def get_array(self):
576
+ """
577
+ Return the array of values, that are mapped to colors.
578
+
579
+ The base class `.ScalarMappable` does not make any assumptions on
580
+ the dimensionality and shape of the array.
581
+ """
582
+ return self._A
583
+
584
+ def changed(self):
585
+ """
586
+ Call this whenever the mappable is changed to notify all the
587
+ callbackSM listeners to the 'changed' signal.
588
+ """
589
+ self.callbacks.process('changed', self)
590
+ self.stale = True
591
+
592
+ @staticmethod
593
+ def _check_exclusionary_keywords(colorizer, **kwargs):
594
+ """
595
+ Raises a ValueError if any kwarg is not None while colorizer is not None
596
+ """
597
+ if colorizer is not None:
598
+ if any([val is not None for val in kwargs.values()]):
599
+ raise ValueError("The `colorizer` keyword cannot be used simultaneously"
600
+ " with any of the following keywords: "
601
+ + ", ".join(f'`{key}`' for key in kwargs.keys()))
602
+
603
+ @staticmethod
604
+ def _get_colorizer(cmap, norm, colorizer):
605
+ if isinstance(colorizer, Colorizer):
606
+ _ScalarMappable._check_exclusionary_keywords(
607
+ Colorizer, cmap=cmap, norm=norm
608
+ )
609
+ return colorizer
610
+ return Colorizer(cmap, norm)
611
+
612
+ # The docstrings here must be generic enough to apply to all relevant methods.
613
+ mpl._docstring.interpd.register(
614
+ cmap_doc="""\
615
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
616
+ The Colormap instance or registered colormap name used to map scalar data
617
+ to colors.""",
618
+ norm_doc="""\
619
+ norm : str or `~matplotlib.colors.Normalize`, optional
620
+ The normalization method used to scale scalar data to the [0, 1] range
621
+ before mapping to colors using *cmap*. By default, a linear scaling is
622
+ used, mapping the lowest value to 0 and the highest to 1.
623
+
624
+ If given, this can be one of the following:
625
+
626
+ - An instance of `.Normalize` or one of its subclasses
627
+ (see :ref:`colormapnorms`).
628
+ - A scale name, i.e. one of "linear", "log", "symlog", "logit", etc. For a
629
+ list of available scales, call `matplotlib.scale.get_scale_names()`.
630
+ In that case, a suitable `.Normalize` subclass is dynamically generated
631
+ and instantiated.""",
632
+ vmin_vmax_doc="""\
633
+ vmin, vmax : float, optional
634
+ When using scalar data and no explicit *norm*, *vmin* and *vmax* define
635
+ the data range that the colormap covers. By default, the colormap covers
636
+ the complete value range of the supplied data. It is an error to use
637
+ *vmin*/*vmax* when a *norm* instance is given (but using a `str` *norm*
638
+ name together with *vmin*/*vmax* is acceptable).""",
639
+ )
640
+
641
+
642
+ class ColorizingArtist(_ScalarMappable, artist.Artist):
643
+ """
644
+ Base class for artists that make map data to color using a `.colorizer.Colorizer`.
645
+
646
+ The `.colorizer.Colorizer` applies data normalization before
647
+ returning RGBA colors from a `~matplotlib.colors.Colormap`.
648
+
649
+ """
650
+ def __init__(self, colorizer, **kwargs):
651
+ """
652
+ Parameters
653
+ ----------
654
+ colorizer : `.colorizer.Colorizer`
655
+ """
656
+ _api.check_isinstance(Colorizer, colorizer=colorizer)
657
+ super().__init__(colorizer=colorizer, **kwargs)
658
+
659
+ @property
660
+ def colorizer(self):
661
+ return self._colorizer
662
+
663
+ @colorizer.setter
664
+ def colorizer(self, cl):
665
+ _api.check_isinstance(Colorizer, colorizer=cl)
666
+ self._colorizer.callbacks.disconnect(self._id_colorizer)
667
+ self._colorizer = cl
668
+ self._id_colorizer = cl.callbacks.connect('changed', self.changed)
669
+
670
+ def _set_colorizer_check_keywords(self, colorizer, **kwargs):
671
+ """
672
+ Raises a ValueError if any kwarg is not None while colorizer is not None.
673
+ """
674
+ self._check_exclusionary_keywords(colorizer, **kwargs)
675
+ self.colorizer = colorizer
676
+
677
+
678
+ def _auto_norm_from_scale(scale_cls):
679
+ """
680
+ Automatically generate a norm class from *scale_cls*.
681
+
682
+ This differs from `.colors.make_norm_from_scale` in the following points:
683
+
684
+ - This function is not a class decorator, but directly returns a norm class
685
+ (as if decorating `.Normalize`).
686
+ - The scale is automatically constructed with ``nonpositive="mask"``, if it
687
+ supports such a parameter, to work around the difference in defaults
688
+ between standard scales (which use "clip") and norms (which use "mask").
689
+
690
+ Note that ``make_norm_from_scale`` caches the generated norm classes
691
+ (not the instances) and reuses them for later calls. For example,
692
+ ``type(_auto_norm_from_scale("log")) == LogNorm``.
693
+ """
694
+ # Actually try to construct an instance, to verify whether
695
+ # ``nonpositive="mask"`` is supported.
696
+ try:
697
+ norm = colors.make_norm_from_scale(
698
+ functools.partial(scale_cls, nonpositive="mask"))(
699
+ colors.Normalize)()
700
+ except TypeError:
701
+ norm = colors.make_norm_from_scale(scale_cls)(
702
+ colors.Normalize)()
703
+ return type(norm)
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib import cbook, colorbar, colors, artist
2
+
3
+ from typing import overload
4
+ import numpy as np
5
+ from numpy.typing import ArrayLike
6
+
7
+
8
+ class Colorizer:
9
+ colorbar: colorbar.Colorbar | None
10
+ callbacks: cbook.CallbackRegistry
11
+ def __init__(
12
+ self,
13
+ cmap: str | colors.Colormap | None = ...,
14
+ norm: str | colors.Normalize | None = ...,
15
+ ) -> None: ...
16
+ @property
17
+ def norm(self) -> colors.Normalize: ...
18
+ @norm.setter
19
+ def norm(self, norm: colors.Normalize | str | None) -> None: ...
20
+ def to_rgba(
21
+ self,
22
+ x: np.ndarray,
23
+ alpha: float | ArrayLike | None = ...,
24
+ bytes: bool = ...,
25
+ norm: bool = ...,
26
+ ) -> np.ndarray: ...
27
+ def autoscale(self, A: ArrayLike) -> None: ...
28
+ def autoscale_None(self, A: ArrayLike) -> None: ...
29
+ @property
30
+ def cmap(self) -> colors.Colormap: ...
31
+ @cmap.setter
32
+ def cmap(self, cmap: colors.Colormap | str | None) -> None: ...
33
+ def get_clim(self) -> tuple[float, float]: ...
34
+ def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ...
35
+ def changed(self) -> None: ...
36
+ @property
37
+ def vmin(self) -> float | None: ...
38
+ @vmin.setter
39
+ def vmin(self, value: float | None) -> None: ...
40
+ @property
41
+ def vmax(self) -> float | None: ...
42
+ @vmax.setter
43
+ def vmax(self, value: float | None) -> None: ...
44
+ @property
45
+ def clip(self) -> bool: ...
46
+ @clip.setter
47
+ def clip(self, value: bool) -> None: ...
48
+
49
+
50
+ class _ColorizerInterface:
51
+ cmap: colors.Colormap
52
+ colorbar: colorbar.Colorbar | None
53
+ callbacks: cbook.CallbackRegistry
54
+ def to_rgba(
55
+ self,
56
+ x: np.ndarray,
57
+ alpha: float | ArrayLike | None = ...,
58
+ bytes: bool = ...,
59
+ norm: bool = ...,
60
+ ) -> np.ndarray: ...
61
+ def get_clim(self) -> tuple[float, float]: ...
62
+ def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ...
63
+ def get_alpha(self) -> float | None: ...
64
+ def get_cmap(self) -> colors.Colormap: ...
65
+ def set_cmap(self, cmap: str | colors.Colormap) -> None: ...
66
+ @property
67
+ def norm(self) -> colors.Normalize: ...
68
+ @norm.setter
69
+ def norm(self, norm: colors.Normalize | str | None) -> None: ...
70
+ def set_norm(self, norm: colors.Normalize | str | None) -> None: ...
71
+ def autoscale(self) -> None: ...
72
+ def autoscale_None(self) -> None: ...
73
+
74
+
75
+ class _ScalarMappable(_ColorizerInterface):
76
+ def __init__(
77
+ self,
78
+ norm: colors.Normalize | None = ...,
79
+ cmap: str | colors.Colormap | None = ...,
80
+ *,
81
+ colorizer: Colorizer | None = ...,
82
+ **kwargs
83
+ ) -> None: ...
84
+ def set_array(self, A: ArrayLike | None) -> None: ...
85
+ def get_array(self) -> np.ndarray | None: ...
86
+ def changed(self) -> None: ...
87
+
88
+
89
+ class ColorizingArtist(_ScalarMappable, artist.Artist):
90
+ callbacks: cbook.CallbackRegistry
91
+ def __init__(
92
+ self,
93
+ colorizer: Colorizer,
94
+ **kwargs
95
+ ) -> None: ...
96
+ def set_array(self, A: ArrayLike | None) -> None: ...
97
+ def get_array(self) -> np.ndarray | None: ...
98
+ def changed(self) -> None: ...
99
+ @property
100
+ def colorizer(self) -> Colorizer: ...
101
+ @colorizer.setter
102
+ def colorizer(self, cl: Colorizer) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py ADDED
The diff for this file is too large to render. See raw diff
 
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
2
+ from matplotlib import cbook, scale
3
+ import re
4
+
5
+ from typing import Any, Literal, overload
6
+ from .typing import ColorType
7
+
8
+ import numpy as np
9
+ from numpy.typing import ArrayLike
10
+
11
+ # Explicitly export colors dictionaries which are imported in the impl
12
+ BASE_COLORS: dict[str, ColorType]
13
+ CSS4_COLORS: dict[str, ColorType]
14
+ TABLEAU_COLORS: dict[str, ColorType]
15
+ XKCD_COLORS: dict[str, ColorType]
16
+
17
+ class _ColorMapping(dict[str, ColorType]):
18
+ cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]]
19
+ def __init__(self, mapping) -> None: ...
20
+ def __setitem__(self, key, value) -> None: ...
21
+ def __delitem__(self, key) -> None: ...
22
+
23
+ def get_named_colors_mapping() -> _ColorMapping: ...
24
+
25
+ class ColorSequenceRegistry(Mapping):
26
+ def __init__(self) -> None: ...
27
+ def __getitem__(self, item: str) -> list[ColorType]: ...
28
+ def __iter__(self) -> Iterator[str]: ...
29
+ def __len__(self) -> int: ...
30
+ def register(self, name: str, color_list: Iterable[ColorType]) -> None: ...
31
+ def unregister(self, name: str) -> None: ...
32
+
33
+ _color_sequences: ColorSequenceRegistry = ...
34
+
35
+ def is_color_like(c: Any) -> bool: ...
36
+ def same_color(c1: ColorType, c2: ColorType) -> bool: ...
37
+ def to_rgba(
38
+ c: ColorType, alpha: float | None = ...
39
+ ) -> tuple[float, float, float, float]: ...
40
+ def to_rgba_array(
41
+ c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ...
42
+ ) -> np.ndarray: ...
43
+ def to_rgb(c: ColorType) -> tuple[float, float, float]: ...
44
+ def to_hex(c: ColorType, keep_alpha: bool = ...) -> str: ...
45
+
46
+ cnames: dict[str, ColorType]
47
+ hexColorPattern: re.Pattern
48
+ rgb2hex = to_hex
49
+ hex2color = to_rgb
50
+
51
+ class ColorConverter:
52
+ colors: _ColorMapping
53
+ cache: dict[tuple[ColorType, float | None], tuple[float, float, float, float]]
54
+ @staticmethod
55
+ def to_rgb(c: ColorType) -> tuple[float, float, float]: ...
56
+ @staticmethod
57
+ def to_rgba(
58
+ c: ColorType, alpha: float | None = ...
59
+ ) -> tuple[float, float, float, float]: ...
60
+ @staticmethod
61
+ def to_rgba_array(
62
+ c: ColorType | ArrayLike, alpha: float | ArrayLike | None = ...
63
+ ) -> np.ndarray: ...
64
+
65
+ colorConverter: ColorConverter
66
+
67
+ class Colormap:
68
+ name: str
69
+ N: int
70
+ colorbar_extend: bool
71
+ def __init__(self, name: str, N: int = ...) -> None: ...
72
+ @overload
73
+ def __call__(
74
+ self, X: Sequence[float] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ...
75
+ ) -> np.ndarray: ...
76
+ @overload
77
+ def __call__(
78
+ self, X: float, alpha: float | None = ..., bytes: bool = ...
79
+ ) -> tuple[float, float, float, float]: ...
80
+ @overload
81
+ def __call__(
82
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ...
83
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
84
+ def __copy__(self) -> Colormap: ...
85
+ def __eq__(self, other: object) -> bool: ...
86
+ def get_bad(self) -> np.ndarray: ...
87
+ def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
88
+ def get_under(self) -> np.ndarray: ...
89
+ def set_under(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
90
+ def get_over(self) -> np.ndarray: ...
91
+ def set_over(self, color: ColorType = ..., alpha: float | None = ...) -> None: ...
92
+ def set_extremes(
93
+ self,
94
+ *,
95
+ bad: ColorType | None = ...,
96
+ under: ColorType | None = ...,
97
+ over: ColorType | None = ...
98
+ ) -> None: ...
99
+ def with_extremes(
100
+ self,
101
+ *,
102
+ bad: ColorType | None = ...,
103
+ under: ColorType | None = ...,
104
+ over: ColorType | None = ...
105
+ ) -> Colormap: ...
106
+ def is_gray(self) -> bool: ...
107
+ def resampled(self, lutsize: int) -> Colormap: ...
108
+ def reversed(self, name: str | None = ...) -> Colormap: ...
109
+ def _repr_html_(self) -> str: ...
110
+ def _repr_png_(self) -> bytes: ...
111
+ def copy(self) -> Colormap: ...
112
+
113
+ class LinearSegmentedColormap(Colormap):
114
+ monochrome: bool
115
+ def __init__(
116
+ self,
117
+ name: str,
118
+ segmentdata: dict[
119
+ Literal["red", "green", "blue", "alpha"], Sequence[tuple[float, ...]]
120
+ ],
121
+ N: int = ...,
122
+ gamma: float = ...,
123
+ ) -> None: ...
124
+ def set_gamma(self, gamma: float) -> None: ...
125
+ @staticmethod
126
+ def from_list(
127
+ name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ...
128
+ ) -> LinearSegmentedColormap: ...
129
+ def resampled(self, lutsize: int) -> LinearSegmentedColormap: ...
130
+ def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ...
131
+
132
+ class ListedColormap(Colormap):
133
+ monochrome: bool
134
+ colors: ArrayLike | ColorType
135
+ def __init__(
136
+ self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ...
137
+ ) -> None: ...
138
+ def resampled(self, lutsize: int) -> ListedColormap: ...
139
+ def reversed(self, name: str | None = ...) -> ListedColormap: ...
140
+
141
+ class MultivarColormap:
142
+ name: str
143
+ n_variates: int
144
+ def __init__(self, colormaps: list[Colormap], combination_mode: Literal['sRGB_add', 'sRGB_sub'], name: str = ...) -> None: ...
145
+ @overload
146
+ def __call__(
147
+ self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ...
148
+ ) -> np.ndarray: ...
149
+ @overload
150
+ def __call__(
151
+ self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ..., clip: bool = ...
152
+ ) -> tuple[float, float, float, float]: ...
153
+ @overload
154
+ def __call__(
155
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ...
156
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
157
+ def copy(self) -> MultivarColormap: ...
158
+ def __copy__(self) -> MultivarColormap: ...
159
+ def __eq__(self, other: Any) -> bool: ...
160
+ def __getitem__(self, item: int) -> Colormap: ...
161
+ def __iter__(self) -> Iterator[Colormap]: ...
162
+ def __len__(self) -> int: ...
163
+ def get_bad(self) -> np.ndarray: ...
164
+ def resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ...
165
+ def with_extremes(
166
+ self,
167
+ *,
168
+ bad: ColorType | None = ...,
169
+ under: Sequence[ColorType] | None = ...,
170
+ over: Sequence[ColorType] | None = ...
171
+ ) -> MultivarColormap: ...
172
+ @property
173
+ def combination_mode(self) -> str: ...
174
+ def _repr_html_(self) -> str: ...
175
+ def _repr_png_(self) -> bytes: ...
176
+
177
+ class BivarColormap:
178
+ name: str
179
+ N: int
180
+ M: int
181
+ n_variates: int
182
+ def __init__(
183
+ self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
184
+ origin: Sequence[float] = ..., name: str = ...
185
+ ) -> None: ...
186
+ @overload
187
+ def __call__(
188
+ self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ...
189
+ ) -> np.ndarray: ...
190
+ @overload
191
+ def __call__(
192
+ self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ...
193
+ ) -> tuple[float, float, float, float]: ...
194
+ @overload
195
+ def __call__(
196
+ self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ...
197
+ ) -> tuple[float, float, float, float] | np.ndarray: ...
198
+ @property
199
+ def lut(self) -> np.ndarray: ...
200
+ @property
201
+ def shape(self) -> str: ...
202
+ @property
203
+ def origin(self) -> tuple[float, float]: ...
204
+ def copy(self) -> BivarColormap: ...
205
+ def __copy__(self) -> BivarColormap: ...
206
+ def __getitem__(self, item: int) -> Colormap: ...
207
+ def __eq__(self, other: Any) -> bool: ...
208
+ def get_bad(self) -> np.ndarray: ...
209
+ def get_outside(self) -> np.ndarray: ...
210
+ def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ...
211
+ def transposed(self) -> BivarColormap: ...
212
+ def reversed(self, axis_0: bool = ..., axis_1: bool = ...) -> BivarColormap: ...
213
+ def with_extremes(
214
+ self,
215
+ *,
216
+ bad: ColorType | None = ...,
217
+ outside: ColorType | None = ...,
218
+ shape: str | None = ...,
219
+ origin: None | Sequence[float] = ...,
220
+ ) -> MultivarColormap: ...
221
+ def _repr_html_(self) -> str: ...
222
+ def _repr_png_(self) -> bytes: ...
223
+
224
+ class SegmentedBivarColormap(BivarColormap):
225
+ def __init__(
226
+ self, patch: np.ndarray, N: int = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
227
+ origin: Sequence[float] = ..., name: str = ...
228
+ ) -> None: ...
229
+
230
+ class BivarColormapFromImage(BivarColormap):
231
+ def __init__(
232
+ self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ...,
233
+ origin: Sequence[float] = ..., name: str = ...
234
+ ) -> None: ...
235
+
236
+ class Normalize:
237
+ callbacks: cbook.CallbackRegistry
238
+ def __init__(
239
+ self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ...
240
+ ) -> None: ...
241
+ @property
242
+ def vmin(self) -> float | None: ...
243
+ @vmin.setter
244
+ def vmin(self, value: float | None) -> None: ...
245
+ @property
246
+ def vmax(self) -> float | None: ...
247
+ @vmax.setter
248
+ def vmax(self, value: float | None) -> None: ...
249
+ @property
250
+ def clip(self) -> bool: ...
251
+ @clip.setter
252
+ def clip(self, value: bool) -> None: ...
253
+ @staticmethod
254
+ def process_value(value: ArrayLike) -> tuple[np.ma.MaskedArray, bool]: ...
255
+ @overload
256
+ def __call__(self, value: float, clip: bool | None = ...) -> float: ...
257
+ @overload
258
+ def __call__(self, value: np.ndarray, clip: bool | None = ...) -> np.ma.MaskedArray: ...
259
+ @overload
260
+ def __call__(self, value: ArrayLike, clip: bool | None = ...) -> ArrayLike: ...
261
+ @overload
262
+ def inverse(self, value: float) -> float: ...
263
+ @overload
264
+ def inverse(self, value: np.ndarray) -> np.ma.MaskedArray: ...
265
+ @overload
266
+ def inverse(self, value: ArrayLike) -> ArrayLike: ...
267
+ def autoscale(self, A: ArrayLike) -> None: ...
268
+ def autoscale_None(self, A: ArrayLike) -> None: ...
269
+ def scaled(self) -> bool: ...
270
+
271
+ class TwoSlopeNorm(Normalize):
272
+ def __init__(
273
+ self, vcenter: float, vmin: float | None = ..., vmax: float | None = ...
274
+ ) -> None: ...
275
+ @property
276
+ def vcenter(self) -> float: ...
277
+ @vcenter.setter
278
+ def vcenter(self, value: float) -> None: ...
279
+ def autoscale_None(self, A: ArrayLike) -> None: ...
280
+
281
+ class CenteredNorm(Normalize):
282
+ def __init__(
283
+ self, vcenter: float = ..., halfrange: float | None = ..., clip: bool = ...
284
+ ) -> None: ...
285
+ @property
286
+ def vcenter(self) -> float: ...
287
+ @vcenter.setter
288
+ def vcenter(self, vcenter: float) -> None: ...
289
+ @property
290
+ def halfrange(self) -> float: ...
291
+ @halfrange.setter
292
+ def halfrange(self, halfrange: float) -> None: ...
293
+
294
+ @overload
295
+ def make_norm_from_scale(
296
+ scale_cls: type[scale.ScaleBase],
297
+ base_norm_cls: type[Normalize],
298
+ *,
299
+ init: Callable | None = ...
300
+ ) -> type[Normalize]: ...
301
+ @overload
302
+ def make_norm_from_scale(
303
+ scale_cls: type[scale.ScaleBase],
304
+ base_norm_cls: None = ...,
305
+ *,
306
+ init: Callable | None = ...
307
+ ) -> Callable[[type[Normalize]], type[Normalize]]: ...
308
+
309
+ class FuncNorm(Normalize):
310
+ def __init__(
311
+ self,
312
+ functions: tuple[Callable, Callable],
313
+ vmin: float | None = ...,
314
+ vmax: float | None = ...,
315
+ clip: bool = ...,
316
+ ) -> None: ...
317
+ class LogNorm(Normalize): ...
318
+
319
+ class SymLogNorm(Normalize):
320
+ def __init__(
321
+ self,
322
+ linthresh: float,
323
+ linscale: float = ...,
324
+ vmin: float | None = ...,
325
+ vmax: float | None = ...,
326
+ clip: bool = ...,
327
+ *,
328
+ base: float = ...,
329
+ ) -> None: ...
330
+ @property
331
+ def linthresh(self) -> float: ...
332
+ @linthresh.setter
333
+ def linthresh(self, value: float) -> None: ...
334
+
335
+ class AsinhNorm(Normalize):
336
+ def __init__(
337
+ self,
338
+ linear_width: float = ...,
339
+ vmin: float | None = ...,
340
+ vmax: float | None = ...,
341
+ clip: bool = ...,
342
+ ) -> None: ...
343
+ @property
344
+ def linear_width(self) -> float: ...
345
+ @linear_width.setter
346
+ def linear_width(self, value: float) -> None: ...
347
+
348
+ class PowerNorm(Normalize):
349
+ gamma: float
350
+ def __init__(
351
+ self,
352
+ gamma: float,
353
+ vmin: float | None = ...,
354
+ vmax: float | None = ...,
355
+ clip: bool = ...,
356
+ ) -> None: ...
357
+
358
+ class BoundaryNorm(Normalize):
359
+ boundaries: np.ndarray
360
+ N: int
361
+ Ncmap: int
362
+ extend: Literal["neither", "both", "min", "max"]
363
+ def __init__(
364
+ self,
365
+ boundaries: ArrayLike,
366
+ ncolors: int,
367
+ clip: bool = ...,
368
+ *,
369
+ extend: Literal["neither", "both", "min", "max"] = ...
370
+ ) -> None: ...
371
+
372
+ class NoNorm(Normalize): ...
373
+
374
+ def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ...
375
+ def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ...
376
+
377
+ class LightSource:
378
+ azdeg: float
379
+ altdeg: float
380
+ hsv_min_val: float
381
+ hsv_max_val: float
382
+ hsv_min_sat: float
383
+ hsv_max_sat: float
384
+ def __init__(
385
+ self,
386
+ azdeg: float = ...,
387
+ altdeg: float = ...,
388
+ hsv_min_val: float = ...,
389
+ hsv_max_val: float = ...,
390
+ hsv_min_sat: float = ...,
391
+ hsv_max_sat: float = ...,
392
+ ) -> None: ...
393
+ @property
394
+ def direction(self) -> np.ndarray: ...
395
+ def hillshade(
396
+ self,
397
+ elevation: ArrayLike,
398
+ vert_exag: float = ...,
399
+ dx: float = ...,
400
+ dy: float = ...,
401
+ fraction: float = ...,
402
+ ) -> np.ndarray: ...
403
+ def shade_normals(
404
+ self, normals: np.ndarray, fraction: float = ...
405
+ ) -> np.ndarray: ...
406
+ def shade(
407
+ self,
408
+ data: ArrayLike,
409
+ cmap: Colormap,
410
+ norm: Normalize | None = ...,
411
+ blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ...,
412
+ vmin: float | None = ...,
413
+ vmax: float | None = ...,
414
+ vert_exag: float = ...,
415
+ dx: float = ...,
416
+ dy: float = ...,
417
+ fraction: float = ...,
418
+ **kwargs
419
+ ) -> np.ndarray: ...
420
+ def shade_rgb(
421
+ self,
422
+ rgb: ArrayLike,
423
+ elevation: ArrayLike,
424
+ fraction: float = ...,
425
+ blend_mode: Literal["hsv", "overlay", "soft"] | Callable = ...,
426
+ vert_exag: float = ...,
427
+ dx: float = ...,
428
+ dy: float = ...,
429
+ **kwargs
430
+ ) -> np.ndarray: ...
431
+ def blend_hsv(
432
+ self,
433
+ rgb: ArrayLike,
434
+ intensity: ArrayLike,
435
+ hsv_max_sat: float | None = ...,
436
+ hsv_max_val: float | None = ...,
437
+ hsv_min_val: float | None = ...,
438
+ hsv_min_sat: float | None = ...,
439
+ ) -> ArrayLike: ...
440
+ def blend_soft_light(
441
+ self, rgb: np.ndarray, intensity: np.ndarray
442
+ ) -> np.ndarray: ...
443
+ def blend_overlay(self, rgb: np.ndarray, intensity: np.ndarray) -> np.ndarray: ...
444
+
445
+ def from_levels_and_colors(
446
+ levels: Sequence[float],
447
+ colors: Sequence[ColorType],
448
+ extend: Literal["neither", "min", "max", "both"] = ...,
449
+ ) -> tuple[ListedColormap, BoundaryNorm]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib import cbook
2
+ from matplotlib.artist import Artist
3
+
4
+
5
+ class Container(tuple):
6
+ """
7
+ Base class for containers.
8
+
9
+ Containers are classes that collect semantically related Artists such as
10
+ the bars of a bar plot.
11
+ """
12
+
13
+ def __repr__(self):
14
+ return f"<{type(self).__name__} object of {len(self)} artists>"
15
+
16
+ def __new__(cls, *args, **kwargs):
17
+ return tuple.__new__(cls, args[0])
18
+
19
+ def __init__(self, kl, label=None):
20
+ self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
21
+ self._remove_method = None
22
+ self._label = str(label) if label is not None else None
23
+
24
+ def remove(self):
25
+ for c in cbook.flatten(
26
+ self, scalarp=lambda x: isinstance(x, Artist)):
27
+ if c is not None:
28
+ c.remove()
29
+ if self._remove_method:
30
+ self._remove_method(self)
31
+
32
+ def get_children(self):
33
+ return [child for child in cbook.flatten(self) if child is not None]
34
+
35
+ get_label = Artist.get_label
36
+ set_label = Artist.set_label
37
+ add_callback = Artist.add_callback
38
+ remove_callback = Artist.remove_callback
39
+ pchanged = Artist.pchanged
40
+
41
+
42
+ class BarContainer(Container):
43
+ """
44
+ Container for the artists of bar plots (e.g. created by `.Axes.bar`).
45
+
46
+ The container can be treated as a tuple of the *patches* themselves.
47
+ Additionally, you can access these and further parameters by the
48
+ attributes.
49
+
50
+ Attributes
51
+ ----------
52
+ patches : list of :class:`~matplotlib.patches.Rectangle`
53
+ The artists of the bars.
54
+
55
+ errorbar : None or :class:`~matplotlib.container.ErrorbarContainer`
56
+ A container for the error bar artists if error bars are present.
57
+ *None* otherwise.
58
+
59
+ datavalues : None or array-like
60
+ The underlying data values corresponding to the bars.
61
+
62
+ orientation : {'vertical', 'horizontal'}, default: None
63
+ If 'vertical', the bars are assumed to be vertical.
64
+ If 'horizontal', the bars are assumed to be horizontal.
65
+
66
+ """
67
+
68
+ def __init__(self, patches, errorbar=None, *, datavalues=None,
69
+ orientation=None, **kwargs):
70
+ self.patches = patches
71
+ self.errorbar = errorbar
72
+ self.datavalues = datavalues
73
+ self.orientation = orientation
74
+ super().__init__(patches, **kwargs)
75
+
76
+
77
+ class ErrorbarContainer(Container):
78
+ """
79
+ Container for the artists of error bars (e.g. created by `.Axes.errorbar`).
80
+
81
+ The container can be treated as the *lines* tuple itself.
82
+ Additionally, you can access these and further parameters by the
83
+ attributes.
84
+
85
+ Attributes
86
+ ----------
87
+ lines : tuple
88
+ Tuple of ``(data_line, caplines, barlinecols)``.
89
+
90
+ - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers
91
+ and/or line.
92
+ - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error
93
+ bar caps.
94
+ - barlinecols : A tuple of `~matplotlib.collections.LineCollection` with the
95
+ horizontal and vertical error ranges.
96
+
97
+ has_xerr, has_yerr : bool
98
+ ``True`` if the errorbar has x/y errors.
99
+
100
+ """
101
+
102
+ def __init__(self, lines, has_xerr=False, has_yerr=False, **kwargs):
103
+ self.lines = lines
104
+ self.has_xerr = has_xerr
105
+ self.has_yerr = has_yerr
106
+ super().__init__(lines, **kwargs)
107
+
108
+
109
+ class StemContainer(Container):
110
+ """
111
+ Container for the artists created in a :meth:`.Axes.stem` plot.
112
+
113
+ The container can be treated like a namedtuple ``(markerline, stemlines,
114
+ baseline)``.
115
+
116
+ Attributes
117
+ ----------
118
+ markerline : `~matplotlib.lines.Line2D`
119
+ The artist of the markers at the stem heads.
120
+
121
+ stemlines : `~matplotlib.collections.LineCollection`
122
+ The artists of the vertical lines for all stems.
123
+
124
+ baseline : `~matplotlib.lines.Line2D`
125
+ The artist of the horizontal baseline.
126
+ """
127
+ def __init__(self, markerline_stemlines_baseline, **kwargs):
128
+ """
129
+ Parameters
130
+ ----------
131
+ markerline_stemlines_baseline : tuple
132
+ Tuple of ``(markerline, stemlines, baseline)``.
133
+ ``markerline`` contains the `.Line2D` of the markers,
134
+ ``stemlines`` is a `.LineCollection` of the main lines,
135
+ ``baseline`` is the `.Line2D` of the baseline.
136
+ """
137
+ markerline, stemlines, baseline = markerline_stemlines_baseline
138
+ self.markerline = markerline
139
+ self.stemlines = stemlines
140
+ self.baseline = baseline
141
+ super().__init__(markerline_stemlines_baseline, **kwargs)
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib.artist import Artist
2
+ from matplotlib.lines import Line2D
3
+ from matplotlib.collections import LineCollection
4
+ from matplotlib.patches import Rectangle
5
+
6
+ from collections.abc import Callable
7
+ from typing import Any, Literal
8
+ from numpy.typing import ArrayLike
9
+
10
+ class Container(tuple):
11
+ def __new__(cls, *args, **kwargs): ...
12
+ def __init__(self, kl, label: Any | None = ...) -> None: ...
13
+ def remove(self) -> None: ...
14
+ def get_children(self) -> list[Artist]: ...
15
+ def get_label(self) -> str | None: ...
16
+ def set_label(self, s: Any) -> None: ...
17
+ def add_callback(self, func: Callable[[Artist], Any]) -> int: ...
18
+ def remove_callback(self, oid: int) -> None: ...
19
+ def pchanged(self) -> None: ...
20
+
21
+ class BarContainer(Container):
22
+ patches: list[Rectangle]
23
+ errorbar: None | ErrorbarContainer
24
+ datavalues: None | ArrayLike
25
+ orientation: None | Literal["vertical", "horizontal"]
26
+ def __init__(
27
+ self,
28
+ patches: list[Rectangle],
29
+ errorbar: ErrorbarContainer | None = ...,
30
+ *,
31
+ datavalues: ArrayLike | None = ...,
32
+ orientation: Literal["vertical", "horizontal"] | None = ...,
33
+ **kwargs
34
+ ) -> None: ...
35
+
36
+ class ErrorbarContainer(Container):
37
+ lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]]
38
+ has_xerr: bool
39
+ has_yerr: bool
40
+ def __init__(
41
+ self,
42
+ lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]],
43
+ has_xerr: bool = ...,
44
+ has_yerr: bool = ...,
45
+ **kwargs
46
+ ) -> None: ...
47
+
48
+ class StemContainer(Container):
49
+ markerline: Line2D
50
+ stemlines: LineCollection
51
+ baseline: Line2D
52
+ def __init__(
53
+ self,
54
+ markerline_stemlines_baseline: tuple[Line2D, LineCollection, Line2D],
55
+ **kwargs
56
+ ) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py ADDED
@@ -0,0 +1,1702 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Classes to support contour plotting and labelling for the Axes class.
3
+ """
4
+
5
+ from contextlib import ExitStack
6
+ import functools
7
+ import math
8
+ from numbers import Integral
9
+
10
+ import numpy as np
11
+ from numpy import ma
12
+
13
+ import matplotlib as mpl
14
+ from matplotlib import _api, _docstring
15
+ from matplotlib.backend_bases import MouseButton
16
+ from matplotlib.lines import Line2D
17
+ from matplotlib.path import Path
18
+ from matplotlib.text import Text
19
+ import matplotlib.ticker as ticker
20
+ import matplotlib.cm as cm
21
+ import matplotlib.colors as mcolors
22
+ import matplotlib.collections as mcoll
23
+ import matplotlib.font_manager as font_manager
24
+ import matplotlib.cbook as cbook
25
+ import matplotlib.patches as mpatches
26
+ import matplotlib.transforms as mtransforms
27
+
28
+
29
+ def _contour_labeler_event_handler(cs, inline, inline_spacing, event):
30
+ canvas = cs.axes.get_figure(root=True).canvas
31
+ is_button = event.name == "button_press_event"
32
+ is_key = event.name == "key_press_event"
33
+ # Quit (even if not in infinite mode; this is consistent with
34
+ # MATLAB and sometimes quite useful, but will require the user to
35
+ # test how many points were actually returned before using data).
36
+ if (is_button and event.button == MouseButton.MIDDLE
37
+ or is_key and event.key in ["escape", "enter"]):
38
+ canvas.stop_event_loop()
39
+ # Pop last click.
40
+ elif (is_button and event.button == MouseButton.RIGHT
41
+ or is_key and event.key in ["backspace", "delete"]):
42
+ # Unfortunately, if one is doing inline labels, then there is currently
43
+ # no way to fix the broken contour - once humpty-dumpty is broken, he
44
+ # can't be put back together. In inline mode, this does nothing.
45
+ if not inline:
46
+ cs.pop_label()
47
+ canvas.draw()
48
+ # Add new click.
49
+ elif (is_button and event.button == MouseButton.LEFT
50
+ # On macOS/gtk, some keys return None.
51
+ or is_key and event.key is not None):
52
+ if cs.axes.contains(event)[0]:
53
+ cs.add_label_near(event.x, event.y, transform=False,
54
+ inline=inline, inline_spacing=inline_spacing)
55
+ canvas.draw()
56
+
57
+
58
+ class ContourLabeler:
59
+ """Mixin to provide labelling capability to `.ContourSet`."""
60
+
61
+ def clabel(self, levels=None, *,
62
+ fontsize=None, inline=True, inline_spacing=5, fmt=None,
63
+ colors=None, use_clabeltext=False, manual=False,
64
+ rightside_up=True, zorder=None):
65
+ """
66
+ Label a contour plot.
67
+
68
+ Adds labels to line contours in this `.ContourSet` (which inherits from
69
+ this mixin class).
70
+
71
+ Parameters
72
+ ----------
73
+ levels : array-like, optional
74
+ A list of level values, that should be labeled. The list must be
75
+ a subset of ``cs.levels``. If not given, all levels are labeled.
76
+
77
+ fontsize : str or float, default: :rc:`font.size`
78
+ Size in points or relative size e.g., 'smaller', 'x-large'.
79
+ See `.Text.set_size` for accepted string values.
80
+
81
+ colors : :mpltype:`color` or colors or None, default: None
82
+ The label colors:
83
+
84
+ - If *None*, the color of each label matches the color of
85
+ the corresponding contour.
86
+
87
+ - If one string color, e.g., *colors* = 'r' or *colors* =
88
+ 'red', all labels will be plotted in this color.
89
+
90
+ - If a tuple of colors (string, float, RGB, etc), different labels
91
+ will be plotted in different colors in the order specified.
92
+
93
+ inline : bool, default: True
94
+ If ``True`` the underlying contour is removed where the label is
95
+ placed.
96
+
97
+ inline_spacing : float, default: 5
98
+ Space in pixels to leave on each side of label when placing inline.
99
+
100
+ This spacing will be exact for labels at locations where the
101
+ contour is straight, less so for labels on curved contours.
102
+
103
+ fmt : `.Formatter` or str or callable or dict, optional
104
+ How the levels are formatted:
105
+
106
+ - If a `.Formatter`, it is used to format all levels at once, using
107
+ its `.Formatter.format_ticks` method.
108
+ - If a str, it is interpreted as a %-style format string.
109
+ - If a callable, it is called with one level at a time and should
110
+ return the corresponding label.
111
+ - If a dict, it should directly map levels to labels.
112
+
113
+ The default is to use a standard `.ScalarFormatter`.
114
+
115
+ manual : bool or iterable, default: False
116
+ If ``True``, contour labels will be placed manually using
117
+ mouse clicks. Click the first button near a contour to
118
+ add a label, click the second button (or potentially both
119
+ mouse buttons at once) to finish adding labels. The third
120
+ button can be used to remove the last label added, but
121
+ only if labels are not inline. Alternatively, the keyboard
122
+ can be used to select label locations (enter to end label
123
+ placement, delete or backspace act like the third mouse button,
124
+ and any other key will select a label location).
125
+
126
+ *manual* can also be an iterable object of (x, y) tuples.
127
+ Contour labels will be created as if mouse is clicked at each
128
+ (x, y) position.
129
+
130
+ rightside_up : bool, default: True
131
+ If ``True``, label rotations will always be plus
132
+ or minus 90 degrees from level.
133
+
134
+ use_clabeltext : bool, default: False
135
+ If ``True``, use `.Text.set_transform_rotates_text` to ensure that
136
+ label rotation is updated whenever the Axes aspect changes.
137
+
138
+ zorder : float or None, default: ``(2 + contour.get_zorder())``
139
+ zorder of the contour labels.
140
+
141
+ Returns
142
+ -------
143
+ labels
144
+ A list of `.Text` instances for the labels.
145
+ """
146
+
147
+ # Based on the input arguments, clabel() adds a list of "label
148
+ # specific" attributes to the ContourSet object. These attributes are
149
+ # all of the form label* and names should be fairly self explanatory.
150
+ #
151
+ # Once these attributes are set, clabel passes control to the labels()
152
+ # method (for automatic label placement) or blocking_input_loop and
153
+ # _contour_labeler_event_handler (for manual label placement).
154
+
155
+ if fmt is None:
156
+ fmt = ticker.ScalarFormatter(useOffset=False)
157
+ fmt.create_dummy_axis()
158
+ self.labelFmt = fmt
159
+ self._use_clabeltext = use_clabeltext
160
+ self.labelManual = manual
161
+ self.rightside_up = rightside_up
162
+ self._clabel_zorder = 2 + self.get_zorder() if zorder is None else zorder
163
+
164
+ if levels is None:
165
+ levels = self.levels
166
+ indices = list(range(len(self.cvalues)))
167
+ else:
168
+ levlabs = list(levels)
169
+ indices, levels = [], []
170
+ for i, lev in enumerate(self.levels):
171
+ if lev in levlabs:
172
+ indices.append(i)
173
+ levels.append(lev)
174
+ if len(levels) < len(levlabs):
175
+ raise ValueError(f"Specified levels {levlabs} don't match "
176
+ f"available levels {self.levels}")
177
+ self.labelLevelList = levels
178
+ self.labelIndiceList = indices
179
+
180
+ self._label_font_props = font_manager.FontProperties(size=fontsize)
181
+
182
+ if colors is None:
183
+ self.labelMappable = self
184
+ self.labelCValueList = np.take(self.cvalues, self.labelIndiceList)
185
+ else:
186
+ cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList))
187
+ self.labelCValueList = list(range(len(self.labelLevelList)))
188
+ self.labelMappable = cm.ScalarMappable(cmap=cmap,
189
+ norm=mcolors.NoNorm())
190
+
191
+ self.labelXYs = []
192
+
193
+ if np.iterable(manual):
194
+ for x, y in manual:
195
+ self.add_label_near(x, y, inline, inline_spacing)
196
+ elif manual:
197
+ print('Select label locations manually using first mouse button.')
198
+ print('End manual selection with second mouse button.')
199
+ if not inline:
200
+ print('Remove last label by clicking third mouse button.')
201
+ mpl._blocking_input.blocking_input_loop(
202
+ self.axes.get_figure(root=True),
203
+ ["button_press_event", "key_press_event"],
204
+ timeout=-1, handler=functools.partial(
205
+ _contour_labeler_event_handler,
206
+ self, inline, inline_spacing))
207
+ else:
208
+ self.labels(inline, inline_spacing)
209
+
210
+ return cbook.silent_list('text.Text', self.labelTexts)
211
+
212
+ def print_label(self, linecontour, labelwidth):
213
+ """Return whether a contour is long enough to hold a label."""
214
+ return (len(linecontour) > 10 * labelwidth
215
+ or (len(linecontour)
216
+ and (np.ptp(linecontour, axis=0) > 1.2 * labelwidth).any()))
217
+
218
+ def too_close(self, x, y, lw):
219
+ """Return whether a label is already near this location."""
220
+ thresh = (1.2 * lw) ** 2
221
+ return any((x - loc[0]) ** 2 + (y - loc[1]) ** 2 < thresh
222
+ for loc in self.labelXYs)
223
+
224
+ def _get_nth_label_width(self, nth):
225
+ """Return the width of the *nth* label, in pixels."""
226
+ fig = self.axes.get_figure(root=False)
227
+ renderer = fig.get_figure(root=True)._get_renderer()
228
+ return (Text(0, 0,
229
+ self.get_text(self.labelLevelList[nth], self.labelFmt),
230
+ figure=fig, fontproperties=self._label_font_props)
231
+ .get_window_extent(renderer).width)
232
+
233
+ def get_text(self, lev, fmt):
234
+ """Get the text of the label."""
235
+ if isinstance(lev, str):
236
+ return lev
237
+ elif isinstance(fmt, dict):
238
+ return fmt.get(lev, '%1.3f')
239
+ elif callable(getattr(fmt, "format_ticks", None)):
240
+ return fmt.format_ticks([*self.labelLevelList, lev])[-1]
241
+ elif callable(fmt):
242
+ return fmt(lev)
243
+ else:
244
+ return fmt % lev
245
+
246
+ def locate_label(self, linecontour, labelwidth):
247
+ """
248
+ Find good place to draw a label (relatively flat part of the contour).
249
+ """
250
+ ctr_size = len(linecontour)
251
+ n_blocks = int(np.ceil(ctr_size / labelwidth)) if labelwidth > 1 else 1
252
+ block_size = ctr_size if n_blocks == 1 else int(labelwidth)
253
+ # Split contour into blocks of length ``block_size``, filling the last
254
+ # block by cycling the contour start (per `np.resize` semantics). (Due
255
+ # to cycling, the index returned is taken modulo ctr_size.)
256
+ xx = np.resize(linecontour[:, 0], (n_blocks, block_size))
257
+ yy = np.resize(linecontour[:, 1], (n_blocks, block_size))
258
+ yfirst = yy[:, :1]
259
+ ylast = yy[:, -1:]
260
+ xfirst = xx[:, :1]
261
+ xlast = xx[:, -1:]
262
+ s = (yfirst - yy) * (xlast - xfirst) - (xfirst - xx) * (ylast - yfirst)
263
+ l = np.hypot(xlast - xfirst, ylast - yfirst)
264
+ # Ignore warning that divide by zero throws, as this is a valid option
265
+ with np.errstate(divide='ignore', invalid='ignore'):
266
+ distances = (abs(s) / l).sum(axis=-1)
267
+ # Labels are drawn in the middle of the block (``hbsize``) where the
268
+ # contour is the closest (per ``distances``) to a straight line, but
269
+ # not `too_close()` to a preexisting label.
270
+ hbsize = block_size // 2
271
+ adist = np.argsort(distances)
272
+ # If all candidates are `too_close()`, go back to the straightest part
273
+ # (``adist[0]``).
274
+ for idx in np.append(adist, adist[0]):
275
+ x, y = xx[idx, hbsize], yy[idx, hbsize]
276
+ if not self.too_close(x, y, labelwidth):
277
+ break
278
+ return x, y, (idx * block_size + hbsize) % ctr_size
279
+
280
+ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=5):
281
+ """
282
+ Prepare for insertion of a label at index *idx* of *path*.
283
+
284
+ Parameters
285
+ ----------
286
+ path : Path
287
+ The path where the label will be inserted, in data space.
288
+ idx : int
289
+ The vertex index after which the label will be inserted.
290
+ screen_pos : (float, float)
291
+ The position where the label will be inserted, in screen space.
292
+ lw : float
293
+ The label width, in screen space.
294
+ spacing : float
295
+ Extra spacing around the label, in screen space.
296
+
297
+ Returns
298
+ -------
299
+ path : Path
300
+ The path, broken so that the label can be drawn over it.
301
+ angle : float
302
+ The rotation of the label.
303
+
304
+ Notes
305
+ -----
306
+ Both tasks are done together to avoid calculating path lengths multiple times,
307
+ which is relatively costly.
308
+
309
+ The method used here involves computing the path length along the contour in
310
+ pixel coordinates and then looking (label width / 2) away from central point to
311
+ determine rotation and then to break contour if desired. The extra spacing is
312
+ taken into account when breaking the path, but not when computing the angle.
313
+ """
314
+ xys = path.vertices
315
+ codes = path.codes
316
+
317
+ # Insert a vertex at idx/pos (converting back to data space), if there isn't yet
318
+ # a vertex there. With infinite precision one could also always insert the
319
+ # extra vertex (it will get masked out by the label below anyways), but floating
320
+ # point inaccuracies (the point can have undergone a data->screen->data
321
+ # transform loop) can slightly shift the point and e.g. shift the angle computed
322
+ # below from exactly zero to nonzero.
323
+ pos = self.get_transform().inverted().transform(screen_pos)
324
+ if not np.allclose(pos, xys[idx]):
325
+ xys = np.insert(xys, idx, pos, axis=0)
326
+ codes = np.insert(codes, idx, Path.LINETO)
327
+
328
+ # Find the connected component where the label will be inserted. Note that a
329
+ # path always starts with a MOVETO, and we consider there's an implicit
330
+ # MOVETO (closing the last path) at the end.
331
+ movetos = (codes == Path.MOVETO).nonzero()[0]
332
+ start = movetos[movetos <= idx][-1]
333
+ try:
334
+ stop = movetos[movetos > idx][0]
335
+ except IndexError:
336
+ stop = len(codes)
337
+
338
+ # Restrict ourselves to the connected component.
339
+ cc_xys = xys[start:stop]
340
+ idx -= start
341
+
342
+ # If the path is closed, rotate it s.t. it starts at the label.
343
+ is_closed_path = codes[stop - 1] == Path.CLOSEPOLY
344
+ if is_closed_path:
345
+ cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]])
346
+ idx = 0
347
+
348
+ # Like np.interp, but additionally vectorized over fp.
349
+ def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T]
350
+
351
+ # Use cumulative path lengths ("cpl") as curvilinear coordinate along contour.
352
+ screen_xys = self.get_transform().transform(cc_xys)
353
+ path_cpls = np.insert(
354
+ np.cumsum(np.hypot(*np.diff(screen_xys, axis=0).T)), 0, 0)
355
+ path_cpls -= path_cpls[idx]
356
+
357
+ # Use linear interpolation to get end coordinates of label.
358
+ target_cpls = np.array([-lw/2, lw/2])
359
+ if is_closed_path: # For closed paths, target from the other end.
360
+ target_cpls[0] += (path_cpls[-1] - path_cpls[0])
361
+ (sx0, sx1), (sy0, sy1) = interp_vec(target_cpls, path_cpls, screen_xys)
362
+ angle = np.rad2deg(np.arctan2(sy1 - sy0, sx1 - sx0)) # Screen space.
363
+ if self.rightside_up: # Fix angle so text is never upside-down
364
+ angle = (angle + 90) % 180 - 90
365
+
366
+ target_cpls += [-spacing, +spacing] # Expand range by spacing.
367
+
368
+ # Get indices near points of interest; use -1 as out of bounds marker.
369
+ i0, i1 = np.interp(target_cpls, path_cpls, range(len(path_cpls)),
370
+ left=-1, right=-1)
371
+ i0 = math.floor(i0)
372
+ i1 = math.ceil(i1)
373
+ (x0, x1), (y0, y1) = interp_vec(target_cpls, path_cpls, cc_xys)
374
+
375
+ # Actually break contours (dropping zero-len parts).
376
+ new_xy_blocks = []
377
+ new_code_blocks = []
378
+ if is_closed_path:
379
+ if i0 != -1 and i1 != -1:
380
+ # This is probably wrong in the case that the entire contour would
381
+ # be discarded, but ensures that a valid path is returned and is
382
+ # consistent with behavior of mpl <3.8
383
+ points = cc_xys[i1:i0+1]
384
+ new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]])
385
+ nlines = len(points) + 1
386
+ new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines])
387
+ else:
388
+ if i0 != -1:
389
+ new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]])
390
+ new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 1)])
391
+ if i1 != -1:
392
+ new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:]])
393
+ new_code_blocks.extend([
394
+ [Path.MOVETO], [Path.LINETO] * (len(cc_xys) - i1)])
395
+
396
+ # Back to the full path.
397
+ xys = np.concatenate([xys[:start], *new_xy_blocks, xys[stop:]])
398
+ codes = np.concatenate([codes[:start], *new_code_blocks, codes[stop:]])
399
+
400
+ return angle, Path(xys, codes)
401
+
402
+ def add_label(self, x, y, rotation, lev, cvalue):
403
+ """Add a contour label, respecting whether *use_clabeltext* was set."""
404
+ data_x, data_y = self.axes.transData.inverted().transform((x, y))
405
+ t = Text(
406
+ data_x, data_y,
407
+ text=self.get_text(lev, self.labelFmt),
408
+ rotation=rotation,
409
+ horizontalalignment='center', verticalalignment='center',
410
+ zorder=self._clabel_zorder,
411
+ color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()),
412
+ fontproperties=self._label_font_props,
413
+ clip_box=self.axes.bbox)
414
+ if self._use_clabeltext:
415
+ data_rotation, = self.axes.transData.inverted().transform_angles(
416
+ [rotation], [[x, y]])
417
+ t.set(rotation=data_rotation, transform_rotates_text=True)
418
+ self.labelTexts.append(t)
419
+ self.labelCValues.append(cvalue)
420
+ self.labelXYs.append((x, y))
421
+ # Add label to plot here - useful for manual mode label selection
422
+ self.axes.add_artist(t)
423
+
424
+ def add_label_near(self, x, y, inline=True, inline_spacing=5,
425
+ transform=None):
426
+ """
427
+ Add a label near the point ``(x, y)``.
428
+
429
+ Parameters
430
+ ----------
431
+ x, y : float
432
+ The approximate location of the label.
433
+ inline : bool, default: True
434
+ If *True* remove the segment of the contour beneath the label.
435
+ inline_spacing : int, default: 5
436
+ Space in pixels to leave on each side of label when placing
437
+ inline. This spacing will be exact for labels at locations where
438
+ the contour is straight, less so for labels on curved contours.
439
+ transform : `.Transform` or `False`, default: ``self.axes.transData``
440
+ A transform applied to ``(x, y)`` before labeling. The default
441
+ causes ``(x, y)`` to be interpreted as data coordinates. `False`
442
+ is a synonym for `.IdentityTransform`; i.e. ``(x, y)`` should be
443
+ interpreted as display coordinates.
444
+ """
445
+
446
+ if transform is None:
447
+ transform = self.axes.transData
448
+ if transform:
449
+ x, y = transform.transform((x, y))
450
+
451
+ idx_level_min, idx_vtx_min, proj = self._find_nearest_contour(
452
+ (x, y), self.labelIndiceList)
453
+ path = self._paths[idx_level_min]
454
+ level = self.labelIndiceList.index(idx_level_min)
455
+ label_width = self._get_nth_label_width(level)
456
+ rotation, path = self._split_path_and_get_label_rotation(
457
+ path, idx_vtx_min, proj, label_width, inline_spacing)
458
+ self.add_label(*proj, rotation, self.labelLevelList[idx_level_min],
459
+ self.labelCValueList[idx_level_min])
460
+
461
+ if inline:
462
+ self._paths[idx_level_min] = path
463
+
464
+ def pop_label(self, index=-1):
465
+ """Defaults to removing last label, but any index can be supplied"""
466
+ self.labelCValues.pop(index)
467
+ t = self.labelTexts.pop(index)
468
+ t.remove()
469
+
470
+ def labels(self, inline, inline_spacing):
471
+ for idx, (icon, lev, cvalue) in enumerate(zip(
472
+ self.labelIndiceList,
473
+ self.labelLevelList,
474
+ self.labelCValueList,
475
+ )):
476
+ trans = self.get_transform()
477
+ label_width = self._get_nth_label_width(idx)
478
+ additions = []
479
+ for subpath in self._paths[icon]._iter_connected_components():
480
+ screen_xys = trans.transform(subpath.vertices)
481
+ # Check if long enough for a label
482
+ if self.print_label(screen_xys, label_width):
483
+ x, y, idx = self.locate_label(screen_xys, label_width)
484
+ rotation, path = self._split_path_and_get_label_rotation(
485
+ subpath, idx, (x, y),
486
+ label_width, inline_spacing)
487
+ self.add_label(x, y, rotation, lev, cvalue) # Really add label.
488
+ if inline: # If inline, add new contours
489
+ additions.append(path)
490
+ else: # If not adding label, keep old path
491
+ additions.append(subpath)
492
+ # After looping over all segments on a contour, replace old path by new one
493
+ # if inlining.
494
+ if inline:
495
+ self._paths[icon] = Path.make_compound_path(*additions)
496
+
497
+ def remove(self):
498
+ super().remove()
499
+ for text in self.labelTexts:
500
+ text.remove()
501
+
502
+
503
+ def _find_closest_point_on_path(xys, p):
504
+ """
505
+ Parameters
506
+ ----------
507
+ xys : (N, 2) array-like
508
+ Coordinates of vertices.
509
+ p : (float, float)
510
+ Coordinates of point.
511
+
512
+ Returns
513
+ -------
514
+ d2min : float
515
+ Minimum square distance of *p* to *xys*.
516
+ proj : (float, float)
517
+ Projection of *p* onto *xys*.
518
+ imin : (int, int)
519
+ Consecutive indices of vertices of segment in *xys* where *proj* is.
520
+ Segments are considered as including their end-points; i.e. if the
521
+ closest point on the path is a node in *xys* with index *i*, this
522
+ returns ``(i-1, i)``. For the special case where *xys* is a single
523
+ point, this returns ``(0, 0)``.
524
+ """
525
+ if len(xys) == 1:
526
+ return (((p - xys[0]) ** 2).sum(), xys[0], (0, 0))
527
+ dxys = xys[1:] - xys[:-1] # Individual segment vectors.
528
+ norms = (dxys ** 2).sum(axis=1)
529
+ norms[norms == 0] = 1 # For zero-length segment, replace 0/0 by 0/1.
530
+ rel_projs = np.clip( # Project onto each segment in relative 0-1 coords.
531
+ ((p - xys[:-1]) * dxys).sum(axis=1) / norms,
532
+ 0, 1)[:, None]
533
+ projs = xys[:-1] + rel_projs * dxys # Projs. onto each segment, in (x, y).
534
+ d2s = ((projs - p) ** 2).sum(axis=1) # Squared distances.
535
+ imin = np.argmin(d2s)
536
+ return (d2s[imin], projs[imin], (imin, imin+1))
537
+
538
+
539
+ _docstring.interpd.register(contour_set_attributes=r"""
540
+ Attributes
541
+ ----------
542
+ levels : array
543
+ The values of the contour levels.
544
+
545
+ layers : array
546
+ Same as levels for line contours; half-way between
547
+ levels for filled contours. See ``ContourSet._process_colors``.
548
+ """)
549
+
550
+
551
+ @_docstring.interpd
552
+ class ContourSet(ContourLabeler, mcoll.Collection):
553
+ """
554
+ Store a set of contour lines or filled regions.
555
+
556
+ User-callable method: `~.Axes.clabel`
557
+
558
+ Parameters
559
+ ----------
560
+ ax : `~matplotlib.axes.Axes`
561
+
562
+ levels : [level0, level1, ..., leveln]
563
+ A list of floating point numbers indicating the contour levels.
564
+
565
+ allsegs : [level0segs, level1segs, ...]
566
+ List of all the polygon segments for all the *levels*.
567
+ For contour lines ``len(allsegs) == len(levels)``, and for
568
+ filled contour regions ``len(allsegs) = len(levels)-1``. The lists
569
+ should look like ::
570
+
571
+ level0segs = [polygon0, polygon1, ...]
572
+ polygon0 = [[x0, y0], [x1, y1], ...]
573
+
574
+ allkinds : ``None`` or [level0kinds, level1kinds, ...]
575
+ Optional list of all the polygon vertex kinds (code types), as
576
+ described and used in Path. This is used to allow multiply-
577
+ connected paths such as holes within filled polygons.
578
+ If not ``None``, ``len(allkinds) == len(allsegs)``. The lists
579
+ should look like ::
580
+
581
+ level0kinds = [polygon0kinds, ...]
582
+ polygon0kinds = [vertexcode0, vertexcode1, ...]
583
+
584
+ If *allkinds* is not ``None``, usually all polygons for a
585
+ particular contour level are grouped together so that
586
+ ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``.
587
+
588
+ **kwargs
589
+ Keyword arguments are as described in the docstring of
590
+ `~.Axes.contour`.
591
+
592
+ %(contour_set_attributes)s
593
+ """
594
+
595
+ def __init__(self, ax, *args,
596
+ levels=None, filled=False, linewidths=None, linestyles=None,
597
+ hatches=(None,), alpha=None, origin=None, extent=None,
598
+ cmap=None, colors=None, norm=None, vmin=None, vmax=None,
599
+ colorizer=None, extend='neither', antialiased=None, nchunk=0,
600
+ locator=None, transform=None, negative_linestyles=None, clip_path=None,
601
+ **kwargs):
602
+ """
603
+ Draw contour lines or filled regions, depending on
604
+ whether keyword arg *filled* is ``False`` (default) or ``True``.
605
+
606
+ Call signature::
607
+
608
+ ContourSet(ax, levels, allsegs, [allkinds], **kwargs)
609
+
610
+ Parameters
611
+ ----------
612
+ ax : `~matplotlib.axes.Axes`
613
+ The `~.axes.Axes` object to draw on.
614
+
615
+ levels : [level0, level1, ..., leveln]
616
+ A list of floating point numbers indicating the contour
617
+ levels.
618
+
619
+ allsegs : [level0segs, level1segs, ...]
620
+ List of all the polygon segments for all the *levels*.
621
+ For contour lines ``len(allsegs) == len(levels)``, and for
622
+ filled contour regions ``len(allsegs) = len(levels)-1``. The lists
623
+ should look like ::
624
+
625
+ level0segs = [polygon0, polygon1, ...]
626
+ polygon0 = [[x0, y0], [x1, y1], ...]
627
+
628
+ allkinds : [level0kinds, level1kinds, ...], optional
629
+ Optional list of all the polygon vertex kinds (code types), as
630
+ described and used in Path. This is used to allow multiply-
631
+ connected paths such as holes within filled polygons.
632
+ If not ``None``, ``len(allkinds) == len(allsegs)``. The lists
633
+ should look like ::
634
+
635
+ level0kinds = [polygon0kinds, ...]
636
+ polygon0kinds = [vertexcode0, vertexcode1, ...]
637
+
638
+ If *allkinds* is not ``None``, usually all polygons for a
639
+ particular contour level are grouped together so that
640
+ ``level0segs = [polygon0]`` and ``level0kinds = [polygon0kinds]``.
641
+
642
+ **kwargs
643
+ Keyword arguments are as described in the docstring of
644
+ `~.Axes.contour`.
645
+ """
646
+ if antialiased is None and filled:
647
+ # Eliminate artifacts; we are not stroking the boundaries.
648
+ antialiased = False
649
+ # The default for line contours will be taken from the
650
+ # LineCollection default, which uses :rc:`lines.antialiased`.
651
+ super().__init__(
652
+ antialiaseds=antialiased,
653
+ alpha=alpha,
654
+ clip_path=clip_path,
655
+ transform=transform,
656
+ colorizer=colorizer,
657
+ )
658
+ self.axes = ax
659
+ self.levels = levels
660
+ self.filled = filled
661
+ self.hatches = hatches
662
+ self.origin = origin
663
+ self.extent = extent
664
+ self.colors = colors
665
+ self.extend = extend
666
+
667
+ self.nchunk = nchunk
668
+ self.locator = locator
669
+
670
+ if colorizer:
671
+ self._set_colorizer_check_keywords(colorizer, cmap=cmap,
672
+ norm=norm, vmin=vmin,
673
+ vmax=vmax, colors=colors)
674
+ norm = colorizer.norm
675
+ cmap = colorizer.cmap
676
+ if (isinstance(norm, mcolors.LogNorm)
677
+ or isinstance(self.locator, ticker.LogLocator)):
678
+ self.logscale = True
679
+ if norm is None:
680
+ norm = mcolors.LogNorm()
681
+ else:
682
+ self.logscale = False
683
+
684
+ _api.check_in_list([None, 'lower', 'upper', 'image'], origin=origin)
685
+ if self.extent is not None and len(self.extent) != 4:
686
+ raise ValueError(
687
+ "If given, 'extent' must be None or (x0, x1, y0, y1)")
688
+ if self.colors is not None and cmap is not None:
689
+ raise ValueError('Either colors or cmap must be None')
690
+ if self.origin == 'image':
691
+ self.origin = mpl.rcParams['image.origin']
692
+
693
+ self._orig_linestyles = linestyles # Only kept for user access.
694
+ self.negative_linestyles = negative_linestyles
695
+ # If negative_linestyles was not defined as a keyword argument, define
696
+ # negative_linestyles with rcParams
697
+ if self.negative_linestyles is None:
698
+ self.negative_linestyles = \
699
+ mpl.rcParams['contour.negative_linestyle']
700
+
701
+ kwargs = self._process_args(*args, **kwargs)
702
+ self._process_levels()
703
+
704
+ self._extend_min = self.extend in ['min', 'both']
705
+ self._extend_max = self.extend in ['max', 'both']
706
+ if self.colors is not None:
707
+ if mcolors.is_color_like(self.colors):
708
+ color_sequence = [self.colors]
709
+ else:
710
+ color_sequence = self.colors
711
+
712
+ ncolors = len(self.levels)
713
+ if self.filled:
714
+ ncolors -= 1
715
+ i0 = 0
716
+
717
+ # Handle the case where colors are given for the extended
718
+ # parts of the contour.
719
+
720
+ use_set_under_over = False
721
+ # if we are extending the lower end, and we've been given enough
722
+ # colors then skip the first color in the resulting cmap. For the
723
+ # extend_max case we don't need to worry about passing more colors
724
+ # than ncolors as ListedColormap will clip.
725
+ total_levels = (ncolors +
726
+ int(self._extend_min) +
727
+ int(self._extend_max))
728
+ if (len(color_sequence) == total_levels and
729
+ (self._extend_min or self._extend_max)):
730
+ use_set_under_over = True
731
+ if self._extend_min:
732
+ i0 = 1
733
+
734
+ cmap = mcolors.ListedColormap(color_sequence[i0:None], N=ncolors)
735
+
736
+ if use_set_under_over:
737
+ if self._extend_min:
738
+ cmap.set_under(color_sequence[0])
739
+ if self._extend_max:
740
+ cmap.set_over(color_sequence[-1])
741
+
742
+ # label lists must be initialized here
743
+ self.labelTexts = []
744
+ self.labelCValues = []
745
+
746
+ self.set_cmap(cmap)
747
+ if norm is not None:
748
+ self.set_norm(norm)
749
+ with self.norm.callbacks.blocked(signal="changed"):
750
+ if vmin is not None:
751
+ self.norm.vmin = vmin
752
+ if vmax is not None:
753
+ self.norm.vmax = vmax
754
+ self.norm._changed()
755
+ self._process_colors()
756
+
757
+ if self._paths is None:
758
+ self._paths = self._make_paths_from_contour_generator()
759
+
760
+ if self.filled:
761
+ if linewidths is not None:
762
+ _api.warn_external('linewidths is ignored by contourf')
763
+ # Lower and upper contour levels.
764
+ lowers, uppers = self._get_lowers_and_uppers()
765
+ self.set(
766
+ edgecolor="none",
767
+ # Default zorder taken from Collection
768
+ zorder=kwargs.pop("zorder", 1),
769
+ )
770
+
771
+ else:
772
+ self.set(
773
+ facecolor="none",
774
+ linewidths=self._process_linewidths(linewidths),
775
+ linestyle=self._process_linestyles(linestyles),
776
+ # Default zorder taken from LineCollection, which is higher
777
+ # than for filled contours so that lines are displayed on top.
778
+ zorder=kwargs.pop("zorder", 2),
779
+ label="_nolegend_",
780
+ )
781
+
782
+ self.axes.add_collection(self, autolim=False)
783
+ self.sticky_edges.x[:] = [self._mins[0], self._maxs[0]]
784
+ self.sticky_edges.y[:] = [self._mins[1], self._maxs[1]]
785
+ self.axes.update_datalim([self._mins, self._maxs])
786
+ self.axes.autoscale_view(tight=True)
787
+
788
+ self.changed() # set the colors
789
+
790
+ if kwargs:
791
+ _api.warn_external(
792
+ 'The following kwargs were not used by contour: ' +
793
+ ", ".join(map(repr, kwargs))
794
+ )
795
+
796
+ allsegs = property(lambda self: [
797
+ [subp.vertices for subp in p._iter_connected_components()]
798
+ for p in self.get_paths()])
799
+ allkinds = property(lambda self: [
800
+ [subp.codes for subp in p._iter_connected_components()]
801
+ for p in self.get_paths()])
802
+ alpha = property(lambda self: self.get_alpha())
803
+ linestyles = property(lambda self: self._orig_linestyles)
804
+
805
+ def get_transform(self):
806
+ """Return the `.Transform` instance used by this ContourSet."""
807
+ if self._transform is None:
808
+ self._transform = self.axes.transData
809
+ elif (not isinstance(self._transform, mtransforms.Transform)
810
+ and hasattr(self._transform, '_as_mpl_transform')):
811
+ self._transform = self._transform._as_mpl_transform(self.axes)
812
+ return self._transform
813
+
814
+ def __getstate__(self):
815
+ state = self.__dict__.copy()
816
+ # the C object _contour_generator cannot currently be pickled. This
817
+ # isn't a big issue as it is not actually used once the contour has
818
+ # been calculated.
819
+ state['_contour_generator'] = None
820
+ return state
821
+
822
+ def legend_elements(self, variable_name='x', str_format=str):
823
+ """
824
+ Return a list of artists and labels suitable for passing through
825
+ to `~.Axes.legend` which represent this ContourSet.
826
+
827
+ The labels have the form "0 < x <= 1" stating the data ranges which
828
+ the artists represent.
829
+
830
+ Parameters
831
+ ----------
832
+ variable_name : str
833
+ The string used inside the inequality used on the labels.
834
+ str_format : function: float -> str
835
+ Function used to format the numbers in the labels.
836
+
837
+ Returns
838
+ -------
839
+ artists : list[`.Artist`]
840
+ A list of the artists.
841
+ labels : list[str]
842
+ A list of the labels.
843
+ """
844
+ artists = []
845
+ labels = []
846
+
847
+ if self.filled:
848
+ lowers, uppers = self._get_lowers_and_uppers()
849
+ n_levels = len(self._paths)
850
+ for idx in range(n_levels):
851
+ artists.append(mpatches.Rectangle(
852
+ (0, 0), 1, 1,
853
+ facecolor=self.get_facecolor()[idx],
854
+ hatch=self.hatches[idx % len(self.hatches)],
855
+ ))
856
+ lower = str_format(lowers[idx])
857
+ upper = str_format(uppers[idx])
858
+ if idx == 0 and self.extend in ('min', 'both'):
859
+ labels.append(fr'${variable_name} \leq {lower}s$')
860
+ elif idx == n_levels - 1 and self.extend in ('max', 'both'):
861
+ labels.append(fr'${variable_name} > {upper}s$')
862
+ else:
863
+ labels.append(fr'${lower} < {variable_name} \leq {upper}$')
864
+ else:
865
+ for idx, level in enumerate(self.levels):
866
+ artists.append(Line2D(
867
+ [], [],
868
+ color=self.get_edgecolor()[idx],
869
+ linewidth=self.get_linewidths()[idx],
870
+ linestyle=self.get_linestyles()[idx],
871
+ ))
872
+ labels.append(fr'${variable_name} = {str_format(level)}$')
873
+
874
+ return artists, labels
875
+
876
+ def _process_args(self, *args, **kwargs):
877
+ """
878
+ Process *args* and *kwargs*; override in derived classes.
879
+
880
+ Must set self.levels, self.zmin and self.zmax, and update Axes limits.
881
+ """
882
+ self.levels = args[0]
883
+ allsegs = args[1]
884
+ allkinds = args[2] if len(args) > 2 else None
885
+ self.zmax = np.max(self.levels)
886
+ self.zmin = np.min(self.levels)
887
+
888
+ if allkinds is None:
889
+ allkinds = [[None] * len(segs) for segs in allsegs]
890
+
891
+ # Check lengths of levels and allsegs.
892
+ if self.filled:
893
+ if len(allsegs) != len(self.levels) - 1:
894
+ raise ValueError('must be one less number of segments as '
895
+ 'levels')
896
+ else:
897
+ if len(allsegs) != len(self.levels):
898
+ raise ValueError('must be same number of segments as levels')
899
+
900
+ # Check length of allkinds.
901
+ if len(allkinds) != len(allsegs):
902
+ raise ValueError('allkinds has different length to allsegs')
903
+
904
+ # Determine x, y bounds and update axes data limits.
905
+ flatseglist = [s for seg in allsegs for s in seg]
906
+ points = np.concatenate(flatseglist, axis=0)
907
+ self._mins = points.min(axis=0)
908
+ self._maxs = points.max(axis=0)
909
+
910
+ # Each entry in (allsegs, allkinds) is a list of (segs, kinds): segs is a list
911
+ # of (N, 2) arrays of xy coordinates, kinds is a list of arrays of corresponding
912
+ # pathcodes. However, kinds can also be None; in which case all paths in that
913
+ # list are codeless (this case is normalized above). These lists are used to
914
+ # construct paths, which then get concatenated.
915
+ self._paths = [Path.make_compound_path(*map(Path, segs, kinds))
916
+ for segs, kinds in zip(allsegs, allkinds)]
917
+
918
+ return kwargs
919
+
920
+ def _make_paths_from_contour_generator(self):
921
+ """Compute ``paths`` using C extension."""
922
+ if self._paths is not None:
923
+ return self._paths
924
+ cg = self._contour_generator
925
+ empty_path = Path(np.empty((0, 2)))
926
+ vertices_and_codes = (
927
+ map(cg.create_filled_contour, *self._get_lowers_and_uppers())
928
+ if self.filled else
929
+ map(cg.create_contour, self.levels))
930
+ return [Path(np.concatenate(vs), np.concatenate(cs)) if len(vs) else empty_path
931
+ for vs, cs in vertices_and_codes]
932
+
933
+ def _get_lowers_and_uppers(self):
934
+ """
935
+ Return ``(lowers, uppers)`` for filled contours.
936
+ """
937
+ lowers = self._levels[:-1]
938
+ if self.zmin == lowers[0]:
939
+ # Include minimum values in lowest interval
940
+ lowers = lowers.copy() # so we don't change self._levels
941
+ if self.logscale:
942
+ lowers[0] = 0.99 * self.zmin
943
+ else:
944
+ lowers[0] -= 1
945
+ uppers = self._levels[1:]
946
+ return (lowers, uppers)
947
+
948
+ def changed(self):
949
+ if not hasattr(self, "cvalues"):
950
+ self._process_colors() # Sets cvalues.
951
+ # Force an autoscale immediately because self.to_rgba() calls
952
+ # autoscale_None() internally with the data passed to it,
953
+ # so if vmin/vmax are not set yet, this would override them with
954
+ # content from *cvalues* rather than levels like we want
955
+ self.norm.autoscale_None(self.levels)
956
+ self.set_array(self.cvalues)
957
+ self.update_scalarmappable()
958
+ alphas = np.broadcast_to(self.get_alpha(), len(self.cvalues))
959
+ for label, cv, alpha in zip(self.labelTexts, self.labelCValues, alphas):
960
+ label.set_alpha(alpha)
961
+ label.set_color(self.labelMappable.to_rgba(cv))
962
+ super().changed()
963
+
964
+ def _autolev(self, N):
965
+ """
966
+ Select contour levels to span the data.
967
+
968
+ The target number of levels, *N*, is used only when the
969
+ scale is not log and default locator is used.
970
+
971
+ We need two more levels for filled contours than for
972
+ line contours, because for the latter we need to specify
973
+ the lower and upper boundary of each range. For example,
974
+ a single contour boundary, say at z = 0, requires only
975
+ one contour line, but two filled regions, and therefore
976
+ three levels to provide boundaries for both regions.
977
+ """
978
+ if self.locator is None:
979
+ if self.logscale:
980
+ self.locator = ticker.LogLocator()
981
+ else:
982
+ self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
983
+
984
+ lev = self.locator.tick_values(self.zmin, self.zmax)
985
+
986
+ try:
987
+ if self.locator._symmetric:
988
+ return lev
989
+ except AttributeError:
990
+ pass
991
+
992
+ # Trim excess levels the locator may have supplied.
993
+ under = np.nonzero(lev < self.zmin)[0]
994
+ i0 = under[-1] if len(under) else 0
995
+ over = np.nonzero(lev > self.zmax)[0]
996
+ i1 = over[0] + 1 if len(over) else len(lev)
997
+ if self.extend in ('min', 'both'):
998
+ i0 += 1
999
+ if self.extend in ('max', 'both'):
1000
+ i1 -= 1
1001
+
1002
+ if i1 - i0 < 3:
1003
+ i0, i1 = 0, len(lev)
1004
+
1005
+ return lev[i0:i1]
1006
+
1007
+ def _process_contour_level_args(self, args, z_dtype):
1008
+ """
1009
+ Determine the contour levels and store in self.levels.
1010
+ """
1011
+ if self.levels is None:
1012
+ if args:
1013
+ levels_arg = args[0]
1014
+ elif np.issubdtype(z_dtype, bool):
1015
+ if self.filled:
1016
+ levels_arg = [0, .5, 1]
1017
+ else:
1018
+ levels_arg = [.5]
1019
+ else:
1020
+ levels_arg = 7 # Default, hard-wired.
1021
+ else:
1022
+ levels_arg = self.levels
1023
+ if isinstance(levels_arg, Integral):
1024
+ self.levels = self._autolev(levels_arg)
1025
+ else:
1026
+ self.levels = np.asarray(levels_arg, np.float64)
1027
+ if self.filled and len(self.levels) < 2:
1028
+ raise ValueError("Filled contours require at least 2 levels.")
1029
+ if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0:
1030
+ raise ValueError("Contour levels must be increasing")
1031
+
1032
+ def _process_levels(self):
1033
+ """
1034
+ Assign values to :attr:`layers` based on :attr:`levels`,
1035
+ adding extended layers as needed if contours are filled.
1036
+
1037
+ For line contours, layers simply coincide with levels;
1038
+ a line is a thin layer. No extended levels are needed
1039
+ with line contours.
1040
+ """
1041
+ # Make a private _levels to include extended regions; we
1042
+ # want to leave the original levels attribute unchanged.
1043
+ # (Colorbar needs this even for line contours.)
1044
+ self._levels = list(self.levels)
1045
+
1046
+ if self.logscale:
1047
+ lower, upper = 1e-250, 1e250
1048
+ else:
1049
+ lower, upper = -1e250, 1e250
1050
+
1051
+ if self.extend in ('both', 'min'):
1052
+ self._levels.insert(0, lower)
1053
+ if self.extend in ('both', 'max'):
1054
+ self._levels.append(upper)
1055
+ self._levels = np.asarray(self._levels)
1056
+
1057
+ if not self.filled:
1058
+ self.layers = self.levels
1059
+ return
1060
+
1061
+ # Layer values are mid-way between levels in screen space.
1062
+ if self.logscale:
1063
+ # Avoid overflow by taking sqrt before multiplying.
1064
+ self.layers = (np.sqrt(self._levels[:-1])
1065
+ * np.sqrt(self._levels[1:]))
1066
+ else:
1067
+ self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
1068
+
1069
+ def _process_colors(self):
1070
+ """
1071
+ Color argument processing for contouring.
1072
+
1073
+ Note that we base the colormapping on the contour levels
1074
+ and layers, not on the actual range of the Z values. This
1075
+ means we don't have to worry about bad values in Z, and we
1076
+ always have the full dynamic range available for the selected
1077
+ levels.
1078
+
1079
+ The color is based on the midpoint of the layer, except for
1080
+ extended end layers. By default, the norm vmin and vmax
1081
+ are the extreme values of the non-extended levels. Hence,
1082
+ the layer color extremes are not the extreme values of
1083
+ the colormap itself, but approach those values as the number
1084
+ of levels increases. An advantage of this scheme is that
1085
+ line contours, when added to filled contours, take on
1086
+ colors that are consistent with those of the filled regions;
1087
+ for example, a contour line on the boundary between two
1088
+ regions will have a color intermediate between those
1089
+ of the regions.
1090
+
1091
+ """
1092
+ self.monochrome = self.cmap.monochrome
1093
+ if self.colors is not None:
1094
+ # Generate integers for direct indexing.
1095
+ i0, i1 = 0, len(self.levels)
1096
+ if self.filled:
1097
+ i1 -= 1
1098
+ # Out of range indices for over and under:
1099
+ if self.extend in ('both', 'min'):
1100
+ i0 -= 1
1101
+ if self.extend in ('both', 'max'):
1102
+ i1 += 1
1103
+ self.cvalues = list(range(i0, i1))
1104
+ self.set_norm(mcolors.NoNorm())
1105
+ else:
1106
+ self.cvalues = self.layers
1107
+ self.norm.autoscale_None(self.levels)
1108
+ self.set_array(self.cvalues)
1109
+ self.update_scalarmappable()
1110
+ if self.extend in ('both', 'max', 'min'):
1111
+ self.norm.clip = False
1112
+
1113
+ def _process_linewidths(self, linewidths):
1114
+ Nlev = len(self.levels)
1115
+ if linewidths is None:
1116
+ default_linewidth = mpl.rcParams['contour.linewidth']
1117
+ if default_linewidth is None:
1118
+ default_linewidth = mpl.rcParams['lines.linewidth']
1119
+ return [default_linewidth] * Nlev
1120
+ elif not np.iterable(linewidths):
1121
+ return [linewidths] * Nlev
1122
+ else:
1123
+ linewidths = list(linewidths)
1124
+ return (linewidths * math.ceil(Nlev / len(linewidths)))[:Nlev]
1125
+
1126
+ def _process_linestyles(self, linestyles):
1127
+ Nlev = len(self.levels)
1128
+ if linestyles is None:
1129
+ tlinestyles = ['solid'] * Nlev
1130
+ if self.monochrome:
1131
+ eps = - (self.zmax - self.zmin) * 1e-15
1132
+ for i, lev in enumerate(self.levels):
1133
+ if lev < eps:
1134
+ tlinestyles[i] = self.negative_linestyles
1135
+ else:
1136
+ if isinstance(linestyles, str):
1137
+ tlinestyles = [linestyles] * Nlev
1138
+ elif np.iterable(linestyles):
1139
+ tlinestyles = list(linestyles)
1140
+ if len(tlinestyles) < Nlev:
1141
+ nreps = int(np.ceil(Nlev / len(linestyles)))
1142
+ tlinestyles = tlinestyles * nreps
1143
+ if len(tlinestyles) > Nlev:
1144
+ tlinestyles = tlinestyles[:Nlev]
1145
+ else:
1146
+ raise ValueError("Unrecognized type for linestyles kwarg")
1147
+ return tlinestyles
1148
+
1149
+ def _find_nearest_contour(self, xy, indices=None):
1150
+ """
1151
+ Find the point in the unfilled contour plot that is closest (in screen
1152
+ space) to point *xy*.
1153
+
1154
+ Parameters
1155
+ ----------
1156
+ xy : tuple[float, float]
1157
+ The reference point (in screen space).
1158
+ indices : list of int or None, default: None
1159
+ Indices of contour levels to consider. If None (the default), all levels
1160
+ are considered.
1161
+
1162
+ Returns
1163
+ -------
1164
+ idx_level_min : int
1165
+ The index of the contour level closest to *xy*.
1166
+ idx_vtx_min : int
1167
+ The index of the `.Path` segment closest to *xy* (at that level).
1168
+ proj : (float, float)
1169
+ The point in the contour plot closest to *xy*.
1170
+ """
1171
+
1172
+ # Convert each contour segment to pixel coordinates and then compare the given
1173
+ # point to those coordinates for each contour. This is fast enough in normal
1174
+ # cases, but speedups may be possible.
1175
+
1176
+ if self.filled:
1177
+ raise ValueError("Method does not support filled contours")
1178
+
1179
+ if indices is None:
1180
+ indices = range(len(self._paths))
1181
+
1182
+ d2min = np.inf
1183
+ idx_level_min = idx_vtx_min = proj_min = None
1184
+
1185
+ for idx_level in indices:
1186
+ path = self._paths[idx_level]
1187
+ idx_vtx_start = 0
1188
+ for subpath in path._iter_connected_components():
1189
+ if not len(subpath.vertices):
1190
+ continue
1191
+ lc = self.get_transform().transform(subpath.vertices)
1192
+ d2, proj, leg = _find_closest_point_on_path(lc, xy)
1193
+ if d2 < d2min:
1194
+ d2min = d2
1195
+ idx_level_min = idx_level
1196
+ idx_vtx_min = leg[1] + idx_vtx_start
1197
+ proj_min = proj
1198
+ idx_vtx_start += len(subpath)
1199
+
1200
+ return idx_level_min, idx_vtx_min, proj_min
1201
+
1202
+ def find_nearest_contour(self, x, y, indices=None, pixel=True):
1203
+ """
1204
+ Find the point in the contour plot that is closest to ``(x, y)``.
1205
+
1206
+ This method does not support filled contours.
1207
+
1208
+ Parameters
1209
+ ----------
1210
+ x, y : float
1211
+ The reference point.
1212
+ indices : list of int or None, default: None
1213
+ Indices of contour levels to consider. If None (the default), all
1214
+ levels are considered.
1215
+ pixel : bool, default: True
1216
+ If *True*, measure distance in pixel (screen) space, which is
1217
+ useful for manual contour labeling; else, measure distance in axes
1218
+ space.
1219
+
1220
+ Returns
1221
+ -------
1222
+ path : int
1223
+ The index of the path that is closest to ``(x, y)``. Each path corresponds
1224
+ to one contour level.
1225
+ subpath : int
1226
+ The index within that closest path of the subpath that is closest to
1227
+ ``(x, y)``. Each subpath corresponds to one unbroken contour line.
1228
+ index : int
1229
+ The index of the vertices within that subpath that are closest to
1230
+ ``(x, y)``.
1231
+ xmin, ymin : float
1232
+ The point in the contour plot that is closest to ``(x, y)``.
1233
+ d2 : float
1234
+ The squared distance from ``(xmin, ymin)`` to ``(x, y)``.
1235
+ """
1236
+ segment = index = d2 = None
1237
+
1238
+ with ExitStack() as stack:
1239
+ if not pixel:
1240
+ # _find_nearest_contour works in pixel space. We want axes space, so
1241
+ # effectively disable the transformation here by setting to identity.
1242
+ stack.enter_context(self._cm_set(
1243
+ transform=mtransforms.IdentityTransform()))
1244
+
1245
+ i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices)
1246
+
1247
+ if i_level is not None:
1248
+ cc_cumlens = np.cumsum(
1249
+ [*map(len, self._paths[i_level]._iter_connected_components())])
1250
+ segment = cc_cumlens.searchsorted(i_vtx, "right")
1251
+ index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1]
1252
+ d2 = (xmin-x)**2 + (ymin-y)**2
1253
+
1254
+ return (i_level, segment, index, xmin, ymin, d2)
1255
+
1256
+ def draw(self, renderer):
1257
+ paths = self._paths
1258
+ n_paths = len(paths)
1259
+ if not self.filled or all(hatch is None for hatch in self.hatches):
1260
+ super().draw(renderer)
1261
+ return
1262
+ # In presence of hatching, draw contours one at a time.
1263
+ edgecolors = self.get_edgecolors()
1264
+ if edgecolors.size == 0:
1265
+ edgecolors = ("none",)
1266
+ for idx in range(n_paths):
1267
+ with cbook._setattr_cm(self, _paths=[paths[idx]]), self._cm_set(
1268
+ hatch=self.hatches[idx % len(self.hatches)],
1269
+ array=[self.get_array()[idx]],
1270
+ linewidths=[self.get_linewidths()[idx % len(self.get_linewidths())]],
1271
+ linestyles=[self.get_linestyles()[idx % len(self.get_linestyles())]],
1272
+ edgecolors=edgecolors[idx % len(edgecolors)],
1273
+ ):
1274
+ super().draw(renderer)
1275
+
1276
+
1277
+ @_docstring.interpd
1278
+ class QuadContourSet(ContourSet):
1279
+ """
1280
+ Create and store a set of contour lines or filled regions.
1281
+
1282
+ This class is typically not instantiated directly by the user but by
1283
+ `~.Axes.contour` and `~.Axes.contourf`.
1284
+
1285
+ %(contour_set_attributes)s
1286
+ """
1287
+
1288
+ def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs):
1289
+ """
1290
+ Process args and kwargs.
1291
+ """
1292
+ if args and isinstance(args[0], QuadContourSet):
1293
+ if self.levels is None:
1294
+ self.levels = args[0].levels
1295
+ self.zmin = args[0].zmin
1296
+ self.zmax = args[0].zmax
1297
+ self._corner_mask = args[0]._corner_mask
1298
+ contour_generator = args[0]._contour_generator
1299
+ self._mins = args[0]._mins
1300
+ self._maxs = args[0]._maxs
1301
+ self._algorithm = args[0]._algorithm
1302
+ else:
1303
+ import contourpy
1304
+
1305
+ if algorithm is None:
1306
+ algorithm = mpl.rcParams['contour.algorithm']
1307
+ mpl.rcParams.validate["contour.algorithm"](algorithm)
1308
+ self._algorithm = algorithm
1309
+
1310
+ if corner_mask is None:
1311
+ if self._algorithm == "mpl2005":
1312
+ # mpl2005 does not support corner_mask=True so if not
1313
+ # specifically requested then disable it.
1314
+ corner_mask = False
1315
+ else:
1316
+ corner_mask = mpl.rcParams['contour.corner_mask']
1317
+ self._corner_mask = corner_mask
1318
+
1319
+ x, y, z = self._contour_args(args, kwargs)
1320
+
1321
+ contour_generator = contourpy.contour_generator(
1322
+ x, y, z, name=self._algorithm, corner_mask=self._corner_mask,
1323
+ line_type=contourpy.LineType.SeparateCode,
1324
+ fill_type=contourpy.FillType.OuterCode,
1325
+ chunk_size=self.nchunk)
1326
+
1327
+ t = self.get_transform()
1328
+
1329
+ # if the transform is not trans data, and some part of it
1330
+ # contains transData, transform the xs and ys to data coordinates
1331
+ if (t != self.axes.transData and
1332
+ any(t.contains_branch_seperately(self.axes.transData))):
1333
+ trans_to_data = t - self.axes.transData
1334
+ pts = np.vstack([x.flat, y.flat]).T
1335
+ transformed_pts = trans_to_data.transform(pts)
1336
+ x = transformed_pts[..., 0]
1337
+ y = transformed_pts[..., 1]
1338
+
1339
+ self._mins = [ma.min(x), ma.min(y)]
1340
+ self._maxs = [ma.max(x), ma.max(y)]
1341
+
1342
+ self._contour_generator = contour_generator
1343
+
1344
+ return kwargs
1345
+
1346
+ def _contour_args(self, args, kwargs):
1347
+ if self.filled:
1348
+ fn = 'contourf'
1349
+ else:
1350
+ fn = 'contour'
1351
+ nargs = len(args)
1352
+
1353
+ if 0 < nargs <= 2:
1354
+ z, *args = args
1355
+ z = ma.asarray(z)
1356
+ x, y = self._initialize_x_y(z)
1357
+ elif 2 < nargs <= 4:
1358
+ x, y, z_orig, *args = args
1359
+ x, y, z = self._check_xyz(x, y, z_orig, kwargs)
1360
+
1361
+ else:
1362
+ raise _api.nargs_error(fn, takes="from 1 to 4", given=nargs)
1363
+ z = ma.masked_invalid(z, copy=False)
1364
+ self.zmax = z.max().astype(float)
1365
+ self.zmin = z.min().astype(float)
1366
+ if self.logscale and self.zmin <= 0:
1367
+ z = ma.masked_where(z <= 0, z)
1368
+ _api.warn_external('Log scale: values of z <= 0 have been masked')
1369
+ self.zmin = z.min().astype(float)
1370
+ self._process_contour_level_args(args, z.dtype)
1371
+ return (x, y, z)
1372
+
1373
+ def _check_xyz(self, x, y, z, kwargs):
1374
+ """
1375
+ Check that the shapes of the input arrays match; if x and y are 1D,
1376
+ convert them to 2D using meshgrid.
1377
+ """
1378
+ x, y = self.axes._process_unit_info([("x", x), ("y", y)], kwargs)
1379
+
1380
+ x = np.asarray(x, dtype=np.float64)
1381
+ y = np.asarray(y, dtype=np.float64)
1382
+ z = ma.asarray(z)
1383
+
1384
+ if z.ndim != 2:
1385
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
1386
+ if z.shape[0] < 2 or z.shape[1] < 2:
1387
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, "
1388
+ f"but has shape {z.shape}")
1389
+ Ny, Nx = z.shape
1390
+
1391
+ if x.ndim != y.ndim:
1392
+ raise TypeError(f"Number of dimensions of x ({x.ndim}) and y "
1393
+ f"({y.ndim}) do not match")
1394
+ if x.ndim == 1:
1395
+ nx, = x.shape
1396
+ ny, = y.shape
1397
+ if nx != Nx:
1398
+ raise TypeError(f"Length of x ({nx}) must match number of "
1399
+ f"columns in z ({Nx})")
1400
+ if ny != Ny:
1401
+ raise TypeError(f"Length of y ({ny}) must match number of "
1402
+ f"rows in z ({Ny})")
1403
+ x, y = np.meshgrid(x, y)
1404
+ elif x.ndim == 2:
1405
+ if x.shape != z.shape:
1406
+ raise TypeError(
1407
+ f"Shapes of x {x.shape} and z {z.shape} do not match")
1408
+ if y.shape != z.shape:
1409
+ raise TypeError(
1410
+ f"Shapes of y {y.shape} and z {z.shape} do not match")
1411
+ else:
1412
+ raise TypeError(f"Inputs x and y must be 1D or 2D, not {x.ndim}D")
1413
+
1414
+ return x, y, z
1415
+
1416
+ def _initialize_x_y(self, z):
1417
+ """
1418
+ Return X, Y arrays such that contour(Z) will match imshow(Z)
1419
+ if origin is not None.
1420
+ The center of pixel Z[i, j] depends on origin:
1421
+ if origin is None, x = j, y = i;
1422
+ if origin is 'lower', x = j + 0.5, y = i + 0.5;
1423
+ if origin is 'upper', x = j + 0.5, y = Nrows - i - 0.5
1424
+ If extent is not None, x and y will be scaled to match,
1425
+ as in imshow.
1426
+ If origin is None and extent is not None, then extent
1427
+ will give the minimum and maximum values of x and y.
1428
+ """
1429
+ if z.ndim != 2:
1430
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
1431
+ elif z.shape[0] < 2 or z.shape[1] < 2:
1432
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, "
1433
+ f"but has shape {z.shape}")
1434
+ else:
1435
+ Ny, Nx = z.shape
1436
+ if self.origin is None: # Not for image-matching.
1437
+ if self.extent is None:
1438
+ return np.meshgrid(np.arange(Nx), np.arange(Ny))
1439
+ else:
1440
+ x0, x1, y0, y1 = self.extent
1441
+ x = np.linspace(x0, x1, Nx)
1442
+ y = np.linspace(y0, y1, Ny)
1443
+ return np.meshgrid(x, y)
1444
+ # Match image behavior:
1445
+ if self.extent is None:
1446
+ x0, x1, y0, y1 = (0, Nx, 0, Ny)
1447
+ else:
1448
+ x0, x1, y0, y1 = self.extent
1449
+ dx = (x1 - x0) / Nx
1450
+ dy = (y1 - y0) / Ny
1451
+ x = x0 + (np.arange(Nx) + 0.5) * dx
1452
+ y = y0 + (np.arange(Ny) + 0.5) * dy
1453
+ if self.origin == 'upper':
1454
+ y = y[::-1]
1455
+ return np.meshgrid(x, y)
1456
+
1457
+
1458
+ _docstring.interpd.register(contour_doc="""
1459
+ `.contour` and `.contourf` draw contour lines and filled contours,
1460
+ respectively. Except as noted, function signatures and return values
1461
+ are the same for both versions.
1462
+
1463
+ Parameters
1464
+ ----------
1465
+ X, Y : array-like, optional
1466
+ The coordinates of the values in *Z*.
1467
+
1468
+ *X* and *Y* must both be 2D with the same shape as *Z* (e.g.
1469
+ created via `numpy.meshgrid`), or they must both be 1-D such
1470
+ that ``len(X) == N`` is the number of columns in *Z* and
1471
+ ``len(Y) == M`` is the number of rows in *Z*.
1472
+
1473
+ *X* and *Y* must both be ordered monotonically.
1474
+
1475
+ If not given, they are assumed to be integer indices, i.e.
1476
+ ``X = range(N)``, ``Y = range(M)``.
1477
+
1478
+ Z : (M, N) array-like
1479
+ The height values over which the contour is drawn. Color-mapping is
1480
+ controlled by *cmap*, *norm*, *vmin*, and *vmax*.
1481
+
1482
+ levels : int or array-like, optional
1483
+ Determines the number and positions of the contour lines / regions.
1484
+
1485
+ If an int *n*, use `~matplotlib.ticker.MaxNLocator`, which tries
1486
+ to automatically choose no more than *n+1* "nice" contour levels
1487
+ between minimum and maximum numeric values of *Z*.
1488
+
1489
+ If array-like, draw contour lines at the specified levels.
1490
+ The values must be in increasing order.
1491
+
1492
+ Returns
1493
+ -------
1494
+ `~.contour.QuadContourSet`
1495
+
1496
+ Other Parameters
1497
+ ----------------
1498
+ corner_mask : bool, default: :rc:`contour.corner_mask`
1499
+ Enable/disable corner masking, which only has an effect if *Z* is
1500
+ a masked array. If ``False``, any quad touching a masked point is
1501
+ masked out. If ``True``, only the triangular corners of quads
1502
+ nearest those points are always masked out, other triangular
1503
+ corners comprising three unmasked points are contoured as usual.
1504
+
1505
+ colors : :mpltype:`color` or list of :mpltype:`color`, optional
1506
+ The colors of the levels, i.e. the lines for `.contour` and the
1507
+ areas for `.contourf`.
1508
+
1509
+ The sequence is cycled for the levels in ascending order. If the
1510
+ sequence is shorter than the number of levels, it's repeated.
1511
+
1512
+ As a shortcut, a single color may be used in place of one-element lists, i.e.
1513
+ ``'red'`` instead of ``['red']`` to color all levels with the same color.
1514
+
1515
+ .. versionchanged:: 3.10
1516
+ Previously a single color had to be expressed as a string, but now any
1517
+ valid color format may be passed.
1518
+
1519
+ By default (value *None*), the colormap specified by *cmap*
1520
+ will be used.
1521
+
1522
+ alpha : float, default: 1
1523
+ The alpha blending value, between 0 (transparent) and 1 (opaque).
1524
+
1525
+ %(cmap_doc)s
1526
+
1527
+ This parameter is ignored if *colors* is set.
1528
+
1529
+ %(norm_doc)s
1530
+
1531
+ This parameter is ignored if *colors* is set.
1532
+
1533
+ %(vmin_vmax_doc)s
1534
+
1535
+ If *vmin* or *vmax* are not given, the default color scaling is based on
1536
+ *levels*.
1537
+
1538
+ This parameter is ignored if *colors* is set.
1539
+
1540
+ %(colorizer_doc)s
1541
+
1542
+ This parameter is ignored if *colors* is set.
1543
+
1544
+ origin : {*None*, 'upper', 'lower', 'image'}, default: None
1545
+ Determines the orientation and exact position of *Z* by specifying
1546
+ the position of ``Z[0, 0]``. This is only relevant, if *X*, *Y*
1547
+ are not given.
1548
+
1549
+ - *None*: ``Z[0, 0]`` is at X=0, Y=0 in the lower left corner.
1550
+ - 'lower': ``Z[0, 0]`` is at X=0.5, Y=0.5 in the lower left corner.
1551
+ - 'upper': ``Z[0, 0]`` is at X=N+0.5, Y=0.5 in the upper left
1552
+ corner.
1553
+ - 'image': Use the value from :rc:`image.origin`.
1554
+
1555
+ extent : (x0, x1, y0, y1), optional
1556
+ If *origin* is not *None*, then *extent* is interpreted as in
1557
+ `.imshow`: it gives the outer pixel boundaries. In this case, the
1558
+ position of Z[0, 0] is the center of the pixel, not a corner. If
1559
+ *origin* is *None*, then (*x0*, *y0*) is the position of Z[0, 0],
1560
+ and (*x1*, *y1*) is the position of Z[-1, -1].
1561
+
1562
+ This argument is ignored if *X* and *Y* are specified in the call
1563
+ to contour.
1564
+
1565
+ locator : ticker.Locator subclass, optional
1566
+ The locator is used to determine the contour levels if they
1567
+ are not given explicitly via *levels*.
1568
+ Defaults to `~.ticker.MaxNLocator`.
1569
+
1570
+ extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
1571
+ Determines the ``contourf``-coloring of values that are outside the
1572
+ *levels* range.
1573
+
1574
+ If 'neither', values outside the *levels* range are not colored.
1575
+ If 'min', 'max' or 'both', color the values below, above or below
1576
+ and above the *levels* range.
1577
+
1578
+ Values below ``min(levels)`` and above ``max(levels)`` are mapped
1579
+ to the under/over values of the `.Colormap`. Note that most
1580
+ colormaps do not have dedicated colors for these by default, so
1581
+ that the over and under values are the edge values of the colormap.
1582
+ You may want to set these values explicitly using
1583
+ `.Colormap.set_under` and `.Colormap.set_over`.
1584
+
1585
+ .. note::
1586
+
1587
+ An existing `.QuadContourSet` does not get notified if
1588
+ properties of its colormap are changed. Therefore, an explicit
1589
+ call `~.ContourSet.changed()` is needed after modifying the
1590
+ colormap. The explicit call can be left out, if a colorbar is
1591
+ assigned to the `.QuadContourSet` because it internally calls
1592
+ `~.ContourSet.changed()`.
1593
+
1594
+ Example::
1595
+
1596
+ x = np.arange(1, 10)
1597
+ y = x.reshape(-1, 1)
1598
+ h = x * y
1599
+
1600
+ cs = plt.contourf(h, levels=[10, 30, 50],
1601
+ colors=['#808080', '#A0A0A0', '#C0C0C0'], extend='both')
1602
+ cs.cmap.set_over('red')
1603
+ cs.cmap.set_under('blue')
1604
+ cs.changed()
1605
+
1606
+ xunits, yunits : registered units, optional
1607
+ Override axis units by specifying an instance of a
1608
+ :class:`matplotlib.units.ConversionInterface`.
1609
+
1610
+ antialiased : bool, optional
1611
+ Enable antialiasing, overriding the defaults. For
1612
+ filled contours, the default is *False*. For line contours,
1613
+ it is taken from :rc:`lines.antialiased`.
1614
+
1615
+ nchunk : int >= 0, optional
1616
+ If 0, no subdivision of the domain. Specify a positive integer to
1617
+ divide the domain into subdomains of *nchunk* by *nchunk* quads.
1618
+ Chunking reduces the maximum length of polygons generated by the
1619
+ contouring algorithm which reduces the rendering workload passed
1620
+ on to the backend and also requires slightly less RAM. It can
1621
+ however introduce rendering artifacts at chunk boundaries depending
1622
+ on the backend, the *antialiased* flag and value of *alpha*.
1623
+
1624
+ linewidths : float or array-like, default: :rc:`contour.linewidth`
1625
+ *Only applies to* `.contour`.
1626
+
1627
+ The line width of the contour lines.
1628
+
1629
+ If a number, all levels will be plotted with this linewidth.
1630
+
1631
+ If a sequence, the levels in ascending order will be plotted with
1632
+ the linewidths in the order specified.
1633
+
1634
+ If None, this falls back to :rc:`lines.linewidth`.
1635
+
1636
+ linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, optional
1637
+ *Only applies to* `.contour`.
1638
+
1639
+ If *linestyles* is *None*, the default is 'solid' unless the lines are
1640
+ monochrome. In that case, negative contours will instead take their
1641
+ linestyle from the *negative_linestyles* argument.
1642
+
1643
+ *linestyles* can also be an iterable of the above strings specifying a set
1644
+ of linestyles to be used. If this iterable is shorter than the number of
1645
+ contour levels it will be repeated as necessary.
1646
+
1647
+ negative_linestyles : {*None*, 'solid', 'dashed', 'dashdot', 'dotted'}, \
1648
+ optional
1649
+ *Only applies to* `.contour`.
1650
+
1651
+ If *linestyles* is *None* and the lines are monochrome, this argument
1652
+ specifies the line style for negative contours.
1653
+
1654
+ If *negative_linestyles* is *None*, the default is taken from
1655
+ :rc:`contour.negative_linestyle`.
1656
+
1657
+ *negative_linestyles* can also be an iterable of the above strings
1658
+ specifying a set of linestyles to be used. If this iterable is shorter than
1659
+ the number of contour levels it will be repeated as necessary.
1660
+
1661
+ hatches : list[str], optional
1662
+ *Only applies to* `.contourf`.
1663
+
1664
+ A list of cross hatch patterns to use on the filled areas.
1665
+ If None, no hatching will be added to the contour.
1666
+
1667
+ algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional
1668
+ Which contouring algorithm to use to calculate the contour lines and
1669
+ polygons. The algorithms are implemented in
1670
+ `ContourPy <https://github.com/contourpy/contourpy>`_, consult the
1671
+ `ContourPy documentation <https://contourpy.readthedocs.io>`_ for
1672
+ further information.
1673
+
1674
+ The default is taken from :rc:`contour.algorithm`.
1675
+
1676
+ clip_path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath`
1677
+ Set the clip path. See `~matplotlib.artist.Artist.set_clip_path`.
1678
+
1679
+ .. versionadded:: 3.8
1680
+
1681
+ data : indexable object, optional
1682
+ DATA_PARAMETER_PLACEHOLDER
1683
+
1684
+ Notes
1685
+ -----
1686
+ 1. `.contourf` differs from the MATLAB version in that it does not draw
1687
+ the polygon edges. To draw edges, add line contours with calls to
1688
+ `.contour`.
1689
+
1690
+ 2. `.contourf` fills intervals that are closed at the top; that is, for
1691
+ boundaries *z1* and *z2*, the filled region is::
1692
+
1693
+ z1 < Z <= z2
1694
+
1695
+ except for the lowest interval, which is closed on both sides (i.e.
1696
+ it includes the lowest value).
1697
+
1698
+ 3. `.contour` and `.contourf` use a `marching squares
1699
+ <https://en.wikipedia.org/wiki/Marching_squares>`_ algorithm to
1700
+ compute contour locations. More information can be found in
1701
+ `ContourPy documentation <https://contourpy.readthedocs.io>`_.
1702
+ """ % _docstring.interpd.params)
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.cm as cm
2
+ from matplotlib.artist import Artist
3
+ from matplotlib.axes import Axes
4
+ from matplotlib.collections import Collection, PathCollection
5
+ from matplotlib.colorizer import Colorizer, ColorizingArtist
6
+ from matplotlib.colors import Colormap, Normalize
7
+ from matplotlib.path import Path
8
+ from matplotlib.patches import Patch
9
+ from matplotlib.text import Text
10
+ from matplotlib.transforms import Transform, TransformedPatchPath, TransformedPath
11
+ from matplotlib.ticker import Locator, Formatter
12
+
13
+ from numpy.typing import ArrayLike
14
+ import numpy as np
15
+ from collections.abc import Callable, Iterable, Sequence
16
+ from typing import Literal
17
+ from .typing import ColorType
18
+
19
+
20
+
21
+ class ContourLabeler:
22
+ labelFmt: str | Formatter | Callable[[float], str] | dict[float, str]
23
+ labelManual: bool | Iterable[tuple[float, float]]
24
+ rightside_up: bool
25
+ labelLevelList: list[float]
26
+ labelIndiceList: list[int]
27
+ labelMappable: cm.ScalarMappable | ColorizingArtist
28
+ labelCValueList: list[ColorType]
29
+ labelXYs: list[tuple[float, float]]
30
+ def clabel(
31
+ self,
32
+ levels: ArrayLike | None = ...,
33
+ *,
34
+ fontsize: str | float | None = ...,
35
+ inline: bool = ...,
36
+ inline_spacing: float = ...,
37
+ fmt: str | Formatter | Callable[[float], str] | dict[float, str] | None = ...,
38
+ colors: ColorType | Sequence[ColorType] | None = ...,
39
+ use_clabeltext: bool = ...,
40
+ manual: bool | Iterable[tuple[float, float]] = ...,
41
+ rightside_up: bool = ...,
42
+ zorder: float | None = ...
43
+ ) -> list[Text]: ...
44
+ def print_label(self, linecontour: ArrayLike, labelwidth: float) -> bool: ...
45
+ def too_close(self, x: float, y: float, lw: float) -> bool: ...
46
+ def get_text(
47
+ self,
48
+ lev: float,
49
+ fmt: str | Formatter | Callable[[float], str] | dict[float, str],
50
+ ) -> str: ...
51
+ def locate_label(
52
+ self, linecontour: ArrayLike, labelwidth: float
53
+ ) -> tuple[float, float, float]: ...
54
+ def add_label(
55
+ self, x: float, y: float, rotation: float, lev: float, cvalue: ColorType
56
+ ) -> None: ...
57
+ def add_label_near(
58
+ self,
59
+ x: float,
60
+ y: float,
61
+ inline: bool = ...,
62
+ inline_spacing: int = ...,
63
+ transform: Transform | Literal[False] | None = ...,
64
+ ) -> None: ...
65
+ def pop_label(self, index: int = ...) -> None: ...
66
+ def labels(self, inline: bool, inline_spacing: int) -> None: ...
67
+ def remove(self) -> None: ...
68
+
69
+ class ContourSet(ContourLabeler, Collection):
70
+ axes: Axes
71
+ levels: Iterable[float]
72
+ filled: bool
73
+ linewidths: float | ArrayLike | None
74
+ hatches: Iterable[str | None]
75
+ origin: Literal["upper", "lower", "image"] | None
76
+ extent: tuple[float, float, float, float] | None
77
+ colors: ColorType | Sequence[ColorType]
78
+ extend: Literal["neither", "both", "min", "max"]
79
+ nchunk: int
80
+ locator: Locator | None
81
+ logscale: bool
82
+ negative_linestyles: None | Literal[
83
+ "solid", "dashed", "dashdot", "dotted"
84
+ ] | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
85
+ clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None
86
+ labelTexts: list[Text]
87
+ labelCValues: list[ColorType]
88
+
89
+ @property
90
+ def allkinds(self) -> list[list[np.ndarray | None]]: ...
91
+ @property
92
+ def allsegs(self) -> list[list[np.ndarray]]: ...
93
+ @property
94
+ def alpha(self) -> float | None: ...
95
+ @property
96
+ def linestyles(self) -> (
97
+ None |
98
+ Literal["solid", "dashed", "dashdot", "dotted"] |
99
+ Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
100
+ ): ...
101
+
102
+ def __init__(
103
+ self,
104
+ ax: Axes,
105
+ *args,
106
+ levels: Iterable[float] | None = ...,
107
+ filled: bool = ...,
108
+ linewidths: float | ArrayLike | None = ...,
109
+ linestyles: Literal["solid", "dashed", "dashdot", "dotted"]
110
+ | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
111
+ | None = ...,
112
+ hatches: Iterable[str | None] = ...,
113
+ alpha: float | None = ...,
114
+ origin: Literal["upper", "lower", "image"] | None = ...,
115
+ extent: tuple[float, float, float, float] | None = ...,
116
+ cmap: str | Colormap | None = ...,
117
+ colors: ColorType | Sequence[ColorType] | None = ...,
118
+ norm: str | Normalize | None = ...,
119
+ vmin: float | None = ...,
120
+ vmax: float | None = ...,
121
+ colorizer: Colorizer | None = ...,
122
+ extend: Literal["neither", "both", "min", "max"] = ...,
123
+ antialiased: bool | None = ...,
124
+ nchunk: int = ...,
125
+ locator: Locator | None = ...,
126
+ transform: Transform | None = ...,
127
+ negative_linestyles: Literal["solid", "dashed", "dashdot", "dotted"]
128
+ | Iterable[Literal["solid", "dashed", "dashdot", "dotted"]]
129
+ | None = ...,
130
+ clip_path: Patch | Path | TransformedPath | TransformedPatchPath | None = ...,
131
+ **kwargs
132
+ ) -> None: ...
133
+ def legend_elements(
134
+ self, variable_name: str = ..., str_format: Callable[[float], str] = ...
135
+ ) -> tuple[list[Artist], list[str]]: ...
136
+ def find_nearest_contour(
137
+ self, x: float, y: float, indices: Iterable[int] | None = ..., pixel: bool = ...
138
+ ) -> tuple[int, int, int, float, float, float]: ...
139
+
140
+ class QuadContourSet(ContourSet): ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py ADDED
@@ -0,0 +1,1840 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Matplotlib provides sophisticated date plotting capabilities, standing on the
3
+ shoulders of python :mod:`datetime` and the add-on module dateutil_.
4
+
5
+ By default, Matplotlib uses the units machinery described in
6
+ `~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64`
7
+ objects when plotted on an x- or y-axis. The user does not
8
+ need to do anything for dates to be formatted, but dates often have strict
9
+ formatting needs, so this module provides many tick locators and formatters.
10
+ A basic example using `numpy.datetime64` is::
11
+
12
+ import numpy as np
13
+
14
+ times = np.arange(np.datetime64('2001-01-02'),
15
+ np.datetime64('2002-02-03'), np.timedelta64(75, 'm'))
16
+ y = np.random.randn(len(times))
17
+
18
+ fig, ax = plt.subplots()
19
+ ax.plot(times, y)
20
+
21
+ .. seealso::
22
+
23
+ - :doc:`/gallery/text_labels_and_annotations/date`
24
+ - :doc:`/gallery/ticks/date_concise_formatter`
25
+ - :doc:`/gallery/ticks/date_demo_convert`
26
+
27
+ .. _date-format:
28
+
29
+ Matplotlib date format
30
+ ----------------------
31
+
32
+ Matplotlib represents dates using floating point numbers specifying the number
33
+ of days since a default epoch of 1970-01-01 UTC; for example,
34
+ 1970-01-01, 06:00 is the floating point number 0.25. The formatters and
35
+ locators require the use of `datetime.datetime` objects, so only dates between
36
+ year 0001 and 9999 can be represented. Microsecond precision
37
+ is achievable for (approximately) 70 years on either side of the epoch, and
38
+ 20 microseconds for the rest of the allowable range of dates (year 0001 to
39
+ 9999). The epoch can be changed at import time via `.dates.set_epoch` or
40
+ :rc:`date.epoch` to other dates if necessary; see
41
+ :doc:`/gallery/ticks/date_precision_and_epochs` for a discussion.
42
+
43
+ .. note::
44
+
45
+ Before Matplotlib 3.3, the epoch was 0000-12-31 which lost modern
46
+ microsecond precision and also made the default axis limit of 0 an invalid
47
+ datetime. In 3.3 the epoch was changed as above. To convert old
48
+ ordinal floats to the new epoch, users can do::
49
+
50
+ new_ordinal = old_ordinal + mdates.date2num(np.datetime64('0000-12-31'))
51
+
52
+
53
+ There are a number of helper functions to convert between :mod:`datetime`
54
+ objects and Matplotlib dates:
55
+
56
+ .. currentmodule:: matplotlib.dates
57
+
58
+ .. autosummary::
59
+ :nosignatures:
60
+
61
+ datestr2num
62
+ date2num
63
+ num2date
64
+ num2timedelta
65
+ drange
66
+ set_epoch
67
+ get_epoch
68
+
69
+ .. note::
70
+
71
+ Like Python's `datetime.datetime`, Matplotlib uses the Gregorian calendar
72
+ for all conversions between dates and floating point numbers. This practice
73
+ is not universal, and calendar differences can cause confusing
74
+ differences between what Python and Matplotlib give as the number of days
75
+ since 0001-01-01 and what other software and databases yield. For
76
+ example, the US Naval Observatory uses a calendar that switches
77
+ from Julian to Gregorian in October, 1582. Hence, using their
78
+ calculator, the number of days between 0001-01-01 and 2006-04-01 is
79
+ 732403, whereas using the Gregorian calendar via the datetime
80
+ module we find::
81
+
82
+ In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal()
83
+ Out[1]: 732401
84
+
85
+ All the Matplotlib date converters, locators and formatters are timezone aware.
86
+ If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a
87
+ string. If you want to use a different timezone, pass the *tz* keyword
88
+ argument of `num2date` to any date tick locators or formatters you create. This
89
+ can be either a `datetime.tzinfo` instance or a string with the timezone name
90
+ that can be parsed by `~dateutil.tz.gettz`.
91
+
92
+ A wide range of specific and general purpose date tick locators and
93
+ formatters are provided in this module. See
94
+ :mod:`matplotlib.ticker` for general information on tick locators
95
+ and formatters. These are described below.
96
+
97
+ The dateutil_ module provides additional code to handle date ticking, making it
98
+ easy to place ticks on any kinds of dates. See examples below.
99
+
100
+ .. _dateutil: https://dateutil.readthedocs.io
101
+
102
+ .. _date-locators:
103
+
104
+ Date tick locators
105
+ ------------------
106
+
107
+ Most of the date tick locators can locate single or multiple ticks. For example::
108
+
109
+ # import constants for the days of the week
110
+ from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU
111
+
112
+ # tick on Mondays every week
113
+ loc = WeekdayLocator(byweekday=MO, tz=tz)
114
+
115
+ # tick on Mondays and Saturdays
116
+ loc = WeekdayLocator(byweekday=(MO, SA))
117
+
118
+ In addition, most of the constructors take an interval argument::
119
+
120
+ # tick on Mondays every second week
121
+ loc = WeekdayLocator(byweekday=MO, interval=2)
122
+
123
+ The rrule locator allows completely general date ticking::
124
+
125
+ # tick every 5th easter
126
+ rule = rrulewrapper(YEARLY, byeaster=1, interval=5)
127
+ loc = RRuleLocator(rule)
128
+
129
+ The available date tick locators are:
130
+
131
+ * `MicrosecondLocator`: Locate microseconds.
132
+
133
+ * `SecondLocator`: Locate seconds.
134
+
135
+ * `MinuteLocator`: Locate minutes.
136
+
137
+ * `HourLocator`: Locate hours.
138
+
139
+ * `DayLocator`: Locate specified days of the month.
140
+
141
+ * `WeekdayLocator`: Locate days of the week, e.g., MO, TU.
142
+
143
+ * `MonthLocator`: Locate months, e.g., 7 for July.
144
+
145
+ * `YearLocator`: Locate years that are multiples of base.
146
+
147
+ * `RRuleLocator`: Locate using a `rrulewrapper`.
148
+ `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule`
149
+ which allow almost arbitrary date tick specifications.
150
+ See :doc:`rrule example </gallery/ticks/date_demo_rrule>`.
151
+
152
+ * `AutoDateLocator`: On autoscale, this class picks the best `DateLocator`
153
+ (e.g., `RRuleLocator`) to set the view limits and the tick locations. If
154
+ called with ``interval_multiples=True`` it will make ticks line up with
155
+ sensible multiples of the tick intervals. For example, if the interval is
156
+ 4 hours, it will pick hours 0, 4, 8, etc. as ticks. This behaviour is not
157
+ guaranteed by default.
158
+
159
+ .. _date-formatters:
160
+
161
+ Date formatters
162
+ ---------------
163
+
164
+ The available date formatters are:
165
+
166
+ * `AutoDateFormatter`: attempts to figure out the best format to use. This is
167
+ most useful when used with the `AutoDateLocator`.
168
+
169
+ * `ConciseDateFormatter`: also attempts to figure out the best format to use,
170
+ and to make the format as compact as possible while still having complete
171
+ date information. This is most useful when used with the `AutoDateLocator`.
172
+
173
+ * `DateFormatter`: use `~datetime.datetime.strftime` format strings.
174
+ """
175
+
176
+ import datetime
177
+ import functools
178
+ import logging
179
+ import re
180
+
181
+ from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
182
+ MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
183
+ SECONDLY)
184
+ from dateutil.relativedelta import relativedelta
185
+ import dateutil.parser
186
+ import dateutil.tz
187
+ import numpy as np
188
+
189
+ import matplotlib as mpl
190
+ from matplotlib import _api, cbook, ticker, units
191
+
192
+ __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
193
+ 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter',
194
+ 'AutoDateFormatter', 'DateLocator', 'RRuleLocator',
195
+ 'AutoDateLocator', 'YearLocator', 'MonthLocator', 'WeekdayLocator',
196
+ 'DayLocator', 'HourLocator', 'MinuteLocator',
197
+ 'SecondLocator', 'MicrosecondLocator',
198
+ 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU',
199
+ 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
200
+ 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta',
201
+ 'DateConverter', 'ConciseDateConverter', 'rrulewrapper')
202
+
203
+
204
+ _log = logging.getLogger(__name__)
205
+ UTC = datetime.timezone.utc
206
+
207
+
208
+ def _get_tzinfo(tz=None):
209
+ """
210
+ Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`.
211
+ If None, retrieve the preferred timezone from the rcParams dictionary.
212
+ """
213
+ tz = mpl._val_or_rc(tz, 'timezone')
214
+ if tz == 'UTC':
215
+ return UTC
216
+ if isinstance(tz, str):
217
+ tzinfo = dateutil.tz.gettz(tz)
218
+ if tzinfo is None:
219
+ raise ValueError(f"{tz} is not a valid timezone as parsed by"
220
+ " dateutil.tz.gettz.")
221
+ return tzinfo
222
+ if isinstance(tz, datetime.tzinfo):
223
+ return tz
224
+ raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.")
225
+
226
+
227
+ # Time-related constants.
228
+ EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
229
+ # EPOCH_OFFSET is not used by matplotlib
230
+ MICROSECONDLY = SECONDLY + 1
231
+ HOURS_PER_DAY = 24.
232
+ MIN_PER_HOUR = 60.
233
+ SEC_PER_MIN = 60.
234
+ MONTHS_PER_YEAR = 12.
235
+
236
+ DAYS_PER_WEEK = 7.
237
+ DAYS_PER_MONTH = 30.
238
+ DAYS_PER_YEAR = 365.0
239
+
240
+ MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY
241
+
242
+ SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR
243
+ SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY
244
+ SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK
245
+
246
+ MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY
247
+
248
+ MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
249
+ MO, TU, WE, TH, FR, SA, SU)
250
+ WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)
251
+
252
+ # default epoch: passed to np.datetime64...
253
+ _epoch = None
254
+
255
+
256
+ def _reset_epoch_test_example():
257
+ """
258
+ Reset the Matplotlib date epoch so it can be set again.
259
+
260
+ Only for use in tests and examples.
261
+ """
262
+ global _epoch
263
+ _epoch = None
264
+
265
+
266
+ def set_epoch(epoch):
267
+ """
268
+ Set the epoch (origin for dates) for datetime calculations.
269
+
270
+ The default epoch is :rc:`date.epoch`.
271
+
272
+ If microsecond accuracy is desired, the date being plotted needs to be
273
+ within approximately 70 years of the epoch. Matplotlib internally
274
+ represents dates as days since the epoch, so floating point dynamic
275
+ range needs to be within a factor of 2^52.
276
+
277
+ `~.dates.set_epoch` must be called before any dates are converted
278
+ (i.e. near the import section) or a RuntimeError will be raised.
279
+
280
+ See also :doc:`/gallery/ticks/date_precision_and_epochs`.
281
+
282
+ Parameters
283
+ ----------
284
+ epoch : str
285
+ valid UTC date parsable by `numpy.datetime64` (do not include
286
+ timezone).
287
+
288
+ """
289
+ global _epoch
290
+ if _epoch is not None:
291
+ raise RuntimeError('set_epoch must be called before dates plotted.')
292
+ _epoch = epoch
293
+
294
+
295
+ def get_epoch():
296
+ """
297
+ Get the epoch used by `.dates`.
298
+
299
+ Returns
300
+ -------
301
+ epoch : str
302
+ String for the epoch (parsable by `numpy.datetime64`).
303
+ """
304
+ global _epoch
305
+
306
+ _epoch = mpl._val_or_rc(_epoch, 'date.epoch')
307
+ return _epoch
308
+
309
+
310
+ def _dt64_to_ordinalf(d):
311
+ """
312
+ Convert `numpy.datetime64` or an `numpy.ndarray` of those types to
313
+ Gregorian date as UTC float relative to the epoch (see `.get_epoch`).
314
+ Roundoff is float64 precision. Practically: microseconds for dates
315
+ between 290301 BC, 294241 AD, milliseconds for larger dates
316
+ (see `numpy.datetime64`).
317
+ """
318
+
319
+ # the "extra" ensures that we at least allow the dynamic range out to
320
+ # seconds. That should get out to +/-2e11 years.
321
+ dseconds = d.astype('datetime64[s]')
322
+ extra = (d - dseconds).astype('timedelta64[ns]')
323
+ t0 = np.datetime64(get_epoch(), 's')
324
+ dt = (dseconds - t0).astype(np.float64)
325
+ dt += extra.astype(np.float64) / 1.0e9
326
+ dt = dt / SEC_PER_DAY
327
+
328
+ NaT_int = np.datetime64('NaT').astype(np.int64)
329
+ d_int = d.astype(np.int64)
330
+ dt[d_int == NaT_int] = np.nan
331
+ return dt
332
+
333
+
334
+ def _from_ordinalf(x, tz=None):
335
+ """
336
+ Convert Gregorian float of the date, preserving hours, minutes,
337
+ seconds and microseconds. Return value is a `.datetime`.
338
+
339
+ The input date *x* is a float in ordinal days at UTC, and the output will
340
+ be the specified `.datetime` object corresponding to that time in
341
+ timezone *tz*, or if *tz* is ``None``, in the timezone specified in
342
+ :rc:`timezone`.
343
+ """
344
+
345
+ tz = _get_tzinfo(tz)
346
+
347
+ dt = (np.datetime64(get_epoch()) +
348
+ np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us'))
349
+ if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'):
350
+ raise ValueError(f'Date ordinal {x} converts to {dt} (using '
351
+ f'epoch {get_epoch()}), but Matplotlib dates must be '
352
+ 'between year 0001 and 9999.')
353
+ # convert from datetime64 to datetime:
354
+ dt = dt.tolist()
355
+
356
+ # datetime64 is always UTC:
357
+ dt = dt.replace(tzinfo=dateutil.tz.gettz('UTC'))
358
+ # but maybe we are working in a different timezone so move.
359
+ dt = dt.astimezone(tz)
360
+ # fix round off errors
361
+ if np.abs(x) > 70 * 365:
362
+ # if x is big, round off to nearest twenty microseconds.
363
+ # This avoids floating point roundoff error
364
+ ms = round(dt.microsecond / 20) * 20
365
+ if ms == 1000000:
366
+ dt = dt.replace(microsecond=0) + datetime.timedelta(seconds=1)
367
+ else:
368
+ dt = dt.replace(microsecond=ms)
369
+
370
+ return dt
371
+
372
+
373
+ # a version of _from_ordinalf that can operate on numpy arrays
374
+ _from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O")
375
+ # a version of dateutil.parser.parse that can operate on numpy arrays
376
+ _dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)
377
+
378
+
379
+ def datestr2num(d, default=None):
380
+ """
381
+ Convert a date string to a datenum using `dateutil.parser.parse`.
382
+
383
+ Parameters
384
+ ----------
385
+ d : str or sequence of str
386
+ The dates to convert.
387
+
388
+ default : datetime.datetime, optional
389
+ The default date to use when fields are missing in *d*.
390
+ """
391
+ if isinstance(d, str):
392
+ dt = dateutil.parser.parse(d, default=default)
393
+ return date2num(dt)
394
+ else:
395
+ if default is not None:
396
+ d = [date2num(dateutil.parser.parse(s, default=default))
397
+ for s in d]
398
+ return np.asarray(d)
399
+ d = np.asarray(d)
400
+ if not d.size:
401
+ return d
402
+ return date2num(_dateutil_parser_parse_np_vectorized(d))
403
+
404
+
405
+ def date2num(d):
406
+ """
407
+ Convert datetime objects to Matplotlib dates.
408
+
409
+ Parameters
410
+ ----------
411
+ d : `datetime.datetime` or `numpy.datetime64` or sequences of these
412
+
413
+ Returns
414
+ -------
415
+ float or sequence of floats
416
+ Number of days since the epoch. See `.get_epoch` for the
417
+ epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. If
418
+ the epoch is "1970-01-01T00:00:00" (default) then noon Jan 1 1970
419
+ ("1970-01-01T12:00:00") returns 0.5.
420
+
421
+ Notes
422
+ -----
423
+ The Gregorian calendar is assumed; this is not universal practice.
424
+ For details see the module docstring.
425
+ """
426
+ # Unpack in case of e.g. Pandas or xarray object
427
+ d = cbook._unpack_to_numpy(d)
428
+
429
+ # make an iterable, but save state to unpack later:
430
+ iterable = np.iterable(d)
431
+ if not iterable:
432
+ d = [d]
433
+
434
+ masked = np.ma.is_masked(d)
435
+ mask = np.ma.getmask(d)
436
+ d = np.asarray(d)
437
+
438
+ # convert to datetime64 arrays, if not already:
439
+ if not np.issubdtype(d.dtype, np.datetime64):
440
+ # datetime arrays
441
+ if not d.size:
442
+ # deals with an empty array...
443
+ return d
444
+ tzi = getattr(d[0], 'tzinfo', None)
445
+ if tzi is not None:
446
+ # make datetime naive:
447
+ d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d]
448
+ d = np.asarray(d)
449
+ d = d.astype('datetime64[us]')
450
+
451
+ d = np.ma.masked_array(d, mask=mask) if masked else d
452
+ d = _dt64_to_ordinalf(d)
453
+
454
+ return d if iterable else d[0]
455
+
456
+
457
+ def num2date(x, tz=None):
458
+ """
459
+ Convert Matplotlib dates to `~datetime.datetime` objects.
460
+
461
+ Parameters
462
+ ----------
463
+ x : float or sequence of floats
464
+ Number of days (fraction part represents hours, minutes, seconds)
465
+ since the epoch. See `.get_epoch` for the
466
+ epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`.
467
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
468
+ Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`.
469
+
470
+ Returns
471
+ -------
472
+ `~datetime.datetime` or sequence of `~datetime.datetime`
473
+ Dates are returned in timezone *tz*.
474
+
475
+ If *x* is a sequence, a sequence of `~datetime.datetime` objects will
476
+ be returned.
477
+
478
+ Notes
479
+ -----
480
+ The Gregorian calendar is assumed; this is not universal practice.
481
+ For details, see the module docstring.
482
+ """
483
+ tz = _get_tzinfo(tz)
484
+ return _from_ordinalf_np_vectorized(x, tz).tolist()
485
+
486
+
487
+ _ordinalf_to_timedelta_np_vectorized = np.vectorize(
488
+ lambda x: datetime.timedelta(days=x), otypes="O")
489
+
490
+
491
+ def num2timedelta(x):
492
+ """
493
+ Convert number of days to a `~datetime.timedelta` object.
494
+
495
+ If *x* is a sequence, a sequence of `~datetime.timedelta` objects will
496
+ be returned.
497
+
498
+ Parameters
499
+ ----------
500
+ x : float, sequence of floats
501
+ Number of days. The fraction part represents hours, minutes, seconds.
502
+
503
+ Returns
504
+ -------
505
+ `datetime.timedelta` or list[`datetime.timedelta`]
506
+ """
507
+ return _ordinalf_to_timedelta_np_vectorized(x).tolist()
508
+
509
+
510
+ def drange(dstart, dend, delta):
511
+ """
512
+ Return a sequence of equally spaced Matplotlib dates.
513
+
514
+ The dates start at *dstart* and reach up to, but not including *dend*.
515
+ They are spaced by *delta*.
516
+
517
+ Parameters
518
+ ----------
519
+ dstart, dend : `~datetime.datetime`
520
+ The date limits.
521
+ delta : `datetime.timedelta`
522
+ Spacing of the dates.
523
+
524
+ Returns
525
+ -------
526
+ `numpy.array`
527
+ A list floats representing Matplotlib dates.
528
+
529
+ """
530
+ f1 = date2num(dstart)
531
+ f2 = date2num(dend)
532
+ step = delta.total_seconds() / SEC_PER_DAY
533
+
534
+ # calculate the difference between dend and dstart in times of delta
535
+ num = int(np.ceil((f2 - f1) / step))
536
+
537
+ # calculate end of the interval which will be generated
538
+ dinterval_end = dstart + num * delta
539
+
540
+ # ensure, that an half open interval will be generated [dstart, dend)
541
+ if dinterval_end >= dend:
542
+ # if the endpoint is greater than or equal to dend,
543
+ # just subtract one delta
544
+ dinterval_end -= delta
545
+ num -= 1
546
+
547
+ f2 = date2num(dinterval_end) # new float-endpoint
548
+ return np.linspace(f1, f2, num + 1)
549
+
550
+
551
+ def _wrap_in_tex(text):
552
+ p = r'([a-zA-Z]+)'
553
+ ret_text = re.sub(p, r'}$\1$\\mathdefault{', text)
554
+
555
+ # Braces ensure symbols are not spaced like binary operators.
556
+ ret_text = ret_text.replace('-', '{-}').replace(':', '{:}')
557
+ # To not concatenate space between numbers.
558
+ ret_text = ret_text.replace(' ', r'\;')
559
+ ret_text = '$\\mathdefault{' + ret_text + '}$'
560
+ ret_text = ret_text.replace('$\\mathdefault{}$', '')
561
+ return ret_text
562
+
563
+
564
+ ## date tick locators and formatters ###
565
+
566
+
567
+ class DateFormatter(ticker.Formatter):
568
+ """
569
+ Format a tick (in days since the epoch) with a
570
+ `~datetime.datetime.strftime` format string.
571
+ """
572
+
573
+ def __init__(self, fmt, tz=None, *, usetex=None):
574
+ """
575
+ Parameters
576
+ ----------
577
+ fmt : str
578
+ `~datetime.datetime.strftime` format string
579
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
580
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
581
+ usetex : bool, default: :rc:`text.usetex`
582
+ To enable/disable the use of TeX's math mode for rendering the
583
+ results of the formatter.
584
+ """
585
+ self.tz = _get_tzinfo(tz)
586
+ self.fmt = fmt
587
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
588
+
589
+ def __call__(self, x, pos=0):
590
+ result = num2date(x, self.tz).strftime(self.fmt)
591
+ return _wrap_in_tex(result) if self._usetex else result
592
+
593
+ def set_tzinfo(self, tz):
594
+ self.tz = _get_tzinfo(tz)
595
+
596
+
597
+ class ConciseDateFormatter(ticker.Formatter):
598
+ """
599
+ A `.Formatter` which attempts to figure out the best format to use for the
600
+ date, and to make it as compact as possible, but still be complete. This is
601
+ most useful when used with the `AutoDateLocator`::
602
+
603
+ >>> locator = AutoDateLocator()
604
+ >>> formatter = ConciseDateFormatter(locator)
605
+
606
+ Parameters
607
+ ----------
608
+ locator : `.ticker.Locator`
609
+ Locator that this axis is using.
610
+
611
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
612
+ Ticks timezone, passed to `.dates.num2date`.
613
+
614
+ formats : list of 6 strings, optional
615
+ Format strings for 6 levels of tick labelling: mostly years,
616
+ months, days, hours, minutes, and seconds. Strings use
617
+ the same format codes as `~datetime.datetime.strftime`. Default is
618
+ ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']``
619
+
620
+ zero_formats : list of 6 strings, optional
621
+ Format strings for tick labels that are "zeros" for a given tick
622
+ level. For instance, if most ticks are months, ticks around 1 Jan 2005
623
+ will be labeled "Dec", "2005", "Feb". The default is
624
+ ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']``
625
+
626
+ offset_formats : list of 6 strings, optional
627
+ Format strings for the 6 levels that is applied to the "offset"
628
+ string found on the right side of an x-axis, or top of a y-axis.
629
+ Combined with the tick labels this should completely specify the
630
+ date. The default is::
631
+
632
+ ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M']
633
+
634
+ show_offset : bool, default: True
635
+ Whether to show the offset or not.
636
+
637
+ usetex : bool, default: :rc:`text.usetex`
638
+ To enable/disable the use of TeX's math mode for rendering the results
639
+ of the formatter.
640
+
641
+ Examples
642
+ --------
643
+ See :doc:`/gallery/ticks/date_concise_formatter`
644
+
645
+ .. plot::
646
+
647
+ import datetime
648
+ import matplotlib.dates as mdates
649
+
650
+ base = datetime.datetime(2005, 2, 1)
651
+ dates = np.array([base + datetime.timedelta(hours=(2 * i))
652
+ for i in range(732)])
653
+ N = len(dates)
654
+ np.random.seed(19680801)
655
+ y = np.cumsum(np.random.randn(N))
656
+
657
+ fig, ax = plt.subplots(constrained_layout=True)
658
+ locator = mdates.AutoDateLocator()
659
+ formatter = mdates.ConciseDateFormatter(locator)
660
+ ax.xaxis.set_major_locator(locator)
661
+ ax.xaxis.set_major_formatter(formatter)
662
+
663
+ ax.plot(dates, y)
664
+ ax.set_title('Concise Date Formatter')
665
+
666
+ """
667
+
668
+ def __init__(self, locator, tz=None, formats=None, offset_formats=None,
669
+ zero_formats=None, show_offset=True, *, usetex=None):
670
+ """
671
+ Autoformat the date labels. The default format is used to form an
672
+ initial string, and then redundant elements are removed.
673
+ """
674
+ self._locator = locator
675
+ self._tz = tz
676
+ self.defaultfmt = '%Y'
677
+ # there are 6 levels with each level getting a specific format
678
+ # 0: mostly years, 1: months, 2: days,
679
+ # 3: hours, 4: minutes, 5: seconds
680
+ if formats:
681
+ if len(formats) != 6:
682
+ raise ValueError('formats argument must be a list of '
683
+ '6 format strings (or None)')
684
+ self.formats = formats
685
+ else:
686
+ self.formats = ['%Y', # ticks are mostly years
687
+ '%b', # ticks are mostly months
688
+ '%d', # ticks are mostly days
689
+ '%H:%M', # hrs
690
+ '%H:%M', # min
691
+ '%S.%f', # secs
692
+ ]
693
+ # fmt for zeros ticks at this level. These are
694
+ # ticks that should be labeled w/ info the level above.
695
+ # like 1 Jan can just be labelled "Jan". 02:02:00 can
696
+ # just be labeled 02:02.
697
+ if zero_formats:
698
+ if len(zero_formats) != 6:
699
+ raise ValueError('zero_formats argument must be a list of '
700
+ '6 format strings (or None)')
701
+ self.zero_formats = zero_formats
702
+ elif formats:
703
+ # use the users formats for the zero tick formats
704
+ self.zero_formats = [''] + self.formats[:-1]
705
+ else:
706
+ # make the defaults a bit nicer:
707
+ self.zero_formats = [''] + self.formats[:-1]
708
+ self.zero_formats[3] = '%b-%d'
709
+
710
+ if offset_formats:
711
+ if len(offset_formats) != 6:
712
+ raise ValueError('offset_formats argument must be a list of '
713
+ '6 format strings (or None)')
714
+ self.offset_formats = offset_formats
715
+ else:
716
+ self.offset_formats = ['',
717
+ '%Y',
718
+ '%Y-%b',
719
+ '%Y-%b-%d',
720
+ '%Y-%b-%d',
721
+ '%Y-%b-%d %H:%M']
722
+ self.offset_string = ''
723
+ self.show_offset = show_offset
724
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
725
+
726
+ def __call__(self, x, pos=None):
727
+ formatter = DateFormatter(self.defaultfmt, self._tz,
728
+ usetex=self._usetex)
729
+ return formatter(x, pos=pos)
730
+
731
+ def format_ticks(self, values):
732
+ tickdatetime = [num2date(value, tz=self._tz) for value in values]
733
+ tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime])
734
+
735
+ # basic algorithm:
736
+ # 1) only display a part of the date if it changes over the ticks.
737
+ # 2) don't display the smaller part of the date if:
738
+ # it is always the same or if it is the start of the
739
+ # year, month, day etc.
740
+ # fmt for most ticks at this level
741
+ fmts = self.formats
742
+ # format beginnings of days, months, years, etc.
743
+ zerofmts = self.zero_formats
744
+ # offset fmt are for the offset in the upper left of the
745
+ # or lower right of the axis.
746
+ offsetfmts = self.offset_formats
747
+ show_offset = self.show_offset
748
+
749
+ # determine the level we will label at:
750
+ # mostly 0: years, 1: months, 2: days,
751
+ # 3: hours, 4: minutes, 5: seconds, 6: microseconds
752
+ for level in range(5, -1, -1):
753
+ unique = np.unique(tickdate[:, level])
754
+ if len(unique) > 1:
755
+ # if 1 is included in unique, the year is shown in ticks
756
+ if level < 2 and np.any(unique == 1):
757
+ show_offset = False
758
+ break
759
+ elif level == 0:
760
+ # all tickdate are the same, so only micros might be different
761
+ # set to the most precise (6: microseconds doesn't exist...)
762
+ level = 5
763
+
764
+ # level is the basic level we will label at.
765
+ # now loop through and decide the actual ticklabels
766
+ zerovals = [0, 1, 1, 0, 0, 0, 0]
767
+ labels = [''] * len(tickdate)
768
+ for nn in range(len(tickdate)):
769
+ if level < 5:
770
+ if tickdate[nn][level] == zerovals[level]:
771
+ fmt = zerofmts[level]
772
+ else:
773
+ fmt = fmts[level]
774
+ else:
775
+ # special handling for seconds + microseconds
776
+ if (tickdatetime[nn].second == tickdatetime[nn].microsecond
777
+ == 0):
778
+ fmt = zerofmts[level]
779
+ else:
780
+ fmt = fmts[level]
781
+ labels[nn] = tickdatetime[nn].strftime(fmt)
782
+
783
+ # special handling of seconds and microseconds:
784
+ # strip extra zeros and decimal if possible.
785
+ # this is complicated by two factors. 1) we have some level-4 strings
786
+ # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the
787
+ # same number of decimals for each string (i.e. 0.5 and 1.0).
788
+ if level >= 5:
789
+ trailing_zeros = min(
790
+ (len(s) - len(s.rstrip('0')) for s in labels if '.' in s),
791
+ default=None)
792
+ if trailing_zeros:
793
+ for nn in range(len(labels)):
794
+ if '.' in labels[nn]:
795
+ labels[nn] = labels[nn][:-trailing_zeros].rstrip('.')
796
+
797
+ if show_offset:
798
+ # set the offset string:
799
+ if (self._locator.axis and
800
+ self._locator.axis.__name__ in ('xaxis', 'yaxis')
801
+ and self._locator.axis.get_inverted()):
802
+ self.offset_string = tickdatetime[0].strftime(offsetfmts[level])
803
+ else:
804
+ self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
805
+ if self._usetex:
806
+ self.offset_string = _wrap_in_tex(self.offset_string)
807
+ else:
808
+ self.offset_string = ''
809
+
810
+ if self._usetex:
811
+ return [_wrap_in_tex(l) for l in labels]
812
+ else:
813
+ return labels
814
+
815
+ def get_offset(self):
816
+ return self.offset_string
817
+
818
+ def format_data_short(self, value):
819
+ return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S')
820
+
821
+
822
+ class AutoDateFormatter(ticker.Formatter):
823
+ """
824
+ A `.Formatter` which attempts to figure out the best format to use. This
825
+ is most useful when used with the `AutoDateLocator`.
826
+
827
+ `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the
828
+ interval in days between one major tick) to format strings; this dictionary
829
+ defaults to ::
830
+
831
+ self.scaled = {
832
+ DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
833
+ DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
834
+ 1: rcParams['date.autoformatter.day'],
835
+ 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
836
+ 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'],
837
+ 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'],
838
+ 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'],
839
+ }
840
+
841
+ The formatter uses the format string corresponding to the lowest key in
842
+ the dictionary that is greater or equal to the current scale. Dictionary
843
+ entries can be customized::
844
+
845
+ locator = AutoDateLocator()
846
+ formatter = AutoDateFormatter(locator)
847
+ formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec
848
+
849
+ Custom callables can also be used instead of format strings. The following
850
+ example shows how to use a custom format function to strip trailing zeros
851
+ from decimal seconds and adds the date to the first ticklabel::
852
+
853
+ def my_format_function(x, pos=None):
854
+ x = matplotlib.dates.num2date(x)
855
+ if pos == 0:
856
+ fmt = '%D %H:%M:%S.%f'
857
+ else:
858
+ fmt = '%H:%M:%S.%f'
859
+ label = x.strftime(fmt)
860
+ label = label.rstrip("0")
861
+ label = label.rstrip(".")
862
+ return label
863
+
864
+ formatter.scaled[1/(24*60)] = my_format_function
865
+ """
866
+
867
+ # This can be improved by providing some user-level direction on
868
+ # how to choose the best format (precedence, etc.).
869
+
870
+ # Perhaps a 'struct' that has a field for each time-type where a
871
+ # zero would indicate "don't show" and a number would indicate
872
+ # "show" with some sort of priority. Same priorities could mean
873
+ # show all with the same priority.
874
+
875
+ # Or more simply, perhaps just a format string for each
876
+ # possibility...
877
+
878
+ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *,
879
+ usetex=None):
880
+ """
881
+ Autoformat the date labels.
882
+
883
+ Parameters
884
+ ----------
885
+ locator : `.ticker.Locator`
886
+ Locator that this axis is using.
887
+
888
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
889
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
890
+
891
+ defaultfmt : str
892
+ The default format to use if none of the values in ``self.scaled``
893
+ are greater than the unit returned by ``locator._get_unit()``.
894
+
895
+ usetex : bool, default: :rc:`text.usetex`
896
+ To enable/disable the use of TeX's math mode for rendering the
897
+ results of the formatter. If any entries in ``self.scaled`` are set
898
+ as functions, then it is up to the customized function to enable or
899
+ disable TeX's math mode itself.
900
+ """
901
+ self._locator = locator
902
+ self._tz = tz
903
+ self.defaultfmt = defaultfmt
904
+ self._formatter = DateFormatter(self.defaultfmt, tz)
905
+ rcParams = mpl.rcParams
906
+ self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
907
+ self.scaled = {
908
+ DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
909
+ DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
910
+ 1: rcParams['date.autoformatter.day'],
911
+ 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
912
+ 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'],
913
+ 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'],
914
+ 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond']
915
+ }
916
+
917
+ def _set_locator(self, locator):
918
+ self._locator = locator
919
+
920
+ def __call__(self, x, pos=None):
921
+ try:
922
+ locator_unit_scale = float(self._locator._get_unit())
923
+ except AttributeError:
924
+ locator_unit_scale = 1
925
+ # Pick the first scale which is greater than the locator unit.
926
+ fmt = next((fmt for scale, fmt in sorted(self.scaled.items())
927
+ if scale >= locator_unit_scale),
928
+ self.defaultfmt)
929
+
930
+ if isinstance(fmt, str):
931
+ self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex)
932
+ result = self._formatter(x, pos)
933
+ elif callable(fmt):
934
+ result = fmt(x, pos)
935
+ else:
936
+ raise TypeError(f'Unexpected type passed to {self!r}.')
937
+
938
+ return result
939
+
940
+
941
+ class rrulewrapper:
942
+ """
943
+ A simple wrapper around a `dateutil.rrule` allowing flexible
944
+ date tick specifications.
945
+ """
946
+ def __init__(self, freq, tzinfo=None, **kwargs):
947
+ """
948
+ Parameters
949
+ ----------
950
+ freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY}
951
+ Tick frequency. These constants are defined in `dateutil.rrule`,
952
+ but they are accessible from `matplotlib.dates` as well.
953
+ tzinfo : `datetime.tzinfo`, optional
954
+ Time zone information. The default is None.
955
+ **kwargs
956
+ Additional keyword arguments are passed to the `dateutil.rrule`.
957
+ """
958
+ kwargs['freq'] = freq
959
+ self._base_tzinfo = tzinfo
960
+
961
+ self._update_rrule(**kwargs)
962
+
963
+ def set(self, **kwargs):
964
+ """Set parameters for an existing wrapper."""
965
+ self._construct.update(kwargs)
966
+
967
+ self._update_rrule(**self._construct)
968
+
969
+ def _update_rrule(self, **kwargs):
970
+ tzinfo = self._base_tzinfo
971
+
972
+ # rrule does not play nicely with timezones - especially pytz time
973
+ # zones, it's best to use naive zones and attach timezones once the
974
+ # datetimes are returned
975
+ if 'dtstart' in kwargs:
976
+ dtstart = kwargs['dtstart']
977
+ if dtstart.tzinfo is not None:
978
+ if tzinfo is None:
979
+ tzinfo = dtstart.tzinfo
980
+ else:
981
+ dtstart = dtstart.astimezone(tzinfo)
982
+
983
+ kwargs['dtstart'] = dtstart.replace(tzinfo=None)
984
+
985
+ if 'until' in kwargs:
986
+ until = kwargs['until']
987
+ if until.tzinfo is not None:
988
+ if tzinfo is not None:
989
+ until = until.astimezone(tzinfo)
990
+ else:
991
+ raise ValueError('until cannot be aware if dtstart '
992
+ 'is naive and tzinfo is None')
993
+
994
+ kwargs['until'] = until.replace(tzinfo=None)
995
+
996
+ self._construct = kwargs.copy()
997
+ self._tzinfo = tzinfo
998
+ self._rrule = rrule(**self._construct)
999
+
1000
+ def _attach_tzinfo(self, dt, tzinfo):
1001
+ # pytz zones are attached by "localizing" the datetime
1002
+ if hasattr(tzinfo, 'localize'):
1003
+ return tzinfo.localize(dt, is_dst=True)
1004
+
1005
+ return dt.replace(tzinfo=tzinfo)
1006
+
1007
+ def _aware_return_wrapper(self, f, returns_list=False):
1008
+ """Decorator function that allows rrule methods to handle tzinfo."""
1009
+ # This is only necessary if we're actually attaching a tzinfo
1010
+ if self._tzinfo is None:
1011
+ return f
1012
+
1013
+ # All datetime arguments must be naive. If they are not naive, they are
1014
+ # converted to the _tzinfo zone before dropping the zone.
1015
+ def normalize_arg(arg):
1016
+ if isinstance(arg, datetime.datetime) and arg.tzinfo is not None:
1017
+ if arg.tzinfo is not self._tzinfo:
1018
+ arg = arg.astimezone(self._tzinfo)
1019
+
1020
+ return arg.replace(tzinfo=None)
1021
+
1022
+ return arg
1023
+
1024
+ def normalize_args(args, kwargs):
1025
+ args = tuple(normalize_arg(arg) for arg in args)
1026
+ kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()}
1027
+
1028
+ return args, kwargs
1029
+
1030
+ # There are two kinds of functions we care about - ones that return
1031
+ # dates and ones that return lists of dates.
1032
+ if not returns_list:
1033
+ def inner_func(*args, **kwargs):
1034
+ args, kwargs = normalize_args(args, kwargs)
1035
+ dt = f(*args, **kwargs)
1036
+ return self._attach_tzinfo(dt, self._tzinfo)
1037
+ else:
1038
+ def inner_func(*args, **kwargs):
1039
+ args, kwargs = normalize_args(args, kwargs)
1040
+ dts = f(*args, **kwargs)
1041
+ return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts]
1042
+
1043
+ return functools.wraps(f)(inner_func)
1044
+
1045
+ def __getattr__(self, name):
1046
+ if name in self.__dict__:
1047
+ return self.__dict__[name]
1048
+
1049
+ f = getattr(self._rrule, name)
1050
+
1051
+ if name in {'after', 'before'}:
1052
+ return self._aware_return_wrapper(f)
1053
+ elif name in {'xafter', 'xbefore', 'between'}:
1054
+ return self._aware_return_wrapper(f, returns_list=True)
1055
+ else:
1056
+ return f
1057
+
1058
+ def __setstate__(self, state):
1059
+ self.__dict__.update(state)
1060
+
1061
+
1062
+ class DateLocator(ticker.Locator):
1063
+ """
1064
+ Determines the tick locations when plotting dates.
1065
+
1066
+ This class is subclassed by other Locators and
1067
+ is not meant to be used on its own.
1068
+ """
1069
+ hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0}
1070
+
1071
+ def __init__(self, tz=None):
1072
+ """
1073
+ Parameters
1074
+ ----------
1075
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1076
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1077
+ """
1078
+ self.tz = _get_tzinfo(tz)
1079
+
1080
+ def set_tzinfo(self, tz):
1081
+ """
1082
+ Set timezone info.
1083
+
1084
+ Parameters
1085
+ ----------
1086
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1087
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1088
+ """
1089
+ self.tz = _get_tzinfo(tz)
1090
+
1091
+ def datalim_to_dt(self):
1092
+ """Convert axis data interval to datetime objects."""
1093
+ dmin, dmax = self.axis.get_data_interval()
1094
+ if dmin > dmax:
1095
+ dmin, dmax = dmax, dmin
1096
+
1097
+ return num2date(dmin, self.tz), num2date(dmax, self.tz)
1098
+
1099
+ def viewlim_to_dt(self):
1100
+ """Convert the view interval to datetime objects."""
1101
+ vmin, vmax = self.axis.get_view_interval()
1102
+ if vmin > vmax:
1103
+ vmin, vmax = vmax, vmin
1104
+ return num2date(vmin, self.tz), num2date(vmax, self.tz)
1105
+
1106
+ def _get_unit(self):
1107
+ """
1108
+ Return how many days a unit of the locator is; used for
1109
+ intelligent autoscaling.
1110
+ """
1111
+ return 1
1112
+
1113
+ def _get_interval(self):
1114
+ """
1115
+ Return the number of units for each tick.
1116
+ """
1117
+ return 1
1118
+
1119
+ def nonsingular(self, vmin, vmax):
1120
+ """
1121
+ Given the proposed upper and lower extent, adjust the range
1122
+ if it is too close to being singular (i.e. a range of ~0).
1123
+ """
1124
+ if not np.isfinite(vmin) or not np.isfinite(vmax):
1125
+ # Except if there is no data, then use 1970 as default.
1126
+ return (date2num(datetime.date(1970, 1, 1)),
1127
+ date2num(datetime.date(1970, 1, 2)))
1128
+ if vmax < vmin:
1129
+ vmin, vmax = vmax, vmin
1130
+ unit = self._get_unit()
1131
+ interval = self._get_interval()
1132
+ if abs(vmax - vmin) < 1e-6:
1133
+ vmin -= 2 * unit * interval
1134
+ vmax += 2 * unit * interval
1135
+ return vmin, vmax
1136
+
1137
+
1138
+ class RRuleLocator(DateLocator):
1139
+ # use the dateutil rrule instance
1140
+
1141
+ def __init__(self, o, tz=None):
1142
+ super().__init__(tz)
1143
+ self.rule = o
1144
+
1145
+ def __call__(self):
1146
+ # if no data have been set, this will tank with a ValueError
1147
+ try:
1148
+ dmin, dmax = self.viewlim_to_dt()
1149
+ except ValueError:
1150
+ return []
1151
+
1152
+ return self.tick_values(dmin, dmax)
1153
+
1154
+ def tick_values(self, vmin, vmax):
1155
+ start, stop = self._create_rrule(vmin, vmax)
1156
+ dates = self.rule.between(start, stop, True)
1157
+ if len(dates) == 0:
1158
+ return date2num([vmin, vmax])
1159
+ return self.raise_if_exceeds(date2num(dates))
1160
+
1161
+ def _create_rrule(self, vmin, vmax):
1162
+ # set appropriate rrule dtstart and until and return
1163
+ # start and end
1164
+ delta = relativedelta(vmax, vmin)
1165
+
1166
+ # We need to cap at the endpoints of valid datetime
1167
+ try:
1168
+ start = vmin - delta
1169
+ except (ValueError, OverflowError):
1170
+ # cap
1171
+ start = datetime.datetime(1, 1, 1, 0, 0, 0,
1172
+ tzinfo=datetime.timezone.utc)
1173
+
1174
+ try:
1175
+ stop = vmax + delta
1176
+ except (ValueError, OverflowError):
1177
+ # cap
1178
+ stop = datetime.datetime(9999, 12, 31, 23, 59, 59,
1179
+ tzinfo=datetime.timezone.utc)
1180
+
1181
+ self.rule.set(dtstart=start, until=stop)
1182
+
1183
+ return vmin, vmax
1184
+
1185
+ def _get_unit(self):
1186
+ # docstring inherited
1187
+ freq = self.rule._rrule._freq
1188
+ return self.get_unit_generic(freq)
1189
+
1190
+ @staticmethod
1191
+ def get_unit_generic(freq):
1192
+ if freq == YEARLY:
1193
+ return DAYS_PER_YEAR
1194
+ elif freq == MONTHLY:
1195
+ return DAYS_PER_MONTH
1196
+ elif freq == WEEKLY:
1197
+ return DAYS_PER_WEEK
1198
+ elif freq == DAILY:
1199
+ return 1.0
1200
+ elif freq == HOURLY:
1201
+ return 1.0 / HOURS_PER_DAY
1202
+ elif freq == MINUTELY:
1203
+ return 1.0 / MINUTES_PER_DAY
1204
+ elif freq == SECONDLY:
1205
+ return 1.0 / SEC_PER_DAY
1206
+ else:
1207
+ # error
1208
+ return -1 # or should this just return '1'?
1209
+
1210
+ def _get_interval(self):
1211
+ return self.rule._rrule._interval
1212
+
1213
+
1214
+ class AutoDateLocator(DateLocator):
1215
+ """
1216
+ On autoscale, this class picks the best `DateLocator` to set the view
1217
+ limits and the tick locations.
1218
+
1219
+ Attributes
1220
+ ----------
1221
+ intervald : dict
1222
+
1223
+ Mapping of tick frequencies to multiples allowed for that ticking.
1224
+ The default is ::
1225
+
1226
+ self.intervald = {
1227
+ YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
1228
+ 1000, 2000, 4000, 5000, 10000],
1229
+ MONTHLY : [1, 2, 3, 4, 6],
1230
+ DAILY : [1, 2, 3, 7, 14, 21],
1231
+ HOURLY : [1, 2, 3, 4, 6, 12],
1232
+ MINUTELY: [1, 5, 10, 15, 30],
1233
+ SECONDLY: [1, 5, 10, 15, 30],
1234
+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500,
1235
+ 1000, 2000, 5000, 10000, 20000, 50000,
1236
+ 100000, 200000, 500000, 1000000],
1237
+ }
1238
+
1239
+ where the keys are defined in `dateutil.rrule`.
1240
+
1241
+ The interval is used to specify multiples that are appropriate for
1242
+ the frequency of ticking. For instance, every 7 days is sensible
1243
+ for daily ticks, but for minutes/seconds, 15 or 30 make sense.
1244
+
1245
+ When customizing, you should only modify the values for the existing
1246
+ keys. You should not add or delete entries.
1247
+
1248
+ Example for forcing ticks every 3 hours::
1249
+
1250
+ locator = AutoDateLocator()
1251
+ locator.intervald[HOURLY] = [3] # only show every 3 hours
1252
+ """
1253
+
1254
+ def __init__(self, tz=None, minticks=5, maxticks=None,
1255
+ interval_multiples=True):
1256
+ """
1257
+ Parameters
1258
+ ----------
1259
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1260
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1261
+ minticks : int
1262
+ The minimum number of ticks desired; controls whether ticks occur
1263
+ yearly, monthly, etc.
1264
+ maxticks : int
1265
+ The maximum number of ticks desired; controls the interval between
1266
+ ticks (ticking every other, every 3, etc.). For fine-grained
1267
+ control, this can be a dictionary mapping individual rrule
1268
+ frequency constants (YEARLY, MONTHLY, etc.) to their own maximum
1269
+ number of ticks. This can be used to keep the number of ticks
1270
+ appropriate to the format chosen in `AutoDateFormatter`. Any
1271
+ frequency not specified in this dictionary is given a default
1272
+ value.
1273
+ interval_multiples : bool, default: True
1274
+ Whether ticks should be chosen to be multiple of the interval,
1275
+ locking them to 'nicer' locations. For example, this will force
1276
+ the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done
1277
+ at 6 hour intervals.
1278
+ """
1279
+ super().__init__(tz=tz)
1280
+ self._freq = YEARLY
1281
+ self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY,
1282
+ SECONDLY, MICROSECONDLY]
1283
+ self.minticks = minticks
1284
+
1285
+ self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
1286
+ MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
1287
+ if maxticks is not None:
1288
+ try:
1289
+ self.maxticks.update(maxticks)
1290
+ except TypeError:
1291
+ # Assume we were given an integer. Use this as the maximum
1292
+ # number of ticks for every frequency and create a
1293
+ # dictionary for this
1294
+ self.maxticks = dict.fromkeys(self._freqs, maxticks)
1295
+ self.interval_multiples = interval_multiples
1296
+ self.intervald = {
1297
+ YEARLY: [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
1298
+ 1000, 2000, 4000, 5000, 10000],
1299
+ MONTHLY: [1, 2, 3, 4, 6],
1300
+ DAILY: [1, 2, 3, 7, 14, 21],
1301
+ HOURLY: [1, 2, 3, 4, 6, 12],
1302
+ MINUTELY: [1, 5, 10, 15, 30],
1303
+ SECONDLY: [1, 5, 10, 15, 30],
1304
+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
1305
+ 5000, 10000, 20000, 50000, 100000, 200000, 500000,
1306
+ 1000000],
1307
+ }
1308
+ if interval_multiples:
1309
+ # Swap "3" for "4" in the DAILY list; If we use 3 we get bad
1310
+ # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1
1311
+ # If we use 4 then we get: 1, 5, ... 25, 29, 1
1312
+ self.intervald[DAILY] = [1, 2, 4, 7, 14]
1313
+
1314
+ self._byranges = [None, range(1, 13), range(1, 32),
1315
+ range(0, 24), range(0, 60), range(0, 60), None]
1316
+
1317
+ def __call__(self):
1318
+ # docstring inherited
1319
+ dmin, dmax = self.viewlim_to_dt()
1320
+ locator = self.get_locator(dmin, dmax)
1321
+ return locator()
1322
+
1323
+ def tick_values(self, vmin, vmax):
1324
+ return self.get_locator(vmin, vmax).tick_values(vmin, vmax)
1325
+
1326
+ def nonsingular(self, vmin, vmax):
1327
+ # whatever is thrown at us, we can scale the unit.
1328
+ # But default nonsingular date plots at an ~4 year period.
1329
+ if not np.isfinite(vmin) or not np.isfinite(vmax):
1330
+ # Except if there is no data, then use 1970 as default.
1331
+ return (date2num(datetime.date(1970, 1, 1)),
1332
+ date2num(datetime.date(1970, 1, 2)))
1333
+ if vmax < vmin:
1334
+ vmin, vmax = vmax, vmin
1335
+ if vmin == vmax:
1336
+ vmin = vmin - DAYS_PER_YEAR * 2
1337
+ vmax = vmax + DAYS_PER_YEAR * 2
1338
+ return vmin, vmax
1339
+
1340
+ def _get_unit(self):
1341
+ if self._freq in [MICROSECONDLY]:
1342
+ return 1. / MUSECONDS_PER_DAY
1343
+ else:
1344
+ return RRuleLocator.get_unit_generic(self._freq)
1345
+
1346
+ def get_locator(self, dmin, dmax):
1347
+ """Pick the best locator based on a distance."""
1348
+ delta = relativedelta(dmax, dmin)
1349
+ tdelta = dmax - dmin
1350
+
1351
+ # take absolute difference
1352
+ if dmin > dmax:
1353
+ delta = -delta
1354
+ tdelta = -tdelta
1355
+ # The following uses a mix of calls to relativedelta and timedelta
1356
+ # methods because there is incomplete overlap in the functionality of
1357
+ # these similar functions, and it's best to avoid doing our own math
1358
+ # whenever possible.
1359
+ numYears = float(delta.years)
1360
+ numMonths = numYears * MONTHS_PER_YEAR + delta.months
1361
+ numDays = tdelta.days # Avoids estimates of days/month, days/year.
1362
+ numHours = numDays * HOURS_PER_DAY + delta.hours
1363
+ numMinutes = numHours * MIN_PER_HOUR + delta.minutes
1364
+ numSeconds = np.floor(tdelta.total_seconds())
1365
+ numMicroseconds = np.floor(tdelta.total_seconds() * 1e6)
1366
+
1367
+ nums = [numYears, numMonths, numDays, numHours, numMinutes,
1368
+ numSeconds, numMicroseconds]
1369
+
1370
+ use_rrule_locator = [True] * 6 + [False]
1371
+
1372
+ # Default setting of bymonth, etc. to pass to rrule
1373
+ # [unused (for year), bymonth, bymonthday, byhour, byminute,
1374
+ # bysecond, unused (for microseconds)]
1375
+ byranges = [None, 1, 1, 0, 0, 0, None]
1376
+
1377
+ # Loop over all the frequencies and try to find one that gives at
1378
+ # least a minticks tick positions. Once this is found, look for
1379
+ # an interval from a list specific to that frequency that gives no
1380
+ # more than maxticks tick positions. Also, set up some ranges
1381
+ # (bymonth, etc.) as appropriate to be passed to rrulewrapper.
1382
+ for i, (freq, num) in enumerate(zip(self._freqs, nums)):
1383
+ # If this particular frequency doesn't give enough ticks, continue
1384
+ if num < self.minticks:
1385
+ # Since we're not using this particular frequency, set
1386
+ # the corresponding by_ to None so the rrule can act as
1387
+ # appropriate
1388
+ byranges[i] = None
1389
+ continue
1390
+
1391
+ # Find the first available interval that doesn't give too many
1392
+ # ticks
1393
+ for interval in self.intervald[freq]:
1394
+ if num <= interval * (self.maxticks[freq] - 1):
1395
+ break
1396
+ else:
1397
+ if not (self.interval_multiples and freq == DAILY):
1398
+ _api.warn_external(
1399
+ f"AutoDateLocator was unable to pick an appropriate "
1400
+ f"interval for this date range. It may be necessary "
1401
+ f"to add an interval value to the AutoDateLocator's "
1402
+ f"intervald dictionary. Defaulting to {interval}.")
1403
+
1404
+ # Set some parameters as appropriate
1405
+ self._freq = freq
1406
+
1407
+ if self._byranges[i] and self.interval_multiples:
1408
+ byranges[i] = self._byranges[i][::interval]
1409
+ if i in (DAILY, WEEKLY):
1410
+ if interval == 14:
1411
+ # just make first and 15th. Avoids 30th.
1412
+ byranges[i] = [1, 15]
1413
+ elif interval == 7:
1414
+ byranges[i] = [1, 8, 15, 22]
1415
+
1416
+ interval = 1
1417
+ else:
1418
+ byranges[i] = self._byranges[i]
1419
+ break
1420
+ else:
1421
+ interval = 1
1422
+
1423
+ if (freq == YEARLY) and self.interval_multiples:
1424
+ locator = YearLocator(interval, tz=self.tz)
1425
+ elif use_rrule_locator[i]:
1426
+ _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges
1427
+ rrule = rrulewrapper(self._freq, interval=interval,
1428
+ dtstart=dmin, until=dmax,
1429
+ bymonth=bymonth, bymonthday=bymonthday,
1430
+ byhour=byhour, byminute=byminute,
1431
+ bysecond=bysecond)
1432
+
1433
+ locator = RRuleLocator(rrule, tz=self.tz)
1434
+ else:
1435
+ locator = MicrosecondLocator(interval, tz=self.tz)
1436
+ if date2num(dmin) > 70 * 365 and interval < 1000:
1437
+ _api.warn_external(
1438
+ 'Plotting microsecond time intervals for dates far from '
1439
+ f'the epoch (time origin: {get_epoch()}) is not well-'
1440
+ 'supported. See matplotlib.dates.set_epoch to change the '
1441
+ 'epoch.')
1442
+
1443
+ locator.set_axis(self.axis)
1444
+ return locator
1445
+
1446
+
1447
+ class YearLocator(RRuleLocator):
1448
+ """
1449
+ Make ticks on a given day of each year that is a multiple of base.
1450
+
1451
+ Examples::
1452
+
1453
+ # Tick every year on Jan 1st
1454
+ locator = YearLocator()
1455
+
1456
+ # Tick every 5 years on July 4th
1457
+ locator = YearLocator(5, month=7, day=4)
1458
+ """
1459
+ def __init__(self, base=1, month=1, day=1, tz=None):
1460
+ """
1461
+ Parameters
1462
+ ----------
1463
+ base : int, default: 1
1464
+ Mark ticks every *base* years.
1465
+ month : int, default: 1
1466
+ The month on which to place the ticks, starting from 1. Default is
1467
+ January.
1468
+ day : int, default: 1
1469
+ The day on which to place the ticks.
1470
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1471
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1472
+ """
1473
+ rule = rrulewrapper(YEARLY, interval=base, bymonth=month,
1474
+ bymonthday=day, **self.hms0d)
1475
+ super().__init__(rule, tz=tz)
1476
+ self.base = ticker._Edge_integer(base, 0)
1477
+
1478
+ def _create_rrule(self, vmin, vmax):
1479
+ # 'start' needs to be a multiple of the interval to create ticks on
1480
+ # interval multiples when the tick frequency is YEARLY
1481
+ ymin = max(self.base.le(vmin.year) * self.base.step, 1)
1482
+ ymax = min(self.base.ge(vmax.year) * self.base.step, 9999)
1483
+
1484
+ c = self.rule._construct
1485
+ replace = {'year': ymin,
1486
+ 'month': c.get('bymonth', 1),
1487
+ 'day': c.get('bymonthday', 1),
1488
+ 'hour': 0, 'minute': 0, 'second': 0}
1489
+
1490
+ start = vmin.replace(**replace)
1491
+ stop = start.replace(year=ymax)
1492
+ self.rule.set(dtstart=start, until=stop)
1493
+
1494
+ return start, stop
1495
+
1496
+
1497
+ class MonthLocator(RRuleLocator):
1498
+ """
1499
+ Make ticks on occurrences of each month, e.g., 1, 3, 12.
1500
+ """
1501
+ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
1502
+ """
1503
+ Parameters
1504
+ ----------
1505
+ bymonth : int or list of int, default: all months
1506
+ Ticks will be placed on every month in *bymonth*. Default is
1507
+ ``range(1, 13)``, i.e. every month.
1508
+ bymonthday : int, default: 1
1509
+ The day on which to place the ticks.
1510
+ interval : int, default: 1
1511
+ The interval between each iteration. For example, if
1512
+ ``interval=2``, mark every second occurrence.
1513
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1514
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1515
+ """
1516
+ if bymonth is None:
1517
+ bymonth = range(1, 13)
1518
+
1519
+ rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
1520
+ interval=interval, **self.hms0d)
1521
+ super().__init__(rule, tz=tz)
1522
+
1523
+
1524
+ class WeekdayLocator(RRuleLocator):
1525
+ """
1526
+ Make ticks on occurrences of each weekday.
1527
+ """
1528
+
1529
+ def __init__(self, byweekday=1, interval=1, tz=None):
1530
+ """
1531
+ Parameters
1532
+ ----------
1533
+ byweekday : int or list of int, default: all days
1534
+ Ticks will be placed on every weekday in *byweekday*. Default is
1535
+ every day.
1536
+
1537
+ Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA,
1538
+ SU, the constants from :mod:`dateutil.rrule`, which have been
1539
+ imported into the :mod:`matplotlib.dates` namespace.
1540
+ interval : int, default: 1
1541
+ The interval between each iteration. For example, if
1542
+ ``interval=2``, mark every second occurrence.
1543
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1544
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1545
+ """
1546
+ rule = rrulewrapper(DAILY, byweekday=byweekday,
1547
+ interval=interval, **self.hms0d)
1548
+ super().__init__(rule, tz=tz)
1549
+
1550
+
1551
+ class DayLocator(RRuleLocator):
1552
+ """
1553
+ Make ticks on occurrences of each day of the month. For example,
1554
+ 1, 15, 30.
1555
+ """
1556
+ def __init__(self, bymonthday=None, interval=1, tz=None):
1557
+ """
1558
+ Parameters
1559
+ ----------
1560
+ bymonthday : int or list of int, default: all days
1561
+ Ticks will be placed on every day in *bymonthday*. Default is
1562
+ ``bymonthday=range(1, 32)``, i.e., every day of the month.
1563
+ interval : int, default: 1
1564
+ The interval between each iteration. For example, if
1565
+ ``interval=2``, mark every second occurrence.
1566
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1567
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1568
+ """
1569
+ if interval != int(interval) or interval < 1:
1570
+ raise ValueError("interval must be an integer greater than 0")
1571
+ if bymonthday is None:
1572
+ bymonthday = range(1, 32)
1573
+
1574
+ rule = rrulewrapper(DAILY, bymonthday=bymonthday,
1575
+ interval=interval, **self.hms0d)
1576
+ super().__init__(rule, tz=tz)
1577
+
1578
+
1579
+ class HourLocator(RRuleLocator):
1580
+ """
1581
+ Make ticks on occurrences of each hour.
1582
+ """
1583
+ def __init__(self, byhour=None, interval=1, tz=None):
1584
+ """
1585
+ Parameters
1586
+ ----------
1587
+ byhour : int or list of int, default: all hours
1588
+ Ticks will be placed on every hour in *byhour*. Default is
1589
+ ``byhour=range(24)``, i.e., every hour.
1590
+ interval : int, default: 1
1591
+ The interval between each iteration. For example, if
1592
+ ``interval=2``, mark every second occurrence.
1593
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1594
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1595
+ """
1596
+ if byhour is None:
1597
+ byhour = range(24)
1598
+
1599
+ rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
1600
+ byminute=0, bysecond=0)
1601
+ super().__init__(rule, tz=tz)
1602
+
1603
+
1604
+ class MinuteLocator(RRuleLocator):
1605
+ """
1606
+ Make ticks on occurrences of each minute.
1607
+ """
1608
+ def __init__(self, byminute=None, interval=1, tz=None):
1609
+ """
1610
+ Parameters
1611
+ ----------
1612
+ byminute : int or list of int, default: all minutes
1613
+ Ticks will be placed on every minute in *byminute*. Default is
1614
+ ``byminute=range(60)``, i.e., every minute.
1615
+ interval : int, default: 1
1616
+ The interval between each iteration. For example, if
1617
+ ``interval=2``, mark every second occurrence.
1618
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1619
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1620
+ """
1621
+ if byminute is None:
1622
+ byminute = range(60)
1623
+
1624
+ rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
1625
+ bysecond=0)
1626
+ super().__init__(rule, tz=tz)
1627
+
1628
+
1629
+ class SecondLocator(RRuleLocator):
1630
+ """
1631
+ Make ticks on occurrences of each second.
1632
+ """
1633
+ def __init__(self, bysecond=None, interval=1, tz=None):
1634
+ """
1635
+ Parameters
1636
+ ----------
1637
+ bysecond : int or list of int, default: all seconds
1638
+ Ticks will be placed on every second in *bysecond*. Default is
1639
+ ``bysecond = range(60)``, i.e., every second.
1640
+ interval : int, default: 1
1641
+ The interval between each iteration. For example, if
1642
+ ``interval=2``, mark every second occurrence.
1643
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1644
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1645
+ """
1646
+ if bysecond is None:
1647
+ bysecond = range(60)
1648
+
1649
+ rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
1650
+ super().__init__(rule, tz=tz)
1651
+
1652
+
1653
+ class MicrosecondLocator(DateLocator):
1654
+ """
1655
+ Make ticks on regular intervals of one or more microsecond(s).
1656
+
1657
+ .. note::
1658
+
1659
+ By default, Matplotlib uses a floating point representation of time in
1660
+ days since the epoch, so plotting data with
1661
+ microsecond time resolution does not work well for
1662
+ dates that are far (about 70 years) from the epoch (check with
1663
+ `~.dates.get_epoch`).
1664
+
1665
+ If you want sub-microsecond resolution time plots, it is strongly
1666
+ recommended to use floating point seconds, not datetime-like
1667
+ time representation.
1668
+
1669
+ If you really must use datetime.datetime() or similar and still
1670
+ need microsecond precision, change the time origin via
1671
+ `.dates.set_epoch` to something closer to the dates being plotted.
1672
+ See :doc:`/gallery/ticks/date_precision_and_epochs`.
1673
+
1674
+ """
1675
+ def __init__(self, interval=1, tz=None):
1676
+ """
1677
+ Parameters
1678
+ ----------
1679
+ interval : int, default: 1
1680
+ The interval between each iteration. For example, if
1681
+ ``interval=2``, mark every second occurrence.
1682
+ tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
1683
+ Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
1684
+ """
1685
+ super().__init__(tz=tz)
1686
+ self._interval = interval
1687
+ self._wrapped_locator = ticker.MultipleLocator(interval)
1688
+
1689
+ def set_axis(self, axis):
1690
+ self._wrapped_locator.set_axis(axis)
1691
+ return super().set_axis(axis)
1692
+
1693
+ def __call__(self):
1694
+ # if no data have been set, this will tank with a ValueError
1695
+ try:
1696
+ dmin, dmax = self.viewlim_to_dt()
1697
+ except ValueError:
1698
+ return []
1699
+
1700
+ return self.tick_values(dmin, dmax)
1701
+
1702
+ def tick_values(self, vmin, vmax):
1703
+ nmin, nmax = date2num((vmin, vmax))
1704
+ t0 = np.floor(nmin)
1705
+ nmax = nmax - t0
1706
+ nmin = nmin - t0
1707
+ nmin *= MUSECONDS_PER_DAY
1708
+ nmax *= MUSECONDS_PER_DAY
1709
+
1710
+ ticks = self._wrapped_locator.tick_values(nmin, nmax)
1711
+
1712
+ ticks = ticks / MUSECONDS_PER_DAY + t0
1713
+ return ticks
1714
+
1715
+ def _get_unit(self):
1716
+ # docstring inherited
1717
+ return 1. / MUSECONDS_PER_DAY
1718
+
1719
+ def _get_interval(self):
1720
+ # docstring inherited
1721
+ return self._interval
1722
+
1723
+
1724
+ class DateConverter(units.ConversionInterface):
1725
+ """
1726
+ Converter for `datetime.date` and `datetime.datetime` data, or for
1727
+ date/time data represented as it would be converted by `date2num`.
1728
+
1729
+ The 'unit' tag for such data is None or a `~datetime.tzinfo` instance.
1730
+ """
1731
+
1732
+ def __init__(self, *, interval_multiples=True):
1733
+ self._interval_multiples = interval_multiples
1734
+ super().__init__()
1735
+
1736
+ def axisinfo(self, unit, axis):
1737
+ """
1738
+ Return the `~matplotlib.units.AxisInfo` for *unit*.
1739
+
1740
+ *unit* is a `~datetime.tzinfo` instance or None.
1741
+ The *axis* argument is required but not used.
1742
+ """
1743
+ tz = unit
1744
+
1745
+ majloc = AutoDateLocator(tz=tz,
1746
+ interval_multiples=self._interval_multiples)
1747
+ majfmt = AutoDateFormatter(majloc, tz=tz)
1748
+ datemin = datetime.date(1970, 1, 1)
1749
+ datemax = datetime.date(1970, 1, 2)
1750
+
1751
+ return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
1752
+ default_limits=(datemin, datemax))
1753
+
1754
+ @staticmethod
1755
+ def convert(value, unit, axis):
1756
+ """
1757
+ If *value* is not already a number or sequence of numbers, convert it
1758
+ with `date2num`.
1759
+
1760
+ The *unit* and *axis* arguments are not used.
1761
+ """
1762
+ return date2num(value)
1763
+
1764
+ @staticmethod
1765
+ def default_units(x, axis):
1766
+ """
1767
+ Return the `~datetime.tzinfo` instance of *x* or of its first element,
1768
+ or None
1769
+ """
1770
+ if isinstance(x, np.ndarray):
1771
+ x = x.ravel()
1772
+
1773
+ try:
1774
+ x = cbook._safe_first_finite(x)
1775
+ except (TypeError, StopIteration):
1776
+ pass
1777
+
1778
+ try:
1779
+ return x.tzinfo
1780
+ except AttributeError:
1781
+ pass
1782
+ return None
1783
+
1784
+
1785
+ class ConciseDateConverter(DateConverter):
1786
+ # docstring inherited
1787
+
1788
+ def __init__(self, formats=None, zero_formats=None, offset_formats=None,
1789
+ show_offset=True, *, interval_multiples=True):
1790
+ self._formats = formats
1791
+ self._zero_formats = zero_formats
1792
+ self._offset_formats = offset_formats
1793
+ self._show_offset = show_offset
1794
+ self._interval_multiples = interval_multiples
1795
+ super().__init__()
1796
+
1797
+ def axisinfo(self, unit, axis):
1798
+ # docstring inherited
1799
+ tz = unit
1800
+ majloc = AutoDateLocator(tz=tz,
1801
+ interval_multiples=self._interval_multiples)
1802
+ majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats,
1803
+ zero_formats=self._zero_formats,
1804
+ offset_formats=self._offset_formats,
1805
+ show_offset=self._show_offset)
1806
+ datemin = datetime.date(1970, 1, 1)
1807
+ datemax = datetime.date(1970, 1, 2)
1808
+ return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
1809
+ default_limits=(datemin, datemax))
1810
+
1811
+
1812
+ class _SwitchableDateConverter:
1813
+ """
1814
+ Helper converter-like object that generates and dispatches to
1815
+ temporary ConciseDateConverter or DateConverter instances based on
1816
+ :rc:`date.converter` and :rc:`date.interval_multiples`.
1817
+ """
1818
+
1819
+ @staticmethod
1820
+ def _get_converter():
1821
+ converter_cls = {
1822
+ "concise": ConciseDateConverter, "auto": DateConverter}[
1823
+ mpl.rcParams["date.converter"]]
1824
+ interval_multiples = mpl.rcParams["date.interval_multiples"]
1825
+ return converter_cls(interval_multiples=interval_multiples)
1826
+
1827
+ def axisinfo(self, *args, **kwargs):
1828
+ return self._get_converter().axisinfo(*args, **kwargs)
1829
+
1830
+ def default_units(self, *args, **kwargs):
1831
+ return self._get_converter().default_units(*args, **kwargs)
1832
+
1833
+ def convert(self, *args, **kwargs):
1834
+ return self._get_converter().convert(*args, **kwargs)
1835
+
1836
+
1837
+ units.registry[np.datetime64] = \
1838
+ units.registry[datetime.date] = \
1839
+ units.registry[datetime.datetime] = \
1840
+ _SwitchableDateConverter()
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py ADDED
@@ -0,0 +1,1139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A module for reading dvi files output by TeX. Several limitations make
3
+ this not (currently) useful as a general-purpose dvi preprocessor, but
4
+ it is currently used by the pdf backend for processing usetex text.
5
+
6
+ Interface::
7
+
8
+ with Dvi(filename, 72) as dvi:
9
+ # iterate over pages:
10
+ for page in dvi:
11
+ w, h, d = page.width, page.height, page.descent
12
+ for x, y, font, glyph, width in page.text:
13
+ fontname = font.texname
14
+ pointsize = font.size
15
+ ...
16
+ for x, y, height, width in page.boxes:
17
+ ...
18
+ """
19
+
20
+ from collections import namedtuple
21
+ import enum
22
+ from functools import lru_cache, partial, wraps
23
+ import logging
24
+ import os
25
+ from pathlib import Path
26
+ import re
27
+ import struct
28
+ import subprocess
29
+ import sys
30
+
31
+ import numpy as np
32
+
33
+ from matplotlib import _api, cbook
34
+
35
+ _log = logging.getLogger(__name__)
36
+
37
+ # Many dvi related files are looked for by external processes, require
38
+ # additional parsing, and are used many times per rendering, which is why they
39
+ # are cached using lru_cache().
40
+
41
+ # Dvi is a bytecode format documented in
42
+ # https://ctan.org/pkg/dvitype
43
+ # https://texdoc.org/serve/dvitype.pdf/0
44
+ #
45
+ # The file consists of a preamble, some number of pages, a postamble,
46
+ # and a finale. Different opcodes are allowed in different contexts,
47
+ # so the Dvi object has a parser state:
48
+ #
49
+ # pre: expecting the preamble
50
+ # outer: between pages (followed by a page or the postamble,
51
+ # also e.g. font definitions are allowed)
52
+ # page: processing a page
53
+ # post_post: state after the postamble (our current implementation
54
+ # just stops reading)
55
+ # finale: the finale (unimplemented in our current implementation)
56
+
57
+ _dvistate = enum.Enum('DviState', 'pre outer inpage post_post finale')
58
+
59
+ # The marks on a page consist of text and boxes. A page also has dimensions.
60
+ Page = namedtuple('Page', 'text boxes height width descent')
61
+ Box = namedtuple('Box', 'x y height width')
62
+
63
+
64
+ # Also a namedtuple, for backcompat.
65
+ class Text(namedtuple('Text', 'x y font glyph width')):
66
+ """
67
+ A glyph in the dvi file.
68
+
69
+ The *x* and *y* attributes directly position the glyph. The *font*,
70
+ *glyph*, and *width* attributes are kept public for back-compatibility,
71
+ but users wanting to draw the glyph themselves are encouraged to instead
72
+ load the font specified by `font_path` at `font_size`, warp it with the
73
+ effects specified by `font_effects`, and load the glyph specified by
74
+ `glyph_name_or_index`.
75
+ """
76
+
77
+ def _get_pdftexmap_entry(self):
78
+ return PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname]
79
+
80
+ @property
81
+ def font_path(self):
82
+ """The `~pathlib.Path` to the font for this glyph."""
83
+ psfont = self._get_pdftexmap_entry()
84
+ if psfont.filename is None:
85
+ raise ValueError("No usable font file found for {} ({}); "
86
+ "the font may lack a Type-1 version"
87
+ .format(psfont.psname.decode("ascii"),
88
+ psfont.texname.decode("ascii")))
89
+ return Path(psfont.filename)
90
+
91
+ @property
92
+ def font_size(self):
93
+ """The font size."""
94
+ return self.font.size
95
+
96
+ @property
97
+ def font_effects(self):
98
+ """
99
+ The "font effects" dict for this glyph.
100
+
101
+ This dict contains the values for this glyph of SlantFont and
102
+ ExtendFont (if any), read off :file:`pdftex.map`.
103
+ """
104
+ return self._get_pdftexmap_entry().effects
105
+
106
+ @property
107
+ def glyph_name_or_index(self):
108
+ """
109
+ Either the glyph name or the native charmap glyph index.
110
+
111
+ If :file:`pdftex.map` specifies an encoding for this glyph's font, that
112
+ is a mapping of glyph indices to Adobe glyph names; use it to convert
113
+ dvi indices to glyph names. Callers can then convert glyph names to
114
+ glyph indices (with FT_Get_Name_Index/get_name_index), and load the
115
+ glyph using FT_Load_Glyph/load_glyph.
116
+
117
+ If :file:`pdftex.map` specifies no encoding, the indices directly map
118
+ to the font's "native" charmap; glyphs should directly load using
119
+ FT_Load_Char/load_char after selecting the native charmap.
120
+ """
121
+ entry = self._get_pdftexmap_entry()
122
+ return (_parse_enc(entry.encoding)[self.glyph]
123
+ if entry.encoding is not None else self.glyph)
124
+
125
+
126
+ # Opcode argument parsing
127
+ #
128
+ # Each of the following functions takes a Dvi object and delta, which is the
129
+ # difference between the opcode and the minimum opcode with the same meaning.
130
+ # Dvi opcodes often encode the number of argument bytes in this delta.
131
+ _arg_mapping = dict(
132
+ # raw: Return delta as is.
133
+ raw=lambda dvi, delta: delta,
134
+ # u1: Read 1 byte as an unsigned number.
135
+ u1=lambda dvi, delta: dvi._read_arg(1, signed=False),
136
+ # u4: Read 4 bytes as an unsigned number.
137
+ u4=lambda dvi, delta: dvi._read_arg(4, signed=False),
138
+ # s4: Read 4 bytes as a signed number.
139
+ s4=lambda dvi, delta: dvi._read_arg(4, signed=True),
140
+ # slen: Read delta bytes as a signed number, or None if delta is None.
141
+ slen=lambda dvi, delta: dvi._read_arg(delta, signed=True) if delta else None,
142
+ # slen1: Read (delta + 1) bytes as a signed number.
143
+ slen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=True),
144
+ # ulen1: Read (delta + 1) bytes as an unsigned number.
145
+ ulen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=False),
146
+ # olen1: Read (delta + 1) bytes as an unsigned number if less than 4 bytes,
147
+ # as a signed number if 4 bytes.
148
+ olen1=lambda dvi, delta: dvi._read_arg(delta + 1, signed=(delta == 3)),
149
+ )
150
+
151
+
152
+ def _dispatch(table, min, max=None, state=None, args=('raw',)):
153
+ """
154
+ Decorator for dispatch by opcode. Sets the values in *table*
155
+ from *min* to *max* to this method, adds a check that the Dvi state
156
+ matches *state* if not None, reads arguments from the file according
157
+ to *args*.
158
+
159
+ Parameters
160
+ ----------
161
+ table : dict[int, callable]
162
+ The dispatch table to be filled in.
163
+
164
+ min, max : int
165
+ Range of opcodes that calls the registered function; *max* defaults to
166
+ *min*.
167
+
168
+ state : _dvistate, optional
169
+ State of the Dvi object in which these opcodes are allowed.
170
+
171
+ args : list[str], default: ['raw']
172
+ Sequence of argument specifications:
173
+
174
+ - 'raw': opcode minus minimum
175
+ - 'u1': read one unsigned byte
176
+ - 'u4': read four bytes, treat as an unsigned number
177
+ - 's4': read four bytes, treat as a signed number
178
+ - 'slen': read (opcode - minimum) bytes, treat as signed
179
+ - 'slen1': read (opcode - minimum + 1) bytes, treat as signed
180
+ - 'ulen1': read (opcode - minimum + 1) bytes, treat as unsigned
181
+ - 'olen1': read (opcode - minimum + 1) bytes, treat as unsigned
182
+ if under four bytes, signed if four bytes
183
+ """
184
+ def decorate(method):
185
+ get_args = [_arg_mapping[x] for x in args]
186
+
187
+ @wraps(method)
188
+ def wrapper(self, byte):
189
+ if state is not None and self.state != state:
190
+ raise ValueError("state precondition failed")
191
+ return method(self, *[f(self, byte-min) for f in get_args])
192
+ if max is None:
193
+ table[min] = wrapper
194
+ else:
195
+ for i in range(min, max+1):
196
+ assert table[i] is None
197
+ table[i] = wrapper
198
+ return wrapper
199
+ return decorate
200
+
201
+
202
+ class Dvi:
203
+ """
204
+ A reader for a dvi ("device-independent") file, as produced by TeX.
205
+
206
+ The current implementation can only iterate through pages in order,
207
+ and does not even attempt to verify the postamble.
208
+
209
+ This class can be used as a context manager to close the underlying
210
+ file upon exit. Pages can be read via iteration. Here is an overly
211
+ simple way to extract text without trying to detect whitespace::
212
+
213
+ >>> with matplotlib.dviread.Dvi('input.dvi', 72) as dvi:
214
+ ... for page in dvi:
215
+ ... print(''.join(chr(t.glyph) for t in page.text))
216
+ """
217
+ # dispatch table
218
+ _dtable = [None] * 256
219
+ _dispatch = partial(_dispatch, _dtable)
220
+
221
+ def __init__(self, filename, dpi):
222
+ """
223
+ Read the data from the file named *filename* and convert
224
+ TeX's internal units to units of *dpi* per inch.
225
+ *dpi* only sets the units and does not limit the resolution.
226
+ Use None to return TeX's internal units.
227
+ """
228
+ _log.debug('Dvi: %s', filename)
229
+ self.file = open(filename, 'rb')
230
+ self.dpi = dpi
231
+ self.fonts = {}
232
+ self.state = _dvistate.pre
233
+ self._missing_font = None
234
+
235
+ def __enter__(self):
236
+ """Context manager enter method, does nothing."""
237
+ return self
238
+
239
+ def __exit__(self, etype, evalue, etrace):
240
+ """
241
+ Context manager exit method, closes the underlying file if it is open.
242
+ """
243
+ self.close()
244
+
245
+ def __iter__(self):
246
+ """
247
+ Iterate through the pages of the file.
248
+
249
+ Yields
250
+ ------
251
+ Page
252
+ Details of all the text and box objects on the page.
253
+ The Page tuple contains lists of Text and Box tuples and
254
+ the page dimensions, and the Text and Box tuples contain
255
+ coordinates transformed into a standard Cartesian
256
+ coordinate system at the dpi value given when initializing.
257
+ The coordinates are floating point numbers, but otherwise
258
+ precision is not lost and coordinate values are not clipped to
259
+ integers.
260
+ """
261
+ while self._read():
262
+ yield self._output()
263
+
264
+ def close(self):
265
+ """Close the underlying file if it is open."""
266
+ if not self.file.closed:
267
+ self.file.close()
268
+
269
+ def _output(self):
270
+ """
271
+ Output the text and boxes belonging to the most recent page.
272
+ page = dvi._output()
273
+ """
274
+ minx = miny = np.inf
275
+ maxx = maxy = -np.inf
276
+ maxy_pure = -np.inf
277
+ for elt in self.text + self.boxes:
278
+ if isinstance(elt, Box):
279
+ x, y, h, w = elt
280
+ e = 0 # zero depth
281
+ else: # glyph
282
+ x, y, font, g, w = elt
283
+ h, e = font._height_depth_of(g)
284
+ minx = min(minx, x)
285
+ miny = min(miny, y - h)
286
+ maxx = max(maxx, x + w)
287
+ maxy = max(maxy, y + e)
288
+ maxy_pure = max(maxy_pure, y)
289
+ if self._baseline_v is not None:
290
+ maxy_pure = self._baseline_v # This should normally be the case.
291
+ self._baseline_v = None
292
+
293
+ if not self.text and not self.boxes: # Avoid infs/nans from inf+/-inf.
294
+ return Page(text=[], boxes=[], width=0, height=0, descent=0)
295
+
296
+ if self.dpi is None:
297
+ # special case for ease of debugging: output raw dvi coordinates
298
+ return Page(text=self.text, boxes=self.boxes,
299
+ width=maxx-minx, height=maxy_pure-miny,
300
+ descent=maxy-maxy_pure)
301
+
302
+ # convert from TeX's "scaled points" to dpi units
303
+ d = self.dpi / (72.27 * 2**16)
304
+ descent = (maxy - maxy_pure) * d
305
+
306
+ text = [Text((x-minx)*d, (maxy-y)*d - descent, f, g, w*d)
307
+ for (x, y, f, g, w) in self.text]
308
+ boxes = [Box((x-minx)*d, (maxy-y)*d - descent, h*d, w*d)
309
+ for (x, y, h, w) in self.boxes]
310
+
311
+ return Page(text=text, boxes=boxes, width=(maxx-minx)*d,
312
+ height=(maxy_pure-miny)*d, descent=descent)
313
+
314
+ def _read(self):
315
+ """
316
+ Read one page from the file. Return True if successful,
317
+ False if there were no more pages.
318
+ """
319
+ # Pages appear to start with the sequence
320
+ # bop (begin of page)
321
+ # xxx comment
322
+ # <push, ..., pop> # if using chemformula
323
+ # down
324
+ # push
325
+ # down
326
+ # <push, push, xxx, right, xxx, pop, pop> # if using xcolor
327
+ # down
328
+ # push
329
+ # down (possibly multiple)
330
+ # push <= here, v is the baseline position.
331
+ # etc.
332
+ # (dviasm is useful to explore this structure.)
333
+ # Thus, we use the vertical position at the first time the stack depth
334
+ # reaches 3, while at least three "downs" have been executed (excluding
335
+ # those popped out (corresponding to the chemformula preamble)), as the
336
+ # baseline (the "down" count is necessary to handle xcolor).
337
+ down_stack = [0]
338
+ self._baseline_v = None
339
+ while True:
340
+ byte = self.file.read(1)[0]
341
+ self._dtable[byte](self, byte)
342
+ if self._missing_font:
343
+ raise self._missing_font.to_exception()
344
+ name = self._dtable[byte].__name__
345
+ if name == "_push":
346
+ down_stack.append(down_stack[-1])
347
+ elif name == "_pop":
348
+ down_stack.pop()
349
+ elif name == "_down":
350
+ down_stack[-1] += 1
351
+ if (self._baseline_v is None
352
+ and len(getattr(self, "stack", [])) == 3
353
+ and down_stack[-1] >= 4):
354
+ self._baseline_v = self.v
355
+ if byte == 140: # end of page
356
+ return True
357
+ if self.state is _dvistate.post_post: # end of file
358
+ self.close()
359
+ return False
360
+
361
+ def _read_arg(self, nbytes, signed=False):
362
+ """
363
+ Read and return a big-endian integer *nbytes* long.
364
+ Signedness is determined by the *signed* keyword.
365
+ """
366
+ return int.from_bytes(self.file.read(nbytes), "big", signed=signed)
367
+
368
+ @_dispatch(min=0, max=127, state=_dvistate.inpage)
369
+ def _set_char_immediate(self, char):
370
+ self._put_char_real(char)
371
+ if isinstance(self.fonts[self.f], cbook._ExceptionInfo):
372
+ return
373
+ self.h += self.fonts[self.f]._width_of(char)
374
+
375
+ @_dispatch(min=128, max=131, state=_dvistate.inpage, args=('olen1',))
376
+ def _set_char(self, char):
377
+ self._put_char_real(char)
378
+ if isinstance(self.fonts[self.f], cbook._ExceptionInfo):
379
+ return
380
+ self.h += self.fonts[self.f]._width_of(char)
381
+
382
+ @_dispatch(132, state=_dvistate.inpage, args=('s4', 's4'))
383
+ def _set_rule(self, a, b):
384
+ self._put_rule_real(a, b)
385
+ self.h += b
386
+
387
+ @_dispatch(min=133, max=136, state=_dvistate.inpage, args=('olen1',))
388
+ def _put_char(self, char):
389
+ self._put_char_real(char)
390
+
391
+ def _put_char_real(self, char):
392
+ font = self.fonts[self.f]
393
+ if isinstance(font, cbook._ExceptionInfo):
394
+ self._missing_font = font
395
+ elif font._vf is None:
396
+ self.text.append(Text(self.h, self.v, font, char,
397
+ font._width_of(char)))
398
+ else:
399
+ scale = font._scale
400
+ for x, y, f, g, w in font._vf[char].text:
401
+ newf = DviFont(scale=_mul2012(scale, f._scale),
402
+ tfm=f._tfm, texname=f.texname, vf=f._vf)
403
+ self.text.append(Text(self.h + _mul2012(x, scale),
404
+ self.v + _mul2012(y, scale),
405
+ newf, g, newf._width_of(g)))
406
+ self.boxes.extend([Box(self.h + _mul2012(x, scale),
407
+ self.v + _mul2012(y, scale),
408
+ _mul2012(a, scale), _mul2012(b, scale))
409
+ for x, y, a, b in font._vf[char].boxes])
410
+
411
+ @_dispatch(137, state=_dvistate.inpage, args=('s4', 's4'))
412
+ def _put_rule(self, a, b):
413
+ self._put_rule_real(a, b)
414
+
415
+ def _put_rule_real(self, a, b):
416
+ if a > 0 and b > 0:
417
+ self.boxes.append(Box(self.h, self.v, a, b))
418
+
419
+ @_dispatch(138)
420
+ def _nop(self, _):
421
+ pass
422
+
423
+ @_dispatch(139, state=_dvistate.outer, args=('s4',)*11)
424
+ def _bop(self, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, p):
425
+ self.state = _dvistate.inpage
426
+ self.h = self.v = self.w = self.x = self.y = self.z = 0
427
+ self.stack = []
428
+ self.text = [] # list of Text objects
429
+ self.boxes = [] # list of Box objects
430
+
431
+ @_dispatch(140, state=_dvistate.inpage)
432
+ def _eop(self, _):
433
+ self.state = _dvistate.outer
434
+ del self.h, self.v, self.w, self.x, self.y, self.z, self.stack
435
+
436
+ @_dispatch(141, state=_dvistate.inpage)
437
+ def _push(self, _):
438
+ self.stack.append((self.h, self.v, self.w, self.x, self.y, self.z))
439
+
440
+ @_dispatch(142, state=_dvistate.inpage)
441
+ def _pop(self, _):
442
+ self.h, self.v, self.w, self.x, self.y, self.z = self.stack.pop()
443
+
444
+ @_dispatch(min=143, max=146, state=_dvistate.inpage, args=('slen1',))
445
+ def _right(self, b):
446
+ self.h += b
447
+
448
+ @_dispatch(min=147, max=151, state=_dvistate.inpage, args=('slen',))
449
+ def _right_w(self, new_w):
450
+ if new_w is not None:
451
+ self.w = new_w
452
+ self.h += self.w
453
+
454
+ @_dispatch(min=152, max=156, state=_dvistate.inpage, args=('slen',))
455
+ def _right_x(self, new_x):
456
+ if new_x is not None:
457
+ self.x = new_x
458
+ self.h += self.x
459
+
460
+ @_dispatch(min=157, max=160, state=_dvistate.inpage, args=('slen1',))
461
+ def _down(self, a):
462
+ self.v += a
463
+
464
+ @_dispatch(min=161, max=165, state=_dvistate.inpage, args=('slen',))
465
+ def _down_y(self, new_y):
466
+ if new_y is not None:
467
+ self.y = new_y
468
+ self.v += self.y
469
+
470
+ @_dispatch(min=166, max=170, state=_dvistate.inpage, args=('slen',))
471
+ def _down_z(self, new_z):
472
+ if new_z is not None:
473
+ self.z = new_z
474
+ self.v += self.z
475
+
476
+ @_dispatch(min=171, max=234, state=_dvistate.inpage)
477
+ def _fnt_num_immediate(self, k):
478
+ self.f = k
479
+
480
+ @_dispatch(min=235, max=238, state=_dvistate.inpage, args=('olen1',))
481
+ def _fnt_num(self, new_f):
482
+ self.f = new_f
483
+
484
+ @_dispatch(min=239, max=242, args=('ulen1',))
485
+ def _xxx(self, datalen):
486
+ special = self.file.read(datalen)
487
+ _log.debug(
488
+ 'Dvi._xxx: encountered special: %s',
489
+ ''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch
490
+ for ch in special]))
491
+
492
+ @_dispatch(min=243, max=246, args=('olen1', 'u4', 'u4', 'u4', 'u1', 'u1'))
493
+ def _fnt_def(self, k, c, s, d, a, l):
494
+ self._fnt_def_real(k, c, s, d, a, l)
495
+
496
+ def _fnt_def_real(self, k, c, s, d, a, l):
497
+ n = self.file.read(a + l)
498
+ fontname = n[-l:].decode('ascii')
499
+ try:
500
+ tfm = _tfmfile(fontname)
501
+ except FileNotFoundError as exc:
502
+ # Explicitly allow defining missing fonts for Vf support; we only
503
+ # register an error when trying to load a glyph from a missing font
504
+ # and throw that error in Dvi._read. For Vf, _finalize_packet
505
+ # checks whether a missing glyph has been used, and in that case
506
+ # skips the glyph definition.
507
+ self.fonts[k] = cbook._ExceptionInfo.from_exception(exc)
508
+ return
509
+ if c != 0 and tfm.checksum != 0 and c != tfm.checksum:
510
+ raise ValueError(f'tfm checksum mismatch: {n}')
511
+ try:
512
+ vf = _vffile(fontname)
513
+ except FileNotFoundError:
514
+ vf = None
515
+ self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf)
516
+
517
+ @_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1'))
518
+ def _pre(self, i, num, den, mag, k):
519
+ self.file.read(k) # comment in the dvi file
520
+ if i != 2:
521
+ raise ValueError(f"Unknown dvi format {i}")
522
+ if num != 25400000 or den != 7227 * 2**16:
523
+ raise ValueError("Nonstandard units in dvi file")
524
+ # meaning: TeX always uses those exact values, so it
525
+ # should be enough for us to support those
526
+ # (There are 72.27 pt to an inch so 7227 pt =
527
+ # 7227 * 2**16 sp to 100 in. The numerator is multiplied
528
+ # by 10^5 to get units of 10**-7 meters.)
529
+ if mag != 1000:
530
+ raise ValueError("Nonstandard magnification in dvi file")
531
+ # meaning: LaTeX seems to frown on setting \mag, so
532
+ # I think we can assume this is constant
533
+ self.state = _dvistate.outer
534
+
535
+ @_dispatch(248, state=_dvistate.outer)
536
+ def _post(self, _):
537
+ self.state = _dvistate.post_post
538
+ # TODO: actually read the postamble and finale?
539
+ # currently post_post just triggers closing the file
540
+
541
+ @_dispatch(249)
542
+ def _post_post(self, _):
543
+ raise NotImplementedError
544
+
545
+ @_dispatch(min=250, max=255)
546
+ def _malformed(self, offset):
547
+ raise ValueError(f"unknown command: byte {250 + offset}")
548
+
549
+
550
+ class DviFont:
551
+ """
552
+ Encapsulation of a font that a DVI file can refer to.
553
+
554
+ This class holds a font's texname and size, supports comparison,
555
+ and knows the widths of glyphs in the same units as the AFM file.
556
+ There are also internal attributes (for use by dviread.py) that
557
+ are *not* used for comparison.
558
+
559
+ The size is in Adobe points (converted from TeX points).
560
+
561
+ Parameters
562
+ ----------
563
+ scale : float
564
+ Factor by which the font is scaled from its natural size.
565
+ tfm : Tfm
566
+ TeX font metrics for this font
567
+ texname : bytes
568
+ Name of the font as used internally by TeX and friends, as an ASCII
569
+ bytestring. This is usually very different from any external font
570
+ names; `PsfontsMap` can be used to find the external name of the font.
571
+ vf : Vf
572
+ A TeX "virtual font" file, or None if this font is not virtual.
573
+
574
+ Attributes
575
+ ----------
576
+ texname : bytes
577
+ size : float
578
+ Size of the font in Adobe points, converted from the slightly
579
+ smaller TeX points.
580
+ widths : list
581
+ Widths of glyphs in glyph-space units, typically 1/1000ths of
582
+ the point size.
583
+
584
+ """
585
+ __slots__ = ('texname', 'size', 'widths', '_scale', '_vf', '_tfm')
586
+
587
+ def __init__(self, scale, tfm, texname, vf):
588
+ _api.check_isinstance(bytes, texname=texname)
589
+ self._scale = scale
590
+ self._tfm = tfm
591
+ self.texname = texname
592
+ self._vf = vf
593
+ self.size = scale * (72.0 / (72.27 * 2**16))
594
+ try:
595
+ nchars = max(tfm.width) + 1
596
+ except ValueError:
597
+ nchars = 0
598
+ self.widths = [(1000*tfm.width.get(char, 0)) >> 20
599
+ for char in range(nchars)]
600
+
601
+ def __eq__(self, other):
602
+ return (type(self) is type(other)
603
+ and self.texname == other.texname and self.size == other.size)
604
+
605
+ def __ne__(self, other):
606
+ return not self.__eq__(other)
607
+
608
+ def __repr__(self):
609
+ return f"<{type(self).__name__}: {self.texname}>"
610
+
611
+ def _width_of(self, char):
612
+ """Width of char in dvi units."""
613
+ width = self._tfm.width.get(char, None)
614
+ if width is not None:
615
+ return _mul2012(width, self._scale)
616
+ _log.debug('No width for char %d in font %s.', char, self.texname)
617
+ return 0
618
+
619
+ def _height_depth_of(self, char):
620
+ """Height and depth of char in dvi units."""
621
+ result = []
622
+ for metric, name in ((self._tfm.height, "height"),
623
+ (self._tfm.depth, "depth")):
624
+ value = metric.get(char, None)
625
+ if value is None:
626
+ _log.debug('No %s for char %d in font %s',
627
+ name, char, self.texname)
628
+ result.append(0)
629
+ else:
630
+ result.append(_mul2012(value, self._scale))
631
+ # cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent
632
+ # so that TeX aligns equations properly
633
+ # (https://tex.stackexchange.com/q/526103/)
634
+ # but we actually care about the rasterization depth to align
635
+ # the dvipng-generated images.
636
+ if re.match(br'^cmsy\d+$', self.texname) and char == 0:
637
+ result[-1] = 0
638
+ return result
639
+
640
+
641
+ class Vf(Dvi):
642
+ r"""
643
+ A virtual font (\*.vf file) containing subroutines for dvi files.
644
+
645
+ Parameters
646
+ ----------
647
+ filename : str or path-like
648
+
649
+ Notes
650
+ -----
651
+ The virtual font format is a derivative of dvi:
652
+ http://mirrors.ctan.org/info/knuth/virtual-fonts
653
+ This class reuses some of the machinery of `Dvi`
654
+ but replaces the `_read` loop and dispatch mechanism.
655
+
656
+ Examples
657
+ --------
658
+ ::
659
+
660
+ vf = Vf(filename)
661
+ glyph = vf[code]
662
+ glyph.text, glyph.boxes, glyph.width
663
+ """
664
+
665
+ def __init__(self, filename):
666
+ super().__init__(filename, 0)
667
+ try:
668
+ self._first_font = None
669
+ self._chars = {}
670
+ self._read()
671
+ finally:
672
+ self.close()
673
+
674
+ def __getitem__(self, code):
675
+ return self._chars[code]
676
+
677
+ def _read(self):
678
+ """
679
+ Read one page from the file. Return True if successful,
680
+ False if there were no more pages.
681
+ """
682
+ packet_char = packet_ends = None
683
+ packet_len = packet_width = None
684
+ while True:
685
+ byte = self.file.read(1)[0]
686
+ # If we are in a packet, execute the dvi instructions
687
+ if self.state is _dvistate.inpage:
688
+ byte_at = self.file.tell()-1
689
+ if byte_at == packet_ends:
690
+ self._finalize_packet(packet_char, packet_width)
691
+ packet_len = packet_char = packet_width = None
692
+ # fall through to out-of-packet code
693
+ elif byte_at > packet_ends:
694
+ raise ValueError("Packet length mismatch in vf file")
695
+ else:
696
+ if byte in (139, 140) or byte >= 243:
697
+ raise ValueError(f"Inappropriate opcode {byte} in vf file")
698
+ Dvi._dtable[byte](self, byte)
699
+ continue
700
+
701
+ # We are outside a packet
702
+ if byte < 242: # a short packet (length given by byte)
703
+ packet_len = byte
704
+ packet_char = self._read_arg(1)
705
+ packet_width = self._read_arg(3)
706
+ packet_ends = self._init_packet(byte)
707
+ self.state = _dvistate.inpage
708
+ elif byte == 242: # a long packet
709
+ packet_len = self._read_arg(4)
710
+ packet_char = self._read_arg(4)
711
+ packet_width = self._read_arg(4)
712
+ self._init_packet(packet_len)
713
+ elif 243 <= byte <= 246:
714
+ k = self._read_arg(byte - 242, byte == 246)
715
+ c = self._read_arg(4)
716
+ s = self._read_arg(4)
717
+ d = self._read_arg(4)
718
+ a = self._read_arg(1)
719
+ l = self._read_arg(1)
720
+ self._fnt_def_real(k, c, s, d, a, l)
721
+ if self._first_font is None:
722
+ self._first_font = k
723
+ elif byte == 247: # preamble
724
+ i = self._read_arg(1)
725
+ k = self._read_arg(1)
726
+ x = self.file.read(k)
727
+ cs = self._read_arg(4)
728
+ ds = self._read_arg(4)
729
+ self._pre(i, x, cs, ds)
730
+ elif byte == 248: # postamble (just some number of 248s)
731
+ break
732
+ else:
733
+ raise ValueError(f"Unknown vf opcode {byte}")
734
+
735
+ def _init_packet(self, pl):
736
+ if self.state != _dvistate.outer:
737
+ raise ValueError("Misplaced packet in vf file")
738
+ self.h = self.v = self.w = self.x = self.y = self.z = 0
739
+ self.stack = []
740
+ self.text = []
741
+ self.boxes = []
742
+ self.f = self._first_font
743
+ self._missing_font = None
744
+ return self.file.tell() + pl
745
+
746
+ def _finalize_packet(self, packet_char, packet_width):
747
+ if not self._missing_font: # Otherwise we don't have full glyph definition.
748
+ self._chars[packet_char] = Page(
749
+ text=self.text, boxes=self.boxes, width=packet_width,
750
+ height=None, descent=None)
751
+ self.state = _dvistate.outer
752
+
753
+ def _pre(self, i, x, cs, ds):
754
+ if self.state is not _dvistate.pre:
755
+ raise ValueError("pre command in middle of vf file")
756
+ if i != 202:
757
+ raise ValueError(f"Unknown vf format {i}")
758
+ if len(x):
759
+ _log.debug('vf file comment: %s', x)
760
+ self.state = _dvistate.outer
761
+ # cs = checksum, ds = design size
762
+
763
+
764
+ def _mul2012(num1, num2):
765
+ """Multiply two numbers in 20.12 fixed point format."""
766
+ # Separated into a function because >> has surprising precedence
767
+ return (num1*num2) >> 20
768
+
769
+
770
+ class Tfm:
771
+ """
772
+ A TeX Font Metric file.
773
+
774
+ This implementation covers only the bare minimum needed by the Dvi class.
775
+
776
+ Parameters
777
+ ----------
778
+ filename : str or path-like
779
+
780
+ Attributes
781
+ ----------
782
+ checksum : int
783
+ Used for verifying against the dvi file.
784
+ design_size : int
785
+ Design size of the font (unknown units)
786
+ width, height, depth : dict
787
+ Dimensions of each character, need to be scaled by the factor
788
+ specified in the dvi file. These are dicts because indexing may
789
+ not start from 0.
790
+ """
791
+ __slots__ = ('checksum', 'design_size', 'width', 'height', 'depth')
792
+
793
+ def __init__(self, filename):
794
+ _log.debug('opening tfm file %s', filename)
795
+ with open(filename, 'rb') as file:
796
+ header1 = file.read(24)
797
+ lh, bc, ec, nw, nh, nd = struct.unpack('!6H', header1[2:14])
798
+ _log.debug('lh=%d, bc=%d, ec=%d, nw=%d, nh=%d, nd=%d',
799
+ lh, bc, ec, nw, nh, nd)
800
+ header2 = file.read(4*lh)
801
+ self.checksum, self.design_size = struct.unpack('!2I', header2[:8])
802
+ # there is also encoding information etc.
803
+ char_info = file.read(4*(ec-bc+1))
804
+ widths = struct.unpack(f'!{nw}i', file.read(4*nw))
805
+ heights = struct.unpack(f'!{nh}i', file.read(4*nh))
806
+ depths = struct.unpack(f'!{nd}i', file.read(4*nd))
807
+ self.width = {}
808
+ self.height = {}
809
+ self.depth = {}
810
+ for idx, char in enumerate(range(bc, ec+1)):
811
+ byte0 = char_info[4*idx]
812
+ byte1 = char_info[4*idx+1]
813
+ self.width[char] = widths[byte0]
814
+ self.height[char] = heights[byte1 >> 4]
815
+ self.depth[char] = depths[byte1 & 0xf]
816
+
817
+
818
+ PsFont = namedtuple('PsFont', 'texname psname effects encoding filename')
819
+
820
+
821
+ class PsfontsMap:
822
+ """
823
+ A psfonts.map formatted file, mapping TeX fonts to PS fonts.
824
+
825
+ Parameters
826
+ ----------
827
+ filename : str or path-like
828
+
829
+ Notes
830
+ -----
831
+ For historical reasons, TeX knows many Type-1 fonts by different
832
+ names than the outside world. (For one thing, the names have to
833
+ fit in eight characters.) Also, TeX's native fonts are not Type-1
834
+ but Metafont, which is nontrivial to convert to PostScript except
835
+ as a bitmap. While high-quality conversions to Type-1 format exist
836
+ and are shipped with modern TeX distributions, we need to know
837
+ which Type-1 fonts are the counterparts of which native fonts. For
838
+ these reasons a mapping is needed from internal font names to font
839
+ file names.
840
+
841
+ A texmf tree typically includes mapping files called e.g.
842
+ :file:`psfonts.map`, :file:`pdftex.map`, or :file:`dvipdfm.map`.
843
+ The file :file:`psfonts.map` is used by :program:`dvips`,
844
+ :file:`pdftex.map` by :program:`pdfTeX`, and :file:`dvipdfm.map`
845
+ by :program:`dvipdfm`. :file:`psfonts.map` might avoid embedding
846
+ the 35 PostScript fonts (i.e., have no filename for them, as in
847
+ the Times-Bold example above), while the pdf-related files perhaps
848
+ only avoid the "Base 14" pdf fonts. But the user may have
849
+ configured these files differently.
850
+
851
+ Examples
852
+ --------
853
+ >>> map = PsfontsMap(find_tex_file('pdftex.map'))
854
+ >>> entry = map[b'ptmbo8r']
855
+ >>> entry.texname
856
+ b'ptmbo8r'
857
+ >>> entry.psname
858
+ b'Times-Bold'
859
+ >>> entry.encoding
860
+ '/usr/local/texlive/2008/texmf-dist/fonts/enc/dvips/base/8r.enc'
861
+ >>> entry.effects
862
+ {'slant': 0.16700000000000001}
863
+ >>> entry.filename
864
+ """
865
+ __slots__ = ('_filename', '_unparsed', '_parsed')
866
+
867
+ # Create a filename -> PsfontsMap cache, so that calling
868
+ # `PsfontsMap(filename)` with the same filename a second time immediately
869
+ # returns the same object.
870
+ @lru_cache
871
+ def __new__(cls, filename):
872
+ self = object.__new__(cls)
873
+ self._filename = os.fsdecode(filename)
874
+ # Some TeX distributions have enormous pdftex.map files which would
875
+ # take hundreds of milliseconds to parse, but it is easy enough to just
876
+ # store the unparsed lines (keyed by the first word, which is the
877
+ # texname) and parse them on-demand.
878
+ with open(filename, 'rb') as file:
879
+ self._unparsed = {}
880
+ for line in file:
881
+ tfmname = line.split(b' ', 1)[0]
882
+ self._unparsed.setdefault(tfmname, []).append(line)
883
+ self._parsed = {}
884
+ return self
885
+
886
+ def __getitem__(self, texname):
887
+ assert isinstance(texname, bytes)
888
+ if texname in self._unparsed:
889
+ for line in self._unparsed.pop(texname):
890
+ if self._parse_and_cache_line(line):
891
+ break
892
+ try:
893
+ return self._parsed[texname]
894
+ except KeyError:
895
+ raise LookupError(
896
+ f"An associated PostScript font (required by Matplotlib) "
897
+ f"could not be found for TeX font {texname.decode('ascii')!r} "
898
+ f"in {self._filename!r}; this problem can often be solved by "
899
+ f"installing a suitable PostScript font package in your TeX "
900
+ f"package manager") from None
901
+
902
+ def _parse_and_cache_line(self, line):
903
+ """
904
+ Parse a line in the font mapping file.
905
+
906
+ The format is (partially) documented at
907
+ http://mirrors.ctan.org/systems/doc/pdftex/manual/pdftex-a.pdf
908
+ https://tug.org/texinfohtml/dvips.html#psfonts_002emap
909
+ Each line can have the following fields:
910
+
911
+ - tfmname (first, only required field),
912
+ - psname (defaults to tfmname, must come immediately after tfmname if
913
+ present),
914
+ - fontflags (integer, must come immediately after psname if present,
915
+ ignored by us),
916
+ - special (SlantFont and ExtendFont, only field that is double-quoted),
917
+ - fontfile, encodingfile (optional, prefixed by <, <<, or <[; << always
918
+ precedes a font, <[ always precedes an encoding, < can precede either
919
+ but then an encoding file must have extension .enc; < and << also
920
+ request different font subsetting behaviors but we ignore that; < can
921
+ be separated from the filename by whitespace).
922
+
923
+ special, fontfile, and encodingfile can appear in any order.
924
+ """
925
+ # If the map file specifies multiple encodings for a font, we
926
+ # follow pdfTeX in choosing the last one specified. Such
927
+ # entries are probably mistakes but they have occurred.
928
+ # https://tex.stackexchange.com/q/10826/
929
+
930
+ if not line or line.startswith((b" ", b"%", b"*", b";", b"#")):
931
+ return
932
+ tfmname = basename = special = encodingfile = fontfile = None
933
+ is_subsetted = is_t1 = is_truetype = False
934
+ matches = re.finditer(br'"([^"]*)(?:"|$)|(\S+)', line)
935
+ for match in matches:
936
+ quoted, unquoted = match.groups()
937
+ if unquoted:
938
+ if unquoted.startswith(b"<<"): # font
939
+ fontfile = unquoted[2:]
940
+ elif unquoted.startswith(b"<["): # encoding
941
+ encodingfile = unquoted[2:]
942
+ elif unquoted.startswith(b"<"): # font or encoding
943
+ word = (
944
+ # <foo => foo
945
+ unquoted[1:]
946
+ # < by itself => read the next word
947
+ or next(filter(None, next(matches).groups())))
948
+ if word.endswith(b".enc"):
949
+ encodingfile = word
950
+ else:
951
+ fontfile = word
952
+ is_subsetted = True
953
+ elif tfmname is None:
954
+ tfmname = unquoted
955
+ elif basename is None:
956
+ basename = unquoted
957
+ elif quoted:
958
+ special = quoted
959
+ effects = {}
960
+ if special:
961
+ words = reversed(special.split())
962
+ for word in words:
963
+ if word == b"SlantFont":
964
+ effects["slant"] = float(next(words))
965
+ elif word == b"ExtendFont":
966
+ effects["extend"] = float(next(words))
967
+
968
+ # Verify some properties of the line that would cause it to be ignored
969
+ # otherwise.
970
+ if fontfile is not None:
971
+ if fontfile.endswith((b".ttf", b".ttc")):
972
+ is_truetype = True
973
+ elif not fontfile.endswith(b".otf"):
974
+ is_t1 = True
975
+ elif basename is not None:
976
+ is_t1 = True
977
+ if is_truetype and is_subsetted and encodingfile is None:
978
+ return
979
+ if not is_t1 and ("slant" in effects or "extend" in effects):
980
+ return
981
+ if abs(effects.get("slant", 0)) > 1:
982
+ return
983
+ if abs(effects.get("extend", 0)) > 2:
984
+ return
985
+
986
+ if basename is None:
987
+ basename = tfmname
988
+ if encodingfile is not None:
989
+ encodingfile = find_tex_file(encodingfile)
990
+ if fontfile is not None:
991
+ fontfile = find_tex_file(fontfile)
992
+ self._parsed[tfmname] = PsFont(
993
+ texname=tfmname, psname=basename, effects=effects,
994
+ encoding=encodingfile, filename=fontfile)
995
+ return True
996
+
997
+
998
+ def _parse_enc(path):
999
+ r"""
1000
+ Parse a \*.enc file referenced from a psfonts.map style file.
1001
+
1002
+ The format supported by this function is a tiny subset of PostScript.
1003
+
1004
+ Parameters
1005
+ ----------
1006
+ path : `os.PathLike`
1007
+
1008
+ Returns
1009
+ -------
1010
+ list
1011
+ The nth entry of the list is the PostScript glyph name of the nth
1012
+ glyph.
1013
+ """
1014
+ no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii"))
1015
+ array = re.search(r"(?s)\[(.*)\]", no_comments).group(1)
1016
+ lines = [line for line in array.split() if line]
1017
+ if all(line.startswith("/") for line in lines):
1018
+ return [line[1:] for line in lines]
1019
+ else:
1020
+ raise ValueError(f"Failed to parse {path} as Postscript encoding")
1021
+
1022
+
1023
+ class _LuatexKpsewhich:
1024
+ @lru_cache # A singleton.
1025
+ def __new__(cls):
1026
+ self = object.__new__(cls)
1027
+ self._proc = self._new_proc()
1028
+ return self
1029
+
1030
+ def _new_proc(self):
1031
+ return subprocess.Popen(
1032
+ ["luatex", "--luaonly",
1033
+ str(cbook._get_data_path("kpsewhich.lua"))],
1034
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1035
+
1036
+ def search(self, filename):
1037
+ if self._proc.poll() is not None: # Dead, restart it.
1038
+ self._proc = self._new_proc()
1039
+ self._proc.stdin.write(os.fsencode(filename) + b"\n")
1040
+ self._proc.stdin.flush()
1041
+ out = self._proc.stdout.readline().rstrip()
1042
+ return None if out == b"nil" else os.fsdecode(out)
1043
+
1044
+
1045
+ @lru_cache
1046
+ def find_tex_file(filename):
1047
+ """
1048
+ Find a file in the texmf tree using kpathsea_.
1049
+
1050
+ The kpathsea library, provided by most existing TeX distributions, both
1051
+ on Unix-like systems and on Windows (MikTeX), is invoked via a long-lived
1052
+ luatex process if luatex is installed, or via kpsewhich otherwise.
1053
+
1054
+ .. _kpathsea: https://www.tug.org/kpathsea/
1055
+
1056
+ Parameters
1057
+ ----------
1058
+ filename : str or path-like
1059
+
1060
+ Raises
1061
+ ------
1062
+ FileNotFoundError
1063
+ If the file is not found.
1064
+ """
1065
+
1066
+ # we expect these to always be ascii encoded, but use utf-8
1067
+ # out of caution
1068
+ if isinstance(filename, bytes):
1069
+ filename = filename.decode('utf-8', errors='replace')
1070
+
1071
+ try:
1072
+ lk = _LuatexKpsewhich()
1073
+ except FileNotFoundError:
1074
+ lk = None # Fallback to directly calling kpsewhich, as below.
1075
+
1076
+ if lk:
1077
+ path = lk.search(filename)
1078
+ else:
1079
+ if sys.platform == 'win32':
1080
+ # On Windows only, kpathsea can use utf-8 for cmd args and output.
1081
+ # The `command_line_encoding` environment variable is set to force
1082
+ # it to always use utf-8 encoding. See Matplotlib issue #11848.
1083
+ kwargs = {'env': {**os.environ, 'command_line_encoding': 'utf-8'},
1084
+ 'encoding': 'utf-8'}
1085
+ else: # On POSIX, run through the equivalent of os.fsdecode().
1086
+ kwargs = {'encoding': sys.getfilesystemencoding(),
1087
+ 'errors': 'surrogateescape'}
1088
+
1089
+ try:
1090
+ path = (cbook._check_and_log_subprocess(['kpsewhich', filename],
1091
+ _log, **kwargs)
1092
+ .rstrip('\n'))
1093
+ except (FileNotFoundError, RuntimeError):
1094
+ path = None
1095
+
1096
+ if path:
1097
+ return path
1098
+ else:
1099
+ raise FileNotFoundError(
1100
+ f"Matplotlib's TeX implementation searched for a file named "
1101
+ f"{filename!r} in your texmf tree, but could not find it")
1102
+
1103
+
1104
+ @lru_cache
1105
+ def _fontfile(cls, suffix, texname):
1106
+ return cls(find_tex_file(texname + suffix))
1107
+
1108
+
1109
+ _tfmfile = partial(_fontfile, Tfm, ".tfm")
1110
+ _vffile = partial(_fontfile, Vf, ".vf")
1111
+
1112
+
1113
+ if __name__ == '__main__':
1114
+ from argparse import ArgumentParser
1115
+ import itertools
1116
+
1117
+ parser = ArgumentParser()
1118
+ parser.add_argument("filename")
1119
+ parser.add_argument("dpi", nargs="?", type=float, default=None)
1120
+ args = parser.parse_args()
1121
+ with Dvi(args.filename, args.dpi) as dvi:
1122
+ fontmap = PsfontsMap(find_tex_file('pdftex.map'))
1123
+ for page in dvi:
1124
+ print(f"=== new page === "
1125
+ f"(w: {page.width}, h: {page.height}, d: {page.descent})")
1126
+ for font, group in itertools.groupby(
1127
+ page.text, lambda text: text.font):
1128
+ print(f"font: {font.texname.decode('latin-1')!r}\t"
1129
+ f"scale: {font._scale / 2 ** 20}")
1130
+ print("x", "y", "glyph", "chr", "w", "(glyphs)", sep="\t")
1131
+ for text in group:
1132
+ print(text.x, text.y, text.glyph,
1133
+ chr(text.glyph) if chr(text.glyph).isprintable()
1134
+ else ".",
1135
+ text.width, sep="\t")
1136
+ if page.boxes:
1137
+ print("x", "y", "h", "w", "", "(boxes)", sep="\t")
1138
+ for box in page.boxes:
1139
+ print(box.x, box.y, box.height, box.width, sep="\t")
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import io
3
+ import os
4
+ from enum import Enum
5
+ from collections.abc import Generator
6
+
7
+ from typing import NamedTuple
8
+ from typing_extensions import Self # < Py 3.11
9
+
10
+ class _dvistate(Enum):
11
+ pre = ...
12
+ outer = ...
13
+ inpage = ...
14
+ post_post = ...
15
+ finale = ...
16
+
17
+ class Page(NamedTuple):
18
+ text: list[Text]
19
+ boxes: list[Box]
20
+ height: int
21
+ width: int
22
+ descent: int
23
+
24
+ class Box(NamedTuple):
25
+ x: int
26
+ y: int
27
+ height: int
28
+ width: int
29
+
30
+ class Text(NamedTuple):
31
+ x: int
32
+ y: int
33
+ font: DviFont
34
+ glyph: int
35
+ width: int
36
+ @property
37
+ def font_path(self) -> Path: ...
38
+ @property
39
+ def font_size(self) -> float: ...
40
+ @property
41
+ def font_effects(self) -> dict[str, float]: ...
42
+ @property
43
+ def glyph_name_or_index(self) -> int | str: ...
44
+
45
+ class Dvi:
46
+ file: io.BufferedReader
47
+ dpi: float | None
48
+ fonts: dict[int, DviFont]
49
+ state: _dvistate
50
+ def __init__(self, filename: str | os.PathLike, dpi: float | None) -> None: ...
51
+ def __enter__(self) -> Self: ...
52
+ def __exit__(self, etype, evalue, etrace) -> None: ...
53
+ def __iter__(self) -> Generator[Page, None, None]: ...
54
+ def close(self) -> None: ...
55
+
56
+ class DviFont:
57
+ texname: bytes
58
+ size: float
59
+ widths: list[int]
60
+ def __init__(
61
+ self, scale: float, tfm: Tfm, texname: bytes, vf: Vf | None
62
+ ) -> None: ...
63
+ def __eq__(self, other: object) -> bool: ...
64
+ def __ne__(self, other: object) -> bool: ...
65
+
66
+ class Vf(Dvi):
67
+ def __init__(self, filename: str | os.PathLike) -> None: ...
68
+ def __getitem__(self, code: int) -> Page: ...
69
+
70
+ class Tfm:
71
+ checksum: int
72
+ design_size: int
73
+ width: dict[int, int]
74
+ height: dict[int, int]
75
+ depth: dict[int, int]
76
+ def __init__(self, filename: str | os.PathLike) -> None: ...
77
+
78
+ class PsFont(NamedTuple):
79
+ texname: bytes
80
+ psname: bytes
81
+ effects: dict[str, float]
82
+ encoding: None | bytes
83
+ filename: str
84
+
85
+ class PsfontsMap:
86
+ def __new__(cls, filename: str | os.PathLike) -> Self: ...
87
+ def __getitem__(self, texname: bytes) -> PsFont: ...
88
+
89
+ def find_tex_file(filename: str | os.PathLike) -> str: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py ADDED
The diff for this file is too large to render. See raw diff
 
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable, Hashable, Iterable, Sequence
2
+ import os
3
+ from typing import Any, IO, Literal, TypeVar, overload
4
+
5
+ import numpy as np
6
+ from numpy.typing import ArrayLike
7
+
8
+ from matplotlib.artist import Artist
9
+ from matplotlib.axes import Axes
10
+ from matplotlib.backend_bases import (
11
+ FigureCanvasBase,
12
+ MouseButton,
13
+ MouseEvent,
14
+ RendererBase,
15
+ )
16
+ from matplotlib.colors import Colormap, Normalize
17
+ from matplotlib.colorbar import Colorbar
18
+ from matplotlib.colorizer import ColorizingArtist, Colorizer
19
+ from matplotlib.cm import ScalarMappable
20
+ from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams
21
+ from matplotlib.image import _ImageBase, FigureImage
22
+ from matplotlib.layout_engine import LayoutEngine
23
+ from matplotlib.legend import Legend
24
+ from matplotlib.lines import Line2D
25
+ from matplotlib.patches import Rectangle, Patch
26
+ from matplotlib.text import Text
27
+ from matplotlib.transforms import Affine2D, Bbox, BboxBase, Transform
28
+ from mpl_toolkits.mplot3d import Axes3D
29
+
30
+ from .typing import ColorType, HashableList
31
+
32
+ _T = TypeVar("_T")
33
+
34
+ class FigureBase(Artist):
35
+ artists: list[Artist]
36
+ lines: list[Line2D]
37
+ patches: list[Patch]
38
+ texts: list[Text]
39
+ images: list[_ImageBase]
40
+ legends: list[Legend]
41
+ subfigs: list[SubFigure]
42
+ stale: bool
43
+ suppressComposite: bool | None
44
+ def __init__(self, **kwargs) -> None: ...
45
+ def autofmt_xdate(
46
+ self,
47
+ bottom: float = ...,
48
+ rotation: int = ...,
49
+ ha: Literal["left", "center", "right"] = ...,
50
+ which: Literal["major", "minor", "both"] = ...,
51
+ ) -> None: ...
52
+ def get_children(self) -> list[Artist]: ...
53
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ...
54
+ def suptitle(self, t: str, **kwargs) -> Text: ...
55
+ def get_suptitle(self) -> str: ...
56
+ def supxlabel(self, t: str, **kwargs) -> Text: ...
57
+ def get_supxlabel(self) -> str: ...
58
+ def supylabel(self, t: str, **kwargs) -> Text: ...
59
+ def get_supylabel(self) -> str: ...
60
+ def get_edgecolor(self) -> ColorType: ...
61
+ def get_facecolor(self) -> ColorType: ...
62
+ def get_frameon(self) -> bool: ...
63
+ def set_linewidth(self, linewidth: float) -> None: ...
64
+ def get_linewidth(self) -> float: ...
65
+ def set_edgecolor(self, color: ColorType) -> None: ...
66
+ def set_facecolor(self, color: ColorType) -> None: ...
67
+ @overload
68
+ def get_figure(self, root: Literal[True]) -> Figure: ...
69
+ @overload
70
+ def get_figure(self, root: Literal[False]) -> Figure | SubFigure: ...
71
+ @overload
72
+ def get_figure(self, root: bool = ...) -> Figure | SubFigure: ...
73
+ def set_frameon(self, b: bool) -> None: ...
74
+ @property
75
+ def frameon(self) -> bool: ...
76
+ @frameon.setter
77
+ def frameon(self, b: bool) -> None: ...
78
+ def add_artist(self, artist: Artist, clip: bool = ...) -> Artist: ...
79
+ @overload
80
+ def add_axes(self, ax: Axes) -> Axes: ...
81
+ @overload
82
+ def add_axes(
83
+ self,
84
+ rect: tuple[float, float, float, float],
85
+ projection: None | str = ...,
86
+ polar: bool = ...,
87
+ **kwargs
88
+ ) -> Axes: ...
89
+
90
+ # TODO: docstring indicates SubplotSpec a valid arg, but none of the listed signatures appear to be that
91
+ @overload
92
+ def add_subplot(self, *args: Any, projection: Literal["3d"], **kwargs: Any) -> Axes3D: ...
93
+ @overload
94
+ def add_subplot(
95
+ self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs: Any
96
+ ) -> Axes: ...
97
+ @overload
98
+ def add_subplot(self, pos: int, **kwargs: Any) -> Axes: ...
99
+ @overload
100
+ def add_subplot(self, ax: Axes, **kwargs: Any) -> Axes: ...
101
+ @overload
102
+ def add_subplot(self, ax: SubplotSpec, **kwargs: Any) -> Axes: ...
103
+ @overload
104
+ def add_subplot(self, **kwargs: Any) -> Axes: ...
105
+
106
+ @overload
107
+ def subplots(
108
+ self,
109
+ nrows: Literal[1] = ...,
110
+ ncols: Literal[1] = ...,
111
+ *,
112
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
113
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
114
+ squeeze: Literal[True] = ...,
115
+ width_ratios: Sequence[float] | None = ...,
116
+ height_ratios: Sequence[float] | None = ...,
117
+ subplot_kw: dict[str, Any] | None = ...,
118
+ gridspec_kw: dict[str, Any] | None = ...,
119
+ ) -> Axes: ...
120
+ @overload
121
+ def subplots(
122
+ self,
123
+ nrows: int = ...,
124
+ ncols: int = ...,
125
+ *,
126
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
127
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
128
+ squeeze: Literal[False],
129
+ width_ratios: Sequence[float] | None = ...,
130
+ height_ratios: Sequence[float] | None = ...,
131
+ subplot_kw: dict[str, Any] | None = ...,
132
+ gridspec_kw: dict[str, Any] | None = ...,
133
+ ) -> np.ndarray: ... # TODO numpy/numpy#24738
134
+ @overload
135
+ def subplots(
136
+ self,
137
+ nrows: int = ...,
138
+ ncols: int = ...,
139
+ *,
140
+ sharex: bool | Literal["none", "all", "row", "col"] = ...,
141
+ sharey: bool | Literal["none", "all", "row", "col"] = ...,
142
+ squeeze: bool = ...,
143
+ width_ratios: Sequence[float] | None = ...,
144
+ height_ratios: Sequence[float] | None = ...,
145
+ subplot_kw: dict[str, Any] | None = ...,
146
+ gridspec_kw: dict[str, Any] | None = ...,
147
+ ) -> Any: ...
148
+ def delaxes(self, ax: Axes) -> None: ...
149
+ def clear(self, keep_observers: bool = ...) -> None: ...
150
+ def clf(self, keep_observers: bool = ...) -> None: ...
151
+
152
+ @overload
153
+ def legend(self) -> Legend: ...
154
+ @overload
155
+ def legend(self, handles: Iterable[Artist], labels: Iterable[str], **kwargs) -> Legend: ...
156
+ @overload
157
+ def legend(self, *, handles: Iterable[Artist], **kwargs) -> Legend: ...
158
+ @overload
159
+ def legend(self, labels: Iterable[str], **kwargs) -> Legend: ...
160
+ @overload
161
+ def legend(self, **kwargs) -> Legend: ...
162
+
163
+ def text(
164
+ self,
165
+ x: float,
166
+ y: float,
167
+ s: str,
168
+ fontdict: dict[str, Any] | None = ...,
169
+ **kwargs
170
+ ) -> Text: ...
171
+ def colorbar(
172
+ self,
173
+ mappable: ScalarMappable | ColorizingArtist,
174
+ cax: Axes | None = ...,
175
+ ax: Axes | Iterable[Axes] | None = ...,
176
+ use_gridspec: bool = ...,
177
+ **kwargs
178
+ ) -> Colorbar: ...
179
+ def subplots_adjust(
180
+ self,
181
+ left: float | None = ...,
182
+ bottom: float | None = ...,
183
+ right: float | None = ...,
184
+ top: float | None = ...,
185
+ wspace: float | None = ...,
186
+ hspace: float | None = ...,
187
+ ) -> None: ...
188
+ def align_xlabels(self, axs: Iterable[Axes] | None = ...) -> None: ...
189
+ def align_ylabels(self, axs: Iterable[Axes] | None = ...) -> None: ...
190
+ def align_titles(self, axs: Iterable[Axes] | None = ...) -> None: ...
191
+ def align_labels(self, axs: Iterable[Axes] | None = ...) -> None: ...
192
+ def add_gridspec(self, nrows: int = ..., ncols: int = ..., **kwargs) -> GridSpec: ...
193
+ @overload
194
+ def subfigures(
195
+ self,
196
+ nrows: int = ...,
197
+ ncols: int = ...,
198
+ squeeze: Literal[False] = ...,
199
+ wspace: float | None = ...,
200
+ hspace: float | None = ...,
201
+ width_ratios: ArrayLike | None = ...,
202
+ height_ratios: ArrayLike | None = ...,
203
+ **kwargs
204
+ ) -> np.ndarray: ...
205
+ @overload
206
+ def subfigures(
207
+ self,
208
+ nrows: int = ...,
209
+ ncols: int = ...,
210
+ squeeze: Literal[True] = ...,
211
+ wspace: float | None = ...,
212
+ hspace: float | None = ...,
213
+ width_ratios: ArrayLike | None = ...,
214
+ height_ratios: ArrayLike | None = ...,
215
+ **kwargs
216
+ ) -> np.ndarray | SubFigure: ...
217
+ def add_subfigure(self, subplotspec: SubplotSpec, **kwargs) -> SubFigure: ...
218
+ def sca(self, a: Axes) -> Axes: ...
219
+ def gca(self) -> Axes: ...
220
+ def _gci(self) -> ColorizingArtist | None: ...
221
+ def _process_projection_requirements(
222
+ self, *, axes_class=None, polar=False, projection=None, **kwargs
223
+ ) -> tuple[type[Axes], dict[str, Any]]: ...
224
+ def get_default_bbox_extra_artists(self) -> list[Artist]: ...
225
+ def get_tightbbox(
226
+ self,
227
+ renderer: RendererBase | None = ...,
228
+ *,
229
+ bbox_extra_artists: Iterable[Artist] | None = ...,
230
+ ) -> Bbox: ...
231
+ @overload
232
+ def subplot_mosaic(
233
+ self,
234
+ mosaic: str,
235
+ *,
236
+ sharex: bool = ...,
237
+ sharey: bool = ...,
238
+ width_ratios: ArrayLike | None = ...,
239
+ height_ratios: ArrayLike | None = ...,
240
+ empty_sentinel: str = ...,
241
+ subplot_kw: dict[str, Any] | None = ...,
242
+ per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ...,
243
+ gridspec_kw: dict[str, Any] | None = ...,
244
+ ) -> dict[str, Axes]: ...
245
+ @overload
246
+ def subplot_mosaic(
247
+ self,
248
+ mosaic: list[HashableList[_T]],
249
+ *,
250
+ sharex: bool = ...,
251
+ sharey: bool = ...,
252
+ width_ratios: ArrayLike | None = ...,
253
+ height_ratios: ArrayLike | None = ...,
254
+ empty_sentinel: _T = ...,
255
+ subplot_kw: dict[str, Any] | None = ...,
256
+ per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ...,
257
+ gridspec_kw: dict[str, Any] | None = ...,
258
+ ) -> dict[_T, Axes]: ...
259
+ @overload
260
+ def subplot_mosaic(
261
+ self,
262
+ mosaic: list[HashableList[Hashable]],
263
+ *,
264
+ sharex: bool = ...,
265
+ sharey: bool = ...,
266
+ width_ratios: ArrayLike | None = ...,
267
+ height_ratios: ArrayLike | None = ...,
268
+ empty_sentinel: Any = ...,
269
+ subplot_kw: dict[str, Any] | None = ...,
270
+ per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ...,
271
+ gridspec_kw: dict[str, Any] | None = ...,
272
+ ) -> dict[Hashable, Axes]: ...
273
+
274
+ class SubFigure(FigureBase):
275
+ @property
276
+ def figure(self) -> Figure: ...
277
+ subplotpars: SubplotParams
278
+ dpi_scale_trans: Affine2D
279
+ transFigure: Transform
280
+ bbox_relative: Bbox
281
+ figbbox: BboxBase
282
+ bbox: BboxBase
283
+ transSubfigure: Transform
284
+ patch: Rectangle
285
+ def __init__(
286
+ self,
287
+ parent: Figure | SubFigure,
288
+ subplotspec: SubplotSpec,
289
+ *,
290
+ facecolor: ColorType | None = ...,
291
+ edgecolor: ColorType | None = ...,
292
+ linewidth: float = ...,
293
+ frameon: bool | None = ...,
294
+ **kwargs
295
+ ) -> None: ...
296
+ @property
297
+ def canvas(self) -> FigureCanvasBase: ...
298
+ @property
299
+ def dpi(self) -> float: ...
300
+ @dpi.setter
301
+ def dpi(self, value: float) -> None: ...
302
+ def get_dpi(self) -> float: ...
303
+ def set_dpi(self, val) -> None: ...
304
+ def get_constrained_layout(self) -> bool: ...
305
+ def get_constrained_layout_pads(
306
+ self, relative: bool = ...
307
+ ) -> tuple[float, float, float, float]: ...
308
+ def get_layout_engine(self) -> LayoutEngine: ...
309
+ @property # type: ignore[misc]
310
+ def axes(self) -> list[Axes]: ... # type: ignore[override]
311
+ def get_axes(self) -> list[Axes]: ...
312
+
313
+ class Figure(FigureBase):
314
+ @property
315
+ def figure(self) -> Figure: ...
316
+ bbox_inches: Bbox
317
+ dpi_scale_trans: Affine2D
318
+ bbox: BboxBase
319
+ figbbox: BboxBase
320
+ transFigure: Transform
321
+ transSubfigure: Transform
322
+ patch: Rectangle
323
+ subplotpars: SubplotParams
324
+ def __init__(
325
+ self,
326
+ figsize: tuple[float, float] | None = ...,
327
+ dpi: float | None = ...,
328
+ *,
329
+ facecolor: ColorType | None = ...,
330
+ edgecolor: ColorType | None = ...,
331
+ linewidth: float = ...,
332
+ frameon: bool | None = ...,
333
+ subplotpars: SubplotParams | None = ...,
334
+ tight_layout: bool | dict[str, Any] | None = ...,
335
+ constrained_layout: bool | dict[str, Any] | None = ...,
336
+ layout: Literal["constrained", "compressed", "tight"]
337
+ | LayoutEngine
338
+ | None = ...,
339
+ **kwargs
340
+ ) -> None: ...
341
+ def pick(self, mouseevent: MouseEvent) -> None: ...
342
+ def set_layout_engine(
343
+ self,
344
+ layout: Literal["constrained", "compressed", "tight", "none"]
345
+ | LayoutEngine
346
+ | None = ...,
347
+ **kwargs
348
+ ) -> None: ...
349
+ def get_layout_engine(self) -> LayoutEngine | None: ...
350
+ def _repr_html_(self) -> str | None: ...
351
+ def show(self, warn: bool = ...) -> None: ...
352
+ @property
353
+ def number(self) -> int | str: ...
354
+ @number.setter
355
+ def number(self, num: int | str) -> None: ...
356
+ @property # type: ignore[misc]
357
+ def axes(self) -> list[Axes]: ... # type: ignore[override]
358
+ def get_axes(self) -> list[Axes]: ...
359
+ @property
360
+ def dpi(self) -> float: ...
361
+ @dpi.setter
362
+ def dpi(self, dpi: float) -> None: ...
363
+ def get_tight_layout(self) -> bool: ...
364
+ def get_constrained_layout_pads(
365
+ self, relative: bool = ...
366
+ ) -> tuple[float, float, float, float]: ...
367
+ def get_constrained_layout(self) -> bool: ...
368
+ canvas: FigureCanvasBase
369
+ def set_canvas(self, canvas: FigureCanvasBase) -> None: ...
370
+ def figimage(
371
+ self,
372
+ X: ArrayLike,
373
+ xo: int = ...,
374
+ yo: int = ...,
375
+ alpha: float | None = ...,
376
+ norm: str | Normalize | None = ...,
377
+ cmap: str | Colormap | None = ...,
378
+ vmin: float | None = ...,
379
+ vmax: float | None = ...,
380
+ origin: Literal["upper", "lower"] | None = ...,
381
+ resize: bool = ...,
382
+ *,
383
+ colorizer: Colorizer | None = ...,
384
+ **kwargs
385
+ ) -> FigureImage: ...
386
+ def set_size_inches(
387
+ self, w: float | tuple[float, float], h: float | None = ..., forward: bool = ...
388
+ ) -> None: ...
389
+ def get_size_inches(self) -> np.ndarray: ...
390
+ def get_figwidth(self) -> float: ...
391
+ def get_figheight(self) -> float: ...
392
+ def get_dpi(self) -> float: ...
393
+ def set_dpi(self, val: float) -> None: ...
394
+ def set_figwidth(self, val: float, forward: bool = ...) -> None: ...
395
+ def set_figheight(self, val: float, forward: bool = ...) -> None: ...
396
+ def clear(self, keep_observers: bool = ...) -> None: ...
397
+ def draw_without_rendering(self) -> None: ...
398
+ def draw_artist(self, a: Artist) -> None: ...
399
+ def add_axobserver(self, func: Callable[[Figure], Any]) -> None: ...
400
+ def savefig(
401
+ self,
402
+ fname: str | os.PathLike | IO,
403
+ *,
404
+ transparent: bool | None = ...,
405
+ **kwargs
406
+ ) -> None: ...
407
+ def ginput(
408
+ self,
409
+ n: int = ...,
410
+ timeout: float = ...,
411
+ show_clicks: bool = ...,
412
+ mouse_add: MouseButton = ...,
413
+ mouse_pop: MouseButton = ...,
414
+ mouse_stop: MouseButton = ...,
415
+ ) -> list[tuple[int, int]]: ...
416
+ def waitforbuttonpress(self, timeout: float = ...) -> None | bool: ...
417
+ def tight_layout(
418
+ self,
419
+ *,
420
+ pad: float = ...,
421
+ h_pad: float | None = ...,
422
+ w_pad: float | None = ...,
423
+ rect: tuple[float, float, float, float] | None = ...
424
+ ) -> None: ...
425
+
426
+ def figaspect(
427
+ arg: float | ArrayLike,
428
+ ) -> np.ndarray[tuple[Literal[2]], np.dtype[np.float64]]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py ADDED
@@ -0,0 +1,1645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A module for finding, managing, and using fonts across platforms.
3
+
4
+ This module provides a single `FontManager` instance, ``fontManager``, that can
5
+ be shared across backends and platforms. The `findfont`
6
+ function returns the best TrueType (TTF) font file in the local or
7
+ system font path that matches the specified `FontProperties`
8
+ instance. The `FontManager` also handles Adobe Font Metrics
9
+ (AFM) font files for use by the PostScript backend.
10
+ The `FontManager.addfont` function adds a custom font from a file without
11
+ installing it into your operating system.
12
+
13
+ The design is based on the `W3C Cascading Style Sheet, Level 1 (CSS1)
14
+ font specification <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_.
15
+ Future versions may implement the Level 2 or 2.1 specifications.
16
+ """
17
+
18
+ # KNOWN ISSUES
19
+ #
20
+ # - documentation
21
+ # - font variant is untested
22
+ # - font stretch is incomplete
23
+ # - font size is incomplete
24
+ # - default font algorithm needs improvement and testing
25
+ # - setWeights function needs improvement
26
+ # - 'light' is an invalid weight value, remove it.
27
+
28
+ from __future__ import annotations
29
+
30
+ from base64 import b64encode
31
+ import copy
32
+ import dataclasses
33
+ from functools import lru_cache
34
+ import functools
35
+ from io import BytesIO
36
+ import json
37
+ import logging
38
+ from numbers import Number
39
+ import os
40
+ from pathlib import Path
41
+ import plistlib
42
+ import re
43
+ import subprocess
44
+ import sys
45
+ import threading
46
+
47
+ import matplotlib as mpl
48
+ from matplotlib import _api, _afm, cbook, ft2font
49
+ from matplotlib._fontconfig_pattern import (
50
+ parse_fontconfig_pattern, generate_fontconfig_pattern)
51
+ from matplotlib.rcsetup import _validators
52
+
53
+ _log = logging.getLogger(__name__)
54
+
55
+ font_scalings = {
56
+ 'xx-small': 0.579,
57
+ 'x-small': 0.694,
58
+ 'small': 0.833,
59
+ 'medium': 1.0,
60
+ 'large': 1.200,
61
+ 'x-large': 1.440,
62
+ 'xx-large': 1.728,
63
+ 'larger': 1.2,
64
+ 'smaller': 0.833,
65
+ None: 1.0,
66
+ }
67
+ stretch_dict = {
68
+ 'ultra-condensed': 100,
69
+ 'extra-condensed': 200,
70
+ 'condensed': 300,
71
+ 'semi-condensed': 400,
72
+ 'normal': 500,
73
+ 'semi-expanded': 600,
74
+ 'semi-extended': 600,
75
+ 'expanded': 700,
76
+ 'extended': 700,
77
+ 'extra-expanded': 800,
78
+ 'extra-extended': 800,
79
+ 'ultra-expanded': 900,
80
+ 'ultra-extended': 900,
81
+ }
82
+ weight_dict = {
83
+ 'ultralight': 100,
84
+ 'light': 200,
85
+ 'normal': 400,
86
+ 'regular': 400,
87
+ 'book': 400,
88
+ 'medium': 500,
89
+ 'roman': 500,
90
+ 'semibold': 600,
91
+ 'demibold': 600,
92
+ 'demi': 600,
93
+ 'bold': 700,
94
+ 'heavy': 800,
95
+ 'extra bold': 800,
96
+ 'black': 900,
97
+ }
98
+ _weight_regexes = [
99
+ # From fontconfig's FcFreeTypeQueryFaceInternal; not the same as
100
+ # weight_dict!
101
+ ("thin", 100),
102
+ ("extralight", 200),
103
+ ("ultralight", 200),
104
+ ("demilight", 350),
105
+ ("semilight", 350),
106
+ ("light", 300), # Needs to come *after* demi/semilight!
107
+ ("book", 380),
108
+ ("regular", 400),
109
+ ("normal", 400),
110
+ ("medium", 500),
111
+ ("demibold", 600),
112
+ ("demi", 600),
113
+ ("semibold", 600),
114
+ ("extrabold", 800),
115
+ ("superbold", 800),
116
+ ("ultrabold", 800),
117
+ ("bold", 700), # Needs to come *after* extra/super/ultrabold!
118
+ ("ultrablack", 1000),
119
+ ("superblack", 1000),
120
+ ("extrablack", 1000),
121
+ (r"\bultra", 1000),
122
+ ("black", 900), # Needs to come *after* ultra/super/extrablack!
123
+ ("heavy", 900),
124
+ ]
125
+ font_family_aliases = {
126
+ 'serif',
127
+ 'sans-serif',
128
+ 'sans serif',
129
+ 'cursive',
130
+ 'fantasy',
131
+ 'monospace',
132
+ 'sans',
133
+ }
134
+
135
+ # OS Font paths
136
+ try:
137
+ _HOME = Path.home()
138
+ except Exception: # Exceptions thrown by home() are not specified...
139
+ _HOME = Path(os.devnull) # Just an arbitrary path with no children.
140
+ MSFolders = \
141
+ r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
142
+ MSFontDirectories = [
143
+ r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
144
+ r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts']
145
+ MSUserFontDirectories = [
146
+ str(_HOME / 'AppData/Local/Microsoft/Windows/Fonts'),
147
+ str(_HOME / 'AppData/Roaming/Microsoft/Windows/Fonts'),
148
+ ]
149
+ X11FontDirectories = [
150
+ # an old standard installation point
151
+ "/usr/X11R6/lib/X11/fonts/TTF/",
152
+ "/usr/X11/lib/X11/fonts",
153
+ # here is the new standard location for fonts
154
+ "/usr/share/fonts/",
155
+ # documented as a good place to install new fonts
156
+ "/usr/local/share/fonts/",
157
+ # common application, not really useful
158
+ "/usr/lib/openoffice/share/fonts/truetype/",
159
+ # user fonts
160
+ str((Path(os.environ.get('XDG_DATA_HOME') or _HOME / ".local/share"))
161
+ / "fonts"),
162
+ str(_HOME / ".fonts"),
163
+ ]
164
+ OSXFontDirectories = [
165
+ "/Library/Fonts/",
166
+ "/Network/Library/Fonts/",
167
+ "/System/Library/Fonts/",
168
+ # fonts installed via MacPorts
169
+ "/opt/local/share/fonts",
170
+ # user fonts
171
+ str(_HOME / "Library/Fonts"),
172
+ ]
173
+
174
+
175
+ def get_fontext_synonyms(fontext):
176
+ """
177
+ Return a list of file extensions that are synonyms for
178
+ the given file extension *fileext*.
179
+ """
180
+ return {
181
+ 'afm': ['afm'],
182
+ 'otf': ['otf', 'ttc', 'ttf'],
183
+ 'ttc': ['otf', 'ttc', 'ttf'],
184
+ 'ttf': ['otf', 'ttc', 'ttf'],
185
+ }[fontext]
186
+
187
+
188
+ def list_fonts(directory, extensions):
189
+ """
190
+ Return a list of all fonts matching any of the extensions, found
191
+ recursively under the directory.
192
+ """
193
+ extensions = ["." + ext for ext in extensions]
194
+ return [os.path.join(dirpath, filename)
195
+ # os.walk ignores access errors, unlike Path.glob.
196
+ for dirpath, _, filenames in os.walk(directory)
197
+ for filename in filenames
198
+ if Path(filename).suffix.lower() in extensions]
199
+
200
+
201
+ def win32FontDirectory():
202
+ r"""
203
+ Return the user-specified font directory for Win32. This is
204
+ looked up from the registry key ::
205
+
206
+ \\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Fonts
207
+
208
+ If the key is not found, ``%WINDIR%\Fonts`` will be returned.
209
+ """ # noqa: E501
210
+ import winreg
211
+ try:
212
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) as user:
213
+ return winreg.QueryValueEx(user, 'Fonts')[0]
214
+ except OSError:
215
+ return os.path.join(os.environ['WINDIR'], 'Fonts')
216
+
217
+
218
+ def _get_win32_installed_fonts():
219
+ """List the font paths known to the Windows registry."""
220
+ import winreg
221
+ items = set()
222
+ # Search and resolve fonts listed in the registry.
223
+ for domain, base_dirs in [
224
+ (winreg.HKEY_LOCAL_MACHINE, [win32FontDirectory()]), # System.
225
+ (winreg.HKEY_CURRENT_USER, MSUserFontDirectories), # User.
226
+ ]:
227
+ for base_dir in base_dirs:
228
+ for reg_path in MSFontDirectories:
229
+ try:
230
+ with winreg.OpenKey(domain, reg_path) as local:
231
+ for j in range(winreg.QueryInfoKey(local)[1]):
232
+ # value may contain the filename of the font or its
233
+ # absolute path.
234
+ key, value, tp = winreg.EnumValue(local, j)
235
+ if not isinstance(value, str):
236
+ continue
237
+ try:
238
+ # If value contains already an absolute path,
239
+ # then it is not changed further.
240
+ path = Path(base_dir, value).resolve()
241
+ except RuntimeError:
242
+ # Don't fail with invalid entries.
243
+ continue
244
+ items.add(path)
245
+ except (OSError, MemoryError):
246
+ continue
247
+ return items
248
+
249
+
250
+ @lru_cache
251
+ def _get_fontconfig_fonts():
252
+ """Cache and list the font paths known to ``fc-list``."""
253
+ try:
254
+ if b'--format' not in subprocess.check_output(['fc-list', '--help']):
255
+ _log.warning( # fontconfig 2.7 implemented --format.
256
+ 'Matplotlib needs fontconfig>=2.7 to query system fonts.')
257
+ return []
258
+ out = subprocess.check_output(['fc-list', '--format=%{file}\\n'])
259
+ except (OSError, subprocess.CalledProcessError):
260
+ return []
261
+ return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')]
262
+
263
+
264
+ @lru_cache
265
+ def _get_macos_fonts():
266
+ """Cache and list the font paths known to ``system_profiler SPFontsDataType``."""
267
+ try:
268
+ d, = plistlib.loads(
269
+ subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"]))
270
+ except (OSError, subprocess.CalledProcessError, plistlib.InvalidFileException):
271
+ return []
272
+ return [Path(entry["path"]) for entry in d["_items"]]
273
+
274
+
275
+ def findSystemFonts(fontpaths=None, fontext='ttf'):
276
+ """
277
+ Search for fonts in the specified font paths. If no paths are
278
+ given, will use a standard set of system paths, as well as the
279
+ list of fonts tracked by fontconfig if fontconfig is installed and
280
+ available. A list of TrueType fonts are returned by default with
281
+ AFM fonts as an option.
282
+ """
283
+ fontfiles = set()
284
+ fontexts = get_fontext_synonyms(fontext)
285
+
286
+ if fontpaths is None:
287
+ if sys.platform == 'win32':
288
+ installed_fonts = _get_win32_installed_fonts()
289
+ fontpaths = []
290
+ else:
291
+ installed_fonts = _get_fontconfig_fonts()
292
+ if sys.platform == 'darwin':
293
+ installed_fonts += _get_macos_fonts()
294
+ fontpaths = [*X11FontDirectories, *OSXFontDirectories]
295
+ else:
296
+ fontpaths = X11FontDirectories
297
+ fontfiles.update(str(path) for path in installed_fonts
298
+ if path.suffix.lower()[1:] in fontexts)
299
+
300
+ elif isinstance(fontpaths, str):
301
+ fontpaths = [fontpaths]
302
+
303
+ for path in fontpaths:
304
+ fontfiles.update(map(os.path.abspath, list_fonts(path, fontexts)))
305
+
306
+ return [fname for fname in fontfiles if os.path.exists(fname)]
307
+
308
+
309
+ @dataclasses.dataclass(frozen=True)
310
+ class FontEntry:
311
+ """
312
+ A class for storing Font properties.
313
+
314
+ It is used when populating the font lookup dictionary.
315
+ """
316
+
317
+ fname: str = ''
318
+ name: str = ''
319
+ style: str = 'normal'
320
+ variant: str = 'normal'
321
+ weight: str | int = 'normal'
322
+ stretch: str = 'normal'
323
+ size: str = 'medium'
324
+
325
+ def _repr_html_(self) -> str:
326
+ png_stream = self._repr_png_()
327
+ png_b64 = b64encode(png_stream).decode()
328
+ return f"<img src=\"data:image/png;base64, {png_b64}\" />"
329
+
330
+ def _repr_png_(self) -> bytes:
331
+ from matplotlib.figure import Figure # Circular import.
332
+ fig = Figure()
333
+ font_path = Path(self.fname) if self.fname != '' else None
334
+ fig.text(0, 0, self.name, font=font_path)
335
+ with BytesIO() as buf:
336
+ fig.savefig(buf, bbox_inches='tight', transparent=True)
337
+ return buf.getvalue()
338
+
339
+
340
+ def ttfFontProperty(font):
341
+ """
342
+ Extract information from a TrueType font file.
343
+
344
+ Parameters
345
+ ----------
346
+ font : `.FT2Font`
347
+ The TrueType font file from which information will be extracted.
348
+
349
+ Returns
350
+ -------
351
+ `FontEntry`
352
+ The extracted font properties.
353
+
354
+ """
355
+ name = font.family_name
356
+
357
+ # Styles are: italic, oblique, and normal (default)
358
+
359
+ sfnt = font.get_sfnt()
360
+ mac_key = (1, # platform: macintosh
361
+ 0, # id: roman
362
+ 0) # langid: english
363
+ ms_key = (3, # platform: microsoft
364
+ 1, # id: unicode_cs
365
+ 0x0409) # langid: english_united_states
366
+
367
+ # These tables are actually mac_roman-encoded, but mac_roman support may be
368
+ # missing in some alternative Python implementations and we are only going
369
+ # to look for ASCII substrings, where any ASCII-compatible encoding works
370
+ # - or big-endian UTF-16, since important Microsoft fonts use that.
371
+ sfnt2 = (sfnt.get((*mac_key, 2), b'').decode('latin-1').lower() or
372
+ sfnt.get((*ms_key, 2), b'').decode('utf_16_be').lower())
373
+ sfnt4 = (sfnt.get((*mac_key, 4), b'').decode('latin-1').lower() or
374
+ sfnt.get((*ms_key, 4), b'').decode('utf_16_be').lower())
375
+
376
+ if sfnt4.find('oblique') >= 0:
377
+ style = 'oblique'
378
+ elif sfnt4.find('italic') >= 0:
379
+ style = 'italic'
380
+ elif sfnt2.find('regular') >= 0:
381
+ style = 'normal'
382
+ elif ft2font.StyleFlags.ITALIC in font.style_flags:
383
+ style = 'italic'
384
+ else:
385
+ style = 'normal'
386
+
387
+ # Variants are: small-caps and normal (default)
388
+
389
+ # !!!! Untested
390
+ if name.lower() in ['capitals', 'small-caps']:
391
+ variant = 'small-caps'
392
+ else:
393
+ variant = 'normal'
394
+
395
+ # The weight-guessing algorithm is directly translated from fontconfig
396
+ # 2.13.1's FcFreeTypeQueryFaceInternal (fcfreetype.c).
397
+ wws_subfamily = 22
398
+ typographic_subfamily = 16
399
+ font_subfamily = 2
400
+ styles = [
401
+ sfnt.get((*mac_key, wws_subfamily), b'').decode('latin-1'),
402
+ sfnt.get((*mac_key, typographic_subfamily), b'').decode('latin-1'),
403
+ sfnt.get((*mac_key, font_subfamily), b'').decode('latin-1'),
404
+ sfnt.get((*ms_key, wws_subfamily), b'').decode('utf-16-be'),
405
+ sfnt.get((*ms_key, typographic_subfamily), b'').decode('utf-16-be'),
406
+ sfnt.get((*ms_key, font_subfamily), b'').decode('utf-16-be'),
407
+ ]
408
+ styles = [*filter(None, styles)] or [font.style_name]
409
+
410
+ def get_weight(): # From fontconfig's FcFreeTypeQueryFaceInternal.
411
+ # OS/2 table weight.
412
+ os2 = font.get_sfnt_table("OS/2")
413
+ if os2 and os2["version"] != 0xffff:
414
+ return os2["usWeightClass"]
415
+ # PostScript font info weight.
416
+ try:
417
+ ps_font_info_weight = (
418
+ font.get_ps_font_info()["weight"].replace(" ", "") or "")
419
+ except ValueError:
420
+ pass
421
+ else:
422
+ for regex, weight in _weight_regexes:
423
+ if re.fullmatch(regex, ps_font_info_weight, re.I):
424
+ return weight
425
+ # Style name weight.
426
+ for style in styles:
427
+ style = style.replace(" ", "")
428
+ for regex, weight in _weight_regexes:
429
+ if re.search(regex, style, re.I):
430
+ return weight
431
+ if ft2font.StyleFlags.BOLD in font.style_flags:
432
+ return 700 # "bold"
433
+ return 500 # "medium", not "regular"!
434
+
435
+ weight = int(get_weight())
436
+
437
+ # Stretch can be absolute and relative
438
+ # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
439
+ # semi-condensed, normal, semi-expanded, expanded, extra-expanded,
440
+ # and ultra-expanded.
441
+ # Relative stretches are: wider, narrower
442
+ # Child value is: inherit
443
+
444
+ if any(word in sfnt4 for word in ['narrow', 'condensed', 'cond']):
445
+ stretch = 'condensed'
446
+ elif 'demi cond' in sfnt4:
447
+ stretch = 'semi-condensed'
448
+ elif any(word in sfnt4 for word in ['wide', 'expanded', 'extended']):
449
+ stretch = 'expanded'
450
+ else:
451
+ stretch = 'normal'
452
+
453
+ # Sizes can be absolute and relative.
454
+ # Absolute sizes are: xx-small, x-small, small, medium, large, x-large,
455
+ # and xx-large.
456
+ # Relative sizes are: larger, smaller
457
+ # Length value is an absolute font size, e.g., 12pt
458
+ # Percentage values are in 'em's. Most robust specification.
459
+
460
+ if not font.scalable:
461
+ raise NotImplementedError("Non-scalable fonts are not supported")
462
+ size = 'scalable'
463
+
464
+ return FontEntry(font.fname, name, style, variant, weight, stretch, size)
465
+
466
+
467
+ def afmFontProperty(fontpath, font):
468
+ """
469
+ Extract information from an AFM font file.
470
+
471
+ Parameters
472
+ ----------
473
+ fontpath : str
474
+ The filename corresponding to *font*.
475
+ font : AFM
476
+ The AFM font file from which information will be extracted.
477
+
478
+ Returns
479
+ -------
480
+ `FontEntry`
481
+ The extracted font properties.
482
+ """
483
+
484
+ name = font.get_familyname()
485
+ fontname = font.get_fontname().lower()
486
+
487
+ # Styles are: italic, oblique, and normal (default)
488
+
489
+ if font.get_angle() != 0 or 'italic' in name.lower():
490
+ style = 'italic'
491
+ elif 'oblique' in name.lower():
492
+ style = 'oblique'
493
+ else:
494
+ style = 'normal'
495
+
496
+ # Variants are: small-caps and normal (default)
497
+
498
+ # !!!! Untested
499
+ if name.lower() in ['capitals', 'small-caps']:
500
+ variant = 'small-caps'
501
+ else:
502
+ variant = 'normal'
503
+
504
+ weight = font.get_weight().lower()
505
+ if weight not in weight_dict:
506
+ weight = 'normal'
507
+
508
+ # Stretch can be absolute and relative
509
+ # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
510
+ # semi-condensed, normal, semi-expanded, expanded, extra-expanded,
511
+ # and ultra-expanded.
512
+ # Relative stretches are: wider, narrower
513
+ # Child value is: inherit
514
+ if 'demi cond' in fontname:
515
+ stretch = 'semi-condensed'
516
+ elif any(word in fontname for word in ['narrow', 'cond']):
517
+ stretch = 'condensed'
518
+ elif any(word in fontname for word in ['wide', 'expanded', 'extended']):
519
+ stretch = 'expanded'
520
+ else:
521
+ stretch = 'normal'
522
+
523
+ # Sizes can be absolute and relative.
524
+ # Absolute sizes are: xx-small, x-small, small, medium, large, x-large,
525
+ # and xx-large.
526
+ # Relative sizes are: larger, smaller
527
+ # Length value is an absolute font size, e.g., 12pt
528
+ # Percentage values are in 'em's. Most robust specification.
529
+
530
+ # All AFM fonts are apparently scalable.
531
+
532
+ size = 'scalable'
533
+
534
+ return FontEntry(fontpath, name, style, variant, weight, stretch, size)
535
+
536
+
537
+ def _cleanup_fontproperties_init(init_method):
538
+ """
539
+ A decorator to limit the call signature to single a positional argument
540
+ or alternatively only keyword arguments.
541
+
542
+ We still accept but deprecate all other call signatures.
543
+
544
+ When the deprecation expires we can switch the signature to::
545
+
546
+ __init__(self, pattern=None, /, *, family=None, style=None, ...)
547
+
548
+ plus a runtime check that pattern is not used alongside with the
549
+ keyword arguments. This results eventually in the two possible
550
+ call signatures::
551
+
552
+ FontProperties(pattern)
553
+ FontProperties(family=..., size=..., ...)
554
+
555
+ """
556
+ @functools.wraps(init_method)
557
+ def wrapper(self, *args, **kwargs):
558
+ # multiple args with at least some positional ones
559
+ if len(args) > 1 or len(args) == 1 and kwargs:
560
+ # Note: Both cases were previously handled as individual properties.
561
+ # Therefore, we do not mention the case of font properties here.
562
+ _api.warn_deprecated(
563
+ "3.10",
564
+ message="Passing individual properties to FontProperties() "
565
+ "positionally was deprecated in Matplotlib %(since)s and "
566
+ "will be removed in %(removal)s. Please pass all properties "
567
+ "via keyword arguments."
568
+ )
569
+ # single non-string arg -> clearly a family not a pattern
570
+ if len(args) == 1 and not kwargs and not cbook.is_scalar_or_string(args[0]):
571
+ # Case font-family list passed as single argument
572
+ _api.warn_deprecated(
573
+ "3.10",
574
+ message="Passing family as positional argument to FontProperties() "
575
+ "was deprecated in Matplotlib %(since)s and will be removed "
576
+ "in %(removal)s. Please pass family names as keyword"
577
+ "argument."
578
+ )
579
+ # Note on single string arg:
580
+ # This has been interpreted as pattern so far. We are already raising if a
581
+ # non-pattern compatible family string was given. Therefore, we do not need
582
+ # to warn for this case.
583
+ return init_method(self, *args, **kwargs)
584
+
585
+ return wrapper
586
+
587
+
588
+ class FontProperties:
589
+ """
590
+ A class for storing and manipulating font properties.
591
+
592
+ The font properties are the six properties described in the
593
+ `W3C Cascading Style Sheet, Level 1
594
+ <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_ font
595
+ specification and *math_fontfamily* for math fonts:
596
+
597
+ - family: A list of font names in decreasing order of priority.
598
+ The items may include a generic font family name, either 'sans-serif',
599
+ 'serif', 'cursive', 'fantasy', or 'monospace'. In that case, the actual
600
+ font to be used will be looked up from the associated rcParam during the
601
+ search process in `.findfont`. Default: :rc:`font.family`
602
+
603
+ - style: Either 'normal', 'italic' or 'oblique'.
604
+ Default: :rc:`font.style`
605
+
606
+ - variant: Either 'normal' or 'small-caps'.
607
+ Default: :rc:`font.variant`
608
+
609
+ - stretch: A numeric value in the range 0-1000 or one of
610
+ 'ultra-condensed', 'extra-condensed', 'condensed',
611
+ 'semi-condensed', 'normal', 'semi-expanded', 'expanded',
612
+ 'extra-expanded' or 'ultra-expanded'. Default: :rc:`font.stretch`
613
+
614
+ - weight: A numeric value in the range 0-1000 or one of
615
+ 'ultralight', 'light', 'normal', 'regular', 'book', 'medium',
616
+ 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy',
617
+ 'extra bold', 'black'. Default: :rc:`font.weight`
618
+
619
+ - size: Either a relative value of 'xx-small', 'x-small',
620
+ 'small', 'medium', 'large', 'x-large', 'xx-large' or an
621
+ absolute font size, e.g., 10. Default: :rc:`font.size`
622
+
623
+ - math_fontfamily: The family of fonts used to render math text.
624
+ Supported values are: 'dejavusans', 'dejavuserif', 'cm',
625
+ 'stix', 'stixsans' and 'custom'. Default: :rc:`mathtext.fontset`
626
+
627
+ Alternatively, a font may be specified using the absolute path to a font
628
+ file, by using the *fname* kwarg. However, in this case, it is typically
629
+ simpler to just pass the path (as a `pathlib.Path`, not a `str`) to the
630
+ *font* kwarg of the `.Text` object.
631
+
632
+ The preferred usage of font sizes is to use the relative values,
633
+ e.g., 'large', instead of absolute font sizes, e.g., 12. This
634
+ approach allows all text sizes to be made larger or smaller based
635
+ on the font manager's default font size.
636
+
637
+ This class accepts a single positional string as fontconfig_ pattern_,
638
+ or alternatively individual properties as keyword arguments::
639
+
640
+ FontProperties(pattern)
641
+ FontProperties(*, family=None, style=None, variant=None, ...)
642
+
643
+ This support does not depend on fontconfig; we are merely borrowing its
644
+ pattern syntax for use here.
645
+
646
+ .. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/
647
+ .. _pattern:
648
+ https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
649
+
650
+ Note that Matplotlib's internal font manager and fontconfig use a
651
+ different algorithm to lookup fonts, so the results of the same pattern
652
+ may be different in Matplotlib than in other applications that use
653
+ fontconfig.
654
+ """
655
+
656
+ @_cleanup_fontproperties_init
657
+ def __init__(self, family=None, style=None, variant=None, weight=None,
658
+ stretch=None, size=None,
659
+ fname=None, # if set, it's a hardcoded filename to use
660
+ math_fontfamily=None):
661
+ self.set_family(family)
662
+ self.set_style(style)
663
+ self.set_variant(variant)
664
+ self.set_weight(weight)
665
+ self.set_stretch(stretch)
666
+ self.set_file(fname)
667
+ self.set_size(size)
668
+ self.set_math_fontfamily(math_fontfamily)
669
+ # Treat family as a fontconfig pattern if it is the only parameter
670
+ # provided. Even in that case, call the other setters first to set
671
+ # attributes not specified by the pattern to the rcParams defaults.
672
+ if (isinstance(family, str)
673
+ and style is None and variant is None and weight is None
674
+ and stretch is None and size is None and fname is None):
675
+ self.set_fontconfig_pattern(family)
676
+
677
+ @classmethod
678
+ def _from_any(cls, arg):
679
+ """
680
+ Generic constructor which can build a `.FontProperties` from any of the
681
+ following:
682
+
683
+ - a `.FontProperties`: it is passed through as is;
684
+ - `None`: a `.FontProperties` using rc values is used;
685
+ - an `os.PathLike`: it is used as path to the font file;
686
+ - a `str`: it is parsed as a fontconfig pattern;
687
+ - a `dict`: it is passed as ``**kwargs`` to `.FontProperties`.
688
+ """
689
+ if arg is None:
690
+ return cls()
691
+ elif isinstance(arg, cls):
692
+ return arg
693
+ elif isinstance(arg, os.PathLike):
694
+ return cls(fname=arg)
695
+ elif isinstance(arg, str):
696
+ return cls(arg)
697
+ else:
698
+ return cls(**arg)
699
+
700
+ def __hash__(self):
701
+ l = (tuple(self.get_family()),
702
+ self.get_slant(),
703
+ self.get_variant(),
704
+ self.get_weight(),
705
+ self.get_stretch(),
706
+ self.get_size(),
707
+ self.get_file(),
708
+ self.get_math_fontfamily())
709
+ return hash(l)
710
+
711
+ def __eq__(self, other):
712
+ return hash(self) == hash(other)
713
+
714
+ def __str__(self):
715
+ return self.get_fontconfig_pattern()
716
+
717
+ def get_family(self):
718
+ """
719
+ Return a list of individual font family names or generic family names.
720
+
721
+ The font families or generic font families (which will be resolved
722
+ from their respective rcParams when searching for a matching font) in
723
+ the order of preference.
724
+ """
725
+ return self._family
726
+
727
+ def get_name(self):
728
+ """
729
+ Return the name of the font that best matches the font properties.
730
+ """
731
+ return get_font(findfont(self)).family_name
732
+
733
+ def get_style(self):
734
+ """
735
+ Return the font style. Values are: 'normal', 'italic' or 'oblique'.
736
+ """
737
+ return self._slant
738
+
739
+ def get_variant(self):
740
+ """
741
+ Return the font variant. Values are: 'normal' or 'small-caps'.
742
+ """
743
+ return self._variant
744
+
745
+ def get_weight(self):
746
+ """
747
+ Set the font weight. Options are: A numeric value in the
748
+ range 0-1000 or one of 'light', 'normal', 'regular', 'book',
749
+ 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold',
750
+ 'heavy', 'extra bold', 'black'
751
+ """
752
+ return self._weight
753
+
754
+ def get_stretch(self):
755
+ """
756
+ Return the font stretch or width. Options are: 'ultra-condensed',
757
+ 'extra-condensed', 'condensed', 'semi-condensed', 'normal',
758
+ 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'.
759
+ """
760
+ return self._stretch
761
+
762
+ def get_size(self):
763
+ """
764
+ Return the font size.
765
+ """
766
+ return self._size
767
+
768
+ def get_file(self):
769
+ """
770
+ Return the filename of the associated font.
771
+ """
772
+ return self._file
773
+
774
+ def get_fontconfig_pattern(self):
775
+ """
776
+ Get a fontconfig_ pattern_ suitable for looking up the font as
777
+ specified with fontconfig's ``fc-match`` utility.
778
+
779
+ This support does not depend on fontconfig; we are merely borrowing its
780
+ pattern syntax for use here.
781
+ """
782
+ return generate_fontconfig_pattern(self)
783
+
784
+ def set_family(self, family):
785
+ """
786
+ Change the font family. Can be either an alias (generic name
787
+ is CSS parlance), such as: 'serif', 'sans-serif', 'cursive',
788
+ 'fantasy', or 'monospace', a real font name or a list of real
789
+ font names. Real font names are not supported when
790
+ :rc:`text.usetex` is `True`. Default: :rc:`font.family`
791
+ """
792
+ if family is None:
793
+ family = mpl.rcParams['font.family']
794
+ if isinstance(family, str):
795
+ family = [family]
796
+ self._family = family
797
+
798
+ def set_style(self, style):
799
+ """
800
+ Set the font style.
801
+
802
+ Parameters
803
+ ----------
804
+ style : {'normal', 'italic', 'oblique'}, default: :rc:`font.style`
805
+ """
806
+ if style is None:
807
+ style = mpl.rcParams['font.style']
808
+ _api.check_in_list(['normal', 'italic', 'oblique'], style=style)
809
+ self._slant = style
810
+
811
+ def set_variant(self, variant):
812
+ """
813
+ Set the font variant.
814
+
815
+ Parameters
816
+ ----------
817
+ variant : {'normal', 'small-caps'}, default: :rc:`font.variant`
818
+ """
819
+ if variant is None:
820
+ variant = mpl.rcParams['font.variant']
821
+ _api.check_in_list(['normal', 'small-caps'], variant=variant)
822
+ self._variant = variant
823
+
824
+ def set_weight(self, weight):
825
+ """
826
+ Set the font weight.
827
+
828
+ Parameters
829
+ ----------
830
+ weight : int or {'ultralight', 'light', 'normal', 'regular', 'book', \
831
+ 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', \
832
+ 'extra bold', 'black'}, default: :rc:`font.weight`
833
+ If int, must be in the range 0-1000.
834
+ """
835
+ if weight is None:
836
+ weight = mpl.rcParams['font.weight']
837
+ if weight in weight_dict:
838
+ self._weight = weight
839
+ return
840
+ try:
841
+ weight = int(weight)
842
+ except ValueError:
843
+ pass
844
+ else:
845
+ if 0 <= weight <= 1000:
846
+ self._weight = weight
847
+ return
848
+ raise ValueError(f"{weight=} is invalid")
849
+
850
+ def set_stretch(self, stretch):
851
+ """
852
+ Set the font stretch or width.
853
+
854
+ Parameters
855
+ ----------
856
+ stretch : int or {'ultra-condensed', 'extra-condensed', 'condensed', \
857
+ 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', \
858
+ 'ultra-expanded'}, default: :rc:`font.stretch`
859
+ If int, must be in the range 0-1000.
860
+ """
861
+ if stretch is None:
862
+ stretch = mpl.rcParams['font.stretch']
863
+ if stretch in stretch_dict:
864
+ self._stretch = stretch
865
+ return
866
+ try:
867
+ stretch = int(stretch)
868
+ except ValueError:
869
+ pass
870
+ else:
871
+ if 0 <= stretch <= 1000:
872
+ self._stretch = stretch
873
+ return
874
+ raise ValueError(f"{stretch=} is invalid")
875
+
876
+ def set_size(self, size):
877
+ """
878
+ Set the font size.
879
+
880
+ Parameters
881
+ ----------
882
+ size : float or {'xx-small', 'x-small', 'small', 'medium', \
883
+ 'large', 'x-large', 'xx-large'}, default: :rc:`font.size`
884
+ If a float, the font size in points. The string values denote
885
+ sizes relative to the default font size.
886
+ """
887
+ if size is None:
888
+ size = mpl.rcParams['font.size']
889
+ try:
890
+ size = float(size)
891
+ except ValueError:
892
+ try:
893
+ scale = font_scalings[size]
894
+ except KeyError as err:
895
+ raise ValueError(
896
+ "Size is invalid. Valid font size are "
897
+ + ", ".join(map(str, font_scalings))) from err
898
+ else:
899
+ size = scale * FontManager.get_default_size()
900
+ if size < 1.0:
901
+ _log.info('Fontsize %1.2f < 1.0 pt not allowed by FreeType. '
902
+ 'Setting fontsize = 1 pt', size)
903
+ size = 1.0
904
+ self._size = size
905
+
906
+ def set_file(self, file):
907
+ """
908
+ Set the filename of the fontfile to use. In this case, all
909
+ other properties will be ignored.
910
+ """
911
+ self._file = os.fspath(file) if file is not None else None
912
+
913
+ def set_fontconfig_pattern(self, pattern):
914
+ """
915
+ Set the properties by parsing a fontconfig_ *pattern*.
916
+
917
+ This support does not depend on fontconfig; we are merely borrowing its
918
+ pattern syntax for use here.
919
+ """
920
+ for key, val in parse_fontconfig_pattern(pattern).items():
921
+ if type(val) is list:
922
+ getattr(self, "set_" + key)(val[0])
923
+ else:
924
+ getattr(self, "set_" + key)(val)
925
+
926
+ def get_math_fontfamily(self):
927
+ """
928
+ Return the name of the font family used for math text.
929
+
930
+ The default font is :rc:`mathtext.fontset`.
931
+ """
932
+ return self._math_fontfamily
933
+
934
+ def set_math_fontfamily(self, fontfamily):
935
+ """
936
+ Set the font family for text in math mode.
937
+
938
+ If not set explicitly, :rc:`mathtext.fontset` will be used.
939
+
940
+ Parameters
941
+ ----------
942
+ fontfamily : str
943
+ The name of the font family.
944
+
945
+ Available font families are defined in the
946
+ :ref:`default matplotlibrc file <customizing-with-matplotlibrc-files>`.
947
+
948
+ See Also
949
+ --------
950
+ .text.Text.get_math_fontfamily
951
+ """
952
+ if fontfamily is None:
953
+ fontfamily = mpl.rcParams['mathtext.fontset']
954
+ else:
955
+ valid_fonts = _validators['mathtext.fontset'].valid.values()
956
+ # _check_in_list() Validates the parameter math_fontfamily as
957
+ # if it were passed to rcParams['mathtext.fontset']
958
+ _api.check_in_list(valid_fonts, math_fontfamily=fontfamily)
959
+ self._math_fontfamily = fontfamily
960
+
961
+ def copy(self):
962
+ """Return a copy of self."""
963
+ return copy.copy(self)
964
+
965
+ # Aliases
966
+ set_name = set_family
967
+ get_slant = get_style
968
+ set_slant = set_style
969
+ get_size_in_points = get_size
970
+
971
+
972
+ class _JSONEncoder(json.JSONEncoder):
973
+ def default(self, o):
974
+ if isinstance(o, FontManager):
975
+ return dict(o.__dict__, __class__='FontManager')
976
+ elif isinstance(o, FontEntry):
977
+ d = dict(o.__dict__, __class__='FontEntry')
978
+ try:
979
+ # Cache paths of fonts shipped with Matplotlib relative to the
980
+ # Matplotlib data path, which helps in the presence of venvs.
981
+ d["fname"] = str(Path(d["fname"]).relative_to(mpl.get_data_path()))
982
+ except ValueError:
983
+ pass
984
+ return d
985
+ else:
986
+ return super().default(o)
987
+
988
+
989
+ def _json_decode(o):
990
+ cls = o.pop('__class__', None)
991
+ if cls is None:
992
+ return o
993
+ elif cls == 'FontManager':
994
+ r = FontManager.__new__(FontManager)
995
+ r.__dict__.update(o)
996
+ return r
997
+ elif cls == 'FontEntry':
998
+ if not os.path.isabs(o['fname']):
999
+ o['fname'] = os.path.join(mpl.get_data_path(), o['fname'])
1000
+ r = FontEntry(**o)
1001
+ return r
1002
+ else:
1003
+ raise ValueError("Don't know how to deserialize __class__=%s" % cls)
1004
+
1005
+
1006
+ def json_dump(data, filename):
1007
+ """
1008
+ Dump `FontManager` *data* as JSON to the file named *filename*.
1009
+
1010
+ See Also
1011
+ --------
1012
+ json_load
1013
+
1014
+ Notes
1015
+ -----
1016
+ File paths that are children of the Matplotlib data path (typically, fonts
1017
+ shipped with Matplotlib) are stored relative to that data path (to remain
1018
+ valid across virtualenvs).
1019
+
1020
+ This function temporarily locks the output file to prevent multiple
1021
+ processes from overwriting one another's output.
1022
+ """
1023
+ try:
1024
+ with cbook._lock_path(filename), open(filename, 'w') as fh:
1025
+ json.dump(data, fh, cls=_JSONEncoder, indent=2)
1026
+ except OSError as e:
1027
+ _log.warning('Could not save font_manager cache %s', e)
1028
+
1029
+
1030
+ def json_load(filename):
1031
+ """
1032
+ Load a `FontManager` from the JSON file named *filename*.
1033
+
1034
+ See Also
1035
+ --------
1036
+ json_dump
1037
+ """
1038
+ with open(filename) as fh:
1039
+ return json.load(fh, object_hook=_json_decode)
1040
+
1041
+
1042
+ class FontManager:
1043
+ """
1044
+ On import, the `FontManager` singleton instance creates a list of ttf and
1045
+ afm fonts and caches their `FontProperties`. The `FontManager.findfont`
1046
+ method does a nearest neighbor search to find the font that most closely
1047
+ matches the specification. If no good enough match is found, the default
1048
+ font is returned.
1049
+
1050
+ Fonts added with the `FontManager.addfont` method will not persist in the
1051
+ cache; therefore, `addfont` will need to be called every time Matplotlib is
1052
+ imported. This method should only be used if and when a font cannot be
1053
+ installed on your operating system by other means.
1054
+
1055
+ Notes
1056
+ -----
1057
+ The `FontManager.addfont` method must be called on the global `FontManager`
1058
+ instance.
1059
+
1060
+ Example usage::
1061
+
1062
+ import matplotlib.pyplot as plt
1063
+ from matplotlib import font_manager
1064
+
1065
+ font_dirs = ["/resources/fonts"] # The path to the custom font file.
1066
+ font_files = font_manager.findSystemFonts(fontpaths=font_dirs)
1067
+
1068
+ for font_file in font_files:
1069
+ font_manager.fontManager.addfont(font_file)
1070
+ """
1071
+ # Increment this version number whenever the font cache data
1072
+ # format or behavior has changed and requires an existing font
1073
+ # cache files to be rebuilt.
1074
+ __version__ = 390
1075
+
1076
+ def __init__(self, size=None, weight='normal'):
1077
+ self._version = self.__version__
1078
+
1079
+ self.__default_weight = weight
1080
+ self.default_size = size
1081
+
1082
+ # Create list of font paths.
1083
+ paths = [cbook._get_data_path('fonts', subdir)
1084
+ for subdir in ['ttf', 'afm', 'pdfcorefonts']]
1085
+ _log.debug('font search path %s', paths)
1086
+
1087
+ self.defaultFamily = {
1088
+ 'ttf': 'DejaVu Sans',
1089
+ 'afm': 'Helvetica'}
1090
+
1091
+ self.afmlist = []
1092
+ self.ttflist = []
1093
+
1094
+ # Delay the warning by 5s.
1095
+ timer = threading.Timer(5, lambda: _log.warning(
1096
+ 'Matplotlib is building the font cache; this may take a moment.'))
1097
+ timer.start()
1098
+ try:
1099
+ for fontext in ["afm", "ttf"]:
1100
+ for path in [*findSystemFonts(paths, fontext=fontext),
1101
+ *findSystemFonts(fontext=fontext)]:
1102
+ try:
1103
+ self.addfont(path)
1104
+ except OSError as exc:
1105
+ _log.info("Failed to open font file %s: %s", path, exc)
1106
+ except Exception as exc:
1107
+ _log.info("Failed to extract font properties from %s: "
1108
+ "%s", path, exc)
1109
+ finally:
1110
+ timer.cancel()
1111
+
1112
+ def addfont(self, path):
1113
+ """
1114
+ Cache the properties of the font at *path* to make it available to the
1115
+ `FontManager`. The type of font is inferred from the path suffix.
1116
+
1117
+ Parameters
1118
+ ----------
1119
+ path : str or path-like
1120
+
1121
+ Notes
1122
+ -----
1123
+ This method is useful for adding a custom font without installing it in
1124
+ your operating system. See the `FontManager` singleton instance for
1125
+ usage and caveats about this function.
1126
+ """
1127
+ # Convert to string in case of a path as
1128
+ # afmFontProperty and FT2Font expect this
1129
+ path = os.fsdecode(path)
1130
+ if Path(path).suffix.lower() == ".afm":
1131
+ with open(path, "rb") as fh:
1132
+ font = _afm.AFM(fh)
1133
+ prop = afmFontProperty(path, font)
1134
+ self.afmlist.append(prop)
1135
+ else:
1136
+ font = ft2font.FT2Font(path)
1137
+ prop = ttfFontProperty(font)
1138
+ self.ttflist.append(prop)
1139
+ self._findfont_cached.cache_clear()
1140
+
1141
+ @property
1142
+ def defaultFont(self):
1143
+ # Lazily evaluated (findfont then caches the result) to avoid including
1144
+ # the venv path in the json serialization.
1145
+ return {ext: self.findfont(family, fontext=ext)
1146
+ for ext, family in self.defaultFamily.items()}
1147
+
1148
+ def get_default_weight(self):
1149
+ """
1150
+ Return the default font weight.
1151
+ """
1152
+ return self.__default_weight
1153
+
1154
+ @staticmethod
1155
+ def get_default_size():
1156
+ """
1157
+ Return the default font size.
1158
+ """
1159
+ return mpl.rcParams['font.size']
1160
+
1161
+ def set_default_weight(self, weight):
1162
+ """
1163
+ Set the default font weight. The initial value is 'normal'.
1164
+ """
1165
+ self.__default_weight = weight
1166
+
1167
+ @staticmethod
1168
+ def _expand_aliases(family):
1169
+ if family in ('sans', 'sans serif'):
1170
+ family = 'sans-serif'
1171
+ return mpl.rcParams['font.' + family]
1172
+
1173
+ # Each of the scoring functions below should return a value between
1174
+ # 0.0 (perfect match) and 1.0 (terrible match)
1175
+ def score_family(self, families, family2):
1176
+ """
1177
+ Return a match score between the list of font families in
1178
+ *families* and the font family name *family2*.
1179
+
1180
+ An exact match at the head of the list returns 0.0.
1181
+
1182
+ A match further down the list will return between 0 and 1.
1183
+
1184
+ No match will return 1.0.
1185
+ """
1186
+ if not isinstance(families, (list, tuple)):
1187
+ families = [families]
1188
+ elif len(families) == 0:
1189
+ return 1.0
1190
+ family2 = family2.lower()
1191
+ step = 1 / len(families)
1192
+ for i, family1 in enumerate(families):
1193
+ family1 = family1.lower()
1194
+ if family1 in font_family_aliases:
1195
+ options = [*map(str.lower, self._expand_aliases(family1))]
1196
+ if family2 in options:
1197
+ idx = options.index(family2)
1198
+ return (i + (idx / len(options))) * step
1199
+ elif family1 == family2:
1200
+ # The score should be weighted by where in the
1201
+ # list the font was found.
1202
+ return i * step
1203
+ return 1.0
1204
+
1205
+ def score_style(self, style1, style2):
1206
+ """
1207
+ Return a match score between *style1* and *style2*.
1208
+
1209
+ An exact match returns 0.0.
1210
+
1211
+ A match between 'italic' and 'oblique' returns 0.1.
1212
+
1213
+ No match returns 1.0.
1214
+ """
1215
+ if style1 == style2:
1216
+ return 0.0
1217
+ elif (style1 in ('italic', 'oblique')
1218
+ and style2 in ('italic', 'oblique')):
1219
+ return 0.1
1220
+ return 1.0
1221
+
1222
+ def score_variant(self, variant1, variant2):
1223
+ """
1224
+ Return a match score between *variant1* and *variant2*.
1225
+
1226
+ An exact match returns 0.0, otherwise 1.0.
1227
+ """
1228
+ if variant1 == variant2:
1229
+ return 0.0
1230
+ else:
1231
+ return 1.0
1232
+
1233
+ def score_stretch(self, stretch1, stretch2):
1234
+ """
1235
+ Return a match score between *stretch1* and *stretch2*.
1236
+
1237
+ The result is the absolute value of the difference between the
1238
+ CSS numeric values of *stretch1* and *stretch2*, normalized
1239
+ between 0.0 and 1.0.
1240
+ """
1241
+ try:
1242
+ stretchval1 = int(stretch1)
1243
+ except ValueError:
1244
+ stretchval1 = stretch_dict.get(stretch1, 500)
1245
+ try:
1246
+ stretchval2 = int(stretch2)
1247
+ except ValueError:
1248
+ stretchval2 = stretch_dict.get(stretch2, 500)
1249
+ return abs(stretchval1 - stretchval2) / 1000.0
1250
+
1251
+ def score_weight(self, weight1, weight2):
1252
+ """
1253
+ Return a match score between *weight1* and *weight2*.
1254
+
1255
+ The result is 0.0 if both weight1 and weight 2 are given as strings
1256
+ and have the same value.
1257
+
1258
+ Otherwise, the result is the absolute value of the difference between
1259
+ the CSS numeric values of *weight1* and *weight2*, normalized between
1260
+ 0.05 and 1.0.
1261
+ """
1262
+ # exact match of the weight names, e.g. weight1 == weight2 == "regular"
1263
+ if cbook._str_equal(weight1, weight2):
1264
+ return 0.0
1265
+ w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1]
1266
+ w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2]
1267
+ return 0.95 * (abs(w1 - w2) / 1000) + 0.05
1268
+
1269
+ def score_size(self, size1, size2):
1270
+ """
1271
+ Return a match score between *size1* and *size2*.
1272
+
1273
+ If *size2* (the size specified in the font file) is 'scalable', this
1274
+ function always returns 0.0, since any font size can be generated.
1275
+
1276
+ Otherwise, the result is the absolute distance between *size1* and
1277
+ *size2*, normalized so that the usual range of font sizes (6pt -
1278
+ 72pt) will lie between 0.0 and 1.0.
1279
+ """
1280
+ if size2 == 'scalable':
1281
+ return 0.0
1282
+ # Size value should have already been
1283
+ try:
1284
+ sizeval1 = float(size1)
1285
+ except ValueError:
1286
+ sizeval1 = self.default_size * font_scalings[size1]
1287
+ try:
1288
+ sizeval2 = float(size2)
1289
+ except ValueError:
1290
+ return 1.0
1291
+ return abs(sizeval1 - sizeval2) / 72
1292
+
1293
+ def findfont(self, prop, fontext='ttf', directory=None,
1294
+ fallback_to_default=True, rebuild_if_missing=True):
1295
+ """
1296
+ Find the path to the font file most closely matching the given font properties.
1297
+
1298
+ Parameters
1299
+ ----------
1300
+ prop : str or `~matplotlib.font_manager.FontProperties`
1301
+ The font properties to search for. This can be either a
1302
+ `.FontProperties` object or a string defining a
1303
+ `fontconfig patterns`_.
1304
+
1305
+ fontext : {'ttf', 'afm'}, default: 'ttf'
1306
+ The extension of the font file:
1307
+
1308
+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
1309
+ - 'afm': Adobe Font Metrics (.afm)
1310
+
1311
+ directory : str, optional
1312
+ If given, only search this directory and its subdirectories.
1313
+
1314
+ fallback_to_default : bool
1315
+ If True, will fall back to the default font family (usually
1316
+ "DejaVu Sans" or "Helvetica") if the first lookup hard-fails.
1317
+
1318
+ rebuild_if_missing : bool
1319
+ Whether to rebuild the font cache and search again if the first
1320
+ match appears to point to a nonexisting font (i.e., the font cache
1321
+ contains outdated entries).
1322
+
1323
+ Returns
1324
+ -------
1325
+ str
1326
+ The filename of the best matching font.
1327
+
1328
+ Notes
1329
+ -----
1330
+ This performs a nearest neighbor search. Each font is given a
1331
+ similarity score to the target font properties. The first font with
1332
+ the highest score is returned. If no matches below a certain
1333
+ threshold are found, the default font (usually DejaVu Sans) is
1334
+ returned.
1335
+
1336
+ The result is cached, so subsequent lookups don't have to
1337
+ perform the O(n) nearest neighbor search.
1338
+
1339
+ See the `W3C Cascading Style Sheet, Level 1
1340
+ <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_ documentation
1341
+ for a description of the font finding algorithm.
1342
+
1343
+ .. _fontconfig patterns:
1344
+ https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
1345
+ """
1346
+ # Pass the relevant rcParams (and the font manager, as `self`) to
1347
+ # _findfont_cached so to prevent using a stale cache entry after an
1348
+ # rcParam was changed.
1349
+ rc_params = tuple(tuple(mpl.rcParams[key]) for key in [
1350
+ "font.serif", "font.sans-serif", "font.cursive", "font.fantasy",
1351
+ "font.monospace"])
1352
+ ret = self._findfont_cached(
1353
+ prop, fontext, directory, fallback_to_default, rebuild_if_missing,
1354
+ rc_params)
1355
+ if isinstance(ret, cbook._ExceptionInfo):
1356
+ raise ret.to_exception()
1357
+ return ret
1358
+
1359
+ def get_font_names(self):
1360
+ """Return the list of available fonts."""
1361
+ return list({font.name for font in self.ttflist})
1362
+
1363
+ def _find_fonts_by_props(self, prop, fontext='ttf', directory=None,
1364
+ fallback_to_default=True, rebuild_if_missing=True):
1365
+ """
1366
+ Find the paths to the font files most closely matching the given properties.
1367
+
1368
+ Parameters
1369
+ ----------
1370
+ prop : str or `~matplotlib.font_manager.FontProperties`
1371
+ The font properties to search for. This can be either a
1372
+ `.FontProperties` object or a string defining a
1373
+ `fontconfig patterns`_.
1374
+
1375
+ fontext : {'ttf', 'afm'}, default: 'ttf'
1376
+ The extension of the font file:
1377
+
1378
+ - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf)
1379
+ - 'afm': Adobe Font Metrics (.afm)
1380
+
1381
+ directory : str, optional
1382
+ If given, only search this directory and its subdirectories.
1383
+
1384
+ fallback_to_default : bool
1385
+ If True, will fall back to the default font family (usually
1386
+ "DejaVu Sans" or "Helvetica") if none of the families were found.
1387
+
1388
+ rebuild_if_missing : bool
1389
+ Whether to rebuild the font cache and search again if the first
1390
+ match appears to point to a nonexisting font (i.e., the font cache
1391
+ contains outdated entries).
1392
+
1393
+ Returns
1394
+ -------
1395
+ list[str]
1396
+ The paths of the fonts found.
1397
+
1398
+ Notes
1399
+ -----
1400
+ This is an extension/wrapper of the original findfont API, which only
1401
+ returns a single font for given font properties. Instead, this API
1402
+ returns a list of filepaths of multiple fonts which closely match the
1403
+ given font properties. Since this internally uses the original API,
1404
+ there's no change to the logic of performing the nearest neighbor
1405
+ search. See `findfont` for more details.
1406
+ """
1407
+
1408
+ prop = FontProperties._from_any(prop)
1409
+
1410
+ fpaths = []
1411
+ for family in prop.get_family():
1412
+ cprop = prop.copy()
1413
+ cprop.set_family(family) # set current prop's family
1414
+
1415
+ try:
1416
+ fpaths.append(
1417
+ self.findfont(
1418
+ cprop, fontext, directory,
1419
+ fallback_to_default=False, # don't fallback to default
1420
+ rebuild_if_missing=rebuild_if_missing,
1421
+ )
1422
+ )
1423
+ except ValueError:
1424
+ if family in font_family_aliases:
1425
+ _log.warning(
1426
+ "findfont: Generic family %r not found because "
1427
+ "none of the following families were found: %s",
1428
+ family, ", ".join(self._expand_aliases(family))
1429
+ )
1430
+ else:
1431
+ _log.warning("findfont: Font family %r not found.", family)
1432
+
1433
+ # only add default family if no other font was found and
1434
+ # fallback_to_default is enabled
1435
+ if not fpaths:
1436
+ if fallback_to_default:
1437
+ dfamily = self.defaultFamily[fontext]
1438
+ cprop = prop.copy()
1439
+ cprop.set_family(dfamily)
1440
+ fpaths.append(
1441
+ self.findfont(
1442
+ cprop, fontext, directory,
1443
+ fallback_to_default=True,
1444
+ rebuild_if_missing=rebuild_if_missing,
1445
+ )
1446
+ )
1447
+ else:
1448
+ raise ValueError("Failed to find any font, and fallback "
1449
+ "to the default font was disabled")
1450
+
1451
+ return fpaths
1452
+
1453
+ @lru_cache(1024)
1454
+ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
1455
+ rebuild_if_missing, rc_params):
1456
+
1457
+ prop = FontProperties._from_any(prop)
1458
+
1459
+ fname = prop.get_file()
1460
+ if fname is not None:
1461
+ return fname
1462
+
1463
+ if fontext == 'afm':
1464
+ fontlist = self.afmlist
1465
+ else:
1466
+ fontlist = self.ttflist
1467
+
1468
+ best_score = 1e64
1469
+ best_font = None
1470
+
1471
+ _log.debug('findfont: Matching %s.', prop)
1472
+ for font in fontlist:
1473
+ if (directory is not None and
1474
+ Path(directory) not in Path(font.fname).parents):
1475
+ continue
1476
+ # Matching family should have top priority, so multiply it by 10.
1477
+ score = (self.score_family(prop.get_family(), font.name) * 10
1478
+ + self.score_style(prop.get_style(), font.style)
1479
+ + self.score_variant(prop.get_variant(), font.variant)
1480
+ + self.score_weight(prop.get_weight(), font.weight)
1481
+ + self.score_stretch(prop.get_stretch(), font.stretch)
1482
+ + self.score_size(prop.get_size(), font.size))
1483
+ _log.debug('findfont: score(%s) = %s', font, score)
1484
+ if score < best_score:
1485
+ best_score = score
1486
+ best_font = font
1487
+ if score == 0:
1488
+ break
1489
+
1490
+ if best_font is None or best_score >= 10.0:
1491
+ if fallback_to_default:
1492
+ _log.warning(
1493
+ 'findfont: Font family %s not found. Falling back to %s.',
1494
+ prop.get_family(), self.defaultFamily[fontext])
1495
+ for family in map(str.lower, prop.get_family()):
1496
+ if family in font_family_aliases:
1497
+ _log.warning(
1498
+ "findfont: Generic family %r not found because "
1499
+ "none of the following families were found: %s",
1500
+ family, ", ".join(self._expand_aliases(family)))
1501
+ default_prop = prop.copy()
1502
+ default_prop.set_family(self.defaultFamily[fontext])
1503
+ return self.findfont(default_prop, fontext, directory,
1504
+ fallback_to_default=False)
1505
+ else:
1506
+ # This return instead of raise is intentional, as we wish to
1507
+ # cache that it was not found, which will not occur if it was
1508
+ # actually raised.
1509
+ return cbook._ExceptionInfo(
1510
+ ValueError,
1511
+ f"Failed to find font {prop}, and fallback to the default font was "
1512
+ f"disabled"
1513
+ )
1514
+ else:
1515
+ _log.debug('findfont: Matching %s to %s (%r) with score of %f.',
1516
+ prop, best_font.name, best_font.fname, best_score)
1517
+ result = best_font.fname
1518
+
1519
+ if not os.path.isfile(result):
1520
+ if rebuild_if_missing:
1521
+ _log.info(
1522
+ 'findfont: Found a missing font file. Rebuilding cache.')
1523
+ new_fm = _load_fontmanager(try_read_cache=False)
1524
+ # Replace self by the new fontmanager, because users may have
1525
+ # a reference to this specific instance.
1526
+ # TODO: _load_fontmanager should really be (used by) a method
1527
+ # modifying the instance in place.
1528
+ vars(self).update(vars(new_fm))
1529
+ return self.findfont(
1530
+ prop, fontext, directory, rebuild_if_missing=False)
1531
+ else:
1532
+ # This return instead of raise is intentional, as we wish to
1533
+ # cache that it was not found, which will not occur if it was
1534
+ # actually raised.
1535
+ return cbook._ExceptionInfo(ValueError, "No valid font could be found")
1536
+
1537
+ return _cached_realpath(result)
1538
+
1539
+
1540
+ @lru_cache
1541
+ def is_opentype_cff_font(filename):
1542
+ """
1543
+ Return whether the given font is a Postscript Compact Font Format Font
1544
+ embedded in an OpenType wrapper. Used by the PostScript and PDF backends
1545
+ that cannot subset these fonts.
1546
+ """
1547
+ if os.path.splitext(filename)[1].lower() == '.otf':
1548
+ with open(filename, 'rb') as fd:
1549
+ return fd.read(4) == b"OTTO"
1550
+ else:
1551
+ return False
1552
+
1553
+
1554
+ @lru_cache(64)
1555
+ def _get_font(font_filepaths, hinting_factor, *, _kerning_factor, thread_id):
1556
+ first_fontpath, *rest = font_filepaths
1557
+ return ft2font.FT2Font(
1558
+ first_fontpath, hinting_factor,
1559
+ _fallback_list=[
1560
+ ft2font.FT2Font(
1561
+ fpath, hinting_factor,
1562
+ _kerning_factor=_kerning_factor
1563
+ )
1564
+ for fpath in rest
1565
+ ],
1566
+ _kerning_factor=_kerning_factor
1567
+ )
1568
+
1569
+
1570
+ # FT2Font objects cannot be used across fork()s because they reference the same
1571
+ # FT_Library object. While invalidating *all* existing FT2Fonts after a fork
1572
+ # would be too complicated to be worth it, the main way FT2Fonts get reused is
1573
+ # via the cache of _get_font, which we can empty upon forking (not on Windows,
1574
+ # which has no fork() or register_at_fork()).
1575
+ if hasattr(os, "register_at_fork"):
1576
+ os.register_at_fork(after_in_child=_get_font.cache_clear)
1577
+
1578
+
1579
+ @lru_cache(64)
1580
+ def _cached_realpath(path):
1581
+ # Resolving the path avoids embedding the font twice in pdf/ps output if a
1582
+ # single font is selected using two different relative paths.
1583
+ return os.path.realpath(path)
1584
+
1585
+
1586
+ def get_font(font_filepaths, hinting_factor=None):
1587
+ """
1588
+ Get an `.ft2font.FT2Font` object given a list of file paths.
1589
+
1590
+ Parameters
1591
+ ----------
1592
+ font_filepaths : Iterable[str, Path, bytes], str, Path, bytes
1593
+ Relative or absolute paths to the font files to be used.
1594
+
1595
+ If a single string, bytes, or `pathlib.Path`, then it will be treated
1596
+ as a list with that entry only.
1597
+
1598
+ If more than one filepath is passed, then the returned FT2Font object
1599
+ will fall back through the fonts, in the order given, to find a needed
1600
+ glyph.
1601
+
1602
+ Returns
1603
+ -------
1604
+ `.ft2font.FT2Font`
1605
+
1606
+ """
1607
+ if isinstance(font_filepaths, (str, Path, bytes)):
1608
+ paths = (_cached_realpath(font_filepaths),)
1609
+ else:
1610
+ paths = tuple(_cached_realpath(fname) for fname in font_filepaths)
1611
+
1612
+ if hinting_factor is None:
1613
+ hinting_factor = mpl.rcParams['text.hinting_factor']
1614
+
1615
+ return _get_font(
1616
+ # must be a tuple to be cached
1617
+ paths,
1618
+ hinting_factor,
1619
+ _kerning_factor=mpl.rcParams['text.kerning_factor'],
1620
+ # also key on the thread ID to prevent segfaults with multi-threading
1621
+ thread_id=threading.get_ident()
1622
+ )
1623
+
1624
+
1625
+ def _load_fontmanager(*, try_read_cache=True):
1626
+ fm_path = Path(
1627
+ mpl.get_cachedir(), f"fontlist-v{FontManager.__version__}.json")
1628
+ if try_read_cache:
1629
+ try:
1630
+ fm = json_load(fm_path)
1631
+ except Exception:
1632
+ pass
1633
+ else:
1634
+ if getattr(fm, "_version", object()) == FontManager.__version__:
1635
+ _log.debug("Using fontManager instance from %s", fm_path)
1636
+ return fm
1637
+ fm = FontManager()
1638
+ json_dump(fm, fm_path)
1639
+ _log.info("generated new fontManager")
1640
+ return fm
1641
+
1642
+
1643
+ fontManager = _load_fontmanager()
1644
+ findfont = fontManager.findfont
1645
+ get_font_names = fontManager.get_font_names
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ import os
3
+
4
+ from matplotlib._afm import AFM
5
+ from matplotlib import ft2font
6
+
7
+ from pathlib import Path
8
+
9
+ from collections.abc import Iterable
10
+ from typing import Any, Literal
11
+
12
+ font_scalings: dict[str | None, float]
13
+ stretch_dict: dict[str, int]
14
+ weight_dict: dict[str, int]
15
+ font_family_aliases: set[str]
16
+ MSFolders: str
17
+ MSFontDirectories: list[str]
18
+ MSUserFontDirectories: list[str]
19
+ X11FontDirectories: list[str]
20
+ OSXFontDirectories: list[str]
21
+
22
+ def get_fontext_synonyms(fontext: str) -> list[str]: ...
23
+ def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ...
24
+ def win32FontDirectory() -> str: ...
25
+ def _get_fontconfig_fonts() -> list[Path]: ...
26
+ def findSystemFonts(
27
+ fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ...
28
+ ) -> list[str]: ...
29
+ @dataclass
30
+ class FontEntry:
31
+ fname: str = ...
32
+ name: str = ...
33
+ style: str = ...
34
+ variant: str = ...
35
+ weight: str | int = ...
36
+ stretch: str = ...
37
+ size: str = ...
38
+ def _repr_html_(self) -> str: ...
39
+ def _repr_png_(self) -> bytes: ...
40
+
41
+ def ttfFontProperty(font: ft2font.FT2Font) -> FontEntry: ...
42
+ def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ...
43
+
44
+ class FontProperties:
45
+ def __init__(
46
+ self,
47
+ family: str | Iterable[str] | None = ...,
48
+ style: Literal["normal", "italic", "oblique"] | None = ...,
49
+ variant: Literal["normal", "small-caps"] | None = ...,
50
+ weight: int | str | None = ...,
51
+ stretch: int | str | None = ...,
52
+ size: float | str | None = ...,
53
+ fname: str | os.PathLike | Path | None = ...,
54
+ math_fontfamily: str | None = ...,
55
+ ) -> None: ...
56
+ def __hash__(self) -> int: ...
57
+ def __eq__(self, other: object) -> bool: ...
58
+ def get_family(self) -> list[str]: ...
59
+ def get_name(self) -> str: ...
60
+ def get_style(self) -> Literal["normal", "italic", "oblique"]: ...
61
+ def get_variant(self) -> Literal["normal", "small-caps"]: ...
62
+ def get_weight(self) -> int | str: ...
63
+ def get_stretch(self) -> int | str: ...
64
+ def get_size(self) -> float: ...
65
+ def get_file(self) -> str | bytes | None: ...
66
+ def get_fontconfig_pattern(self) -> dict[str, list[Any]]: ...
67
+ def set_family(self, family: str | Iterable[str] | None) -> None: ...
68
+ def set_style(
69
+ self, style: Literal["normal", "italic", "oblique"] | None
70
+ ) -> None: ...
71
+ def set_variant(self, variant: Literal["normal", "small-caps"] | None) -> None: ...
72
+ def set_weight(self, weight: int | str | None) -> None: ...
73
+ def set_stretch(self, stretch: int | str | None) -> None: ...
74
+ def set_size(self, size: float | str | None) -> None: ...
75
+ def set_file(self, file: str | os.PathLike | Path | None) -> None: ...
76
+ def set_fontconfig_pattern(self, pattern: str) -> None: ...
77
+ def get_math_fontfamily(self) -> str: ...
78
+ def set_math_fontfamily(self, fontfamily: str | None) -> None: ...
79
+ def copy(self) -> FontProperties: ...
80
+ # Aliases
81
+ set_name = set_family
82
+ get_slant = get_style
83
+ set_slant = set_style
84
+ get_size_in_points = get_size
85
+
86
+ def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ...
87
+ def json_load(filename: str | Path | os.PathLike) -> FontManager: ...
88
+
89
+ class FontManager:
90
+ __version__: int
91
+ default_size: float | None
92
+ defaultFamily: dict[str, str]
93
+ afmlist: list[FontEntry]
94
+ ttflist: list[FontEntry]
95
+ def __init__(self, size: float | None = ..., weight: str = ...) -> None: ...
96
+ def addfont(self, path: str | Path | os.PathLike) -> None: ...
97
+ @property
98
+ def defaultFont(self) -> dict[str, str]: ...
99
+ def get_default_weight(self) -> str: ...
100
+ @staticmethod
101
+ def get_default_size() -> float: ...
102
+ def set_default_weight(self, weight: str) -> None: ...
103
+ def score_family(
104
+ self, families: str | list[str] | tuple[str], family2: str
105
+ ) -> float: ...
106
+ def score_style(self, style1: str, style2: str) -> float: ...
107
+ def score_variant(self, variant1: str, variant2: str) -> float: ...
108
+ def score_stretch(self, stretch1: str | int, stretch2: str | int) -> float: ...
109
+ def score_weight(self, weight1: str | float, weight2: str | float) -> float: ...
110
+ def score_size(self, size1: str | float, size2: str | float) -> float: ...
111
+ def findfont(
112
+ self,
113
+ prop: str | FontProperties,
114
+ fontext: Literal["ttf", "afm"] = ...,
115
+ directory: str | None = ...,
116
+ fallback_to_default: bool = ...,
117
+ rebuild_if_missing: bool = ...,
118
+ ) -> str: ...
119
+ def get_font_names(self) -> list[str]: ...
120
+
121
+ def is_opentype_cff_font(filename: str) -> bool: ...
122
+ def get_font(
123
+ font_filepaths: Iterable[str | Path | bytes] | str | Path | bytes,
124
+ hinting_factor: int | None = ...,
125
+ ) -> ft2font.FT2Font: ...
126
+
127
+ fontManager: FontManager
128
+
129
+ def findfont(
130
+ prop: str | FontProperties,
131
+ fontext: Literal["ttf", "afm"] = ...,
132
+ directory: str | None = ...,
133
+ fallback_to_default: bool = ...,
134
+ rebuild_if_missing: bool = ...,
135
+ ) -> str: ...
136
+ def get_font_names() -> list[str]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19f8050e94615789419cfcaeb674e459b448c35c5f4572830d3479e59235ff8c
3
+ size 1279544
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum, Flag
2
+ import sys
3
+ from typing import BinaryIO, Literal, TypedDict, final, overload, cast
4
+ from typing_extensions import Buffer # < Py 3.12
5
+
6
+ import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+ __freetype_build_type__: str
10
+ __freetype_version__: str
11
+
12
+ class FaceFlags(Flag):
13
+ SCALABLE = cast(int, ...)
14
+ FIXED_SIZES = cast(int, ...)
15
+ FIXED_WIDTH = cast(int, ...)
16
+ SFNT = cast(int, ...)
17
+ HORIZONTAL = cast(int, ...)
18
+ VERTICAL = cast(int, ...)
19
+ KERNING = cast(int, ...)
20
+ FAST_GLYPHS = cast(int, ...)
21
+ MULTIPLE_MASTERS = cast(int, ...)
22
+ GLYPH_NAMES = cast(int, ...)
23
+ EXTERNAL_STREAM = cast(int, ...)
24
+ HINTER = cast(int, ...)
25
+ CID_KEYED = cast(int, ...)
26
+ TRICKY = cast(int, ...)
27
+ COLOR = cast(int, ...)
28
+ # VARIATION = cast(int, ...) # FT 2.9
29
+ # SVG = cast(int, ...) # FT 2.12
30
+ # SBIX = cast(int, ...) # FT 2.12
31
+ # SBIX_OVERLAY = cast(int, ...) # FT 2.12
32
+
33
+ class Kerning(Enum):
34
+ DEFAULT = cast(int, ...)
35
+ UNFITTED = cast(int, ...)
36
+ UNSCALED = cast(int, ...)
37
+
38
+ class LoadFlags(Flag):
39
+ DEFAULT = cast(int, ...)
40
+ NO_SCALE = cast(int, ...)
41
+ NO_HINTING = cast(int, ...)
42
+ RENDER = cast(int, ...)
43
+ NO_BITMAP = cast(int, ...)
44
+ VERTICAL_LAYOUT = cast(int, ...)
45
+ FORCE_AUTOHINT = cast(int, ...)
46
+ CROP_BITMAP = cast(int, ...)
47
+ PEDANTIC = cast(int, ...)
48
+ IGNORE_GLOBAL_ADVANCE_WIDTH = cast(int, ...)
49
+ NO_RECURSE = cast(int, ...)
50
+ IGNORE_TRANSFORM = cast(int, ...)
51
+ MONOCHROME = cast(int, ...)
52
+ LINEAR_DESIGN = cast(int, ...)
53
+ NO_AUTOHINT = cast(int, ...)
54
+ COLOR = cast(int, ...)
55
+ COMPUTE_METRICS = cast(int, ...) # FT 2.6.1
56
+ # BITMAP_METRICS_ONLY = cast(int, ...) # FT 2.7.1
57
+ # NO_SVG = cast(int, ...) # FT 2.13.1
58
+ # The following should be unique, but the above can be OR'd together.
59
+ TARGET_NORMAL = cast(int, ...)
60
+ TARGET_LIGHT = cast(int, ...)
61
+ TARGET_MONO = cast(int, ...)
62
+ TARGET_LCD = cast(int, ...)
63
+ TARGET_LCD_V = cast(int, ...)
64
+
65
+ class StyleFlags(Flag):
66
+ NORMAL = cast(int, ...)
67
+ ITALIC = cast(int, ...)
68
+ BOLD = cast(int, ...)
69
+
70
+ class _SfntHeadDict(TypedDict):
71
+ version: tuple[int, int]
72
+ fontRevision: tuple[int, int]
73
+ checkSumAdjustment: int
74
+ magicNumber: int
75
+ flags: int
76
+ unitsPerEm: int
77
+ created: tuple[int, int]
78
+ modified: tuple[int, int]
79
+ xMin: int
80
+ yMin: int
81
+ xMax: int
82
+ yMax: int
83
+ macStyle: int
84
+ lowestRecPPEM: int
85
+ fontDirectionHint: int
86
+ indexToLocFormat: int
87
+ glyphDataFormat: int
88
+
89
+ class _SfntMaxpDict(TypedDict):
90
+ version: tuple[int, int]
91
+ numGlyphs: int
92
+ maxPoints: int
93
+ maxContours: int
94
+ maxComponentPoints: int
95
+ maxComponentContours: int
96
+ maxZones: int
97
+ maxTwilightPoints: int
98
+ maxStorage: int
99
+ maxFunctionDefs: int
100
+ maxInstructionDefs: int
101
+ maxStackElements: int
102
+ maxSizeOfInstructions: int
103
+ maxComponentElements: int
104
+ maxComponentDepth: int
105
+
106
+ class _SfntOs2Dict(TypedDict):
107
+ version: int
108
+ xAvgCharWidth: int
109
+ usWeightClass: int
110
+ usWidthClass: int
111
+ fsType: int
112
+ ySubscriptXSize: int
113
+ ySubscriptYSize: int
114
+ ySubscriptXOffset: int
115
+ ySubscriptYOffset: int
116
+ ySuperscriptXSize: int
117
+ ySuperscriptYSize: int
118
+ ySuperscriptXOffset: int
119
+ ySuperscriptYOffset: int
120
+ yStrikeoutSize: int
121
+ yStrikeoutPosition: int
122
+ sFamilyClass: int
123
+ panose: bytes
124
+ ulCharRange: tuple[int, int, int, int]
125
+ achVendID: bytes
126
+ fsSelection: int
127
+ fsFirstCharIndex: int
128
+ fsLastCharIndex: int
129
+
130
+ class _SfntHheaDict(TypedDict):
131
+ version: tuple[int, int]
132
+ ascent: int
133
+ descent: int
134
+ lineGap: int
135
+ advanceWidthMax: int
136
+ minLeftBearing: int
137
+ minRightBearing: int
138
+ xMaxExtent: int
139
+ caretSlopeRise: int
140
+ caretSlopeRun: int
141
+ caretOffset: int
142
+ metricDataFormat: int
143
+ numOfLongHorMetrics: int
144
+
145
+ class _SfntVheaDict(TypedDict):
146
+ version: tuple[int, int]
147
+ vertTypoAscender: int
148
+ vertTypoDescender: int
149
+ vertTypoLineGap: int
150
+ advanceHeightMax: int
151
+ minTopSideBearing: int
152
+ minBottomSizeBearing: int
153
+ yMaxExtent: int
154
+ caretSlopeRise: int
155
+ caretSlopeRun: int
156
+ caretOffset: int
157
+ metricDataFormat: int
158
+ numOfLongVerMetrics: int
159
+
160
+ class _SfntPostDict(TypedDict):
161
+ format: tuple[int, int]
162
+ italicAngle: tuple[int, int]
163
+ underlinePosition: int
164
+ underlineThickness: int
165
+ isFixedPitch: int
166
+ minMemType42: int
167
+ maxMemType42: int
168
+ minMemType1: int
169
+ maxMemType1: int
170
+
171
+ class _SfntPcltDict(TypedDict):
172
+ version: tuple[int, int]
173
+ fontNumber: int
174
+ pitch: int
175
+ xHeight: int
176
+ style: int
177
+ typeFamily: int
178
+ capHeight: int
179
+ symbolSet: int
180
+ typeFace: bytes
181
+ characterComplement: bytes
182
+ strokeWeight: int
183
+ widthType: int
184
+ serifStyle: int
185
+
186
+ @final
187
+ class FT2Font(Buffer):
188
+ def __init__(
189
+ self,
190
+ filename: str | BinaryIO,
191
+ hinting_factor: int = ...,
192
+ *,
193
+ _fallback_list: list[FT2Font] | None = ...,
194
+ _kerning_factor: int = ...
195
+ ) -> None: ...
196
+ if sys.version_info[:2] >= (3, 12):
197
+ def __buffer__(self, flags: int) -> memoryview: ...
198
+ def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
199
+ def clear(self) -> None: ...
200
+ def draw_glyph_to_bitmap(
201
+ self, image: FT2Image, x: int, y: int, glyph: Glyph, antialiased: bool = ...
202
+ ) -> None: ...
203
+ def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
204
+ def get_bitmap_offset(self) -> tuple[int, int]: ...
205
+ def get_char_index(self, codepoint: int) -> int: ...
206
+ def get_charmap(self) -> dict[int, int]: ...
207
+ def get_descent(self) -> int: ...
208
+ def get_glyph_name(self, index: int) -> str: ...
209
+ def get_image(self) -> NDArray[np.uint8]: ...
210
+ def get_kerning(self, left: int, right: int, mode: Kerning) -> int: ...
211
+ def get_name_index(self, name: str) -> int: ...
212
+ def get_num_glyphs(self) -> int: ...
213
+ def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ...
214
+ def get_ps_font_info(
215
+ self,
216
+ ) -> tuple[str, str, str, str, str, int, int, int, int]: ...
217
+ def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ...
218
+ @overload
219
+ def get_sfnt_table(self, name: Literal["head"]) -> _SfntHeadDict | None: ...
220
+ @overload
221
+ def get_sfnt_table(self, name: Literal["maxp"]) -> _SfntMaxpDict | None: ...
222
+ @overload
223
+ def get_sfnt_table(self, name: Literal["OS/2"]) -> _SfntOs2Dict | None: ...
224
+ @overload
225
+ def get_sfnt_table(self, name: Literal["hhea"]) -> _SfntHheaDict | None: ...
226
+ @overload
227
+ def get_sfnt_table(self, name: Literal["vhea"]) -> _SfntVheaDict | None: ...
228
+ @overload
229
+ def get_sfnt_table(self, name: Literal["post"]) -> _SfntPostDict | None: ...
230
+ @overload
231
+ def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ...
232
+ def get_width_height(self) -> tuple[int, int]: ...
233
+ def load_char(self, charcode: int, flags: LoadFlags = ...) -> Glyph: ...
234
+ def load_glyph(self, glyphindex: int, flags: LoadFlags = ...) -> Glyph: ...
235
+ def select_charmap(self, i: int) -> None: ...
236
+ def set_charmap(self, i: int) -> None: ...
237
+ def set_size(self, ptsize: float, dpi: float) -> None: ...
238
+ def set_text(
239
+ self, string: str, angle: float = ..., flags: LoadFlags = ...
240
+ ) -> NDArray[np.float64]: ...
241
+ @property
242
+ def ascender(self) -> int: ...
243
+ @property
244
+ def bbox(self) -> tuple[int, int, int, int]: ...
245
+ @property
246
+ def descender(self) -> int: ...
247
+ @property
248
+ def face_flags(self) -> FaceFlags: ...
249
+ @property
250
+ def family_name(self) -> str: ...
251
+ @property
252
+ def fname(self) -> str: ...
253
+ @property
254
+ def height(self) -> int: ...
255
+ @property
256
+ def max_advance_height(self) -> int: ...
257
+ @property
258
+ def max_advance_width(self) -> int: ...
259
+ @property
260
+ def num_charmaps(self) -> int: ...
261
+ @property
262
+ def num_faces(self) -> int: ...
263
+ @property
264
+ def num_fixed_sizes(self) -> int: ...
265
+ @property
266
+ def num_glyphs(self) -> int: ...
267
+ @property
268
+ def num_named_instances(self) -> int: ...
269
+ @property
270
+ def postscript_name(self) -> str: ...
271
+ @property
272
+ def scalable(self) -> bool: ...
273
+ @property
274
+ def style_flags(self) -> StyleFlags: ...
275
+ @property
276
+ def style_name(self) -> str: ...
277
+ @property
278
+ def underline_position(self) -> int: ...
279
+ @property
280
+ def underline_thickness(self) -> int: ...
281
+ @property
282
+ def units_per_EM(self) -> int: ...
283
+
284
+ @final
285
+ class FT2Image(Buffer):
286
+ def __init__(self, width: int, height: int) -> None: ...
287
+ def draw_rect_filled(self, x0: int, y0: int, x1: int, y1: int) -> None: ...
288
+ if sys.version_info[:2] >= (3, 12):
289
+ def __buffer__(self, flags: int) -> memoryview: ...
290
+
291
+ @final
292
+ class Glyph:
293
+ @property
294
+ def width(self) -> int: ...
295
+ @property
296
+ def height(self) -> int: ...
297
+ @property
298
+ def horiBearingX(self) -> int: ...
299
+ @property
300
+ def horiBearingY(self) -> int: ...
301
+ @property
302
+ def horiAdvance(self) -> int: ...
303
+ @property
304
+ def linearHoriAdvance(self) -> int: ...
305
+ @property
306
+ def vertBearingX(self) -> int: ...
307
+ @property
308
+ def vertBearingY(self) -> int: ...
309
+ @property
310
+ def vertAdvance(self) -> int: ...
311
+ @property
312
+ def bbox(self) -> tuple[int, int, int, int]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py ADDED
@@ -0,0 +1,788 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ :mod:`~matplotlib.gridspec` contains classes that help to layout multiple
3
+ `~.axes.Axes` in a grid-like pattern within a figure.
4
+
5
+ The `GridSpec` specifies the overall grid structure. Individual cells within
6
+ the grid are referenced by `SubplotSpec`\s.
7
+
8
+ Often, users need not access this module directly, and can use higher-level
9
+ methods like `~.pyplot.subplots`, `~.pyplot.subplot_mosaic` and
10
+ `~.Figure.subfigures`. See the tutorial :ref:`arranging_axes` for a guide.
11
+ """
12
+
13
+ import copy
14
+ import logging
15
+ from numbers import Integral
16
+
17
+ import numpy as np
18
+
19
+ import matplotlib as mpl
20
+ from matplotlib import _api, _pylab_helpers, _tight_layout
21
+ from matplotlib.transforms import Bbox
22
+
23
+ _log = logging.getLogger(__name__)
24
+
25
+
26
+ class GridSpecBase:
27
+ """
28
+ A base class of GridSpec that specifies the geometry of the grid
29
+ that a subplot will be placed.
30
+ """
31
+
32
+ def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None):
33
+ """
34
+ Parameters
35
+ ----------
36
+ nrows, ncols : int
37
+ The number of rows and columns of the grid.
38
+ width_ratios : array-like of length *ncols*, optional
39
+ Defines the relative widths of the columns. Each column gets a
40
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
41
+ If not given, all columns will have the same width.
42
+ height_ratios : array-like of length *nrows*, optional
43
+ Defines the relative heights of the rows. Each row gets a
44
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
45
+ If not given, all rows will have the same height.
46
+ """
47
+ if not isinstance(nrows, Integral) or nrows <= 0:
48
+ raise ValueError(
49
+ f"Number of rows must be a positive integer, not {nrows!r}")
50
+ if not isinstance(ncols, Integral) or ncols <= 0:
51
+ raise ValueError(
52
+ f"Number of columns must be a positive integer, not {ncols!r}")
53
+ self._nrows, self._ncols = nrows, ncols
54
+ self.set_height_ratios(height_ratios)
55
+ self.set_width_ratios(width_ratios)
56
+
57
+ def __repr__(self):
58
+ height_arg = (f', height_ratios={self._row_height_ratios!r}'
59
+ if len(set(self._row_height_ratios)) != 1 else '')
60
+ width_arg = (f', width_ratios={self._col_width_ratios!r}'
61
+ if len(set(self._col_width_ratios)) != 1 else '')
62
+ return '{clsname}({nrows}, {ncols}{optionals})'.format(
63
+ clsname=self.__class__.__name__,
64
+ nrows=self._nrows,
65
+ ncols=self._ncols,
66
+ optionals=height_arg + width_arg,
67
+ )
68
+
69
+ nrows = property(lambda self: self._nrows,
70
+ doc="The number of rows in the grid.")
71
+ ncols = property(lambda self: self._ncols,
72
+ doc="The number of columns in the grid.")
73
+
74
+ def get_geometry(self):
75
+ """
76
+ Return a tuple containing the number of rows and columns in the grid.
77
+ """
78
+ return self._nrows, self._ncols
79
+
80
+ def get_subplot_params(self, figure=None):
81
+ # Must be implemented in subclasses
82
+ pass
83
+
84
+ def new_subplotspec(self, loc, rowspan=1, colspan=1):
85
+ """
86
+ Create and return a `.SubplotSpec` instance.
87
+
88
+ Parameters
89
+ ----------
90
+ loc : (int, int)
91
+ The position of the subplot in the grid as
92
+ ``(row_index, column_index)``.
93
+ rowspan, colspan : int, default: 1
94
+ The number of rows and columns the subplot should span in the grid.
95
+ """
96
+ loc1, loc2 = loc
97
+ subplotspec = self[loc1:loc1+rowspan, loc2:loc2+colspan]
98
+ return subplotspec
99
+
100
+ def set_width_ratios(self, width_ratios):
101
+ """
102
+ Set the relative widths of the columns.
103
+
104
+ *width_ratios* must be of length *ncols*. Each column gets a relative
105
+ width of ``width_ratios[i] / sum(width_ratios)``.
106
+ """
107
+ if width_ratios is None:
108
+ width_ratios = [1] * self._ncols
109
+ elif len(width_ratios) != self._ncols:
110
+ raise ValueError('Expected the given number of width ratios to '
111
+ 'match the number of columns of the grid')
112
+ self._col_width_ratios = width_ratios
113
+
114
+ def get_width_ratios(self):
115
+ """
116
+ Return the width ratios.
117
+
118
+ This is *None* if no width ratios have been set explicitly.
119
+ """
120
+ return self._col_width_ratios
121
+
122
+ def set_height_ratios(self, height_ratios):
123
+ """
124
+ Set the relative heights of the rows.
125
+
126
+ *height_ratios* must be of length *nrows*. Each row gets a relative
127
+ height of ``height_ratios[i] / sum(height_ratios)``.
128
+ """
129
+ if height_ratios is None:
130
+ height_ratios = [1] * self._nrows
131
+ elif len(height_ratios) != self._nrows:
132
+ raise ValueError('Expected the given number of height ratios to '
133
+ 'match the number of rows of the grid')
134
+ self._row_height_ratios = height_ratios
135
+
136
+ def get_height_ratios(self):
137
+ """
138
+ Return the height ratios.
139
+
140
+ This is *None* if no height ratios have been set explicitly.
141
+ """
142
+ return self._row_height_ratios
143
+
144
+ def get_grid_positions(self, fig):
145
+ """
146
+ Return the positions of the grid cells in figure coordinates.
147
+
148
+ Parameters
149
+ ----------
150
+ fig : `~matplotlib.figure.Figure`
151
+ The figure the grid should be applied to. The subplot parameters
152
+ (margins and spacing between subplots) are taken from *fig*.
153
+
154
+ Returns
155
+ -------
156
+ bottoms, tops, lefts, rights : array
157
+ The bottom, top, left, right positions of the grid cells in
158
+ figure coordinates.
159
+ """
160
+ nrows, ncols = self.get_geometry()
161
+ subplot_params = self.get_subplot_params(fig)
162
+ left = subplot_params.left
163
+ right = subplot_params.right
164
+ bottom = subplot_params.bottom
165
+ top = subplot_params.top
166
+ wspace = subplot_params.wspace
167
+ hspace = subplot_params.hspace
168
+ tot_width = right - left
169
+ tot_height = top - bottom
170
+
171
+ # calculate accumulated heights of columns
172
+ cell_h = tot_height / (nrows + hspace*(nrows-1))
173
+ sep_h = hspace * cell_h
174
+ norm = cell_h * nrows / sum(self._row_height_ratios)
175
+ cell_heights = [r * norm for r in self._row_height_ratios]
176
+ sep_heights = [0] + ([sep_h] * (nrows-1))
177
+ cell_hs = np.cumsum(np.column_stack([sep_heights, cell_heights]).flat)
178
+
179
+ # calculate accumulated widths of rows
180
+ cell_w = tot_width / (ncols + wspace*(ncols-1))
181
+ sep_w = wspace * cell_w
182
+ norm = cell_w * ncols / sum(self._col_width_ratios)
183
+ cell_widths = [r * norm for r in self._col_width_ratios]
184
+ sep_widths = [0] + ([sep_w] * (ncols-1))
185
+ cell_ws = np.cumsum(np.column_stack([sep_widths, cell_widths]).flat)
186
+
187
+ fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T
188
+ fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T
189
+ return fig_bottoms, fig_tops, fig_lefts, fig_rights
190
+
191
+ @staticmethod
192
+ def _check_gridspec_exists(figure, nrows, ncols):
193
+ """
194
+ Check if the figure already has a gridspec with these dimensions,
195
+ or create a new one
196
+ """
197
+ for ax in figure.get_axes():
198
+ gs = ax.get_gridspec()
199
+ if gs is not None:
200
+ if hasattr(gs, 'get_topmost_subplotspec'):
201
+ # This is needed for colorbar gridspec layouts.
202
+ # This is probably OK because this whole logic tree
203
+ # is for when the user is doing simple things with the
204
+ # add_subplot command. For complicated layouts
205
+ # like subgridspecs the proper gridspec is passed in...
206
+ gs = gs.get_topmost_subplotspec().get_gridspec()
207
+ if gs.get_geometry() == (nrows, ncols):
208
+ return gs
209
+ # else gridspec not found:
210
+ return GridSpec(nrows, ncols, figure=figure)
211
+
212
+ def __getitem__(self, key):
213
+ """Create and return a `.SubplotSpec` instance."""
214
+ nrows, ncols = self.get_geometry()
215
+
216
+ def _normalize(key, size, axis): # Includes last index.
217
+ orig_key = key
218
+ if isinstance(key, slice):
219
+ start, stop, _ = key.indices(size)
220
+ if stop > start:
221
+ return start, stop - 1
222
+ raise IndexError("GridSpec slice would result in no space "
223
+ "allocated for subplot")
224
+ else:
225
+ if key < 0:
226
+ key = key + size
227
+ if 0 <= key < size:
228
+ return key, key
229
+ elif axis is not None:
230
+ raise IndexError(f"index {orig_key} is out of bounds for "
231
+ f"axis {axis} with size {size}")
232
+ else: # flat index
233
+ raise IndexError(f"index {orig_key} is out of bounds for "
234
+ f"GridSpec with size {size}")
235
+
236
+ if isinstance(key, tuple):
237
+ try:
238
+ k1, k2 = key
239
+ except ValueError as err:
240
+ raise ValueError("Unrecognized subplot spec") from err
241
+ num1, num2 = np.ravel_multi_index(
242
+ [_normalize(k1, nrows, 0), _normalize(k2, ncols, 1)],
243
+ (nrows, ncols))
244
+ else: # Single key
245
+ num1, num2 = _normalize(key, nrows * ncols, None)
246
+
247
+ return SubplotSpec(self, num1, num2)
248
+
249
+ def subplots(self, *, sharex=False, sharey=False, squeeze=True,
250
+ subplot_kw=None):
251
+ """
252
+ Add all subplots specified by this `GridSpec` to its parent figure.
253
+
254
+ See `.Figure.subplots` for detailed documentation.
255
+ """
256
+
257
+ figure = self.figure
258
+
259
+ if figure is None:
260
+ raise ValueError("GridSpec.subplots() only works for GridSpecs "
261
+ "created with a parent figure")
262
+
263
+ if not isinstance(sharex, str):
264
+ sharex = "all" if sharex else "none"
265
+ if not isinstance(sharey, str):
266
+ sharey = "all" if sharey else "none"
267
+
268
+ _api.check_in_list(["all", "row", "col", "none", False, True],
269
+ sharex=sharex, sharey=sharey)
270
+ if subplot_kw is None:
271
+ subplot_kw = {}
272
+ # don't mutate kwargs passed by user...
273
+ subplot_kw = subplot_kw.copy()
274
+
275
+ # Create array to hold all Axes.
276
+ axarr = np.empty((self._nrows, self._ncols), dtype=object)
277
+ for row in range(self._nrows):
278
+ for col in range(self._ncols):
279
+ shared_with = {"none": None, "all": axarr[0, 0],
280
+ "row": axarr[row, 0], "col": axarr[0, col]}
281
+ subplot_kw["sharex"] = shared_with[sharex]
282
+ subplot_kw["sharey"] = shared_with[sharey]
283
+ axarr[row, col] = figure.add_subplot(
284
+ self[row, col], **subplot_kw)
285
+
286
+ # turn off redundant tick labeling
287
+ if sharex in ["col", "all"]:
288
+ for ax in axarr.flat:
289
+ ax._label_outer_xaxis(skip_non_rectangular_axes=True)
290
+ if sharey in ["row", "all"]:
291
+ for ax in axarr.flat:
292
+ ax._label_outer_yaxis(skip_non_rectangular_axes=True)
293
+
294
+ if squeeze:
295
+ # Discarding unneeded dimensions that equal 1. If we only have one
296
+ # subplot, just return it instead of a 1-element array.
297
+ return axarr.item() if axarr.size == 1 else axarr.squeeze()
298
+ else:
299
+ # Returned axis array will be always 2-d, even if nrows=ncols=1.
300
+ return axarr
301
+
302
+
303
+ class GridSpec(GridSpecBase):
304
+ """
305
+ A grid layout to place subplots within a figure.
306
+
307
+ The location of the grid cells is determined in a similar way to
308
+ `.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace*
309
+ and *hspace*.
310
+
311
+ Indexing a GridSpec instance returns a `.SubplotSpec`.
312
+ """
313
+ def __init__(self, nrows, ncols, figure=None,
314
+ left=None, bottom=None, right=None, top=None,
315
+ wspace=None, hspace=None,
316
+ width_ratios=None, height_ratios=None):
317
+ """
318
+ Parameters
319
+ ----------
320
+ nrows, ncols : int
321
+ The number of rows and columns of the grid.
322
+
323
+ figure : `.Figure`, optional
324
+ Only used for constrained layout to create a proper layoutgrid.
325
+
326
+ left, right, top, bottom : float, optional
327
+ Extent of the subplots as a fraction of figure width or height.
328
+ Left cannot be larger than right, and bottom cannot be larger than
329
+ top. If not given, the values will be inferred from a figure or
330
+ rcParams at draw time. See also `GridSpec.get_subplot_params`.
331
+
332
+ wspace : float, optional
333
+ The amount of width reserved for space between subplots,
334
+ expressed as a fraction of the average axis width.
335
+ If not given, the values will be inferred from a figure or
336
+ rcParams when necessary. See also `GridSpec.get_subplot_params`.
337
+
338
+ hspace : float, optional
339
+ The amount of height reserved for space between subplots,
340
+ expressed as a fraction of the average axis height.
341
+ If not given, the values will be inferred from a figure or
342
+ rcParams when necessary. See also `GridSpec.get_subplot_params`.
343
+
344
+ width_ratios : array-like of length *ncols*, optional
345
+ Defines the relative widths of the columns. Each column gets a
346
+ relative width of ``width_ratios[i] / sum(width_ratios)``.
347
+ If not given, all columns will have the same width.
348
+
349
+ height_ratios : array-like of length *nrows*, optional
350
+ Defines the relative heights of the rows. Each row gets a
351
+ relative height of ``height_ratios[i] / sum(height_ratios)``.
352
+ If not given, all rows will have the same height.
353
+
354
+ """
355
+ self.left = left
356
+ self.bottom = bottom
357
+ self.right = right
358
+ self.top = top
359
+ self.wspace = wspace
360
+ self.hspace = hspace
361
+ self.figure = figure
362
+
363
+ super().__init__(nrows, ncols,
364
+ width_ratios=width_ratios,
365
+ height_ratios=height_ratios)
366
+
367
+ _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"]
368
+
369
+ def update(self, **kwargs):
370
+ """
371
+ Update the subplot parameters of the grid.
372
+
373
+ Parameters that are not explicitly given are not changed. Setting a
374
+ parameter to *None* resets it to :rc:`figure.subplot.*`.
375
+
376
+ Parameters
377
+ ----------
378
+ left, right, top, bottom : float or None, optional
379
+ Extent of the subplots as a fraction of figure width or height.
380
+ wspace, hspace : float, optional
381
+ Spacing between the subplots as a fraction of the average subplot
382
+ width / height.
383
+ """
384
+ for k, v in kwargs.items():
385
+ if k in self._AllowedKeys:
386
+ setattr(self, k, v)
387
+ else:
388
+ raise AttributeError(f"{k} is an unknown keyword")
389
+ for figmanager in _pylab_helpers.Gcf.figs.values():
390
+ for ax in figmanager.canvas.figure.axes:
391
+ if ax.get_subplotspec() is not None:
392
+ ss = ax.get_subplotspec().get_topmost_subplotspec()
393
+ if ss.get_gridspec() == self:
394
+ fig = ax.get_figure(root=False)
395
+ ax._set_position(ax.get_subplotspec().get_position(fig))
396
+
397
+ def get_subplot_params(self, figure=None):
398
+ """
399
+ Return the `.SubplotParams` for the GridSpec.
400
+
401
+ In order of precedence the values are taken from
402
+
403
+ - non-*None* attributes of the GridSpec
404
+ - the provided *figure*
405
+ - :rc:`figure.subplot.*`
406
+
407
+ Note that the ``figure`` attribute of the GridSpec is always ignored.
408
+ """
409
+ if figure is None:
410
+ kw = {k: mpl.rcParams["figure.subplot."+k]
411
+ for k in self._AllowedKeys}
412
+ subplotpars = SubplotParams(**kw)
413
+ else:
414
+ subplotpars = copy.copy(figure.subplotpars)
415
+
416
+ subplotpars.update(**{k: getattr(self, k) for k in self._AllowedKeys})
417
+
418
+ return subplotpars
419
+
420
+ def locally_modified_subplot_params(self):
421
+ """
422
+ Return a list of the names of the subplot parameters explicitly set
423
+ in the GridSpec.
424
+
425
+ This is a subset of the attributes of `.SubplotParams`.
426
+ """
427
+ return [k for k in self._AllowedKeys if getattr(self, k)]
428
+
429
+ def tight_layout(self, figure, renderer=None,
430
+ pad=1.08, h_pad=None, w_pad=None, rect=None):
431
+ """
432
+ Adjust subplot parameters to give specified padding.
433
+
434
+ Parameters
435
+ ----------
436
+ figure : `.Figure`
437
+ The figure.
438
+ renderer : `.RendererBase` subclass, optional
439
+ The renderer to be used.
440
+ pad : float
441
+ Padding between the figure edge and the edges of subplots, as a
442
+ fraction of the font-size.
443
+ h_pad, w_pad : float, optional
444
+ Padding (height/width) between edges of adjacent subplots.
445
+ Defaults to *pad*.
446
+ rect : tuple (left, bottom, right, top), default: None
447
+ (left, bottom, right, top) rectangle in normalized figure
448
+ coordinates that the whole subplots area (including labels) will
449
+ fit into. Default (None) is the whole figure.
450
+ """
451
+ if renderer is None:
452
+ renderer = figure._get_renderer()
453
+ kwargs = _tight_layout.get_tight_layout_figure(
454
+ figure, figure.axes,
455
+ _tight_layout.get_subplotspec_list(figure.axes, grid_spec=self),
456
+ renderer, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
457
+ if kwargs:
458
+ self.update(**kwargs)
459
+
460
+
461
+ class GridSpecFromSubplotSpec(GridSpecBase):
462
+ """
463
+ GridSpec whose subplot layout parameters are inherited from the
464
+ location specified by a given SubplotSpec.
465
+ """
466
+ def __init__(self, nrows, ncols,
467
+ subplot_spec,
468
+ wspace=None, hspace=None,
469
+ height_ratios=None, width_ratios=None):
470
+ """
471
+ Parameters
472
+ ----------
473
+ nrows, ncols : int
474
+ Number of rows and number of columns of the grid.
475
+ subplot_spec : SubplotSpec
476
+ Spec from which the layout parameters are inherited.
477
+ wspace, hspace : float, optional
478
+ See `GridSpec` for more details. If not specified default values
479
+ (from the figure or rcParams) are used.
480
+ height_ratios : array-like of length *nrows*, optional
481
+ See `GridSpecBase` for details.
482
+ width_ratios : array-like of length *ncols*, optional
483
+ See `GridSpecBase` for details.
484
+ """
485
+ self._wspace = wspace
486
+ self._hspace = hspace
487
+ if isinstance(subplot_spec, SubplotSpec):
488
+ self._subplot_spec = subplot_spec
489
+ else:
490
+ raise TypeError(
491
+ "subplot_spec must be type SubplotSpec, "
492
+ "usually from GridSpec, or axes.get_subplotspec.")
493
+ self.figure = self._subplot_spec.get_gridspec().figure
494
+ super().__init__(nrows, ncols,
495
+ width_ratios=width_ratios,
496
+ height_ratios=height_ratios)
497
+
498
+ def get_subplot_params(self, figure=None):
499
+ """Return a dictionary of subplot layout parameters."""
500
+ hspace = (self._hspace if self._hspace is not None
501
+ else figure.subplotpars.hspace if figure is not None
502
+ else mpl.rcParams["figure.subplot.hspace"])
503
+ wspace = (self._wspace if self._wspace is not None
504
+ else figure.subplotpars.wspace if figure is not None
505
+ else mpl.rcParams["figure.subplot.wspace"])
506
+
507
+ figbox = self._subplot_spec.get_position(figure)
508
+ left, bottom, right, top = figbox.extents
509
+
510
+ return SubplotParams(left=left, right=right,
511
+ bottom=bottom, top=top,
512
+ wspace=wspace, hspace=hspace)
513
+
514
+ def get_topmost_subplotspec(self):
515
+ """
516
+ Return the topmost `.SubplotSpec` instance associated with the subplot.
517
+ """
518
+ return self._subplot_spec.get_topmost_subplotspec()
519
+
520
+
521
+ class SubplotSpec:
522
+ """
523
+ The location of a subplot in a `GridSpec`.
524
+
525
+ .. note::
526
+
527
+ Likely, you will never instantiate a `SubplotSpec` yourself. Instead,
528
+ you will typically obtain one from a `GridSpec` using item-access.
529
+
530
+ Parameters
531
+ ----------
532
+ gridspec : `~matplotlib.gridspec.GridSpec`
533
+ The GridSpec, which the subplot is referencing.
534
+ num1, num2 : int
535
+ The subplot will occupy the *num1*-th cell of the given
536
+ *gridspec*. If *num2* is provided, the subplot will span between
537
+ *num1*-th cell and *num2*-th cell **inclusive**.
538
+
539
+ The index starts from 0.
540
+ """
541
+ def __init__(self, gridspec, num1, num2=None):
542
+ self._gridspec = gridspec
543
+ self.num1 = num1
544
+ self.num2 = num2
545
+
546
+ def __repr__(self):
547
+ return (f"{self.get_gridspec()}["
548
+ f"{self.rowspan.start}:{self.rowspan.stop}, "
549
+ f"{self.colspan.start}:{self.colspan.stop}]")
550
+
551
+ @staticmethod
552
+ def _from_subplot_args(figure, args):
553
+ """
554
+ Construct a `.SubplotSpec` from a parent `.Figure` and either
555
+
556
+ - a `.SubplotSpec` -- returned as is;
557
+ - one or three numbers -- a MATLAB-style subplot specifier.
558
+ """
559
+ if len(args) == 1:
560
+ arg, = args
561
+ if isinstance(arg, SubplotSpec):
562
+ return arg
563
+ elif not isinstance(arg, Integral):
564
+ raise ValueError(
565
+ f"Single argument to subplot must be a three-digit "
566
+ f"integer, not {arg!r}")
567
+ try:
568
+ rows, cols, num = map(int, str(arg))
569
+ except ValueError:
570
+ raise ValueError(
571
+ f"Single argument to subplot must be a three-digit "
572
+ f"integer, not {arg!r}") from None
573
+ elif len(args) == 3:
574
+ rows, cols, num = args
575
+ else:
576
+ raise _api.nargs_error("subplot", takes="1 or 3", given=len(args))
577
+
578
+ gs = GridSpec._check_gridspec_exists(figure, rows, cols)
579
+ if gs is None:
580
+ gs = GridSpec(rows, cols, figure=figure)
581
+ if isinstance(num, tuple) and len(num) == 2:
582
+ if not all(isinstance(n, Integral) for n in num):
583
+ raise ValueError(
584
+ f"Subplot specifier tuple must contain integers, not {num}"
585
+ )
586
+ i, j = num
587
+ else:
588
+ if not isinstance(num, Integral) or num < 1 or num > rows*cols:
589
+ raise ValueError(
590
+ f"num must be an integer with 1 <= num <= {rows*cols}, "
591
+ f"not {num!r}"
592
+ )
593
+ i = j = num
594
+ return gs[i-1:j]
595
+
596
+ # num2 is a property only to handle the case where it is None and someone
597
+ # mutates num1.
598
+
599
+ @property
600
+ def num2(self):
601
+ return self.num1 if self._num2 is None else self._num2
602
+
603
+ @num2.setter
604
+ def num2(self, value):
605
+ self._num2 = value
606
+
607
+ def get_gridspec(self):
608
+ return self._gridspec
609
+
610
+ def get_geometry(self):
611
+ """
612
+ Return the subplot geometry as tuple ``(n_rows, n_cols, start, stop)``.
613
+
614
+ The indices *start* and *stop* define the range of the subplot within
615
+ the `GridSpec`. *stop* is inclusive (i.e. for a single cell
616
+ ``start == stop``).
617
+ """
618
+ rows, cols = self.get_gridspec().get_geometry()
619
+ return rows, cols, self.num1, self.num2
620
+
621
+ @property
622
+ def rowspan(self):
623
+ """The rows spanned by this subplot, as a `range` object."""
624
+ ncols = self.get_gridspec().ncols
625
+ return range(self.num1 // ncols, self.num2 // ncols + 1)
626
+
627
+ @property
628
+ def colspan(self):
629
+ """The columns spanned by this subplot, as a `range` object."""
630
+ ncols = self.get_gridspec().ncols
631
+ # We explicitly support num2 referring to a column on num1's *left*, so
632
+ # we must sort the column indices here so that the range makes sense.
633
+ c1, c2 = sorted([self.num1 % ncols, self.num2 % ncols])
634
+ return range(c1, c2 + 1)
635
+
636
+ def is_first_row(self):
637
+ return self.rowspan.start == 0
638
+
639
+ def is_last_row(self):
640
+ return self.rowspan.stop == self.get_gridspec().nrows
641
+
642
+ def is_first_col(self):
643
+ return self.colspan.start == 0
644
+
645
+ def is_last_col(self):
646
+ return self.colspan.stop == self.get_gridspec().ncols
647
+
648
+ def get_position(self, figure):
649
+ """
650
+ Update the subplot position from ``figure.subplotpars``.
651
+ """
652
+ gridspec = self.get_gridspec()
653
+ nrows, ncols = gridspec.get_geometry()
654
+ rows, cols = np.unravel_index([self.num1, self.num2], (nrows, ncols))
655
+ fig_bottoms, fig_tops, fig_lefts, fig_rights = \
656
+ gridspec.get_grid_positions(figure)
657
+
658
+ fig_bottom = fig_bottoms[rows].min()
659
+ fig_top = fig_tops[rows].max()
660
+ fig_left = fig_lefts[cols].min()
661
+ fig_right = fig_rights[cols].max()
662
+ return Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top)
663
+
664
+ def get_topmost_subplotspec(self):
665
+ """
666
+ Return the topmost `SubplotSpec` instance associated with the subplot.
667
+ """
668
+ gridspec = self.get_gridspec()
669
+ if hasattr(gridspec, "get_topmost_subplotspec"):
670
+ return gridspec.get_topmost_subplotspec()
671
+ else:
672
+ return self
673
+
674
+ def __eq__(self, other):
675
+ """
676
+ Two SubplotSpecs are considered equal if they refer to the same
677
+ position(s) in the same `GridSpec`.
678
+ """
679
+ # other may not even have the attributes we are checking.
680
+ return ((self._gridspec, self.num1, self.num2)
681
+ == (getattr(other, "_gridspec", object()),
682
+ getattr(other, "num1", object()),
683
+ getattr(other, "num2", object())))
684
+
685
+ def __hash__(self):
686
+ return hash((self._gridspec, self.num1, self.num2))
687
+
688
+ def subgridspec(self, nrows, ncols, **kwargs):
689
+ """
690
+ Create a GridSpec within this subplot.
691
+
692
+ The created `.GridSpecFromSubplotSpec` will have this `SubplotSpec` as
693
+ a parent.
694
+
695
+ Parameters
696
+ ----------
697
+ nrows : int
698
+ Number of rows in grid.
699
+
700
+ ncols : int
701
+ Number of columns in grid.
702
+
703
+ Returns
704
+ -------
705
+ `.GridSpecFromSubplotSpec`
706
+
707
+ Other Parameters
708
+ ----------------
709
+ **kwargs
710
+ All other parameters are passed to `.GridSpecFromSubplotSpec`.
711
+
712
+ See Also
713
+ --------
714
+ matplotlib.pyplot.subplots
715
+
716
+ Examples
717
+ --------
718
+ Adding three subplots in the space occupied by a single subplot::
719
+
720
+ fig = plt.figure()
721
+ gs0 = fig.add_gridspec(3, 1)
722
+ ax1 = fig.add_subplot(gs0[0])
723
+ ax2 = fig.add_subplot(gs0[1])
724
+ gssub = gs0[2].subgridspec(1, 3)
725
+ for i in range(3):
726
+ fig.add_subplot(gssub[0, i])
727
+ """
728
+ return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs)
729
+
730
+
731
+ class SubplotParams:
732
+ """
733
+ Parameters defining the positioning of a subplots grid in a figure.
734
+ """
735
+
736
+ def __init__(self, left=None, bottom=None, right=None, top=None,
737
+ wspace=None, hspace=None):
738
+ """
739
+ Defaults are given by :rc:`figure.subplot.[name]`.
740
+
741
+ Parameters
742
+ ----------
743
+ left : float
744
+ The position of the left edge of the subplots,
745
+ as a fraction of the figure width.
746
+ right : float
747
+ The position of the right edge of the subplots,
748
+ as a fraction of the figure width.
749
+ bottom : float
750
+ The position of the bottom edge of the subplots,
751
+ as a fraction of the figure height.
752
+ top : float
753
+ The position of the top edge of the subplots,
754
+ as a fraction of the figure height.
755
+ wspace : float
756
+ The width of the padding between subplots,
757
+ as a fraction of the average Axes width.
758
+ hspace : float
759
+ The height of the padding between subplots,
760
+ as a fraction of the average Axes height.
761
+ """
762
+ for key in ["left", "bottom", "right", "top", "wspace", "hspace"]:
763
+ setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"])
764
+ self.update(left, bottom, right, top, wspace, hspace)
765
+
766
+ def update(self, left=None, bottom=None, right=None, top=None,
767
+ wspace=None, hspace=None):
768
+ """
769
+ Update the dimensions of the passed parameters. *None* means unchanged.
770
+ """
771
+ if ((left if left is not None else self.left)
772
+ >= (right if right is not None else self.right)):
773
+ raise ValueError('left cannot be >= right')
774
+ if ((bottom if bottom is not None else self.bottom)
775
+ >= (top if top is not None else self.top)):
776
+ raise ValueError('bottom cannot be >= top')
777
+ if left is not None:
778
+ self.left = left
779
+ if right is not None:
780
+ self.right = right
781
+ if bottom is not None:
782
+ self.bottom = bottom
783
+ if top is not None:
784
+ self.top = top
785
+ if wspace is not None:
786
+ self.wspace = wspace
787
+ if hspace is not None:
788
+ self.hspace = hspace
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Literal, overload
2
+
3
+ from numpy.typing import ArrayLike
4
+ import numpy as np
5
+
6
+ from matplotlib.axes import Axes
7
+ from matplotlib.backend_bases import RendererBase
8
+ from matplotlib.figure import Figure
9
+ from matplotlib.transforms import Bbox
10
+
11
+ class GridSpecBase:
12
+ def __init__(
13
+ self,
14
+ nrows: int,
15
+ ncols: int,
16
+ height_ratios: ArrayLike | None = ...,
17
+ width_ratios: ArrayLike | None = ...,
18
+ ) -> None: ...
19
+ @property
20
+ def nrows(self) -> int: ...
21
+ @property
22
+ def ncols(self) -> int: ...
23
+ def get_geometry(self) -> tuple[int, int]: ...
24
+ def get_subplot_params(self, figure: Figure | None = ...) -> SubplotParams: ...
25
+ def new_subplotspec(
26
+ self, loc: tuple[int, int], rowspan: int = ..., colspan: int = ...
27
+ ) -> SubplotSpec: ...
28
+ def set_width_ratios(self, width_ratios: ArrayLike | None) -> None: ...
29
+ def get_width_ratios(self) -> ArrayLike: ...
30
+ def set_height_ratios(self, height_ratios: ArrayLike | None) -> None: ...
31
+ def get_height_ratios(self) -> ArrayLike: ...
32
+ def get_grid_positions(
33
+ self, fig: Figure
34
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: ...
35
+ @staticmethod
36
+ def _check_gridspec_exists(figure: Figure, nrows: int, ncols: int) -> GridSpec: ...
37
+ def __getitem__(
38
+ self, key: tuple[int | slice, int | slice] | slice | int
39
+ ) -> SubplotSpec: ...
40
+ @overload
41
+ def subplots(
42
+ self,
43
+ *,
44
+ sharex: bool | Literal["all", "row", "col", "none"] = ...,
45
+ sharey: bool | Literal["all", "row", "col", "none"] = ...,
46
+ squeeze: Literal[False],
47
+ subplot_kw: dict[str, Any] | None = ...
48
+ ) -> np.ndarray: ...
49
+ @overload
50
+ def subplots(
51
+ self,
52
+ *,
53
+ sharex: bool | Literal["all", "row", "col", "none"] = ...,
54
+ sharey: bool | Literal["all", "row", "col", "none"] = ...,
55
+ squeeze: Literal[True] = ...,
56
+ subplot_kw: dict[str, Any] | None = ...
57
+ ) -> np.ndarray | Axes: ...
58
+
59
+ class GridSpec(GridSpecBase):
60
+ left: float | None
61
+ bottom: float | None
62
+ right: float | None
63
+ top: float | None
64
+ wspace: float | None
65
+ hspace: float | None
66
+ figure: Figure | None
67
+ def __init__(
68
+ self,
69
+ nrows: int,
70
+ ncols: int,
71
+ figure: Figure | None = ...,
72
+ left: float | None = ...,
73
+ bottom: float | None = ...,
74
+ right: float | None = ...,
75
+ top: float | None = ...,
76
+ wspace: float | None = ...,
77
+ hspace: float | None = ...,
78
+ width_ratios: ArrayLike | None = ...,
79
+ height_ratios: ArrayLike | None = ...,
80
+ ) -> None: ...
81
+ def update(self, **kwargs: float | None) -> None: ...
82
+ def locally_modified_subplot_params(self) -> list[str]: ...
83
+ def tight_layout(
84
+ self,
85
+ figure: Figure,
86
+ renderer: RendererBase | None = ...,
87
+ pad: float = ...,
88
+ h_pad: float | None = ...,
89
+ w_pad: float | None = ...,
90
+ rect: tuple[float, float, float, float] | None = ...,
91
+ ) -> None: ...
92
+
93
+ class GridSpecFromSubplotSpec(GridSpecBase):
94
+ figure: Figure | None
95
+ def __init__(
96
+ self,
97
+ nrows: int,
98
+ ncols: int,
99
+ subplot_spec: SubplotSpec,
100
+ wspace: float | None = ...,
101
+ hspace: float | None = ...,
102
+ height_ratios: ArrayLike | None = ...,
103
+ width_ratios: ArrayLike | None = ...,
104
+ ) -> None: ...
105
+ def get_topmost_subplotspec(self) -> SubplotSpec: ...
106
+
107
+ class SubplotSpec:
108
+ num1: int
109
+ def __init__(
110
+ self, gridspec: GridSpecBase, num1: int, num2: int | None = ...
111
+ ) -> None: ...
112
+ @staticmethod
113
+ def _from_subplot_args(figure, args): ...
114
+ @property
115
+ def num2(self) -> int: ...
116
+ @num2.setter
117
+ def num2(self, value: int) -> None: ...
118
+ def get_gridspec(self) -> GridSpecBase: ...
119
+ def get_geometry(self) -> tuple[int, int, int, int]: ...
120
+ @property
121
+ def rowspan(self) -> range: ...
122
+ @property
123
+ def colspan(self) -> range: ...
124
+ def is_first_row(self) -> bool: ...
125
+ def is_last_row(self) -> bool: ...
126
+ def is_first_col(self) -> bool: ...
127
+ def is_last_col(self) -> bool: ...
128
+ def get_position(self, figure: Figure) -> Bbox: ...
129
+ def get_topmost_subplotspec(self) -> SubplotSpec: ...
130
+ def __eq__(self, other: object) -> bool: ...
131
+ def __hash__(self) -> int: ...
132
+ def subgridspec(
133
+ self, nrows: int, ncols: int, **kwargs
134
+ ) -> GridSpecFromSubplotSpec: ...
135
+
136
+ class SubplotParams:
137
+ def __init__(
138
+ self,
139
+ left: float | None = ...,
140
+ bottom: float | None = ...,
141
+ right: float | None = ...,
142
+ top: float | None = ...,
143
+ wspace: float | None = ...,
144
+ hspace: float | None = ...,
145
+ ) -> None: ...
146
+ left: float
147
+ right: float
148
+ bottom: float
149
+ top: float
150
+ wspace: float
151
+ hspace: float
152
+ def update(
153
+ self,
154
+ left: float | None = ...,
155
+ bottom: float | None = ...,
156
+ right: float | None = ...,
157
+ top: float | None = ...,
158
+ wspace: float | None = ...,
159
+ hspace: float | None = ...,
160
+ ) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Contains classes for generating hatch patterns."""
2
+
3
+ import numpy as np
4
+
5
+ from matplotlib import _api
6
+ from matplotlib.path import Path
7
+
8
+
9
+ class HatchPatternBase:
10
+ """The base class for a hatch pattern."""
11
+ pass
12
+
13
+
14
+ class HorizontalHatch(HatchPatternBase):
15
+ def __init__(self, hatch, density):
16
+ self.num_lines = int((hatch.count('-') + hatch.count('+')) * density)
17
+ self.num_vertices = self.num_lines * 2
18
+
19
+ def set_vertices_and_codes(self, vertices, codes):
20
+ steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
21
+ retstep=True)
22
+ steps += stepsize / 2.
23
+ vertices[0::2, 0] = 0.0
24
+ vertices[0::2, 1] = steps
25
+ vertices[1::2, 0] = 1.0
26
+ vertices[1::2, 1] = steps
27
+ codes[0::2] = Path.MOVETO
28
+ codes[1::2] = Path.LINETO
29
+
30
+
31
+ class VerticalHatch(HatchPatternBase):
32
+ def __init__(self, hatch, density):
33
+ self.num_lines = int((hatch.count('|') + hatch.count('+')) * density)
34
+ self.num_vertices = self.num_lines * 2
35
+
36
+ def set_vertices_and_codes(self, vertices, codes):
37
+ steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
38
+ retstep=True)
39
+ steps += stepsize / 2.
40
+ vertices[0::2, 0] = steps
41
+ vertices[0::2, 1] = 0.0
42
+ vertices[1::2, 0] = steps
43
+ vertices[1::2, 1] = 1.0
44
+ codes[0::2] = Path.MOVETO
45
+ codes[1::2] = Path.LINETO
46
+
47
+
48
+ class NorthEastHatch(HatchPatternBase):
49
+ def __init__(self, hatch, density):
50
+ self.num_lines = int(
51
+ (hatch.count('/') + hatch.count('x') + hatch.count('X')) * density)
52
+ if self.num_lines:
53
+ self.num_vertices = (self.num_lines + 1) * 2
54
+ else:
55
+ self.num_vertices = 0
56
+
57
+ def set_vertices_and_codes(self, vertices, codes):
58
+ steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
59
+ vertices[0::2, 0] = 0.0 + steps
60
+ vertices[0::2, 1] = 0.0 - steps
61
+ vertices[1::2, 0] = 1.0 + steps
62
+ vertices[1::2, 1] = 1.0 - steps
63
+ codes[0::2] = Path.MOVETO
64
+ codes[1::2] = Path.LINETO
65
+
66
+
67
+ class SouthEastHatch(HatchPatternBase):
68
+ def __init__(self, hatch, density):
69
+ self.num_lines = int(
70
+ (hatch.count('\\') + hatch.count('x') + hatch.count('X'))
71
+ * density)
72
+ if self.num_lines:
73
+ self.num_vertices = (self.num_lines + 1) * 2
74
+ else:
75
+ self.num_vertices = 0
76
+
77
+ def set_vertices_and_codes(self, vertices, codes):
78
+ steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
79
+ vertices[0::2, 0] = 0.0 + steps
80
+ vertices[0::2, 1] = 1.0 + steps
81
+ vertices[1::2, 0] = 1.0 + steps
82
+ vertices[1::2, 1] = 0.0 + steps
83
+ codes[0::2] = Path.MOVETO
84
+ codes[1::2] = Path.LINETO
85
+
86
+
87
+ class Shapes(HatchPatternBase):
88
+ filled = False
89
+
90
+ def __init__(self, hatch, density):
91
+ if self.num_rows == 0:
92
+ self.num_shapes = 0
93
+ self.num_vertices = 0
94
+ else:
95
+ self.num_shapes = ((self.num_rows // 2 + 1) * (self.num_rows + 1) +
96
+ (self.num_rows // 2) * self.num_rows)
97
+ self.num_vertices = (self.num_shapes *
98
+ len(self.shape_vertices) *
99
+ (1 if self.filled else 2))
100
+
101
+ def set_vertices_and_codes(self, vertices, codes):
102
+ offset = 1.0 / self.num_rows
103
+ shape_vertices = self.shape_vertices * offset * self.size
104
+ shape_codes = self.shape_codes
105
+ if not self.filled:
106
+ shape_vertices = np.concatenate( # Forward, then backward.
107
+ [shape_vertices, shape_vertices[::-1] * 0.9])
108
+ shape_codes = np.concatenate([shape_codes, shape_codes])
109
+ vertices_parts = []
110
+ codes_parts = []
111
+ for row in range(self.num_rows + 1):
112
+ if row % 2 == 0:
113
+ cols = np.linspace(0, 1, self.num_rows + 1)
114
+ else:
115
+ cols = np.linspace(offset / 2, 1 - offset / 2, self.num_rows)
116
+ row_pos = row * offset
117
+ for col_pos in cols:
118
+ vertices_parts.append(shape_vertices + [col_pos, row_pos])
119
+ codes_parts.append(shape_codes)
120
+ np.concatenate(vertices_parts, out=vertices)
121
+ np.concatenate(codes_parts, out=codes)
122
+
123
+
124
+ class Circles(Shapes):
125
+ def __init__(self, hatch, density):
126
+ path = Path.unit_circle()
127
+ self.shape_vertices = path.vertices
128
+ self.shape_codes = path.codes
129
+ super().__init__(hatch, density)
130
+
131
+
132
+ class SmallCircles(Circles):
133
+ size = 0.2
134
+
135
+ def __init__(self, hatch, density):
136
+ self.num_rows = (hatch.count('o')) * density
137
+ super().__init__(hatch, density)
138
+
139
+
140
+ class LargeCircles(Circles):
141
+ size = 0.35
142
+
143
+ def __init__(self, hatch, density):
144
+ self.num_rows = (hatch.count('O')) * density
145
+ super().__init__(hatch, density)
146
+
147
+
148
+ class SmallFilledCircles(Circles):
149
+ size = 0.1
150
+ filled = True
151
+
152
+ def __init__(self, hatch, density):
153
+ self.num_rows = (hatch.count('.')) * density
154
+ super().__init__(hatch, density)
155
+
156
+
157
+ class Stars(Shapes):
158
+ size = 1.0 / 3.0
159
+ filled = True
160
+
161
+ def __init__(self, hatch, density):
162
+ self.num_rows = (hatch.count('*')) * density
163
+ path = Path.unit_regular_star(5)
164
+ self.shape_vertices = path.vertices
165
+ self.shape_codes = np.full(len(self.shape_vertices), Path.LINETO,
166
+ dtype=Path.code_type)
167
+ self.shape_codes[0] = Path.MOVETO
168
+ super().__init__(hatch, density)
169
+
170
+ _hatch_types = [
171
+ HorizontalHatch,
172
+ VerticalHatch,
173
+ NorthEastHatch,
174
+ SouthEastHatch,
175
+ SmallCircles,
176
+ LargeCircles,
177
+ SmallFilledCircles,
178
+ Stars
179
+ ]
180
+
181
+
182
+ def _validate_hatch_pattern(hatch):
183
+ valid_hatch_patterns = set(r'-+|/\xXoO.*')
184
+ if hatch is not None:
185
+ invalids = set(hatch).difference(valid_hatch_patterns)
186
+ if invalids:
187
+ valid = ''.join(sorted(valid_hatch_patterns))
188
+ invalids = ''.join(sorted(invalids))
189
+ _api.warn_deprecated(
190
+ '3.4',
191
+ removal='3.11', # one release after custom hatches (#20690)
192
+ message=f'hatch must consist of a string of "{valid}" or '
193
+ 'None, but found the following invalid values '
194
+ f'"{invalids}". Passing invalid values is deprecated '
195
+ 'since %(since)s and will become an error in %(removal)s.'
196
+ )
197
+
198
+
199
+ def get_path(hatchpattern, density=6):
200
+ """
201
+ Given a hatch specifier, *hatchpattern*, generates Path to render
202
+ the hatch in a unit square. *density* is the number of lines per
203
+ unit square.
204
+ """
205
+ density = int(density)
206
+
207
+ patterns = [hatch_type(hatchpattern, density)
208
+ for hatch_type in _hatch_types]
209
+ num_vertices = sum([pattern.num_vertices for pattern in patterns])
210
+
211
+ if num_vertices == 0:
212
+ return Path(np.empty((0, 2)))
213
+
214
+ vertices = np.empty((num_vertices, 2))
215
+ codes = np.empty(num_vertices, Path.code_type)
216
+
217
+ cursor = 0
218
+ for pattern in patterns:
219
+ if pattern.num_vertices != 0:
220
+ vertices_chunk = vertices[cursor:cursor + pattern.num_vertices]
221
+ codes_chunk = codes[cursor:cursor + pattern.num_vertices]
222
+ pattern.set_vertices_and_codes(vertices_chunk, codes_chunk)
223
+ cursor += pattern.num_vertices
224
+
225
+ return Path(vertices, codes)
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib.path import Path
2
+
3
+ import numpy as np
4
+ from numpy.typing import ArrayLike
5
+
6
+ class HatchPatternBase: ...
7
+
8
+ class HorizontalHatch(HatchPatternBase):
9
+ num_lines: int
10
+ num_vertices: int
11
+ def __init__(self, hatch: str, density: int) -> None: ...
12
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
13
+
14
+ class VerticalHatch(HatchPatternBase):
15
+ num_lines: int
16
+ num_vertices: int
17
+ def __init__(self, hatch: str, density: int) -> None: ...
18
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
19
+
20
+ class NorthEastHatch(HatchPatternBase):
21
+ num_lines: int
22
+ num_vertices: int
23
+ def __init__(self, hatch: str, density: int) -> None: ...
24
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
25
+
26
+ class SouthEastHatch(HatchPatternBase):
27
+ num_lines: int
28
+ num_vertices: int
29
+ def __init__(self, hatch: str, density: int) -> None: ...
30
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
31
+
32
+ class Shapes(HatchPatternBase):
33
+ filled: bool
34
+ num_shapes: int
35
+ num_vertices: int
36
+ def __init__(self, hatch: str, density: int) -> None: ...
37
+ def set_vertices_and_codes(self, vertices: ArrayLike, codes: ArrayLike) -> None: ...
38
+
39
+ class Circles(Shapes):
40
+ shape_vertices: np.ndarray
41
+ shape_codes: np.ndarray
42
+ def __init__(self, hatch: str, density: int) -> None: ...
43
+
44
+ class SmallCircles(Circles):
45
+ size: float
46
+ num_rows: int
47
+ def __init__(self, hatch: str, density: int) -> None: ...
48
+
49
+ class LargeCircles(Circles):
50
+ size: float
51
+ num_rows: int
52
+ def __init__(self, hatch: str, density: int) -> None: ...
53
+
54
+ class SmallFilledCircles(Circles):
55
+ size: float
56
+ filled: bool
57
+ num_rows: int
58
+ def __init__(self, hatch: str, density: int) -> None: ...
59
+
60
+ class Stars(Shapes):
61
+ size: float
62
+ filled: bool
63
+ num_rows: int
64
+ shape_vertices: np.ndarray
65
+ shape_codes: np.ndarray
66
+ def __init__(self, hatch: str, density: int) -> None: ...
67
+
68
+ def get_path(hatchpattern: str, density: int = ...) -> Path: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py ADDED
@@ -0,0 +1,1786 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The image module supports basic image loading, rescaling and display
3
+ operations.
4
+ """
5
+
6
+ import math
7
+ import os
8
+ import logging
9
+ from pathlib import Path
10
+ import warnings
11
+
12
+ import numpy as np
13
+ import PIL.Image
14
+ import PIL.PngImagePlugin
15
+
16
+ import matplotlib as mpl
17
+ from matplotlib import _api, cbook
18
+ # For clarity, names from _image are given explicitly in this module
19
+ from matplotlib import _image
20
+ # For user convenience, the names from _image are also imported into
21
+ # the image namespace
22
+ from matplotlib._image import * # noqa: F401, F403
23
+ import matplotlib.artist as martist
24
+ import matplotlib.colorizer as mcolorizer
25
+ from matplotlib.backend_bases import FigureCanvasBase
26
+ import matplotlib.colors as mcolors
27
+ from matplotlib.transforms import (
28
+ Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo,
29
+ IdentityTransform, TransformedBbox)
30
+
31
+ _log = logging.getLogger(__name__)
32
+
33
+ # map interpolation strings to module constants
34
+ _interpd_ = {
35
+ 'auto': _image.NEAREST, # this will use nearest or Hanning...
36
+ 'none': _image.NEAREST, # fall back to nearest when not supported
37
+ 'nearest': _image.NEAREST,
38
+ 'bilinear': _image.BILINEAR,
39
+ 'bicubic': _image.BICUBIC,
40
+ 'spline16': _image.SPLINE16,
41
+ 'spline36': _image.SPLINE36,
42
+ 'hanning': _image.HANNING,
43
+ 'hamming': _image.HAMMING,
44
+ 'hermite': _image.HERMITE,
45
+ 'kaiser': _image.KAISER,
46
+ 'quadric': _image.QUADRIC,
47
+ 'catrom': _image.CATROM,
48
+ 'gaussian': _image.GAUSSIAN,
49
+ 'bessel': _image.BESSEL,
50
+ 'mitchell': _image.MITCHELL,
51
+ 'sinc': _image.SINC,
52
+ 'lanczos': _image.LANCZOS,
53
+ 'blackman': _image.BLACKMAN,
54
+ 'antialiased': _image.NEAREST, # this will use nearest or Hanning...
55
+ }
56
+
57
+ interpolations_names = set(_interpd_)
58
+
59
+
60
+ def composite_images(images, renderer, magnification=1.0):
61
+ """
62
+ Composite a number of RGBA images into one. The images are
63
+ composited in the order in which they appear in the *images* list.
64
+
65
+ Parameters
66
+ ----------
67
+ images : list of Images
68
+ Each must have a `make_image` method. For each image,
69
+ `can_composite` should return `True`, though this is not
70
+ enforced by this function. Each image must have a purely
71
+ affine transformation with no shear.
72
+
73
+ renderer : `.RendererBase`
74
+
75
+ magnification : float, default: 1
76
+ The additional magnification to apply for the renderer in use.
77
+
78
+ Returns
79
+ -------
80
+ image : (M, N, 4) `numpy.uint8` array
81
+ The composited RGBA image.
82
+ offset_x, offset_y : float
83
+ The (left, bottom) offset where the composited image should be placed
84
+ in the output figure.
85
+ """
86
+ if len(images) == 0:
87
+ return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
88
+
89
+ parts = []
90
+ bboxes = []
91
+ for image in images:
92
+ data, x, y, trans = image.make_image(renderer, magnification)
93
+ if data is not None:
94
+ x *= magnification
95
+ y *= magnification
96
+ parts.append((data, x, y, image._get_scalar_alpha()))
97
+ bboxes.append(
98
+ Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]]))
99
+
100
+ if len(parts) == 0:
101
+ return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
102
+
103
+ bbox = Bbox.union(bboxes)
104
+
105
+ output = np.zeros(
106
+ (int(bbox.height), int(bbox.width), 4), dtype=np.uint8)
107
+
108
+ for data, x, y, alpha in parts:
109
+ trans = Affine2D().translate(x - bbox.x0, y - bbox.y0)
110
+ _image.resample(data, output, trans, _image.NEAREST,
111
+ resample=False, alpha=alpha)
112
+
113
+ return output, bbox.x0 / magnification, bbox.y0 / magnification
114
+
115
+
116
+ def _draw_list_compositing_images(
117
+ renderer, parent, artists, suppress_composite=None):
118
+ """
119
+ Draw a sorted list of artists, compositing images into a single
120
+ image where possible.
121
+
122
+ For internal Matplotlib use only: It is here to reduce duplication
123
+ between `Figure.draw` and `Axes.draw`, but otherwise should not be
124
+ generally useful.
125
+ """
126
+ has_images = any(isinstance(x, _ImageBase) for x in artists)
127
+
128
+ # override the renderer default if suppressComposite is not None
129
+ not_composite = (suppress_composite if suppress_composite is not None
130
+ else renderer.option_image_nocomposite())
131
+
132
+ if not_composite or not has_images:
133
+ for a in artists:
134
+ a.draw(renderer)
135
+ else:
136
+ # Composite any adjacent images together
137
+ image_group = []
138
+ mag = renderer.get_image_magnification()
139
+
140
+ def flush_images():
141
+ if len(image_group) == 1:
142
+ image_group[0].draw(renderer)
143
+ elif len(image_group) > 1:
144
+ data, l, b = composite_images(image_group, renderer, mag)
145
+ if data.size != 0:
146
+ gc = renderer.new_gc()
147
+ gc.set_clip_rectangle(parent.bbox)
148
+ gc.set_clip_path(parent.get_clip_path())
149
+ renderer.draw_image(gc, round(l), round(b), data)
150
+ gc.restore()
151
+ del image_group[:]
152
+
153
+ for a in artists:
154
+ if (isinstance(a, _ImageBase) and a.can_composite() and
155
+ a.get_clip_on() and not a.get_clip_path()):
156
+ image_group.append(a)
157
+ else:
158
+ flush_images()
159
+ a.draw(renderer)
160
+ flush_images()
161
+
162
+
163
+ def _resample(
164
+ image_obj, data, out_shape, transform, *, resample=None, alpha=1):
165
+ """
166
+ Convenience wrapper around `._image.resample` to resample *data* to
167
+ *out_shape* (with a third dimension if *data* is RGBA) that takes care of
168
+ allocating the output array and fetching the relevant properties from the
169
+ Image object *image_obj*.
170
+ """
171
+ # AGG can only handle coordinates smaller than 24-bit signed integers,
172
+ # so raise errors if the input data is larger than _image.resample can
173
+ # handle.
174
+ msg = ('Data with more than {n} cannot be accurately displayed. '
175
+ 'Downsampling to less than {n} before displaying. '
176
+ 'To remove this warning, manually downsample your data.')
177
+ if data.shape[1] > 2**23:
178
+ warnings.warn(msg.format(n='2**23 columns'))
179
+ step = int(np.ceil(data.shape[1] / 2**23))
180
+ data = data[:, ::step]
181
+ transform = Affine2D().scale(step, 1) + transform
182
+ if data.shape[0] > 2**24:
183
+ warnings.warn(msg.format(n='2**24 rows'))
184
+ step = int(np.ceil(data.shape[0] / 2**24))
185
+ data = data[::step, :]
186
+ transform = Affine2D().scale(1, step) + transform
187
+ # decide if we need to apply anti-aliasing if the data is upsampled:
188
+ # compare the number of displayed pixels to the number of
189
+ # the data pixels.
190
+ interpolation = image_obj.get_interpolation()
191
+ if interpolation in ['antialiased', 'auto']:
192
+ # don't antialias if upsampling by an integer number or
193
+ # if zooming in more than a factor of 3
194
+ pos = np.array([[0, 0], [data.shape[1], data.shape[0]]])
195
+ disp = transform.transform(pos)
196
+ dispx = np.abs(np.diff(disp[:, 0]))
197
+ dispy = np.abs(np.diff(disp[:, 1]))
198
+ if ((dispx > 3 * data.shape[1] or
199
+ dispx == data.shape[1] or
200
+ dispx == 2 * data.shape[1]) and
201
+ (dispy > 3 * data.shape[0] or
202
+ dispy == data.shape[0] or
203
+ dispy == 2 * data.shape[0])):
204
+ interpolation = 'nearest'
205
+ else:
206
+ interpolation = 'hanning'
207
+ out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
208
+ if resample is None:
209
+ resample = image_obj.get_resample()
210
+ _image.resample(data, out, transform,
211
+ _interpd_[interpolation],
212
+ resample,
213
+ alpha,
214
+ image_obj.get_filternorm(),
215
+ image_obj.get_filterrad())
216
+ return out
217
+
218
+
219
+ def _rgb_to_rgba(A):
220
+ """
221
+ Convert an RGB image to RGBA, as required by the image resample C++
222
+ extension.
223
+ """
224
+ rgba = np.zeros((A.shape[0], A.shape[1], 4), dtype=A.dtype)
225
+ rgba[:, :, :3] = A
226
+ if rgba.dtype == np.uint8:
227
+ rgba[:, :, 3] = 255
228
+ else:
229
+ rgba[:, :, 3] = 1.0
230
+ return rgba
231
+
232
+
233
+ class _ImageBase(mcolorizer.ColorizingArtist):
234
+ """
235
+ Base class for images.
236
+
237
+ interpolation and cmap default to their rc settings
238
+
239
+ cmap is a colors.Colormap instance
240
+ norm is a colors.Normalize instance to map luminance to 0-1
241
+
242
+ extent is data axes (left, right, bottom, top) for making image plots
243
+ registered with data plots. Default is to label the pixel
244
+ centers with the zero-based row and column indices.
245
+
246
+ Additional kwargs are matplotlib.artist properties
247
+ """
248
+ zorder = 0
249
+
250
+ def __init__(self, ax,
251
+ cmap=None,
252
+ norm=None,
253
+ colorizer=None,
254
+ interpolation=None,
255
+ origin=None,
256
+ filternorm=True,
257
+ filterrad=4.0,
258
+ resample=False,
259
+ *,
260
+ interpolation_stage=None,
261
+ **kwargs
262
+ ):
263
+ super().__init__(self._get_colorizer(cmap, norm, colorizer))
264
+ if origin is None:
265
+ origin = mpl.rcParams['image.origin']
266
+ _api.check_in_list(["upper", "lower"], origin=origin)
267
+ self.origin = origin
268
+ self.set_filternorm(filternorm)
269
+ self.set_filterrad(filterrad)
270
+ self.set_interpolation(interpolation)
271
+ self.set_interpolation_stage(interpolation_stage)
272
+ self.set_resample(resample)
273
+ self.axes = ax
274
+
275
+ self._imcache = None
276
+
277
+ self._internal_update(kwargs)
278
+
279
+ def __str__(self):
280
+ try:
281
+ shape = self.get_shape()
282
+ return f"{type(self).__name__}(shape={shape!r})"
283
+ except RuntimeError:
284
+ return type(self).__name__
285
+
286
+ def __getstate__(self):
287
+ # Save some space on the pickle by not saving the cache.
288
+ return {**super().__getstate__(), "_imcache": None}
289
+
290
+ def get_size(self):
291
+ """Return the size of the image as tuple (numrows, numcols)."""
292
+ return self.get_shape()[:2]
293
+
294
+ def get_shape(self):
295
+ """
296
+ Return the shape of the image as tuple (numrows, numcols, channels).
297
+ """
298
+ if self._A is None:
299
+ raise RuntimeError('You must first set the image array')
300
+
301
+ return self._A.shape
302
+
303
+ def set_alpha(self, alpha):
304
+ """
305
+ Set the alpha value used for blending - not supported on all backends.
306
+
307
+ Parameters
308
+ ----------
309
+ alpha : float or 2D array-like or None
310
+ """
311
+ martist.Artist._set_alpha_for_array(self, alpha)
312
+ if np.ndim(alpha) not in (0, 2):
313
+ raise TypeError('alpha must be a float, two-dimensional '
314
+ 'array, or None')
315
+ self._imcache = None
316
+
317
+ def _get_scalar_alpha(self):
318
+ """
319
+ Get a scalar alpha value to be applied to the artist as a whole.
320
+
321
+ If the alpha value is a matrix, the method returns 1.0 because pixels
322
+ have individual alpha values (see `~._ImageBase._make_image` for
323
+ details). If the alpha value is a scalar, the method returns said value
324
+ to be applied to the artist as a whole because pixels do not have
325
+ individual alpha values.
326
+ """
327
+ return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \
328
+ else self._alpha
329
+
330
+ def changed(self):
331
+ """
332
+ Call this whenever the mappable is changed so observers can update.
333
+ """
334
+ self._imcache = None
335
+ super().changed()
336
+
337
+ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
338
+ unsampled=False, round_to_pixel_border=True):
339
+ """
340
+ Normalize, rescale, and colormap the image *A* from the given *in_bbox*
341
+ (in data space), to the given *out_bbox* (in pixel space) clipped to
342
+ the given *clip_bbox* (also in pixel space), and magnified by the
343
+ *magnification* factor.
344
+
345
+ Parameters
346
+ ----------
347
+ A : ndarray
348
+
349
+ - a (M, N) array interpreted as scalar (greyscale) image,
350
+ with one of the dtypes `~numpy.float32`, `~numpy.float64`,
351
+ `~numpy.float128`, `~numpy.uint16` or `~numpy.uint8`.
352
+ - (M, N, 4) RGBA image with a dtype of `~numpy.float32`,
353
+ `~numpy.float64`, `~numpy.float128`, or `~numpy.uint8`.
354
+
355
+ in_bbox : `~matplotlib.transforms.Bbox`
356
+
357
+ out_bbox : `~matplotlib.transforms.Bbox`
358
+
359
+ clip_bbox : `~matplotlib.transforms.Bbox`
360
+
361
+ magnification : float, default: 1
362
+
363
+ unsampled : bool, default: False
364
+ If True, the image will not be scaled, but an appropriate
365
+ affine transformation will be returned instead.
366
+
367
+ round_to_pixel_border : bool, default: True
368
+ If True, the output image size will be rounded to the nearest pixel
369
+ boundary. This makes the images align correctly with the Axes.
370
+ It should not be used if exact scaling is needed, such as for
371
+ `.FigureImage`.
372
+
373
+ Returns
374
+ -------
375
+ image : (M, N, 4) `numpy.uint8` array
376
+ The RGBA image, resampled unless *unsampled* is True.
377
+ x, y : float
378
+ The upper left corner where the image should be drawn, in pixel
379
+ space.
380
+ trans : `~matplotlib.transforms.Affine2D`
381
+ The affine transformation from image to pixel space.
382
+ """
383
+ if A is None:
384
+ raise RuntimeError('You must first set the image '
385
+ 'array or the image attribute')
386
+ if A.size == 0:
387
+ raise RuntimeError("_make_image must get a non-empty image. "
388
+ "Your Artist's draw method must filter before "
389
+ "this method is called.")
390
+
391
+ clipped_bbox = Bbox.intersection(out_bbox, clip_bbox)
392
+
393
+ if clipped_bbox is None:
394
+ return None, 0, 0, None
395
+
396
+ out_width_base = clipped_bbox.width * magnification
397
+ out_height_base = clipped_bbox.height * magnification
398
+
399
+ if out_width_base == 0 or out_height_base == 0:
400
+ return None, 0, 0, None
401
+
402
+ if self.origin == 'upper':
403
+ # Flip the input image using a transform. This avoids the
404
+ # problem with flipping the array, which results in a copy
405
+ # when it is converted to contiguous in the C wrapper
406
+ t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1)
407
+ else:
408
+ t0 = IdentityTransform()
409
+
410
+ t0 += (
411
+ Affine2D()
412
+ .scale(
413
+ in_bbox.width / A.shape[1],
414
+ in_bbox.height / A.shape[0])
415
+ .translate(in_bbox.x0, in_bbox.y0)
416
+ + self.get_transform())
417
+
418
+ t = (t0
419
+ + (Affine2D()
420
+ .translate(-clipped_bbox.x0, -clipped_bbox.y0)
421
+ .scale(magnification)))
422
+
423
+ # So that the image is aligned with the edge of the Axes, we want to
424
+ # round up the output width to the next integer. This also means
425
+ # scaling the transform slightly to account for the extra subpixel.
426
+ if ((not unsampled) and t.is_affine and round_to_pixel_border and
427
+ (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)):
428
+ out_width = math.ceil(out_width_base)
429
+ out_height = math.ceil(out_height_base)
430
+ extra_width = (out_width - out_width_base) / out_width_base
431
+ extra_height = (out_height - out_height_base) / out_height_base
432
+ t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
433
+ else:
434
+ out_width = int(out_width_base)
435
+ out_height = int(out_height_base)
436
+ out_shape = (out_height, out_width)
437
+
438
+ if not unsampled:
439
+ if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
440
+ raise ValueError(f"Invalid shape {A.shape} for image data")
441
+
442
+ # if antialiased, this needs to change as window sizes
443
+ # change:
444
+ interpolation_stage = self._interpolation_stage
445
+ if interpolation_stage in ['antialiased', 'auto']:
446
+ pos = np.array([[0, 0], [A.shape[1], A.shape[0]]])
447
+ disp = t.transform(pos)
448
+ dispx = np.abs(np.diff(disp[:, 0])) / A.shape[1]
449
+ dispy = np.abs(np.diff(disp[:, 1])) / A.shape[0]
450
+ if (dispx < 3) or (dispy < 3):
451
+ interpolation_stage = 'rgba'
452
+ else:
453
+ interpolation_stage = 'data'
454
+
455
+ if A.ndim == 2 and interpolation_stage == 'data':
456
+ # if we are a 2D array, then we are running through the
457
+ # norm + colormap transformation. However, in general the
458
+ # input data is not going to match the size on the screen so we
459
+ # have to resample to the correct number of pixels
460
+
461
+ if A.dtype.kind == 'f': # Float dtype: scale to same dtype.
462
+ scaled_dtype = np.dtype("f8" if A.dtype.itemsize > 4 else "f4")
463
+ if scaled_dtype.itemsize < A.dtype.itemsize:
464
+ _api.warn_external(f"Casting input data from {A.dtype}"
465
+ f" to {scaled_dtype} for imshow.")
466
+ else: # Int dtype, likely.
467
+ # TODO slice input array first
468
+ # Scale to appropriately sized float: use float32 if the
469
+ # dynamic range is small, to limit the memory footprint.
470
+ da = A.max().astype("f8") - A.min().astype("f8")
471
+ scaled_dtype = "f8" if da > 1e8 else "f4"
472
+
473
+ # resample the input data to the correct resolution and shape
474
+ A_resampled = _resample(self, A.astype(scaled_dtype), out_shape, t)
475
+
476
+ # if using NoNorm, cast back to the original datatype
477
+ if isinstance(self.norm, mcolors.NoNorm):
478
+ A_resampled = A_resampled.astype(A.dtype)
479
+
480
+ # Compute out_mask (what screen pixels include "bad" data
481
+ # pixels) and out_alpha (to what extent screen pixels are
482
+ # covered by data pixels: 0 outside the data extent, 1 inside
483
+ # (even for bad data), and intermediate values at the edges).
484
+ mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
485
+ if A.mask.shape == A.shape # nontrivial mask
486
+ else np.ones_like(A, np.float32))
487
+ # we always have to interpolate the mask to account for
488
+ # non-affine transformations
489
+ out_alpha = _resample(self, mask, out_shape, t, resample=True)
490
+ del mask # Make sure we don't use mask anymore!
491
+ out_mask = np.isnan(out_alpha)
492
+ out_alpha[out_mask] = 1
493
+ # Apply the pixel-by-pixel alpha values if present
494
+ alpha = self.get_alpha()
495
+ if alpha is not None and np.ndim(alpha) > 0:
496
+ out_alpha *= _resample(self, alpha, out_shape, t, resample=True)
497
+ # mask and run through the norm
498
+ resampled_masked = np.ma.masked_array(A_resampled, out_mask)
499
+ output = self.norm(resampled_masked)
500
+ else:
501
+ if A.ndim == 2: # interpolation_stage = 'rgba'
502
+ self.norm.autoscale_None(A)
503
+ A = self.to_rgba(A)
504
+ alpha = self.get_alpha()
505
+ if alpha is None: # alpha parameter not specified
506
+ if A.shape[2] == 3: # image has no alpha channel
507
+ output_alpha = 255 if A.dtype == np.uint8 else 1.0
508
+ else:
509
+ output_alpha = _resample( # resample alpha channel
510
+ self, A[..., 3], out_shape, t)
511
+ output = _resample( # resample rgb channels
512
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
513
+ elif np.ndim(alpha) > 0: # Array alpha
514
+ # user-specified array alpha overrides the existing alpha channel
515
+ output_alpha = _resample(self, alpha, out_shape, t)
516
+ output = _resample(
517
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
518
+ else: # Scalar alpha
519
+ if A.shape[2] == 3: # broadcast scalar alpha
520
+ output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha
521
+ else: # or apply scalar alpha to existing alpha channel
522
+ output_alpha = _resample(self, A[..., 3], out_shape, t) * alpha
523
+ output = _resample(
524
+ self, _rgb_to_rgba(A[..., :3]), out_shape, t)
525
+ output[..., 3] = output_alpha # recombine rgb and alpha
526
+
527
+ # output is now either a 2D array of normed (int or float) data
528
+ # or an RGBA array of re-sampled input
529
+ output = self.to_rgba(output, bytes=True, norm=False)
530
+ # output is now a correctly sized RGBA array of uint8
531
+
532
+ # Apply alpha *after* if the input was greyscale without a mask
533
+ if A.ndim == 2:
534
+ alpha = self._get_scalar_alpha()
535
+ alpha_channel = output[:, :, 3]
536
+ alpha_channel[:] = ( # Assignment will cast to uint8.
537
+ alpha_channel.astype(np.float32) * out_alpha * alpha)
538
+
539
+ else:
540
+ if self._imcache is None:
541
+ self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2))
542
+ output = self._imcache
543
+
544
+ # Subset the input image to only the part that will be displayed.
545
+ subset = TransformedBbox(clip_bbox, t0.inverted()).frozen()
546
+ output = output[
547
+ int(max(subset.ymin, 0)):
548
+ int(min(subset.ymax + 1, output.shape[0])),
549
+ int(max(subset.xmin, 0)):
550
+ int(min(subset.xmax + 1, output.shape[1]))]
551
+
552
+ t = Affine2D().translate(
553
+ int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t
554
+
555
+ return output, clipped_bbox.x0, clipped_bbox.y0, t
556
+
557
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
558
+ """
559
+ Normalize, rescale, and colormap this image's data for rendering using
560
+ *renderer*, with the given *magnification*.
561
+
562
+ If *unsampled* is True, the image will not be scaled, but an
563
+ appropriate affine transformation will be returned instead.
564
+
565
+ Returns
566
+ -------
567
+ image : (M, N, 4) `numpy.uint8` array
568
+ The RGBA image, resampled unless *unsampled* is True.
569
+ x, y : float
570
+ The upper left corner where the image should be drawn, in pixel
571
+ space.
572
+ trans : `~matplotlib.transforms.Affine2D`
573
+ The affine transformation from image to pixel space.
574
+ """
575
+ raise NotImplementedError('The make_image method must be overridden')
576
+
577
+ def _check_unsampled_image(self):
578
+ """
579
+ Return whether the image is better to be drawn unsampled.
580
+
581
+ The derived class needs to override it.
582
+ """
583
+ return False
584
+
585
+ @martist.allow_rasterization
586
+ def draw(self, renderer):
587
+ # if not visible, declare victory and return
588
+ if not self.get_visible():
589
+ self.stale = False
590
+ return
591
+ # for empty images, there is nothing to draw!
592
+ if self.get_array().size == 0:
593
+ self.stale = False
594
+ return
595
+ # actually render the image.
596
+ gc = renderer.new_gc()
597
+ self._set_gc_clip(gc)
598
+ gc.set_alpha(self._get_scalar_alpha())
599
+ gc.set_url(self.get_url())
600
+ gc.set_gid(self.get_gid())
601
+ if (renderer.option_scale_image() # Renderer supports transform kwarg.
602
+ and self._check_unsampled_image()
603
+ and self.get_transform().is_affine):
604
+ im, l, b, trans = self.make_image(renderer, unsampled=True)
605
+ if im is not None:
606
+ trans = Affine2D().scale(im.shape[1], im.shape[0]) + trans
607
+ renderer.draw_image(gc, l, b, im, trans)
608
+ else:
609
+ im, l, b, trans = self.make_image(
610
+ renderer, renderer.get_image_magnification())
611
+ if im is not None:
612
+ renderer.draw_image(gc, l, b, im)
613
+ gc.restore()
614
+ self.stale = False
615
+
616
+ def contains(self, mouseevent):
617
+ """Test whether the mouse event occurred within the image."""
618
+ if (self._different_canvas(mouseevent)
619
+ # This doesn't work for figimage.
620
+ or not self.axes.contains(mouseevent)[0]):
621
+ return False, {}
622
+ # TODO: make sure this is consistent with patch and patch
623
+ # collection on nonlinear transformed coordinates.
624
+ # TODO: consider returning image coordinates (shouldn't
625
+ # be too difficult given that the image is rectilinear
626
+ trans = self.get_transform().inverted()
627
+ x, y = trans.transform([mouseevent.x, mouseevent.y])
628
+ xmin, xmax, ymin, ymax = self.get_extent()
629
+ # This checks xmin <= x <= xmax *or* xmax <= x <= xmin.
630
+ inside = (x is not None and (x - xmin) * (x - xmax) <= 0
631
+ and y is not None and (y - ymin) * (y - ymax) <= 0)
632
+ return inside, {}
633
+
634
+ def write_png(self, fname):
635
+ """Write the image to png file *fname*."""
636
+ im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A,
637
+ bytes=True, norm=True)
638
+ PIL.Image.fromarray(im).save(fname, format="png")
639
+
640
+ @staticmethod
641
+ def _normalize_image_array(A):
642
+ """
643
+ Check validity of image-like input *A* and normalize it to a format suitable for
644
+ Image subclasses.
645
+ """
646
+ A = cbook.safe_masked_invalid(A, copy=True)
647
+ if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"):
648
+ raise TypeError(f"Image data of dtype {A.dtype} cannot be "
649
+ f"converted to float")
650
+ if A.ndim == 3 and A.shape[-1] == 1:
651
+ A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap.
652
+ if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]):
653
+ raise TypeError(f"Invalid shape {A.shape} for image data")
654
+ if A.ndim == 3:
655
+ # If the input data has values outside the valid range (after
656
+ # normalisation), we issue a warning and then clip X to the bounds
657
+ # - otherwise casting wraps extreme values, hiding outliers and
658
+ # making reliable interpretation impossible.
659
+ high = 255 if np.issubdtype(A.dtype, np.integer) else 1
660
+ if A.min() < 0 or high < A.max():
661
+ _log.warning(
662
+ 'Clipping input data to the valid range for imshow with '
663
+ 'RGB data ([0..1] for floats or [0..255] for integers). '
664
+ 'Got range [%s..%s].',
665
+ A.min(), A.max()
666
+ )
667
+ A = np.clip(A, 0, high)
668
+ # Cast unsupported integer types to uint8
669
+ if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer):
670
+ A = A.astype(np.uint8)
671
+ return A
672
+
673
+ def set_data(self, A):
674
+ """
675
+ Set the image array.
676
+
677
+ Note that this function does *not* update the normalization used.
678
+
679
+ Parameters
680
+ ----------
681
+ A : array-like or `PIL.Image.Image`
682
+ """
683
+ if isinstance(A, PIL.Image.Image):
684
+ A = pil_to_array(A) # Needed e.g. to apply png palette.
685
+ self._A = self._normalize_image_array(A)
686
+ self._imcache = None
687
+ self.stale = True
688
+
689
+ def set_array(self, A):
690
+ """
691
+ Retained for backwards compatibility - use set_data instead.
692
+
693
+ Parameters
694
+ ----------
695
+ A : array-like
696
+ """
697
+ # This also needs to be here to override the inherited
698
+ # cm.ScalarMappable.set_array method so it is not invoked by mistake.
699
+ self.set_data(A)
700
+
701
+ def get_interpolation(self):
702
+ """
703
+ Return the interpolation method the image uses when resizing.
704
+
705
+ One of 'auto', 'antialiased', 'nearest', 'bilinear', 'bicubic',
706
+ 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser',
707
+ 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
708
+ or 'none'.
709
+ """
710
+ return self._interpolation
711
+
712
+ def set_interpolation(self, s):
713
+ """
714
+ Set the interpolation method the image uses when resizing.
715
+
716
+ If None, use :rc:`image.interpolation`. If 'none', the image is
717
+ shown as is without interpolating. 'none' is only supported in
718
+ agg, ps and pdf backends and will fall back to 'nearest' mode
719
+ for other backends.
720
+
721
+ Parameters
722
+ ----------
723
+ s : {'auto', 'nearest', 'bilinear', 'bicubic', 'spline16', \
724
+ 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
725
+ 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} or None
726
+ """
727
+ s = mpl._val_or_rc(s, 'image.interpolation').lower()
728
+ _api.check_in_list(interpolations_names, interpolation=s)
729
+ self._interpolation = s
730
+ self.stale = True
731
+
732
+ def get_interpolation_stage(self):
733
+ """
734
+ Return when interpolation happens during the transform to RGBA.
735
+
736
+ One of 'data', 'rgba', 'auto'.
737
+ """
738
+ return self._interpolation_stage
739
+
740
+ def set_interpolation_stage(self, s):
741
+ """
742
+ Set when interpolation happens during the transform to RGBA.
743
+
744
+ Parameters
745
+ ----------
746
+ s : {'data', 'rgba', 'auto'} or None
747
+ Whether to apply up/downsampling interpolation in data or RGBA
748
+ space. If None, use :rc:`image.interpolation_stage`.
749
+ If 'auto' we will check upsampling rate and if less
750
+ than 3 then use 'rgba', otherwise use 'data'.
751
+ """
752
+ s = mpl._val_or_rc(s, 'image.interpolation_stage')
753
+ _api.check_in_list(['data', 'rgba', 'auto'], s=s)
754
+ self._interpolation_stage = s
755
+ self.stale = True
756
+
757
+ def can_composite(self):
758
+ """Return whether the image can be composited with its neighbors."""
759
+ trans = self.get_transform()
760
+ return (
761
+ self._interpolation != 'none' and
762
+ trans.is_affine and
763
+ trans.is_separable)
764
+
765
+ def set_resample(self, v):
766
+ """
767
+ Set whether image resampling is used.
768
+
769
+ Parameters
770
+ ----------
771
+ v : bool or None
772
+ If None, use :rc:`image.resample`.
773
+ """
774
+ v = mpl._val_or_rc(v, 'image.resample')
775
+ self._resample = v
776
+ self.stale = True
777
+
778
+ def get_resample(self):
779
+ """Return whether image resampling is used."""
780
+ return self._resample
781
+
782
+ def set_filternorm(self, filternorm):
783
+ """
784
+ Set whether the resize filter normalizes the weights.
785
+
786
+ See help for `~.Axes.imshow`.
787
+
788
+ Parameters
789
+ ----------
790
+ filternorm : bool
791
+ """
792
+ self._filternorm = bool(filternorm)
793
+ self.stale = True
794
+
795
+ def get_filternorm(self):
796
+ """Return whether the resize filter normalizes the weights."""
797
+ return self._filternorm
798
+
799
+ def set_filterrad(self, filterrad):
800
+ """
801
+ Set the resize filter radius only applicable to some
802
+ interpolation schemes -- see help for imshow
803
+
804
+ Parameters
805
+ ----------
806
+ filterrad : positive float
807
+ """
808
+ r = float(filterrad)
809
+ if r <= 0:
810
+ raise ValueError("The filter radius must be a positive number")
811
+ self._filterrad = r
812
+ self.stale = True
813
+
814
+ def get_filterrad(self):
815
+ """Return the filterrad setting."""
816
+ return self._filterrad
817
+
818
+
819
+ class AxesImage(_ImageBase):
820
+ """
821
+ An image with pixels on a regular grid, attached to an Axes.
822
+
823
+ Parameters
824
+ ----------
825
+ ax : `~matplotlib.axes.Axes`
826
+ The Axes the image will belong to.
827
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
828
+ The Colormap instance or registered colormap name used to map scalar
829
+ data to colors.
830
+ norm : str or `~matplotlib.colors.Normalize`
831
+ Maps luminance to 0-1.
832
+ interpolation : str, default: :rc:`image.interpolation`
833
+ Supported values are 'none', 'auto', 'nearest', 'bilinear',
834
+ 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
835
+ 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
836
+ 'sinc', 'lanczos', 'blackman'.
837
+ interpolation_stage : {'data', 'rgba'}, default: 'data'
838
+ If 'data', interpolation
839
+ is carried out on the data provided by the user. If 'rgba', the
840
+ interpolation is carried out after the colormapping has been
841
+ applied (visual interpolation).
842
+ origin : {'upper', 'lower'}, default: :rc:`image.origin`
843
+ Place the [0, 0] index of the array in the upper left or lower left
844
+ corner of the Axes. The convention 'upper' is typically used for
845
+ matrices and images.
846
+ extent : tuple, optional
847
+ The data axes (left, right, bottom, top) for making image plots
848
+ registered with data plots. Default is to label the pixel
849
+ centers with the zero-based row and column indices.
850
+ filternorm : bool, default: True
851
+ A parameter for the antigrain image resize filter
852
+ (see the antigrain documentation).
853
+ If filternorm is set, the filter normalizes integer values and corrects
854
+ the rounding errors. It doesn't do anything with the source floating
855
+ point values, it corrects only integers according to the rule of 1.0
856
+ which means that any sum of pixel weights must be equal to 1.0. So,
857
+ the filter function must produce a graph of the proper shape.
858
+ filterrad : float > 0, default: 4
859
+ The filter radius for filters that have a radius parameter, i.e. when
860
+ interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
861
+ resample : bool, default: False
862
+ When True, use a full resampling method. When False, only resample when
863
+ the output image is larger than the input image.
864
+ **kwargs : `~matplotlib.artist.Artist` properties
865
+ """
866
+
867
+ def __init__(self, ax,
868
+ *,
869
+ cmap=None,
870
+ norm=None,
871
+ colorizer=None,
872
+ interpolation=None,
873
+ origin=None,
874
+ extent=None,
875
+ filternorm=True,
876
+ filterrad=4.0,
877
+ resample=False,
878
+ interpolation_stage=None,
879
+ **kwargs
880
+ ):
881
+
882
+ self._extent = extent
883
+
884
+ super().__init__(
885
+ ax,
886
+ cmap=cmap,
887
+ norm=norm,
888
+ colorizer=colorizer,
889
+ interpolation=interpolation,
890
+ origin=origin,
891
+ filternorm=filternorm,
892
+ filterrad=filterrad,
893
+ resample=resample,
894
+ interpolation_stage=interpolation_stage,
895
+ **kwargs
896
+ )
897
+
898
+ def get_window_extent(self, renderer=None):
899
+ x0, x1, y0, y1 = self._extent
900
+ bbox = Bbox.from_extents([x0, y0, x1, y1])
901
+ return bbox.transformed(self.get_transform())
902
+
903
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
904
+ # docstring inherited
905
+ trans = self.get_transform()
906
+ # image is created in the canvas coordinate.
907
+ x1, x2, y1, y2 = self.get_extent()
908
+ bbox = Bbox(np.array([[x1, y1], [x2, y2]]))
909
+ transformed_bbox = TransformedBbox(bbox, trans)
910
+ clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on()
911
+ else self.get_figure(root=True).bbox)
912
+ return self._make_image(self._A, bbox, transformed_bbox, clip,
913
+ magnification, unsampled=unsampled)
914
+
915
+ def _check_unsampled_image(self):
916
+ """Return whether the image would be better drawn unsampled."""
917
+ return self.get_interpolation() == "none"
918
+
919
+ def set_extent(self, extent, **kwargs):
920
+ """
921
+ Set the image extent.
922
+
923
+ Parameters
924
+ ----------
925
+ extent : 4-tuple of float
926
+ The position and size of the image as tuple
927
+ ``(left, right, bottom, top)`` in data coordinates.
928
+ **kwargs
929
+ Other parameters from which unit info (i.e., the *xunits*,
930
+ *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for
931
+ polar Axes) entries are applied, if present.
932
+
933
+ Notes
934
+ -----
935
+ This updates `.Axes.dataLim`, and, if autoscaling, sets `.Axes.viewLim`
936
+ to tightly fit the image, regardless of `~.Axes.dataLim`. Autoscaling
937
+ state is not changed, so a subsequent call to `.Axes.autoscale_view`
938
+ will redo the autoscaling in accord with `~.Axes.dataLim`.
939
+ """
940
+ (xmin, xmax), (ymin, ymax) = self.axes._process_unit_info(
941
+ [("x", [extent[0], extent[1]]),
942
+ ("y", [extent[2], extent[3]])],
943
+ kwargs)
944
+ if kwargs:
945
+ raise _api.kwarg_error("set_extent", kwargs)
946
+ xmin = self.axes._validate_converted_limits(
947
+ xmin, self.convert_xunits)
948
+ xmax = self.axes._validate_converted_limits(
949
+ xmax, self.convert_xunits)
950
+ ymin = self.axes._validate_converted_limits(
951
+ ymin, self.convert_yunits)
952
+ ymax = self.axes._validate_converted_limits(
953
+ ymax, self.convert_yunits)
954
+ extent = [xmin, xmax, ymin, ymax]
955
+
956
+ self._extent = extent
957
+ corners = (xmin, ymin), (xmax, ymax)
958
+ self.axes.update_datalim(corners)
959
+ self.sticky_edges.x[:] = [xmin, xmax]
960
+ self.sticky_edges.y[:] = [ymin, ymax]
961
+ if self.axes.get_autoscalex_on():
962
+ self.axes.set_xlim((xmin, xmax), auto=None)
963
+ if self.axes.get_autoscaley_on():
964
+ self.axes.set_ylim((ymin, ymax), auto=None)
965
+ self.stale = True
966
+
967
+ def get_extent(self):
968
+ """Return the image extent as tuple (left, right, bottom, top)."""
969
+ if self._extent is not None:
970
+ return self._extent
971
+ else:
972
+ sz = self.get_size()
973
+ numrows, numcols = sz
974
+ if self.origin == 'upper':
975
+ return (-0.5, numcols-0.5, numrows-0.5, -0.5)
976
+ else:
977
+ return (-0.5, numcols-0.5, -0.5, numrows-0.5)
978
+
979
+ def get_cursor_data(self, event):
980
+ """
981
+ Return the image value at the event position or *None* if the event is
982
+ outside the image.
983
+
984
+ See Also
985
+ --------
986
+ matplotlib.artist.Artist.get_cursor_data
987
+ """
988
+ xmin, xmax, ymin, ymax = self.get_extent()
989
+ if self.origin == 'upper':
990
+ ymin, ymax = ymax, ymin
991
+ arr = self.get_array()
992
+ data_extent = Bbox([[xmin, ymin], [xmax, ymax]])
993
+ array_extent = Bbox([[0, 0], [arr.shape[1], arr.shape[0]]])
994
+ trans = self.get_transform().inverted()
995
+ trans += BboxTransform(boxin=data_extent, boxout=array_extent)
996
+ point = trans.transform([event.x, event.y])
997
+ if any(np.isnan(point)):
998
+ return None
999
+ j, i = point.astype(int)
1000
+ # Clip the coordinates at array bounds
1001
+ if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]):
1002
+ return None
1003
+ else:
1004
+ return arr[i, j]
1005
+
1006
+
1007
+ class NonUniformImage(AxesImage):
1008
+ """
1009
+ An image with pixels on a rectilinear grid.
1010
+
1011
+ In contrast to `.AxesImage`, where pixels are on a regular grid,
1012
+ NonUniformImage allows rows and columns with individual heights / widths.
1013
+
1014
+ See also :doc:`/gallery/images_contours_and_fields/image_nonuniform`.
1015
+ """
1016
+
1017
+ def __init__(self, ax, *, interpolation='nearest', **kwargs):
1018
+ """
1019
+ Parameters
1020
+ ----------
1021
+ ax : `~matplotlib.axes.Axes`
1022
+ The Axes the image will belong to.
1023
+ interpolation : {'nearest', 'bilinear'}, default: 'nearest'
1024
+ The interpolation scheme used in the resampling.
1025
+ **kwargs
1026
+ All other keyword arguments are identical to those of `.AxesImage`.
1027
+ """
1028
+ super().__init__(ax, **kwargs)
1029
+ self.set_interpolation(interpolation)
1030
+
1031
+ def _check_unsampled_image(self):
1032
+ """Return False. Do not use unsampled image."""
1033
+ return False
1034
+
1035
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
1036
+ # docstring inherited
1037
+ if self._A is None:
1038
+ raise RuntimeError('You must first set the image array')
1039
+ if unsampled:
1040
+ raise ValueError('unsampled not supported on NonUniformImage')
1041
+ A = self._A
1042
+ if A.ndim == 2:
1043
+ if A.dtype != np.uint8:
1044
+ A = self.to_rgba(A, bytes=True)
1045
+ else:
1046
+ A = np.repeat(A[:, :, np.newaxis], 4, 2)
1047
+ A[:, :, 3] = 255
1048
+ else:
1049
+ if A.dtype != np.uint8:
1050
+ A = (255*A).astype(np.uint8)
1051
+ if A.shape[2] == 3:
1052
+ B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8)
1053
+ B[:, :, 0:3] = A
1054
+ B[:, :, 3] = 255
1055
+ A = B
1056
+ l, b, r, t = self.axes.bbox.extents
1057
+ width = int(((round(r) + 0.5) - (round(l) - 0.5)) * magnification)
1058
+ height = int(((round(t) + 0.5) - (round(b) - 0.5)) * magnification)
1059
+
1060
+ invertedTransform = self.axes.transData.inverted()
1061
+ x_pix = invertedTransform.transform(
1062
+ [(x, b) for x in np.linspace(l, r, width)])[:, 0]
1063
+ y_pix = invertedTransform.transform(
1064
+ [(l, y) for y in np.linspace(b, t, height)])[:, 1]
1065
+
1066
+ if self._interpolation == "nearest":
1067
+ x_mid = (self._Ax[:-1] + self._Ax[1:]) / 2
1068
+ y_mid = (self._Ay[:-1] + self._Ay[1:]) / 2
1069
+ x_int = x_mid.searchsorted(x_pix)
1070
+ y_int = y_mid.searchsorted(y_pix)
1071
+ # The following is equal to `A[y_int[:, None], x_int[None, :]]`,
1072
+ # but many times faster. Both casting to uint32 (to have an
1073
+ # effectively 1D array) and manual index flattening matter.
1074
+ im = (
1075
+ np.ascontiguousarray(A).view(np.uint32).ravel()[
1076
+ np.add.outer(y_int * A.shape[1], x_int)]
1077
+ .view(np.uint8).reshape((height, width, 4)))
1078
+ else: # self._interpolation == "bilinear"
1079
+ # Use np.interp to compute x_int/x_float has similar speed.
1080
+ x_int = np.clip(
1081
+ self._Ax.searchsorted(x_pix) - 1, 0, len(self._Ax) - 2)
1082
+ y_int = np.clip(
1083
+ self._Ay.searchsorted(y_pix) - 1, 0, len(self._Ay) - 2)
1084
+ idx_int = np.add.outer(y_int * A.shape[1], x_int)
1085
+ x_frac = np.clip(
1086
+ np.divide(x_pix - self._Ax[x_int], np.diff(self._Ax)[x_int],
1087
+ dtype=np.float32), # Downcasting helps with speed.
1088
+ 0, 1)
1089
+ y_frac = np.clip(
1090
+ np.divide(y_pix - self._Ay[y_int], np.diff(self._Ay)[y_int],
1091
+ dtype=np.float32),
1092
+ 0, 1)
1093
+ f00 = np.outer(1 - y_frac, 1 - x_frac)
1094
+ f10 = np.outer(y_frac, 1 - x_frac)
1095
+ f01 = np.outer(1 - y_frac, x_frac)
1096
+ f11 = np.outer(y_frac, x_frac)
1097
+ im = np.empty((height, width, 4), np.uint8)
1098
+ for chan in range(4):
1099
+ ac = A[:, :, chan].reshape(-1) # reshape(-1) avoids a copy.
1100
+ # Shifting the buffer start (`ac[offset:]`) avoids an array
1101
+ # addition (`ac[idx_int + offset]`).
1102
+ buf = f00 * ac[idx_int]
1103
+ buf += f10 * ac[A.shape[1]:][idx_int]
1104
+ buf += f01 * ac[1:][idx_int]
1105
+ buf += f11 * ac[A.shape[1] + 1:][idx_int]
1106
+ im[:, :, chan] = buf # Implicitly casts to uint8.
1107
+ return im, l, b, IdentityTransform()
1108
+
1109
+ def set_data(self, x, y, A):
1110
+ """
1111
+ Set the grid for the pixel centers, and the pixel values.
1112
+
1113
+ Parameters
1114
+ ----------
1115
+ x, y : 1D array-like
1116
+ Monotonic arrays of shapes (N,) and (M,), respectively, specifying
1117
+ pixel centers.
1118
+ A : array-like
1119
+ (M, N) `~numpy.ndarray` or masked array of values to be
1120
+ colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array.
1121
+ """
1122
+ A = self._normalize_image_array(A)
1123
+ x = np.array(x, np.float32)
1124
+ y = np.array(y, np.float32)
1125
+ if not (x.ndim == y.ndim == 1 and A.shape[:2] == y.shape + x.shape):
1126
+ raise TypeError("Axes don't match array shape")
1127
+ self._A = A
1128
+ self._Ax = x
1129
+ self._Ay = y
1130
+ self._imcache = None
1131
+ self.stale = True
1132
+
1133
+ def set_array(self, *args):
1134
+ raise NotImplementedError('Method not supported')
1135
+
1136
+ def set_interpolation(self, s):
1137
+ """
1138
+ Parameters
1139
+ ----------
1140
+ s : {'nearest', 'bilinear'} or None
1141
+ If None, use :rc:`image.interpolation`.
1142
+ """
1143
+ if s is not None and s not in ('nearest', 'bilinear'):
1144
+ raise NotImplementedError('Only nearest neighbor and '
1145
+ 'bilinear interpolations are supported')
1146
+ super().set_interpolation(s)
1147
+
1148
+ def get_extent(self):
1149
+ if self._A is None:
1150
+ raise RuntimeError('Must set data first')
1151
+ return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1]
1152
+
1153
+ def set_filternorm(self, filternorm):
1154
+ pass
1155
+
1156
+ def set_filterrad(self, filterrad):
1157
+ pass
1158
+
1159
+ def set_norm(self, norm):
1160
+ if self._A is not None:
1161
+ raise RuntimeError('Cannot change colors after loading data')
1162
+ super().set_norm(norm)
1163
+
1164
+ def set_cmap(self, cmap):
1165
+ if self._A is not None:
1166
+ raise RuntimeError('Cannot change colors after loading data')
1167
+ super().set_cmap(cmap)
1168
+
1169
+ def get_cursor_data(self, event):
1170
+ # docstring inherited
1171
+ x, y = event.xdata, event.ydata
1172
+ if (x < self._Ax[0] or x > self._Ax[-1] or
1173
+ y < self._Ay[0] or y > self._Ay[-1]):
1174
+ return None
1175
+ j = np.searchsorted(self._Ax, x) - 1
1176
+ i = np.searchsorted(self._Ay, y) - 1
1177
+ return self._A[i, j]
1178
+
1179
+
1180
+ class PcolorImage(AxesImage):
1181
+ """
1182
+ Make a pcolor-style plot with an irregular rectangular grid.
1183
+
1184
+ This uses a variation of the original irregular image code,
1185
+ and it is used by pcolorfast for the corresponding grid type.
1186
+ """
1187
+
1188
+ def __init__(self, ax,
1189
+ x=None,
1190
+ y=None,
1191
+ A=None,
1192
+ *,
1193
+ cmap=None,
1194
+ norm=None,
1195
+ colorizer=None,
1196
+ **kwargs
1197
+ ):
1198
+ """
1199
+ Parameters
1200
+ ----------
1201
+ ax : `~matplotlib.axes.Axes`
1202
+ The Axes the image will belong to.
1203
+ x, y : 1D array-like, optional
1204
+ Monotonic arrays of length N+1 and M+1, respectively, specifying
1205
+ rectangle boundaries. If not given, will default to
1206
+ ``range(N + 1)`` and ``range(M + 1)``, respectively.
1207
+ A : array-like
1208
+ The data to be color-coded. The interpretation depends on the
1209
+ shape:
1210
+
1211
+ - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
1212
+ - (M, N, 3): RGB array
1213
+ - (M, N, 4): RGBA array
1214
+
1215
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
1216
+ The Colormap instance or registered colormap name used to map
1217
+ scalar data to colors.
1218
+ norm : str or `~matplotlib.colors.Normalize`
1219
+ Maps luminance to 0-1.
1220
+ **kwargs : `~matplotlib.artist.Artist` properties
1221
+ """
1222
+ super().__init__(ax, norm=norm, cmap=cmap, colorizer=colorizer)
1223
+ self._internal_update(kwargs)
1224
+ if A is not None:
1225
+ self.set_data(x, y, A)
1226
+
1227
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
1228
+ # docstring inherited
1229
+ if self._A is None:
1230
+ raise RuntimeError('You must first set the image array')
1231
+ if unsampled:
1232
+ raise ValueError('unsampled not supported on PColorImage')
1233
+
1234
+ if self._imcache is None:
1235
+ A = self.to_rgba(self._A, bytes=True)
1236
+ self._imcache = np.pad(A, [(1, 1), (1, 1), (0, 0)], "constant")
1237
+ padded_A = self._imcache
1238
+ bg = mcolors.to_rgba(self.axes.patch.get_facecolor(), 0)
1239
+ bg = (np.array(bg) * 255).astype(np.uint8)
1240
+ if (padded_A[0, 0] != bg).all():
1241
+ padded_A[[0, -1], :] = padded_A[:, [0, -1]] = bg
1242
+
1243
+ l, b, r, t = self.axes.bbox.extents
1244
+ width = (round(r) + 0.5) - (round(l) - 0.5)
1245
+ height = (round(t) + 0.5) - (round(b) - 0.5)
1246
+ width = round(width * magnification)
1247
+ height = round(height * magnification)
1248
+ vl = self.axes.viewLim
1249
+
1250
+ x_pix = np.linspace(vl.x0, vl.x1, width)
1251
+ y_pix = np.linspace(vl.y0, vl.y1, height)
1252
+ x_int = self._Ax.searchsorted(x_pix)
1253
+ y_int = self._Ay.searchsorted(y_pix)
1254
+ im = ( # See comment in NonUniformImage.make_image re: performance.
1255
+ padded_A.view(np.uint32).ravel()[
1256
+ np.add.outer(y_int * padded_A.shape[1], x_int)]
1257
+ .view(np.uint8).reshape((height, width, 4)))
1258
+ return im, l, b, IdentityTransform()
1259
+
1260
+ def _check_unsampled_image(self):
1261
+ return False
1262
+
1263
+ def set_data(self, x, y, A):
1264
+ """
1265
+ Set the grid for the rectangle boundaries, and the data values.
1266
+
1267
+ Parameters
1268
+ ----------
1269
+ x, y : 1D array-like, optional
1270
+ Monotonic arrays of length N+1 and M+1, respectively, specifying
1271
+ rectangle boundaries. If not given, will default to
1272
+ ``range(N + 1)`` and ``range(M + 1)``, respectively.
1273
+ A : array-like
1274
+ The data to be color-coded. The interpretation depends on the
1275
+ shape:
1276
+
1277
+ - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
1278
+ - (M, N, 3): RGB array
1279
+ - (M, N, 4): RGBA array
1280
+ """
1281
+ A = self._normalize_image_array(A)
1282
+ x = np.arange(0., A.shape[1] + 1) if x is None else np.array(x, float).ravel()
1283
+ y = np.arange(0., A.shape[0] + 1) if y is None else np.array(y, float).ravel()
1284
+ if A.shape[:2] != (y.size - 1, x.size - 1):
1285
+ raise ValueError(
1286
+ "Axes don't match array shape. Got %s, expected %s." %
1287
+ (A.shape[:2], (y.size - 1, x.size - 1)))
1288
+ # For efficient cursor readout, ensure x and y are increasing.
1289
+ if x[-1] < x[0]:
1290
+ x = x[::-1]
1291
+ A = A[:, ::-1]
1292
+ if y[-1] < y[0]:
1293
+ y = y[::-1]
1294
+ A = A[::-1]
1295
+ self._A = A
1296
+ self._Ax = x
1297
+ self._Ay = y
1298
+ self._imcache = None
1299
+ self.stale = True
1300
+
1301
+ def set_array(self, *args):
1302
+ raise NotImplementedError('Method not supported')
1303
+
1304
+ def get_cursor_data(self, event):
1305
+ # docstring inherited
1306
+ x, y = event.xdata, event.ydata
1307
+ if (x < self._Ax[0] or x > self._Ax[-1] or
1308
+ y < self._Ay[0] or y > self._Ay[-1]):
1309
+ return None
1310
+ j = np.searchsorted(self._Ax, x) - 1
1311
+ i = np.searchsorted(self._Ay, y) - 1
1312
+ return self._A[i, j]
1313
+
1314
+
1315
+ class FigureImage(_ImageBase):
1316
+ """An image attached to a figure."""
1317
+
1318
+ zorder = 0
1319
+
1320
+ _interpolation = 'nearest'
1321
+
1322
+ def __init__(self, fig,
1323
+ *,
1324
+ cmap=None,
1325
+ norm=None,
1326
+ colorizer=None,
1327
+ offsetx=0,
1328
+ offsety=0,
1329
+ origin=None,
1330
+ **kwargs
1331
+ ):
1332
+ """
1333
+ cmap is a colors.Colormap instance
1334
+ norm is a colors.Normalize instance to map luminance to 0-1
1335
+
1336
+ kwargs are an optional list of Artist keyword args
1337
+ """
1338
+ super().__init__(
1339
+ None,
1340
+ norm=norm,
1341
+ cmap=cmap,
1342
+ colorizer=colorizer,
1343
+ origin=origin
1344
+ )
1345
+ self.set_figure(fig)
1346
+ self.ox = offsetx
1347
+ self.oy = offsety
1348
+ self._internal_update(kwargs)
1349
+ self.magnification = 1.0
1350
+
1351
+ def get_extent(self):
1352
+ """Return the image extent as tuple (left, right, bottom, top)."""
1353
+ numrows, numcols = self.get_size()
1354
+ return (-0.5 + self.ox, numcols-0.5 + self.ox,
1355
+ -0.5 + self.oy, numrows-0.5 + self.oy)
1356
+
1357
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
1358
+ # docstring inherited
1359
+ fig = self.get_figure(root=True)
1360
+ fac = renderer.dpi/fig.dpi
1361
+ # fac here is to account for pdf, eps, svg backends where
1362
+ # figure.dpi is set to 72. This means we need to scale the
1363
+ # image (using magnification) and offset it appropriately.
1364
+ bbox = Bbox([[self.ox/fac, self.oy/fac],
1365
+ [(self.ox/fac + self._A.shape[1]),
1366
+ (self.oy/fac + self._A.shape[0])]])
1367
+ width, height = fig.get_size_inches()
1368
+ width *= renderer.dpi
1369
+ height *= renderer.dpi
1370
+ clip = Bbox([[0, 0], [width, height]])
1371
+ return self._make_image(
1372
+ self._A, bbox, bbox, clip, magnification=magnification / fac,
1373
+ unsampled=unsampled, round_to_pixel_border=False)
1374
+
1375
+ def set_data(self, A):
1376
+ """Set the image array."""
1377
+ super().set_data(A)
1378
+ self.stale = True
1379
+
1380
+
1381
+ class BboxImage(_ImageBase):
1382
+ """The Image class whose size is determined by the given bbox."""
1383
+
1384
+ def __init__(self, bbox,
1385
+ *,
1386
+ cmap=None,
1387
+ norm=None,
1388
+ colorizer=None,
1389
+ interpolation=None,
1390
+ origin=None,
1391
+ filternorm=True,
1392
+ filterrad=4.0,
1393
+ resample=False,
1394
+ **kwargs
1395
+ ):
1396
+ """
1397
+ cmap is a colors.Colormap instance
1398
+ norm is a colors.Normalize instance to map luminance to 0-1
1399
+
1400
+ kwargs are an optional list of Artist keyword args
1401
+ """
1402
+ super().__init__(
1403
+ None,
1404
+ cmap=cmap,
1405
+ norm=norm,
1406
+ colorizer=colorizer,
1407
+ interpolation=interpolation,
1408
+ origin=origin,
1409
+ filternorm=filternorm,
1410
+ filterrad=filterrad,
1411
+ resample=resample,
1412
+ **kwargs
1413
+ )
1414
+ self.bbox = bbox
1415
+
1416
+ def get_window_extent(self, renderer=None):
1417
+ if renderer is None:
1418
+ renderer = self.get_figure()._get_renderer()
1419
+
1420
+ if isinstance(self.bbox, BboxBase):
1421
+ return self.bbox
1422
+ elif callable(self.bbox):
1423
+ return self.bbox(renderer)
1424
+ else:
1425
+ raise ValueError("Unknown type of bbox")
1426
+
1427
+ def contains(self, mouseevent):
1428
+ """Test whether the mouse event occurred within the image."""
1429
+ if self._different_canvas(mouseevent) or not self.get_visible():
1430
+ return False, {}
1431
+ x, y = mouseevent.x, mouseevent.y
1432
+ inside = self.get_window_extent().contains(x, y)
1433
+ return inside, {}
1434
+
1435
+ def make_image(self, renderer, magnification=1.0, unsampled=False):
1436
+ # docstring inherited
1437
+ width, height = renderer.get_canvas_width_height()
1438
+ bbox_in = self.get_window_extent(renderer).frozen()
1439
+ bbox_in._points /= [width, height]
1440
+ bbox_out = self.get_window_extent(renderer)
1441
+ clip = Bbox([[0, 0], [width, height]])
1442
+ self._transform = BboxTransformTo(clip)
1443
+ return self._make_image(
1444
+ self._A,
1445
+ bbox_in, bbox_out, clip, magnification, unsampled=unsampled)
1446
+
1447
+
1448
+ def imread(fname, format=None):
1449
+ """
1450
+ Read an image from a file into an array.
1451
+
1452
+ .. note::
1453
+
1454
+ This function exists for historical reasons. It is recommended to
1455
+ use `PIL.Image.open` instead for loading images.
1456
+
1457
+ Parameters
1458
+ ----------
1459
+ fname : str or file-like
1460
+ The image file to read: a filename, a URL or a file-like object opened
1461
+ in read-binary mode.
1462
+
1463
+ Passing a URL is deprecated. Please open the URL
1464
+ for reading and pass the result to Pillow, e.g. with
1465
+ ``np.array(PIL.Image.open(urllib.request.urlopen(url)))``.
1466
+ format : str, optional
1467
+ The image file format assumed for reading the data. The image is
1468
+ loaded as a PNG file if *format* is set to "png", if *fname* is a path
1469
+ or opened file with a ".png" extension, or if it is a URL. In all
1470
+ other cases, *format* is ignored and the format is auto-detected by
1471
+ `PIL.Image.open`.
1472
+
1473
+ Returns
1474
+ -------
1475
+ `numpy.array`
1476
+ The image data. The returned array has shape
1477
+
1478
+ - (M, N) for grayscale images.
1479
+ - (M, N, 3) for RGB images.
1480
+ - (M, N, 4) for RGBA images.
1481
+
1482
+ PNG images are returned as float arrays (0-1). All other formats are
1483
+ returned as int arrays, with a bit depth determined by the file's
1484
+ contents.
1485
+ """
1486
+ # hide imports to speed initial import on systems with slow linkers
1487
+ from urllib import parse
1488
+
1489
+ if format is None:
1490
+ if isinstance(fname, str):
1491
+ parsed = parse.urlparse(fname)
1492
+ # If the string is a URL (Windows paths appear as if they have a
1493
+ # length-1 scheme), assume png.
1494
+ if len(parsed.scheme) > 1:
1495
+ ext = 'png'
1496
+ else:
1497
+ ext = Path(fname).suffix.lower()[1:]
1498
+ elif hasattr(fname, 'geturl'): # Returned by urlopen().
1499
+ # We could try to parse the url's path and use the extension, but
1500
+ # returning png is consistent with the block above. Note that this
1501
+ # if clause has to come before checking for fname.name as
1502
+ # urlopen("file:///...") also has a name attribute (with the fixed
1503
+ # value "<urllib response>").
1504
+ ext = 'png'
1505
+ elif hasattr(fname, 'name'):
1506
+ ext = Path(fname.name).suffix.lower()[1:]
1507
+ else:
1508
+ ext = 'png'
1509
+ else:
1510
+ ext = format
1511
+ img_open = (
1512
+ PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open)
1513
+ if isinstance(fname, str) and len(parse.urlparse(fname).scheme) > 1:
1514
+ # Pillow doesn't handle URLs directly.
1515
+ raise ValueError(
1516
+ "Please open the URL for reading and pass the "
1517
+ "result to Pillow, e.g. with "
1518
+ "``np.array(PIL.Image.open(urllib.request.urlopen(url)))``."
1519
+ )
1520
+ with img_open(fname) as image:
1521
+ return (_pil_png_to_float_array(image)
1522
+ if isinstance(image, PIL.PngImagePlugin.PngImageFile) else
1523
+ pil_to_array(image))
1524
+
1525
+
1526
+ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
1527
+ origin=None, dpi=100, *, metadata=None, pil_kwargs=None):
1528
+ """
1529
+ Colormap and save an array as an image file.
1530
+
1531
+ RGB(A) images are passed through. Single channel images will be
1532
+ colormapped according to *cmap* and *norm*.
1533
+
1534
+ .. note::
1535
+
1536
+ If you want to save a single channel image as gray scale please use an
1537
+ image I/O library (such as pillow, tifffile, or imageio) directly.
1538
+
1539
+ Parameters
1540
+ ----------
1541
+ fname : str or path-like or file-like
1542
+ A path or a file-like object to store the image in.
1543
+ If *format* is not set, then the output format is inferred from the
1544
+ extension of *fname*, if any, and from :rc:`savefig.format` otherwise.
1545
+ If *format* is set, it determines the output format.
1546
+ arr : array-like
1547
+ The image data. Accepts NumPy arrays or sequences
1548
+ (e.g., lists or tuples). The shape can be one of
1549
+ MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA).
1550
+ vmin, vmax : float, optional
1551
+ *vmin* and *vmax* set the color scaling for the image by fixing the
1552
+ values that map to the colormap color limits. If either *vmin*
1553
+ or *vmax* is None, that limit is determined from the *arr*
1554
+ min/max value.
1555
+ cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
1556
+ A Colormap instance or registered colormap name. The colormap
1557
+ maps scalar data to colors. It is ignored for RGB(A) data.
1558
+ format : str, optional
1559
+ The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this
1560
+ is unset is documented under *fname*.
1561
+ origin : {'upper', 'lower'}, default: :rc:`image.origin`
1562
+ Indicates whether the ``(0, 0)`` index of the array is in the upper
1563
+ left or lower left corner of the Axes.
1564
+ dpi : float
1565
+ The DPI to store in the metadata of the file. This does not affect the
1566
+ resolution of the output image. Depending on file format, this may be
1567
+ rounded to the nearest integer.
1568
+ metadata : dict, optional
1569
+ Metadata in the image file. The supported keys depend on the output
1570
+ format, see the documentation of the respective backends for more
1571
+ information.
1572
+ Currently only supported for "png", "pdf", "ps", "eps", and "svg".
1573
+ pil_kwargs : dict, optional
1574
+ Keyword arguments passed to `PIL.Image.Image.save`. If the 'pnginfo'
1575
+ key is present, it completely overrides *metadata*, including the
1576
+ default 'Software' key.
1577
+ """
1578
+ from matplotlib.figure import Figure
1579
+
1580
+ # Normalizing input (e.g., list or tuples) to NumPy array if needed
1581
+ arr = np.asanyarray(arr)
1582
+
1583
+ if isinstance(fname, os.PathLike):
1584
+ fname = os.fspath(fname)
1585
+ if format is None:
1586
+ format = (Path(fname).suffix[1:] if isinstance(fname, str)
1587
+ else mpl.rcParams["savefig.format"]).lower()
1588
+ if format in ["pdf", "ps", "eps", "svg"]:
1589
+ # Vector formats that are not handled by PIL.
1590
+ if pil_kwargs is not None:
1591
+ raise ValueError(
1592
+ f"Cannot use 'pil_kwargs' when saving to {format}")
1593
+ fig = Figure(dpi=dpi, frameon=False)
1594
+ fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin,
1595
+ resize=True)
1596
+ fig.savefig(fname, dpi=dpi, format=format, transparent=True,
1597
+ metadata=metadata)
1598
+ else:
1599
+ # Don't bother creating an image; this avoids rounding errors on the
1600
+ # size when dividing and then multiplying by dpi.
1601
+ if origin is None:
1602
+ origin = mpl.rcParams["image.origin"]
1603
+ else:
1604
+ _api.check_in_list(('upper', 'lower'), origin=origin)
1605
+ if origin == "lower":
1606
+ arr = arr[::-1]
1607
+ if (isinstance(arr, memoryview) and arr.format == "B"
1608
+ and arr.ndim == 3 and arr.shape[-1] == 4):
1609
+ # Such an ``arr`` would also be handled fine by sm.to_rgba below
1610
+ # (after casting with asarray), but it is useful to special-case it
1611
+ # because that's what backend_agg passes, and can be in fact used
1612
+ # as is, saving a few operations.
1613
+ rgba = arr
1614
+ else:
1615
+ sm = mcolorizer.Colorizer(cmap=cmap)
1616
+ sm.set_clim(vmin, vmax)
1617
+ rgba = sm.to_rgba(arr, bytes=True)
1618
+ if pil_kwargs is None:
1619
+ pil_kwargs = {}
1620
+ else:
1621
+ # we modify this below, so make a copy (don't modify caller's dict)
1622
+ pil_kwargs = pil_kwargs.copy()
1623
+ pil_shape = (rgba.shape[1], rgba.shape[0])
1624
+ rgba = np.require(rgba, requirements='C')
1625
+ image = PIL.Image.frombuffer(
1626
+ "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
1627
+ if format == "png":
1628
+ # Only use the metadata kwarg if pnginfo is not set, because the
1629
+ # semantics of duplicate keys in pnginfo is unclear.
1630
+ if "pnginfo" in pil_kwargs:
1631
+ if metadata:
1632
+ _api.warn_external("'metadata' is overridden by the "
1633
+ "'pnginfo' entry in 'pil_kwargs'.")
1634
+ else:
1635
+ metadata = {
1636
+ "Software": (f"Matplotlib version{mpl.__version__}, "
1637
+ f"https://matplotlib.org/"),
1638
+ **(metadata if metadata is not None else {}),
1639
+ }
1640
+ pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo()
1641
+ for k, v in metadata.items():
1642
+ if v is not None:
1643
+ pnginfo.add_text(k, v)
1644
+ elif metadata is not None:
1645
+ raise ValueError(f"metadata not supported for format {format!r}")
1646
+ if format in ["jpg", "jpeg"]:
1647
+ format = "jpeg" # Pillow doesn't recognize "jpg".
1648
+ facecolor = mpl.rcParams["savefig.facecolor"]
1649
+ if cbook._str_equal(facecolor, "auto"):
1650
+ facecolor = mpl.rcParams["figure.facecolor"]
1651
+ color = tuple(int(x * 255) for x in mcolors.to_rgb(facecolor))
1652
+ background = PIL.Image.new("RGB", pil_shape, color)
1653
+ background.paste(image, image)
1654
+ image = background
1655
+ pil_kwargs.setdefault("format", format)
1656
+ pil_kwargs.setdefault("dpi", (dpi, dpi))
1657
+ image.save(fname, **pil_kwargs)
1658
+
1659
+
1660
+ def pil_to_array(pilImage):
1661
+ """
1662
+ Load a `PIL image`_ and return it as a numpy int array.
1663
+
1664
+ .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html
1665
+
1666
+ Returns
1667
+ -------
1668
+ numpy.array
1669
+
1670
+ The array shape depends on the image type:
1671
+
1672
+ - (M, N) for grayscale images.
1673
+ - (M, N, 3) for RGB images.
1674
+ - (M, N, 4) for RGBA images.
1675
+ """
1676
+ if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']:
1677
+ # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array
1678
+ return np.asarray(pilImage)
1679
+ elif pilImage.mode.startswith('I;16'):
1680
+ # return MxN luminance array of uint16
1681
+ raw = pilImage.tobytes('raw', pilImage.mode)
1682
+ if pilImage.mode.endswith('B'):
1683
+ x = np.frombuffer(raw, '>u2')
1684
+ else:
1685
+ x = np.frombuffer(raw, '<u2')
1686
+ return x.reshape(pilImage.size[::-1]).astype('=u2')
1687
+ else: # try to convert to an rgba image
1688
+ try:
1689
+ pilImage = pilImage.convert('RGBA')
1690
+ except ValueError as err:
1691
+ raise RuntimeError('Unknown image mode') from err
1692
+ return np.asarray(pilImage) # return MxNx4 RGBA array
1693
+
1694
+
1695
+ def _pil_png_to_float_array(pil_png):
1696
+ """Convert a PIL `PNGImageFile` to a 0-1 float array."""
1697
+ # Unlike pil_to_array this converts to 0-1 float32s for backcompat with the
1698
+ # old libpng-based loader.
1699
+ # The supported rawmodes are from PIL.PngImagePlugin._MODES. When
1700
+ # mode == "RGB(A)", the 16-bit raw data has already been coarsened to 8-bit
1701
+ # by Pillow.
1702
+ mode = pil_png.mode
1703
+ rawmode = pil_png.png.im_rawmode
1704
+ if rawmode == "1": # Grayscale.
1705
+ return np.asarray(pil_png, np.float32)
1706
+ if rawmode == "L;2": # Grayscale.
1707
+ return np.divide(pil_png, 2**2 - 1, dtype=np.float32)
1708
+ if rawmode == "L;4": # Grayscale.
1709
+ return np.divide(pil_png, 2**4 - 1, dtype=np.float32)
1710
+ if rawmode == "L": # Grayscale.
1711
+ return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1712
+ if rawmode == "I;16B": # Grayscale.
1713
+ return np.divide(pil_png, 2**16 - 1, dtype=np.float32)
1714
+ if mode == "RGB": # RGB.
1715
+ return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1716
+ if mode == "P": # Palette.
1717
+ return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32)
1718
+ if mode == "LA": # Grayscale + alpha.
1719
+ return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32)
1720
+ if mode == "RGBA": # RGBA.
1721
+ return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1722
+ raise ValueError(f"Unknown PIL rawmode: {rawmode}")
1723
+
1724
+
1725
+ def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear',
1726
+ preview=False):
1727
+ """
1728
+ Make a thumbnail of image in *infile* with output filename *thumbfile*.
1729
+
1730
+ See :doc:`/gallery/misc/image_thumbnail_sgskip`.
1731
+
1732
+ Parameters
1733
+ ----------
1734
+ infile : str or file-like
1735
+ The image file. Matplotlib relies on Pillow_ for image reading, and
1736
+ thus supports a wide range of file formats, including PNG, JPG, TIFF
1737
+ and others.
1738
+
1739
+ .. _Pillow: https://python-pillow.github.io
1740
+
1741
+ thumbfile : str or file-like
1742
+ The thumbnail filename.
1743
+
1744
+ scale : float, default: 0.1
1745
+ The scale factor for the thumbnail.
1746
+
1747
+ interpolation : str, default: 'bilinear'
1748
+ The interpolation scheme used in the resampling. See the
1749
+ *interpolation* parameter of `~.Axes.imshow` for possible values.
1750
+
1751
+ preview : bool, default: False
1752
+ If True, the default backend (presumably a user interface
1753
+ backend) will be used which will cause a figure to be raised if
1754
+ `~matplotlib.pyplot.show` is called. If it is False, the figure is
1755
+ created using `.FigureCanvasBase` and the drawing backend is selected
1756
+ as `.Figure.savefig` would normally do.
1757
+
1758
+ Returns
1759
+ -------
1760
+ `.Figure`
1761
+ The figure instance containing the thumbnail.
1762
+ """
1763
+
1764
+ im = imread(infile)
1765
+ rows, cols, depth = im.shape
1766
+
1767
+ # This doesn't really matter (it cancels in the end) but the API needs it.
1768
+ dpi = 100
1769
+
1770
+ height = rows / dpi * scale
1771
+ width = cols / dpi * scale
1772
+
1773
+ if preview:
1774
+ # Let the UI backend do everything.
1775
+ import matplotlib.pyplot as plt
1776
+ fig = plt.figure(figsize=(width, height), dpi=dpi)
1777
+ else:
1778
+ from matplotlib.figure import Figure
1779
+ fig = Figure(figsize=(width, height), dpi=dpi)
1780
+ FigureCanvasBase(fig)
1781
+
1782
+ ax = fig.add_axes([0, 0, 1, 1], aspect='auto',
1783
+ frameon=False, xticks=[], yticks=[])
1784
+ ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation)
1785
+ fig.savefig(thumbfile, dpi=dpi)
1786
+ return fig
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.pyi ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable, Sequence
2
+ import os
3
+ import pathlib
4
+ from typing import Any, BinaryIO, Literal
5
+
6
+ import numpy as np
7
+ from numpy.typing import ArrayLike, NDArray
8
+ import PIL.Image
9
+
10
+ from matplotlib.axes import Axes
11
+ from matplotlib import colorizer
12
+ from matplotlib.backend_bases import RendererBase, MouseEvent
13
+ from matplotlib.colorizer import Colorizer
14
+ from matplotlib.colors import Colormap, Normalize
15
+ from matplotlib.figure import Figure
16
+ from matplotlib.transforms import Affine2D, BboxBase, Bbox, Transform
17
+
18
+ #
19
+ # These names are re-exported from matplotlib._image.
20
+ #
21
+
22
+ BESSEL: int
23
+ BICUBIC: int
24
+ BILINEAR: int
25
+ BLACKMAN: int
26
+ CATROM: int
27
+ GAUSSIAN: int
28
+ HAMMING: int
29
+ HANNING: int
30
+ HERMITE: int
31
+ KAISER: int
32
+ LANCZOS: int
33
+ MITCHELL: int
34
+ NEAREST: int
35
+ QUADRIC: int
36
+ SINC: int
37
+ SPLINE16: int
38
+ SPLINE36: int
39
+
40
+ def resample(
41
+ input_array: NDArray[np.float32] | NDArray[np.float64] | NDArray[np.int8],
42
+ output_array: NDArray[np.float32] | NDArray[np.float64] | NDArray[np.int8],
43
+ transform: Transform,
44
+ interpolation: int = ...,
45
+ resample: bool = ...,
46
+ alpha: float = ...,
47
+ norm: bool = ...,
48
+ radius: float = ...,
49
+ ) -> None: ...
50
+
51
+ #
52
+ # END names re-exported from matplotlib._image.
53
+ #
54
+
55
+ interpolations_names: set[str]
56
+
57
+ def composite_images(
58
+ images: Sequence[_ImageBase], renderer: RendererBase, magnification: float = ...
59
+ ) -> tuple[np.ndarray, float, float]: ...
60
+
61
+ class _ImageBase(colorizer.ColorizingArtist):
62
+ zorder: float
63
+ origin: Literal["upper", "lower"]
64
+ axes: Axes
65
+ def __init__(
66
+ self,
67
+ ax: Axes,
68
+ cmap: str | Colormap | None = ...,
69
+ norm: str | Normalize | None = ...,
70
+ colorizer: Colorizer | None = ...,
71
+ interpolation: str | None = ...,
72
+ origin: Literal["upper", "lower"] | None = ...,
73
+ filternorm: bool = ...,
74
+ filterrad: float = ...,
75
+ resample: bool | None = ...,
76
+ *,
77
+ interpolation_stage: Literal["data", "rgba", "auto"] | None = ...,
78
+ **kwargs
79
+ ) -> None: ...
80
+ def get_size(self) -> tuple[int, int]: ...
81
+ def set_alpha(self, alpha: float | ArrayLike | None) -> None: ...
82
+ def changed(self) -> None: ...
83
+ def make_image(
84
+ self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ...
85
+ ) -> tuple[np.ndarray, float, float, Affine2D]: ...
86
+ def draw(self, renderer: RendererBase) -> None: ...
87
+ def write_png(self, fname: str | pathlib.Path | BinaryIO) -> None: ...
88
+ def set_data(self, A: ArrayLike | None) -> None: ...
89
+ def set_array(self, A: ArrayLike | None) -> None: ...
90
+ def get_shape(self) -> tuple[int, int, int]: ...
91
+ def get_interpolation(self) -> str: ...
92
+ def set_interpolation(self, s: str | None) -> None: ...
93
+ def get_interpolation_stage(self) -> Literal["data", "rgba", "auto"]: ...
94
+ def set_interpolation_stage(self, s: Literal["data", "rgba", "auto"]) -> None: ...
95
+ def can_composite(self) -> bool: ...
96
+ def set_resample(self, v: bool | None) -> None: ...
97
+ def get_resample(self) -> bool: ...
98
+ def set_filternorm(self, filternorm: bool) -> None: ...
99
+ def get_filternorm(self) -> bool: ...
100
+ def set_filterrad(self, filterrad: float) -> None: ...
101
+ def get_filterrad(self) -> float: ...
102
+
103
+ class AxesImage(_ImageBase):
104
+ def __init__(
105
+ self,
106
+ ax: Axes,
107
+ *,
108
+ cmap: str | Colormap | None = ...,
109
+ norm: str | Normalize | None = ...,
110
+ colorizer: Colorizer | None = ...,
111
+ interpolation: str | None = ...,
112
+ origin: Literal["upper", "lower"] | None = ...,
113
+ extent: tuple[float, float, float, float] | None = ...,
114
+ filternorm: bool = ...,
115
+ filterrad: float = ...,
116
+ resample: bool = ...,
117
+ interpolation_stage: Literal["data", "rgba", "auto"] | None = ...,
118
+ **kwargs
119
+ ) -> None: ...
120
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
121
+ def make_image(
122
+ self, renderer: RendererBase, magnification: float = ..., unsampled: bool = ...
123
+ ) -> tuple[np.ndarray, float, float, Affine2D]: ...
124
+ def set_extent(
125
+ self, extent: tuple[float, float, float, float], **kwargs
126
+ ) -> None: ...
127
+ def get_extent(self) -> tuple[float, float, float, float]: ...
128
+ def get_cursor_data(self, event: MouseEvent) -> None | float: ...
129
+
130
+ class NonUniformImage(AxesImage):
131
+ mouseover: bool
132
+ def __init__(
133
+ self, ax: Axes, *, interpolation: Literal["nearest", "bilinear"] = ..., **kwargs
134
+ ) -> None: ...
135
+ def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override]
136
+ # more limited interpolation available here than base class
137
+ def set_interpolation(self, s: Literal["nearest", "bilinear"]) -> None: ... # type: ignore[override]
138
+
139
+ class PcolorImage(AxesImage):
140
+ def __init__(
141
+ self,
142
+ ax: Axes,
143
+ x: ArrayLike | None = ...,
144
+ y: ArrayLike | None = ...,
145
+ A: ArrayLike | None = ...,
146
+ *,
147
+ cmap: str | Colormap | None = ...,
148
+ norm: str | Normalize | None = ...,
149
+ colorizer: Colorizer | None = ...,
150
+ **kwargs
151
+ ) -> None: ...
152
+ def set_data(self, x: ArrayLike, y: ArrayLike, A: ArrayLike) -> None: ... # type: ignore[override]
153
+
154
+ class FigureImage(_ImageBase):
155
+ zorder: float
156
+ figure: Figure
157
+ ox: float
158
+ oy: float
159
+ magnification: float
160
+ def __init__(
161
+ self,
162
+ fig: Figure,
163
+ *,
164
+ cmap: str | Colormap | None = ...,
165
+ norm: str | Normalize | None = ...,
166
+ colorizer: Colorizer | None = ...,
167
+ offsetx: int = ...,
168
+ offsety: int = ...,
169
+ origin: Literal["upper", "lower"] | None = ...,
170
+ **kwargs
171
+ ) -> None: ...
172
+ def get_extent(self) -> tuple[float, float, float, float]: ...
173
+
174
+ class BboxImage(_ImageBase):
175
+ bbox: BboxBase
176
+ def __init__(
177
+ self,
178
+ bbox: BboxBase | Callable[[RendererBase | None], Bbox],
179
+ *,
180
+ cmap: str | Colormap | None = ...,
181
+ norm: str | Normalize | None = ...,
182
+ colorizer: Colorizer | None = ...,
183
+ interpolation: str | None = ...,
184
+ origin: Literal["upper", "lower"] | None = ...,
185
+ filternorm: bool = ...,
186
+ filterrad: float = ...,
187
+ resample: bool = ...,
188
+ **kwargs
189
+ ) -> None: ...
190
+ def get_window_extent(self, renderer: RendererBase | None = ...) -> Bbox: ...
191
+
192
+ def imread(
193
+ fname: str | pathlib.Path | BinaryIO, format: str | None = ...
194
+ ) -> np.ndarray: ...
195
+ def imsave(
196
+ fname: str | os.PathLike | BinaryIO,
197
+ arr: ArrayLike,
198
+ vmin: float | None = ...,
199
+ vmax: float | None = ...,
200
+ cmap: str | Colormap | None = ...,
201
+ format: str | None = ...,
202
+ origin: Literal["upper", "lower"] | None = ...,
203
+ dpi: float = ...,
204
+ *,
205
+ metadata: dict[str, str] | None = ...,
206
+ pil_kwargs: dict[str, Any] | None = ...
207
+ ) -> None: ...
208
+ def pil_to_array(pilImage: PIL.Image.Image) -> np.ndarray: ...
209
+ def thumbnail(
210
+ infile: str | BinaryIO,
211
+ thumbfile: str | BinaryIO,
212
+ scale: float = ...,
213
+ interpolation: str = ...,
214
+ preview: bool = ...,
215
+ ) -> Figure: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The inset module defines the InsetIndicator class, which draws the rectangle and
3
+ connectors required for `.Axes.indicate_inset` and `.Axes.indicate_inset_zoom`.
4
+ """
5
+
6
+ from . import _api, artist, transforms
7
+ from matplotlib.patches import ConnectionPatch, PathPatch, Rectangle
8
+ from matplotlib.path import Path
9
+
10
+
11
+ _shared_properties = ('alpha', 'edgecolor', 'linestyle', 'linewidth')
12
+
13
+
14
+ class InsetIndicator(artist.Artist):
15
+ """
16
+ An artist to highlight an area of interest.
17
+
18
+ An inset indicator is a rectangle on the plot at the position indicated by
19
+ *bounds* that optionally has lines that connect the rectangle to an inset
20
+ Axes (`.Axes.inset_axes`).
21
+
22
+ .. versionadded:: 3.10
23
+ """
24
+ zorder = 4.99
25
+
26
+ def __init__(self, bounds=None, inset_ax=None, zorder=None, **kwargs):
27
+ """
28
+ Parameters
29
+ ----------
30
+ bounds : [x0, y0, width, height], optional
31
+ Lower-left corner of rectangle to be marked, and its width
32
+ and height. If not set, the bounds will be calculated from the
33
+ data limits of inset_ax, which must be supplied.
34
+
35
+ inset_ax : `~.axes.Axes`, optional
36
+ An optional inset Axes to draw connecting lines to. Two lines are
37
+ drawn connecting the indicator box to the inset Axes on corners
38
+ chosen so as to not overlap with the indicator box.
39
+
40
+ zorder : float, default: 4.99
41
+ Drawing order of the rectangle and connector lines. The default,
42
+ 4.99, is just below the default level of inset Axes.
43
+
44
+ **kwargs
45
+ Other keyword arguments are passed on to the `.Rectangle` patch.
46
+ """
47
+ if bounds is None and inset_ax is None:
48
+ raise ValueError("At least one of bounds or inset_ax must be supplied")
49
+
50
+ self._inset_ax = inset_ax
51
+
52
+ if bounds is None:
53
+ # Work out bounds from inset_ax
54
+ self._auto_update_bounds = True
55
+ bounds = self._bounds_from_inset_ax()
56
+ else:
57
+ self._auto_update_bounds = False
58
+
59
+ x, y, width, height = bounds
60
+
61
+ self._rectangle = Rectangle((x, y), width, height, clip_on=False, **kwargs)
62
+
63
+ # Connector positions cannot be calculated till the artist has been added
64
+ # to an axes, so just make an empty list for now.
65
+ self._connectors = []
66
+
67
+ super().__init__()
68
+ self.set_zorder(zorder)
69
+
70
+ # Initial style properties for the artist should match the rectangle.
71
+ for prop in _shared_properties:
72
+ setattr(self, f'_{prop}', artist.getp(self._rectangle, prop))
73
+
74
+ def _shared_setter(self, prop, val):
75
+ """
76
+ Helper function to set the same style property on the artist and its children.
77
+ """
78
+ setattr(self, f'_{prop}', val)
79
+
80
+ artist.setp([self._rectangle, *self._connectors], prop, val)
81
+
82
+ def set_alpha(self, alpha):
83
+ # docstring inherited
84
+ self._shared_setter('alpha', alpha)
85
+
86
+ def set_edgecolor(self, color):
87
+ """
88
+ Set the edge color of the rectangle and the connectors.
89
+
90
+ Parameters
91
+ ----------
92
+ color : :mpltype:`color` or None
93
+ """
94
+ self._shared_setter('edgecolor', color)
95
+
96
+ def set_color(self, c):
97
+ """
98
+ Set the edgecolor of the rectangle and the connectors, and the
99
+ facecolor for the rectangle.
100
+
101
+ Parameters
102
+ ----------
103
+ c : :mpltype:`color`
104
+ """
105
+ self._shared_setter('edgecolor', c)
106
+ self._shared_setter('facecolor', c)
107
+
108
+ def set_linewidth(self, w):
109
+ """
110
+ Set the linewidth in points of the rectangle and the connectors.
111
+
112
+ Parameters
113
+ ----------
114
+ w : float or None
115
+ """
116
+ self._shared_setter('linewidth', w)
117
+
118
+ def set_linestyle(self, ls):
119
+ """
120
+ Set the linestyle of the rectangle and the connectors.
121
+
122
+ ========================================== =================
123
+ linestyle description
124
+ ========================================== =================
125
+ ``'-'`` or ``'solid'`` solid line
126
+ ``'--'`` or ``'dashed'`` dashed line
127
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
128
+ ``':'`` or ``'dotted'`` dotted line
129
+ ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
130
+ ========================================== =================
131
+
132
+ Alternatively a dash tuple of the following form can be provided::
133
+
134
+ (offset, onoffseq)
135
+
136
+ where ``onoffseq`` is an even length tuple of on and off ink in points.
137
+
138
+ Parameters
139
+ ----------
140
+ ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
141
+ The line style.
142
+ """
143
+ self._shared_setter('linestyle', ls)
144
+
145
+ def _bounds_from_inset_ax(self):
146
+ xlim = self._inset_ax.get_xlim()
147
+ ylim = self._inset_ax.get_ylim()
148
+ return (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0])
149
+
150
+ def _update_connectors(self):
151
+ (x, y) = self._rectangle.get_xy()
152
+ width = self._rectangle.get_width()
153
+ height = self._rectangle.get_height()
154
+
155
+ existing_connectors = self._connectors or [None] * 4
156
+
157
+ # connect the inset_axes to the rectangle
158
+ for xy_inset_ax, existing in zip([(0, 0), (0, 1), (1, 0), (1, 1)],
159
+ existing_connectors):
160
+ # inset_ax positions are in axes coordinates
161
+ # The 0, 1 values define the four edges if the inset_ax
162
+ # lower_left, upper_left, lower_right upper_right.
163
+ ex, ey = xy_inset_ax
164
+ if self.axes.xaxis.get_inverted():
165
+ ex = 1 - ex
166
+ if self.axes.yaxis.get_inverted():
167
+ ey = 1 - ey
168
+ xy_data = x + ex * width, y + ey * height
169
+ if existing is None:
170
+ # Create new connection patch with styles inherited from the
171
+ # parent artist.
172
+ p = ConnectionPatch(
173
+ xyA=xy_inset_ax, coordsA=self._inset_ax.transAxes,
174
+ xyB=xy_data, coordsB=self.axes.transData,
175
+ arrowstyle="-",
176
+ edgecolor=self._edgecolor, alpha=self.get_alpha(),
177
+ linestyle=self._linestyle, linewidth=self._linewidth)
178
+ self._connectors.append(p)
179
+ else:
180
+ # Only update positioning of existing connection patch. We
181
+ # do not want to override any style settings made by the user.
182
+ existing.xy1 = xy_inset_ax
183
+ existing.xy2 = xy_data
184
+ existing.coords1 = self._inset_ax.transAxes
185
+ existing.coords2 = self.axes.transData
186
+
187
+ if existing is None:
188
+ # decide which two of the lines to keep visible....
189
+ pos = self._inset_ax.get_position()
190
+ bboxins = pos.transformed(self.get_figure(root=False).transSubfigure)
191
+ rectbbox = transforms.Bbox.from_bounds(x, y, width, height).transformed(
192
+ self._rectangle.get_transform())
193
+ x0 = rectbbox.x0 < bboxins.x0
194
+ x1 = rectbbox.x1 < bboxins.x1
195
+ y0 = rectbbox.y0 < bboxins.y0
196
+ y1 = rectbbox.y1 < bboxins.y1
197
+ self._connectors[0].set_visible(x0 ^ y0)
198
+ self._connectors[1].set_visible(x0 == y1)
199
+ self._connectors[2].set_visible(x1 == y0)
200
+ self._connectors[3].set_visible(x1 ^ y1)
201
+
202
+ @property
203
+ def rectangle(self):
204
+ """`.Rectangle`: the indicator frame."""
205
+ return self._rectangle
206
+
207
+ @property
208
+ def connectors(self):
209
+ """
210
+ 4-tuple of `.patches.ConnectionPatch` or None
211
+ The four connector lines connecting to (lower_left, upper_left,
212
+ lower_right upper_right) corners of *inset_ax*. Two lines are
213
+ set with visibility to *False*, but the user can set the
214
+ visibility to True if the automatic choice is not deemed correct.
215
+ """
216
+ if self._inset_ax is None:
217
+ return
218
+
219
+ if self._auto_update_bounds:
220
+ self._rectangle.set_bounds(self._bounds_from_inset_ax())
221
+ self._update_connectors()
222
+ return tuple(self._connectors)
223
+
224
+ def draw(self, renderer):
225
+ # docstring inherited
226
+ conn_same_style = []
227
+
228
+ # Figure out which connectors have the same style as the box, so should
229
+ # be drawn as a single path.
230
+ for conn in self.connectors or []:
231
+ if conn.get_visible():
232
+ drawn = False
233
+ for s in _shared_properties:
234
+ if artist.getp(self._rectangle, s) != artist.getp(conn, s):
235
+ # Draw this connector by itself
236
+ conn.draw(renderer)
237
+ drawn = True
238
+ break
239
+
240
+ if not drawn:
241
+ # Connector has same style as box.
242
+ conn_same_style.append(conn)
243
+
244
+ if conn_same_style:
245
+ # Since at least one connector has the same style as the rectangle, draw
246
+ # them as a compound path.
247
+ artists = [self._rectangle] + conn_same_style
248
+ paths = [a.get_transform().transform_path(a.get_path()) for a in artists]
249
+ path = Path.make_compound_path(*paths)
250
+
251
+ # Create a temporary patch to draw the path.
252
+ p = PathPatch(path)
253
+ p.update_from(self._rectangle)
254
+ p.set_transform(transforms.IdentityTransform())
255
+ p.draw(renderer)
256
+
257
+ return
258
+
259
+ # Just draw the rectangle
260
+ self._rectangle.draw(renderer)
261
+
262
+ @_api.deprecated(
263
+ '3.10',
264
+ message=('Since Matplotlib 3.10 indicate_inset_[zoom] returns a single '
265
+ 'InsetIndicator artist with a rectangle property and a connectors '
266
+ 'property. From 3.12 it will no longer be possible to unpack the '
267
+ 'return value into two elements.'))
268
+ def __getitem__(self, key):
269
+ return [self._rectangle, self.connectors][key]
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from . import artist
2
+ from .axes import Axes
3
+ from .backend_bases import RendererBase
4
+ from .patches import ConnectionPatch, Rectangle
5
+
6
+ from .typing import ColorType, LineStyleType
7
+
8
+ class InsetIndicator(artist.Artist):
9
+ def __init__(
10
+ self,
11
+ bounds: tuple[float, float, float, float] | None = ...,
12
+ inset_ax: Axes | None = ...,
13
+ zorder: float | None = ...,
14
+ **kwargs
15
+ ) -> None: ...
16
+ def set_alpha(self, alpha: float | None) -> None: ...
17
+ def set_edgecolor(self, color: ColorType | None) -> None: ...
18
+ def set_color(self, c: ColorType | None) -> None: ...
19
+ def set_linewidth(self, w: float | None) -> None: ...
20
+ def set_linestyle(self, ls: LineStyleType | None) -> None: ...
21
+ @property
22
+ def rectangle(self) -> Rectangle: ...
23
+ @property
24
+ def connectors(self) -> tuple[ConnectionPatch, ConnectionPatch, ConnectionPatch, ConnectionPatch] | None: ...
25
+ def draw(self, renderer: RendererBase) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Classes to layout elements in a `.Figure`.
3
+
4
+ Figures have a ``layout_engine`` property that holds a subclass of
5
+ `~.LayoutEngine` defined here (or *None* for no layout). At draw time
6
+ ``figure.get_layout_engine().execute()`` is called, the goal of which is
7
+ usually to rearrange Axes on the figure to produce a pleasing layout. This is
8
+ like a ``draw`` callback but with two differences. First, when printing we
9
+ disable the layout engine for the final draw. Second, it is useful to know the
10
+ layout engine while the figure is being created. In particular, colorbars are
11
+ made differently with different layout engines (for historical reasons).
12
+
13
+ Matplotlib has two built-in layout engines:
14
+
15
+ - `.TightLayoutEngine` was the first layout engine added to Matplotlib.
16
+ See also :ref:`tight_layout_guide`.
17
+ - `.ConstrainedLayoutEngine` is more modern and generally gives better results.
18
+ See also :ref:`constrainedlayout_guide`.
19
+
20
+ Third parties can create their own layout engine by subclassing `.LayoutEngine`.
21
+ """
22
+
23
+ from contextlib import nullcontext
24
+
25
+ import matplotlib as mpl
26
+
27
+ from matplotlib._constrained_layout import do_constrained_layout
28
+ from matplotlib._tight_layout import (get_subplotspec_list,
29
+ get_tight_layout_figure)
30
+
31
+
32
+ class LayoutEngine:
33
+ """
34
+ Base class for Matplotlib layout engines.
35
+
36
+ A layout engine can be passed to a figure at instantiation or at any time
37
+ with `~.figure.Figure.set_layout_engine`. Once attached to a figure, the
38
+ layout engine ``execute`` function is called at draw time by
39
+ `~.figure.Figure.draw`, providing a special draw-time hook.
40
+
41
+ .. note::
42
+
43
+ However, note that layout engines affect the creation of colorbars, so
44
+ `~.figure.Figure.set_layout_engine` should be called before any
45
+ colorbars are created.
46
+
47
+ Currently, there are two properties of `LayoutEngine` classes that are
48
+ consulted while manipulating the figure:
49
+
50
+ - ``engine.colorbar_gridspec`` tells `.Figure.colorbar` whether to make the
51
+ axes using the gridspec method (see `.colorbar.make_axes_gridspec`) or
52
+ not (see `.colorbar.make_axes`);
53
+ - ``engine.adjust_compatible`` stops `.Figure.subplots_adjust` from being
54
+ run if it is not compatible with the layout engine.
55
+
56
+ To implement a custom `LayoutEngine`:
57
+
58
+ 1. override ``_adjust_compatible`` and ``_colorbar_gridspec``
59
+ 2. override `LayoutEngine.set` to update *self._params*
60
+ 3. override `LayoutEngine.execute` with your implementation
61
+
62
+ """
63
+ # override these in subclass
64
+ _adjust_compatible = None
65
+ _colorbar_gridspec = None
66
+
67
+ def __init__(self, **kwargs):
68
+ super().__init__(**kwargs)
69
+ self._params = {}
70
+
71
+ def set(self, **kwargs):
72
+ """
73
+ Set the parameters for the layout engine.
74
+ """
75
+ raise NotImplementedError
76
+
77
+ @property
78
+ def colorbar_gridspec(self):
79
+ """
80
+ Return a boolean if the layout engine creates colorbars using a
81
+ gridspec.
82
+ """
83
+ if self._colorbar_gridspec is None:
84
+ raise NotImplementedError
85
+ return self._colorbar_gridspec
86
+
87
+ @property
88
+ def adjust_compatible(self):
89
+ """
90
+ Return a boolean if the layout engine is compatible with
91
+ `~.Figure.subplots_adjust`.
92
+ """
93
+ if self._adjust_compatible is None:
94
+ raise NotImplementedError
95
+ return self._adjust_compatible
96
+
97
+ def get(self):
98
+ """
99
+ Return copy of the parameters for the layout engine.
100
+ """
101
+ return dict(self._params)
102
+
103
+ def execute(self, fig):
104
+ """
105
+ Execute the layout on the figure given by *fig*.
106
+ """
107
+ # subclasses must implement this.
108
+ raise NotImplementedError
109
+
110
+
111
+ class PlaceHolderLayoutEngine(LayoutEngine):
112
+ """
113
+ This layout engine does not adjust the figure layout at all.
114
+
115
+ The purpose of this `.LayoutEngine` is to act as a placeholder when the user removes
116
+ a layout engine to ensure an incompatible `.LayoutEngine` cannot be set later.
117
+
118
+ Parameters
119
+ ----------
120
+ adjust_compatible, colorbar_gridspec : bool
121
+ Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever
122
+ layout engine it is replacing.
123
+
124
+ """
125
+ def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs):
126
+ self._adjust_compatible = adjust_compatible
127
+ self._colorbar_gridspec = colorbar_gridspec
128
+ super().__init__(**kwargs)
129
+
130
+ def execute(self, fig):
131
+ """
132
+ Do nothing.
133
+ """
134
+ return
135
+
136
+
137
+ class TightLayoutEngine(LayoutEngine):
138
+ """
139
+ Implements the ``tight_layout`` geometry management. See
140
+ :ref:`tight_layout_guide` for details.
141
+ """
142
+ _adjust_compatible = True
143
+ _colorbar_gridspec = True
144
+
145
+ def __init__(self, *, pad=1.08, h_pad=None, w_pad=None,
146
+ rect=(0, 0, 1, 1), **kwargs):
147
+ """
148
+ Initialize tight_layout engine.
149
+
150
+ Parameters
151
+ ----------
152
+ pad : float, default: 1.08
153
+ Padding between the figure edge and the edges of subplots, as a
154
+ fraction of the font size.
155
+ h_pad, w_pad : float
156
+ Padding (height/width) between edges of adjacent subplots.
157
+ Defaults to *pad*.
158
+ rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1).
159
+ rectangle in normalized figure coordinates that the subplots
160
+ (including labels) will fit into.
161
+ """
162
+ super().__init__(**kwargs)
163
+ for td in ['pad', 'h_pad', 'w_pad', 'rect']:
164
+ # initialize these in case None is passed in above:
165
+ self._params[td] = None
166
+ self.set(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
167
+
168
+ def execute(self, fig):
169
+ """
170
+ Execute tight_layout.
171
+
172
+ This decides the subplot parameters given the padding that
173
+ will allow the Axes labels to not be covered by other labels
174
+ and Axes.
175
+
176
+ Parameters
177
+ ----------
178
+ fig : `.Figure` to perform layout on.
179
+
180
+ See Also
181
+ --------
182
+ .figure.Figure.tight_layout
183
+ .pyplot.tight_layout
184
+ """
185
+ info = self._params
186
+ renderer = fig._get_renderer()
187
+ with getattr(renderer, "_draw_disabled", nullcontext)():
188
+ kwargs = get_tight_layout_figure(
189
+ fig, fig.axes, get_subplotspec_list(fig.axes), renderer,
190
+ pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'],
191
+ rect=info['rect'])
192
+ if kwargs:
193
+ fig.subplots_adjust(**kwargs)
194
+
195
+ def set(self, *, pad=None, w_pad=None, h_pad=None, rect=None):
196
+ """
197
+ Set the pads for tight_layout.
198
+
199
+ Parameters
200
+ ----------
201
+ pad : float
202
+ Padding between the figure edge and the edges of subplots, as a
203
+ fraction of the font size.
204
+ w_pad, h_pad : float
205
+ Padding (width/height) between edges of adjacent subplots.
206
+ Defaults to *pad*.
207
+ rect : tuple (left, bottom, right, top)
208
+ rectangle in normalized figure coordinates that the subplots
209
+ (including labels) will fit into.
210
+ """
211
+ for td in self.set.__kwdefaults__:
212
+ if locals()[td] is not None:
213
+ self._params[td] = locals()[td]
214
+
215
+
216
+ class ConstrainedLayoutEngine(LayoutEngine):
217
+ """
218
+ Implements the ``constrained_layout`` geometry management. See
219
+ :ref:`constrainedlayout_guide` for details.
220
+ """
221
+
222
+ _adjust_compatible = False
223
+ _colorbar_gridspec = False
224
+
225
+ def __init__(self, *, h_pad=None, w_pad=None,
226
+ hspace=None, wspace=None, rect=(0, 0, 1, 1),
227
+ compress=False, **kwargs):
228
+ """
229
+ Initialize ``constrained_layout`` settings.
230
+
231
+ Parameters
232
+ ----------
233
+ h_pad, w_pad : float
234
+ Padding around the Axes elements in inches.
235
+ Default to :rc:`figure.constrained_layout.h_pad` and
236
+ :rc:`figure.constrained_layout.w_pad`.
237
+ hspace, wspace : float
238
+ Fraction of the figure to dedicate to space between the
239
+ axes. These are evenly spread between the gaps between the Axes.
240
+ A value of 0.2 for a three-column layout would have a space
241
+ of 0.1 of the figure width between each column.
242
+ If h/wspace < h/w_pad, then the pads are used instead.
243
+ Default to :rc:`figure.constrained_layout.hspace` and
244
+ :rc:`figure.constrained_layout.wspace`.
245
+ rect : tuple of 4 floats
246
+ Rectangle in figure coordinates to perform constrained layout in
247
+ (left, bottom, width, height), each from 0-1.
248
+ compress : bool
249
+ Whether to shift Axes so that white space in between them is
250
+ removed. This is useful for simple grids of fixed-aspect Axes (e.g.
251
+ a grid of images). See :ref:`compressed_layout`.
252
+ """
253
+ super().__init__(**kwargs)
254
+ # set the defaults:
255
+ self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'],
256
+ h_pad=mpl.rcParams['figure.constrained_layout.h_pad'],
257
+ wspace=mpl.rcParams['figure.constrained_layout.wspace'],
258
+ hspace=mpl.rcParams['figure.constrained_layout.hspace'],
259
+ rect=(0, 0, 1, 1))
260
+ # set anything that was passed in (None will be ignored):
261
+ self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace,
262
+ rect=rect)
263
+ self._compress = compress
264
+
265
+ def execute(self, fig):
266
+ """
267
+ Perform constrained_layout and move and resize Axes accordingly.
268
+
269
+ Parameters
270
+ ----------
271
+ fig : `.Figure` to perform layout on.
272
+ """
273
+ width, height = fig.get_size_inches()
274
+ # pads are relative to the current state of the figure...
275
+ w_pad = self._params['w_pad'] / width
276
+ h_pad = self._params['h_pad'] / height
277
+
278
+ return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279
+ wspace=self._params['wspace'],
280
+ hspace=self._params['hspace'],
281
+ rect=self._params['rect'],
282
+ compress=self._compress)
283
+
284
+ def set(self, *, h_pad=None, w_pad=None,
285
+ hspace=None, wspace=None, rect=None):
286
+ """
287
+ Set the pads for constrained_layout.
288
+
289
+ Parameters
290
+ ----------
291
+ h_pad, w_pad : float
292
+ Padding around the Axes elements in inches.
293
+ Default to :rc:`figure.constrained_layout.h_pad` and
294
+ :rc:`figure.constrained_layout.w_pad`.
295
+ hspace, wspace : float
296
+ Fraction of the figure to dedicate to space between the
297
+ axes. These are evenly spread between the gaps between the Axes.
298
+ A value of 0.2 for a three-column layout would have a space
299
+ of 0.1 of the figure width between each column.
300
+ If h/wspace < h/w_pad, then the pads are used instead.
301
+ Default to :rc:`figure.constrained_layout.hspace` and
302
+ :rc:`figure.constrained_layout.wspace`.
303
+ rect : tuple of 4 floats
304
+ Rectangle in figure coordinates to perform constrained layout in
305
+ (left, bottom, width, height), each from 0-1.
306
+ """
307
+ for td in self.set.__kwdefaults__:
308
+ if locals()[td] is not None:
309
+ self._params[td] = locals()[td]
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib.figure import Figure
2
+
3
+ from typing import Any
4
+
5
+ class LayoutEngine:
6
+ def __init__(self, **kwargs: Any) -> None: ...
7
+ def set(self) -> None: ...
8
+ @property
9
+ def colorbar_gridspec(self) -> bool: ...
10
+ @property
11
+ def adjust_compatible(self) -> bool: ...
12
+ def get(self) -> dict[str, Any]: ...
13
+ def execute(self, fig: Figure) -> None: ...
14
+
15
+ class PlaceHolderLayoutEngine(LayoutEngine):
16
+ def __init__(
17
+ self, adjust_compatible: bool, colorbar_gridspec: bool, **kwargs: Any
18
+ ) -> None: ...
19
+ def execute(self, fig: Figure) -> None: ...
20
+
21
+ class TightLayoutEngine(LayoutEngine):
22
+ def __init__(
23
+ self,
24
+ *,
25
+ pad: float = ...,
26
+ h_pad: float | None = ...,
27
+ w_pad: float | None = ...,
28
+ rect: tuple[float, float, float, float] = ...,
29
+ **kwargs: Any
30
+ ) -> None: ...
31
+ def execute(self, fig: Figure) -> None: ...
32
+ def set(
33
+ self,
34
+ *,
35
+ pad: float | None = ...,
36
+ w_pad: float | None = ...,
37
+ h_pad: float | None = ...,
38
+ rect: tuple[float, float, float, float] | None = ...
39
+ ) -> None: ...
40
+
41
+ class ConstrainedLayoutEngine(LayoutEngine):
42
+ def __init__(
43
+ self,
44
+ *,
45
+ h_pad: float | None = ...,
46
+ w_pad: float | None = ...,
47
+ hspace: float | None = ...,
48
+ wspace: float | None = ...,
49
+ rect: tuple[float, float, float, float] = ...,
50
+ compress: bool = ...,
51
+ **kwargs: Any
52
+ ) -> None: ...
53
+ def execute(self, fig: Figure) -> Any: ...
54
+ def set(
55
+ self,
56
+ *,
57
+ h_pad: float | None = ...,
58
+ w_pad: float | None = ...,
59
+ hspace: float | None = ...,
60
+ wspace: float | None = ...,
61
+ rect: tuple[float, float, float, float] | None = ...
62
+ ) -> None: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py ADDED
@@ -0,0 +1,1371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The legend module defines the Legend class, which is responsible for
3
+ drawing legends associated with Axes and/or figures.
4
+
5
+ .. important::
6
+
7
+ It is unlikely that you would ever create a Legend instance manually.
8
+ Most users would normally create a legend via the `~.Axes.legend`
9
+ function. For more details on legends there is also a :ref:`legend guide
10
+ <legend_guide>`.
11
+
12
+ The `Legend` class is a container of legend handles and legend texts.
13
+
14
+ The legend handler map specifies how to create legend handles from artists
15
+ (lines, patches, etc.) in the Axes or figures. Default legend handlers are
16
+ defined in the :mod:`~matplotlib.legend_handler` module. While not all artist
17
+ types are covered by the default legend handlers, custom legend handlers can be
18
+ defined to support arbitrary objects.
19
+
20
+ See the :ref`<legend_guide>` for more
21
+ information.
22
+ """
23
+
24
+ import itertools
25
+ import logging
26
+ import numbers
27
+ import time
28
+
29
+ import numpy as np
30
+
31
+ import matplotlib as mpl
32
+ from matplotlib import _api, _docstring, cbook, colors, offsetbox
33
+ from matplotlib.artist import Artist, allow_rasterization
34
+ from matplotlib.cbook import silent_list
35
+ from matplotlib.font_manager import FontProperties
36
+ from matplotlib.lines import Line2D
37
+ from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch,
38
+ StepPatch)
39
+ from matplotlib.collections import (
40
+ Collection, CircleCollection, LineCollection, PathCollection,
41
+ PolyCollection, RegularPolyCollection)
42
+ from matplotlib.text import Text
43
+ from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
44
+ from matplotlib.transforms import BboxTransformTo, BboxTransformFrom
45
+ from matplotlib.offsetbox import (
46
+ AnchoredOffsetbox, DraggableOffsetBox,
47
+ HPacker, VPacker,
48
+ DrawingArea, TextArea,
49
+ )
50
+ from matplotlib.container import ErrorbarContainer, BarContainer, StemContainer
51
+ from . import legend_handler
52
+
53
+
54
+ class DraggableLegend(DraggableOffsetBox):
55
+ def __init__(self, legend, use_blit=False, update="loc"):
56
+ """
57
+ Wrapper around a `.Legend` to support mouse dragging.
58
+
59
+ Parameters
60
+ ----------
61
+ legend : `.Legend`
62
+ The `.Legend` instance to wrap.
63
+ use_blit : bool, optional
64
+ Use blitting for faster image composition. For details see
65
+ :ref:`func-animation`.
66
+ update : {'loc', 'bbox'}, optional
67
+ If "loc", update the *loc* parameter of the legend upon finalizing.
68
+ If "bbox", update the *bbox_to_anchor* parameter.
69
+ """
70
+ self.legend = legend
71
+
72
+ _api.check_in_list(["loc", "bbox"], update=update)
73
+ self._update = update
74
+
75
+ super().__init__(legend, legend._legend_box, use_blit=use_blit)
76
+
77
+ def finalize_offset(self):
78
+ if self._update == "loc":
79
+ self._update_loc(self.get_loc_in_canvas())
80
+ elif self._update == "bbox":
81
+ self._update_bbox_to_anchor(self.get_loc_in_canvas())
82
+
83
+ def _update_loc(self, loc_in_canvas):
84
+ bbox = self.legend.get_bbox_to_anchor()
85
+ # if bbox has zero width or height, the transformation is
86
+ # ill-defined. Fall back to the default bbox_to_anchor.
87
+ if bbox.width == 0 or bbox.height == 0:
88
+ self.legend.set_bbox_to_anchor(None)
89
+ bbox = self.legend.get_bbox_to_anchor()
90
+ _bbox_transform = BboxTransformFrom(bbox)
91
+ self.legend._loc = tuple(_bbox_transform.transform(loc_in_canvas))
92
+
93
+ def _update_bbox_to_anchor(self, loc_in_canvas):
94
+ loc_in_bbox = self.legend.axes.transAxes.transform(loc_in_canvas)
95
+ self.legend.set_bbox_to_anchor(loc_in_bbox)
96
+
97
+
98
+ _legend_kw_doc_base = """
99
+ bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
100
+ Box that is used to position the legend in conjunction with *loc*.
101
+ Defaults to ``axes.bbox`` (if called as a method to `.Axes.legend`) or
102
+ ``figure.bbox`` (if ``figure.legend``). This argument allows arbitrary
103
+ placement of the legend.
104
+
105
+ Bbox coordinates are interpreted in the coordinate system given by
106
+ *bbox_transform*, with the default transform
107
+ Axes or Figure coordinates, depending on which ``legend`` is called.
108
+
109
+ If a 4-tuple or `.BboxBase` is given, then it specifies the bbox
110
+ ``(x, y, width, height)`` that the legend is placed in.
111
+ To put the legend in the best location in the bottom right
112
+ quadrant of the Axes (or figure)::
113
+
114
+ loc='best', bbox_to_anchor=(0.5, 0., 0.5, 0.5)
115
+
116
+ A 2-tuple ``(x, y)`` places the corner of the legend specified by *loc* at
117
+ x, y. For example, to put the legend's upper right-hand corner in the
118
+ center of the Axes (or figure) the following keywords can be used::
119
+
120
+ loc='upper right', bbox_to_anchor=(0.5, 0.5)
121
+
122
+ ncols : int, default: 1
123
+ The number of columns that the legend has.
124
+
125
+ For backward compatibility, the spelling *ncol* is also supported
126
+ but it is discouraged. If both are given, *ncols* takes precedence.
127
+
128
+ prop : None or `~matplotlib.font_manager.FontProperties` or dict
129
+ The font properties of the legend. If None (default), the current
130
+ :data:`matplotlib.rcParams` will be used.
131
+
132
+ fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \
133
+ 'x-large', 'xx-large'}
134
+ The font size of the legend. If the value is numeric the size will be the
135
+ absolute font size in points. String values are relative to the current
136
+ default font size. This argument is only used if *prop* is not specified.
137
+
138
+ labelcolor : str or list, default: :rc:`legend.labelcolor`
139
+ The color of the text in the legend. Either a valid color string
140
+ (for example, 'red'), or a list of color strings. The labelcolor can
141
+ also be made to match the color of the line or marker using 'linecolor',
142
+ 'markerfacecolor' (or 'mfc'), or 'markeredgecolor' (or 'mec').
143
+
144
+ Labelcolor can be set globally using :rc:`legend.labelcolor`. If None,
145
+ use :rc:`text.color`.
146
+
147
+ numpoints : int, default: :rc:`legend.numpoints`
148
+ The number of marker points in the legend when creating a legend
149
+ entry for a `.Line2D` (line).
150
+
151
+ scatterpoints : int, default: :rc:`legend.scatterpoints`
152
+ The number of marker points in the legend when creating
153
+ a legend entry for a `.PathCollection` (scatter plot).
154
+
155
+ scatteryoffsets : iterable of floats, default: ``[0.375, 0.5, 0.3125]``
156
+ The vertical offset (relative to the font size) for the markers
157
+ created for a scatter plot legend entry. 0.0 is at the base the
158
+ legend text, and 1.0 is at the top. To draw all markers at the
159
+ same height, set to ``[0.5]``.
160
+
161
+ markerscale : float, default: :rc:`legend.markerscale`
162
+ The relative size of legend markers compared to the originally drawn ones.
163
+
164
+ markerfirst : bool, default: True
165
+ If *True*, legend marker is placed to the left of the legend label.
166
+ If *False*, legend marker is placed to the right of the legend label.
167
+
168
+ reverse : bool, default: False
169
+ If *True*, the legend labels are displayed in reverse order from the input.
170
+ If *False*, the legend labels are displayed in the same order as the input.
171
+
172
+ .. versionadded:: 3.7
173
+
174
+ frameon : bool, default: :rc:`legend.frameon`
175
+ Whether the legend should be drawn on a patch (frame).
176
+
177
+ fancybox : bool, default: :rc:`legend.fancybox`
178
+ Whether round edges should be enabled around the `.FancyBboxPatch` which
179
+ makes up the legend's background.
180
+
181
+ shadow : None, bool or dict, default: :rc:`legend.shadow`
182
+ Whether to draw a shadow behind the legend.
183
+ The shadow can be configured using `.Patch` keywords.
184
+ Customization via :rc:`legend.shadow` is currently not supported.
185
+
186
+ framealpha : float, default: :rc:`legend.framealpha`
187
+ The alpha transparency of the legend's background.
188
+ If *shadow* is activated and *framealpha* is ``None``, the default value is
189
+ ignored.
190
+
191
+ facecolor : "inherit" or color, default: :rc:`legend.facecolor`
192
+ The legend's background color.
193
+ If ``"inherit"``, use :rc:`axes.facecolor`.
194
+
195
+ edgecolor : "inherit" or color, default: :rc:`legend.edgecolor`
196
+ The legend's background patch edge color.
197
+ If ``"inherit"``, use :rc:`axes.edgecolor`.
198
+
199
+ mode : {"expand", None}
200
+ If *mode* is set to ``"expand"`` the legend will be horizontally
201
+ expanded to fill the Axes area (or *bbox_to_anchor* if defines
202
+ the legend's size).
203
+
204
+ bbox_transform : None or `~matplotlib.transforms.Transform`
205
+ The transform for the bounding box (*bbox_to_anchor*). For a value
206
+ of ``None`` (default) the Axes'
207
+ :data:`~matplotlib.axes.Axes.transAxes` transform will be used.
208
+
209
+ title : str or None
210
+ The legend's title. Default is no title (``None``).
211
+
212
+ title_fontproperties : None or `~matplotlib.font_manager.FontProperties` or dict
213
+ The font properties of the legend's title. If None (default), the
214
+ *title_fontsize* argument will be used if present; if *title_fontsize* is
215
+ also None, the current :rc:`legend.title_fontsize` will be used.
216
+
217
+ title_fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \
218
+ 'x-large', 'xx-large'}, default: :rc:`legend.title_fontsize`
219
+ The font size of the legend's title.
220
+ Note: This cannot be combined with *title_fontproperties*. If you want
221
+ to set the fontsize alongside other font properties, use the *size*
222
+ parameter in *title_fontproperties*.
223
+
224
+ alignment : {'center', 'left', 'right'}, default: 'center'
225
+ The alignment of the legend title and the box of entries. The entries
226
+ are aligned as a single block, so that markers always lined up.
227
+
228
+ borderpad : float, default: :rc:`legend.borderpad`
229
+ The fractional whitespace inside the legend border, in font-size units.
230
+
231
+ labelspacing : float, default: :rc:`legend.labelspacing`
232
+ The vertical space between the legend entries, in font-size units.
233
+
234
+ handlelength : float, default: :rc:`legend.handlelength`
235
+ The length of the legend handles, in font-size units.
236
+
237
+ handleheight : float, default: :rc:`legend.handleheight`
238
+ The height of the legend handles, in font-size units.
239
+
240
+ handletextpad : float, default: :rc:`legend.handletextpad`
241
+ The pad between the legend handle and text, in font-size units.
242
+
243
+ borderaxespad : float, default: :rc:`legend.borderaxespad`
244
+ The pad between the Axes and legend border, in font-size units.
245
+
246
+ columnspacing : float, default: :rc:`legend.columnspacing`
247
+ The spacing between columns, in font-size units.
248
+
249
+ handler_map : dict or None
250
+ The custom dictionary mapping instances or types to a legend
251
+ handler. This *handler_map* updates the default handler map
252
+ found at `matplotlib.legend.Legend.get_legend_handler_map`.
253
+
254
+ draggable : bool, default: False
255
+ Whether the legend can be dragged with the mouse.
256
+ """
257
+
258
+ _loc_doc_base = """
259
+ loc : str or pair of floats, default: {default}
260
+ The location of the legend.
261
+
262
+ The strings ``'upper left'``, ``'upper right'``, ``'lower left'``,
263
+ ``'lower right'`` place the legend at the corresponding corner of the
264
+ {parent}.
265
+
266
+ The strings ``'upper center'``, ``'lower center'``, ``'center left'``,
267
+ ``'center right'`` place the legend at the center of the corresponding edge
268
+ of the {parent}.
269
+
270
+ The string ``'center'`` places the legend at the center of the {parent}.
271
+ {best}
272
+ The location can also be a 2-tuple giving the coordinates of the lower-left
273
+ corner of the legend in {parent} coordinates (in which case *bbox_to_anchor*
274
+ will be ignored).
275
+
276
+ For back-compatibility, ``'center right'`` (but no other location) can also
277
+ be spelled ``'right'``, and each "string" location can also be given as a
278
+ numeric value:
279
+
280
+ ================== =============
281
+ Location String Location Code
282
+ ================== =============
283
+ 'best' (Axes only) 0
284
+ 'upper right' 1
285
+ 'upper left' 2
286
+ 'lower left' 3
287
+ 'lower right' 4
288
+ 'right' 5
289
+ 'center left' 6
290
+ 'center right' 7
291
+ 'lower center' 8
292
+ 'upper center' 9
293
+ 'center' 10
294
+ ================== =============
295
+ {outside}"""
296
+
297
+ _loc_doc_best = """
298
+ The string ``'best'`` places the legend at the location, among the nine
299
+ locations defined so far, with the minimum overlap with other drawn
300
+ artists. This option can be quite slow for plots with large amounts of
301
+ data; your plotting speed may benefit from providing a specific location.
302
+ """
303
+
304
+ _legend_kw_axes_st = (
305
+ _loc_doc_base.format(parent='axes', default=':rc:`legend.loc`',
306
+ best=_loc_doc_best, outside='') +
307
+ _legend_kw_doc_base)
308
+ _docstring.interpd.register(_legend_kw_axes=_legend_kw_axes_st)
309
+
310
+ _outside_doc = """
311
+ If a figure is using the constrained layout manager, the string codes
312
+ of the *loc* keyword argument can get better layout behaviour using the
313
+ prefix 'outside'. There is ambiguity at the corners, so 'outside
314
+ upper right' will make space for the legend above the rest of the
315
+ axes in the layout, and 'outside right upper' will make space on the
316
+ right side of the layout. In addition to the values of *loc*
317
+ listed above, we have 'outside right upper', 'outside right lower',
318
+ 'outside left upper', and 'outside left lower'. See
319
+ :ref:`legend_guide` for more details.
320
+ """
321
+
322
+ _legend_kw_figure_st = (
323
+ _loc_doc_base.format(parent='figure', default="'upper right'",
324
+ best='', outside=_outside_doc) +
325
+ _legend_kw_doc_base)
326
+ _docstring.interpd.register(_legend_kw_figure=_legend_kw_figure_st)
327
+
328
+ _legend_kw_both_st = (
329
+ _loc_doc_base.format(parent='axes/figure',
330
+ default=":rc:`legend.loc` for Axes, 'upper right' for Figure",
331
+ best=_loc_doc_best, outside=_outside_doc) +
332
+ _legend_kw_doc_base)
333
+ _docstring.interpd.register(_legend_kw_doc=_legend_kw_both_st)
334
+
335
+ _legend_kw_set_loc_st = (
336
+ _loc_doc_base.format(parent='axes/figure',
337
+ default=":rc:`legend.loc` for Axes, 'upper right' for Figure",
338
+ best=_loc_doc_best, outside=_outside_doc))
339
+ _docstring.interpd.register(_legend_kw_set_loc_doc=_legend_kw_set_loc_st)
340
+
341
+
342
+ class Legend(Artist):
343
+ """
344
+ Place a legend on the figure/axes.
345
+ """
346
+
347
+ # 'best' is only implemented for Axes legends
348
+ codes = {'best': 0, **AnchoredOffsetbox.codes}
349
+ zorder = 5
350
+
351
+ def __str__(self):
352
+ return "Legend"
353
+
354
+ @_docstring.interpd
355
+ def __init__(
356
+ self, parent, handles, labels,
357
+ *,
358
+ loc=None,
359
+ numpoints=None, # number of points in the legend line
360
+ markerscale=None, # relative size of legend markers vs. original
361
+ markerfirst=True, # left/right ordering of legend marker and label
362
+ reverse=False, # reverse ordering of legend marker and label
363
+ scatterpoints=None, # number of scatter points
364
+ scatteryoffsets=None,
365
+ prop=None, # properties for the legend texts
366
+ fontsize=None, # keyword to set font size directly
367
+ labelcolor=None, # keyword to set the text color
368
+
369
+ # spacing & pad defined as a fraction of the font-size
370
+ borderpad=None, # whitespace inside the legend border
371
+ labelspacing=None, # vertical space between the legend entries
372
+ handlelength=None, # length of the legend handles
373
+ handleheight=None, # height of the legend handles
374
+ handletextpad=None, # pad between the legend handle and text
375
+ borderaxespad=None, # pad between the Axes and legend border
376
+ columnspacing=None, # spacing between columns
377
+
378
+ ncols=1, # number of columns
379
+ mode=None, # horizontal distribution of columns: None or "expand"
380
+
381
+ fancybox=None, # True: fancy box, False: rounded box, None: rcParam
382
+ shadow=None,
383
+ title=None, # legend title
384
+ title_fontsize=None, # legend title font size
385
+ framealpha=None, # set frame alpha
386
+ edgecolor=None, # frame patch edgecolor
387
+ facecolor=None, # frame patch facecolor
388
+
389
+ bbox_to_anchor=None, # bbox to which the legend will be anchored
390
+ bbox_transform=None, # transform for the bbox
391
+ frameon=None, # draw frame
392
+ handler_map=None,
393
+ title_fontproperties=None, # properties for the legend title
394
+ alignment="center", # control the alignment within the legend box
395
+ ncol=1, # synonym for ncols (backward compatibility)
396
+ draggable=False # whether the legend can be dragged with the mouse
397
+ ):
398
+ """
399
+ Parameters
400
+ ----------
401
+ parent : `~matplotlib.axes.Axes` or `.Figure`
402
+ The artist that contains the legend.
403
+
404
+ handles : list of (`.Artist` or tuple of `.Artist`)
405
+ A list of Artists (lines, patches) to be added to the legend.
406
+
407
+ labels : list of str
408
+ A list of labels to show next to the artists. The length of handles
409
+ and labels should be the same. If they are not, they are truncated
410
+ to the length of the shorter list.
411
+
412
+ Other Parameters
413
+ ----------------
414
+ %(_legend_kw_doc)s
415
+
416
+ Attributes
417
+ ----------
418
+ legend_handles
419
+ List of `.Artist` objects added as legend entries.
420
+
421
+ .. versionadded:: 3.7
422
+ """
423
+ # local import only to avoid circularity
424
+ from matplotlib.axes import Axes
425
+ from matplotlib.figure import FigureBase
426
+
427
+ super().__init__()
428
+
429
+ if prop is None:
430
+ self.prop = FontProperties(size=mpl._val_or_rc(fontsize, "legend.fontsize"))
431
+ else:
432
+ self.prop = FontProperties._from_any(prop)
433
+ if isinstance(prop, dict) and "size" not in prop:
434
+ self.prop.set_size(mpl.rcParams["legend.fontsize"])
435
+
436
+ self._fontsize = self.prop.get_size_in_points()
437
+
438
+ self.texts = []
439
+ self.legend_handles = []
440
+ self._legend_title_box = None
441
+
442
+ #: A dictionary with the extra handler mappings for this Legend
443
+ #: instance.
444
+ self._custom_handler_map = handler_map
445
+
446
+ self.numpoints = mpl._val_or_rc(numpoints, 'legend.numpoints')
447
+ self.markerscale = mpl._val_or_rc(markerscale, 'legend.markerscale')
448
+ self.scatterpoints = mpl._val_or_rc(scatterpoints, 'legend.scatterpoints')
449
+ self.borderpad = mpl._val_or_rc(borderpad, 'legend.borderpad')
450
+ self.labelspacing = mpl._val_or_rc(labelspacing, 'legend.labelspacing')
451
+ self.handlelength = mpl._val_or_rc(handlelength, 'legend.handlelength')
452
+ self.handleheight = mpl._val_or_rc(handleheight, 'legend.handleheight')
453
+ self.handletextpad = mpl._val_or_rc(handletextpad, 'legend.handletextpad')
454
+ self.borderaxespad = mpl._val_or_rc(borderaxespad, 'legend.borderaxespad')
455
+ self.columnspacing = mpl._val_or_rc(columnspacing, 'legend.columnspacing')
456
+ self.shadow = mpl._val_or_rc(shadow, 'legend.shadow')
457
+
458
+ if reverse:
459
+ labels = [*reversed(labels)]
460
+ handles = [*reversed(handles)]
461
+
462
+ handles = list(handles)
463
+ if len(handles) < 2:
464
+ ncols = 1
465
+ self._ncols = ncols if ncols != 1 else ncol
466
+
467
+ if self.numpoints <= 0:
468
+ raise ValueError("numpoints must be > 0; it was %d" % numpoints)
469
+
470
+ # introduce y-offset for handles of the scatter plot
471
+ if scatteryoffsets is None:
472
+ self._scatteryoffsets = np.array([3. / 8., 4. / 8., 2.5 / 8.])
473
+ else:
474
+ self._scatteryoffsets = np.asarray(scatteryoffsets)
475
+ reps = self.scatterpoints // len(self._scatteryoffsets) + 1
476
+ self._scatteryoffsets = np.tile(self._scatteryoffsets,
477
+ reps)[:self.scatterpoints]
478
+
479
+ # _legend_box is a VPacker instance that contains all
480
+ # legend items and will be initialized from _init_legend_box()
481
+ # method.
482
+ self._legend_box = None
483
+
484
+ if isinstance(parent, Axes):
485
+ self.isaxes = True
486
+ self.axes = parent
487
+ self.set_figure(parent.get_figure(root=False))
488
+ elif isinstance(parent, FigureBase):
489
+ self.isaxes = False
490
+ self.set_figure(parent)
491
+ else:
492
+ raise TypeError(
493
+ "Legend needs either Axes or FigureBase as parent"
494
+ )
495
+ self.parent = parent
496
+
497
+ self._mode = mode
498
+ self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
499
+
500
+ # Figure out if self.shadow is valid
501
+ # If shadow was None, rcParams loads False
502
+ # So it shouldn't be None here
503
+
504
+ self._shadow_props = {'ox': 2, 'oy': -2} # default location offsets
505
+ if isinstance(self.shadow, dict):
506
+ self._shadow_props.update(self.shadow)
507
+ self.shadow = True
508
+ elif self.shadow in (0, 1, True, False):
509
+ self.shadow = bool(self.shadow)
510
+ else:
511
+ raise ValueError(
512
+ 'Legend shadow must be a dict or bool, not '
513
+ f'{self.shadow!r} of type {type(self.shadow)}.'
514
+ )
515
+
516
+ # We use FancyBboxPatch to draw a legend frame. The location
517
+ # and size of the box will be updated during the drawing time.
518
+
519
+ facecolor = mpl._val_or_rc(facecolor, "legend.facecolor")
520
+ if facecolor == 'inherit':
521
+ facecolor = mpl.rcParams["axes.facecolor"]
522
+
523
+ edgecolor = mpl._val_or_rc(edgecolor, "legend.edgecolor")
524
+ if edgecolor == 'inherit':
525
+ edgecolor = mpl.rcParams["axes.edgecolor"]
526
+
527
+ fancybox = mpl._val_or_rc(fancybox, "legend.fancybox")
528
+
529
+ self.legendPatch = FancyBboxPatch(
530
+ xy=(0, 0), width=1, height=1,
531
+ facecolor=facecolor, edgecolor=edgecolor,
532
+ # If shadow is used, default to alpha=1 (#8943).
533
+ alpha=(framealpha if framealpha is not None
534
+ else 1 if shadow
535
+ else mpl.rcParams["legend.framealpha"]),
536
+ # The width and height of the legendPatch will be set (in draw())
537
+ # to the length that includes the padding. Thus we set pad=0 here.
538
+ boxstyle=("round,pad=0,rounding_size=0.2" if fancybox
539
+ else "square,pad=0"),
540
+ mutation_scale=self._fontsize,
541
+ snap=True,
542
+ visible=mpl._val_or_rc(frameon, "legend.frameon")
543
+ )
544
+ self._set_artist_props(self.legendPatch)
545
+
546
+ _api.check_in_list(["center", "left", "right"], alignment=alignment)
547
+ self._alignment = alignment
548
+
549
+ # init with null renderer
550
+ self._init_legend_box(handles, labels, markerfirst)
551
+
552
+ # Set legend location
553
+ self.set_loc(loc)
554
+
555
+ # figure out title font properties:
556
+ if title_fontsize is not None and title_fontproperties is not None:
557
+ raise ValueError(
558
+ "title_fontsize and title_fontproperties can't be specified "
559
+ "at the same time. Only use one of them. ")
560
+ title_prop_fp = FontProperties._from_any(title_fontproperties)
561
+ if isinstance(title_fontproperties, dict):
562
+ if "size" not in title_fontproperties:
563
+ title_fontsize = mpl.rcParams["legend.title_fontsize"]
564
+ title_prop_fp.set_size(title_fontsize)
565
+ elif title_fontsize is not None:
566
+ title_prop_fp.set_size(title_fontsize)
567
+ elif not isinstance(title_fontproperties, FontProperties):
568
+ title_fontsize = mpl.rcParams["legend.title_fontsize"]
569
+ title_prop_fp.set_size(title_fontsize)
570
+
571
+ self.set_title(title, prop=title_prop_fp)
572
+
573
+ self._draggable = None
574
+ self.set_draggable(state=draggable)
575
+
576
+ # set the text color
577
+
578
+ color_getters = { # getter function depends on line or patch
579
+ 'linecolor': ['get_color', 'get_facecolor'],
580
+ 'markerfacecolor': ['get_markerfacecolor', 'get_facecolor'],
581
+ 'mfc': ['get_markerfacecolor', 'get_facecolor'],
582
+ 'markeredgecolor': ['get_markeredgecolor', 'get_edgecolor'],
583
+ 'mec': ['get_markeredgecolor', 'get_edgecolor'],
584
+ }
585
+ labelcolor = mpl._val_or_rc(labelcolor, 'legend.labelcolor')
586
+ if labelcolor is None:
587
+ labelcolor = mpl.rcParams['text.color']
588
+ if isinstance(labelcolor, str) and labelcolor in color_getters:
589
+ getter_names = color_getters[labelcolor]
590
+ for handle, text in zip(self.legend_handles, self.texts):
591
+ try:
592
+ if handle.get_array() is not None:
593
+ continue
594
+ except AttributeError:
595
+ pass
596
+ for getter_name in getter_names:
597
+ try:
598
+ color = getattr(handle, getter_name)()
599
+ if isinstance(color, np.ndarray):
600
+ if (
601
+ color.shape[0] == 1
602
+ or np.isclose(color, color[0]).all()
603
+ ):
604
+ text.set_color(color[0])
605
+ else:
606
+ pass
607
+ else:
608
+ text.set_color(color)
609
+ break
610
+ except AttributeError:
611
+ pass
612
+ elif cbook._str_equal(labelcolor, 'none'):
613
+ for text in self.texts:
614
+ text.set_color(labelcolor)
615
+ elif np.iterable(labelcolor):
616
+ for text, color in zip(self.texts,
617
+ itertools.cycle(
618
+ colors.to_rgba_array(labelcolor))):
619
+ text.set_color(color)
620
+ else:
621
+ raise ValueError(f"Invalid labelcolor: {labelcolor!r}")
622
+
623
+ def _set_artist_props(self, a):
624
+ """
625
+ Set the boilerplate props for artists added to Axes.
626
+ """
627
+ a.set_figure(self.get_figure(root=False))
628
+ if self.isaxes:
629
+ a.axes = self.axes
630
+
631
+ a.set_transform(self.get_transform())
632
+
633
+ @_docstring.interpd
634
+ def set_loc(self, loc=None):
635
+ """
636
+ Set the location of the legend.
637
+
638
+ .. versionadded:: 3.8
639
+
640
+ Parameters
641
+ ----------
642
+ %(_legend_kw_set_loc_doc)s
643
+ """
644
+ loc0 = loc
645
+ self._loc_used_default = loc is None
646
+ if loc is None:
647
+ loc = mpl.rcParams["legend.loc"]
648
+ if not self.isaxes and loc in [0, 'best']:
649
+ loc = 'upper right'
650
+
651
+ type_err_message = ("loc must be string, coordinate tuple, or"
652
+ f" an integer 0-10, not {loc!r}")
653
+
654
+ # handle outside legends:
655
+ self._outside_loc = None
656
+ if isinstance(loc, str):
657
+ if loc.split()[0] == 'outside':
658
+ # strip outside:
659
+ loc = loc.split('outside ')[1]
660
+ # strip "center" at the beginning
661
+ self._outside_loc = loc.replace('center ', '')
662
+ # strip first
663
+ self._outside_loc = self._outside_loc.split()[0]
664
+ locs = loc.split()
665
+ if len(locs) > 1 and locs[0] in ('right', 'left'):
666
+ # locs doesn't accept "left upper", etc, so swap
667
+ if locs[0] != 'center':
668
+ locs = locs[::-1]
669
+ loc = locs[0] + ' ' + locs[1]
670
+ # check that loc is in acceptable strings
671
+ loc = _api.check_getitem(self.codes, loc=loc)
672
+ elif np.iterable(loc):
673
+ # coerce iterable into tuple
674
+ loc = tuple(loc)
675
+ # validate the tuple represents Real coordinates
676
+ if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
677
+ raise ValueError(type_err_message)
678
+ elif isinstance(loc, int):
679
+ # validate the integer represents a string numeric value
680
+ if loc < 0 or loc > 10:
681
+ raise ValueError(type_err_message)
682
+ else:
683
+ # all other cases are invalid values of loc
684
+ raise ValueError(type_err_message)
685
+
686
+ if self.isaxes and self._outside_loc:
687
+ raise ValueError(
688
+ f"'outside' option for loc='{loc0}' keyword argument only "
689
+ "works for figure legends")
690
+
691
+ if not self.isaxes and loc == 0:
692
+ raise ValueError(
693
+ "Automatic legend placement (loc='best') not implemented for "
694
+ "figure legend")
695
+
696
+ tmp = self._loc_used_default
697
+ self._set_loc(loc)
698
+ self._loc_used_default = tmp # ignore changes done by _set_loc
699
+
700
+ def _set_loc(self, loc):
701
+ # find_offset function will be provided to _legend_box and
702
+ # _legend_box will draw itself at the location of the return
703
+ # value of the find_offset.
704
+ self._loc_used_default = False
705
+ self._loc_real = loc
706
+ self.stale = True
707
+ self._legend_box.set_offset(self._findoffset)
708
+
709
+ def set_ncols(self, ncols):
710
+ """Set the number of columns."""
711
+ self._ncols = ncols
712
+
713
+ def _get_loc(self):
714
+ return self._loc_real
715
+
716
+ _loc = property(_get_loc, _set_loc)
717
+
718
+ def _findoffset(self, width, height, xdescent, ydescent, renderer):
719
+ """Helper function to locate the legend."""
720
+
721
+ if self._loc == 0: # "best".
722
+ x, y = self._find_best_position(width, height, renderer)
723
+ elif self._loc in Legend.codes.values(): # Fixed location.
724
+ bbox = Bbox.from_bounds(0, 0, width, height)
725
+ x, y = self._get_anchored_bbox(self._loc, bbox,
726
+ self.get_bbox_to_anchor(),
727
+ renderer)
728
+ else: # Axes or figure coordinates.
729
+ fx, fy = self._loc
730
+ bbox = self.get_bbox_to_anchor()
731
+ x, y = bbox.x0 + bbox.width * fx, bbox.y0 + bbox.height * fy
732
+
733
+ return x + xdescent, y + ydescent
734
+
735
+ @allow_rasterization
736
+ def draw(self, renderer):
737
+ # docstring inherited
738
+ if not self.get_visible():
739
+ return
740
+
741
+ renderer.open_group('legend', gid=self.get_gid())
742
+
743
+ fontsize = renderer.points_to_pixels(self._fontsize)
744
+
745
+ # if mode == fill, set the width of the legend_box to the
746
+ # width of the parent (minus pads)
747
+ if self._mode in ["expand"]:
748
+ pad = 2 * (self.borderaxespad + self.borderpad) * fontsize
749
+ self._legend_box.set_width(self.get_bbox_to_anchor().width - pad)
750
+
751
+ # update the location and size of the legend. This needs to
752
+ # be done in any case to clip the figure right.
753
+ bbox = self._legend_box.get_window_extent(renderer)
754
+ self.legendPatch.set_bounds(bbox.bounds)
755
+ self.legendPatch.set_mutation_scale(fontsize)
756
+
757
+ # self.shadow is validated in __init__
758
+ # So by here it is a bool and self._shadow_props contains any configs
759
+
760
+ if self.shadow:
761
+ Shadow(self.legendPatch, **self._shadow_props).draw(renderer)
762
+
763
+ self.legendPatch.draw(renderer)
764
+ self._legend_box.draw(renderer)
765
+
766
+ renderer.close_group('legend')
767
+ self.stale = False
768
+
769
+ # _default_handler_map defines the default mapping between plot
770
+ # elements and the legend handlers.
771
+
772
+ _default_handler_map = {
773
+ StemContainer: legend_handler.HandlerStem(),
774
+ ErrorbarContainer: legend_handler.HandlerErrorbar(),
775
+ Line2D: legend_handler.HandlerLine2D(),
776
+ Patch: legend_handler.HandlerPatch(),
777
+ StepPatch: legend_handler.HandlerStepPatch(),
778
+ LineCollection: legend_handler.HandlerLineCollection(),
779
+ RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(),
780
+ CircleCollection: legend_handler.HandlerCircleCollection(),
781
+ BarContainer: legend_handler.HandlerPatch(
782
+ update_func=legend_handler.update_from_first_child),
783
+ tuple: legend_handler.HandlerTuple(),
784
+ PathCollection: legend_handler.HandlerPathCollection(),
785
+ PolyCollection: legend_handler.HandlerPolyCollection()
786
+ }
787
+
788
+ # (get|set|update)_default_handler_maps are public interfaces to
789
+ # modify the default handler map.
790
+
791
+ @classmethod
792
+ def get_default_handler_map(cls):
793
+ """Return the global default handler map, shared by all legends."""
794
+ return cls._default_handler_map
795
+
796
+ @classmethod
797
+ def set_default_handler_map(cls, handler_map):
798
+ """Set the global default handler map, shared by all legends."""
799
+ cls._default_handler_map = handler_map
800
+
801
+ @classmethod
802
+ def update_default_handler_map(cls, handler_map):
803
+ """Update the global default handler map, shared by all legends."""
804
+ cls._default_handler_map.update(handler_map)
805
+
806
+ def get_legend_handler_map(self):
807
+ """Return this legend instance's handler map."""
808
+ default_handler_map = self.get_default_handler_map()
809
+ return ({**default_handler_map, **self._custom_handler_map}
810
+ if self._custom_handler_map else default_handler_map)
811
+
812
+ @staticmethod
813
+ def get_legend_handler(legend_handler_map, orig_handle):
814
+ """
815
+ Return a legend handler from *legend_handler_map* that
816
+ corresponds to *orig_handler*.
817
+
818
+ *legend_handler_map* should be a dictionary object (that is
819
+ returned by the get_legend_handler_map method).
820
+
821
+ It first checks if the *orig_handle* itself is a key in the
822
+ *legend_handler_map* and return the associated value.
823
+ Otherwise, it checks for each of the classes in its
824
+ method-resolution-order. If no matching key is found, it
825
+ returns ``None``.
826
+ """
827
+ try:
828
+ return legend_handler_map[orig_handle]
829
+ except (TypeError, KeyError): # TypeError if unhashable.
830
+ pass
831
+ for handle_type in type(orig_handle).mro():
832
+ try:
833
+ return legend_handler_map[handle_type]
834
+ except KeyError:
835
+ pass
836
+ return None
837
+
838
+ def _init_legend_box(self, handles, labels, markerfirst=True):
839
+ """
840
+ Initialize the legend_box. The legend_box is an instance of
841
+ the OffsetBox, which is packed with legend handles and
842
+ texts. Once packed, their location is calculated during the
843
+ drawing time.
844
+ """
845
+
846
+ fontsize = self._fontsize
847
+
848
+ # legend_box is a HPacker, horizontally packed with columns.
849
+ # Each column is a VPacker, vertically packed with legend items.
850
+ # Each legend item is a HPacker packed with:
851
+ # - handlebox: a DrawingArea which contains the legend handle.
852
+ # - labelbox: a TextArea which contains the legend text.
853
+
854
+ text_list = [] # the list of text instances
855
+ handle_list = [] # the list of handle instances
856
+ handles_and_labels = []
857
+
858
+ # The approximate height and descent of text. These values are
859
+ # only used for plotting the legend handle.
860
+ descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic.
861
+ height = fontsize * self.handleheight - descent
862
+ # each handle needs to be drawn inside a box of (x, y, w, h) =
863
+ # (0, -descent, width, height). And their coordinates should
864
+ # be given in the display coordinates.
865
+
866
+ # The transformation of each handle will be automatically set
867
+ # to self.get_transform(). If the artist does not use its
868
+ # default transform (e.g., Collections), you need to
869
+ # manually set their transform to the self.get_transform().
870
+ legend_handler_map = self.get_legend_handler_map()
871
+
872
+ for orig_handle, label in zip(handles, labels):
873
+ handler = self.get_legend_handler(legend_handler_map, orig_handle)
874
+ if handler is None:
875
+ _api.warn_external(
876
+ "Legend does not support handles for "
877
+ f"{type(orig_handle).__name__} "
878
+ "instances.\nA proxy artist may be used "
879
+ "instead.\nSee: https://matplotlib.org/"
880
+ "stable/users/explain/axes/legend_guide.html"
881
+ "#controlling-the-legend-entries")
882
+ # No handle for this artist, so we just defer to None.
883
+ handle_list.append(None)
884
+ else:
885
+ textbox = TextArea(label, multilinebaseline=True,
886
+ textprops=dict(
887
+ verticalalignment='baseline',
888
+ horizontalalignment='left',
889
+ fontproperties=self.prop))
890
+ handlebox = DrawingArea(width=self.handlelength * fontsize,
891
+ height=height,
892
+ xdescent=0., ydescent=descent)
893
+
894
+ text_list.append(textbox._text)
895
+ # Create the artist for the legend which represents the
896
+ # original artist/handle.
897
+ handle_list.append(handler.legend_artist(self, orig_handle,
898
+ fontsize, handlebox))
899
+ handles_and_labels.append((handlebox, textbox))
900
+
901
+ columnbox = []
902
+ # array_split splits n handles_and_labels into ncols columns, with the
903
+ # first n%ncols columns having an extra entry. filter(len, ...)
904
+ # handles the case where n < ncols: the last ncols-n columns are empty
905
+ # and get filtered out.
906
+ for handles_and_labels_column in filter(
907
+ len, np.array_split(handles_and_labels, self._ncols)):
908
+ # pack handlebox and labelbox into itembox
909
+ itemboxes = [HPacker(pad=0,
910
+ sep=self.handletextpad * fontsize,
911
+ children=[h, t] if markerfirst else [t, h],
912
+ align="baseline")
913
+ for h, t in handles_and_labels_column]
914
+ # pack columnbox
915
+ alignment = "baseline" if markerfirst else "right"
916
+ columnbox.append(VPacker(pad=0,
917
+ sep=self.labelspacing * fontsize,
918
+ align=alignment,
919
+ children=itemboxes))
920
+
921
+ mode = "expand" if self._mode == "expand" else "fixed"
922
+ sep = self.columnspacing * fontsize
923
+ self._legend_handle_box = HPacker(pad=0,
924
+ sep=sep, align="baseline",
925
+ mode=mode,
926
+ children=columnbox)
927
+ self._legend_title_box = TextArea("")
928
+ self._legend_box = VPacker(pad=self.borderpad * fontsize,
929
+ sep=self.labelspacing * fontsize,
930
+ align=self._alignment,
931
+ children=[self._legend_title_box,
932
+ self._legend_handle_box])
933
+ self._legend_box.set_figure(self.get_figure(root=False))
934
+ self._legend_box.axes = self.axes
935
+ self.texts = text_list
936
+ self.legend_handles = handle_list
937
+
938
+ def _auto_legend_data(self, renderer):
939
+ """
940
+ Return display coordinates for hit testing for "best" positioning.
941
+
942
+ Returns
943
+ -------
944
+ bboxes
945
+ List of bounding boxes of all patches.
946
+ lines
947
+ List of `.Path` corresponding to each line.
948
+ offsets
949
+ List of (x, y) offsets of all collection.
950
+ """
951
+ assert self.isaxes # always holds, as this is only called internally
952
+ bboxes = []
953
+ lines = []
954
+ offsets = []
955
+ for artist in self.parent._children:
956
+ if isinstance(artist, Line2D):
957
+ lines.append(
958
+ artist.get_transform().transform_path(artist.get_path()))
959
+ elif isinstance(artist, Rectangle):
960
+ bboxes.append(
961
+ artist.get_bbox().transformed(artist.get_data_transform()))
962
+ elif isinstance(artist, Patch):
963
+ lines.append(
964
+ artist.get_transform().transform_path(artist.get_path()))
965
+ elif isinstance(artist, PolyCollection):
966
+ lines.extend(artist.get_transform().transform_path(path)
967
+ for path in artist.get_paths())
968
+ elif isinstance(artist, Collection):
969
+ transform, transOffset, hoffsets, _ = artist._prepare_points()
970
+ if len(hoffsets):
971
+ offsets.extend(transOffset.transform(hoffsets))
972
+ elif isinstance(artist, Text):
973
+ bboxes.append(artist.get_window_extent(renderer))
974
+
975
+ return bboxes, lines, offsets
976
+
977
+ def get_children(self):
978
+ # docstring inherited
979
+ return [self._legend_box, self.get_frame()]
980
+
981
+ def get_frame(self):
982
+ """Return the `~.patches.Rectangle` used to frame the legend."""
983
+ return self.legendPatch
984
+
985
+ def get_lines(self):
986
+ r"""Return the list of `~.lines.Line2D`\s in the legend."""
987
+ return [h for h in self.legend_handles if isinstance(h, Line2D)]
988
+
989
+ def get_patches(self):
990
+ r"""Return the list of `~.patches.Patch`\s in the legend."""
991
+ return silent_list('Patch',
992
+ [h for h in self.legend_handles
993
+ if isinstance(h, Patch)])
994
+
995
+ def get_texts(self):
996
+ r"""Return the list of `~.text.Text`\s in the legend."""
997
+ return silent_list('Text', self.texts)
998
+
999
+ def set_alignment(self, alignment):
1000
+ """
1001
+ Set the alignment of the legend title and the box of entries.
1002
+
1003
+ The entries are aligned as a single block, so that markers always
1004
+ lined up.
1005
+
1006
+ Parameters
1007
+ ----------
1008
+ alignment : {'center', 'left', 'right'}.
1009
+
1010
+ """
1011
+ _api.check_in_list(["center", "left", "right"], alignment=alignment)
1012
+ self._alignment = alignment
1013
+ self._legend_box.align = alignment
1014
+
1015
+ def get_alignment(self):
1016
+ """Get the alignment value of the legend box"""
1017
+ return self._legend_box.align
1018
+
1019
+ def set_title(self, title, prop=None):
1020
+ """
1021
+ Set legend title and title style.
1022
+
1023
+ Parameters
1024
+ ----------
1025
+ title : str
1026
+ The legend title.
1027
+
1028
+ prop : `.font_manager.FontProperties` or `str` or `pathlib.Path`
1029
+ The font properties of the legend title.
1030
+ If a `str`, it is interpreted as a fontconfig pattern parsed by
1031
+ `.FontProperties`. If a `pathlib.Path`, it is interpreted as the
1032
+ absolute path to a font file.
1033
+
1034
+ """
1035
+ self._legend_title_box._text.set_text(title)
1036
+ if title:
1037
+ self._legend_title_box._text.set_visible(True)
1038
+ self._legend_title_box.set_visible(True)
1039
+ else:
1040
+ self._legend_title_box._text.set_visible(False)
1041
+ self._legend_title_box.set_visible(False)
1042
+
1043
+ if prop is not None:
1044
+ self._legend_title_box._text.set_fontproperties(prop)
1045
+
1046
+ self.stale = True
1047
+
1048
+ def get_title(self):
1049
+ """Return the `.Text` instance for the legend title."""
1050
+ return self._legend_title_box._text
1051
+
1052
+ def get_window_extent(self, renderer=None):
1053
+ # docstring inherited
1054
+ if renderer is None:
1055
+ renderer = self.get_figure(root=True)._get_renderer()
1056
+ return self._legend_box.get_window_extent(renderer=renderer)
1057
+
1058
+ def get_tightbbox(self, renderer=None):
1059
+ # docstring inherited
1060
+ return self._legend_box.get_window_extent(renderer)
1061
+
1062
+ def get_frame_on(self):
1063
+ """Get whether the legend box patch is drawn."""
1064
+ return self.legendPatch.get_visible()
1065
+
1066
+ def set_frame_on(self, b):
1067
+ """
1068
+ Set whether the legend box patch is drawn.
1069
+
1070
+ Parameters
1071
+ ----------
1072
+ b : bool
1073
+ """
1074
+ self.legendPatch.set_visible(b)
1075
+ self.stale = True
1076
+
1077
+ draw_frame = set_frame_on # Backcompat alias.
1078
+
1079
+ def get_bbox_to_anchor(self):
1080
+ """Return the bbox that the legend will be anchored to."""
1081
+ if self._bbox_to_anchor is None:
1082
+ return self.parent.bbox
1083
+ else:
1084
+ return self._bbox_to_anchor
1085
+
1086
+ def set_bbox_to_anchor(self, bbox, transform=None):
1087
+ """
1088
+ Set the bbox that the legend will be anchored to.
1089
+
1090
+ Parameters
1091
+ ----------
1092
+ bbox : `~matplotlib.transforms.BboxBase` or tuple
1093
+ The bounding box can be specified in the following ways:
1094
+
1095
+ - A `.BboxBase` instance
1096
+ - A tuple of ``(left, bottom, width, height)`` in the given
1097
+ transform (normalized axes coordinate if None)
1098
+ - A tuple of ``(left, bottom)`` where the width and height will be
1099
+ assumed to be zero.
1100
+ - *None*, to remove the bbox anchoring, and use the parent bbox.
1101
+
1102
+ transform : `~matplotlib.transforms.Transform`, optional
1103
+ A transform to apply to the bounding box. If not specified, this
1104
+ will use a transform to the bounding box of the parent.
1105
+ """
1106
+ if bbox is None:
1107
+ self._bbox_to_anchor = None
1108
+ return
1109
+ elif isinstance(bbox, BboxBase):
1110
+ self._bbox_to_anchor = bbox
1111
+ else:
1112
+ try:
1113
+ l = len(bbox)
1114
+ except TypeError as err:
1115
+ raise ValueError(f"Invalid bbox: {bbox}") from err
1116
+
1117
+ if l == 2:
1118
+ bbox = [bbox[0], bbox[1], 0, 0]
1119
+
1120
+ self._bbox_to_anchor = Bbox.from_bounds(*bbox)
1121
+
1122
+ if transform is None:
1123
+ transform = BboxTransformTo(self.parent.bbox)
1124
+
1125
+ self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor,
1126
+ transform)
1127
+ self.stale = True
1128
+
1129
+ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
1130
+ """
1131
+ Place the *bbox* inside the *parentbbox* according to a given
1132
+ location code. Return the (x, y) coordinate of the bbox.
1133
+
1134
+ Parameters
1135
+ ----------
1136
+ loc : int
1137
+ A location code in range(1, 11). This corresponds to the possible
1138
+ values for ``self._loc``, excluding "best".
1139
+ bbox : `~matplotlib.transforms.Bbox`
1140
+ bbox to be placed, in display coordinates.
1141
+ parentbbox : `~matplotlib.transforms.Bbox`
1142
+ A parent box which will contain the bbox, in display coordinates.
1143
+ """
1144
+ return offsetbox._get_anchored_bbox(
1145
+ loc, bbox, parentbbox,
1146
+ self.borderaxespad * renderer.points_to_pixels(self._fontsize))
1147
+
1148
+ def _find_best_position(self, width, height, renderer):
1149
+ """Determine the best location to place the legend."""
1150
+ assert self.isaxes # always holds, as this is only called internally
1151
+
1152
+ start_time = time.perf_counter()
1153
+
1154
+ bboxes, lines, offsets = self._auto_legend_data(renderer)
1155
+
1156
+ bbox = Bbox.from_bounds(0, 0, width, height)
1157
+
1158
+ candidates = []
1159
+ for idx in range(1, len(self.codes)):
1160
+ l, b = self._get_anchored_bbox(idx, bbox,
1161
+ self.get_bbox_to_anchor(),
1162
+ renderer)
1163
+ legendBox = Bbox.from_bounds(l, b, width, height)
1164
+ # XXX TODO: If markers are present, it would be good to take them
1165
+ # into account when checking vertex overlaps in the next line.
1166
+ badness = (sum(legendBox.count_contains(line.vertices)
1167
+ for line in lines)
1168
+ + legendBox.count_contains(offsets)
1169
+ + legendBox.count_overlaps(bboxes)
1170
+ + sum(line.intersects_bbox(legendBox, filled=False)
1171
+ for line in lines))
1172
+ # Include the index to favor lower codes in case of a tie.
1173
+ candidates.append((badness, idx, (l, b)))
1174
+ if badness == 0:
1175
+ break
1176
+
1177
+ _, _, (l, b) = min(candidates)
1178
+
1179
+ if self._loc_used_default and time.perf_counter() - start_time > 1:
1180
+ _api.warn_external(
1181
+ 'Creating legend with loc="best" can be slow with large '
1182
+ 'amounts of data.')
1183
+
1184
+ return l, b
1185
+
1186
+ def contains(self, mouseevent):
1187
+ return self.legendPatch.contains(mouseevent)
1188
+
1189
+ def set_draggable(self, state, use_blit=False, update='loc'):
1190
+ """
1191
+ Enable or disable mouse dragging support of the legend.
1192
+
1193
+ Parameters
1194
+ ----------
1195
+ state : bool
1196
+ Whether mouse dragging is enabled.
1197
+ use_blit : bool, optional
1198
+ Use blitting for faster image composition. For details see
1199
+ :ref:`func-animation`.
1200
+ update : {'loc', 'bbox'}, optional
1201
+ The legend parameter to be changed when dragged:
1202
+
1203
+ - 'loc': update the *loc* parameter of the legend
1204
+ - 'bbox': update the *bbox_to_anchor* parameter of the legend
1205
+
1206
+ Returns
1207
+ -------
1208
+ `.DraggableLegend` or *None*
1209
+ If *state* is ``True`` this returns the `.DraggableLegend` helper
1210
+ instance. Otherwise this returns *None*.
1211
+ """
1212
+ if state:
1213
+ if self._draggable is None:
1214
+ self._draggable = DraggableLegend(self,
1215
+ use_blit,
1216
+ update=update)
1217
+ else:
1218
+ if self._draggable is not None:
1219
+ self._draggable.disconnect()
1220
+ self._draggable = None
1221
+ return self._draggable
1222
+
1223
+ def get_draggable(self):
1224
+ """Return ``True`` if the legend is draggable, ``False`` otherwise."""
1225
+ return self._draggable is not None
1226
+
1227
+
1228
+ # Helper functions to parse legend arguments for both `figure.legend` and
1229
+ # `axes.legend`:
1230
+ def _get_legend_handles(axs, legend_handler_map=None):
1231
+ """Yield artists that can be used as handles in a legend."""
1232
+ handles_original = []
1233
+ for ax in axs:
1234
+ handles_original += [
1235
+ *(a for a in ax._children
1236
+ if isinstance(a, (Line2D, Patch, Collection, Text))),
1237
+ *ax.containers]
1238
+ # support parasite Axes:
1239
+ if hasattr(ax, 'parasites'):
1240
+ for axx in ax.parasites:
1241
+ handles_original += [
1242
+ *(a for a in axx._children
1243
+ if isinstance(a, (Line2D, Patch, Collection, Text))),
1244
+ *axx.containers]
1245
+
1246
+ handler_map = {**Legend.get_default_handler_map(),
1247
+ **(legend_handler_map or {})}
1248
+ has_handler = Legend.get_legend_handler
1249
+ for handle in handles_original:
1250
+ label = handle.get_label()
1251
+ if label != '_nolegend_' and has_handler(handler_map, handle):
1252
+ yield handle
1253
+ elif (label and not label.startswith('_') and
1254
+ not has_handler(handler_map, handle)):
1255
+ _api.warn_external(
1256
+ "Legend does not support handles for "
1257
+ f"{type(handle).__name__} "
1258
+ "instances.\nSee: https://matplotlib.org/stable/"
1259
+ "tutorials/intermediate/legend_guide.html"
1260
+ "#implementing-a-custom-legend-handler")
1261
+ continue
1262
+
1263
+
1264
+ def _get_legend_handles_labels(axs, legend_handler_map=None):
1265
+ """Return handles and labels for legend."""
1266
+ handles = []
1267
+ labels = []
1268
+ for handle in _get_legend_handles(axs, legend_handler_map):
1269
+ label = handle.get_label()
1270
+ if label and not label.startswith('_'):
1271
+ handles.append(handle)
1272
+ labels.append(label)
1273
+ return handles, labels
1274
+
1275
+
1276
+ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs):
1277
+ """
1278
+ Get the handles and labels from the calls to either ``figure.legend``
1279
+ or ``axes.legend``.
1280
+
1281
+ The parser is a bit involved because we support::
1282
+
1283
+ legend()
1284
+ legend(labels)
1285
+ legend(handles, labels)
1286
+ legend(labels=labels)
1287
+ legend(handles=handles)
1288
+ legend(handles=handles, labels=labels)
1289
+
1290
+ The behavior for a mixture of positional and keyword handles and labels
1291
+ is undefined and issues a warning; it will be an error in the future.
1292
+
1293
+ Parameters
1294
+ ----------
1295
+ axs : list of `.Axes`
1296
+ If handles are not given explicitly, the artists in these Axes are
1297
+ used as handles.
1298
+ *args : tuple
1299
+ Positional parameters passed to ``legend()``.
1300
+ handles
1301
+ The value of the keyword argument ``legend(handles=...)``, or *None*
1302
+ if that keyword argument was not used.
1303
+ labels
1304
+ The value of the keyword argument ``legend(labels=...)``, or *None*
1305
+ if that keyword argument was not used.
1306
+ **kwargs
1307
+ All other keyword arguments passed to ``legend()``.
1308
+
1309
+ Returns
1310
+ -------
1311
+ handles : list of (`.Artist` or tuple of `.Artist`)
1312
+ The legend handles.
1313
+ labels : list of str
1314
+ The legend labels.
1315
+ kwargs : dict
1316
+ *kwargs* with keywords handles and labels removed.
1317
+
1318
+ """
1319
+ log = logging.getLogger(__name__)
1320
+
1321
+ handlers = kwargs.get('handler_map')
1322
+
1323
+ if (handles is not None or labels is not None) and args:
1324
+ _api.warn_deprecated("3.9", message=(
1325
+ "You have mixed positional and keyword arguments, some input may "
1326
+ "be discarded. This is deprecated since %(since)s and will "
1327
+ "become an error in %(removal)s."))
1328
+
1329
+ if (hasattr(handles, "__len__") and
1330
+ hasattr(labels, "__len__") and
1331
+ len(handles) != len(labels)):
1332
+ _api.warn_external(f"Mismatched number of handles and labels: "
1333
+ f"len(handles) = {len(handles)} "
1334
+ f"len(labels) = {len(labels)}")
1335
+ # if got both handles and labels as kwargs, make same length
1336
+ if handles and labels:
1337
+ handles, labels = zip(*zip(handles, labels))
1338
+
1339
+ elif handles is not None and labels is None:
1340
+ labels = [handle.get_label() for handle in handles]
1341
+
1342
+ elif labels is not None and handles is None:
1343
+ # Get as many handles as there are labels.
1344
+ handles = [handle for handle, label
1345
+ in zip(_get_legend_handles(axs, handlers), labels)]
1346
+
1347
+ elif len(args) == 0: # 0 args: automatically detect labels and handles.
1348
+ handles, labels = _get_legend_handles_labels(axs, handlers)
1349
+ if not handles:
1350
+ _api.warn_external(
1351
+ "No artists with labels found to put in legend. Note that "
1352
+ "artists whose label start with an underscore are ignored "
1353
+ "when legend() is called with no argument.")
1354
+
1355
+ elif len(args) == 1: # 1 arg: user defined labels, automatic handle detection.
1356
+ labels, = args
1357
+ if any(isinstance(l, Artist) for l in labels):
1358
+ raise TypeError("A single argument passed to legend() must be a "
1359
+ "list of labels, but found an Artist in there.")
1360
+
1361
+ # Get as many handles as there are labels.
1362
+ handles = [handle for handle, label
1363
+ in zip(_get_legend_handles(axs, handlers), labels)]
1364
+
1365
+ elif len(args) == 2: # 2 args: user defined handles and labels.
1366
+ handles, labels = args[:2]
1367
+
1368
+ else:
1369
+ raise _api.nargs_error('legend', '0-2', len(args))
1370
+
1371
+ return handles, labels, kwargs
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from matplotlib.axes import Axes
2
+ from matplotlib.artist import Artist
3
+ from matplotlib.backend_bases import MouseEvent
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.font_manager import FontProperties
6
+ from matplotlib.legend_handler import HandlerBase
7
+ from matplotlib.lines import Line2D
8
+ from matplotlib.offsetbox import (
9
+ DraggableOffsetBox,
10
+ )
11
+ from matplotlib.patches import FancyBboxPatch, Patch, Rectangle
12
+ from matplotlib.text import Text
13
+ from matplotlib.transforms import (
14
+ BboxBase,
15
+ Transform,
16
+ )
17
+
18
+
19
+ import pathlib
20
+ from collections.abc import Iterable
21
+ from typing import Any, Literal, overload
22
+ from .typing import ColorType
23
+
24
+ class DraggableLegend(DraggableOffsetBox):
25
+ legend: Legend
26
+ def __init__(
27
+ self, legend: Legend, use_blit: bool = ..., update: Literal["loc", "bbox"] = ...
28
+ ) -> None: ...
29
+ def finalize_offset(self) -> None: ...
30
+
31
+ class Legend(Artist):
32
+ codes: dict[str, int]
33
+ zorder: float
34
+ prop: FontProperties
35
+ texts: list[Text]
36
+ legend_handles: list[Artist | None]
37
+ numpoints: int
38
+ markerscale: float
39
+ scatterpoints: int
40
+ borderpad: float
41
+ labelspacing: float
42
+ handlelength: float
43
+ handleheight: float
44
+ handletextpad: float
45
+ borderaxespad: float
46
+ columnspacing: float
47
+ shadow: bool
48
+ isaxes: bool
49
+ axes: Axes
50
+ parent: Axes | Figure
51
+ legendPatch: FancyBboxPatch
52
+ def __init__(
53
+ self,
54
+ parent: Axes | Figure,
55
+ handles: Iterable[Artist | tuple[Artist, ...]],
56
+ labels: Iterable[str],
57
+ *,
58
+ loc: str | tuple[float, float] | int | None = ...,
59
+ numpoints: int | None = ...,
60
+ markerscale: float | None = ...,
61
+ markerfirst: bool = ...,
62
+ reverse: bool = ...,
63
+ scatterpoints: int | None = ...,
64
+ scatteryoffsets: Iterable[float] | None = ...,
65
+ prop: FontProperties | dict[str, Any] | None = ...,
66
+ fontsize: float | str | None = ...,
67
+ labelcolor: ColorType
68
+ | Iterable[ColorType]
69
+ | Literal["linecolor", "markerfacecolor", "mfc", "markeredgecolor", "mec"]
70
+ | None = ...,
71
+ borderpad: float | None = ...,
72
+ labelspacing: float | None = ...,
73
+ handlelength: float | None = ...,
74
+ handleheight: float | None = ...,
75
+ handletextpad: float | None = ...,
76
+ borderaxespad: float | None = ...,
77
+ columnspacing: float | None = ...,
78
+ ncols: int = ...,
79
+ mode: Literal["expand"] | None = ...,
80
+ fancybox: bool | None = ...,
81
+ shadow: bool | dict[str, Any] | None = ...,
82
+ title: str | None = ...,
83
+ title_fontsize: float | None = ...,
84
+ framealpha: float | None = ...,
85
+ edgecolor: Literal["inherit"] | ColorType | None = ...,
86
+ facecolor: Literal["inherit"] | ColorType | None = ...,
87
+ bbox_to_anchor: BboxBase
88
+ | tuple[float, float]
89
+ | tuple[float, float, float, float]
90
+ | None = ...,
91
+ bbox_transform: Transform | None = ...,
92
+ frameon: bool | None = ...,
93
+ handler_map: dict[Artist | type, HandlerBase] | None = ...,
94
+ title_fontproperties: FontProperties | dict[str, Any] | None = ...,
95
+ alignment: Literal["center", "left", "right"] = ...,
96
+ ncol: int = ...,
97
+ draggable: bool = ...
98
+ ) -> None: ...
99
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict[Any, Any]]: ...
100
+ def set_ncols(self, ncols: int) -> None: ...
101
+ @classmethod
102
+ def get_default_handler_map(cls) -> dict[type, HandlerBase]: ...
103
+ @classmethod
104
+ def set_default_handler_map(cls, handler_map: dict[type, HandlerBase]) -> None: ...
105
+ @classmethod
106
+ def update_default_handler_map(
107
+ cls, handler_map: dict[type, HandlerBase]
108
+ ) -> None: ...
109
+ def get_legend_handler_map(self) -> dict[type, HandlerBase]: ...
110
+ @staticmethod
111
+ def get_legend_handler(
112
+ legend_handler_map: dict[type, HandlerBase], orig_handle: Any
113
+ ) -> HandlerBase | None: ...
114
+ def get_children(self) -> list[Artist]: ...
115
+ def get_frame(self) -> Rectangle: ...
116
+ def get_lines(self) -> list[Line2D]: ...
117
+ def get_patches(self) -> list[Patch]: ...
118
+ def get_texts(self) -> list[Text]: ...
119
+ def set_alignment(self, alignment: Literal["center", "left", "right"]) -> None: ...
120
+ def get_alignment(self) -> Literal["center", "left", "right"]: ...
121
+ def set_loc(self, loc: str | tuple[float, float] | int | None = ...) -> None: ...
122
+ def set_title(
123
+ self, title: str, prop: FontProperties | str | pathlib.Path | None = ...
124
+ ) -> None: ...
125
+ def get_title(self) -> Text: ...
126
+ def get_frame_on(self) -> bool: ...
127
+ def set_frame_on(self, b: bool) -> None: ...
128
+ draw_frame = set_frame_on
129
+ def get_bbox_to_anchor(self) -> BboxBase: ...
130
+ def set_bbox_to_anchor(
131
+ self,
132
+ bbox: BboxBase
133
+ | tuple[float, float]
134
+ | tuple[float, float, float, float]
135
+ | None,
136
+ transform: Transform | None = ...
137
+ ) -> None: ...
138
+ @overload
139
+ def set_draggable(
140
+ self,
141
+ state: Literal[True],
142
+ use_blit: bool = ...,
143
+ update: Literal["loc", "bbox"] = ...,
144
+ ) -> DraggableLegend: ...
145
+ @overload
146
+ def set_draggable(
147
+ self,
148
+ state: Literal[False],
149
+ use_blit: bool = ...,
150
+ update: Literal["loc", "bbox"] = ...,
151
+ ) -> None: ...
152
+ def get_draggable(self) -> bool: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py ADDED
@@ -0,0 +1,812 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Default legend handlers.
3
+
4
+ .. important::
5
+
6
+ This is a low-level legend API, which most end users do not need.
7
+
8
+ We recommend that you are familiar with the :ref:`legend guide
9
+ <legend_guide>` before reading this documentation.
10
+
11
+ Legend handlers are expected to be a callable object with a following
12
+ signature::
13
+
14
+ legend_handler(legend, orig_handle, fontsize, handlebox)
15
+
16
+ Where *legend* is the legend itself, *orig_handle* is the original
17
+ plot, *fontsize* is the fontsize in pixels, and *handlebox* is an
18
+ `.OffsetBox` instance. Within the call, you should create relevant
19
+ artists (using relevant properties from the *legend* and/or
20
+ *orig_handle*) and add them into the *handlebox*. The artists need to
21
+ be scaled according to the *fontsize* (note that the size is in pixels,
22
+ i.e., this is dpi-scaled value).
23
+
24
+ This module includes definition of several legend handler classes
25
+ derived from the base class (HandlerBase) with the following method::
26
+
27
+ def legend_artist(self, legend, orig_handle, fontsize, handlebox)
28
+ """
29
+
30
+ from itertools import cycle
31
+
32
+ import numpy as np
33
+
34
+ from matplotlib import cbook
35
+ from matplotlib.lines import Line2D
36
+ from matplotlib.patches import Rectangle
37
+ import matplotlib.collections as mcoll
38
+
39
+
40
+ def update_from_first_child(tgt, src):
41
+ first_child = next(iter(src.get_children()), None)
42
+ if first_child is not None:
43
+ tgt.update_from(first_child)
44
+
45
+
46
+ class HandlerBase:
47
+ """
48
+ A base class for default legend handlers.
49
+
50
+ The derived classes are meant to override *create_artists* method, which
51
+ has the following signature::
52
+
53
+ def create_artists(self, legend, orig_handle,
54
+ xdescent, ydescent, width, height, fontsize,
55
+ trans):
56
+
57
+ The overridden method needs to create artists of the given
58
+ transform that fits in the given dimension (xdescent, ydescent,
59
+ width, height) that are scaled by fontsize if necessary.
60
+
61
+ """
62
+ def __init__(self, xpad=0., ypad=0., update_func=None):
63
+ """
64
+ Parameters
65
+ ----------
66
+ xpad : float, optional
67
+ Padding in x-direction.
68
+ ypad : float, optional
69
+ Padding in y-direction.
70
+ update_func : callable, optional
71
+ Function for updating the legend handler properties from another
72
+ legend handler, used by `~HandlerBase.update_prop`.
73
+ """
74
+ self._xpad, self._ypad = xpad, ypad
75
+ self._update_prop_func = update_func
76
+
77
+ def _update_prop(self, legend_handle, orig_handle):
78
+ if self._update_prop_func is None:
79
+ self._default_update_prop(legend_handle, orig_handle)
80
+ else:
81
+ self._update_prop_func(legend_handle, orig_handle)
82
+
83
+ def _default_update_prop(self, legend_handle, orig_handle):
84
+ legend_handle.update_from(orig_handle)
85
+
86
+ def update_prop(self, legend_handle, orig_handle, legend):
87
+
88
+ self._update_prop(legend_handle, orig_handle)
89
+
90
+ legend._set_artist_props(legend_handle)
91
+ legend_handle.set_clip_box(None)
92
+ legend_handle.set_clip_path(None)
93
+
94
+ def adjust_drawing_area(self, legend, orig_handle,
95
+ xdescent, ydescent, width, height, fontsize,
96
+ ):
97
+ xdescent = xdescent - self._xpad * fontsize
98
+ ydescent = ydescent - self._ypad * fontsize
99
+ width = width - self._xpad * fontsize
100
+ height = height - self._ypad * fontsize
101
+ return xdescent, ydescent, width, height
102
+
103
+ def legend_artist(self, legend, orig_handle,
104
+ fontsize, handlebox):
105
+ """
106
+ Return the artist that this HandlerBase generates for the given
107
+ original artist/handle.
108
+
109
+ Parameters
110
+ ----------
111
+ legend : `~matplotlib.legend.Legend`
112
+ The legend for which these legend artists are being created.
113
+ orig_handle : :class:`matplotlib.artist.Artist` or similar
114
+ The object for which these legend artists are being created.
115
+ fontsize : int
116
+ The fontsize in pixels. The artists being created should
117
+ be scaled according to the given fontsize.
118
+ handlebox : `~matplotlib.offsetbox.OffsetBox`
119
+ The box which has been created to hold this legend entry's
120
+ artists. Artists created in the `legend_artist` method must
121
+ be added to this handlebox inside this method.
122
+
123
+ """
124
+ xdescent, ydescent, width, height = self.adjust_drawing_area(
125
+ legend, orig_handle,
126
+ handlebox.xdescent, handlebox.ydescent,
127
+ handlebox.width, handlebox.height,
128
+ fontsize)
129
+ artists = self.create_artists(legend, orig_handle,
130
+ xdescent, ydescent, width, height,
131
+ fontsize, handlebox.get_transform())
132
+
133
+ # create_artists will return a list of artists.
134
+ for a in artists:
135
+ handlebox.add_artist(a)
136
+
137
+ # we only return the first artist
138
+ return artists[0]
139
+
140
+ def create_artists(self, legend, orig_handle,
141
+ xdescent, ydescent, width, height, fontsize,
142
+ trans):
143
+ """
144
+ Return the legend artists generated.
145
+
146
+ Parameters
147
+ ----------
148
+ legend : `~matplotlib.legend.Legend`
149
+ The legend for which these legend artists are being created.
150
+ orig_handle : `~matplotlib.artist.Artist` or similar
151
+ The object for which these legend artists are being created.
152
+ xdescent, ydescent, width, height : int
153
+ The rectangle (*xdescent*, *ydescent*, *width*, *height*) that the
154
+ legend artists being created should fit within.
155
+ fontsize : int
156
+ The fontsize in pixels. The legend artists being created should
157
+ be scaled according to the given fontsize.
158
+ trans : `~matplotlib.transforms.Transform`
159
+ The transform that is applied to the legend artists being created.
160
+ Typically from unit coordinates in the handler box to screen
161
+ coordinates.
162
+ """
163
+ raise NotImplementedError('Derived must override')
164
+
165
+
166
+ class HandlerNpoints(HandlerBase):
167
+ """
168
+ A legend handler that shows *numpoints* points in the legend entry.
169
+ """
170
+
171
+ def __init__(self, marker_pad=0.3, numpoints=None, **kwargs):
172
+ """
173
+ Parameters
174
+ ----------
175
+ marker_pad : float
176
+ Padding between points in legend entry.
177
+ numpoints : int
178
+ Number of points to show in legend entry.
179
+ **kwargs
180
+ Keyword arguments forwarded to `.HandlerBase`.
181
+ """
182
+ super().__init__(**kwargs)
183
+
184
+ self._numpoints = numpoints
185
+ self._marker_pad = marker_pad
186
+
187
+ def get_numpoints(self, legend):
188
+ if self._numpoints is None:
189
+ return legend.numpoints
190
+ else:
191
+ return self._numpoints
192
+
193
+ def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
194
+ numpoints = self.get_numpoints(legend)
195
+ if numpoints > 1:
196
+ # we put some pad here to compensate the size of the marker
197
+ pad = self._marker_pad * fontsize
198
+ xdata = np.linspace(-xdescent + pad,
199
+ -xdescent + width - pad,
200
+ numpoints)
201
+ xdata_marker = xdata
202
+ else:
203
+ xdata = [-xdescent, -xdescent + width]
204
+ xdata_marker = [-xdescent + 0.5 * width]
205
+ return xdata, xdata_marker
206
+
207
+
208
+ class HandlerNpointsYoffsets(HandlerNpoints):
209
+ """
210
+ A legend handler that shows *numpoints* in the legend, and allows them to
211
+ be individually offset in the y-direction.
212
+ """
213
+
214
+ def __init__(self, numpoints=None, yoffsets=None, **kwargs):
215
+ """
216
+ Parameters
217
+ ----------
218
+ numpoints : int
219
+ Number of points to show in legend entry.
220
+ yoffsets : array of floats
221
+ Length *numpoints* list of y offsets for each point in
222
+ legend entry.
223
+ **kwargs
224
+ Keyword arguments forwarded to `.HandlerNpoints`.
225
+ """
226
+ super().__init__(numpoints=numpoints, **kwargs)
227
+ self._yoffsets = yoffsets
228
+
229
+ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
230
+ if self._yoffsets is None:
231
+ ydata = height * legend._scatteryoffsets
232
+ else:
233
+ ydata = height * np.asarray(self._yoffsets)
234
+
235
+ return ydata
236
+
237
+
238
+ class HandlerLine2DCompound(HandlerNpoints):
239
+ """
240
+ Original handler for `.Line2D` instances, that relies on combining
241
+ a line-only with a marker-only artist. May be deprecated in the future.
242
+ """
243
+
244
+ def create_artists(self, legend, orig_handle,
245
+ xdescent, ydescent, width, height, fontsize,
246
+ trans):
247
+ # docstring inherited
248
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
249
+ width, height, fontsize)
250
+
251
+ ydata = np.full_like(xdata, ((height - ydescent) / 2))
252
+ legline = Line2D(xdata, ydata)
253
+
254
+ self.update_prop(legline, orig_handle, legend)
255
+ legline.set_drawstyle('default')
256
+ legline.set_marker("")
257
+
258
+ legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)])
259
+ self.update_prop(legline_marker, orig_handle, legend)
260
+ legline_marker.set_linestyle('None')
261
+ if legend.markerscale != 1:
262
+ newsz = legline_marker.get_markersize() * legend.markerscale
263
+ legline_marker.set_markersize(newsz)
264
+ # we don't want to add this to the return list because
265
+ # the texts and handles are assumed to be in one-to-one
266
+ # correspondence.
267
+ legline._legmarker = legline_marker
268
+
269
+ legline.set_transform(trans)
270
+ legline_marker.set_transform(trans)
271
+
272
+ return [legline, legline_marker]
273
+
274
+
275
+ class HandlerLine2D(HandlerNpoints):
276
+ """
277
+ Handler for `.Line2D` instances.
278
+
279
+ See Also
280
+ --------
281
+ HandlerLine2DCompound : An earlier handler implementation, which used one
282
+ artist for the line and another for the marker(s).
283
+ """
284
+
285
+ def create_artists(self, legend, orig_handle,
286
+ xdescent, ydescent, width, height, fontsize,
287
+ trans):
288
+ # docstring inherited
289
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
290
+ width, height, fontsize)
291
+
292
+ markevery = None
293
+ if self.get_numpoints(legend) == 1:
294
+ # Special case: one wants a single marker in the center
295
+ # and a line that extends on both sides. One will use a
296
+ # 3 points line, but only mark the #1 (i.e. middle) point.
297
+ xdata = np.linspace(xdata[0], xdata[-1], 3)
298
+ markevery = [1]
299
+
300
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
301
+ legline = Line2D(xdata, ydata, markevery=markevery)
302
+
303
+ self.update_prop(legline, orig_handle, legend)
304
+
305
+ if legend.markerscale != 1:
306
+ newsz = legline.get_markersize() * legend.markerscale
307
+ legline.set_markersize(newsz)
308
+
309
+ legline.set_transform(trans)
310
+
311
+ return [legline]
312
+
313
+
314
+ class HandlerPatch(HandlerBase):
315
+ """
316
+ Handler for `.Patch` instances.
317
+ """
318
+
319
+ def __init__(self, patch_func=None, **kwargs):
320
+ """
321
+ Parameters
322
+ ----------
323
+ patch_func : callable, optional
324
+ The function that creates the legend key artist.
325
+ *patch_func* should have the signature::
326
+
327
+ def patch_func(legend=legend, orig_handle=orig_handle,
328
+ xdescent=xdescent, ydescent=ydescent,
329
+ width=width, height=height, fontsize=fontsize)
330
+
331
+ Subsequently, the created artist will have its ``update_prop``
332
+ method called and the appropriate transform will be applied.
333
+
334
+ **kwargs
335
+ Keyword arguments forwarded to `.HandlerBase`.
336
+ """
337
+ super().__init__(**kwargs)
338
+ self._patch_func = patch_func
339
+
340
+ def _create_patch(self, legend, orig_handle,
341
+ xdescent, ydescent, width, height, fontsize):
342
+ if self._patch_func is None:
343
+ p = Rectangle(xy=(-xdescent, -ydescent),
344
+ width=width, height=height)
345
+ else:
346
+ p = self._patch_func(legend=legend, orig_handle=orig_handle,
347
+ xdescent=xdescent, ydescent=ydescent,
348
+ width=width, height=height, fontsize=fontsize)
349
+ return p
350
+
351
+ def create_artists(self, legend, orig_handle,
352
+ xdescent, ydescent, width, height, fontsize, trans):
353
+ # docstring inherited
354
+ p = self._create_patch(legend, orig_handle,
355
+ xdescent, ydescent, width, height, fontsize)
356
+ self.update_prop(p, orig_handle, legend)
357
+ p.set_transform(trans)
358
+ return [p]
359
+
360
+
361
+ class HandlerStepPatch(HandlerBase):
362
+ """
363
+ Handler for `~.matplotlib.patches.StepPatch` instances.
364
+ """
365
+
366
+ @staticmethod
367
+ def _create_patch(orig_handle, xdescent, ydescent, width, height):
368
+ return Rectangle(xy=(-xdescent, -ydescent), width=width,
369
+ height=height, color=orig_handle.get_facecolor())
370
+
371
+ @staticmethod
372
+ def _create_line(orig_handle, width, height):
373
+ # Unfilled StepPatch should show as a line
374
+ legline = Line2D([0, width], [height/2, height/2],
375
+ color=orig_handle.get_edgecolor(),
376
+ linestyle=orig_handle.get_linestyle(),
377
+ linewidth=orig_handle.get_linewidth(),
378
+ )
379
+
380
+ # Overwrite manually because patch and line properties don't mix
381
+ legline.set_drawstyle('default')
382
+ legline.set_marker("")
383
+ return legline
384
+
385
+ def create_artists(self, legend, orig_handle,
386
+ xdescent, ydescent, width, height, fontsize, trans):
387
+ # docstring inherited
388
+ if orig_handle.get_fill() or (orig_handle.get_hatch() is not None):
389
+ p = self._create_patch(orig_handle, xdescent, ydescent, width,
390
+ height)
391
+ self.update_prop(p, orig_handle, legend)
392
+ else:
393
+ p = self._create_line(orig_handle, width, height)
394
+ p.set_transform(trans)
395
+ return [p]
396
+
397
+
398
+ class HandlerLineCollection(HandlerLine2D):
399
+ """
400
+ Handler for `.LineCollection` instances.
401
+ """
402
+ def get_numpoints(self, legend):
403
+ if self._numpoints is None:
404
+ return legend.scatterpoints
405
+ else:
406
+ return self._numpoints
407
+
408
+ def _default_update_prop(self, legend_handle, orig_handle):
409
+ lw = orig_handle.get_linewidths()[0]
410
+ dashes = orig_handle._us_linestyles[0]
411
+ color = orig_handle.get_colors()[0]
412
+ legend_handle.set_color(color)
413
+ legend_handle.set_linestyle(dashes)
414
+ legend_handle.set_linewidth(lw)
415
+
416
+ def create_artists(self, legend, orig_handle,
417
+ xdescent, ydescent, width, height, fontsize, trans):
418
+ # docstring inherited
419
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
420
+ width, height, fontsize)
421
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
422
+ legline = Line2D(xdata, ydata)
423
+
424
+ self.update_prop(legline, orig_handle, legend)
425
+ legline.set_transform(trans)
426
+
427
+ return [legline]
428
+
429
+
430
+ class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
431
+ r"""Handler for `.RegularPolyCollection`\s."""
432
+
433
+ def __init__(self, yoffsets=None, sizes=None, **kwargs):
434
+ super().__init__(yoffsets=yoffsets, **kwargs)
435
+
436
+ self._sizes = sizes
437
+
438
+ def get_numpoints(self, legend):
439
+ if self._numpoints is None:
440
+ return legend.scatterpoints
441
+ else:
442
+ return self._numpoints
443
+
444
+ def get_sizes(self, legend, orig_handle,
445
+ xdescent, ydescent, width, height, fontsize):
446
+ if self._sizes is None:
447
+ handle_sizes = orig_handle.get_sizes()
448
+ if not len(handle_sizes):
449
+ handle_sizes = [1]
450
+ size_max = max(handle_sizes) * legend.markerscale ** 2
451
+ size_min = min(handle_sizes) * legend.markerscale ** 2
452
+
453
+ numpoints = self.get_numpoints(legend)
454
+ if numpoints < 4:
455
+ sizes = [.5 * (size_max + size_min), size_max,
456
+ size_min][:numpoints]
457
+ else:
458
+ rng = (size_max - size_min)
459
+ sizes = rng * np.linspace(0, 1, numpoints) + size_min
460
+ else:
461
+ sizes = self._sizes
462
+
463
+ return sizes
464
+
465
+ def update_prop(self, legend_handle, orig_handle, legend):
466
+
467
+ self._update_prop(legend_handle, orig_handle)
468
+
469
+ legend_handle.set_figure(legend.get_figure(root=False))
470
+ # legend._set_artist_props(legend_handle)
471
+ legend_handle.set_clip_box(None)
472
+ legend_handle.set_clip_path(None)
473
+
474
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
475
+ return type(orig_handle)(
476
+ orig_handle.get_numsides(),
477
+ rotation=orig_handle.get_rotation(), sizes=sizes,
478
+ offsets=offsets, offset_transform=offset_transform,
479
+ )
480
+
481
+ def create_artists(self, legend, orig_handle,
482
+ xdescent, ydescent, width, height, fontsize,
483
+ trans):
484
+ # docstring inherited
485
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
486
+ width, height, fontsize)
487
+
488
+ ydata = self.get_ydata(legend, xdescent, ydescent,
489
+ width, height, fontsize)
490
+
491
+ sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent,
492
+ width, height, fontsize)
493
+
494
+ p = self.create_collection(
495
+ orig_handle, sizes,
496
+ offsets=list(zip(xdata_marker, ydata)), offset_transform=trans)
497
+
498
+ self.update_prop(p, orig_handle, legend)
499
+ p.set_offset_transform(trans)
500
+ return [p]
501
+
502
+
503
+ class HandlerPathCollection(HandlerRegularPolyCollection):
504
+ r"""Handler for `.PathCollection`\s, which are used by `~.Axes.scatter`."""
505
+
506
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
507
+ return type(orig_handle)(
508
+ [orig_handle.get_paths()[0]], sizes=sizes,
509
+ offsets=offsets, offset_transform=offset_transform,
510
+ )
511
+
512
+
513
+ class HandlerCircleCollection(HandlerRegularPolyCollection):
514
+ r"""Handler for `.CircleCollection`\s."""
515
+
516
+ def create_collection(self, orig_handle, sizes, offsets, offset_transform):
517
+ return type(orig_handle)(
518
+ sizes, offsets=offsets, offset_transform=offset_transform)
519
+
520
+
521
+ class HandlerErrorbar(HandlerLine2D):
522
+ """Handler for Errorbars."""
523
+
524
+ def __init__(self, xerr_size=0.5, yerr_size=None,
525
+ marker_pad=0.3, numpoints=None, **kwargs):
526
+
527
+ self._xerr_size = xerr_size
528
+ self._yerr_size = yerr_size
529
+
530
+ super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kwargs)
531
+
532
+ def get_err_size(self, legend, xdescent, ydescent,
533
+ width, height, fontsize):
534
+ xerr_size = self._xerr_size * fontsize
535
+
536
+ if self._yerr_size is None:
537
+ yerr_size = xerr_size
538
+ else:
539
+ yerr_size = self._yerr_size * fontsize
540
+
541
+ return xerr_size, yerr_size
542
+
543
+ def create_artists(self, legend, orig_handle,
544
+ xdescent, ydescent, width, height, fontsize,
545
+ trans):
546
+ # docstring inherited
547
+ plotlines, caplines, barlinecols = orig_handle
548
+
549
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
550
+ width, height, fontsize)
551
+
552
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
553
+ legline = Line2D(xdata, ydata)
554
+
555
+ xdata_marker = np.asarray(xdata_marker)
556
+ ydata_marker = np.asarray(ydata[:len(xdata_marker)])
557
+
558
+ xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent,
559
+ width, height, fontsize)
560
+
561
+ legline_marker = Line2D(xdata_marker, ydata_marker)
562
+
563
+ # when plotlines are None (only errorbars are drawn), we just
564
+ # make legline invisible.
565
+ if plotlines is None:
566
+ legline.set_visible(False)
567
+ legline_marker.set_visible(False)
568
+ else:
569
+ self.update_prop(legline, plotlines, legend)
570
+
571
+ legline.set_drawstyle('default')
572
+ legline.set_marker('none')
573
+
574
+ self.update_prop(legline_marker, plotlines, legend)
575
+ legline_marker.set_linestyle('None')
576
+
577
+ if legend.markerscale != 1:
578
+ newsz = legline_marker.get_markersize() * legend.markerscale
579
+ legline_marker.set_markersize(newsz)
580
+
581
+ handle_barlinecols = []
582
+ handle_caplines = []
583
+
584
+ if orig_handle.has_xerr:
585
+ verts = [((x - xerr_size, y), (x + xerr_size, y))
586
+ for x, y in zip(xdata_marker, ydata_marker)]
587
+ coll = mcoll.LineCollection(verts)
588
+ self.update_prop(coll, barlinecols[0], legend)
589
+ handle_barlinecols.append(coll)
590
+
591
+ if caplines:
592
+ capline_left = Line2D(xdata_marker - xerr_size, ydata_marker)
593
+ capline_right = Line2D(xdata_marker + xerr_size, ydata_marker)
594
+ self.update_prop(capline_left, caplines[0], legend)
595
+ self.update_prop(capline_right, caplines[0], legend)
596
+ capline_left.set_marker("|")
597
+ capline_right.set_marker("|")
598
+
599
+ handle_caplines.append(capline_left)
600
+ handle_caplines.append(capline_right)
601
+
602
+ if orig_handle.has_yerr:
603
+ verts = [((x, y - yerr_size), (x, y + yerr_size))
604
+ for x, y in zip(xdata_marker, ydata_marker)]
605
+ coll = mcoll.LineCollection(verts)
606
+ self.update_prop(coll, barlinecols[0], legend)
607
+ handle_barlinecols.append(coll)
608
+
609
+ if caplines:
610
+ capline_left = Line2D(xdata_marker, ydata_marker - yerr_size)
611
+ capline_right = Line2D(xdata_marker, ydata_marker + yerr_size)
612
+ self.update_prop(capline_left, caplines[0], legend)
613
+ self.update_prop(capline_right, caplines[0], legend)
614
+ capline_left.set_marker("_")
615
+ capline_right.set_marker("_")
616
+
617
+ handle_caplines.append(capline_left)
618
+ handle_caplines.append(capline_right)
619
+
620
+ artists = [
621
+ *handle_barlinecols, *handle_caplines, legline, legline_marker,
622
+ ]
623
+ for artist in artists:
624
+ artist.set_transform(trans)
625
+ return artists
626
+
627
+
628
+ class HandlerStem(HandlerNpointsYoffsets):
629
+ """
630
+ Handler for plots produced by `~.Axes.stem`.
631
+ """
632
+
633
+ def __init__(self, marker_pad=0.3, numpoints=None,
634
+ bottom=None, yoffsets=None, **kwargs):
635
+ """
636
+ Parameters
637
+ ----------
638
+ marker_pad : float, default: 0.3
639
+ Padding between points in legend entry.
640
+ numpoints : int, optional
641
+ Number of points to show in legend entry.
642
+ bottom : float, optional
643
+
644
+ yoffsets : array of floats, optional
645
+ Length *numpoints* list of y offsets for each point in
646
+ legend entry.
647
+ **kwargs
648
+ Keyword arguments forwarded to `.HandlerNpointsYoffsets`.
649
+ """
650
+ super().__init__(marker_pad=marker_pad, numpoints=numpoints,
651
+ yoffsets=yoffsets, **kwargs)
652
+ self._bottom = bottom
653
+
654
+ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
655
+ if self._yoffsets is None:
656
+ ydata = height * (0.5 * legend._scatteryoffsets + 0.5)
657
+ else:
658
+ ydata = height * np.asarray(self._yoffsets)
659
+
660
+ return ydata
661
+
662
+ def create_artists(self, legend, orig_handle,
663
+ xdescent, ydescent, width, height, fontsize,
664
+ trans):
665
+ # docstring inherited
666
+ markerline, stemlines, baseline = orig_handle
667
+ # Check to see if the stemcontainer is storing lines as a list or a
668
+ # LineCollection. Eventually using a list will be removed, and this
669
+ # logic can also be removed.
670
+ using_linecoll = isinstance(stemlines, mcoll.LineCollection)
671
+
672
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
673
+ width, height, fontsize)
674
+
675
+ ydata = self.get_ydata(legend, xdescent, ydescent,
676
+ width, height, fontsize)
677
+
678
+ if self._bottom is None:
679
+ bottom = 0.
680
+ else:
681
+ bottom = self._bottom
682
+
683
+ leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
684
+ self.update_prop(leg_markerline, markerline, legend)
685
+
686
+ leg_stemlines = [Line2D([x, x], [bottom, y])
687
+ for x, y in zip(xdata_marker, ydata)]
688
+
689
+ if using_linecoll:
690
+ # change the function used by update_prop() from the default
691
+ # to one that handles LineCollection
692
+ with cbook._setattr_cm(
693
+ self, _update_prop_func=self._copy_collection_props):
694
+ for line in leg_stemlines:
695
+ self.update_prop(line, stemlines, legend)
696
+
697
+ else:
698
+ for lm, m in zip(leg_stemlines, stemlines):
699
+ self.update_prop(lm, m, legend)
700
+
701
+ leg_baseline = Line2D([np.min(xdata), np.max(xdata)],
702
+ [bottom, bottom])
703
+ self.update_prop(leg_baseline, baseline, legend)
704
+
705
+ artists = [*leg_stemlines, leg_baseline, leg_markerline]
706
+ for artist in artists:
707
+ artist.set_transform(trans)
708
+ return artists
709
+
710
+ def _copy_collection_props(self, legend_handle, orig_handle):
711
+ """
712
+ Copy properties from the `.LineCollection` *orig_handle* to the
713
+ `.Line2D` *legend_handle*.
714
+ """
715
+ legend_handle.set_color(orig_handle.get_color()[0])
716
+ legend_handle.set_linestyle(orig_handle.get_linestyle()[0])
717
+
718
+
719
+ class HandlerTuple(HandlerBase):
720
+ """
721
+ Handler for Tuple.
722
+ """
723
+
724
+ def __init__(self, ndivide=1, pad=None, **kwargs):
725
+ """
726
+ Parameters
727
+ ----------
728
+ ndivide : int or None, default: 1
729
+ The number of sections to divide the legend area into. If None,
730
+ use the length of the input tuple.
731
+ pad : float, default: :rc:`legend.borderpad`
732
+ Padding in units of fraction of font size.
733
+ **kwargs
734
+ Keyword arguments forwarded to `.HandlerBase`.
735
+ """
736
+ self._ndivide = ndivide
737
+ self._pad = pad
738
+ super().__init__(**kwargs)
739
+
740
+ def create_artists(self, legend, orig_handle,
741
+ xdescent, ydescent, width, height, fontsize,
742
+ trans):
743
+ # docstring inherited
744
+ handler_map = legend.get_legend_handler_map()
745
+
746
+ if self._ndivide is None:
747
+ ndivide = len(orig_handle)
748
+ else:
749
+ ndivide = self._ndivide
750
+
751
+ if self._pad is None:
752
+ pad = legend.borderpad * fontsize
753
+ else:
754
+ pad = self._pad * fontsize
755
+
756
+ if ndivide > 1:
757
+ width = (width - pad * (ndivide - 1)) / ndivide
758
+
759
+ xds_cycle = cycle(xdescent - (width + pad) * np.arange(ndivide))
760
+
761
+ a_list = []
762
+ for handle1 in orig_handle:
763
+ handler = legend.get_legend_handler(handler_map, handle1)
764
+ _a_list = handler.create_artists(
765
+ legend, handle1,
766
+ next(xds_cycle), ydescent, width, height, fontsize, trans)
767
+ a_list.extend(_a_list)
768
+
769
+ return a_list
770
+
771
+
772
+ class HandlerPolyCollection(HandlerBase):
773
+ """
774
+ Handler for `.PolyCollection` used in `~.Axes.fill_between` and
775
+ `~.Axes.stackplot`.
776
+ """
777
+ def _update_prop(self, legend_handle, orig_handle):
778
+ def first_color(colors):
779
+ if colors.size == 0:
780
+ return (0, 0, 0, 0)
781
+ return tuple(colors[0])
782
+
783
+ def get_first(prop_array):
784
+ if len(prop_array):
785
+ return prop_array[0]
786
+ else:
787
+ return None
788
+
789
+ # orig_handle is a PolyCollection and legend_handle is a Patch.
790
+ # Directly set Patch color attributes (must be RGBA tuples).
791
+ legend_handle._facecolor = first_color(orig_handle.get_facecolor())
792
+ legend_handle._edgecolor = first_color(orig_handle.get_edgecolor())
793
+ legend_handle._original_facecolor = orig_handle._original_facecolor
794
+ legend_handle._original_edgecolor = orig_handle._original_edgecolor
795
+ legend_handle._fill = orig_handle.get_fill()
796
+ legend_handle._hatch = orig_handle.get_hatch()
797
+ # Hatch color is anomalous in having no getters and setters.
798
+ legend_handle._hatch_color = orig_handle._hatch_color
799
+ # Setters are fine for the remaining attributes.
800
+ legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
801
+ legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
802
+ legend_handle.set_transform(get_first(orig_handle.get_transforms()))
803
+ # Alpha is already taken into account by the color attributes.
804
+
805
+ def create_artists(self, legend, orig_handle,
806
+ xdescent, ydescent, width, height, fontsize, trans):
807
+ # docstring inherited
808
+ p = Rectangle(xy=(-xdescent, -ydescent),
809
+ width=width, height=height)
810
+ self.update_prop(p, orig_handle, legend)
811
+ p.set_transform(trans)
812
+ return [p]
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable, Sequence
2
+ from matplotlib.artist import Artist
3
+ from matplotlib.legend import Legend
4
+ from matplotlib.offsetbox import OffsetBox
5
+ from matplotlib.transforms import Transform
6
+
7
+ from typing import TypeVar
8
+
9
+ from numpy.typing import ArrayLike
10
+
11
+ def update_from_first_child(tgt: Artist, src: Artist) -> None: ...
12
+
13
+ class HandlerBase:
14
+ def __init__(
15
+ self,
16
+ xpad: float = ...,
17
+ ypad: float = ...,
18
+ update_func: Callable[[Artist, Artist], None] | None = ...,
19
+ ) -> None: ...
20
+ def update_prop(
21
+ self, legend_handle: Artist, orig_handle: Artist, legend: Legend
22
+ ) -> None: ...
23
+ def adjust_drawing_area(
24
+ self,
25
+ legend: Legend,
26
+ orig_handle: Artist,
27
+ xdescent: float,
28
+ ydescent: float,
29
+ width: float,
30
+ height: float,
31
+ fontsize: float,
32
+ ) -> tuple[float, float, float, float]: ...
33
+ def legend_artist(
34
+ self, legend: Legend, orig_handle: Artist, fontsize: float, handlebox: OffsetBox
35
+ ) -> Artist: ...
36
+ def create_artists(
37
+ self,
38
+ legend: Legend,
39
+ orig_handle: Artist,
40
+ xdescent: float,
41
+ ydescent: float,
42
+ width: float,
43
+ height: float,
44
+ fontsize: float,
45
+ trans: Transform,
46
+ ) -> Sequence[Artist]: ...
47
+
48
+ class HandlerNpoints(HandlerBase):
49
+ def __init__(
50
+ self, marker_pad: float = ..., numpoints: int | None = ..., **kwargs
51
+ ) -> None: ...
52
+ def get_numpoints(self, legend: Legend) -> int | None: ...
53
+ def get_xdata(
54
+ self,
55
+ legend: Legend,
56
+ xdescent: float,
57
+ ydescent: float,
58
+ width: float,
59
+ height: float,
60
+ fontsize: float,
61
+ ) -> tuple[ArrayLike, ArrayLike]: ...
62
+
63
+ class HandlerNpointsYoffsets(HandlerNpoints):
64
+ def __init__(
65
+ self,
66
+ numpoints: int | None = ...,
67
+ yoffsets: Sequence[float] | None = ...,
68
+ **kwargs
69
+ ) -> None: ...
70
+ def get_ydata(
71
+ self,
72
+ legend: Legend,
73
+ xdescent: float,
74
+ ydescent: float,
75
+ width: float,
76
+ height: float,
77
+ fontsize: float,
78
+ ) -> ArrayLike: ...
79
+
80
+ class HandlerLine2DCompound(HandlerNpoints):
81
+ def create_artists(
82
+ self,
83
+ legend: Legend,
84
+ orig_handle: Artist,
85
+ xdescent: float,
86
+ ydescent: float,
87
+ width: float,
88
+ height: float,
89
+ fontsize: float,
90
+ trans: Transform,
91
+ ) -> Sequence[Artist]: ...
92
+
93
+ class HandlerLine2D(HandlerNpoints):
94
+ def create_artists(
95
+ self,
96
+ legend: Legend,
97
+ orig_handle: Artist,
98
+ xdescent: float,
99
+ ydescent: float,
100
+ width: float,
101
+ height: float,
102
+ fontsize: float,
103
+ trans: Transform,
104
+ ) -> Sequence[Artist]: ...
105
+
106
+ class HandlerPatch(HandlerBase):
107
+ def __init__(self, patch_func: Callable | None = ..., **kwargs) -> None: ...
108
+ def create_artists(
109
+ self,
110
+ legend: Legend,
111
+ orig_handle: Artist,
112
+ xdescent: float,
113
+ ydescent: float,
114
+ width: float,
115
+ height: float,
116
+ fontsize: float,
117
+ trans: Transform,
118
+ ) -> Sequence[Artist]: ...
119
+
120
+ class HandlerStepPatch(HandlerBase):
121
+ def create_artists(
122
+ self,
123
+ legend: Legend,
124
+ orig_handle: Artist,
125
+ xdescent: float,
126
+ ydescent: float,
127
+ width: float,
128
+ height: float,
129
+ fontsize: float,
130
+ trans: Transform,
131
+ ) -> Sequence[Artist]: ...
132
+
133
+ class HandlerLineCollection(HandlerLine2D):
134
+ def get_numpoints(self, legend: Legend) -> int: ...
135
+ def create_artists(
136
+ self,
137
+ legend: Legend,
138
+ orig_handle: Artist,
139
+ xdescent: float,
140
+ ydescent: float,
141
+ width: float,
142
+ height: float,
143
+ fontsize: float,
144
+ trans: Transform,
145
+ ) -> Sequence[Artist]: ...
146
+
147
+ _T = TypeVar("_T", bound=Artist)
148
+
149
+ class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
150
+ def __init__(
151
+ self,
152
+ yoffsets: Sequence[float] | None = ...,
153
+ sizes: Sequence[float] | None = ...,
154
+ **kwargs
155
+ ) -> None: ...
156
+ def get_numpoints(self, legend: Legend) -> int: ...
157
+ def get_sizes(
158
+ self,
159
+ legend: Legend,
160
+ orig_handle: Artist,
161
+ xdescent: float,
162
+ ydescent: float,
163
+ width: float,
164
+ height: float,
165
+ fontsize: float,
166
+ ) -> Sequence[float]: ...
167
+ def update_prop(
168
+ self, legend_handle, orig_handle: Artist, legend: Legend
169
+ ) -> None: ...
170
+ def create_collection(
171
+ self,
172
+ orig_handle: _T,
173
+ sizes: Sequence[float] | None,
174
+ offsets: Sequence[float] | None,
175
+ offset_transform: Transform,
176
+ ) -> _T: ...
177
+ def create_artists(
178
+ self,
179
+ legend: Legend,
180
+ orig_handle: Artist,
181
+ xdescent: float,
182
+ ydescent: float,
183
+ width: float,
184
+ height: float,
185
+ fontsize: float,
186
+ trans: Transform,
187
+ ) -> Sequence[Artist]: ...
188
+
189
+ class HandlerPathCollection(HandlerRegularPolyCollection):
190
+ def create_collection(
191
+ self,
192
+ orig_handle: _T,
193
+ sizes: Sequence[float] | None,
194
+ offsets: Sequence[float] | None,
195
+ offset_transform: Transform,
196
+ ) -> _T: ...
197
+
198
+ class HandlerCircleCollection(HandlerRegularPolyCollection):
199
+ def create_collection(
200
+ self,
201
+ orig_handle: _T,
202
+ sizes: Sequence[float] | None,
203
+ offsets: Sequence[float] | None,
204
+ offset_transform: Transform,
205
+ ) -> _T: ...
206
+
207
+ class HandlerErrorbar(HandlerLine2D):
208
+ def __init__(
209
+ self,
210
+ xerr_size: float = ...,
211
+ yerr_size: float | None = ...,
212
+ marker_pad: float = ...,
213
+ numpoints: int | None = ...,
214
+ **kwargs
215
+ ) -> None: ...
216
+ def get_err_size(
217
+ self,
218
+ legend: Legend,
219
+ xdescent: float,
220
+ ydescent: float,
221
+ width: float,
222
+ height: float,
223
+ fontsize: float,
224
+ ) -> tuple[float, float]: ...
225
+ def create_artists(
226
+ self,
227
+ legend: Legend,
228
+ orig_handle: Artist,
229
+ xdescent: float,
230
+ ydescent: float,
231
+ width: float,
232
+ height: float,
233
+ fontsize: float,
234
+ trans: Transform,
235
+ ) -> Sequence[Artist]: ...
236
+
237
+ class HandlerStem(HandlerNpointsYoffsets):
238
+ def __init__(
239
+ self,
240
+ marker_pad: float = ...,
241
+ numpoints: int | None = ...,
242
+ bottom: float | None = ...,
243
+ yoffsets: Sequence[float] | None = ...,
244
+ **kwargs
245
+ ) -> None: ...
246
+ def get_ydata(
247
+ self,
248
+ legend: Legend,
249
+ xdescent: float,
250
+ ydescent: float,
251
+ width: float,
252
+ height: float,
253
+ fontsize: float,
254
+ ) -> ArrayLike: ...
255
+ def create_artists(
256
+ self,
257
+ legend: Legend,
258
+ orig_handle: Artist,
259
+ xdescent: float,
260
+ ydescent: float,
261
+ width: float,
262
+ height: float,
263
+ fontsize: float,
264
+ trans: Transform,
265
+ ) -> Sequence[Artist]: ...
266
+
267
+ class HandlerTuple(HandlerBase):
268
+ def __init__(
269
+ self, ndivide: int | None = ..., pad: float | None = ..., **kwargs
270
+ ) -> None: ...
271
+ def create_artists(
272
+ self,
273
+ legend: Legend,
274
+ orig_handle: Artist,
275
+ xdescent: float,
276
+ ydescent: float,
277
+ width: float,
278
+ height: float,
279
+ fontsize: float,
280
+ trans: Transform,
281
+ ) -> Sequence[Artist]: ...
282
+
283
+ class HandlerPolyCollection(HandlerBase):
284
+ def create_artists(
285
+ self,
286
+ legend: Legend,
287
+ orig_handle: Artist,
288
+ xdescent: float,
289
+ ydescent: float,
290
+ width: float,
291
+ height: float,
292
+ fontsize: float,
293
+ trans: Transform,
294
+ ) -> Sequence[Artist]: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py ADDED
@@ -0,0 +1,1720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 2D lines with support for a variety of line styles, markers, colors, etc.
3
+ """
4
+
5
+ import copy
6
+
7
+ from numbers import Integral, Number, Real
8
+ import logging
9
+
10
+ import numpy as np
11
+
12
+ import matplotlib as mpl
13
+ from . import _api, cbook, colors as mcolors, _docstring
14
+ from .artist import Artist, allow_rasterization
15
+ from .cbook import (
16
+ _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP)
17
+ from .markers import MarkerStyle
18
+ from .path import Path
19
+ from .transforms import Bbox, BboxTransformTo, TransformedPath
20
+ from ._enums import JoinStyle, CapStyle
21
+
22
+ # Imported here for backward compatibility, even though they don't
23
+ # really belong.
24
+ from . import _path
25
+ from .markers import ( # noqa
26
+ CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
27
+ CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE,
28
+ TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN)
29
+
30
+ _log = logging.getLogger(__name__)
31
+
32
+
33
+ def _get_dash_pattern(style):
34
+ """Convert linestyle to dash pattern."""
35
+ # go from short hand -> full strings
36
+ if isinstance(style, str):
37
+ style = ls_mapper.get(style, style)
38
+ # un-dashed styles
39
+ if style in ['solid', 'None']:
40
+ offset = 0
41
+ dashes = None
42
+ # dashed styles
43
+ elif style in ['dashed', 'dashdot', 'dotted']:
44
+ offset = 0
45
+ dashes = tuple(mpl.rcParams[f'lines.{style}_pattern'])
46
+ #
47
+ elif isinstance(style, tuple):
48
+ offset, dashes = style
49
+ if offset is None:
50
+ raise ValueError(f'Unrecognized linestyle: {style!r}')
51
+ else:
52
+ raise ValueError(f'Unrecognized linestyle: {style!r}')
53
+
54
+ # normalize offset to be positive and shorter than the dash cycle
55
+ if dashes is not None:
56
+ dsum = sum(dashes)
57
+ if dsum:
58
+ offset %= dsum
59
+
60
+ return offset, dashes
61
+
62
+
63
+ def _get_dash_patterns(styles):
64
+ """Convert linestyle or sequence of linestyles to list of dash patterns."""
65
+ try:
66
+ patterns = [_get_dash_pattern(styles)]
67
+ except ValueError:
68
+ try:
69
+ patterns = [_get_dash_pattern(x) for x in styles]
70
+ except ValueError as err:
71
+ emsg = f'Do not know how to convert {styles!r} to dashes'
72
+ raise ValueError(emsg) from err
73
+
74
+ return patterns
75
+
76
+
77
+ def _get_inverse_dash_pattern(offset, dashes):
78
+ """Return the inverse of the given dash pattern, for filling the gaps."""
79
+ # Define the inverse pattern by moving the last gap to the start of the
80
+ # sequence.
81
+ gaps = dashes[-1:] + dashes[:-1]
82
+ # Set the offset so that this new first segment is skipped
83
+ # (see backend_bases.GraphicsContextBase.set_dashes for offset definition).
84
+ offset_gaps = offset + dashes[-1]
85
+
86
+ return offset_gaps, gaps
87
+
88
+
89
+ def _scale_dashes(offset, dashes, lw):
90
+ if not mpl.rcParams['lines.scale_dashes']:
91
+ return offset, dashes
92
+ scaled_offset = offset * lw
93
+ scaled_dashes = ([x * lw if x is not None else None for x in dashes]
94
+ if dashes is not None else None)
95
+ return scaled_offset, scaled_dashes
96
+
97
+
98
+ def segment_hits(cx, cy, x, y, radius):
99
+ """
100
+ Return the indices of the segments in the polyline with coordinates (*cx*,
101
+ *cy*) that are within a distance *radius* of the point (*x*, *y*).
102
+ """
103
+ # Process single points specially
104
+ if len(x) <= 1:
105
+ res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2)
106
+ return res
107
+
108
+ # We need to lop the last element off a lot.
109
+ xr, yr = x[:-1], y[:-1]
110
+
111
+ # Only look at line segments whose nearest point to C on the line
112
+ # lies within the segment.
113
+ dx, dy = x[1:] - xr, y[1:] - yr
114
+ Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0
115
+ u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq
116
+ candidates = (u >= 0) & (u <= 1)
117
+
118
+ # Note that there is a little area near one side of each point
119
+ # which will be near neither segment, and another which will
120
+ # be near both, depending on the angle of the lines. The
121
+ # following radius test eliminates these ambiguities.
122
+ point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2
123
+ candidates = candidates & ~(point_hits[:-1] | point_hits[1:])
124
+
125
+ # For those candidates which remain, determine how far they lie away
126
+ # from the line.
127
+ px, py = xr + u * dx, yr + u * dy
128
+ line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2
129
+ line_hits = line_hits & candidates
130
+ points, = point_hits.ravel().nonzero()
131
+ lines, = line_hits.ravel().nonzero()
132
+ return np.concatenate((points, lines))
133
+
134
+
135
+ def _mark_every_path(markevery, tpath, affine, ax):
136
+ """
137
+ Helper function that sorts out how to deal the input
138
+ `markevery` and returns the points where markers should be drawn.
139
+
140
+ Takes in the `markevery` value and the line path and returns the
141
+ sub-sampled path.
142
+ """
143
+ # pull out the two bits of data we want from the path
144
+ codes, verts = tpath.codes, tpath.vertices
145
+
146
+ def _slice_or_none(in_v, slc):
147
+ """Helper function to cope with `codes` being an ndarray or `None`."""
148
+ if in_v is None:
149
+ return None
150
+ return in_v[slc]
151
+
152
+ # if just an int, assume starting at 0 and make a tuple
153
+ if isinstance(markevery, Integral):
154
+ markevery = (0, markevery)
155
+ # if just a float, assume starting at 0.0 and make a tuple
156
+ elif isinstance(markevery, Real):
157
+ markevery = (0.0, markevery)
158
+
159
+ if isinstance(markevery, tuple):
160
+ if len(markevery) != 2:
161
+ raise ValueError('`markevery` is a tuple but its len is not 2; '
162
+ f'markevery={markevery}')
163
+ start, step = markevery
164
+ # if step is an int, old behavior
165
+ if isinstance(step, Integral):
166
+ # tuple of 2 int is for backwards compatibility,
167
+ if not isinstance(start, Integral):
168
+ raise ValueError(
169
+ '`markevery` is a tuple with len 2 and second element is '
170
+ 'an int, but the first element is not an int; '
171
+ f'markevery={markevery}')
172
+ # just return, we are done here
173
+
174
+ return Path(verts[slice(start, None, step)],
175
+ _slice_or_none(codes, slice(start, None, step)))
176
+
177
+ elif isinstance(step, Real):
178
+ if not isinstance(start, Real):
179
+ raise ValueError(
180
+ '`markevery` is a tuple with len 2 and second element is '
181
+ 'a float, but the first element is not a float or an int; '
182
+ f'markevery={markevery}')
183
+ if ax is None:
184
+ raise ValueError(
185
+ "markevery is specified relative to the Axes size, but "
186
+ "the line does not have a Axes as parent")
187
+
188
+ # calc cumulative distance along path (in display coords):
189
+ fin = np.isfinite(verts).all(axis=1)
190
+ fverts = verts[fin]
191
+ disp_coords = affine.transform(fverts)
192
+
193
+ delta = np.empty((len(disp_coords), 2))
194
+ delta[0, :] = 0
195
+ delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :]
196
+ delta = np.hypot(*delta.T).cumsum()
197
+ # calc distance between markers along path based on the Axes
198
+ # bounding box diagonal being a distance of unity:
199
+ (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
200
+ scale = np.hypot(x1 - x0, y1 - y0)
201
+ marker_delta = np.arange(start * scale, delta[-1], step * scale)
202
+ # find closest actual data point that is closest to
203
+ # the theoretical distance along the path:
204
+ inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
205
+ inds = inds.argmin(axis=1)
206
+ inds = np.unique(inds)
207
+ # return, we are done here
208
+ return Path(fverts[inds], _slice_or_none(codes, inds))
209
+ else:
210
+ raise ValueError(
211
+ f"markevery={markevery!r} is a tuple with len 2, but its "
212
+ f"second element is not an int or a float")
213
+
214
+ elif isinstance(markevery, slice):
215
+ # mazol tov, it's already a slice, just return
216
+ return Path(verts[markevery], _slice_or_none(codes, markevery))
217
+
218
+ elif np.iterable(markevery):
219
+ # fancy indexing
220
+ try:
221
+ return Path(verts[markevery], _slice_or_none(codes, markevery))
222
+ except (ValueError, IndexError) as err:
223
+ raise ValueError(
224
+ f"markevery={markevery!r} is iterable but not a valid numpy "
225
+ f"fancy index") from err
226
+ else:
227
+ raise ValueError(f"markevery={markevery!r} is not a recognized value")
228
+
229
+
230
+ @_docstring.interpd
231
+ @_api.define_aliases({
232
+ "antialiased": ["aa"],
233
+ "color": ["c"],
234
+ "drawstyle": ["ds"],
235
+ "linestyle": ["ls"],
236
+ "linewidth": ["lw"],
237
+ "markeredgecolor": ["mec"],
238
+ "markeredgewidth": ["mew"],
239
+ "markerfacecolor": ["mfc"],
240
+ "markerfacecoloralt": ["mfcalt"],
241
+ "markersize": ["ms"],
242
+ })
243
+ class Line2D(Artist):
244
+ """
245
+ A line - the line can have both a solid linestyle connecting all
246
+ the vertices, and a marker at each vertex. Additionally, the
247
+ drawing of the solid line is influenced by the drawstyle, e.g., one
248
+ can create "stepped" lines in various styles.
249
+ """
250
+
251
+ lineStyles = _lineStyles = { # hidden names deprecated
252
+ '-': '_draw_solid',
253
+ '--': '_draw_dashed',
254
+ '-.': '_draw_dash_dot',
255
+ ':': '_draw_dotted',
256
+ 'None': '_draw_nothing',
257
+ ' ': '_draw_nothing',
258
+ '': '_draw_nothing',
259
+ }
260
+
261
+ _drawStyles_l = {
262
+ 'default': '_draw_lines',
263
+ 'steps-mid': '_draw_steps_mid',
264
+ 'steps-pre': '_draw_steps_pre',
265
+ 'steps-post': '_draw_steps_post',
266
+ }
267
+
268
+ _drawStyles_s = {
269
+ 'steps': '_draw_steps_pre',
270
+ }
271
+
272
+ # drawStyles should now be deprecated.
273
+ drawStyles = {**_drawStyles_l, **_drawStyles_s}
274
+ # Need a list ordered with long names first:
275
+ drawStyleKeys = [*_drawStyles_l, *_drawStyles_s]
276
+
277
+ # Referenced here to maintain API. These are defined in
278
+ # MarkerStyle
279
+ markers = MarkerStyle.markers
280
+ filled_markers = MarkerStyle.filled_markers
281
+ fillStyles = MarkerStyle.fillstyles
282
+
283
+ zorder = 2
284
+
285
+ _subslice_optim_min_size = 1000
286
+
287
+ def __str__(self):
288
+ if self._label != "":
289
+ return f"Line2D({self._label})"
290
+ elif self._x is None:
291
+ return "Line2D()"
292
+ elif len(self._x) > 3:
293
+ return "Line2D(({:g},{:g}),({:g},{:g}),...,({:g},{:g}))".format(
294
+ self._x[0], self._y[0],
295
+ self._x[1], self._y[1],
296
+ self._x[-1], self._y[-1])
297
+ else:
298
+ return "Line2D(%s)" % ",".join(
299
+ map("({:g},{:g})".format, self._x, self._y))
300
+
301
+ def __init__(self, xdata, ydata, *,
302
+ linewidth=None, # all Nones default to rc
303
+ linestyle=None,
304
+ color=None,
305
+ gapcolor=None,
306
+ marker=None,
307
+ markersize=None,
308
+ markeredgewidth=None,
309
+ markeredgecolor=None,
310
+ markerfacecolor=None,
311
+ markerfacecoloralt='none',
312
+ fillstyle=None,
313
+ antialiased=None,
314
+ dash_capstyle=None,
315
+ solid_capstyle=None,
316
+ dash_joinstyle=None,
317
+ solid_joinstyle=None,
318
+ pickradius=5,
319
+ drawstyle=None,
320
+ markevery=None,
321
+ **kwargs
322
+ ):
323
+ """
324
+ Create a `.Line2D` instance with *x* and *y* data in sequences of
325
+ *xdata*, *ydata*.
326
+
327
+ Additional keyword arguments are `.Line2D` properties:
328
+
329
+ %(Line2D:kwdoc)s
330
+
331
+ See :meth:`set_linestyle` for a description of the line styles,
332
+ :meth:`set_marker` for a description of the markers, and
333
+ :meth:`set_drawstyle` for a description of the draw styles.
334
+
335
+ """
336
+ super().__init__()
337
+
338
+ # Convert sequences to NumPy arrays.
339
+ if not np.iterable(xdata):
340
+ raise RuntimeError('xdata must be a sequence')
341
+ if not np.iterable(ydata):
342
+ raise RuntimeError('ydata must be a sequence')
343
+
344
+ if linewidth is None:
345
+ linewidth = mpl.rcParams['lines.linewidth']
346
+
347
+ if linestyle is None:
348
+ linestyle = mpl.rcParams['lines.linestyle']
349
+ if marker is None:
350
+ marker = mpl.rcParams['lines.marker']
351
+ if color is None:
352
+ color = mpl.rcParams['lines.color']
353
+
354
+ if markersize is None:
355
+ markersize = mpl.rcParams['lines.markersize']
356
+ if antialiased is None:
357
+ antialiased = mpl.rcParams['lines.antialiased']
358
+ if dash_capstyle is None:
359
+ dash_capstyle = mpl.rcParams['lines.dash_capstyle']
360
+ if dash_joinstyle is None:
361
+ dash_joinstyle = mpl.rcParams['lines.dash_joinstyle']
362
+ if solid_capstyle is None:
363
+ solid_capstyle = mpl.rcParams['lines.solid_capstyle']
364
+ if solid_joinstyle is None:
365
+ solid_joinstyle = mpl.rcParams['lines.solid_joinstyle']
366
+
367
+ if drawstyle is None:
368
+ drawstyle = 'default'
369
+
370
+ self._dashcapstyle = None
371
+ self._dashjoinstyle = None
372
+ self._solidjoinstyle = None
373
+ self._solidcapstyle = None
374
+ self.set_dash_capstyle(dash_capstyle)
375
+ self.set_dash_joinstyle(dash_joinstyle)
376
+ self.set_solid_capstyle(solid_capstyle)
377
+ self.set_solid_joinstyle(solid_joinstyle)
378
+
379
+ self._linestyles = None
380
+ self._drawstyle = None
381
+ self._linewidth = linewidth
382
+ self._unscaled_dash_pattern = (0, None) # offset, dash
383
+ self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
384
+
385
+ self.set_linewidth(linewidth)
386
+ self.set_linestyle(linestyle)
387
+ self.set_drawstyle(drawstyle)
388
+
389
+ self._color = None
390
+ self.set_color(color)
391
+ if marker is None:
392
+ marker = 'none' # Default.
393
+ if not isinstance(marker, MarkerStyle):
394
+ self._marker = MarkerStyle(marker, fillstyle)
395
+ else:
396
+ self._marker = marker
397
+
398
+ self._gapcolor = None
399
+ self.set_gapcolor(gapcolor)
400
+
401
+ self._markevery = None
402
+ self._markersize = None
403
+ self._antialiased = None
404
+
405
+ self.set_markevery(markevery)
406
+ self.set_antialiased(antialiased)
407
+ self.set_markersize(markersize)
408
+
409
+ self._markeredgecolor = None
410
+ self._markeredgewidth = None
411
+ self._markerfacecolor = None
412
+ self._markerfacecoloralt = None
413
+
414
+ self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc.
415
+ self.set_markerfacecoloralt(markerfacecoloralt)
416
+ self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc.
417
+ self.set_markeredgewidth(markeredgewidth)
418
+
419
+ # update kwargs before updating data to give the caller a
420
+ # chance to init axes (and hence unit support)
421
+ self._internal_update(kwargs)
422
+ self.pickradius = pickradius
423
+ self.ind_offset = 0
424
+ if (isinstance(self._picker, Number) and
425
+ not isinstance(self._picker, bool)):
426
+ self._pickradius = self._picker
427
+
428
+ self._xorig = np.asarray([])
429
+ self._yorig = np.asarray([])
430
+ self._invalidx = True
431
+ self._invalidy = True
432
+ self._x = None
433
+ self._y = None
434
+ self._xy = None
435
+ self._path = None
436
+ self._transformed_path = None
437
+ self._subslice = False
438
+ self._x_filled = None # used in subslicing; only x is needed
439
+
440
+ self.set_data(xdata, ydata)
441
+
442
+ def contains(self, mouseevent):
443
+ """
444
+ Test whether *mouseevent* occurred on the line.
445
+
446
+ An event is deemed to have occurred "on" the line if it is less
447
+ than ``self.pickradius`` (default: 5 points) away from it. Use
448
+ `~.Line2D.get_pickradius` or `~.Line2D.set_pickradius` to get or set
449
+ the pick radius.
450
+
451
+ Parameters
452
+ ----------
453
+ mouseevent : `~matplotlib.backend_bases.MouseEvent`
454
+
455
+ Returns
456
+ -------
457
+ contains : bool
458
+ Whether any values are within the radius.
459
+ details : dict
460
+ A dictionary ``{'ind': pointlist}``, where *pointlist* is a
461
+ list of points of the line that are within the pickradius around
462
+ the event position.
463
+
464
+ TODO: sort returned indices by distance
465
+ """
466
+ if self._different_canvas(mouseevent):
467
+ return False, {}
468
+
469
+ # Make sure we have data to plot
470
+ if self._invalidy or self._invalidx:
471
+ self.recache()
472
+ if len(self._xy) == 0:
473
+ return False, {}
474
+
475
+ # Convert points to pixels
476
+ transformed_path = self._get_transformed_path()
477
+ path, affine = transformed_path.get_transformed_path_and_affine()
478
+ path = affine.transform_path(path)
479
+ xy = path.vertices
480
+ xt = xy[:, 0]
481
+ yt = xy[:, 1]
482
+
483
+ # Convert pick radius from points to pixels
484
+ fig = self.get_figure(root=True)
485
+ if fig is None:
486
+ _log.warning('no figure set when check if mouse is on line')
487
+ pixels = self._pickradius
488
+ else:
489
+ pixels = fig.dpi / 72. * self._pickradius
490
+
491
+ # The math involved in checking for containment (here and inside of
492
+ # segment_hits) assumes that it is OK to overflow, so temporarily set
493
+ # the error flags accordingly.
494
+ with np.errstate(all='ignore'):
495
+ # Check for collision
496
+ if self._linestyle in ['None', None]:
497
+ # If no line, return the nearby point(s)
498
+ ind, = np.nonzero(
499
+ (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2
500
+ <= pixels ** 2)
501
+ else:
502
+ # If line, return the nearby segment(s)
503
+ ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels)
504
+ if self._drawstyle.startswith("steps"):
505
+ ind //= 2
506
+
507
+ ind += self.ind_offset
508
+
509
+ # Return the point(s) within radius
510
+ return len(ind) > 0, dict(ind=ind)
511
+
512
+ def get_pickradius(self):
513
+ """
514
+ Return the pick radius used for containment tests.
515
+
516
+ See `.contains` for more details.
517
+ """
518
+ return self._pickradius
519
+
520
+ def set_pickradius(self, pickradius):
521
+ """
522
+ Set the pick radius used for containment tests.
523
+
524
+ See `.contains` for more details.
525
+
526
+ Parameters
527
+ ----------
528
+ pickradius : float
529
+ Pick radius, in points.
530
+ """
531
+ if not isinstance(pickradius, Real) or pickradius < 0:
532
+ raise ValueError("pick radius should be a distance")
533
+ self._pickradius = pickradius
534
+
535
+ pickradius = property(get_pickradius, set_pickradius)
536
+
537
+ def get_fillstyle(self):
538
+ """
539
+ Return the marker fill style.
540
+
541
+ See also `~.Line2D.set_fillstyle`.
542
+ """
543
+ return self._marker.get_fillstyle()
544
+
545
+ def set_fillstyle(self, fs):
546
+ """
547
+ Set the marker fill style.
548
+
549
+ Parameters
550
+ ----------
551
+ fs : {'full', 'left', 'right', 'bottom', 'top', 'none'}
552
+ Possible values:
553
+
554
+ - 'full': Fill the whole marker with the *markerfacecolor*.
555
+ - 'left', 'right', 'bottom', 'top': Fill the marker half at
556
+ the given side with the *markerfacecolor*. The other
557
+ half of the marker is filled with *markerfacecoloralt*.
558
+ - 'none': No filling.
559
+
560
+ For examples see :ref:`marker_fill_styles`.
561
+ """
562
+ self.set_marker(MarkerStyle(self._marker.get_marker(), fs))
563
+ self.stale = True
564
+
565
+ def set_markevery(self, every):
566
+ """
567
+ Set the markevery property to subsample the plot when using markers.
568
+
569
+ e.g., if ``every=5``, every 5-th marker will be plotted.
570
+
571
+ Parameters
572
+ ----------
573
+ every : None or int or (int, int) or slice or list[int] or float or \
574
+ (float, float) or list[bool]
575
+ Which markers to plot.
576
+
577
+ - ``every=None``: every point will be plotted.
578
+ - ``every=N``: every N-th marker will be plotted starting with
579
+ marker 0.
580
+ - ``every=(start, N)``: every N-th marker, starting at index
581
+ *start*, will be plotted.
582
+ - ``every=slice(start, end, N)``: every N-th marker, starting at
583
+ index *start*, up to but not including index *end*, will be
584
+ plotted.
585
+ - ``every=[i, j, m, ...]``: only markers at the given indices
586
+ will be plotted.
587
+ - ``every=[True, False, True, ...]``: only positions that are True
588
+ will be plotted. The list must have the same length as the data
589
+ points.
590
+ - ``every=0.1``, (i.e. a float): markers will be spaced at
591
+ approximately equal visual distances along the line; the distance
592
+ along the line between markers is determined by multiplying the
593
+ display-coordinate distance of the Axes bounding-box diagonal
594
+ by the value of *every*.
595
+ - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar
596
+ to ``every=0.1`` but the first marker will be offset along the
597
+ line by 0.5 multiplied by the
598
+ display-coordinate-diagonal-distance along the line.
599
+
600
+ For examples see
601
+ :doc:`/gallery/lines_bars_and_markers/markevery_demo`.
602
+
603
+ Notes
604
+ -----
605
+ Setting *markevery* will still only draw markers at actual data points.
606
+ While the float argument form aims for uniform visual spacing, it has
607
+ to coerce from the ideal spacing to the nearest available data point.
608
+ Depending on the number and distribution of data points, the result
609
+ may still not look evenly spaced.
610
+
611
+ When using a start offset to specify the first marker, the offset will
612
+ be from the first data point which may be different from the first
613
+ the visible data point if the plot is zoomed in.
614
+
615
+ If zooming in on a plot when using float arguments then the actual
616
+ data points that have markers will change because the distance between
617
+ markers is always determined from the display-coordinates
618
+ axes-bounding-box-diagonal regardless of the actual axes data limits.
619
+
620
+ """
621
+ self._markevery = every
622
+ self.stale = True
623
+
624
+ def get_markevery(self):
625
+ """
626
+ Return the markevery setting for marker subsampling.
627
+
628
+ See also `~.Line2D.set_markevery`.
629
+ """
630
+ return self._markevery
631
+
632
+ def set_picker(self, p):
633
+ """
634
+ Set the event picker details for the line.
635
+
636
+ Parameters
637
+ ----------
638
+ p : float or callable[[Artist, Event], tuple[bool, dict]]
639
+ If a float, it is used as the pick radius in points.
640
+ """
641
+ if not callable(p):
642
+ self.set_pickradius(p)
643
+ self._picker = p
644
+
645
+ def get_bbox(self):
646
+ """Get the bounding box of this line."""
647
+ bbox = Bbox([[0, 0], [0, 0]])
648
+ bbox.update_from_data_xy(self.get_xydata())
649
+ return bbox
650
+
651
+ def get_window_extent(self, renderer=None):
652
+ bbox = Bbox([[0, 0], [0, 0]])
653
+ trans_data_to_xy = self.get_transform().transform
654
+ bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()),
655
+ ignore=True)
656
+ # correct for marker size, if any
657
+ if self._marker:
658
+ ms = (self._markersize / 72.0 * self.get_figure(root=True).dpi) * 0.5
659
+ bbox = bbox.padded(ms)
660
+ return bbox
661
+
662
+ def set_data(self, *args):
663
+ """
664
+ Set the x and y data.
665
+
666
+ Parameters
667
+ ----------
668
+ *args : (2, N) array or two 1D arrays
669
+
670
+ See Also
671
+ --------
672
+ set_xdata
673
+ set_ydata
674
+ """
675
+ if len(args) == 1:
676
+ (x, y), = args
677
+ else:
678
+ x, y = args
679
+
680
+ self.set_xdata(x)
681
+ self.set_ydata(y)
682
+
683
+ def recache_always(self):
684
+ self.recache(always=True)
685
+
686
+ def recache(self, always=False):
687
+ if always or self._invalidx:
688
+ xconv = self.convert_xunits(self._xorig)
689
+ x = _to_unmasked_float_array(xconv).ravel()
690
+ else:
691
+ x = self._x
692
+ if always or self._invalidy:
693
+ yconv = self.convert_yunits(self._yorig)
694
+ y = _to_unmasked_float_array(yconv).ravel()
695
+ else:
696
+ y = self._y
697
+
698
+ self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
699
+ self._x, self._y = self._xy.T # views
700
+
701
+ self._subslice = False
702
+ if (self.axes
703
+ and len(x) > self._subslice_optim_min_size
704
+ and _path.is_sorted_and_has_non_nan(x)
705
+ and self.axes.name == 'rectilinear'
706
+ and self.axes.get_xscale() == 'linear'
707
+ and self._markevery is None
708
+ and self.get_clip_on()
709
+ and self.get_transform() == self.axes.transData):
710
+ self._subslice = True
711
+ nanmask = np.isnan(x)
712
+ if nanmask.any():
713
+ self._x_filled = self._x.copy()
714
+ indices = np.arange(len(x))
715
+ self._x_filled[nanmask] = np.interp(
716
+ indices[nanmask], indices[~nanmask], self._x[~nanmask])
717
+ else:
718
+ self._x_filled = self._x
719
+
720
+ if self._path is not None:
721
+ interpolation_steps = self._path._interpolation_steps
722
+ else:
723
+ interpolation_steps = 1
724
+ xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
725
+ self._path = Path(np.asarray(xy).T,
726
+ _interpolation_steps=interpolation_steps)
727
+ self._transformed_path = None
728
+ self._invalidx = False
729
+ self._invalidy = False
730
+
731
+ def _transform_path(self, subslice=None):
732
+ """
733
+ Put a TransformedPath instance at self._transformed_path;
734
+ all invalidation of the transform is then handled by the
735
+ TransformedPath instance.
736
+ """
737
+ # Masked arrays are now handled by the Path class itself
738
+ if subslice is not None:
739
+ xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T)
740
+ _path = Path(np.asarray(xy).T,
741
+ _interpolation_steps=self._path._interpolation_steps)
742
+ else:
743
+ _path = self._path
744
+ self._transformed_path = TransformedPath(_path, self.get_transform())
745
+
746
+ def _get_transformed_path(self):
747
+ """Return this line's `~matplotlib.transforms.TransformedPath`."""
748
+ if self._transformed_path is None:
749
+ self._transform_path()
750
+ return self._transformed_path
751
+
752
+ def set_transform(self, t):
753
+ # docstring inherited
754
+ self._invalidx = True
755
+ self._invalidy = True
756
+ super().set_transform(t)
757
+
758
+ @allow_rasterization
759
+ def draw(self, renderer):
760
+ # docstring inherited
761
+
762
+ if not self.get_visible():
763
+ return
764
+
765
+ if self._invalidy or self._invalidx:
766
+ self.recache()
767
+ self.ind_offset = 0 # Needed for contains() method.
768
+ if self._subslice and self.axes:
769
+ x0, x1 = self.axes.get_xbound()
770
+ i0 = self._x_filled.searchsorted(x0, 'left')
771
+ i1 = self._x_filled.searchsorted(x1, 'right')
772
+ subslice = slice(max(i0 - 1, 0), i1 + 1)
773
+ self.ind_offset = subslice.start
774
+ self._transform_path(subslice)
775
+ else:
776
+ subslice = None
777
+
778
+ if self.get_path_effects():
779
+ from matplotlib.patheffects import PathEffectRenderer
780
+ renderer = PathEffectRenderer(self.get_path_effects(), renderer)
781
+
782
+ renderer.open_group('line2d', self.get_gid())
783
+ if self._lineStyles[self._linestyle] != '_draw_nothing':
784
+ tpath, affine = (self._get_transformed_path()
785
+ .get_transformed_path_and_affine())
786
+ if len(tpath.vertices):
787
+ gc = renderer.new_gc()
788
+ self._set_gc_clip(gc)
789
+ gc.set_url(self.get_url())
790
+
791
+ gc.set_antialiased(self._antialiased)
792
+ gc.set_linewidth(self._linewidth)
793
+
794
+ if self.is_dashed():
795
+ cap = self._dashcapstyle
796
+ join = self._dashjoinstyle
797
+ else:
798
+ cap = self._solidcapstyle
799
+ join = self._solidjoinstyle
800
+ gc.set_joinstyle(join)
801
+ gc.set_capstyle(cap)
802
+ gc.set_snap(self.get_snap())
803
+ if self.get_sketch_params() is not None:
804
+ gc.set_sketch_params(*self.get_sketch_params())
805
+
806
+ # We first draw a path within the gaps if needed.
807
+ if self.is_dashed() and self._gapcolor is not None:
808
+ lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha)
809
+ gc.set_foreground(lc_rgba, isRGBA=True)
810
+
811
+ offset_gaps, gaps = _get_inverse_dash_pattern(
812
+ *self._dash_pattern)
813
+
814
+ gc.set_dashes(offset_gaps, gaps)
815
+ renderer.draw_path(gc, tpath, affine.frozen())
816
+
817
+ lc_rgba = mcolors.to_rgba(self._color, self._alpha)
818
+ gc.set_foreground(lc_rgba, isRGBA=True)
819
+
820
+ gc.set_dashes(*self._dash_pattern)
821
+ renderer.draw_path(gc, tpath, affine.frozen())
822
+ gc.restore()
823
+
824
+ if self._marker and self._markersize > 0:
825
+ gc = renderer.new_gc()
826
+ self._set_gc_clip(gc)
827
+ gc.set_url(self.get_url())
828
+ gc.set_linewidth(self._markeredgewidth)
829
+ gc.set_antialiased(self._antialiased)
830
+
831
+ ec_rgba = mcolors.to_rgba(
832
+ self.get_markeredgecolor(), self._alpha)
833
+ fc_rgba = mcolors.to_rgba(
834
+ self._get_markerfacecolor(), self._alpha)
835
+ fcalt_rgba = mcolors.to_rgba(
836
+ self._get_markerfacecolor(alt=True), self._alpha)
837
+ # If the edgecolor is "auto", it is set according to the *line*
838
+ # color but inherits the alpha value of the *face* color, if any.
839
+ if (cbook._str_equal(self._markeredgecolor, "auto")
840
+ and not cbook._str_lower_equal(
841
+ self.get_markerfacecolor(), "none")):
842
+ ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
843
+ gc.set_foreground(ec_rgba, isRGBA=True)
844
+ if self.get_sketch_params() is not None:
845
+ scale, length, randomness = self.get_sketch_params()
846
+ gc.set_sketch_params(scale/2, length/2, 2*randomness)
847
+
848
+ marker = self._marker
849
+
850
+ # Markers *must* be drawn ignoring the drawstyle (but don't pay the
851
+ # recaching if drawstyle is already "default").
852
+ if self.get_drawstyle() != "default":
853
+ with cbook._setattr_cm(
854
+ self, _drawstyle="default", _transformed_path=None):
855
+ self.recache()
856
+ self._transform_path(subslice)
857
+ tpath, affine = (self._get_transformed_path()
858
+ .get_transformed_points_and_affine())
859
+ else:
860
+ tpath, affine = (self._get_transformed_path()
861
+ .get_transformed_points_and_affine())
862
+
863
+ if len(tpath.vertices):
864
+ # subsample the markers if markevery is not None
865
+ markevery = self.get_markevery()
866
+ if markevery is not None:
867
+ subsampled = _mark_every_path(
868
+ markevery, tpath, affine, self.axes)
869
+ else:
870
+ subsampled = tpath
871
+
872
+ snap = marker.get_snap_threshold()
873
+ if isinstance(snap, Real):
874
+ snap = renderer.points_to_pixels(self._markersize) >= snap
875
+ gc.set_snap(snap)
876
+ gc.set_joinstyle(marker.get_joinstyle())
877
+ gc.set_capstyle(marker.get_capstyle())
878
+ marker_path = marker.get_path()
879
+ marker_trans = marker.get_transform()
880
+ w = renderer.points_to_pixels(self._markersize)
881
+
882
+ if cbook._str_equal(marker.get_marker(), ","):
883
+ gc.set_linewidth(0)
884
+ else:
885
+ # Don't scale for pixels, and don't stroke them
886
+ marker_trans = marker_trans.scale(w)
887
+ renderer.draw_markers(gc, marker_path, marker_trans,
888
+ subsampled, affine.frozen(),
889
+ fc_rgba)
890
+
891
+ alt_marker_path = marker.get_alt_path()
892
+ if alt_marker_path:
893
+ alt_marker_trans = marker.get_alt_transform()
894
+ alt_marker_trans = alt_marker_trans.scale(w)
895
+ renderer.draw_markers(
896
+ gc, alt_marker_path, alt_marker_trans, subsampled,
897
+ affine.frozen(), fcalt_rgba)
898
+
899
+ gc.restore()
900
+
901
+ renderer.close_group('line2d')
902
+ self.stale = False
903
+
904
+ def get_antialiased(self):
905
+ """Return whether antialiased rendering is used."""
906
+ return self._antialiased
907
+
908
+ def get_color(self):
909
+ """
910
+ Return the line color.
911
+
912
+ See also `~.Line2D.set_color`.
913
+ """
914
+ return self._color
915
+
916
+ def get_drawstyle(self):
917
+ """
918
+ Return the drawstyle.
919
+
920
+ See also `~.Line2D.set_drawstyle`.
921
+ """
922
+ return self._drawstyle
923
+
924
+ def get_gapcolor(self):
925
+ """
926
+ Return the line gapcolor.
927
+
928
+ See also `~.Line2D.set_gapcolor`.
929
+ """
930
+ return self._gapcolor
931
+
932
+ def get_linestyle(self):
933
+ """
934
+ Return the linestyle.
935
+
936
+ See also `~.Line2D.set_linestyle`.
937
+ """
938
+ return self._linestyle
939
+
940
+ def get_linewidth(self):
941
+ """
942
+ Return the linewidth in points.
943
+
944
+ See also `~.Line2D.set_linewidth`.
945
+ """
946
+ return self._linewidth
947
+
948
+ def get_marker(self):
949
+ """
950
+ Return the line marker.
951
+
952
+ See also `~.Line2D.set_marker`.
953
+ """
954
+ return self._marker.get_marker()
955
+
956
+ def get_markeredgecolor(self):
957
+ """
958
+ Return the marker edge color.
959
+
960
+ See also `~.Line2D.set_markeredgecolor`.
961
+ """
962
+ mec = self._markeredgecolor
963
+ if cbook._str_equal(mec, 'auto'):
964
+ if mpl.rcParams['_internal.classic_mode']:
965
+ if self._marker.get_marker() in ('.', ','):
966
+ return self._color
967
+ if (self._marker.is_filled()
968
+ and self._marker.get_fillstyle() != 'none'):
969
+ return 'k' # Bad hard-wired default...
970
+ return self._color
971
+ else:
972
+ return mec
973
+
974
+ def get_markeredgewidth(self):
975
+ """
976
+ Return the marker edge width in points.
977
+
978
+ See also `~.Line2D.set_markeredgewidth`.
979
+ """
980
+ return self._markeredgewidth
981
+
982
+ def _get_markerfacecolor(self, alt=False):
983
+ if self._marker.get_fillstyle() == 'none':
984
+ return 'none'
985
+ fc = self._markerfacecoloralt if alt else self._markerfacecolor
986
+ if cbook._str_lower_equal(fc, 'auto'):
987
+ return self._color
988
+ else:
989
+ return fc
990
+
991
+ def get_markerfacecolor(self):
992
+ """
993
+ Return the marker face color.
994
+
995
+ See also `~.Line2D.set_markerfacecolor`.
996
+ """
997
+ return self._get_markerfacecolor(alt=False)
998
+
999
+ def get_markerfacecoloralt(self):
1000
+ """
1001
+ Return the alternate marker face color.
1002
+
1003
+ See also `~.Line2D.set_markerfacecoloralt`.
1004
+ """
1005
+ return self._get_markerfacecolor(alt=True)
1006
+
1007
+ def get_markersize(self):
1008
+ """
1009
+ Return the marker size in points.
1010
+
1011
+ See also `~.Line2D.set_markersize`.
1012
+ """
1013
+ return self._markersize
1014
+
1015
+ def get_data(self, orig=True):
1016
+ """
1017
+ Return the line data as an ``(xdata, ydata)`` pair.
1018
+
1019
+ If *orig* is *True*, return the original data.
1020
+ """
1021
+ return self.get_xdata(orig=orig), self.get_ydata(orig=orig)
1022
+
1023
+ def get_xdata(self, orig=True):
1024
+ """
1025
+ Return the xdata.
1026
+
1027
+ If *orig* is *True*, return the original data, else the
1028
+ processed data.
1029
+ """
1030
+ if orig:
1031
+ return self._xorig
1032
+ if self._invalidx:
1033
+ self.recache()
1034
+ return self._x
1035
+
1036
+ def get_ydata(self, orig=True):
1037
+ """
1038
+ Return the ydata.
1039
+
1040
+ If *orig* is *True*, return the original data, else the
1041
+ processed data.
1042
+ """
1043
+ if orig:
1044
+ return self._yorig
1045
+ if self._invalidy:
1046
+ self.recache()
1047
+ return self._y
1048
+
1049
+ def get_path(self):
1050
+ """Return the `~matplotlib.path.Path` associated with this line."""
1051
+ if self._invalidy or self._invalidx:
1052
+ self.recache()
1053
+ return self._path
1054
+
1055
+ def get_xydata(self):
1056
+ """Return the *xy* data as a (N, 2) array."""
1057
+ if self._invalidy or self._invalidx:
1058
+ self.recache()
1059
+ return self._xy
1060
+
1061
+ def set_antialiased(self, b):
1062
+ """
1063
+ Set whether to use antialiased rendering.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ b : bool
1068
+ """
1069
+ if self._antialiased != b:
1070
+ self.stale = True
1071
+ self._antialiased = b
1072
+
1073
+ def set_color(self, color):
1074
+ """
1075
+ Set the color of the line.
1076
+
1077
+ Parameters
1078
+ ----------
1079
+ color : :mpltype:`color`
1080
+ """
1081
+ mcolors._check_color_like(color=color)
1082
+ self._color = color
1083
+ self.stale = True
1084
+
1085
+ def set_drawstyle(self, drawstyle):
1086
+ """
1087
+ Set the drawstyle of the plot.
1088
+
1089
+ The drawstyle determines how the points are connected.
1090
+
1091
+ Parameters
1092
+ ----------
1093
+ drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \
1094
+ 'steps-post'}, default: 'default'
1095
+ For 'default', the points are connected with straight lines.
1096
+
1097
+ The steps variants connect the points with step-like lines,
1098
+ i.e. horizontal lines with vertical steps. They differ in the
1099
+ location of the step:
1100
+
1101
+ - 'steps-pre': The step is at the beginning of the line segment,
1102
+ i.e. the line will be at the y-value of point to the right.
1103
+ - 'steps-mid': The step is halfway between the points.
1104
+ - 'steps-post: The step is at the end of the line segment,
1105
+ i.e. the line will be at the y-value of the point to the left.
1106
+ - 'steps' is equal to 'steps-pre' and is maintained for
1107
+ backward-compatibility.
1108
+
1109
+ For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`.
1110
+ """
1111
+ if drawstyle is None:
1112
+ drawstyle = 'default'
1113
+ _api.check_in_list(self.drawStyles, drawstyle=drawstyle)
1114
+ if self._drawstyle != drawstyle:
1115
+ self.stale = True
1116
+ # invalidate to trigger a recache of the path
1117
+ self._invalidx = True
1118
+ self._drawstyle = drawstyle
1119
+
1120
+ def set_gapcolor(self, gapcolor):
1121
+ """
1122
+ Set a color to fill the gaps in the dashed line style.
1123
+
1124
+ .. note::
1125
+
1126
+ Striped lines are created by drawing two interleaved dashed lines.
1127
+ There can be overlaps between those two, which may result in
1128
+ artifacts when using transparency.
1129
+
1130
+ This functionality is experimental and may change.
1131
+
1132
+ Parameters
1133
+ ----------
1134
+ gapcolor : :mpltype:`color` or None
1135
+ The color with which to fill the gaps. If None, the gaps are
1136
+ unfilled.
1137
+ """
1138
+ if gapcolor is not None:
1139
+ mcolors._check_color_like(color=gapcolor)
1140
+ self._gapcolor = gapcolor
1141
+ self.stale = True
1142
+
1143
+ def set_linewidth(self, w):
1144
+ """
1145
+ Set the line width in points.
1146
+
1147
+ Parameters
1148
+ ----------
1149
+ w : float
1150
+ Line width, in points.
1151
+ """
1152
+ w = float(w)
1153
+ if self._linewidth != w:
1154
+ self.stale = True
1155
+ self._linewidth = w
1156
+ self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w)
1157
+
1158
+ def set_linestyle(self, ls):
1159
+ """
1160
+ Set the linestyle of the line.
1161
+
1162
+ Parameters
1163
+ ----------
1164
+ ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
1165
+ Possible values:
1166
+
1167
+ - A string:
1168
+
1169
+ ========================================== =================
1170
+ linestyle description
1171
+ ========================================== =================
1172
+ ``'-'`` or ``'solid'`` solid line
1173
+ ``'--'`` or ``'dashed'`` dashed line
1174
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
1175
+ ``':'`` or ``'dotted'`` dotted line
1176
+ ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
1177
+ ========================================== =================
1178
+
1179
+ - Alternatively a dash tuple of the following form can be
1180
+ provided::
1181
+
1182
+ (offset, onoffseq)
1183
+
1184
+ where ``onoffseq`` is an even length tuple of on and off ink
1185
+ in points. See also :meth:`set_dashes`.
1186
+
1187
+ For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`.
1188
+ """
1189
+ if isinstance(ls, str):
1190
+ if ls in [' ', '', 'none']:
1191
+ ls = 'None'
1192
+ _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls)
1193
+ if ls not in self._lineStyles:
1194
+ ls = ls_mapper_r[ls]
1195
+ self._linestyle = ls
1196
+ else:
1197
+ self._linestyle = '--'
1198
+ self._unscaled_dash_pattern = _get_dash_pattern(ls)
1199
+ self._dash_pattern = _scale_dashes(
1200
+ *self._unscaled_dash_pattern, self._linewidth)
1201
+ self.stale = True
1202
+
1203
+ @_docstring.interpd
1204
+ def set_marker(self, marker):
1205
+ """
1206
+ Set the line marker.
1207
+
1208
+ Parameters
1209
+ ----------
1210
+ marker : marker style string, `~.path.Path` or `~.markers.MarkerStyle`
1211
+ See `~matplotlib.markers` for full description of possible
1212
+ arguments.
1213
+ """
1214
+ self._marker = MarkerStyle(marker, self._marker.get_fillstyle())
1215
+ self.stale = True
1216
+
1217
+ def _set_markercolor(self, name, has_rcdefault, val):
1218
+ if val is None:
1219
+ val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto"
1220
+ attr = f"_{name}"
1221
+ current = getattr(self, attr)
1222
+ if current is None:
1223
+ self.stale = True
1224
+ else:
1225
+ neq = current != val
1226
+ # Much faster than `np.any(current != val)` if no arrays are used.
1227
+ if neq.any() if isinstance(neq, np.ndarray) else neq:
1228
+ self.stale = True
1229
+ setattr(self, attr, val)
1230
+
1231
+ def set_markeredgecolor(self, ec):
1232
+ """
1233
+ Set the marker edge color.
1234
+
1235
+ Parameters
1236
+ ----------
1237
+ ec : :mpltype:`color`
1238
+ """
1239
+ self._set_markercolor("markeredgecolor", True, ec)
1240
+
1241
+ def set_markerfacecolor(self, fc):
1242
+ """
1243
+ Set the marker face color.
1244
+
1245
+ Parameters
1246
+ ----------
1247
+ fc : :mpltype:`color`
1248
+ """
1249
+ self._set_markercolor("markerfacecolor", True, fc)
1250
+
1251
+ def set_markerfacecoloralt(self, fc):
1252
+ """
1253
+ Set the alternate marker face color.
1254
+
1255
+ Parameters
1256
+ ----------
1257
+ fc : :mpltype:`color`
1258
+ """
1259
+ self._set_markercolor("markerfacecoloralt", False, fc)
1260
+
1261
+ def set_markeredgewidth(self, ew):
1262
+ """
1263
+ Set the marker edge width in points.
1264
+
1265
+ Parameters
1266
+ ----------
1267
+ ew : float
1268
+ Marker edge width, in points.
1269
+ """
1270
+ if ew is None:
1271
+ ew = mpl.rcParams['lines.markeredgewidth']
1272
+ if self._markeredgewidth != ew:
1273
+ self.stale = True
1274
+ self._markeredgewidth = ew
1275
+
1276
+ def set_markersize(self, sz):
1277
+ """
1278
+ Set the marker size in points.
1279
+
1280
+ Parameters
1281
+ ----------
1282
+ sz : float
1283
+ Marker size, in points.
1284
+ """
1285
+ sz = float(sz)
1286
+ if self._markersize != sz:
1287
+ self.stale = True
1288
+ self._markersize = sz
1289
+
1290
+ def set_xdata(self, x):
1291
+ """
1292
+ Set the data array for x.
1293
+
1294
+ Parameters
1295
+ ----------
1296
+ x : 1D array
1297
+
1298
+ See Also
1299
+ --------
1300
+ set_data
1301
+ set_ydata
1302
+ """
1303
+ if not np.iterable(x):
1304
+ raise RuntimeError('x must be a sequence')
1305
+ self._xorig = copy.copy(x)
1306
+ self._invalidx = True
1307
+ self.stale = True
1308
+
1309
+ def set_ydata(self, y):
1310
+ """
1311
+ Set the data array for y.
1312
+
1313
+ Parameters
1314
+ ----------
1315
+ y : 1D array
1316
+
1317
+ See Also
1318
+ --------
1319
+ set_data
1320
+ set_xdata
1321
+ """
1322
+ if not np.iterable(y):
1323
+ raise RuntimeError('y must be a sequence')
1324
+ self._yorig = copy.copy(y)
1325
+ self._invalidy = True
1326
+ self.stale = True
1327
+
1328
+ def set_dashes(self, seq):
1329
+ """
1330
+ Set the dash sequence.
1331
+
1332
+ The dash sequence is a sequence of floats of even length describing
1333
+ the length of dashes and spaces in points.
1334
+
1335
+ For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
1336
+ dashes separated by 2 point spaces.
1337
+
1338
+ See also `~.Line2D.set_gapcolor`, which allows those spaces to be
1339
+ filled with a color.
1340
+
1341
+ Parameters
1342
+ ----------
1343
+ seq : sequence of floats (on/off ink in points) or (None, None)
1344
+ If *seq* is empty or ``(None, None)``, the linestyle will be set
1345
+ to solid.
1346
+ """
1347
+ if seq == (None, None) or len(seq) == 0:
1348
+ self.set_linestyle('-')
1349
+ else:
1350
+ self.set_linestyle((0, seq))
1351
+
1352
+ def update_from(self, other):
1353
+ """Copy properties from *other* to self."""
1354
+ super().update_from(other)
1355
+ self._linestyle = other._linestyle
1356
+ self._linewidth = other._linewidth
1357
+ self._color = other._color
1358
+ self._gapcolor = other._gapcolor
1359
+ self._markersize = other._markersize
1360
+ self._markerfacecolor = other._markerfacecolor
1361
+ self._markerfacecoloralt = other._markerfacecoloralt
1362
+ self._markeredgecolor = other._markeredgecolor
1363
+ self._markeredgewidth = other._markeredgewidth
1364
+ self._unscaled_dash_pattern = other._unscaled_dash_pattern
1365
+ self._dash_pattern = other._dash_pattern
1366
+ self._dashcapstyle = other._dashcapstyle
1367
+ self._dashjoinstyle = other._dashjoinstyle
1368
+ self._solidcapstyle = other._solidcapstyle
1369
+ self._solidjoinstyle = other._solidjoinstyle
1370
+
1371
+ self._linestyle = other._linestyle
1372
+ self._marker = MarkerStyle(marker=other._marker)
1373
+ self._drawstyle = other._drawstyle
1374
+
1375
+ @_docstring.interpd
1376
+ def set_dash_joinstyle(self, s):
1377
+ """
1378
+ How to join segments of the line if it `~Line2D.is_dashed`.
1379
+
1380
+ The default joinstyle is :rc:`lines.dash_joinstyle`.
1381
+
1382
+ Parameters
1383
+ ----------
1384
+ s : `.JoinStyle` or %(JoinStyle)s
1385
+ """
1386
+ js = JoinStyle(s)
1387
+ if self._dashjoinstyle != js:
1388
+ self.stale = True
1389
+ self._dashjoinstyle = js
1390
+
1391
+ @_docstring.interpd
1392
+ def set_solid_joinstyle(self, s):
1393
+ """
1394
+ How to join segments if the line is solid (not `~Line2D.is_dashed`).
1395
+
1396
+ The default joinstyle is :rc:`lines.solid_joinstyle`.
1397
+
1398
+ Parameters
1399
+ ----------
1400
+ s : `.JoinStyle` or %(JoinStyle)s
1401
+ """
1402
+ js = JoinStyle(s)
1403
+ if self._solidjoinstyle != js:
1404
+ self.stale = True
1405
+ self._solidjoinstyle = js
1406
+
1407
+ def get_dash_joinstyle(self):
1408
+ """
1409
+ Return the `.JoinStyle` for dashed lines.
1410
+
1411
+ See also `~.Line2D.set_dash_joinstyle`.
1412
+ """
1413
+ return self._dashjoinstyle.name
1414
+
1415
+ def get_solid_joinstyle(self):
1416
+ """
1417
+ Return the `.JoinStyle` for solid lines.
1418
+
1419
+ See also `~.Line2D.set_solid_joinstyle`.
1420
+ """
1421
+ return self._solidjoinstyle.name
1422
+
1423
+ @_docstring.interpd
1424
+ def set_dash_capstyle(self, s):
1425
+ """
1426
+ How to draw the end caps if the line is `~Line2D.is_dashed`.
1427
+
1428
+ The default capstyle is :rc:`lines.dash_capstyle`.
1429
+
1430
+ Parameters
1431
+ ----------
1432
+ s : `.CapStyle` or %(CapStyle)s
1433
+ """
1434
+ cs = CapStyle(s)
1435
+ if self._dashcapstyle != cs:
1436
+ self.stale = True
1437
+ self._dashcapstyle = cs
1438
+
1439
+ @_docstring.interpd
1440
+ def set_solid_capstyle(self, s):
1441
+ """
1442
+ How to draw the end caps if the line is solid (not `~Line2D.is_dashed`)
1443
+
1444
+ The default capstyle is :rc:`lines.solid_capstyle`.
1445
+
1446
+ Parameters
1447
+ ----------
1448
+ s : `.CapStyle` or %(CapStyle)s
1449
+ """
1450
+ cs = CapStyle(s)
1451
+ if self._solidcapstyle != cs:
1452
+ self.stale = True
1453
+ self._solidcapstyle = cs
1454
+
1455
+ def get_dash_capstyle(self):
1456
+ """
1457
+ Return the `.CapStyle` for dashed lines.
1458
+
1459
+ See also `~.Line2D.set_dash_capstyle`.
1460
+ """
1461
+ return self._dashcapstyle.name
1462
+
1463
+ def get_solid_capstyle(self):
1464
+ """
1465
+ Return the `.CapStyle` for solid lines.
1466
+
1467
+ See also `~.Line2D.set_solid_capstyle`.
1468
+ """
1469
+ return self._solidcapstyle.name
1470
+
1471
+ def is_dashed(self):
1472
+ """
1473
+ Return whether line has a dashed linestyle.
1474
+
1475
+ A custom linestyle is assumed to be dashed, we do not inspect the
1476
+ ``onoffseq`` directly.
1477
+
1478
+ See also `~.Line2D.set_linestyle`.
1479
+ """
1480
+ return self._linestyle in ('--', '-.', ':')
1481
+
1482
+
1483
+ class AxLine(Line2D):
1484
+ """
1485
+ A helper class that implements `~.Axes.axline`, by recomputing the artist
1486
+ transform at draw time.
1487
+ """
1488
+
1489
+ def __init__(self, xy1, xy2, slope, **kwargs):
1490
+ """
1491
+ Parameters
1492
+ ----------
1493
+ xy1 : (float, float)
1494
+ The first set of (x, y) coordinates for the line to pass through.
1495
+ xy2 : (float, float) or None
1496
+ The second set of (x, y) coordinates for the line to pass through.
1497
+ Both *xy2* and *slope* must be passed, but one of them must be None.
1498
+ slope : float or None
1499
+ The slope of the line. Both *xy2* and *slope* must be passed, but one of
1500
+ them must be None.
1501
+ """
1502
+ super().__init__([0, 1], [0, 1], **kwargs)
1503
+
1504
+ if (xy2 is None and slope is None or
1505
+ xy2 is not None and slope is not None):
1506
+ raise TypeError(
1507
+ "Exactly one of 'xy2' and 'slope' must be given")
1508
+
1509
+ self._slope = slope
1510
+ self._xy1 = xy1
1511
+ self._xy2 = xy2
1512
+
1513
+ def get_transform(self):
1514
+ ax = self.axes
1515
+ points_transform = self._transform - ax.transData + ax.transScale
1516
+
1517
+ if self._xy2 is not None:
1518
+ # two points were given
1519
+ (x1, y1), (x2, y2) = \
1520
+ points_transform.transform([self._xy1, self._xy2])
1521
+ dx = x2 - x1
1522
+ dy = y2 - y1
1523
+ if dx == 0:
1524
+ if dy == 0:
1525
+ raise ValueError(
1526
+ f"Cannot draw a line through two identical points "
1527
+ f"(x={(x1, x2)}, y={(y1, y2)})")
1528
+ slope = np.inf
1529
+ else:
1530
+ slope = dy / dx
1531
+ else:
1532
+ # one point and a slope were given
1533
+ x1, y1 = points_transform.transform(self._xy1)
1534
+ slope = self._slope
1535
+ (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
1536
+ # General case: find intersections with view limits in either
1537
+ # direction, and draw between the middle two points.
1538
+ if slope == 0:
1539
+ start = vxlo, y1
1540
+ stop = vxhi, y1
1541
+ elif np.isinf(slope):
1542
+ start = x1, vylo
1543
+ stop = x1, vyhi
1544
+ else:
1545
+ _, start, stop, _ = sorted([
1546
+ (vxlo, y1 + (vxlo - x1) * slope),
1547
+ (vxhi, y1 + (vxhi - x1) * slope),
1548
+ (x1 + (vylo - y1) / slope, vylo),
1549
+ (x1 + (vyhi - y1) / slope, vyhi),
1550
+ ])
1551
+ return (BboxTransformTo(Bbox([start, stop]))
1552
+ + ax.transLimits + ax.transAxes)
1553
+
1554
+ def draw(self, renderer):
1555
+ self._transformed_path = None # Force regen.
1556
+ super().draw(renderer)
1557
+
1558
+ def get_xy1(self):
1559
+ """Return the *xy1* value of the line."""
1560
+ return self._xy1
1561
+
1562
+ def get_xy2(self):
1563
+ """Return the *xy2* value of the line."""
1564
+ return self._xy2
1565
+
1566
+ def get_slope(self):
1567
+ """Return the *slope* value of the line."""
1568
+ return self._slope
1569
+
1570
+ def set_xy1(self, *args, **kwargs):
1571
+ """
1572
+ Set the *xy1* value of the line.
1573
+
1574
+ Parameters
1575
+ ----------
1576
+ xy1 : tuple[float, float]
1577
+ Points for the line to pass through.
1578
+ """
1579
+ params = _api.select_matching_signature([
1580
+ lambda self, x, y: locals(), lambda self, xy1: locals(),
1581
+ ], self, *args, **kwargs)
1582
+ if "x" in params:
1583
+ _api.warn_deprecated("3.10", message=(
1584
+ "Passing x and y separately to AxLine.set_xy1 is deprecated since "
1585
+ "%(since)s; pass them as a single tuple instead."))
1586
+ xy1 = params["x"], params["y"]
1587
+ else:
1588
+ xy1 = params["xy1"]
1589
+ self._xy1 = xy1
1590
+
1591
+ def set_xy2(self, *args, **kwargs):
1592
+ """
1593
+ Set the *xy2* value of the line.
1594
+
1595
+ .. note::
1596
+
1597
+ You can only set *xy2* if the line was created using the *xy2*
1598
+ parameter. If the line was created using *slope*, please use
1599
+ `~.AxLine.set_slope`.
1600
+
1601
+ Parameters
1602
+ ----------
1603
+ xy2 : tuple[float, float]
1604
+ Points for the line to pass through.
1605
+ """
1606
+ if self._slope is None:
1607
+ params = _api.select_matching_signature([
1608
+ lambda self, x, y: locals(), lambda self, xy2: locals(),
1609
+ ], self, *args, **kwargs)
1610
+ if "x" in params:
1611
+ _api.warn_deprecated("3.10", message=(
1612
+ "Passing x and y separately to AxLine.set_xy2 is deprecated since "
1613
+ "%(since)s; pass them as a single tuple instead."))
1614
+ xy2 = params["x"], params["y"]
1615
+ else:
1616
+ xy2 = params["xy2"]
1617
+ self._xy2 = xy2
1618
+ else:
1619
+ raise ValueError("Cannot set an 'xy2' value while 'slope' is set;"
1620
+ " they differ but their functionalities overlap")
1621
+
1622
+ def set_slope(self, slope):
1623
+ """
1624
+ Set the *slope* value of the line.
1625
+
1626
+ .. note::
1627
+
1628
+ You can only set *slope* if the line was created using the *slope*
1629
+ parameter. If the line was created using *xy2*, please use
1630
+ `~.AxLine.set_xy2`.
1631
+
1632
+ Parameters
1633
+ ----------
1634
+ slope : float
1635
+ The slope of the line.
1636
+ """
1637
+ if self._xy2 is None:
1638
+ self._slope = slope
1639
+ else:
1640
+ raise ValueError("Cannot set a 'slope' value while 'xy2' is set;"
1641
+ " they differ but their functionalities overlap")
1642
+
1643
+
1644
+ class VertexSelector:
1645
+ """
1646
+ Manage the callbacks to maintain a list of selected vertices for `.Line2D`.
1647
+ Derived classes should override the `process_selected` method to do
1648
+ something with the picks.
1649
+
1650
+ Here is an example which highlights the selected verts with red circles::
1651
+
1652
+ import numpy as np
1653
+ import matplotlib.pyplot as plt
1654
+ import matplotlib.lines as lines
1655
+
1656
+ class HighlightSelected(lines.VertexSelector):
1657
+ def __init__(self, line, fmt='ro', **kwargs):
1658
+ super().__init__(line)
1659
+ self.markers, = self.axes.plot([], [], fmt, **kwargs)
1660
+
1661
+ def process_selected(self, ind, xs, ys):
1662
+ self.markers.set_data(xs, ys)
1663
+ self.canvas.draw()
1664
+
1665
+ fig, ax = plt.subplots()
1666
+ x, y = np.random.rand(2, 30)
1667
+ line, = ax.plot(x, y, 'bs-', picker=5)
1668
+
1669
+ selector = HighlightSelected(line)
1670
+ plt.show()
1671
+ """
1672
+
1673
+ def __init__(self, line):
1674
+ """
1675
+ Parameters
1676
+ ----------
1677
+ line : `~matplotlib.lines.Line2D`
1678
+ The line must already have been added to an `~.axes.Axes` and must
1679
+ have its picker property set.
1680
+ """
1681
+ if line.axes is None:
1682
+ raise RuntimeError('You must first add the line to the Axes')
1683
+ if line.get_picker() is None:
1684
+ raise RuntimeError('You must first set the picker property '
1685
+ 'of the line')
1686
+ self.axes = line.axes
1687
+ self.line = line
1688
+ self.cid = self.canvas.callbacks._connect_picklable(
1689
+ 'pick_event', self.onpick)
1690
+ self.ind = set()
1691
+
1692
+ canvas = property(lambda self: self.axes.get_figure(root=True).canvas)
1693
+
1694
+ def process_selected(self, ind, xs, ys):
1695
+ """
1696
+ Default "do nothing" implementation of the `process_selected` method.
1697
+
1698
+ Parameters
1699
+ ----------
1700
+ ind : list of int
1701
+ The indices of the selected vertices.
1702
+ xs, ys : array-like
1703
+ The coordinates of the selected vertices.
1704
+ """
1705
+ pass
1706
+
1707
+ def onpick(self, event):
1708
+ """When the line is picked, update the set of selected indices."""
1709
+ if event.artist is not self.line:
1710
+ return
1711
+ self.ind ^= set(event.ind)
1712
+ ind = sorted(self.ind)
1713
+ xdata, ydata = self.line.get_data()
1714
+ self.process_selected(ind, xdata[ind], ydata[ind])
1715
+
1716
+
1717
+ lineStyles = Line2D._lineStyles
1718
+ lineMarkers = MarkerStyle.markers
1719
+ drawStyles = Line2D.drawStyles
1720
+ fillStyles = MarkerStyle.fillstyles
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .artist import Artist
2
+ from .axes import Axes
3
+ from .backend_bases import MouseEvent, FigureCanvasBase
4
+ from .path import Path
5
+ from .transforms import Bbox
6
+
7
+ from collections.abc import Callable, Sequence
8
+ from typing import Any, Literal, overload
9
+ from .typing import (
10
+ ColorType,
11
+ DrawStyleType,
12
+ FillStyleType,
13
+ LineStyleType,
14
+ CapStyleType,
15
+ JoinStyleType,
16
+ MarkEveryType,
17
+ MarkerType,
18
+ )
19
+ from numpy.typing import ArrayLike
20
+
21
+ def segment_hits(
22
+ cx: ArrayLike, cy: ArrayLike, x: ArrayLike, y: ArrayLike, radius: ArrayLike
23
+ ) -> ArrayLike: ...
24
+
25
+ class Line2D(Artist):
26
+ lineStyles: dict[str, str]
27
+ drawStyles: dict[str, str]
28
+ drawStyleKeys: list[str]
29
+ markers: dict[str | int, str]
30
+ filled_markers: tuple[str, ...]
31
+ fillStyles: tuple[str, ...]
32
+ zorder: float
33
+ ind_offset: float
34
+ def __init__(
35
+ self,
36
+ xdata: ArrayLike,
37
+ ydata: ArrayLike,
38
+ *,
39
+ linewidth: float | None = ...,
40
+ linestyle: LineStyleType | None = ...,
41
+ color: ColorType | None = ...,
42
+ gapcolor: ColorType | None = ...,
43
+ marker: MarkerType | None = ...,
44
+ markersize: float | None = ...,
45
+ markeredgewidth: float | None = ...,
46
+ markeredgecolor: ColorType | None = ...,
47
+ markerfacecolor: ColorType | None = ...,
48
+ markerfacecoloralt: ColorType = ...,
49
+ fillstyle: FillStyleType | None = ...,
50
+ antialiased: bool | None = ...,
51
+ dash_capstyle: CapStyleType | None = ...,
52
+ solid_capstyle: CapStyleType | None = ...,
53
+ dash_joinstyle: JoinStyleType | None = ...,
54
+ solid_joinstyle: JoinStyleType | None = ...,
55
+ pickradius: float = ...,
56
+ drawstyle: DrawStyleType | None = ...,
57
+ markevery: MarkEveryType | None = ...,
58
+ **kwargs
59
+ ) -> None: ...
60
+ def contains(self, mouseevent: MouseEvent) -> tuple[bool, dict]: ...
61
+ def get_pickradius(self) -> float: ...
62
+ def set_pickradius(self, pickradius: float) -> None: ...
63
+ pickradius: float
64
+ def get_fillstyle(self) -> FillStyleType: ...
65
+ stale: bool
66
+ def set_fillstyle(self, fs: FillStyleType) -> None: ...
67
+ def set_markevery(self, every: MarkEveryType) -> None: ...
68
+ def get_markevery(self) -> MarkEveryType: ...
69
+ def set_picker(
70
+ self, p: None | bool | float | Callable[[Artist, MouseEvent], tuple[bool, dict]]
71
+ ) -> None: ...
72
+ def get_bbox(self) -> Bbox: ...
73
+ @overload
74
+ def set_data(self, args: ArrayLike) -> None: ...
75
+ @overload
76
+ def set_data(self, x: ArrayLike, y: ArrayLike) -> None: ...
77
+ def recache_always(self) -> None: ...
78
+ def recache(self, always: bool = ...) -> None: ...
79
+ def get_antialiased(self) -> bool: ...
80
+ def get_color(self) -> ColorType: ...
81
+ def get_drawstyle(self) -> DrawStyleType: ...
82
+ def get_gapcolor(self) -> ColorType: ...
83
+ def get_linestyle(self) -> LineStyleType: ...
84
+ def get_linewidth(self) -> float: ...
85
+ def get_marker(self) -> MarkerType: ...
86
+ def get_markeredgecolor(self) -> ColorType: ...
87
+ def get_markeredgewidth(self) -> float: ...
88
+ def get_markerfacecolor(self) -> ColorType: ...
89
+ def get_markerfacecoloralt(self) -> ColorType: ...
90
+ def get_markersize(self) -> float: ...
91
+ def get_data(self, orig: bool = ...) -> tuple[ArrayLike, ArrayLike]: ...
92
+ def get_xdata(self, orig: bool = ...) -> ArrayLike: ...
93
+ def get_ydata(self, orig: bool = ...) -> ArrayLike: ...
94
+ def get_path(self) -> Path: ...
95
+ def get_xydata(self) -> ArrayLike: ...
96
+ def set_antialiased(self, b: bool) -> None: ...
97
+ def set_color(self, color: ColorType) -> None: ...
98
+ def set_drawstyle(self, drawstyle: DrawStyleType | None) -> None: ...
99
+ def set_gapcolor(self, gapcolor: ColorType | None) -> None: ...
100
+ def set_linewidth(self, w: float) -> None: ...
101
+ def set_linestyle(self, ls: LineStyleType) -> None: ...
102
+ def set_marker(self, marker: MarkerType) -> None: ...
103
+ def set_markeredgecolor(self, ec: ColorType | None) -> None: ...
104
+ def set_markerfacecolor(self, fc: ColorType | None) -> None: ...
105
+ def set_markerfacecoloralt(self, fc: ColorType | None) -> None: ...
106
+ def set_markeredgewidth(self, ew: float | None) -> None: ...
107
+ def set_markersize(self, sz: float) -> None: ...
108
+ def set_xdata(self, x: ArrayLike) -> None: ...
109
+ def set_ydata(self, y: ArrayLike) -> None: ...
110
+ def set_dashes(self, seq: Sequence[float] | tuple[None, None]) -> None: ...
111
+ def update_from(self, other: Artist) -> None: ...
112
+ def set_dash_joinstyle(self, s: JoinStyleType) -> None: ...
113
+ def set_solid_joinstyle(self, s: JoinStyleType) -> None: ...
114
+ def get_dash_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
115
+ def get_solid_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
116
+ def set_dash_capstyle(self, s: CapStyleType) -> None: ...
117
+ def set_solid_capstyle(self, s: CapStyleType) -> None: ...
118
+ def get_dash_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
119
+ def get_solid_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
120
+ def is_dashed(self) -> bool: ...
121
+
122
+ class AxLine(Line2D):
123
+ def __init__(
124
+ self,
125
+ xy1: tuple[float, float],
126
+ xy2: tuple[float, float] | None,
127
+ slope: float | None,
128
+ **kwargs
129
+ ) -> None: ...
130
+ def get_xy1(self) -> tuple[float, float] | None: ...
131
+ def get_xy2(self) -> tuple[float, float] | None: ...
132
+ def get_slope(self) -> float: ...
133
+ def set_xy1(self, xy1: tuple[float, float]) -> None: ...
134
+ def set_xy2(self, xy2: tuple[float, float]) -> None: ...
135
+ def set_slope(self, slope: float) -> None: ...
136
+
137
+ class VertexSelector:
138
+ axes: Axes
139
+ line: Line2D
140
+ cid: int
141
+ ind: set[int]
142
+ def __init__(self, line: Line2D) -> None: ...
143
+ @property
144
+ def canvas(self) -> FigureCanvasBase: ...
145
+ def process_selected(
146
+ self, ind: Sequence[int], xs: ArrayLike, ys: ArrayLike
147
+ ) -> None: ...
148
+ def onpick(self, event: Any) -> None: ...
149
+
150
+ lineStyles: dict[str, str]
151
+ lineMarkers: dict[str | int, str]
152
+ drawStyles: dict[str, str]
153
+ fillStyles: tuple[FillStyleType, ...]
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py ADDED
@@ -0,0 +1,908 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ Functions to handle markers; used by the marker functionality of
3
+ `~matplotlib.axes.Axes.plot`, `~matplotlib.axes.Axes.scatter`, and
4
+ `~matplotlib.axes.Axes.errorbar`.
5
+
6
+ All possible markers are defined here:
7
+
8
+ ============================== ====== =========================================
9
+ marker symbol description
10
+ ============================== ====== =========================================
11
+ ``"."`` |m00| point
12
+ ``","`` |m01| pixel
13
+ ``"o"`` |m02| circle
14
+ ``"v"`` |m03| triangle_down
15
+ ``"^"`` |m04| triangle_up
16
+ ``"<"`` |m05| triangle_left
17
+ ``">"`` |m06| triangle_right
18
+ ``"1"`` |m07| tri_down
19
+ ``"2"`` |m08| tri_up
20
+ ``"3"`` |m09| tri_left
21
+ ``"4"`` |m10| tri_right
22
+ ``"8"`` |m11| octagon
23
+ ``"s"`` |m12| square
24
+ ``"p"`` |m13| pentagon
25
+ ``"P"`` |m23| plus (filled)
26
+ ``"*"`` |m14| star
27
+ ``"h"`` |m15| hexagon1
28
+ ``"H"`` |m16| hexagon2
29
+ ``"+"`` |m17| plus
30
+ ``"x"`` |m18| x
31
+ ``"X"`` |m24| x (filled)
32
+ ``"D"`` |m19| diamond
33
+ ``"d"`` |m20| thin_diamond
34
+ ``"|"`` |m21| vline
35
+ ``"_"`` |m22| hline
36
+ ``0`` (``TICKLEFT``) |m25| tickleft
37
+ ``1`` (``TICKRIGHT``) |m26| tickright
38
+ ``2`` (``TICKUP``) |m27| tickup
39
+ ``3`` (``TICKDOWN``) |m28| tickdown
40
+ ``4`` (``CARETLEFT``) |m29| caretleft
41
+ ``5`` (``CARETRIGHT``) |m30| caretright
42
+ ``6`` (``CARETUP``) |m31| caretup
43
+ ``7`` (``CARETDOWN``) |m32| caretdown
44
+ ``8`` (``CARETLEFTBASE``) |m33| caretleft (centered at base)
45
+ ``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base)
46
+ ``10`` (``CARETUPBASE``) |m35| caretup (centered at base)
47
+ ``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base)
48
+ ``"none"`` or ``"None"`` nothing
49
+ ``" "`` or ``""`` nothing
50
+ ``"$...$"`` |m37| Render the string using mathtext.
51
+ E.g ``"$f$"`` for marker showing the
52
+ letter ``f``.
53
+ ``verts`` A list of (x, y) pairs used for Path
54
+ vertices. The center of the marker is
55
+ located at (0, 0) and the size is
56
+ normalized, such that the created path
57
+ is encapsulated inside the unit cell.
58
+ ``path`` A `~matplotlib.path.Path` instance.
59
+ ``(numsides, 0, angle)`` A regular polygon with ``numsides``
60
+ sides, rotated by ``angle``.
61
+ ``(numsides, 1, angle)`` A star-like symbol with ``numsides``
62
+ sides, rotated by ``angle``.
63
+ ``(numsides, 2, angle)`` An asterisk with ``numsides`` sides,
64
+ rotated by ``angle``.
65
+ ============================== ====== =========================================
66
+
67
+ Note that special symbols can be defined via the
68
+ :ref:`STIX math font <mathtext>`,
69
+ e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the
70
+ `STIX font table <http://www.stixfonts.org/allGlyphs.html>`_.
71
+ Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`.
72
+
73
+ Integer numbers from ``0`` to ``11`` create lines and triangles. Those are
74
+ equally accessible via capitalized variables, like ``CARETDOWNBASE``.
75
+ Hence the following are equivalent::
76
+
77
+ plt.plot([1, 2, 3], marker=11)
78
+ plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE)
79
+
80
+ Markers join and cap styles can be customized by creating a new instance of
81
+ MarkerStyle.
82
+ A MarkerStyle can also have a custom `~matplotlib.transforms.Transform`
83
+ allowing it to be arbitrarily rotated or offset.
84
+
85
+ Examples showing the use of markers:
86
+
87
+ * :doc:`/gallery/lines_bars_and_markers/marker_reference`
88
+ * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly`
89
+ * :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot`
90
+
91
+ .. |m00| image:: /_static/markers/m00.png
92
+ .. |m01| image:: /_static/markers/m01.png
93
+ .. |m02| image:: /_static/markers/m02.png
94
+ .. |m03| image:: /_static/markers/m03.png
95
+ .. |m04| image:: /_static/markers/m04.png
96
+ .. |m05| image:: /_static/markers/m05.png
97
+ .. |m06| image:: /_static/markers/m06.png
98
+ .. |m07| image:: /_static/markers/m07.png
99
+ .. |m08| image:: /_static/markers/m08.png
100
+ .. |m09| image:: /_static/markers/m09.png
101
+ .. |m10| image:: /_static/markers/m10.png
102
+ .. |m11| image:: /_static/markers/m11.png
103
+ .. |m12| image:: /_static/markers/m12.png
104
+ .. |m13| image:: /_static/markers/m13.png
105
+ .. |m14| image:: /_static/markers/m14.png
106
+ .. |m15| image:: /_static/markers/m15.png
107
+ .. |m16| image:: /_static/markers/m16.png
108
+ .. |m17| image:: /_static/markers/m17.png
109
+ .. |m18| image:: /_static/markers/m18.png
110
+ .. |m19| image:: /_static/markers/m19.png
111
+ .. |m20| image:: /_static/markers/m20.png
112
+ .. |m21| image:: /_static/markers/m21.png
113
+ .. |m22| image:: /_static/markers/m22.png
114
+ .. |m23| image:: /_static/markers/m23.png
115
+ .. |m24| image:: /_static/markers/m24.png
116
+ .. |m25| image:: /_static/markers/m25.png
117
+ .. |m26| image:: /_static/markers/m26.png
118
+ .. |m27| image:: /_static/markers/m27.png
119
+ .. |m28| image:: /_static/markers/m28.png
120
+ .. |m29| image:: /_static/markers/m29.png
121
+ .. |m30| image:: /_static/markers/m30.png
122
+ .. |m31| image:: /_static/markers/m31.png
123
+ .. |m32| image:: /_static/markers/m32.png
124
+ .. |m33| image:: /_static/markers/m33.png
125
+ .. |m34| image:: /_static/markers/m34.png
126
+ .. |m35| image:: /_static/markers/m35.png
127
+ .. |m36| image:: /_static/markers/m36.png
128
+ .. |m37| image:: /_static/markers/m37.png
129
+ """
130
+ import copy
131
+
132
+ from collections.abc import Sized
133
+
134
+ import numpy as np
135
+
136
+ import matplotlib as mpl
137
+ from . import _api, cbook
138
+ from .path import Path
139
+ from .transforms import IdentityTransform, Affine2D
140
+ from ._enums import JoinStyle, CapStyle
141
+
142
+ # special-purpose marker identifiers:
143
+ (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
144
+ CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
145
+ CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12)
146
+
147
+ _empty_path = Path(np.empty((0, 2)))
148
+
149
+
150
+ class MarkerStyle:
151
+ """
152
+ A class representing marker types.
153
+
154
+ Instances are immutable. If you need to change anything, create a new
155
+ instance.
156
+
157
+ Attributes
158
+ ----------
159
+ markers : dict
160
+ All known markers.
161
+ filled_markers : tuple
162
+ All known filled markers. This is a subset of *markers*.
163
+ fillstyles : tuple
164
+ The supported fillstyles.
165
+ """
166
+
167
+ markers = {
168
+ '.': 'point',
169
+ ',': 'pixel',
170
+ 'o': 'circle',
171
+ 'v': 'triangle_down',
172
+ '^': 'triangle_up',
173
+ '<': 'triangle_left',
174
+ '>': 'triangle_right',
175
+ '1': 'tri_down',
176
+ '2': 'tri_up',
177
+ '3': 'tri_left',
178
+ '4': 'tri_right',
179
+ '8': 'octagon',
180
+ 's': 'square',
181
+ 'p': 'pentagon',
182
+ '*': 'star',
183
+ 'h': 'hexagon1',
184
+ 'H': 'hexagon2',
185
+ '+': 'plus',
186
+ 'x': 'x',
187
+ 'D': 'diamond',
188
+ 'd': 'thin_diamond',
189
+ '|': 'vline',
190
+ '_': 'hline',
191
+ 'P': 'plus_filled',
192
+ 'X': 'x_filled',
193
+ TICKLEFT: 'tickleft',
194
+ TICKRIGHT: 'tickright',
195
+ TICKUP: 'tickup',
196
+ TICKDOWN: 'tickdown',
197
+ CARETLEFT: 'caretleft',
198
+ CARETRIGHT: 'caretright',
199
+ CARETUP: 'caretup',
200
+ CARETDOWN: 'caretdown',
201
+ CARETLEFTBASE: 'caretleftbase',
202
+ CARETRIGHTBASE: 'caretrightbase',
203
+ CARETUPBASE: 'caretupbase',
204
+ CARETDOWNBASE: 'caretdownbase',
205
+ "None": 'nothing',
206
+ "none": 'nothing',
207
+ ' ': 'nothing',
208
+ '': 'nothing'
209
+ }
210
+
211
+ # Just used for informational purposes. is_filled()
212
+ # is calculated in the _set_* functions.
213
+ filled_markers = (
214
+ '.', 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd',
215
+ 'P', 'X')
216
+
217
+ fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none')
218
+ _half_fillstyles = ('left', 'right', 'bottom', 'top')
219
+
220
+ def __init__(self, marker,
221
+ fillstyle=None, transform=None, capstyle=None, joinstyle=None):
222
+ """
223
+ Parameters
224
+ ----------
225
+ marker : str, array-like, Path, MarkerStyle
226
+ - Another instance of `MarkerStyle` copies the details of that *marker*.
227
+ - For other possible marker values, see the module docstring
228
+ `matplotlib.markers`.
229
+
230
+ fillstyle : str, default: :rc:`markers.fillstyle`
231
+ One of 'full', 'left', 'right', 'bottom', 'top', 'none'.
232
+
233
+ transform : `~matplotlib.transforms.Transform`, optional
234
+ Transform that will be combined with the native transform of the
235
+ marker.
236
+
237
+ capstyle : `.CapStyle` or %(CapStyle)s, optional
238
+ Cap style that will override the default cap style of the marker.
239
+
240
+ joinstyle : `.JoinStyle` or %(JoinStyle)s, optional
241
+ Join style that will override the default join style of the marker.
242
+ """
243
+ self._marker_function = None
244
+ self._user_transform = transform
245
+ self._user_capstyle = CapStyle(capstyle) if capstyle is not None else None
246
+ self._user_joinstyle = JoinStyle(joinstyle) if joinstyle is not None else None
247
+ self._set_fillstyle(fillstyle)
248
+ self._set_marker(marker)
249
+
250
+ def _recache(self):
251
+ if self._marker_function is None:
252
+ return
253
+ self._path = _empty_path
254
+ self._transform = IdentityTransform()
255
+ self._alt_path = None
256
+ self._alt_transform = None
257
+ self._snap_threshold = None
258
+ self._joinstyle = JoinStyle.round
259
+ self._capstyle = self._user_capstyle or CapStyle.butt
260
+ # Initial guess: Assume the marker is filled unless the fillstyle is
261
+ # set to 'none'. The marker function will override this for unfilled
262
+ # markers.
263
+ self._filled = self._fillstyle != 'none'
264
+ self._marker_function()
265
+
266
+ def __bool__(self):
267
+ return bool(len(self._path.vertices))
268
+
269
+ def is_filled(self):
270
+ return self._filled
271
+
272
+ def get_fillstyle(self):
273
+ return self._fillstyle
274
+
275
+ def _set_fillstyle(self, fillstyle):
276
+ """
277
+ Set the fillstyle.
278
+
279
+ Parameters
280
+ ----------
281
+ fillstyle : {'full', 'left', 'right', 'bottom', 'top', 'none'}
282
+ The part of the marker surface that is colored with
283
+ markerfacecolor.
284
+ """
285
+ if fillstyle is None:
286
+ fillstyle = mpl.rcParams['markers.fillstyle']
287
+ _api.check_in_list(self.fillstyles, fillstyle=fillstyle)
288
+ self._fillstyle = fillstyle
289
+
290
+ def get_joinstyle(self):
291
+ return self._joinstyle.name
292
+
293
+ def get_capstyle(self):
294
+ return self._capstyle.name
295
+
296
+ def get_marker(self):
297
+ return self._marker
298
+
299
+ def _set_marker(self, marker):
300
+ """
301
+ Set the marker.
302
+
303
+ Parameters
304
+ ----------
305
+ marker : str, array-like, Path, MarkerStyle
306
+ - Another instance of `MarkerStyle` copies the details of that *marker*.
307
+ - For other possible marker values see the module docstring
308
+ `matplotlib.markers`.
309
+ """
310
+ if isinstance(marker, str) and cbook.is_math_text(marker):
311
+ self._marker_function = self._set_mathtext_path
312
+ elif isinstance(marker, (int, str)) and marker in self.markers:
313
+ self._marker_function = getattr(self, '_set_' + self.markers[marker])
314
+ elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and
315
+ marker.shape[1] == 2):
316
+ self._marker_function = self._set_vertices
317
+ elif isinstance(marker, Path):
318
+ self._marker_function = self._set_path_marker
319
+ elif (isinstance(marker, Sized) and len(marker) in (2, 3) and
320
+ marker[1] in (0, 1, 2)):
321
+ self._marker_function = self._set_tuple_marker
322
+ elif isinstance(marker, MarkerStyle):
323
+ self.__dict__ = copy.deepcopy(marker.__dict__)
324
+ else:
325
+ try:
326
+ Path(marker)
327
+ self._marker_function = self._set_vertices
328
+ except ValueError as err:
329
+ raise ValueError(
330
+ f'Unrecognized marker style {marker!r}') from err
331
+
332
+ if not isinstance(marker, MarkerStyle):
333
+ self._marker = marker
334
+ self._recache()
335
+
336
+ def get_path(self):
337
+ """
338
+ Return a `.Path` for the primary part of the marker.
339
+
340
+ For unfilled markers this is the whole marker, for filled markers,
341
+ this is the area to be drawn with *markerfacecolor*.
342
+ """
343
+ return self._path
344
+
345
+ def get_transform(self):
346
+ """
347
+ Return the transform to be applied to the `.Path` from
348
+ `MarkerStyle.get_path()`.
349
+ """
350
+ if self._user_transform is None:
351
+ return self._transform.frozen()
352
+ else:
353
+ return (self._transform + self._user_transform).frozen()
354
+
355
+ def get_alt_path(self):
356
+ """
357
+ Return a `.Path` for the alternate part of the marker.
358
+
359
+ For unfilled markers, this is *None*; for filled markers, this is the
360
+ area to be drawn with *markerfacecoloralt*.
361
+ """
362
+ return self._alt_path
363
+
364
+ def get_alt_transform(self):
365
+ """
366
+ Return the transform to be applied to the `.Path` from
367
+ `MarkerStyle.get_alt_path()`.
368
+ """
369
+ if self._user_transform is None:
370
+ return self._alt_transform.frozen()
371
+ else:
372
+ return (self._alt_transform + self._user_transform).frozen()
373
+
374
+ def get_snap_threshold(self):
375
+ return self._snap_threshold
376
+
377
+ def get_user_transform(self):
378
+ """Return user supplied part of marker transform."""
379
+ if self._user_transform is not None:
380
+ return self._user_transform.frozen()
381
+
382
+ def transformed(self, transform):
383
+ """
384
+ Return a new version of this marker with the transform applied.
385
+
386
+ Parameters
387
+ ----------
388
+ transform : `~matplotlib.transforms.Affine2D`
389
+ Transform will be combined with current user supplied transform.
390
+ """
391
+ new_marker = MarkerStyle(self)
392
+ if new_marker._user_transform is not None:
393
+ new_marker._user_transform += transform
394
+ else:
395
+ new_marker._user_transform = transform
396
+ return new_marker
397
+
398
+ def rotated(self, *, deg=None, rad=None):
399
+ """
400
+ Return a new version of this marker rotated by specified angle.
401
+
402
+ Parameters
403
+ ----------
404
+ deg : float, optional
405
+ Rotation angle in degrees.
406
+
407
+ rad : float, optional
408
+ Rotation angle in radians.
409
+
410
+ .. note:: You must specify exactly one of deg or rad.
411
+ """
412
+ if deg is None and rad is None:
413
+ raise ValueError('One of deg or rad is required')
414
+ if deg is not None and rad is not None:
415
+ raise ValueError('Only one of deg and rad can be supplied')
416
+ new_marker = MarkerStyle(self)
417
+ if new_marker._user_transform is None:
418
+ new_marker._user_transform = Affine2D()
419
+
420
+ if deg is not None:
421
+ new_marker._user_transform.rotate_deg(deg)
422
+ if rad is not None:
423
+ new_marker._user_transform.rotate(rad)
424
+
425
+ return new_marker
426
+
427
+ def scaled(self, sx, sy=None):
428
+ """
429
+ Return new marker scaled by specified scale factors.
430
+
431
+ If *sy* is not given, the same scale is applied in both the *x*- and
432
+ *y*-directions.
433
+
434
+ Parameters
435
+ ----------
436
+ sx : float
437
+ *X*-direction scaling factor.
438
+ sy : float, optional
439
+ *Y*-direction scaling factor.
440
+ """
441
+ if sy is None:
442
+ sy = sx
443
+
444
+ new_marker = MarkerStyle(self)
445
+ _transform = new_marker._user_transform or Affine2D()
446
+ new_marker._user_transform = _transform.scale(sx, sy)
447
+ return new_marker
448
+
449
+ def _set_nothing(self):
450
+ self._filled = False
451
+
452
+ def _set_custom_marker(self, path):
453
+ rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
454
+ self._transform = Affine2D().scale(0.5 / rescale)
455
+ self._path = path
456
+
457
+ def _set_path_marker(self):
458
+ self._set_custom_marker(self._marker)
459
+
460
+ def _set_vertices(self):
461
+ self._set_custom_marker(Path(self._marker))
462
+
463
+ def _set_tuple_marker(self):
464
+ marker = self._marker
465
+ if len(marker) == 2:
466
+ numsides, rotation = marker[0], 0.0
467
+ elif len(marker) == 3:
468
+ numsides, rotation = marker[0], marker[2]
469
+ symstyle = marker[1]
470
+ if symstyle == 0:
471
+ self._path = Path.unit_regular_polygon(numsides)
472
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
473
+ elif symstyle == 1:
474
+ self._path = Path.unit_regular_star(numsides)
475
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
476
+ elif symstyle == 2:
477
+ self._path = Path.unit_regular_asterisk(numsides)
478
+ self._filled = False
479
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
480
+ else:
481
+ raise ValueError(f"Unexpected tuple marker: {marker}")
482
+ self._transform = Affine2D().scale(0.5).rotate_deg(rotation)
483
+
484
+ def _set_mathtext_path(self):
485
+ """
486
+ Draw mathtext markers '$...$' using `.TextPath` object.
487
+
488
+ Submitted by tcb
489
+ """
490
+ from matplotlib.text import TextPath
491
+
492
+ # again, the properties could be initialised just once outside
493
+ # this function
494
+ text = TextPath(xy=(0, 0), s=self.get_marker(),
495
+ usetex=mpl.rcParams['text.usetex'])
496
+ if len(text.vertices) == 0:
497
+ return
498
+
499
+ bbox = text.get_extents()
500
+ max_dim = max(bbox.width, bbox.height)
501
+ self._transform = (
502
+ Affine2D()
503
+ .translate(-bbox.xmin + 0.5 * -bbox.width, -bbox.ymin + 0.5 * -bbox.height)
504
+ .scale(1.0 / max_dim))
505
+ self._path = text
506
+ self._snap = False
507
+
508
+ def _half_fill(self):
509
+ return self.get_fillstyle() in self._half_fillstyles
510
+
511
+ def _set_circle(self, size=1.0):
512
+ self._transform = Affine2D().scale(0.5 * size)
513
+ self._snap_threshold = np.inf
514
+ if not self._half_fill():
515
+ self._path = Path.unit_circle()
516
+ else:
517
+ self._path = self._alt_path = Path.unit_circle_righthalf()
518
+ fs = self.get_fillstyle()
519
+ self._transform.rotate_deg(
520
+ {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs])
521
+ self._alt_transform = self._transform.frozen().rotate_deg(180.)
522
+
523
+ def _set_point(self):
524
+ self._set_circle(size=0.5)
525
+
526
+ def _set_pixel(self):
527
+ self._path = Path.unit_rectangle()
528
+ # Ideally, you'd want -0.5, -0.5 here, but then the snapping
529
+ # algorithm in the Agg backend will round this to a 2x2
530
+ # rectangle from (-1, -1) to (1, 1). By offsetting it
531
+ # slightly, we can force it to be (0, 0) to (1, 1), which both
532
+ # makes it only be a single pixel and places it correctly
533
+ # aligned to 1-width stroking (i.e. the ticks). This hack is
534
+ # the best of a number of bad alternatives, mainly because the
535
+ # backends are not aware of what marker is actually being used
536
+ # beyond just its path data.
537
+ self._transform = Affine2D().translate(-0.49999, -0.49999)
538
+ self._snap_threshold = None
539
+
540
+ _triangle_path = Path._create_closed([[0, 1], [-1, -1], [1, -1]])
541
+ # Going down halfway looks to small. Golden ratio is too far.
542
+ _triangle_path_u = Path._create_closed([[0, 1], [-3/5, -1/5], [3/5, -1/5]])
543
+ _triangle_path_d = Path._create_closed(
544
+ [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1]])
545
+ _triangle_path_l = Path._create_closed([[0, 1], [0, -1], [-1, -1]])
546
+ _triangle_path_r = Path._create_closed([[0, 1], [0, -1], [1, -1]])
547
+
548
+ def _set_triangle(self, rot, skip):
549
+ self._transform = Affine2D().scale(0.5).rotate_deg(rot)
550
+ self._snap_threshold = 5.0
551
+
552
+ if not self._half_fill():
553
+ self._path = self._triangle_path
554
+ else:
555
+ mpaths = [self._triangle_path_u,
556
+ self._triangle_path_l,
557
+ self._triangle_path_d,
558
+ self._triangle_path_r]
559
+
560
+ fs = self.get_fillstyle()
561
+ if fs == 'top':
562
+ self._path = mpaths[(0 + skip) % 4]
563
+ self._alt_path = mpaths[(2 + skip) % 4]
564
+ elif fs == 'bottom':
565
+ self._path = mpaths[(2 + skip) % 4]
566
+ self._alt_path = mpaths[(0 + skip) % 4]
567
+ elif fs == 'left':
568
+ self._path = mpaths[(1 + skip) % 4]
569
+ self._alt_path = mpaths[(3 + skip) % 4]
570
+ else:
571
+ self._path = mpaths[(3 + skip) % 4]
572
+ self._alt_path = mpaths[(1 + skip) % 4]
573
+
574
+ self._alt_transform = self._transform
575
+
576
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
577
+
578
+ def _set_triangle_up(self):
579
+ return self._set_triangle(0.0, 0)
580
+
581
+ def _set_triangle_down(self):
582
+ return self._set_triangle(180.0, 2)
583
+
584
+ def _set_triangle_left(self):
585
+ return self._set_triangle(90.0, 3)
586
+
587
+ def _set_triangle_right(self):
588
+ return self._set_triangle(270.0, 1)
589
+
590
+ def _set_square(self):
591
+ self._transform = Affine2D().translate(-0.5, -0.5)
592
+ self._snap_threshold = 2.0
593
+ if not self._half_fill():
594
+ self._path = Path.unit_rectangle()
595
+ else:
596
+ # Build a bottom filled square out of two rectangles, one filled.
597
+ self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 0.5],
598
+ [0.0, 0.5], [0.0, 0.0]])
599
+ self._alt_path = Path([[0.0, 0.5], [1.0, 0.5], [1.0, 1.0],
600
+ [0.0, 1.0], [0.0, 0.5]])
601
+ fs = self.get_fillstyle()
602
+ rotate = {'bottom': 0, 'right': 90, 'top': 180, 'left': 270}[fs]
603
+ self._transform.rotate_deg(rotate)
604
+ self._alt_transform = self._transform
605
+
606
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
607
+
608
+ def _set_diamond(self):
609
+ self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45)
610
+ self._snap_threshold = 5.0
611
+ if not self._half_fill():
612
+ self._path = Path.unit_rectangle()
613
+ else:
614
+ self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]])
615
+ self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]])
616
+ fs = self.get_fillstyle()
617
+ rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs]
618
+ self._transform.rotate_deg(rotate)
619
+ self._alt_transform = self._transform
620
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
621
+
622
+ def _set_thin_diamond(self):
623
+ self._set_diamond()
624
+ self._transform.scale(0.6, 1.0)
625
+
626
+ def _set_pentagon(self):
627
+ self._transform = Affine2D().scale(0.5)
628
+ self._snap_threshold = 5.0
629
+
630
+ polypath = Path.unit_regular_polygon(5)
631
+
632
+ if not self._half_fill():
633
+ self._path = polypath
634
+ else:
635
+ verts = polypath.vertices
636
+ y = (1 + np.sqrt(5)) / 4.
637
+ top = Path(verts[[0, 1, 4, 0]])
638
+ bottom = Path(verts[[1, 2, 3, 4, 1]])
639
+ left = Path([verts[0], verts[1], verts[2], [0, -y], verts[0]])
640
+ right = Path([verts[0], verts[4], verts[3], [0, -y], verts[0]])
641
+ self._path, self._alt_path = {
642
+ 'top': (top, bottom), 'bottom': (bottom, top),
643
+ 'left': (left, right), 'right': (right, left),
644
+ }[self.get_fillstyle()]
645
+ self._alt_transform = self._transform
646
+
647
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
648
+
649
+ def _set_star(self):
650
+ self._transform = Affine2D().scale(0.5)
651
+ self._snap_threshold = 5.0
652
+
653
+ polypath = Path.unit_regular_star(5, innerCircle=0.381966)
654
+
655
+ if not self._half_fill():
656
+ self._path = polypath
657
+ else:
658
+ verts = polypath.vertices
659
+ top = Path(np.concatenate([verts[0:4], verts[7:10], verts[0:1]]))
660
+ bottom = Path(np.concatenate([verts[3:8], verts[3:4]]))
661
+ left = Path(np.concatenate([verts[0:6], verts[0:1]]))
662
+ right = Path(np.concatenate([verts[0:1], verts[5:10], verts[0:1]]))
663
+ self._path, self._alt_path = {
664
+ 'top': (top, bottom), 'bottom': (bottom, top),
665
+ 'left': (left, right), 'right': (right, left),
666
+ }[self.get_fillstyle()]
667
+ self._alt_transform = self._transform
668
+
669
+ self._joinstyle = self._user_joinstyle or JoinStyle.bevel
670
+
671
+ def _set_hexagon1(self):
672
+ self._transform = Affine2D().scale(0.5)
673
+ self._snap_threshold = None
674
+
675
+ polypath = Path.unit_regular_polygon(6)
676
+
677
+ if not self._half_fill():
678
+ self._path = polypath
679
+ else:
680
+ verts = polypath.vertices
681
+ # not drawing inside lines
682
+ x = np.abs(np.cos(5 * np.pi / 6.))
683
+ top = Path(np.concatenate([[(-x, 0)], verts[[1, 0, 5]], [(x, 0)]]))
684
+ bottom = Path(np.concatenate([[(-x, 0)], verts[2:5], [(x, 0)]]))
685
+ left = Path(verts[0:4])
686
+ right = Path(verts[[0, 5, 4, 3]])
687
+ self._path, self._alt_path = {
688
+ 'top': (top, bottom), 'bottom': (bottom, top),
689
+ 'left': (left, right), 'right': (right, left),
690
+ }[self.get_fillstyle()]
691
+ self._alt_transform = self._transform
692
+
693
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
694
+
695
+ def _set_hexagon2(self):
696
+ self._transform = Affine2D().scale(0.5).rotate_deg(30)
697
+ self._snap_threshold = None
698
+
699
+ polypath = Path.unit_regular_polygon(6)
700
+
701
+ if not self._half_fill():
702
+ self._path = polypath
703
+ else:
704
+ verts = polypath.vertices
705
+ # not drawing inside lines
706
+ x, y = np.sqrt(3) / 4, 3 / 4.
707
+ top = Path(verts[[1, 0, 5, 4, 1]])
708
+ bottom = Path(verts[1:5])
709
+ left = Path(np.concatenate([
710
+ [(x, y)], verts[:3], [(-x, -y), (x, y)]]))
711
+ right = Path(np.concatenate([
712
+ [(x, y)], verts[5:2:-1], [(-x, -y)]]))
713
+ self._path, self._alt_path = {
714
+ 'top': (top, bottom), 'bottom': (bottom, top),
715
+ 'left': (left, right), 'right': (right, left),
716
+ }[self.get_fillstyle()]
717
+ self._alt_transform = self._transform
718
+
719
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
720
+
721
+ def _set_octagon(self):
722
+ self._transform = Affine2D().scale(0.5)
723
+ self._snap_threshold = 5.0
724
+
725
+ polypath = Path.unit_regular_polygon(8)
726
+
727
+ if not self._half_fill():
728
+ self._transform.rotate_deg(22.5)
729
+ self._path = polypath
730
+ else:
731
+ x = np.sqrt(2.) / 4.
732
+ self._path = self._alt_path = Path(
733
+ [[0, -1], [0, 1], [-x, 1], [-1, x],
734
+ [-1, -x], [-x, -1], [0, -1]])
735
+ fs = self.get_fillstyle()
736
+ self._transform.rotate_deg(
737
+ {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs])
738
+ self._alt_transform = self._transform.frozen().rotate_deg(180.0)
739
+
740
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
741
+
742
+ _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]])
743
+
744
+ def _set_vline(self):
745
+ self._transform = Affine2D().scale(0.5)
746
+ self._snap_threshold = 1.0
747
+ self._filled = False
748
+ self._path = self._line_marker_path
749
+
750
+ def _set_hline(self):
751
+ self._set_vline()
752
+ self._transform = self._transform.rotate_deg(90)
753
+
754
+ _tickhoriz_path = Path([[0.0, 0.0], [1.0, 0.0]])
755
+
756
+ def _set_tickleft(self):
757
+ self._transform = Affine2D().scale(-1.0, 1.0)
758
+ self._snap_threshold = 1.0
759
+ self._filled = False
760
+ self._path = self._tickhoriz_path
761
+
762
+ def _set_tickright(self):
763
+ self._transform = Affine2D().scale(1.0, 1.0)
764
+ self._snap_threshold = 1.0
765
+ self._filled = False
766
+ self._path = self._tickhoriz_path
767
+
768
+ _tickvert_path = Path([[-0.0, 0.0], [-0.0, 1.0]])
769
+
770
+ def _set_tickup(self):
771
+ self._transform = Affine2D().scale(1.0, 1.0)
772
+ self._snap_threshold = 1.0
773
+ self._filled = False
774
+ self._path = self._tickvert_path
775
+
776
+ def _set_tickdown(self):
777
+ self._transform = Affine2D().scale(1.0, -1.0)
778
+ self._snap_threshold = 1.0
779
+ self._filled = False
780
+ self._path = self._tickvert_path
781
+
782
+ _tri_path = Path([[0.0, 0.0], [0.0, -1.0],
783
+ [0.0, 0.0], [0.8, 0.5],
784
+ [0.0, 0.0], [-0.8, 0.5]],
785
+ [Path.MOVETO, Path.LINETO,
786
+ Path.MOVETO, Path.LINETO,
787
+ Path.MOVETO, Path.LINETO])
788
+
789
+ def _set_tri_down(self):
790
+ self._transform = Affine2D().scale(0.5)
791
+ self._snap_threshold = 5.0
792
+ self._filled = False
793
+ self._path = self._tri_path
794
+
795
+ def _set_tri_up(self):
796
+ self._set_tri_down()
797
+ self._transform = self._transform.rotate_deg(180)
798
+
799
+ def _set_tri_left(self):
800
+ self._set_tri_down()
801
+ self._transform = self._transform.rotate_deg(270)
802
+
803
+ def _set_tri_right(self):
804
+ self._set_tri_down()
805
+ self._transform = self._transform.rotate_deg(90)
806
+
807
+ _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]])
808
+
809
+ def _set_caretdown(self):
810
+ self._transform = Affine2D().scale(0.5)
811
+ self._snap_threshold = 3.0
812
+ self._filled = False
813
+ self._path = self._caret_path
814
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
815
+
816
+ def _set_caretup(self):
817
+ self._set_caretdown()
818
+ self._transform = self._transform.rotate_deg(180)
819
+
820
+ def _set_caretleft(self):
821
+ self._set_caretdown()
822
+ self._transform = self._transform.rotate_deg(270)
823
+
824
+ def _set_caretright(self):
825
+ self._set_caretdown()
826
+ self._transform = self._transform.rotate_deg(90)
827
+
828
+ _caret_path_base = Path([[-1.0, 0.0], [0.0, -1.5], [1.0, 0]])
829
+
830
+ def _set_caretdownbase(self):
831
+ self._set_caretdown()
832
+ self._path = self._caret_path_base
833
+
834
+ def _set_caretupbase(self):
835
+ self._set_caretdownbase()
836
+ self._transform = self._transform.rotate_deg(180)
837
+
838
+ def _set_caretleftbase(self):
839
+ self._set_caretdownbase()
840
+ self._transform = self._transform.rotate_deg(270)
841
+
842
+ def _set_caretrightbase(self):
843
+ self._set_caretdownbase()
844
+ self._transform = self._transform.rotate_deg(90)
845
+
846
+ _plus_path = Path([[-1.0, 0.0], [1.0, 0.0],
847
+ [0.0, -1.0], [0.0, 1.0]],
848
+ [Path.MOVETO, Path.LINETO,
849
+ Path.MOVETO, Path.LINETO])
850
+
851
+ def _set_plus(self):
852
+ self._transform = Affine2D().scale(0.5)
853
+ self._snap_threshold = 1.0
854
+ self._filled = False
855
+ self._path = self._plus_path
856
+
857
+ _x_path = Path([[-1.0, -1.0], [1.0, 1.0],
858
+ [-1.0, 1.0], [1.0, -1.0]],
859
+ [Path.MOVETO, Path.LINETO,
860
+ Path.MOVETO, Path.LINETO])
861
+
862
+ def _set_x(self):
863
+ self._transform = Affine2D().scale(0.5)
864
+ self._snap_threshold = 3.0
865
+ self._filled = False
866
+ self._path = self._x_path
867
+
868
+ _plus_filled_path = Path._create_closed(np.array([
869
+ (-1, -3), (+1, -3), (+1, -1), (+3, -1), (+3, +1), (+1, +1),
870
+ (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, -1), (-1, -1)]) / 6)
871
+ _plus_filled_path_t = Path._create_closed(np.array([
872
+ (+3, 0), (+3, +1), (+1, +1), (+1, +3),
873
+ (-1, +3), (-1, +1), (-3, +1), (-3, 0)]) / 6)
874
+
875
+ def _set_plus_filled(self):
876
+ self._transform = Affine2D()
877
+ self._snap_threshold = 5.0
878
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
879
+ if not self._half_fill():
880
+ self._path = self._plus_filled_path
881
+ else:
882
+ # Rotate top half path to support all partitions
883
+ self._path = self._alt_path = self._plus_filled_path_t
884
+ fs = self.get_fillstyle()
885
+ self._transform.rotate_deg(
886
+ {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
887
+ self._alt_transform = self._transform.frozen().rotate_deg(180)
888
+
889
+ _x_filled_path = Path._create_closed(np.array([
890
+ (-1, -2), (0, -1), (+1, -2), (+2, -1), (+1, 0), (+2, +1),
891
+ (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0), (-2, -1)]) / 4)
892
+ _x_filled_path_t = Path._create_closed(np.array([
893
+ (+1, 0), (+2, +1), (+1, +2), (0, +1),
894
+ (-1, +2), (-2, +1), (-1, 0)]) / 4)
895
+
896
+ def _set_x_filled(self):
897
+ self._transform = Affine2D()
898
+ self._snap_threshold = 5.0
899
+ self._joinstyle = self._user_joinstyle or JoinStyle.miter
900
+ if not self._half_fill():
901
+ self._path = self._x_filled_path
902
+ else:
903
+ # Rotate top half path to support all partitions
904
+ self._path = self._alt_path = self._x_filled_path_t
905
+ fs = self.get_fillstyle()
906
+ self._transform.rotate_deg(
907
+ {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
908
+ self._alt_transform = self._transform.frozen().rotate_deg(180)
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal
2
+
3
+ from .path import Path
4
+ from .transforms import Affine2D, Transform
5
+
6
+ from numpy.typing import ArrayLike
7
+ from .typing import CapStyleType, FillStyleType, JoinStyleType
8
+
9
+ TICKLEFT: int
10
+ TICKRIGHT: int
11
+ TICKUP: int
12
+ TICKDOWN: int
13
+ CARETLEFT: int
14
+ CARETRIGHT: int
15
+ CARETUP: int
16
+ CARETDOWN: int
17
+ CARETLEFTBASE: int
18
+ CARETRIGHTBASE: int
19
+ CARETUPBASE: int
20
+ CARETDOWNBASE: int
21
+
22
+ class MarkerStyle:
23
+ markers: dict[str | int, str]
24
+ filled_markers: tuple[str, ...]
25
+ fillstyles: tuple[FillStyleType, ...]
26
+
27
+ def __init__(
28
+ self,
29
+ marker: str | ArrayLike | Path | MarkerStyle,
30
+ fillstyle: FillStyleType | None = ...,
31
+ transform: Transform | None = ...,
32
+ capstyle: CapStyleType | None = ...,
33
+ joinstyle: JoinStyleType | None = ...,
34
+ ) -> None: ...
35
+ def __bool__(self) -> bool: ...
36
+ def is_filled(self) -> bool: ...
37
+ def get_fillstyle(self) -> FillStyleType: ...
38
+ def get_joinstyle(self) -> Literal["miter", "round", "bevel"]: ...
39
+ def get_capstyle(self) -> Literal["butt", "projecting", "round"]: ...
40
+ def get_marker(self) -> str | ArrayLike | Path | None: ...
41
+ def get_path(self) -> Path: ...
42
+ def get_transform(self) -> Transform: ...
43
+ def get_alt_path(self) -> Path | None: ...
44
+ def get_alt_transform(self) -> Transform: ...
45
+ def get_snap_threshold(self) -> float | None: ...
46
+ def get_user_transform(self) -> Transform | None: ...
47
+ def transformed(self, transform: Affine2D) -> MarkerStyle: ...
48
+ def rotated(
49
+ self, *, deg: float | None = ..., rad: float | None = ...
50
+ ) -> MarkerStyle: ...
51
+ def scaled(self, sx: float, sy: float | None = ...) -> MarkerStyle: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ A module for parsing a subset of the TeX math syntax and rendering it to a
3
+ Matplotlib backend.
4
+
5
+ For a tutorial of its usage, see :ref:`mathtext`. This
6
+ document is primarily concerned with implementation details.
7
+
8
+ The module uses pyparsing_ to parse the TeX expression.
9
+
10
+ .. _pyparsing: https://pypi.org/project/pyparsing/
11
+
12
+ The Bakoma distribution of the TeX Computer Modern fonts, and STIX
13
+ fonts are supported. There is experimental support for using
14
+ arbitrary fonts, but results may vary without proper tweaking and
15
+ metrics for those fonts.
16
+ """
17
+
18
+ import functools
19
+ import logging
20
+
21
+ import matplotlib as mpl
22
+ from matplotlib import _api, _mathtext
23
+ from matplotlib.ft2font import LoadFlags
24
+ from matplotlib.font_manager import FontProperties
25
+ from ._mathtext import ( # noqa: F401, reexported API
26
+ RasterParse, VectorParse, get_unicode_index)
27
+
28
+ _log = logging.getLogger(__name__)
29
+
30
+
31
+ get_unicode_index.__module__ = __name__
32
+
33
+ ##############################################################################
34
+ # MAIN
35
+
36
+
37
+ class MathTextParser:
38
+ _parser = None
39
+ _font_type_mapping = {
40
+ 'cm': _mathtext.BakomaFonts,
41
+ 'dejavuserif': _mathtext.DejaVuSerifFonts,
42
+ 'dejavusans': _mathtext.DejaVuSansFonts,
43
+ 'stix': _mathtext.StixFonts,
44
+ 'stixsans': _mathtext.StixSansFonts,
45
+ 'custom': _mathtext.UnicodeFonts,
46
+ }
47
+
48
+ def __init__(self, output):
49
+ """
50
+ Create a MathTextParser for the given backend *output*.
51
+
52
+ Parameters
53
+ ----------
54
+ output : {"path", "agg"}
55
+ Whether to return a `VectorParse` ("path") or a
56
+ `RasterParse` ("agg", or its synonym "macosx").
57
+ """
58
+ self._output_type = _api.check_getitem(
59
+ {"path": "vector", "agg": "raster", "macosx": "raster"},
60
+ output=output.lower())
61
+
62
+ def parse(self, s, dpi=72, prop=None, *, antialiased=None):
63
+ """
64
+ Parse the given math expression *s* at the given *dpi*. If *prop* is
65
+ provided, it is a `.FontProperties` object specifying the "default"
66
+ font to use in the math expression, used for all non-math text.
67
+
68
+ The results are cached, so multiple calls to `parse`
69
+ with the same expression should be fast.
70
+
71
+ Depending on the *output* type, this returns either a `VectorParse` or
72
+ a `RasterParse`.
73
+ """
74
+ # lru_cache can't decorate parse() directly because prop is
75
+ # mutable, so we key the cache using an internal copy (see
76
+ # Text._get_text_metrics_with_cache for a similar case); likewise,
77
+ # we need to check the mutable state of the text.antialiased and
78
+ # text.hinting rcParams.
79
+ prop = prop.copy() if prop is not None else None
80
+ antialiased = mpl._val_or_rc(antialiased, 'text.antialiased')
81
+ from matplotlib.backends import backend_agg
82
+ load_glyph_flags = {
83
+ "vector": LoadFlags.NO_HINTING,
84
+ "raster": backend_agg.get_hinting_flag(),
85
+ }[self._output_type]
86
+ return self._parse_cached(s, dpi, prop, antialiased, load_glyph_flags)
87
+
88
+ @functools.lru_cache(50)
89
+ def _parse_cached(self, s, dpi, prop, antialiased, load_glyph_flags):
90
+ if prop is None:
91
+ prop = FontProperties()
92
+ fontset_class = _api.check_getitem(
93
+ self._font_type_mapping, fontset=prop.get_math_fontfamily())
94
+ fontset = fontset_class(prop, load_glyph_flags)
95
+ fontsize = prop.get_size_in_points()
96
+
97
+ if self._parser is None: # Cache the parser globally.
98
+ self.__class__._parser = _mathtext.Parser()
99
+
100
+ box = self._parser.parse(s, fontset, fontsize, dpi)
101
+ output = _mathtext.ship(box)
102
+ if self._output_type == "vector":
103
+ return output.to_vector()
104
+ elif self._output_type == "raster":
105
+ return output.to_raster(antialiased=antialiased)
106
+
107
+
108
+ def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None,
109
+ *, color=None):
110
+ """
111
+ Given a math expression, renders it in a closely-clipped bounding
112
+ box to an image file.
113
+
114
+ Parameters
115
+ ----------
116
+ s : str
117
+ A math expression. The math portion must be enclosed in dollar signs.
118
+ filename_or_obj : str or path-like or file-like
119
+ Where to write the image data.
120
+ prop : `.FontProperties`, optional
121
+ The size and style of the text.
122
+ dpi : float, optional
123
+ The output dpi. If not set, the dpi is determined as for
124
+ `.Figure.savefig`.
125
+ format : str, optional
126
+ The output format, e.g., 'svg', 'pdf', 'ps' or 'png'. If not set, the
127
+ format is determined as for `.Figure.savefig`.
128
+ color : str, optional
129
+ Foreground color, defaults to :rc:`text.color`.
130
+ """
131
+ from matplotlib import figure
132
+
133
+ parser = MathTextParser('path')
134
+ width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
135
+
136
+ fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
137
+ fig.text(0, depth/height, s, fontproperties=prop, color=color)
138
+ fig.savefig(filename_or_obj, dpi=dpi, format=format)
139
+
140
+ return depth
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Generic, IO, Literal, TypeVar, overload
3
+
4
+ from matplotlib.font_manager import FontProperties
5
+ from matplotlib.typing import ColorType
6
+
7
+ # Re-exported API from _mathtext.
8
+ from ._mathtext import (
9
+ RasterParse as RasterParse,
10
+ VectorParse as VectorParse,
11
+ get_unicode_index as get_unicode_index,
12
+ )
13
+
14
+ _ParseType = TypeVar("_ParseType", RasterParse, VectorParse)
15
+
16
+ class MathTextParser(Generic[_ParseType]):
17
+ @overload
18
+ def __init__(self: MathTextParser[VectorParse], output: Literal["path"]) -> None: ...
19
+ @overload
20
+ def __init__(self: MathTextParser[RasterParse], output: Literal["agg", "raster", "macosx"]) -> None: ...
21
+ def parse(
22
+ self, s: str, dpi: float = ..., prop: FontProperties | None = ..., *, antialiased: bool | None = ...
23
+ ) -> _ParseType: ...
24
+
25
+ def math_to_image(
26
+ s: str,
27
+ filename_or_obj: str | os.PathLike | IO,
28
+ prop: FontProperties | None = ...,
29
+ dpi: float | None = ...,
30
+ format: str | None = ...,
31
+ *,
32
+ color: ColorType | None = ...
33
+ ) -> float: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py ADDED
@@ -0,0 +1,913 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Numerical Python functions written for compatibility with MATLAB
3
+ commands with the same names. Most numerical Python functions can be found in
4
+ the `NumPy`_ and `SciPy`_ libraries. What remains here is code for performing
5
+ spectral computations and kernel density estimations.
6
+
7
+ .. _NumPy: https://numpy.org
8
+ .. _SciPy: https://www.scipy.org
9
+
10
+ Spectral functions
11
+ ------------------
12
+
13
+ `cohere`
14
+ Coherence (normalized cross spectral density)
15
+
16
+ `csd`
17
+ Cross spectral density using Welch's average periodogram
18
+
19
+ `detrend`
20
+ Remove the mean or best fit line from an array
21
+
22
+ `psd`
23
+ Power spectral density using Welch's average periodogram
24
+
25
+ `specgram`
26
+ Spectrogram (spectrum over segments of time)
27
+
28
+ `complex_spectrum`
29
+ Return the complex-valued frequency spectrum of a signal
30
+
31
+ `magnitude_spectrum`
32
+ Return the magnitude of the frequency spectrum of a signal
33
+
34
+ `angle_spectrum`
35
+ Return the angle (wrapped phase) of the frequency spectrum of a signal
36
+
37
+ `phase_spectrum`
38
+ Return the phase (unwrapped angle) of the frequency spectrum of a signal
39
+
40
+ `detrend_mean`
41
+ Remove the mean from a line.
42
+
43
+ `detrend_linear`
44
+ Remove the best fit line from a line.
45
+
46
+ `detrend_none`
47
+ Return the original line.
48
+ """
49
+
50
+ import functools
51
+ from numbers import Number
52
+
53
+ import numpy as np
54
+
55
+ from matplotlib import _api, _docstring, cbook
56
+
57
+
58
+ def window_hanning(x):
59
+ """
60
+ Return *x* times the Hanning (or Hann) window of len(*x*).
61
+
62
+ See Also
63
+ --------
64
+ window_none : Another window algorithm.
65
+ """
66
+ return np.hanning(len(x))*x
67
+
68
+
69
+ def window_none(x):
70
+ """
71
+ No window function; simply return *x*.
72
+
73
+ See Also
74
+ --------
75
+ window_hanning : Another window algorithm.
76
+ """
77
+ return x
78
+
79
+
80
+ def detrend(x, key=None, axis=None):
81
+ """
82
+ Return *x* with its trend removed.
83
+
84
+ Parameters
85
+ ----------
86
+ x : array or sequence
87
+ Array or sequence containing the data.
88
+
89
+ key : {'default', 'constant', 'mean', 'linear', 'none'} or function
90
+ The detrending algorithm to use. 'default', 'mean', and 'constant' are
91
+ the same as `detrend_mean`. 'linear' is the same as `detrend_linear`.
92
+ 'none' is the same as `detrend_none`. The default is 'mean'. See the
93
+ corresponding functions for more details regarding the algorithms. Can
94
+ also be a function that carries out the detrend operation.
95
+
96
+ axis : int
97
+ The axis along which to do the detrending.
98
+
99
+ See Also
100
+ --------
101
+ detrend_mean : Implementation of the 'mean' algorithm.
102
+ detrend_linear : Implementation of the 'linear' algorithm.
103
+ detrend_none : Implementation of the 'none' algorithm.
104
+ """
105
+ if key is None or key in ['constant', 'mean', 'default']:
106
+ return detrend(x, key=detrend_mean, axis=axis)
107
+ elif key == 'linear':
108
+ return detrend(x, key=detrend_linear, axis=axis)
109
+ elif key == 'none':
110
+ return detrend(x, key=detrend_none, axis=axis)
111
+ elif callable(key):
112
+ x = np.asarray(x)
113
+ if axis is not None and axis + 1 > x.ndim:
114
+ raise ValueError(f'axis(={axis}) out of bounds')
115
+ if (axis is None and x.ndim == 0) or (not axis and x.ndim == 1):
116
+ return key(x)
117
+ # try to use the 'axis' argument if the function supports it,
118
+ # otherwise use apply_along_axis to do it
119
+ try:
120
+ return key(x, axis=axis)
121
+ except TypeError:
122
+ return np.apply_along_axis(key, axis=axis, arr=x)
123
+ else:
124
+ raise ValueError(
125
+ f"Unknown value for key: {key!r}, must be one of: 'default', "
126
+ f"'constant', 'mean', 'linear', or a function")
127
+
128
+
129
+ def detrend_mean(x, axis=None):
130
+ """
131
+ Return *x* minus the mean(*x*).
132
+
133
+ Parameters
134
+ ----------
135
+ x : array or sequence
136
+ Array or sequence containing the data
137
+ Can have any dimensionality
138
+
139
+ axis : int
140
+ The axis along which to take the mean. See `numpy.mean` for a
141
+ description of this argument.
142
+
143
+ See Also
144
+ --------
145
+ detrend_linear : Another detrend algorithm.
146
+ detrend_none : Another detrend algorithm.
147
+ detrend : A wrapper around all the detrend algorithms.
148
+ """
149
+ x = np.asarray(x)
150
+
151
+ if axis is not None and axis+1 > x.ndim:
152
+ raise ValueError('axis(=%s) out of bounds' % axis)
153
+
154
+ return x - x.mean(axis, keepdims=True)
155
+
156
+
157
+ def detrend_none(x, axis=None):
158
+ """
159
+ Return *x*: no detrending.
160
+
161
+ Parameters
162
+ ----------
163
+ x : any object
164
+ An object containing the data
165
+
166
+ axis : int
167
+ This parameter is ignored.
168
+ It is included for compatibility with detrend_mean
169
+
170
+ See Also
171
+ --------
172
+ detrend_mean : Another detrend algorithm.
173
+ detrend_linear : Another detrend algorithm.
174
+ detrend : A wrapper around all the detrend algorithms.
175
+ """
176
+ return x
177
+
178
+
179
+ def detrend_linear(y):
180
+ """
181
+ Return *x* minus best fit line; 'linear' detrending.
182
+
183
+ Parameters
184
+ ----------
185
+ y : 0-D or 1-D array or sequence
186
+ Array or sequence containing the data
187
+
188
+ See Also
189
+ --------
190
+ detrend_mean : Another detrend algorithm.
191
+ detrend_none : Another detrend algorithm.
192
+ detrend : A wrapper around all the detrend algorithms.
193
+ """
194
+ # This is faster than an algorithm based on linalg.lstsq.
195
+ y = np.asarray(y)
196
+
197
+ if y.ndim > 1:
198
+ raise ValueError('y cannot have ndim > 1')
199
+
200
+ # short-circuit 0-D array.
201
+ if not y.ndim:
202
+ return np.array(0., dtype=y.dtype)
203
+
204
+ x = np.arange(y.size, dtype=float)
205
+
206
+ C = np.cov(x, y, bias=1)
207
+ b = C[0, 1]/C[0, 0]
208
+
209
+ a = y.mean() - b*x.mean()
210
+ return y - (b*x + a)
211
+
212
+
213
+ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
214
+ window=None, noverlap=None, pad_to=None,
215
+ sides=None, scale_by_freq=None, mode=None):
216
+ """
217
+ Private helper implementing the common parts between the psd, csd,
218
+ spectrogram and complex, magnitude, angle, and phase spectrums.
219
+ """
220
+ if y is None:
221
+ # if y is None use x for y
222
+ same_data = True
223
+ else:
224
+ # The checks for if y is x are so that we can use the same function to
225
+ # implement the core of psd(), csd(), and spectrogram() without doing
226
+ # extra calculations. We return the unaveraged Pxy, freqs, and t.
227
+ same_data = y is x
228
+
229
+ if Fs is None:
230
+ Fs = 2
231
+ if noverlap is None:
232
+ noverlap = 0
233
+ if detrend_func is None:
234
+ detrend_func = detrend_none
235
+ if window is None:
236
+ window = window_hanning
237
+
238
+ # if NFFT is set to None use the whole signal
239
+ if NFFT is None:
240
+ NFFT = 256
241
+
242
+ if noverlap >= NFFT:
243
+ raise ValueError('noverlap must be less than NFFT')
244
+
245
+ if mode is None or mode == 'default':
246
+ mode = 'psd'
247
+ _api.check_in_list(
248
+ ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'],
249
+ mode=mode)
250
+
251
+ if not same_data and mode != 'psd':
252
+ raise ValueError("x and y must be equal if mode is not 'psd'")
253
+
254
+ # Make sure we're dealing with a numpy array. If y and x were the same
255
+ # object to start with, keep them that way
256
+ x = np.asarray(x)
257
+ if not same_data:
258
+ y = np.asarray(y)
259
+
260
+ if sides is None or sides == 'default':
261
+ if np.iscomplexobj(x):
262
+ sides = 'twosided'
263
+ else:
264
+ sides = 'onesided'
265
+ _api.check_in_list(['default', 'onesided', 'twosided'], sides=sides)
266
+
267
+ # zero pad x and y up to NFFT if they are shorter than NFFT
268
+ if len(x) < NFFT:
269
+ n = len(x)
270
+ x = np.resize(x, NFFT)
271
+ x[n:] = 0
272
+
273
+ if not same_data and len(y) < NFFT:
274
+ n = len(y)
275
+ y = np.resize(y, NFFT)
276
+ y[n:] = 0
277
+
278
+ if pad_to is None:
279
+ pad_to = NFFT
280
+
281
+ if mode != 'psd':
282
+ scale_by_freq = False
283
+ elif scale_by_freq is None:
284
+ scale_by_freq = True
285
+
286
+ # For real x, ignore the negative frequencies unless told otherwise
287
+ if sides == 'twosided':
288
+ numFreqs = pad_to
289
+ if pad_to % 2:
290
+ freqcenter = (pad_to - 1)//2 + 1
291
+ else:
292
+ freqcenter = pad_to//2
293
+ scaling_factor = 1.
294
+ elif sides == 'onesided':
295
+ if pad_to % 2:
296
+ numFreqs = (pad_to + 1)//2
297
+ else:
298
+ numFreqs = pad_to//2 + 1
299
+ scaling_factor = 2.
300
+
301
+ if not np.iterable(window):
302
+ window = window(np.ones(NFFT, x.dtype))
303
+ if len(window) != NFFT:
304
+ raise ValueError(
305
+ "The window length must match the data's first dimension")
306
+
307
+ result = np.lib.stride_tricks.sliding_window_view(
308
+ x, NFFT, axis=0)[::NFFT - noverlap].T
309
+ result = detrend(result, detrend_func, axis=0)
310
+ result = result * window.reshape((-1, 1))
311
+ result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :]
312
+ freqs = np.fft.fftfreq(pad_to, 1/Fs)[:numFreqs]
313
+
314
+ if not same_data:
315
+ # if same_data is False, mode must be 'psd'
316
+ resultY = np.lib.stride_tricks.sliding_window_view(
317
+ y, NFFT, axis=0)[::NFFT - noverlap].T
318
+ resultY = detrend(resultY, detrend_func, axis=0)
319
+ resultY = resultY * window.reshape((-1, 1))
320
+ resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :]
321
+ result = np.conj(result) * resultY
322
+ elif mode == 'psd':
323
+ result = np.conj(result) * result
324
+ elif mode == 'magnitude':
325
+ result = np.abs(result) / window.sum()
326
+ elif mode == 'angle' or mode == 'phase':
327
+ # we unwrap the phase later to handle the onesided vs. twosided case
328
+ result = np.angle(result)
329
+ elif mode == 'complex':
330
+ result /= window.sum()
331
+
332
+ if mode == 'psd':
333
+
334
+ # Also include scaling factors for one-sided densities and dividing by
335
+ # the sampling frequency, if desired. Scale everything, except the DC
336
+ # component and the NFFT/2 component:
337
+
338
+ # if we have a even number of frequencies, don't scale NFFT/2
339
+ if not NFFT % 2:
340
+ slc = slice(1, -1, None)
341
+ # if we have an odd number, just don't scale DC
342
+ else:
343
+ slc = slice(1, None, None)
344
+
345
+ result[slc] *= scaling_factor
346
+
347
+ # MATLAB divides by the sampling frequency so that density function
348
+ # has units of dB/Hz and can be integrated by the plotted frequency
349
+ # values. Perform the same scaling here.
350
+ if scale_by_freq:
351
+ result /= Fs
352
+ # Scale the spectrum by the norm of the window to compensate for
353
+ # windowing loss; see Bendat & Piersol Sec 11.5.2.
354
+ result /= (window**2).sum()
355
+ else:
356
+ # In this case, preserve power in the segment, not amplitude
357
+ result /= window.sum()**2
358
+
359
+ t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs
360
+
361
+ if sides == 'twosided':
362
+ # center the frequency range at zero
363
+ freqs = np.roll(freqs, -freqcenter, axis=0)
364
+ result = np.roll(result, -freqcenter, axis=0)
365
+ elif not pad_to % 2:
366
+ # get the last value correctly, it is negative otherwise
367
+ freqs[-1] *= -1
368
+
369
+ # we unwrap the phase here to handle the onesided vs. twosided case
370
+ if mode == 'phase':
371
+ result = np.unwrap(result, axis=0)
372
+
373
+ return result, freqs, t
374
+
375
+
376
+ def _single_spectrum_helper(
377
+ mode, x, Fs=None, window=None, pad_to=None, sides=None):
378
+ """
379
+ Private helper implementing the commonality between the complex, magnitude,
380
+ angle, and phase spectrums.
381
+ """
382
+ _api.check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode)
383
+
384
+ if pad_to is None:
385
+ pad_to = len(x)
386
+
387
+ spec, freqs, _ = _spectral_helper(x=x, y=None, NFFT=len(x), Fs=Fs,
388
+ detrend_func=detrend_none, window=window,
389
+ noverlap=0, pad_to=pad_to,
390
+ sides=sides,
391
+ scale_by_freq=False,
392
+ mode=mode)
393
+ if mode != 'complex':
394
+ spec = spec.real
395
+
396
+ if spec.ndim == 2 and spec.shape[1] == 1:
397
+ spec = spec[:, 0]
398
+
399
+ return spec, freqs
400
+
401
+
402
+ # Split out these keyword docs so that they can be used elsewhere
403
+ _docstring.interpd.register(
404
+ Spectral="""\
405
+ Fs : float, default: 2
406
+ The sampling frequency (samples per time unit). It is used to calculate
407
+ the Fourier frequencies, *freqs*, in cycles per time unit.
408
+
409
+ window : callable or ndarray, default: `.window_hanning`
410
+ A function or a vector of length *NFFT*. To create window vectors see
411
+ `.window_hanning`, `.window_none`, `numpy.blackman`, `numpy.hamming`,
412
+ `numpy.bartlett`, `scipy.signal`, `scipy.signal.get_window`, etc. If a
413
+ function is passed as the argument, it must take a data segment as an
414
+ argument and return the windowed version of the segment.
415
+
416
+ sides : {'default', 'onesided', 'twosided'}, optional
417
+ Which sides of the spectrum to return. 'default' is one-sided for real
418
+ data and two-sided for complex data. 'onesided' forces the return of a
419
+ one-sided spectrum, while 'twosided' forces two-sided.""",
420
+
421
+ Single_Spectrum="""\
422
+ pad_to : int, optional
423
+ The number of points to which the data segment is padded when performing
424
+ the FFT. While not increasing the actual resolution of the spectrum (the
425
+ minimum distance between resolvable peaks), this can give more points in
426
+ the plot, allowing for more detail. This corresponds to the *n* parameter
427
+ in the call to `~numpy.fft.fft`. The default is None, which sets *pad_to*
428
+ equal to the length of the input signal (i.e. no padding).""",
429
+
430
+ PSD="""\
431
+ pad_to : int, optional
432
+ The number of points to which the data segment is padded when performing
433
+ the FFT. This can be different from *NFFT*, which specifies the number
434
+ of data points used. While not increasing the actual resolution of the
435
+ spectrum (the minimum distance between resolvable peaks), this can give
436
+ more points in the plot, allowing for more detail. This corresponds to
437
+ the *n* parameter in the call to `~numpy.fft.fft`. The default is None,
438
+ which sets *pad_to* equal to *NFFT*
439
+
440
+ NFFT : int, default: 256
441
+ The number of data points used in each block for the FFT. A power 2 is
442
+ most efficient. This should *NOT* be used to get zero padding, or the
443
+ scaling of the result will be incorrect; use *pad_to* for this instead.
444
+
445
+ detrend : {'none', 'mean', 'linear'} or callable, default: 'none'
446
+ The function applied to each segment before fft-ing, designed to remove
447
+ the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter
448
+ is a vector, in Matplotlib it is a function. The :mod:`~matplotlib.mlab`
449
+ module defines `.detrend_none`, `.detrend_mean`, and `.detrend_linear`,
450
+ but you can use a custom function as well. You can also use a string to
451
+ choose one of the functions: 'none' calls `.detrend_none`. 'mean' calls
452
+ `.detrend_mean`. 'linear' calls `.detrend_linear`.
453
+
454
+ scale_by_freq : bool, default: True
455
+ Whether the resulting density values should be scaled by the scaling
456
+ frequency, which gives density in units of 1/Hz. This allows for
457
+ integration over the returned frequency values. The default is True for
458
+ MATLAB compatibility.""")
459
+
460
+
461
+ @_docstring.interpd
462
+ def psd(x, NFFT=None, Fs=None, detrend=None, window=None,
463
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
464
+ r"""
465
+ Compute the power spectral density.
466
+
467
+ The power spectral density :math:`P_{xx}` by Welch's average
468
+ periodogram method. The vector *x* is divided into *NFFT* length
469
+ segments. Each segment is detrended by function *detrend* and
470
+ windowed by function *window*. *noverlap* gives the length of
471
+ the overlap between segments. The :math:`|\mathrm{fft}(i)|^2`
472
+ of each segment :math:`i` are averaged to compute :math:`P_{xx}`.
473
+
474
+ If len(*x*) < *NFFT*, it will be zero padded to *NFFT*.
475
+
476
+ Parameters
477
+ ----------
478
+ x : 1-D array or sequence
479
+ Array or sequence containing the data
480
+
481
+ %(Spectral)s
482
+
483
+ %(PSD)s
484
+
485
+ noverlap : int, default: 0 (no overlap)
486
+ The number of points of overlap between segments.
487
+
488
+ Returns
489
+ -------
490
+ Pxx : 1-D array
491
+ The values for the power spectrum :math:`P_{xx}` (real valued)
492
+
493
+ freqs : 1-D array
494
+ The frequencies corresponding to the elements in *Pxx*
495
+
496
+ References
497
+ ----------
498
+ Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
499
+ Wiley & Sons (1986)
500
+
501
+ See Also
502
+ --------
503
+ specgram
504
+ `specgram` differs in the default overlap; in not returning the mean of
505
+ the segment periodograms; and in returning the times of the segments.
506
+
507
+ magnitude_spectrum : returns the magnitude spectrum.
508
+
509
+ csd : returns the spectral density between two signals.
510
+ """
511
+ Pxx, freqs = csd(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend=detrend,
512
+ window=window, noverlap=noverlap, pad_to=pad_to,
513
+ sides=sides, scale_by_freq=scale_by_freq)
514
+ return Pxx.real, freqs
515
+
516
+
517
+ @_docstring.interpd
518
+ def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None,
519
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
520
+ """
521
+ Compute the cross-spectral density.
522
+
523
+ The cross spectral density :math:`P_{xy}` by Welch's average
524
+ periodogram method. The vectors *x* and *y* are divided into
525
+ *NFFT* length segments. Each segment is detrended by function
526
+ *detrend* and windowed by function *window*. *noverlap* gives
527
+ the length of the overlap between segments. The product of
528
+ the direct FFTs of *x* and *y* are averaged over each segment
529
+ to compute :math:`P_{xy}`, with a scaling to correct for power
530
+ loss due to windowing.
531
+
532
+ If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero
533
+ padded to *NFFT*.
534
+
535
+ Parameters
536
+ ----------
537
+ x, y : 1-D arrays or sequences
538
+ Arrays or sequences containing the data
539
+
540
+ %(Spectral)s
541
+
542
+ %(PSD)s
543
+
544
+ noverlap : int, default: 0 (no overlap)
545
+ The number of points of overlap between segments.
546
+
547
+ Returns
548
+ -------
549
+ Pxy : 1-D array
550
+ The values for the cross spectrum :math:`P_{xy}` before scaling (real
551
+ valued)
552
+
553
+ freqs : 1-D array
554
+ The frequencies corresponding to the elements in *Pxy*
555
+
556
+ References
557
+ ----------
558
+ Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
559
+ Wiley & Sons (1986)
560
+
561
+ See Also
562
+ --------
563
+ psd : equivalent to setting ``y = x``.
564
+ """
565
+ if NFFT is None:
566
+ NFFT = 256
567
+ Pxy, freqs, _ = _spectral_helper(x=x, y=y, NFFT=NFFT, Fs=Fs,
568
+ detrend_func=detrend, window=window,
569
+ noverlap=noverlap, pad_to=pad_to,
570
+ sides=sides, scale_by_freq=scale_by_freq,
571
+ mode='psd')
572
+
573
+ if Pxy.ndim == 2:
574
+ if Pxy.shape[1] > 1:
575
+ Pxy = Pxy.mean(axis=1)
576
+ else:
577
+ Pxy = Pxy[:, 0]
578
+ return Pxy, freqs
579
+
580
+
581
+ _single_spectrum_docs = """\
582
+ Compute the {quantity} of *x*.
583
+ Data is padded to a length of *pad_to* and the windowing function *window* is
584
+ applied to the signal.
585
+
586
+ Parameters
587
+ ----------
588
+ x : 1-D array or sequence
589
+ Array or sequence containing the data
590
+
591
+ {Spectral}
592
+
593
+ {Single_Spectrum}
594
+
595
+ Returns
596
+ -------
597
+ spectrum : 1-D array
598
+ The {quantity}.
599
+ freqs : 1-D array
600
+ The frequencies corresponding to the elements in *spectrum*.
601
+
602
+ See Also
603
+ --------
604
+ psd
605
+ Returns the power spectral density.
606
+ complex_spectrum
607
+ Returns the complex-valued frequency spectrum.
608
+ magnitude_spectrum
609
+ Returns the absolute value of the `complex_spectrum`.
610
+ angle_spectrum
611
+ Returns the angle of the `complex_spectrum`.
612
+ phase_spectrum
613
+ Returns the phase (unwrapped angle) of the `complex_spectrum`.
614
+ specgram
615
+ Can return the complex spectrum of segments within the signal.
616
+ """
617
+
618
+
619
+ complex_spectrum = functools.partial(_single_spectrum_helper, "complex")
620
+ complex_spectrum.__doc__ = _single_spectrum_docs.format(
621
+ quantity="complex-valued frequency spectrum",
622
+ **_docstring.interpd.params)
623
+ magnitude_spectrum = functools.partial(_single_spectrum_helper, "magnitude")
624
+ magnitude_spectrum.__doc__ = _single_spectrum_docs.format(
625
+ quantity="magnitude (absolute value) of the frequency spectrum",
626
+ **_docstring.interpd.params)
627
+ angle_spectrum = functools.partial(_single_spectrum_helper, "angle")
628
+ angle_spectrum.__doc__ = _single_spectrum_docs.format(
629
+ quantity="angle of the frequency spectrum (wrapped phase spectrum)",
630
+ **_docstring.interpd.params)
631
+ phase_spectrum = functools.partial(_single_spectrum_helper, "phase")
632
+ phase_spectrum.__doc__ = _single_spectrum_docs.format(
633
+ quantity="phase of the frequency spectrum (unwrapped phase spectrum)",
634
+ **_docstring.interpd.params)
635
+
636
+
637
+ @_docstring.interpd
638
+ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None,
639
+ noverlap=None, pad_to=None, sides=None, scale_by_freq=None,
640
+ mode=None):
641
+ """
642
+ Compute a spectrogram.
643
+
644
+ Compute and plot a spectrogram of data in *x*. Data are split into
645
+ *NFFT* length segments and the spectrum of each section is
646
+ computed. The windowing function *window* is applied to each
647
+ segment, and the amount of overlap of each segment is
648
+ specified with *noverlap*.
649
+
650
+ Parameters
651
+ ----------
652
+ x : array-like
653
+ 1-D array or sequence.
654
+
655
+ %(Spectral)s
656
+
657
+ %(PSD)s
658
+
659
+ noverlap : int, default: 128
660
+ The number of points of overlap between blocks.
661
+ mode : str, default: 'psd'
662
+ What sort of spectrum to use:
663
+ 'psd'
664
+ Returns the power spectral density.
665
+ 'complex'
666
+ Returns the complex-valued frequency spectrum.
667
+ 'magnitude'
668
+ Returns the magnitude spectrum.
669
+ 'angle'
670
+ Returns the phase spectrum without unwrapping.
671
+ 'phase'
672
+ Returns the phase spectrum with unwrapping.
673
+
674
+ Returns
675
+ -------
676
+ spectrum : array-like
677
+ 2D array, columns are the periodograms of successive segments.
678
+
679
+ freqs : array-like
680
+ 1-D array, frequencies corresponding to the rows in *spectrum*.
681
+
682
+ t : array-like
683
+ 1-D array, the times corresponding to midpoints of segments
684
+ (i.e the columns in *spectrum*).
685
+
686
+ See Also
687
+ --------
688
+ psd : differs in the overlap and in the return values.
689
+ complex_spectrum : similar, but with complex valued frequencies.
690
+ magnitude_spectrum : similar single segment when *mode* is 'magnitude'.
691
+ angle_spectrum : similar to single segment when *mode* is 'angle'.
692
+ phase_spectrum : similar to single segment when *mode* is 'phase'.
693
+
694
+ Notes
695
+ -----
696
+ *detrend* and *scale_by_freq* only apply when *mode* is set to 'psd'.
697
+
698
+ """
699
+ if noverlap is None:
700
+ noverlap = 128 # default in _spectral_helper() is noverlap = 0
701
+ if NFFT is None:
702
+ NFFT = 256 # same default as in _spectral_helper()
703
+ if len(x) <= NFFT:
704
+ _api.warn_external("Only one segment is calculated since parameter "
705
+ f"NFFT (={NFFT}) >= signal length (={len(x)}).")
706
+
707
+ spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs,
708
+ detrend_func=detrend, window=window,
709
+ noverlap=noverlap, pad_to=pad_to,
710
+ sides=sides,
711
+ scale_by_freq=scale_by_freq,
712
+ mode=mode)
713
+
714
+ if mode != 'complex':
715
+ spec = spec.real # Needed since helper implements generically
716
+
717
+ return spec, freqs, t
718
+
719
+
720
+ @_docstring.interpd
721
+ def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
722
+ noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
723
+ r"""
724
+ The coherence between *x* and *y*. Coherence is the normalized
725
+ cross spectral density:
726
+
727
+ .. math::
728
+
729
+ C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}}
730
+
731
+ Parameters
732
+ ----------
733
+ x, y
734
+ Array or sequence containing the data
735
+
736
+ %(Spectral)s
737
+
738
+ %(PSD)s
739
+
740
+ noverlap : int, default: 0 (no overlap)
741
+ The number of points of overlap between segments.
742
+
743
+ Returns
744
+ -------
745
+ Cxy : 1-D array
746
+ The coherence vector.
747
+ freqs : 1-D array
748
+ The frequencies for the elements in *Cxy*.
749
+
750
+ See Also
751
+ --------
752
+ :func:`psd`, :func:`csd` :
753
+ For information about the methods used to compute :math:`P_{xy}`,
754
+ :math:`P_{xx}` and :math:`P_{yy}`.
755
+ """
756
+ if len(x) < 2 * NFFT:
757
+ raise ValueError(
758
+ "Coherence is calculated by averaging over *NFFT* length "
759
+ "segments. Your signal is too short for your choice of *NFFT*.")
760
+ Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
761
+ scale_by_freq)
762
+ Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
763
+ scale_by_freq)
764
+ Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
765
+ scale_by_freq)
766
+ Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy)
767
+ return Cxy, f
768
+
769
+
770
+ class GaussianKDE:
771
+ """
772
+ Representation of a kernel-density estimate using Gaussian kernels.
773
+
774
+ Parameters
775
+ ----------
776
+ dataset : array-like
777
+ Datapoints to estimate from. In case of univariate data this is a 1-D
778
+ array, otherwise a 2D array with shape (# of dims, # of data).
779
+ bw_method : {'scott', 'silverman'} or float or callable, optional
780
+ The method used to calculate the estimator bandwidth. If a
781
+ float, this will be used directly as `kde.factor`. If a
782
+ callable, it should take a `GaussianKDE` instance as only
783
+ parameter and return a float. If None (default), 'scott' is used.
784
+
785
+ Attributes
786
+ ----------
787
+ dataset : ndarray
788
+ The dataset passed to the constructor.
789
+ dim : int
790
+ Number of dimensions.
791
+ num_dp : int
792
+ Number of datapoints.
793
+ factor : float
794
+ The bandwidth factor, obtained from `kde.covariance_factor`, with which
795
+ the covariance matrix is multiplied.
796
+ covariance : ndarray
797
+ The covariance matrix of *dataset*, scaled by the calculated bandwidth
798
+ (`kde.factor`).
799
+ inv_cov : ndarray
800
+ The inverse of *covariance*.
801
+
802
+ Methods
803
+ -------
804
+ kde.evaluate(points) : ndarray
805
+ Evaluate the estimated pdf on a provided set of points.
806
+ kde(points) : ndarray
807
+ Same as kde.evaluate(points)
808
+ """
809
+
810
+ # This implementation with minor modification was too good to pass up.
811
+ # from scipy: https://github.com/scipy/scipy/blob/master/scipy/stats/kde.py
812
+
813
+ def __init__(self, dataset, bw_method=None):
814
+ self.dataset = np.atleast_2d(dataset)
815
+ if not np.array(self.dataset).size > 1:
816
+ raise ValueError("`dataset` input should have multiple elements.")
817
+
818
+ self.dim, self.num_dp = np.array(self.dataset).shape
819
+
820
+ if bw_method is None:
821
+ pass
822
+ elif cbook._str_equal(bw_method, 'scott'):
823
+ self.covariance_factor = self.scotts_factor
824
+ elif cbook._str_equal(bw_method, 'silverman'):
825
+ self.covariance_factor = self.silverman_factor
826
+ elif isinstance(bw_method, Number):
827
+ self._bw_method = 'use constant'
828
+ self.covariance_factor = lambda: bw_method
829
+ elif callable(bw_method):
830
+ self._bw_method = bw_method
831
+ self.covariance_factor = lambda: self._bw_method(self)
832
+ else:
833
+ raise ValueError("`bw_method` should be 'scott', 'silverman', a "
834
+ "scalar or a callable")
835
+
836
+ # Computes the covariance matrix for each Gaussian kernel using
837
+ # covariance_factor().
838
+
839
+ self.factor = self.covariance_factor()
840
+ # Cache covariance and inverse covariance of the data
841
+ if not hasattr(self, '_data_inv_cov'):
842
+ self.data_covariance = np.atleast_2d(
843
+ np.cov(
844
+ self.dataset,
845
+ rowvar=1,
846
+ bias=False))
847
+ self.data_inv_cov = np.linalg.inv(self.data_covariance)
848
+
849
+ self.covariance = self.data_covariance * self.factor ** 2
850
+ self.inv_cov = self.data_inv_cov / self.factor ** 2
851
+ self.norm_factor = (np.sqrt(np.linalg.det(2 * np.pi * self.covariance))
852
+ * self.num_dp)
853
+
854
+ def scotts_factor(self):
855
+ return np.power(self.num_dp, -1. / (self.dim + 4))
856
+
857
+ def silverman_factor(self):
858
+ return np.power(
859
+ self.num_dp * (self.dim + 2.0) / 4.0, -1. / (self.dim + 4))
860
+
861
+ # Default method to calculate bandwidth, can be overwritten by subclass
862
+ covariance_factor = scotts_factor
863
+
864
+ def evaluate(self, points):
865
+ """
866
+ Evaluate the estimated pdf on a set of points.
867
+
868
+ Parameters
869
+ ----------
870
+ points : (# of dimensions, # of points)-array
871
+ Alternatively, a (# of dimensions,) vector can be passed in and
872
+ treated as a single point.
873
+
874
+ Returns
875
+ -------
876
+ (# of points,)-array
877
+ The values at each point.
878
+
879
+ Raises
880
+ ------
881
+ ValueError : if the dimensionality of the input points is different
882
+ than the dimensionality of the KDE.
883
+
884
+ """
885
+ points = np.atleast_2d(points)
886
+
887
+ dim, num_m = np.array(points).shape
888
+ if dim != self.dim:
889
+ raise ValueError(f"points have dimension {dim}, dataset has "
890
+ f"dimension {self.dim}")
891
+
892
+ result = np.zeros(num_m)
893
+
894
+ if num_m >= self.num_dp:
895
+ # there are more points than data, so loop over data
896
+ for i in range(self.num_dp):
897
+ diff = self.dataset[:, i, np.newaxis] - points
898
+ tdiff = np.dot(self.inv_cov, diff)
899
+ energy = np.sum(diff * tdiff, axis=0) / 2.0
900
+ result = result + np.exp(-energy)
901
+ else:
902
+ # loop over points
903
+ for i in range(num_m):
904
+ diff = self.dataset - points[:, i, np.newaxis]
905
+ tdiff = np.dot(self.inv_cov, diff)
906
+ energy = np.sum(diff * tdiff, axis=0) / 2.0
907
+ result[i] = np.sum(np.exp(-energy), axis=0)
908
+
909
+ result = result / self.norm_factor
910
+
911
+ return result
912
+
913
+ __call__ = evaluate
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable
2
+ import functools
3
+ from typing import Literal
4
+
5
+ import numpy as np
6
+ from numpy.typing import ArrayLike
7
+
8
+ def window_hanning(x: ArrayLike) -> ArrayLike: ...
9
+ def window_none(x: ArrayLike) -> ArrayLike: ...
10
+ def detrend(
11
+ x: ArrayLike,
12
+ key: Literal["default", "constant", "mean", "linear", "none"]
13
+ | Callable[[ArrayLike, int | None], ArrayLike]
14
+ | None = ...,
15
+ axis: int | None = ...,
16
+ ) -> ArrayLike: ...
17
+ def detrend_mean(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ...
18
+ def detrend_none(x: ArrayLike, axis: int | None = ...) -> ArrayLike: ...
19
+ def detrend_linear(y: ArrayLike) -> ArrayLike: ...
20
+ def psd(
21
+ x: ArrayLike,
22
+ NFFT: int | None = ...,
23
+ Fs: float | None = ...,
24
+ detrend: Literal["none", "mean", "linear"]
25
+ | Callable[[ArrayLike, int | None], ArrayLike]
26
+ | None = ...,
27
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
28
+ noverlap: int | None = ...,
29
+ pad_to: int | None = ...,
30
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
31
+ scale_by_freq: bool | None = ...,
32
+ ) -> tuple[ArrayLike, ArrayLike]: ...
33
+ def csd(
34
+ x: ArrayLike,
35
+ y: ArrayLike | None,
36
+ NFFT: int | None = ...,
37
+ Fs: float | None = ...,
38
+ detrend: Literal["none", "mean", "linear"]
39
+ | Callable[[ArrayLike, int | None], ArrayLike]
40
+ | None = ...,
41
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
42
+ noverlap: int | None = ...,
43
+ pad_to: int | None = ...,
44
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
45
+ scale_by_freq: bool | None = ...,
46
+ ) -> tuple[ArrayLike, ArrayLike]: ...
47
+
48
+ complex_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
49
+ magnitude_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
50
+ angle_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
51
+ phase_spectrum = functools.partial(tuple[ArrayLike, ArrayLike])
52
+
53
+ def specgram(
54
+ x: ArrayLike,
55
+ NFFT: int | None = ...,
56
+ Fs: float | None = ...,
57
+ detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] | None = ...,
58
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = ...,
59
+ noverlap: int | None = ...,
60
+ pad_to: int | None = ...,
61
+ sides: Literal["default", "onesided", "twosided"] | None = ...,
62
+ scale_by_freq: bool | None = ...,
63
+ mode: Literal["psd", "complex", "magnitude", "angle", "phase"] | None = ...,
64
+ ) -> tuple[ArrayLike, ArrayLike, ArrayLike]: ...
65
+ def cohere(
66
+ x: ArrayLike,
67
+ y: ArrayLike,
68
+ NFFT: int = ...,
69
+ Fs: float = ...,
70
+ detrend: Literal["none", "mean", "linear"] | Callable[[ArrayLike, int | None], ArrayLike] = ...,
71
+ window: Callable[[ArrayLike], ArrayLike] | ArrayLike = ...,
72
+ noverlap: int = ...,
73
+ pad_to: int | None = ...,
74
+ sides: Literal["default", "onesided", "twosided"] = ...,
75
+ scale_by_freq: bool | None = ...,
76
+ ) -> tuple[ArrayLike, ArrayLike]: ...
77
+
78
+ class GaussianKDE:
79
+ dataset: ArrayLike
80
+ dim: int
81
+ num_dp: int
82
+ factor: float
83
+ data_covariance: ArrayLike
84
+ data_inv_cov: ArrayLike
85
+ covariance: ArrayLike
86
+ inv_cov: ArrayLike
87
+ norm_factor: float
88
+ def __init__(
89
+ self,
90
+ dataset: ArrayLike,
91
+ bw_method: Literal["scott", "silverman"]
92
+ | float
93
+ | Callable[[GaussianKDE], float]
94
+ | None = ...,
95
+ ) -> None: ...
96
+ def scotts_factor(self) -> float: ...
97
+ def silverman_factor(self) -> float: ...
98
+ def covariance_factor(self) -> float: ...
99
+ def evaluate(self, points: ArrayLike) -> np.ndarray: ...
100
+ def __call__(self, points: ArrayLike) -> np.ndarray: ...
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Creation Date: Thu Jun 21 22:23:20 1990
3
+ Comment UniqueID 5000774
4
+ FontName CMEX10
5
+ EncodingScheme FontSpecific
6
+ FullName CMEX10
7
+ FamilyName Computer Modern
8
+ Weight Medium
9
+ ItalicAngle 0
10
+ IsFixedPitch false
11
+ Version 1.00
12
+ Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
13
+ Comment Computer Modern fonts were designed by Donald E. Knuth
14
+ FontBBox -24 -2960 1454 772
15
+ XHeight 430.556
16
+ Comment CapHeight 0
17
+ Ascender 750
18
+ Comment Descender -1760
19
+ Descender -2960
20
+ Comment FontID CMEX
21
+ Comment DesignSize 10 (pts)
22
+ Comment CharacterCodingScheme TeX math extension
23
+ Comment Space 0 0 0
24
+ Comment ExtraSpace 0
25
+ Comment Quad 1000
26
+ Comment DefaultRuleThickness 40
27
+ Comment BigOpSpacing 111.111 166.667 200 600 100
28
+ Comment Ascendible characters (74) % macro - PS charname
29
+ Comment Ascending 0, 16, 18, 32, 48 % ( - parenleft
30
+ Comment Ascending 1, 17, 19, 33, 49 % ) - parenright
31
+ Comment Ascending 2, 104, 20, 34, 50 % [ - bracketleft
32
+ Comment Ascending 3, 105, 21, 35, 51 % ] - bracketright
33
+ Comment Ascending 4, 106, 22, 36, 52 % lfloor - floorleft
34
+ Comment Ascending 5, 107, 23, 37, 53 % rfloor - floorright
35
+ Comment Ascending 6, 108, 24, 38, 54 % lceil - ceilingleft
36
+ Comment Ascending 7, 109, 25, 39, 55 % rceil - ceilingright
37
+ Comment Ascending 8, 110, 26, 40, 56 % { - braceleft
38
+ Comment Ascending 9, 111, 27, 41, 57 % } - braceright
39
+ Comment Ascending 10, 68, 28, 42 % < - anglebracketleft
40
+ Comment Ascending 11, 69, 29, 43 % > - anglebracketright
41
+ Comment Ascending 14, 46, 30, 44 % / - slash
42
+ Comment Ascending 15, 47, 31, 45 % \ - backslash
43
+ Comment Ascending 70, 71 % bigsqcup - unionsq
44
+ Comment Ascending 72, 73 % oint - contintegral
45
+ Comment Ascending 74, 75 % bigodot - circledot
46
+ Comment Ascending 76, 77 % bigoplus - circleplus
47
+ Comment Ascending 78, 79 % bigotimes - circlemultiply
48
+ Comment Ascending 80, 88 % sum - summation
49
+ Comment Ascending 81, 89 % prod - product
50
+ Comment Ascending 82, 90 % int - integral
51
+ Comment Ascending 83, 91 % bigcup - union
52
+ Comment Ascending 84, 92 % bigcap - intersection
53
+ Comment Ascending 85, 93 % biguplus - unionmulti
54
+ Comment Ascending 86, 94 % bigwedge - logicaland
55
+ Comment Ascending 87, 95 % bigvee - logicalor
56
+ Comment Ascending 96, 97 % coprod - coproduct
57
+ Comment Ascending 98, 99, 100 % widehat - hatwide
58
+ Comment Ascending 101, 102, 103 % widetilde - tildewide
59
+ Comment Ascending 112, 113, 114, 115, 116 % radical - sqrt
60
+ Comment Extensible characters (28)
61
+ Comment Extensible 12 top 0 mid 0 bot 0 rep 12 % vert - thin bar
62
+ Comment Extensible 13 top 0 mid 0 bot 0 rep 13 % Vert - thin double bar
63
+ Comment Extensible 48 top 48 mid 0 bot 64 rep 66 % ( - parenleft
64
+ Comment Extensible 49 top 49 mid 0 bot 65 rep 67 % ) - parenright
65
+ Comment Extensible 50 top 50 mid 0 bot 52 rep 54 % [ - bracketleft
66
+ Comment Extensible 51 top 51 mid 0 bot 53 rep 55 % ] - bracketright
67
+ Comment Extensible 52 top 0 mid 0 bot 52 rep 54 % lfloor - floorleft
68
+ Comment Extensible 53 top 0 mid 0 bot 53 rep 55 % rfloor - floorright
69
+ Comment Extensible 54 top 50 mid 0 bot 0 rep 54 % lceil - ceilingleft
70
+ Comment Extensible 55 top 51 mid 0 bot 0 rep 55 % rceil - ceilingright
71
+ Comment Extensible 56 top 56 mid 60 bot 58 rep 62 % { - braceleft
72
+ Comment Extensible 57 top 57 mid 61 bot 59 rep 62 % } - braceright
73
+ Comment Extensible 58 top 56 mid 0 bot 58 rep 62 % lgroup
74
+ Comment Extensible 59 top 57 mid 0 bot 59 rep 62 % rgroup
75
+ Comment Extensible 60 top 0 mid 0 bot 0 rep 63 % arrowvert
76
+ Comment Extensible 61 top 0 mid 0 bot 0 rep 119 % Arrowvert
77
+ Comment Extensible 62 top 0 mid 0 bot 0 rep 62 % bracevert
78
+ Comment Extensible 63 top 120 mid 0 bot 121 rep 63 % updownarrow
79
+ Comment Extensible 64 top 56 mid 0 bot 59 rep 62 % lmoustache
80
+ Comment Extensible 65 top 57 mid 0 bot 58 rep 62 % rmoustache
81
+ Comment Extensible 66 top 0 mid 0 bot 0 rep 66 % parenleftexten
82
+ Comment Extensible 67 top 0 mid 0 bot 0 rep 67 % parenrightexten
83
+ Comment Extensible 116 top 118 mid 0 bot 116 rep 117 % radical
84
+ Comment Extensible 119 top 126 mid 0 bot 127 rep 119 % Updownarrow
85
+ Comment Extensible 120 top 120 mid 0 bot 0 rep 63 % uparrow
86
+ Comment Extensible 121 top 0 mid 0 bot 121 rep 63 % downarrow
87
+ Comment Extensible 126 top 126 mid 0 bot 0 rep 119 % Uparrow
88
+ Comment Extensible 127 top 0 mid 0 bot 127 rep 119 % Downarrow
89
+ StartCharMetrics 129
90
+ C 0 ; WX 458.333 ; N parenleftbig ; B 152 -1159 413 40 ;
91
+ C 1 ; WX 458.333 ; N parenrightbig ; B 44 -1159 305 40 ;
92
+ C 2 ; WX 416.667 ; N bracketleftbig ; B 202 -1159 394 40 ;
93
+ C 3 ; WX 416.667 ; N bracketrightbig ; B 22 -1159 214 40 ;
94
+ C 4 ; WX 472.222 ; N floorleftbig ; B 202 -1159 449 40 ;
95
+ C 5 ; WX 472.222 ; N floorrightbig ; B 22 -1159 269 40 ;
96
+ C 6 ; WX 472.222 ; N ceilingleftbig ; B 202 -1159 449 40 ;
97
+ C 7 ; WX 472.222 ; N ceilingrightbig ; B 22 -1159 269 40 ;
98
+ C 8 ; WX 583.333 ; N braceleftbig ; B 113 -1159 469 40 ;
99
+ C 9 ; WX 583.333 ; N bracerightbig ; B 113 -1159 469 40 ;
100
+ C 10 ; WX 472.222 ; N angbracketleftbig ; B 98 -1160 393 40 ;
101
+ C 11 ; WX 472.222 ; N angbracketrightbig ; B 78 -1160 373 40 ;
102
+ C 12 ; WX 333.333 ; N vextendsingle ; B 145 -621 188 21 ;
103
+ C 13 ; WX 555.556 ; N vextenddouble ; B 145 -621 410 21 ;
104
+ C 14 ; WX 577.778 ; N slashbig ; B 56 -1159 521 40 ;
105
+ C 15 ; WX 577.778 ; N backslashbig ; B 56 -1159 521 40 ;
106
+ C 16 ; WX 597.222 ; N parenleftBig ; B 180 -1759 560 40 ;
107
+ C 17 ; WX 597.222 ; N parenrightBig ; B 36 -1759 416 40 ;
108
+ C 18 ; WX 736.111 ; N parenleftbigg ; B 208 -2359 700 40 ;
109
+ C 19 ; WX 736.111 ; N parenrightbigg ; B 35 -2359 527 40 ;
110
+ C 20 ; WX 527.778 ; N bracketleftbigg ; B 250 -2359 513 40 ;
111
+ C 21 ; WX 527.778 ; N bracketrightbigg ; B 14 -2359 277 40 ;
112
+ C 22 ; WX 583.333 ; N floorleftbigg ; B 250 -2359 568 40 ;
113
+ C 23 ; WX 583.333 ; N floorrightbigg ; B 14 -2359 332 40 ;
114
+ C 24 ; WX 583.333 ; N ceilingleftbigg ; B 250 -2359 568 40 ;
115
+ C 25 ; WX 583.333 ; N ceilingrightbigg ; B 14 -2359 332 40 ;
116
+ C 26 ; WX 750 ; N braceleftbigg ; B 131 -2359 618 40 ;
117
+ C 27 ; WX 750 ; N bracerightbigg ; B 131 -2359 618 40 ;
118
+ C 28 ; WX 750 ; N angbracketleftbigg ; B 125 -2359 652 40 ;
119
+ C 29 ; WX 750 ; N angbracketrightbigg ; B 97 -2359 624 40 ;
120
+ C 30 ; WX 1044.44 ; N slashbigg ; B 56 -2359 987 40 ;
121
+ C 31 ; WX 1044.44 ; N backslashbigg ; B 56 -2359 987 40 ;
122
+ C 32 ; WX 791.667 ; N parenleftBigg ; B 236 -2959 757 40 ;
123
+ C 33 ; WX 791.667 ; N parenrightBigg ; B 34 -2959 555 40 ;
124
+ C 34 ; WX 583.333 ; N bracketleftBigg ; B 275 -2959 571 40 ;
125
+ C 35 ; WX 583.333 ; N bracketrightBigg ; B 11 -2959 307 40 ;
126
+ C 36 ; WX 638.889 ; N floorleftBigg ; B 275 -2959 627 40 ;
127
+ C 37 ; WX 638.889 ; N floorrightBigg ; B 11 -2959 363 40 ;
128
+ C 38 ; WX 638.889 ; N ceilingleftBigg ; B 275 -2959 627 40 ;
129
+ C 39 ; WX 638.889 ; N ceilingrightBigg ; B 11 -2959 363 40 ;
130
+ C 40 ; WX 805.556 ; N braceleftBigg ; B 144 -2959 661 40 ;
131
+ C 41 ; WX 805.556 ; N bracerightBigg ; B 144 -2959 661 40 ;
132
+ C 42 ; WX 805.556 ; N angbracketleftBigg ; B 139 -2960 697 40 ;
133
+ C 43 ; WX 805.556 ; N angbracketrightBigg ; B 108 -2960 666 40 ;
134
+ C 44 ; WX 1277.78 ; N slashBigg ; B 56 -2959 1221 40 ;
135
+ C 45 ; WX 1277.78 ; N backslashBigg ; B 56 -2959 1221 40 ;
136
+ C 46 ; WX 811.111 ; N slashBig ; B 56 -1759 754 40 ;
137
+ C 47 ; WX 811.111 ; N backslashBig ; B 56 -1759 754 40 ;
138
+ C 48 ; WX 875 ; N parenlefttp ; B 291 -1770 842 39 ;
139
+ C 49 ; WX 875 ; N parenrighttp ; B 32 -1770 583 39 ;
140
+ C 50 ; WX 666.667 ; N bracketlefttp ; B 326 -1760 659 39 ;
141
+ C 51 ; WX 666.667 ; N bracketrighttp ; B 7 -1760 340 39 ;
142
+ C 52 ; WX 666.667 ; N bracketleftbt ; B 326 -1759 659 40 ;
143
+ C 53 ; WX 666.667 ; N bracketrightbt ; B 7 -1759 340 40 ;
144
+ C 54 ; WX 666.667 ; N bracketleftex ; B 326 -601 395 1 ;
145
+ C 55 ; WX 666.667 ; N bracketrightex ; B 271 -601 340 1 ;
146
+ C 56 ; WX 888.889 ; N bracelefttp ; B 384 -910 718 -1 ;
147
+ C 57 ; WX 888.889 ; N bracerighttp ; B 170 -910 504 -1 ;
148
+ C 58 ; WX 888.889 ; N braceleftbt ; B 384 -899 718 10 ;
149
+ C 59 ; WX 888.889 ; N bracerightbt ; B 170 -899 504 10 ;
150
+ C 60 ; WX 888.889 ; N braceleftmid ; B 170 -1810 504 10 ;
151
+ C 61 ; WX 888.889 ; N bracerightmid ; B 384 -1810 718 10 ;
152
+ C 62 ; WX 888.889 ; N braceex ; B 384 -310 504 10 ;
153
+ C 63 ; WX 666.667 ; N arrowvertex ; B 312 -601 355 1 ;
154
+ C 64 ; WX 875 ; N parenleftbt ; B 291 -1759 842 50 ;
155
+ C 65 ; WX 875 ; N parenrightbt ; B 32 -1759 583 50 ;
156
+ C 66 ; WX 875 ; N parenleftex ; B 291 -610 402 10 ;
157
+ C 67 ; WX 875 ; N parenrightex ; B 472 -610 583 10 ;
158
+ C 68 ; WX 611.111 ; N angbracketleftBig ; B 112 -1759 522 40 ;
159
+ C 69 ; WX 611.111 ; N angbracketrightBig ; B 88 -1759 498 40 ;
160
+ C 70 ; WX 833.333 ; N unionsqtext ; B 56 -1000 776 0 ;
161
+ C 71 ; WX 1111.11 ; N unionsqdisplay ; B 56 -1400 1054 0 ;
162
+ C 72 ; WX 472.222 ; N contintegraltext ; B 56 -1111 609 0 ;
163
+ C 73 ; WX 555.556 ; N contintegraldisplay ; B 56 -2222 943 0 ;
164
+ C 74 ; WX 1111.11 ; N circledottext ; B 56 -1000 1054 0 ;
165
+ C 75 ; WX 1511.11 ; N circledotdisplay ; B 56 -1400 1454 0 ;
166
+ C 76 ; WX 1111.11 ; N circleplustext ; B 56 -1000 1054 0 ;
167
+ C 77 ; WX 1511.11 ; N circleplusdisplay ; B 56 -1400 1454 0 ;
168
+ C 78 ; WX 1111.11 ; N circlemultiplytext ; B 56 -1000 1054 0 ;
169
+ C 79 ; WX 1511.11 ; N circlemultiplydisplay ; B 56 -1400 1454 0 ;
170
+ C 80 ; WX 1055.56 ; N summationtext ; B 56 -1000 999 0 ;
171
+ C 81 ; WX 944.444 ; N producttext ; B 56 -1000 887 0 ;
172
+ C 82 ; WX 472.222 ; N integraltext ; B 56 -1111 609 0 ;
173
+ C 83 ; WX 833.333 ; N uniontext ; B 56 -1000 776 0 ;
174
+ C 84 ; WX 833.333 ; N intersectiontext ; B 56 -1000 776 0 ;
175
+ C 85 ; WX 833.333 ; N unionmultitext ; B 56 -1000 776 0 ;
176
+ C 86 ; WX 833.333 ; N logicalandtext ; B 56 -1000 776 0 ;
177
+ C 87 ; WX 833.333 ; N logicalortext ; B 56 -1000 776 0 ;
178
+ C 88 ; WX 1444.44 ; N summationdisplay ; B 56 -1400 1387 0 ;
179
+ C 89 ; WX 1277.78 ; N productdisplay ; B 56 -1400 1221 0 ;
180
+ C 90 ; WX 555.556 ; N integraldisplay ; B 56 -2222 943 0 ;
181
+ C 91 ; WX 1111.11 ; N uniondisplay ; B 56 -1400 1054 0 ;
182
+ C 92 ; WX 1111.11 ; N intersectiondisplay ; B 56 -1400 1054 0 ;
183
+ C 93 ; WX 1111.11 ; N unionmultidisplay ; B 56 -1400 1054 0 ;
184
+ C 94 ; WX 1111.11 ; N logicalanddisplay ; B 56 -1400 1054 0 ;
185
+ C 95 ; WX 1111.11 ; N logicalordisplay ; B 56 -1400 1054 0 ;
186
+ C 96 ; WX 944.444 ; N coproducttext ; B 56 -1000 887 0 ;
187
+ C 97 ; WX 1277.78 ; N coproductdisplay ; B 56 -1400 1221 0 ;
188
+ C 98 ; WX 555.556 ; N hatwide ; B -5 562 561 744 ;
189
+ C 99 ; WX 1000 ; N hatwider ; B -4 575 1003 772 ;
190
+ C 100 ; WX 1444.44 ; N hatwidest ; B -3 575 1446 772 ;
191
+ C 101 ; WX 555.556 ; N tildewide ; B 0 608 555 722 ;
192
+ C 102 ; WX 1000 ; N tildewider ; B 0 624 999 750 ;
193
+ C 103 ; WX 1444.44 ; N tildewidest ; B 0 623 1443 750 ;
194
+ C 104 ; WX 472.222 ; N bracketleftBig ; B 226 -1759 453 40 ;
195
+ C 105 ; WX 472.222 ; N bracketrightBig ; B 18 -1759 245 40 ;
196
+ C 106 ; WX 527.778 ; N floorleftBig ; B 226 -1759 509 40 ;
197
+ C 107 ; WX 527.778 ; N floorrightBig ; B 18 -1759 301 40 ;
198
+ C 108 ; WX 527.778 ; N ceilingleftBig ; B 226 -1759 509 40 ;
199
+ C 109 ; WX 527.778 ; N ceilingrightBig ; B 18 -1759 301 40 ;
200
+ C 110 ; WX 666.667 ; N braceleftBig ; B 119 -1759 547 40 ;
201
+ C 111 ; WX 666.667 ; N bracerightBig ; B 119 -1759 547 40 ;
202
+ C 112 ; WX 1000 ; N radicalbig ; B 110 -1160 1020 40 ;
203
+ C 113 ; WX 1000 ; N radicalBig ; B 110 -1760 1020 40 ;
204
+ C 114 ; WX 1000 ; N radicalbigg ; B 111 -2360 1020 40 ;
205
+ C 115 ; WX 1000 ; N radicalBigg ; B 111 -2960 1020 40 ;
206
+ C 116 ; WX 1055.56 ; N radicalbt ; B 111 -1800 742 20 ;
207
+ C 117 ; WX 1055.56 ; N radicalvertex ; B 702 -620 742 20 ;
208
+ C 118 ; WX 1055.56 ; N radicaltp ; B 702 -580 1076 40 ;
209
+ C 119 ; WX 777.778 ; N arrowvertexdbl ; B 257 -601 521 1 ;
210
+ C 120 ; WX 666.667 ; N arrowtp ; B 111 -600 556 0 ;
211
+ C 121 ; WX 666.667 ; N arrowbt ; B 111 -600 556 0 ;
212
+ C 122 ; WX 450 ; N bracehtipdownleft ; B -24 -214 460 120 ;
213
+ C 123 ; WX 450 ; N bracehtipdownright ; B -10 -214 474 120 ;
214
+ C 124 ; WX 450 ; N bracehtipupleft ; B -24 0 460 334 ;
215
+ C 125 ; WX 450 ; N bracehtipupright ; B -10 0 474 334 ;
216
+ C 126 ; WX 777.778 ; N arrowdbltp ; B 56 -600 722 -1 ;
217
+ C 127 ; WX 777.778 ; N arrowdblbt ; B 56 -599 722 0 ;
218
+ C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
219
+ EndCharMetrics
220
+ EndFontMetrics
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Creation Date: Thu Jun 21 22:23:22 1990
3
+ Comment UniqueID 5000785
4
+ FontName CMMI10
5
+ EncodingScheme FontSpecific
6
+ FullName CMMI10
7
+ FamilyName Computer Modern
8
+ Weight Medium
9
+ ItalicAngle -14.04
10
+ IsFixedPitch false
11
+ Version 1.00A
12
+ Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
13
+ Comment Computer Modern fonts were designed by Donald E. Knuth
14
+ FontBBox -32 -250 1048 750
15
+ CapHeight 683.333
16
+ XHeight 430.556
17
+ Ascender 694.444
18
+ Descender -194.444
19
+ Comment FontID CMMI
20
+ Comment DesignSize 10 (pts)
21
+ Comment CharacterCodingScheme TeX math italic
22
+ Comment Space 0 0 0
23
+ Comment Quad 1000
24
+ StartCharMetrics 129
25
+ C 0 ; WX 615.276 ; N Gamma ; B 39 0 723 680 ;
26
+ C 1 ; WX 833.333 ; N Delta ; B 49 0 787 716 ;
27
+ C 2 ; WX 762.774 ; N Theta ; B 50 -22 739 705 ;
28
+ C 3 ; WX 694.444 ; N Lambda ; B 35 0 666 716 ;
29
+ C 4 ; WX 742.361 ; N Xi ; B 53 0 777 677 ;
30
+ C 5 ; WX 831.25 ; N Pi ; B 39 0 880 680 ;
31
+ C 6 ; WX 779.861 ; N Sigma ; B 59 0 807 683 ;
32
+ C 7 ; WX 583.333 ; N Upsilon ; B 29 0 700 705 ;
33
+ C 8 ; WX 666.667 ; N Phi ; B 24 0 642 683 ;
34
+ C 9 ; WX 612.221 ; N Psi ; B 28 0 692 683 ;
35
+ C 10 ; WX 772.396 ; N Omega ; B 80 0 785 705 ;
36
+ C 11 ; WX 639.7 ; N alpha ; B 41 -11 601 442 ;
37
+ C 12 ; WX 565.625 ; N beta ; B 25 -194 590 705 ;
38
+ C 13 ; WX 517.73 ; N gamma ; B 18 -215 542 442 ;
39
+ C 14 ; WX 444.444 ; N delta ; B 41 -12 452 705 ;
40
+ C 15 ; WX 405.902 ; N epsilon1 ; B 47 -11 376 431 ;
41
+ C 16 ; WX 437.5 ; N zeta ; B 47 -205 474 697 ;
42
+ C 17 ; WX 496.53 ; N eta ; B 29 -216 496 442 ;
43
+ C 18 ; WX 469.442 ; N theta ; B 42 -11 455 705 ;
44
+ C 19 ; WX 353.935 ; N iota ; B 56 -11 324 442 ;
45
+ C 20 ; WX 576.159 ; N kappa ; B 55 -11 546 442 ;
46
+ C 21 ; WX 583.333 ; N lambda ; B 53 -13 547 694 ;
47
+ C 22 ; WX 602.548 ; N mu ; B 30 -216 572 442 ;
48
+ C 23 ; WX 493.981 ; N nu ; B 53 0 524 442 ;
49
+ C 24 ; WX 437.5 ; N xi ; B 24 -205 446 697 ;
50
+ C 25 ; WX 570.025 ; N pi ; B 27 -11 567 431 ;
51
+ C 26 ; WX 517.014 ; N rho ; B 30 -216 502 442 ;
52
+ C 27 ; WX 571.429 ; N sigma ; B 38 -11 567 431 ;
53
+ C 28 ; WX 437.153 ; N tau ; B 27 -12 511 431 ;
54
+ C 29 ; WX 540.278 ; N upsilon ; B 29 -11 524 443 ;
55
+ C 30 ; WX 595.833 ; N phi ; B 49 -205 573 694 ;
56
+ C 31 ; WX 625.691 ; N chi ; B 32 -205 594 442 ;
57
+ C 32 ; WX 651.39 ; N psi ; B 29 -205 635 694 ;
58
+ C 33 ; WX 622.453 ; N omega ; B 13 -11 605 443 ;
59
+ C 34 ; WX 466.316 ; N epsilon ; B 27 -22 428 453 ;
60
+ C 35 ; WX 591.438 ; N theta1 ; B 29 -11 561 705 ;
61
+ C 36 ; WX 828.125 ; N pi1 ; B 27 -11 817 431 ;
62
+ C 37 ; WX 517.014 ; N rho1 ; B 74 -194 502 442 ;
63
+ C 38 ; WX 362.846 ; N sigma1 ; B 32 -108 408 442 ;
64
+ C 39 ; WX 654.165 ; N phi1 ; B 50 -218 619 442 ;
65
+ C 40 ; WX 1000 ; N arrowlefttophalf ; B 56 230 943 428 ;
66
+ C 41 ; WX 1000 ; N arrowleftbothalf ; B 56 72 943 270 ;
67
+ C 42 ; WX 1000 ; N arrowrighttophalf ; B 56 230 943 428 ;
68
+ C 43 ; WX 1000 ; N arrowrightbothalf ; B 56 72 943 270 ;
69
+ C 44 ; WX 277.778 ; N arrowhookleft ; B 56 230 221 464 ;
70
+ C 45 ; WX 277.778 ; N arrowhookright ; B 56 230 221 464 ;
71
+ C 46 ; WX 500 ; N triangleright ; B 27 -4 472 504 ;
72
+ C 47 ; WX 500 ; N triangleleft ; B 27 -4 472 504 ;
73
+ C 48 ; WX 500 ; N zerooldstyle ; B 40 -22 459 453 ;
74
+ C 49 ; WX 500 ; N oneoldstyle ; B 92 0 418 453 ;
75
+ C 50 ; WX 500 ; N twooldstyle ; B 44 0 449 453 ;
76
+ C 51 ; WX 500 ; N threeoldstyle ; B 42 -216 457 453 ;
77
+ C 52 ; WX 500 ; N fouroldstyle ; B 28 -194 471 464 ;
78
+ C 53 ; WX 500 ; N fiveoldstyle ; B 50 -216 449 453 ;
79
+ C 54 ; WX 500 ; N sixoldstyle ; B 42 -22 457 666 ;
80
+ C 55 ; WX 500 ; N sevenoldstyle ; B 56 -216 485 463 ;
81
+ C 56 ; WX 500 ; N eightoldstyle ; B 42 -22 457 666 ;
82
+ C 57 ; WX 500 ; N nineoldstyle ; B 42 -216 457 453 ;
83
+ C 58 ; WX 277.778 ; N period ; B 86 0 192 106 ;
84
+ C 59 ; WX 277.778 ; N comma ; B 86 -193 203 106 ;
85
+ C 60 ; WX 777.778 ; N less ; B 83 -39 694 539 ;
86
+ C 61 ; WX 500 ; N slash ; B 56 -250 443 750 ;
87
+ C 62 ; WX 777.778 ; N greater ; B 83 -39 694 539 ;
88
+ C 63 ; WX 500 ; N star ; B 4 16 496 486 ;
89
+ C 64 ; WX 530.902 ; N partialdiff ; B 40 -22 566 716 ;
90
+ C 65 ; WX 750 ; N A ; B 35 0 722 716 ;
91
+ C 66 ; WX 758.508 ; N B ; B 42 0 756 683 ;
92
+ C 67 ; WX 714.72 ; N C ; B 51 -22 759 705 ;
93
+ C 68 ; WX 827.915 ; N D ; B 41 0 803 683 ;
94
+ C 69 ; WX 738.193 ; N E ; B 39 0 765 680 ;
95
+ C 70 ; WX 643.055 ; N F ; B 39 0 751 680 ;
96
+ C 71 ; WX 786.247 ; N G ; B 51 -22 760 705 ;
97
+ C 72 ; WX 831.25 ; N H ; B 39 0 881 683 ;
98
+ C 73 ; WX 439.583 ; N I ; B 34 0 498 683 ;
99
+ C 74 ; WX 554.512 ; N J ; B 73 -22 633 683 ;
100
+ C 75 ; WX 849.305 ; N K ; B 39 0 889 683 ;
101
+ C 76 ; WX 680.556 ; N L ; B 39 0 643 683 ;
102
+ C 77 ; WX 970.138 ; N M ; B 43 0 1044 683 ;
103
+ C 78 ; WX 803.471 ; N N ; B 39 0 881 683 ;
104
+ C 79 ; WX 762.774 ; N O ; B 50 -22 739 705 ;
105
+ C 80 ; WX 642.012 ; N P ; B 41 0 753 683 ;
106
+ C 81 ; WX 790.553 ; N Q ; B 50 -194 739 705 ;
107
+ C 82 ; WX 759.288 ; N R ; B 41 -22 755 683 ;
108
+ C 83 ; WX 613.193 ; N S ; B 53 -22 645 705 ;
109
+ C 84 ; WX 584.375 ; N T ; B 24 0 704 677 ;
110
+ C 85 ; WX 682.776 ; N U ; B 68 -22 760 683 ;
111
+ C 86 ; WX 583.333 ; N V ; B 56 -22 769 683 ;
112
+ C 87 ; WX 944.444 ; N W ; B 55 -22 1048 683 ;
113
+ C 88 ; WX 828.472 ; N X ; B 27 0 851 683 ;
114
+ C 89 ; WX 580.556 ; N Y ; B 34 0 762 683 ;
115
+ C 90 ; WX 682.638 ; N Z ; B 59 0 722 683 ;
116
+ C 91 ; WX 388.889 ; N flat ; B 56 -22 332 750 ;
117
+ C 92 ; WX 388.889 ; N natural ; B 79 -217 309 728 ;
118
+ C 93 ; WX 388.889 ; N sharp ; B 56 -216 332 716 ;
119
+ C 94 ; WX 1000 ; N slurbelow ; B 56 133 943 371 ;
120
+ C 95 ; WX 1000 ; N slurabove ; B 56 130 943 381 ;
121
+ C 96 ; WX 416.667 ; N lscript ; B 11 -12 398 705 ;
122
+ C 97 ; WX 528.588 ; N a ; B 40 -11 498 442 ;
123
+ C 98 ; WX 429.165 ; N b ; B 47 -11 415 694 ;
124
+ C 99 ; WX 432.755 ; N c ; B 41 -11 430 442 ;
125
+ C 100 ; WX 520.486 ; N d ; B 40 -11 517 694 ;
126
+ C 101 ; WX 465.625 ; N e ; B 46 -11 430 442 ;
127
+ C 102 ; WX 489.583 ; N f ; B 53 -205 552 705 ;
128
+ C 103 ; WX 476.967 ; N g ; B 16 -205 474 442 ;
129
+ C 104 ; WX 576.159 ; N h ; B 55 -11 546 694 ;
130
+ C 105 ; WX 344.511 ; N i ; B 29 -11 293 661 ;
131
+ C 106 ; WX 411.805 ; N j ; B -13 -205 397 661 ;
132
+ C 107 ; WX 520.602 ; N k ; B 55 -11 508 694 ;
133
+ C 108 ; WX 298.378 ; N l ; B 46 -11 260 694 ;
134
+ C 109 ; WX 878.012 ; N m ; B 29 -11 848 442 ;
135
+ C 110 ; WX 600.233 ; N n ; B 29 -11 571 442 ;
136
+ C 111 ; WX 484.721 ; N o ; B 41 -11 469 442 ;
137
+ C 112 ; WX 503.125 ; N p ; B -32 -194 490 442 ;
138
+ C 113 ; WX 446.412 ; N q ; B 40 -194 453 442 ;
139
+ C 114 ; WX 451.158 ; N r ; B 29 -11 436 442 ;
140
+ C 115 ; WX 468.75 ; N s ; B 52 -11 419 442 ;
141
+ C 116 ; WX 361.111 ; N t ; B 23 -11 330 626 ;
142
+ C 117 ; WX 572.456 ; N u ; B 29 -11 543 442 ;
143
+ C 118 ; WX 484.722 ; N v ; B 29 -11 468 443 ;
144
+ C 119 ; WX 715.916 ; N w ; B 29 -11 691 443 ;
145
+ C 120 ; WX 571.527 ; N x ; B 29 -11 527 442 ;
146
+ C 121 ; WX 490.28 ; N y ; B 29 -205 490 442 ;
147
+ C 122 ; WX 465.048 ; N z ; B 43 -11 467 442 ;
148
+ C 123 ; WX 322.454 ; N dotlessi ; B 29 -11 293 442 ;
149
+ C 124 ; WX 384.028 ; N dotlessj ; B -13 -205 360 442 ;
150
+ C 125 ; WX 636.457 ; N weierstrass ; B 76 -216 618 453 ;
151
+ C 126 ; WX 500 ; N vector ; B 182 516 625 714 ;
152
+ C 127 ; WX 277.778 ; N tie ; B 264 538 651 665 ;
153
+ C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
154
+ EndCharMetrics
155
+ Comment The following are bogus kern pairs for TeX positioning of accents
156
+ StartKernData
157
+ StartKernPairs 166
158
+ KPX Gamma slash -55.556
159
+ KPX Gamma comma -111.111
160
+ KPX Gamma period -111.111
161
+ KPX Gamma tie 83.333
162
+ KPX Delta tie 166.667
163
+ KPX Theta tie 83.333
164
+ KPX Lambda tie 166.667
165
+ KPX Xi tie 83.333
166
+ KPX Pi slash -55.556
167
+ KPX Pi comma -55.556
168
+ KPX Pi period -55.556
169
+ KPX Pi tie 55.556
170
+ KPX Sigma tie 83.333
171
+ KPX Upsilon slash -55.556
172
+ KPX Upsilon comma -111.111
173
+ KPX Upsilon period -111.111
174
+ KPX Upsilon tie 55.556
175
+ KPX Phi tie 83.333
176
+ KPX Psi slash -55.556
177
+ KPX Psi comma -55.556
178
+ KPX Psi period -55.556
179
+ KPX Psi tie 55.556
180
+ KPX Omega tie 83.333
181
+ KPX alpha tie 27.778
182
+ KPX beta tie 83.333
183
+ KPX delta comma -55.556
184
+ KPX delta period -55.556
185
+ KPX delta tie 55.556
186
+ KPX epsilon1 tie 55.556
187
+ KPX zeta tie 83.333
188
+ KPX eta tie 55.556
189
+ KPX theta tie 83.333
190
+ KPX iota tie 55.556
191
+ KPX mu tie 27.778
192
+ KPX nu comma -55.556
193
+ KPX nu period -55.556
194
+ KPX nu tie 27.778
195
+ KPX xi tie 111.111
196
+ KPX rho tie 83.333
197
+ KPX sigma comma -55.556
198
+ KPX sigma period -55.556
199
+ KPX tau comma -55.556
200
+ KPX tau period -55.556
201
+ KPX tau tie 27.778
202
+ KPX upsilon tie 27.778
203
+ KPX phi tie 83.333
204
+ KPX chi tie 55.556
205
+ KPX psi tie 111.111
206
+ KPX epsilon tie 83.333
207
+ KPX theta1 tie 83.333
208
+ KPX rho1 tie 83.333
209
+ KPX sigma1 tie 83.333
210
+ KPX phi1 tie 83.333
211
+ KPX slash Delta -55.556
212
+ KPX slash A -55.556
213
+ KPX slash M -55.556
214
+ KPX slash N -55.556
215
+ KPX slash Y 55.556
216
+ KPX slash Z -55.556
217
+ KPX partialdiff tie 83.333
218
+ KPX A tie 138.889
219
+ KPX B tie 83.333
220
+ KPX C slash -27.778
221
+ KPX C comma -55.556
222
+ KPX C period -55.556
223
+ KPX C tie 83.333
224
+ KPX D tie 55.556
225
+ KPX E tie 83.333
226
+ KPX F slash -55.556
227
+ KPX F comma -111.111
228
+ KPX F period -111.111
229
+ KPX F tie 83.333
230
+ KPX G tie 83.333
231
+ KPX H slash -55.556
232
+ KPX H comma -55.556
233
+ KPX H period -55.556
234
+ KPX H tie 55.556
235
+ KPX I tie 111.111
236
+ KPX J slash -55.556
237
+ KPX J comma -111.111
238
+ KPX J period -111.111
239
+ KPX J tie 166.667
240
+ KPX K slash -55.556
241
+ KPX K comma -55.556
242
+ KPX K period -55.556
243
+ KPX K tie 55.556
244
+ KPX L tie 27.778
245
+ KPX M slash -55.556
246
+ KPX M comma -55.556
247
+ KPX M period -55.556
248
+ KPX M tie 83.333
249
+ KPX N slash -83.333
250
+ KPX N slash -27.778
251
+ KPX N comma -55.556
252
+ KPX N period -55.556
253
+ KPX N tie 83.333
254
+ KPX O tie 83.333
255
+ KPX P slash -55.556
256
+ KPX P comma -111.111
257
+ KPX P period -111.111
258
+ KPX P tie 83.333
259
+ KPX Q tie 83.333
260
+ KPX R tie 83.333
261
+ KPX S slash -55.556
262
+ KPX S comma -55.556
263
+ KPX S period -55.556
264
+ KPX S tie 83.333
265
+ KPX T slash -27.778
266
+ KPX T comma -55.556
267
+ KPX T period -55.556
268
+ KPX T tie 83.333
269
+ KPX U comma -111.111
270
+ KPX U period -111.111
271
+ KPX U slash -55.556
272
+ KPX U tie 27.778
273
+ KPX V comma -166.667
274
+ KPX V period -166.667
275
+ KPX V slash -111.111
276
+ KPX W comma -166.667
277
+ KPX W period -166.667
278
+ KPX W slash -111.111
279
+ KPX X slash -83.333
280
+ KPX X slash -27.778
281
+ KPX X comma -55.556
282
+ KPX X period -55.556
283
+ KPX X tie 83.333
284
+ KPX Y comma -166.667
285
+ KPX Y period -166.667
286
+ KPX Y slash -111.111
287
+ KPX Z slash -55.556
288
+ KPX Z comma -55.556
289
+ KPX Z period -55.556
290
+ KPX Z tie 83.333
291
+ KPX lscript tie 111.111
292
+ KPX c tie 55.556
293
+ KPX d Y 55.556
294
+ KPX d Z -55.556
295
+ KPX d j -111.111
296
+ KPX d f -166.667
297
+ KPX d tie 166.667
298
+ KPX e tie 55.556
299
+ KPX f comma -55.556
300
+ KPX f period -55.556
301
+ KPX f tie 166.667
302
+ KPX g tie 27.778
303
+ KPX h tie -27.778
304
+ KPX j comma -55.556
305
+ KPX j period -55.556
306
+ KPX l tie 83.333
307
+ KPX o tie 55.556
308
+ KPX p tie 83.333
309
+ KPX q tie 83.333
310
+ KPX r comma -55.556
311
+ KPX r period -55.556
312
+ KPX r tie 55.556
313
+ KPX s tie 55.556
314
+ KPX t tie 83.333
315
+ KPX u tie 27.778
316
+ KPX v tie 27.778
317
+ KPX w tie 83.333
318
+ KPX x tie 27.778
319
+ KPX y tie 55.556
320
+ KPX z tie 55.556
321
+ KPX dotlessi tie 27.778
322
+ KPX dotlessj tie 83.333
323
+ KPX weierstrass tie 111.111
324
+ EndKernPairs
325
+ EndKernData
326
+ EndFontMetrics
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Creation Date: Thu Jun 21 22:23:28 1990
3
+ Comment UniqueID 5000793
4
+ FontName CMR10
5
+ EncodingScheme FontSpecific
6
+ FullName CMR10
7
+ FamilyName Computer Modern
8
+ Weight Medium
9
+ ItalicAngle 0.0
10
+ IsFixedPitch false
11
+ Version 1.00B
12
+ Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
13
+ Comment Computer Modern fonts were designed by Donald E. Knuth
14
+ FontBBox -40 -250 1009 969
15
+ CapHeight 683.333
16
+ XHeight 430.556
17
+ Ascender 694.444
18
+ Descender -194.444
19
+ Comment FontID CMR
20
+ Comment DesignSize 10 (pts)
21
+ Comment CharacterCodingScheme TeX text
22
+ Comment Space 333.333 166.667 111.111
23
+ Comment ExtraSpace 111.111
24
+ Comment Quad 1000
25
+ StartCharMetrics 129
26
+ C 0 ; WX 625 ; N Gamma ; B 33 0 582 680 ;
27
+ C 1 ; WX 833.333 ; N Delta ; B 47 0 785 716 ;
28
+ C 2 ; WX 777.778 ; N Theta ; B 56 -22 721 705 ;
29
+ C 3 ; WX 694.444 ; N Lambda ; B 32 0 661 716 ;
30
+ C 4 ; WX 666.667 ; N Xi ; B 42 0 624 677 ;
31
+ C 5 ; WX 750 ; N Pi ; B 33 0 716 680 ;
32
+ C 6 ; WX 722.222 ; N Sigma ; B 56 0 665 683 ;
33
+ C 7 ; WX 777.778 ; N Upsilon ; B 56 0 721 705 ;
34
+ C 8 ; WX 722.222 ; N Phi ; B 56 0 665 683 ;
35
+ C 9 ; WX 777.778 ; N Psi ; B 57 0 720 683 ;
36
+ C 10 ; WX 722.222 ; N Omega ; B 44 0 677 705 ;
37
+ C 11 ; WX 583.333 ; N ff ; B 27 0 628 705 ; L i ffi ; L l ffl ;
38
+ C 12 ; WX 555.556 ; N fi ; B 27 0 527 705 ;
39
+ C 13 ; WX 555.556 ; N fl ; B 27 0 527 705 ;
40
+ C 14 ; WX 833.333 ; N ffi ; B 27 0 804 705 ;
41
+ C 15 ; WX 833.333 ; N ffl ; B 27 0 804 705 ;
42
+ C 16 ; WX 277.778 ; N dotlessi ; B 33 0 247 442 ;
43
+ C 17 ; WX 305.556 ; N dotlessj ; B -40 -205 210 442 ;
44
+ C 18 ; WX 500 ; N grave ; B 107 510 293 698 ;
45
+ C 19 ; WX 500 ; N acute ; B 206 510 392 698 ;
46
+ C 20 ; WX 500 ; N caron ; B 118 516 381 638 ;
47
+ C 21 ; WX 500 ; N breve ; B 100 522 399 694 ;
48
+ C 22 ; WX 500 ; N macron ; B 69 559 430 590 ;
49
+ C 23 ; WX 750 ; N ring ; B 279 541 470 716 ;
50
+ C 24 ; WX 444.444 ; N cedilla ; B 131 -203 367 -22 ;
51
+ C 25 ; WX 500 ; N germandbls ; B 28 -11 471 705 ;
52
+ C 26 ; WX 722.222 ; N ae ; B 45 -11 693 448 ;
53
+ C 27 ; WX 777.778 ; N oe ; B 28 -11 749 448 ;
54
+ C 28 ; WX 500 ; N oslash ; B 35 -102 464 534 ;
55
+ C 29 ; WX 902.778 ; N AE ; B 32 0 874 683 ;
56
+ C 30 ; WX 1013.89 ; N OE ; B 70 -22 985 705 ;
57
+ C 31 ; WX 777.778 ; N Oslash ; B 56 -56 721 739 ;
58
+ C 32 ; WX 277.778 ; N suppress ; B 27 280 262 392 ;
59
+ C 33 ; WX 277.778 ; N exclam ; B 86 0 192 716 ; L quoteleft exclamdown ;
60
+ C 34 ; WX 500 ; N quotedblright ; B 33 395 347 694 ;
61
+ C 35 ; WX 833.333 ; N numbersign ; B 56 -194 776 694 ;
62
+ C 36 ; WX 500 ; N dollar ; B 56 -56 443 750 ;
63
+ C 37 ; WX 833.333 ; N percent ; B 56 -56 776 750 ;
64
+ C 38 ; WX 777.778 ; N ampersand ; B 42 -22 727 716 ;
65
+ C 39 ; WX 277.778 ; N quoteright ; B 86 395 206 694 ; L quoteright quotedblright ;
66
+ C 40 ; WX 388.889 ; N parenleft ; B 99 -250 331 750 ;
67
+ C 41 ; WX 388.889 ; N parenright ; B 57 -250 289 750 ;
68
+ C 42 ; WX 500 ; N asterisk ; B 65 319 434 750 ;
69
+ C 43 ; WX 777.778 ; N plus ; B 56 -83 721 583 ;
70
+ C 44 ; WX 277.778 ; N comma ; B 86 -193 203 106 ;
71
+ C 45 ; WX 333.333 ; N hyphen ; B 11 187 276 245 ; L hyphen endash ;
72
+ C 46 ; WX 277.778 ; N period ; B 86 0 192 106 ;
73
+ C 47 ; WX 500 ; N slash ; B 56 -250 443 750 ;
74
+ C 48 ; WX 500 ; N zero ; B 39 -22 460 666 ;
75
+ C 49 ; WX 500 ; N one ; B 89 0 419 666 ;
76
+ C 50 ; WX 500 ; N two ; B 50 0 449 666 ;
77
+ C 51 ; WX 500 ; N three ; B 42 -22 457 666 ;
78
+ C 52 ; WX 500 ; N four ; B 28 0 471 677 ;
79
+ C 53 ; WX 500 ; N five ; B 50 -22 449 666 ;
80
+ C 54 ; WX 500 ; N six ; B 42 -22 457 666 ;
81
+ C 55 ; WX 500 ; N seven ; B 56 -22 485 676 ;
82
+ C 56 ; WX 500 ; N eight ; B 42 -22 457 666 ;
83
+ C 57 ; WX 500 ; N nine ; B 42 -22 457 666 ;
84
+ C 58 ; WX 277.778 ; N colon ; B 86 0 192 431 ;
85
+ C 59 ; WX 277.778 ; N semicolon ; B 86 -193 195 431 ;
86
+ C 60 ; WX 277.778 ; N exclamdown ; B 86 -216 192 500 ;
87
+ C 61 ; WX 777.778 ; N equal ; B 56 133 721 367 ;
88
+ C 62 ; WX 472.222 ; N questiondown ; B 56 -205 415 500 ;
89
+ C 63 ; WX 472.222 ; N question ; B 56 0 415 705 ; L quoteleft questiondown ;
90
+ C 64 ; WX 777.778 ; N at ; B 56 -11 721 705 ;
91
+ C 65 ; WX 750 ; N A ; B 32 0 717 716 ;
92
+ C 66 ; WX 708.333 ; N B ; B 36 0 651 683 ;
93
+ C 67 ; WX 722.222 ; N C ; B 56 -22 665 705 ;
94
+ C 68 ; WX 763.889 ; N D ; B 35 0 707 683 ;
95
+ C 69 ; WX 680.556 ; N E ; B 33 0 652 680 ;
96
+ C 70 ; WX 652.778 ; N F ; B 33 0 610 680 ;
97
+ C 71 ; WX 784.722 ; N G ; B 56 -22 735 705 ;
98
+ C 72 ; WX 750 ; N H ; B 33 0 716 683 ;
99
+ C 73 ; WX 361.111 ; N I ; B 28 0 333 683 ;
100
+ C 74 ; WX 513.889 ; N J ; B 41 -22 465 683 ;
101
+ C 75 ; WX 777.778 ; N K ; B 33 0 736 683 ;
102
+ C 76 ; WX 625 ; N L ; B 33 0 582 683 ;
103
+ C 77 ; WX 916.667 ; N M ; B 37 0 879 683 ;
104
+ C 78 ; WX 750 ; N N ; B 33 0 716 683 ;
105
+ C 79 ; WX 777.778 ; N O ; B 56 -22 721 705 ;
106
+ C 80 ; WX 680.556 ; N P ; B 35 0 624 683 ;
107
+ C 81 ; WX 777.778 ; N Q ; B 56 -194 727 705 ;
108
+ C 82 ; WX 736.111 ; N R ; B 35 -22 732 683 ;
109
+ C 83 ; WX 555.556 ; N S ; B 56 -22 499 705 ;
110
+ C 84 ; WX 722.222 ; N T ; B 36 0 685 677 ;
111
+ C 85 ; WX 750 ; N U ; B 33 -22 716 683 ;
112
+ C 86 ; WX 750 ; N V ; B 19 -22 730 683 ;
113
+ C 87 ; WX 1027.78 ; N W ; B 18 -22 1009 683 ;
114
+ C 88 ; WX 750 ; N X ; B 24 0 726 683 ;
115
+ C 89 ; WX 750 ; N Y ; B 11 0 738 683 ;
116
+ C 90 ; WX 611.111 ; N Z ; B 56 0 560 683 ;
117
+ C 91 ; WX 277.778 ; N bracketleft ; B 118 -250 255 750 ;
118
+ C 92 ; WX 500 ; N quotedblleft ; B 152 394 466 693 ;
119
+ C 93 ; WX 277.778 ; N bracketright ; B 22 -250 159 750 ;
120
+ C 94 ; WX 500 ; N circumflex ; B 116 540 383 694 ;
121
+ C 95 ; WX 277.778 ; N dotaccent ; B 85 563 192 669 ;
122
+ C 96 ; WX 277.778 ; N quoteleft ; B 72 394 192 693 ; L quoteleft quotedblleft ;
123
+ C 97 ; WX 500 ; N a ; B 42 -11 493 448 ;
124
+ C 98 ; WX 555.556 ; N b ; B 28 -11 521 694 ;
125
+ C 99 ; WX 444.444 ; N c ; B 34 -11 415 448 ;
126
+ C 100 ; WX 555.556 ; N d ; B 34 -11 527 694 ;
127
+ C 101 ; WX 444.444 ; N e ; B 28 -11 415 448 ;
128
+ C 102 ; WX 305.556 ; N f ; B 33 0 357 705 ; L i fi ; L f ff ; L l fl ;
129
+ C 103 ; WX 500 ; N g ; B 28 -206 485 453 ;
130
+ C 104 ; WX 555.556 ; N h ; B 32 0 535 694 ;
131
+ C 105 ; WX 277.778 ; N i ; B 33 0 247 669 ;
132
+ C 106 ; WX 305.556 ; N j ; B -40 -205 210 669 ;
133
+ C 107 ; WX 527.778 ; N k ; B 28 0 511 694 ;
134
+ C 108 ; WX 277.778 ; N l ; B 33 0 255 694 ;
135
+ C 109 ; WX 833.333 ; N m ; B 32 0 813 442 ;
136
+ C 110 ; WX 555.556 ; N n ; B 32 0 535 442 ;
137
+ C 111 ; WX 500 ; N o ; B 28 -11 471 448 ;
138
+ C 112 ; WX 555.556 ; N p ; B 28 -194 521 442 ;
139
+ C 113 ; WX 527.778 ; N q ; B 34 -194 527 442 ;
140
+ C 114 ; WX 391.667 ; N r ; B 28 0 364 442 ;
141
+ C 115 ; WX 394.444 ; N s ; B 33 -11 360 448 ;
142
+ C 116 ; WX 388.889 ; N t ; B 19 -11 332 615 ;
143
+ C 117 ; WX 555.556 ; N u ; B 32 -11 535 442 ;
144
+ C 118 ; WX 527.778 ; N v ; B 19 -11 508 431 ;
145
+ C 119 ; WX 722.222 ; N w ; B 18 -11 703 431 ;
146
+ C 120 ; WX 527.778 ; N x ; B 12 0 516 431 ;
147
+ C 121 ; WX 527.778 ; N y ; B 19 -205 508 431 ;
148
+ C 122 ; WX 444.444 ; N z ; B 28 0 401 431 ;
149
+ C 123 ; WX 500 ; N endash ; B 0 255 499 277 ; L hyphen emdash ;
150
+ C 124 ; WX 1000 ; N emdash ; B 0 255 999 277 ;
151
+ C 125 ; WX 500 ; N hungarumlaut ; B 128 513 420 699 ;
152
+ C 126 ; WX 500 ; N tilde ; B 83 575 416 668 ;
153
+ C 127 ; WX 500 ; N dieresis ; B 103 569 396 669 ;
154
+ C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
155
+ EndCharMetrics
156
+ StartKernData
157
+ StartKernPairs 183
158
+ KPX ff quoteright 77.778
159
+ KPX ff question 77.778
160
+ KPX ff exclam 77.778
161
+ KPX ff parenright 77.778
162
+ KPX ff bracketright 77.778
163
+ KPX suppress l -277.778
164
+ KPX suppress L -319.444
165
+ KPX quoteright question 111.111
166
+ KPX quoteright exclam 111.111
167
+ KPX A t -27.778
168
+ KPX A C -27.778
169
+ KPX A O -27.778
170
+ KPX A G -27.778
171
+ KPX A U -27.778
172
+ KPX A Q -27.778
173
+ KPX A T -83.333
174
+ KPX A Y -83.333
175
+ KPX A V -111.111
176
+ KPX A W -111.111
177
+ KPX D X -27.778
178
+ KPX D W -27.778
179
+ KPX D A -27.778
180
+ KPX D V -27.778
181
+ KPX D Y -27.778
182
+ KPX F o -83.333
183
+ KPX F e -83.333
184
+ KPX F u -83.333
185
+ KPX F r -83.333
186
+ KPX F a -83.333
187
+ KPX F A -111.111
188
+ KPX F O -27.778
189
+ KPX F C -27.778
190
+ KPX F G -27.778
191
+ KPX F Q -27.778
192
+ KPX I I 27.778
193
+ KPX K O -27.778
194
+ KPX K C -27.778
195
+ KPX K G -27.778
196
+ KPX K Q -27.778
197
+ KPX L T -83.333
198
+ KPX L Y -83.333
199
+ KPX L V -111.111
200
+ KPX L W -111.111
201
+ KPX O X -27.778
202
+ KPX O W -27.778
203
+ KPX O A -27.778
204
+ KPX O V -27.778
205
+ KPX O Y -27.778
206
+ KPX P A -83.333
207
+ KPX P o -27.778
208
+ KPX P e -27.778
209
+ KPX P a -27.778
210
+ KPX P period -83.333
211
+ KPX P comma -83.333
212
+ KPX R t -27.778
213
+ KPX R C -27.778
214
+ KPX R O -27.778
215
+ KPX R G -27.778
216
+ KPX R U -27.778
217
+ KPX R Q -27.778
218
+ KPX R T -83.333
219
+ KPX R Y -83.333
220
+ KPX R V -111.111
221
+ KPX R W -111.111
222
+ KPX T y -27.778
223
+ KPX T e -83.333
224
+ KPX T o -83.333
225
+ KPX T r -83.333
226
+ KPX T a -83.333
227
+ KPX T A -83.333
228
+ KPX T u -83.333
229
+ KPX V o -83.333
230
+ KPX V e -83.333
231
+ KPX V u -83.333
232
+ KPX V r -83.333
233
+ KPX V a -83.333
234
+ KPX V A -111.111
235
+ KPX V O -27.778
236
+ KPX V C -27.778
237
+ KPX V G -27.778
238
+ KPX V Q -27.778
239
+ KPX W o -83.333
240
+ KPX W e -83.333
241
+ KPX W u -83.333
242
+ KPX W r -83.333
243
+ KPX W a -83.333
244
+ KPX W A -111.111
245
+ KPX W O -27.778
246
+ KPX W C -27.778
247
+ KPX W G -27.778
248
+ KPX W Q -27.778
249
+ KPX X O -27.778
250
+ KPX X C -27.778
251
+ KPX X G -27.778
252
+ KPX X Q -27.778
253
+ KPX Y e -83.333
254
+ KPX Y o -83.333
255
+ KPX Y r -83.333
256
+ KPX Y a -83.333
257
+ KPX Y A -83.333
258
+ KPX Y u -83.333
259
+ KPX a v -27.778
260
+ KPX a j 55.556
261
+ KPX a y -27.778
262
+ KPX a w -27.778
263
+ KPX b e 27.778
264
+ KPX b o 27.778
265
+ KPX b x -27.778
266
+ KPX b d 27.778
267
+ KPX b c 27.778
268
+ KPX b q 27.778
269
+ KPX b v -27.778
270
+ KPX b j 55.556
271
+ KPX b y -27.778
272
+ KPX b w -27.778
273
+ KPX c h -27.778
274
+ KPX c k -27.778
275
+ KPX f quoteright 77.778
276
+ KPX f question 77.778
277
+ KPX f exclam 77.778
278
+ KPX f parenright 77.778
279
+ KPX f bracketright 77.778
280
+ KPX g j 27.778
281
+ KPX h t -27.778
282
+ KPX h u -27.778
283
+ KPX h b -27.778
284
+ KPX h y -27.778
285
+ KPX h v -27.778
286
+ KPX h w -27.778
287
+ KPX k a -55.556
288
+ KPX k e -27.778
289
+ KPX k a -27.778
290
+ KPX k o -27.778
291
+ KPX k c -27.778
292
+ KPX m t -27.778
293
+ KPX m u -27.778
294
+ KPX m b -27.778
295
+ KPX m y -27.778
296
+ KPX m v -27.778
297
+ KPX m w -27.778
298
+ KPX n t -27.778
299
+ KPX n u -27.778
300
+ KPX n b -27.778
301
+ KPX n y -27.778
302
+ KPX n v -27.778
303
+ KPX n w -27.778
304
+ KPX o e 27.778
305
+ KPX o o 27.778
306
+ KPX o x -27.778
307
+ KPX o d 27.778
308
+ KPX o c 27.778
309
+ KPX o q 27.778
310
+ KPX o v -27.778
311
+ KPX o j 55.556
312
+ KPX o y -27.778
313
+ KPX o w -27.778
314
+ KPX p e 27.778
315
+ KPX p o 27.778
316
+ KPX p x -27.778
317
+ KPX p d 27.778
318
+ KPX p c 27.778
319
+ KPX p q 27.778
320
+ KPX p v -27.778
321
+ KPX p j 55.556
322
+ KPX p y -27.778
323
+ KPX p w -27.778
324
+ KPX t y -27.778
325
+ KPX t w -27.778
326
+ KPX u w -27.778
327
+ KPX v a -55.556
328
+ KPX v e -27.778
329
+ KPX v a -27.778
330
+ KPX v o -27.778
331
+ KPX v c -27.778
332
+ KPX w e -27.778
333
+ KPX w a -27.778
334
+ KPX w o -27.778
335
+ KPX w c -27.778
336
+ KPX y o -27.778
337
+ KPX y e -27.778
338
+ KPX y a -27.778
339
+ KPX y period -83.333
340
+ KPX y comma -83.333
341
+ EndKernPairs
342
+ EndKernData
343
+ EndFontMetrics
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Creation Date: Thu Jun 21 22:23:44 1990
3
+ Comment UniqueID 5000820
4
+ FontName CMSY10
5
+ EncodingScheme FontSpecific
6
+ FullName CMSY10
7
+ FamilyName Computer Modern
8
+ Weight Medium
9
+ ItalicAngle -14.035
10
+ IsFixedPitch false
11
+ Version 1.00
12
+ Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
13
+ Comment Computer Modern fonts were designed by Donald E. Knuth
14
+ FontBBox -29 -960 1116 775
15
+ CapHeight 683.333
16
+ XHeight 430.556
17
+ Ascender 694.444
18
+ Descender -960
19
+ Comment FontID CMSY
20
+ Comment DesignSize 10 (pts)
21
+ Comment CharacterCodingScheme TeX math symbols
22
+ Comment Space 0 0 0
23
+ Comment ExtraSpace 0
24
+ Comment Quad 1000
25
+ Comment Num 676.508 393.732 443.731
26
+ Comment Denom 685.951 344.841
27
+ Comment Sup 412.892 362.892 288.889
28
+ Comment Sub 150 247.217
29
+ Comment Supdrop 386.108
30
+ Comment Subdrop 50
31
+ Comment Delim 2390 1010
32
+ Comment Axisheight 250
33
+ StartCharMetrics 129
34
+ C 0 ; WX 777.778 ; N minus ; B 83 230 694 270 ;
35
+ C 1 ; WX 277.778 ; N periodcentered ; B 86 197 192 303 ;
36
+ C 2 ; WX 777.778 ; N multiply ; B 147 9 630 491 ;
37
+ C 3 ; WX 500 ; N asteriskmath ; B 65 34 434 465 ;
38
+ C 4 ; WX 777.778 ; N divide ; B 56 -30 722 530 ;
39
+ C 5 ; WX 500 ; N diamondmath ; B 11 11 489 489 ;
40
+ C 6 ; WX 777.778 ; N plusminus ; B 56 0 721 666 ;
41
+ C 7 ; WX 777.778 ; N minusplus ; B 56 -166 721 500 ;
42
+ C 8 ; WX 777.778 ; N circleplus ; B 56 -83 721 583 ;
43
+ C 9 ; WX 777.778 ; N circleminus ; B 56 -83 721 583 ;
44
+ C 10 ; WX 777.778 ; N circlemultiply ; B 56 -83 721 583 ;
45
+ C 11 ; WX 777.778 ; N circledivide ; B 56 -83 721 583 ;
46
+ C 12 ; WX 777.778 ; N circledot ; B 56 -83 721 583 ;
47
+ C 13 ; WX 1000 ; N circlecopyrt ; B 56 -216 943 716 ;
48
+ C 14 ; WX 500 ; N openbullet ; B 56 56 443 444 ;
49
+ C 15 ; WX 500 ; N bullet ; B 56 56 443 444 ;
50
+ C 16 ; WX 777.778 ; N equivasymptotic ; B 56 16 721 484 ;
51
+ C 17 ; WX 777.778 ; N equivalence ; B 56 36 721 464 ;
52
+ C 18 ; WX 777.778 ; N reflexsubset ; B 83 -137 694 636 ;
53
+ C 19 ; WX 777.778 ; N reflexsuperset ; B 83 -137 694 636 ;
54
+ C 20 ; WX 777.778 ; N lessequal ; B 83 -137 694 636 ;
55
+ C 21 ; WX 777.778 ; N greaterequal ; B 83 -137 694 636 ;
56
+ C 22 ; WX 777.778 ; N precedesequal ; B 83 -137 694 636 ;
57
+ C 23 ; WX 777.778 ; N followsequal ; B 83 -137 694 636 ;
58
+ C 24 ; WX 777.778 ; N similar ; B 56 133 721 367 ;
59
+ C 25 ; WX 777.778 ; N approxequal ; B 56 56 721 483 ;
60
+ C 26 ; WX 777.778 ; N propersubset ; B 83 -40 694 540 ;
61
+ C 27 ; WX 777.778 ; N propersuperset ; B 83 -40 694 540 ;
62
+ C 28 ; WX 1000 ; N lessmuch ; B 56 -66 943 566 ;
63
+ C 29 ; WX 1000 ; N greatermuch ; B 56 -66 943 566 ;
64
+ C 30 ; WX 777.778 ; N precedes ; B 83 -40 694 539 ;
65
+ C 31 ; WX 777.778 ; N follows ; B 83 -40 694 539 ;
66
+ C 32 ; WX 1000 ; N arrowleft ; B 57 72 943 428 ;
67
+ C 33 ; WX 1000 ; N arrowright ; B 56 72 942 428 ;
68
+ C 34 ; WX 500 ; N arrowup ; B 72 -194 428 693 ;
69
+ C 35 ; WX 500 ; N arrowdown ; B 72 -193 428 694 ;
70
+ C 36 ; WX 1000 ; N arrowboth ; B 57 72 942 428 ;
71
+ C 37 ; WX 1000 ; N arrownortheast ; B 56 -193 946 697 ;
72
+ C 38 ; WX 1000 ; N arrowsoutheast ; B 56 -197 946 693 ;
73
+ C 39 ; WX 777.778 ; N similarequal ; B 56 36 721 464 ;
74
+ C 40 ; WX 1000 ; N arrowdblleft ; B 57 -25 943 525 ;
75
+ C 41 ; WX 1000 ; N arrowdblright ; B 56 -25 942 525 ;
76
+ C 42 ; WX 611.111 ; N arrowdblup ; B 30 -194 580 694 ;
77
+ C 43 ; WX 611.111 ; N arrowdbldown ; B 30 -194 580 694 ;
78
+ C 44 ; WX 1000 ; N arrowdblboth ; B 35 -25 964 525 ;
79
+ C 45 ; WX 1000 ; N arrownorthwest ; B 53 -193 943 697 ;
80
+ C 46 ; WX 1000 ; N arrowsouthwest ; B 53 -197 943 693 ;
81
+ C 47 ; WX 777.778 ; N proportional ; B 56 -11 722 442 ;
82
+ C 48 ; WX 275 ; N prime ; B 29 45 262 559 ;
83
+ C 49 ; WX 1000 ; N infinity ; B 56 -11 943 442 ;
84
+ C 50 ; WX 666.667 ; N element ; B 83 -40 583 540 ;
85
+ C 51 ; WX 666.667 ; N owner ; B 83 -40 583 540 ;
86
+ C 52 ; WX 888.889 ; N triangle ; B 59 0 829 716 ;
87
+ C 53 ; WX 888.889 ; N triangleinv ; B 59 -216 829 500 ;
88
+ C 54 ; WX 0 ; N negationslash ; B 139 -216 638 716 ;
89
+ C 55 ; WX 0 ; N mapsto ; B 56 64 124 436 ;
90
+ C 56 ; WX 555.556 ; N universal ; B 0 -22 556 694 ;
91
+ C 57 ; WX 555.556 ; N existential ; B 56 0 499 694 ;
92
+ C 58 ; WX 666.667 ; N logicalnot ; B 56 89 610 356 ;
93
+ C 59 ; WX 500 ; N emptyset ; B 47 -78 452 772 ;
94
+ C 60 ; WX 722.222 ; N Rfractur ; B 46 -22 714 716 ;
95
+ C 61 ; WX 722.222 ; N Ifractur ; B 56 -11 693 705 ;
96
+ C 62 ; WX 777.778 ; N latticetop ; B 56 0 722 666 ;
97
+ C 63 ; WX 777.778 ; N perpendicular ; B 56 0 722 666 ;
98
+ C 64 ; WX 611.111 ; N aleph ; B 56 0 554 693 ;
99
+ C 65 ; WX 798.469 ; N A ; B 27 -50 798 722 ;
100
+ C 66 ; WX 656.808 ; N B ; B 30 -22 665 706 ;
101
+ C 67 ; WX 526.527 ; N C ; B 12 -24 534 705 ;
102
+ C 68 ; WX 771.391 ; N D ; B 20 0 766 683 ;
103
+ C 69 ; WX 527.778 ; N E ; B 28 -22 565 705 ;
104
+ C 70 ; WX 718.75 ; N F ; B 17 -33 829 683 ;
105
+ C 71 ; WX 594.864 ; N G ; B 44 -119 601 705 ;
106
+ C 72 ; WX 844.516 ; N H ; B 20 -47 818 683 ;
107
+ C 73 ; WX 544.513 ; N I ; B -24 0 635 683 ;
108
+ C 74 ; WX 677.778 ; N J ; B 47 -119 840 683 ;
109
+ C 75 ; WX 761.949 ; N K ; B 30 -22 733 705 ;
110
+ C 76 ; WX 689.723 ; N L ; B 31 -22 656 705 ;
111
+ C 77 ; WX 1200.9 ; N M ; B 27 -50 1116 705 ;
112
+ C 78 ; WX 820.489 ; N N ; B -29 -50 978 775 ;
113
+ C 79 ; WX 796.112 ; N O ; B 57 -22 777 705 ;
114
+ C 80 ; WX 695.558 ; N P ; B 20 -50 733 683 ;
115
+ C 81 ; WX 816.667 ; N Q ; B 113 -124 788 705 ;
116
+ C 82 ; WX 847.502 ; N R ; B 20 -22 837 683 ;
117
+ C 83 ; WX 605.556 ; N S ; B 18 -22 642 705 ;
118
+ C 84 ; WX 544.643 ; N T ; B 29 0 798 717 ;
119
+ C 85 ; WX 625.83 ; N U ; B -17 -28 688 683 ;
120
+ C 86 ; WX 612.781 ; N V ; B 35 -45 660 683 ;
121
+ C 87 ; WX 987.782 ; N W ; B 35 -45 1036 683 ;
122
+ C 88 ; WX 713.295 ; N X ; B 50 0 808 683 ;
123
+ C 89 ; WX 668.335 ; N Y ; B 31 -135 717 683 ;
124
+ C 90 ; WX 724.724 ; N Z ; B 37 0 767 683 ;
125
+ C 91 ; WX 666.667 ; N union ; B 56 -22 610 598 ;
126
+ C 92 ; WX 666.667 ; N intersection ; B 56 -22 610 598 ;
127
+ C 93 ; WX 666.667 ; N unionmulti ; B 56 -22 610 598 ;
128
+ C 94 ; WX 666.667 ; N logicaland ; B 56 -22 610 598 ;
129
+ C 95 ; WX 666.667 ; N logicalor ; B 56 -22 610 598 ;
130
+ C 96 ; WX 611.111 ; N turnstileleft ; B 56 0 554 694 ;
131
+ C 97 ; WX 611.111 ; N turnstileright ; B 56 0 554 694 ;
132
+ C 98 ; WX 444.444 ; N floorleft ; B 174 -250 422 750 ;
133
+ C 99 ; WX 444.444 ; N floorright ; B 21 -250 269 750 ;
134
+ C 100 ; WX 444.444 ; N ceilingleft ; B 174 -250 422 750 ;
135
+ C 101 ; WX 444.444 ; N ceilingright ; B 21 -250 269 750 ;
136
+ C 102 ; WX 500 ; N braceleft ; B 72 -250 427 750 ;
137
+ C 103 ; WX 500 ; N braceright ; B 72 -250 427 750 ;
138
+ C 104 ; WX 388.889 ; N angbracketleft ; B 110 -250 332 750 ;
139
+ C 105 ; WX 388.889 ; N angbracketright ; B 56 -250 278 750 ;
140
+ C 106 ; WX 277.778 ; N bar ; B 119 -250 159 750 ;
141
+ C 107 ; WX 500 ; N bardbl ; B 132 -250 367 750 ;
142
+ C 108 ; WX 500 ; N arrowbothv ; B 72 -272 428 772 ;
143
+ C 109 ; WX 611.111 ; N arrowdblbothv ; B 30 -272 580 772 ;
144
+ C 110 ; WX 500 ; N backslash ; B 56 -250 443 750 ;
145
+ C 111 ; WX 277.778 ; N wreathproduct ; B 56 -83 221 583 ;
146
+ C 112 ; WX 833.333 ; N radical ; B 73 -960 853 40 ;
147
+ C 113 ; WX 750 ; N coproduct ; B 36 0 713 683 ;
148
+ C 114 ; WX 833.333 ; N nabla ; B 47 -33 785 683 ;
149
+ C 115 ; WX 416.667 ; N integral ; B 56 -216 471 716 ;
150
+ C 116 ; WX 666.667 ; N unionsq ; B 61 0 605 598 ;
151
+ C 117 ; WX 666.667 ; N intersectionsq ; B 61 0 605 598 ;
152
+ C 118 ; WX 777.778 ; N subsetsqequal ; B 83 -137 714 636 ;
153
+ C 119 ; WX 777.778 ; N supersetsqequal ; B 63 -137 694 636 ;
154
+ C 120 ; WX 444.444 ; N section ; B 69 -205 374 705 ;
155
+ C 121 ; WX 444.444 ; N dagger ; B 56 -216 387 705 ;
156
+ C 122 ; WX 444.444 ; N daggerdbl ; B 56 -205 387 705 ;
157
+ C 123 ; WX 611.111 ; N paragraph ; B 56 -194 582 694 ;
158
+ C 124 ; WX 777.778 ; N club ; B 28 -130 750 727 ;
159
+ C 125 ; WX 777.778 ; N diamond ; B 56 -163 722 727 ;
160
+ C 126 ; WX 777.778 ; N heart ; B 56 -33 722 716 ;
161
+ C 127 ; WX 777.778 ; N spade ; B 56 -130 722 727 ;
162
+ C -1 ; WX 333.333 ; N space ; B 0 0 0 0 ;
163
+ EndCharMetrics
164
+ Comment The following are bogus kern pairs for TeX positioning of accents
165
+ StartKernData
166
+ StartKernPairs 26
167
+ KPX A prime 194.444
168
+ KPX B prime 138.889
169
+ KPX C prime 138.889
170
+ KPX D prime 83.333
171
+ KPX E prime 111.111
172
+ KPX F prime 111.111
173
+ KPX G prime 111.111
174
+ KPX H prime 111.111
175
+ KPX I prime 27.778
176
+ KPX J prime 166.667
177
+ KPX K prime 55.556
178
+ KPX L prime 138.889
179
+ KPX M prime 138.889
180
+ KPX N prime 83.333
181
+ KPX O prime 111.111
182
+ KPX P prime 83.333
183
+ KPX Q prime 111.111
184
+ KPX R prime 83.333
185
+ KPX S prime 138.889
186
+ KPX T prime 27.778
187
+ KPX U prime 83.333
188
+ KPX V prime 27.778
189
+ KPX W prime 83.333
190
+ KPX X prime 138.889
191
+ KPX Y prime 83.333
192
+ KPX Z prime 138.889
193
+ EndKernPairs
194
+ EndKernData
195
+ EndFontMetrics
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Creation Date: Thu Jun 21 22:23:51 1990
3
+ Comment UniqueID 5000832
4
+ FontName CMTT10
5
+ EncodingScheme FontSpecific
6
+ FullName CMTT10
7
+ FamilyName Computer Modern
8
+ Weight Medium
9
+ ItalicAngle 0.0
10
+ IsFixedPitch true
11
+ Version 1.00B
12
+ Notice Copyright (c) 1997 American Mathematical Society. All Rights Reserved.
13
+ Comment Computer Modern fonts were designed by Donald E. Knuth
14
+ FontBBox -4 -235 731 800
15
+ CapHeight 611.111
16
+ XHeight 430.556
17
+ Ascender 611.111
18
+ Descender -222.222
19
+ Comment FontID CMTT
20
+ Comment DesignSize 10 (pts)
21
+ Comment CharacterCodingScheme TeX typewriter text
22
+ Comment Space 525 0 0
23
+ Comment ExtraSpace 525
24
+ Comment Quad 1050
25
+ StartCharMetrics 129
26
+ C 0 ; WX 525 ; N Gamma ; B 32 0 488 611 ;
27
+ C 1 ; WX 525 ; N Delta ; B 34 0 490 623 ;
28
+ C 2 ; WX 525 ; N Theta ; B 56 -11 468 622 ;
29
+ C 3 ; WX 525 ; N Lambda ; B 29 0 495 623 ;
30
+ C 4 ; WX 525 ; N Xi ; B 33 0 491 611 ;
31
+ C 5 ; WX 525 ; N Pi ; B 22 0 502 611 ;
32
+ C 6 ; WX 525 ; N Sigma ; B 40 0 484 611 ;
33
+ C 7 ; WX 525 ; N Upsilon ; B 38 0 486 622 ;
34
+ C 8 ; WX 525 ; N Phi ; B 40 0 484 611 ;
35
+ C 9 ; WX 525 ; N Psi ; B 38 0 486 611 ;
36
+ C 10 ; WX 525 ; N Omega ; B 32 0 492 622 ;
37
+ C 11 ; WX 525 ; N arrowup ; B 59 0 465 611 ;
38
+ C 12 ; WX 525 ; N arrowdown ; B 59 0 465 611 ;
39
+ C 13 ; WX 525 ; N quotesingle ; B 217 328 309 622 ;
40
+ C 14 ; WX 525 ; N exclamdown ; B 212 -233 312 389 ;
41
+ C 15 ; WX 525 ; N questiondown ; B 62 -228 462 389 ;
42
+ C 16 ; WX 525 ; N dotlessi ; B 78 0 455 431 ;
43
+ C 17 ; WX 525 ; N dotlessj ; B 48 -228 368 431 ;
44
+ C 18 ; WX 525 ; N grave ; B 117 477 329 611 ;
45
+ C 19 ; WX 525 ; N acute ; B 195 477 407 611 ;
46
+ C 20 ; WX 525 ; N caron ; B 101 454 423 572 ;
47
+ C 21 ; WX 525 ; N breve ; B 86 498 438 611 ;
48
+ C 22 ; WX 525 ; N macron ; B 73 514 451 577 ;
49
+ C 23 ; WX 525 ; N ring ; B 181 499 343 619 ;
50
+ C 24 ; WX 525 ; N cedilla ; B 162 -208 428 45 ;
51
+ C 25 ; WX 525 ; N germandbls ; B 17 -6 495 617 ;
52
+ C 26 ; WX 525 ; N ae ; B 33 -6 504 440 ;
53
+ C 27 ; WX 525 ; N oe ; B 19 -6 505 440 ;
54
+ C 28 ; WX 525 ; N oslash ; B 43 -140 481 571 ;
55
+ C 29 ; WX 525 ; N AE ; B 23 0 499 611 ;
56
+ C 30 ; WX 525 ; N OE ; B 29 -11 502 622 ;
57
+ C 31 ; WX 525 ; N Oslash ; B 56 -85 468 696 ;
58
+ C 32 ; WX 525 ; N visiblespace ; B 44 -132 480 240 ;
59
+ C 33 ; WX 525 ; N exclam ; B 212 0 312 622 ; L quoteleft exclamdown ;
60
+ C 34 ; WX 525 ; N quotedbl ; B 126 328 398 622 ;
61
+ C 35 ; WX 525 ; N numbersign ; B 35 0 489 611 ;
62
+ C 36 ; WX 525 ; N dollar ; B 58 -83 466 694 ;
63
+ C 37 ; WX 525 ; N percent ; B 35 -83 489 694 ;
64
+ C 38 ; WX 525 ; N ampersand ; B 28 -11 490 622 ;
65
+ C 39 ; WX 525 ; N quoteright ; B 180 302 341 611 ;
66
+ C 40 ; WX 525 ; N parenleft ; B 173 -82 437 694 ;
67
+ C 41 ; WX 525 ; N parenright ; B 88 -82 352 694 ;
68
+ C 42 ; WX 525 ; N asterisk ; B 68 90 456 521 ;
69
+ C 43 ; WX 525 ; N plus ; B 38 81 486 531 ;
70
+ C 44 ; WX 525 ; N comma ; B 180 -139 346 125 ;
71
+ C 45 ; WX 525 ; N hyphen ; B 56 271 468 341 ;
72
+ C 46 ; WX 525 ; N period ; B 200 0 325 125 ;
73
+ C 47 ; WX 525 ; N slash ; B 58 -83 466 694 ;
74
+ C 48 ; WX 525 ; N zero ; B 50 -11 474 622 ;
75
+ C 49 ; WX 525 ; N one ; B 105 0 442 622 ;
76
+ C 50 ; WX 525 ; N two ; B 52 0 472 622 ;
77
+ C 51 ; WX 525 ; N three ; B 44 -11 480 622 ;
78
+ C 52 ; WX 525 ; N four ; B 29 0 495 623 ;
79
+ C 53 ; WX 525 ; N five ; B 52 -11 472 611 ;
80
+ C 54 ; WX 525 ; N six ; B 53 -11 471 622 ;
81
+ C 55 ; WX 525 ; N seven ; B 44 -11 480 627 ;
82
+ C 56 ; WX 525 ; N eight ; B 44 -11 480 622 ;
83
+ C 57 ; WX 525 ; N nine ; B 53 -11 471 622 ;
84
+ C 58 ; WX 525 ; N colon ; B 200 0 325 431 ;
85
+ C 59 ; WX 525 ; N semicolon ; B 180 -139 330 431 ;
86
+ C 60 ; WX 525 ; N less ; B 56 56 468 556 ;
87
+ C 61 ; WX 525 ; N equal ; B 38 195 486 417 ;
88
+ C 62 ; WX 525 ; N greater ; B 56 56 468 556 ;
89
+ C 63 ; WX 525 ; N question ; B 62 0 462 617 ; L quoteleft questiondown ;
90
+ C 64 ; WX 525 ; N at ; B 44 -6 480 617 ;
91
+ C 65 ; WX 525 ; N A ; B 27 0 497 623 ;
92
+ C 66 ; WX 525 ; N B ; B 23 0 482 611 ;
93
+ C 67 ; WX 525 ; N C ; B 40 -11 484 622 ;
94
+ C 68 ; WX 525 ; N D ; B 19 0 485 611 ;
95
+ C 69 ; WX 525 ; N E ; B 26 0 502 611 ;
96
+ C 70 ; WX 525 ; N F ; B 28 0 490 611 ;
97
+ C 71 ; WX 525 ; N G ; B 38 -11 496 622 ;
98
+ C 72 ; WX 525 ; N H ; B 22 0 502 611 ;
99
+ C 73 ; WX 525 ; N I ; B 79 0 446 611 ;
100
+ C 74 ; WX 525 ; N J ; B 71 -11 478 611 ;
101
+ C 75 ; WX 525 ; N K ; B 26 0 495 611 ;
102
+ C 76 ; WX 525 ; N L ; B 32 0 488 611 ;
103
+ C 77 ; WX 525 ; N M ; B 17 0 507 611 ;
104
+ C 78 ; WX 525 ; N N ; B 28 0 496 611 ;
105
+ C 79 ; WX 525 ; N O ; B 56 -11 468 622 ;
106
+ C 80 ; WX 525 ; N P ; B 26 0 480 611 ;
107
+ C 81 ; WX 525 ; N Q ; B 56 -139 468 622 ;
108
+ C 82 ; WX 525 ; N R ; B 22 -11 522 611 ;
109
+ C 83 ; WX 525 ; N S ; B 52 -11 472 622 ;
110
+ C 84 ; WX 525 ; N T ; B 26 0 498 611 ;
111
+ C 85 ; WX 525 ; N U ; B 4 -11 520 611 ;
112
+ C 86 ; WX 525 ; N V ; B 18 -8 506 611 ;
113
+ C 87 ; WX 525 ; N W ; B 11 -8 513 611 ;
114
+ C 88 ; WX 525 ; N X ; B 27 0 496 611 ;
115
+ C 89 ; WX 525 ; N Y ; B 19 0 505 611 ;
116
+ C 90 ; WX 525 ; N Z ; B 48 0 481 611 ;
117
+ C 91 ; WX 525 ; N bracketleft ; B 222 -83 483 694 ;
118
+ C 92 ; WX 525 ; N backslash ; B 58 -83 466 694 ;
119
+ C 93 ; WX 525 ; N bracketright ; B 41 -83 302 694 ;
120
+ C 94 ; WX 525 ; N asciicircum ; B 100 471 424 611 ;
121
+ C 95 ; WX 525 ; N underscore ; B 56 -95 468 -25 ;
122
+ C 96 ; WX 525 ; N quoteleft ; B 183 372 344 681 ;
123
+ C 97 ; WX 525 ; N a ; B 55 -6 524 440 ;
124
+ C 98 ; WX 525 ; N b ; B 12 -6 488 611 ;
125
+ C 99 ; WX 525 ; N c ; B 73 -6 466 440 ;
126
+ C 100 ; WX 525 ; N d ; B 36 -6 512 611 ;
127
+ C 101 ; WX 525 ; N e ; B 55 -6 464 440 ;
128
+ C 102 ; WX 525 ; N f ; B 42 0 437 617 ;
129
+ C 103 ; WX 525 ; N g ; B 29 -229 509 442 ;
130
+ C 104 ; WX 525 ; N h ; B 12 0 512 611 ;
131
+ C 105 ; WX 525 ; N i ; B 78 0 455 612 ;
132
+ C 106 ; WX 525 ; N j ; B 48 -228 368 612 ;
133
+ C 107 ; WX 525 ; N k ; B 21 0 508 611 ;
134
+ C 108 ; WX 525 ; N l ; B 58 0 467 611 ;
135
+ C 109 ; WX 525 ; N m ; B -4 0 516 437 ;
136
+ C 110 ; WX 525 ; N n ; B 12 0 512 437 ;
137
+ C 111 ; WX 525 ; N o ; B 57 -6 467 440 ;
138
+ C 112 ; WX 525 ; N p ; B 12 -222 488 437 ;
139
+ C 113 ; WX 525 ; N q ; B 40 -222 537 437 ;
140
+ C 114 ; WX 525 ; N r ; B 32 0 487 437 ;
141
+ C 115 ; WX 525 ; N s ; B 72 -6 459 440 ;
142
+ C 116 ; WX 525 ; N t ; B 25 -6 449 554 ;
143
+ C 117 ; WX 525 ; N u ; B 12 -6 512 431 ;
144
+ C 118 ; WX 525 ; N v ; B 24 -4 500 431 ;
145
+ C 119 ; WX 525 ; N w ; B 16 -4 508 431 ;
146
+ C 120 ; WX 525 ; N x ; B 27 0 496 431 ;
147
+ C 121 ; WX 525 ; N y ; B 26 -228 500 431 ;
148
+ C 122 ; WX 525 ; N z ; B 33 0 475 431 ;
149
+ C 123 ; WX 525 ; N braceleft ; B 57 -83 467 694 ;
150
+ C 124 ; WX 525 ; N bar ; B 227 -83 297 694 ;
151
+ C 125 ; WX 525 ; N braceright ; B 57 -83 467 694 ;
152
+ C 126 ; WX 525 ; N asciitilde ; B 87 491 437 611 ;
153
+ C 127 ; WX 525 ; N dieresis ; B 110 512 414 612 ;
154
+ C -1 ; WX 525 ; N space ; B 0 0 0 0 ;
155
+ EndCharMetrics
156
+ EndFontMetrics
outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/pagd8a.afm ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ StartFontMetrics 2.0
2
+ Comment Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.
3
+ Comment Creation Date: Mon Mar 4 13:46:34 1991
4
+ Comment UniqueID 34370
5
+ Comment VMusage 24954 31846
6
+ FontName AvantGarde-Demi
7
+ FullName ITC Avant Garde Gothic Demi
8
+ FamilyName ITC Avant Garde Gothic
9
+ Weight Demi
10
+ ItalicAngle 0
11
+ IsFixedPitch false
12
+ FontBBox -123 -251 1222 1021
13
+ UnderlinePosition -100
14
+ UnderlineThickness 50
15
+ Version 001.007
16
+ Notice Copyright (c) 1985, 1987, 1989, 1990, 1991 Adobe Systems Incorporated. All Rights Reserved.ITC Avant Garde Gothic is a registered trademark of International Typeface Corporation.
17
+ EncodingScheme AdobeStandardEncoding
18
+ CapHeight 740
19
+ XHeight 555
20
+ Ascender 740
21
+ Descender -185
22
+ StartCharMetrics 228
23
+ C 32 ; WX 280 ; N space ; B 0 0 0 0 ;
24
+ C 33 ; WX 280 ; N exclam ; B 73 0 206 740 ;
25
+ C 34 ; WX 360 ; N quotedbl ; B 19 444 341 740 ;
26
+ C 35 ; WX 560 ; N numbersign ; B 29 0 525 700 ;
27
+ C 36 ; WX 560 ; N dollar ; B 58 -86 501 857 ;
28
+ C 37 ; WX 860 ; N percent ; B 36 -15 822 755 ;
29
+ C 38 ; WX 680 ; N ampersand ; B 34 -15 665 755 ;
30
+ C 39 ; WX 280 ; N quoteright ; B 72 466 205 740 ;
31
+ C 40 ; WX 380 ; N parenleft ; B 74 -157 350 754 ;
32
+ C 41 ; WX 380 ; N parenright ; B 37 -157 313 754 ;
33
+ C 42 ; WX 440 ; N asterisk ; B 67 457 374 755 ;
34
+ C 43 ; WX 600 ; N plus ; B 48 0 552 506 ;
35
+ C 44 ; WX 280 ; N comma ; B 73 -141 206 133 ;
36
+ C 45 ; WX 420 ; N hyphen ; B 71 230 349 348 ;
37
+ C 46 ; WX 280 ; N period ; B 73 0 206 133 ;
38
+ C 47 ; WX 460 ; N slash ; B 6 -100 454 740 ;
39
+ C 48 ; WX 560 ; N zero ; B 32 -15 529 755 ;
40
+ C 49 ; WX 560 ; N one ; B 137 0 363 740 ;
41
+ C 50 ; WX 560 ; N two ; B 36 0 523 755 ;
42
+ C 51 ; WX 560 ; N three ; B 28 -15 532 755 ;
43
+ C 52 ; WX 560 ; N four ; B 15 0 545 740 ;
44
+ C 53 ; WX 560 ; N five ; B 25 -15 535 740 ;
45
+ C 54 ; WX 560 ; N six ; B 23 -15 536 739 ;
46
+ C 55 ; WX 560 ; N seven ; B 62 0 498 740 ;
47
+ C 56 ; WX 560 ; N eight ; B 33 -15 527 755 ;
48
+ C 57 ; WX 560 ; N nine ; B 24 0 537 754 ;
49
+ C 58 ; WX 280 ; N colon ; B 73 0 206 555 ;
50
+ C 59 ; WX 280 ; N semicolon ; B 73 -141 206 555 ;
51
+ C 60 ; WX 600 ; N less ; B 46 -8 554 514 ;
52
+ C 61 ; WX 600 ; N equal ; B 48 81 552 425 ;
53
+ C 62 ; WX 600 ; N greater ; B 46 -8 554 514 ;
54
+ C 63 ; WX 560 ; N question ; B 38 0 491 755 ;
55
+ C 64 ; WX 740 ; N at ; B 50 -12 750 712 ;
56
+ C 65 ; WX 740 ; N A ; B 7 0 732 740 ;
57
+ C 66 ; WX 580 ; N B ; B 70 0 551 740 ;
58
+ C 67 ; WX 780 ; N C ; B 34 -15 766 755 ;
59
+ C 68 ; WX 700 ; N D ; B 63 0 657 740 ;
60
+ C 69 ; WX 520 ; N E ; B 61 0 459 740 ;
61
+ C 70 ; WX 480 ; N F ; B 61 0 438 740 ;
62
+ C 71 ; WX 840 ; N G ; B 27 -15 817 755 ;
63
+ C 72 ; WX 680 ; N H ; B 71 0 610 740 ;
64
+ C 73 ; WX 280 ; N I ; B 72 0 209 740 ;
65
+ C 74 ; WX 480 ; N J ; B 2 -15 409 740 ;
66
+ C 75 ; WX 620 ; N K ; B 89 0 620 740 ;
67
+ C 76 ; WX 440 ; N L ; B 72 0 435 740 ;
68
+ C 77 ; WX 900 ; N M ; B 63 0 837 740 ;
69
+ C 78 ; WX 740 ; N N ; B 70 0 671 740 ;
70
+ C 79 ; WX 840 ; N O ; B 33 -15 807 755 ;
71
+ C 80 ; WX 560 ; N P ; B 72 0 545 740 ;
72
+ C 81 ; WX 840 ; N Q ; B 32 -15 824 755 ;
73
+ C 82 ; WX 580 ; N R ; B 64 0 565 740 ;
74
+ C 83 ; WX 520 ; N S ; B 12 -15 493 755 ;
75
+ C 84 ; WX 420 ; N T ; B 6 0 418 740 ;
76
+ C 85 ; WX 640 ; N U ; B 55 -15 585 740 ;
77
+ C 86 ; WX 700 ; N V ; B 8 0 695 740 ;
78
+ C 87 ; WX 900 ; N W ; B 7 0 899 740 ;
79
+ C 88 ; WX 680 ; N X ; B 4 0 676 740 ;
80
+ C 89 ; WX 620 ; N Y ; B -2 0 622 740 ;
81
+ C 90 ; WX 500 ; N Z ; B 19 0 481 740 ;
82
+ C 91 ; WX 320 ; N bracketleft ; B 66 -157 284 754 ;
83
+ C 92 ; WX 640 ; N backslash ; B 96 -100 544 740 ;
84
+ C 93 ; WX 320 ; N bracketright ; B 36 -157 254 754 ;
85
+ C 94 ; WX 600 ; N asciicircum ; B 73 375 527 740 ;
86
+ C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
87
+ C 96 ; WX 280 ; N quoteleft ; B 72 466 205 740 ;
88
+ C 97 ; WX 660 ; N a ; B 27 -18 613 574 ;
89
+ C 98 ; WX 660 ; N b ; B 47 -18 632 740 ;
90
+ C 99 ; WX 640 ; N c ; B 37 -18 610 574 ;
91
+ C 100 ; WX 660 ; N d ; B 34 -18 618 740 ;
92
+ C 101 ; WX 640 ; N e ; B 31 -18 610 577 ;
93
+ C 102 ; WX 280 ; N f ; B 15 0 280 755 ; L i fi ; L l fl ;
94
+ C 103 ; WX 660 ; N g ; B 32 -226 623 574 ;
95
+ C 104 ; WX 600 ; N h ; B 54 0 546 740 ;
96
+ C 105 ; WX 240 ; N i ; B 53 0 186 740 ;
97
+ C 106 ; WX 260 ; N j ; B 16 -185 205 740 ;
98
+ C 107 ; WX 580 ; N k ; B 80 0 571 740 ;
99
+ C 108 ; WX 240 ; N l ; B 54 0 187 740 ;
100
+ C 109 ; WX 940 ; N m ; B 54 0 887 574 ;
101
+ C 110 ; WX 600 ; N n ; B 54 0 547 574 ;
102
+ C 111 ; WX 640 ; N o ; B 25 -18 615 574 ;
103
+ C 112 ; WX 660 ; N p ; B 47 -185 629 574 ;
104
+ C 113 ; WX 660 ; N q ; B 31 -185 613 574 ;
105
+ C 114 ; WX 320 ; N r ; B 63 0 317 574 ;
106
+ C 115 ; WX 440 ; N s ; B 19 -18 421 574 ;
107
+ C 116 ; WX 300 ; N t ; B 21 0 299 740 ;
108
+ C 117 ; WX 600 ; N u ; B 50 -18 544 555 ;
109
+ C 118 ; WX 560 ; N v ; B 3 0 556 555 ;
110
+ C 119 ; WX 800 ; N w ; B 11 0 789 555 ;
111
+ C 120 ; WX 560 ; N x ; B 3 0 556 555 ;
112
+ C 121 ; WX 580 ; N y ; B 8 -185 571 555 ;
113
+ C 122 ; WX 460 ; N z ; B 20 0 442 555 ;
114
+ C 123 ; WX 340 ; N braceleft ; B -3 -191 317 747 ;
115
+ C 124 ; WX 600 ; N bar ; B 233 -100 366 740 ;
116
+ C 125 ; WX 340 ; N braceright ; B 23 -191 343 747 ;
117
+ C 126 ; WX 600 ; N asciitilde ; B 67 160 533 347 ;
118
+ C 161 ; WX 280 ; N exclamdown ; B 74 -185 207 555 ;
119
+ C 162 ; WX 560 ; N cent ; B 43 39 517 715 ;
120
+ C 163 ; WX 560 ; N sterling ; B -2 0 562 755 ;
121
+ C 164 ; WX 160 ; N fraction ; B -123 0 282 740 ;
122
+ C 165 ; WX 560 ; N yen ; B -10 0 570 740 ;
123
+ C 166 ; WX 560 ; N florin ; B 0 -151 512 824 ;
124
+ C 167 ; WX 560 ; N section ; B 28 -158 530 755 ;
125
+ C 168 ; WX 560 ; N currency ; B 27 69 534 577 ;
126
+ C 169 ; WX 220 ; N quotesingle ; B 44 444 177 740 ;
127
+ C 170 ; WX 480 ; N quotedblleft ; B 70 466 410 740 ;
128
+ C 171 ; WX 460 ; N guillemotleft ; B 61 108 400 469 ;
129
+ C 172 ; WX 240 ; N guilsinglleft ; B 50 108 190 469 ;
130
+ C 173 ; WX 240 ; N guilsinglright ; B 50 108 190 469 ;
131
+ C 174 ; WX 520 ; N fi ; B 25 0 461 755 ;
132
+ C 175 ; WX 520 ; N fl ; B 25 0 461 755 ;
133
+ C 177 ; WX 500 ; N endash ; B 35 230 465 348 ;
134
+ C 178 ; WX 560 ; N dagger ; B 51 -142 509 740 ;
135
+ C 179 ; WX 560 ; N daggerdbl ; B 51 -142 509 740 ;
136
+ C 180 ; WX 280 ; N periodcentered ; B 73 187 206 320 ;
137
+ C 182 ; WX 600 ; N paragraph ; B -7 -103 607 740 ;
138
+ C 183 ; WX 600 ; N bullet ; B 148 222 453 532 ;
139
+ C 184 ; WX 280 ; N quotesinglbase ; B 72 -141 205 133 ;
140
+ C 185 ; WX 480 ; N quotedblbase ; B 70 -141 410 133 ;
141
+ C 186 ; WX 480 ; N quotedblright ; B 70 466 410 740 ;
142
+ C 187 ; WX 460 ; N guillemotright ; B 61 108 400 469 ;
143
+ C 188 ; WX 1000 ; N ellipsis ; B 100 0 899 133 ;
144
+ C 189 ; WX 1280 ; N perthousand ; B 36 -15 1222 755 ;
145
+ C 191 ; WX 560 ; N questiondown ; B 68 -200 521 555 ;
146
+ C 193 ; WX 420 ; N grave ; B 50 624 329 851 ;
147
+ C 194 ; WX 420 ; N acute ; B 91 624 370 849 ;
148
+ C 195 ; WX 540 ; N circumflex ; B 71 636 470 774 ;
149
+ C 196 ; WX 480 ; N tilde ; B 44 636 437 767 ;
150
+ C 197 ; WX 420 ; N macron ; B 72 648 349 759 ;
151
+ C 198 ; WX 480 ; N breve ; B 42 633 439 770 ;
152
+ C 199 ; WX 280 ; N dotaccent ; B 74 636 207 769 ;
153
+ C 200 ; WX 500 ; N dieresis ; B 78 636 422 769 ;
154
+ C 202 ; WX 360 ; N ring ; B 73 619 288 834 ;
155
+ C 203 ; WX 340 ; N cedilla ; B 98 -251 298 6 ;
156
+ C 205 ; WX 700 ; N hungarumlaut ; B 132 610 609 862 ;
157
+ C 206 ; WX 340 ; N ogonek ; B 79 -195 262 9 ;
158
+ C 207 ; WX 540 ; N caron ; B 71 636 470 774 ;
159
+ C 208 ; WX 1000 ; N emdash ; B 35 230 965 348 ;
160
+ C 225 ; WX 900 ; N AE ; B -5 0 824 740 ;
161
+ C 227 ; WX 360 ; N ordfeminine ; B 19 438 334 755 ;
162
+ C 232 ; WX 480 ; N Lslash ; B 26 0 460 740 ;
163
+ C 233 ; WX 840 ; N Oslash ; B 33 -71 807 814 ;
164
+ C 234 ; WX 1060 ; N OE ; B 37 -15 1007 755 ;
165
+ C 235 ; WX 360 ; N ordmasculine ; B 23 438 338 755 ;
166
+ C 241 ; WX 1080 ; N ae ; B 29 -18 1048 574 ;
167
+ C 245 ; WX 240 ; N dotlessi ; B 53 0 186 555 ;
168
+ C 248 ; WX 320 ; N lslash ; B 34 0 305 740 ;
169
+ C 249 ; WX 660 ; N oslash ; B 35 -50 625 608 ;
170
+ C 250 ; WX 1080 ; N oe ; B 30 -18 1050 574 ;
171
+ C 251 ; WX 600 ; N germandbls ; B 51 -18 585 755 ;
172
+ C -1 ; WX 640 ; N ecircumflex ; B 31 -18 610 774 ;
173
+ C -1 ; WX 640 ; N edieresis ; B 31 -18 610 769 ;
174
+ C -1 ; WX 660 ; N aacute ; B 27 -18 613 849 ;
175
+ C -1 ; WX 740 ; N registered ; B -12 -12 752 752 ;
176
+ C -1 ; WX 240 ; N icircumflex ; B -79 0 320 774 ;
177
+ C -1 ; WX 600 ; N udieresis ; B 50 -18 544 769 ;
178
+ C -1 ; WX 640 ; N ograve ; B 25 -18 615 851 ;
179
+ C -1 ; WX 600 ; N uacute ; B 50 -18 544 849 ;
180
+ C -1 ; WX 600 ; N ucircumflex ; B 50 -18 544 774 ;
181
+ C -1 ; WX 740 ; N Aacute ; B 7 0 732 1019 ;
182
+ C -1 ; WX 240 ; N igrave ; B -65 0 214 851 ;
183
+ C -1 ; WX 280 ; N Icircumflex ; B -59 0 340 944 ;
184
+ C -1 ; WX 640 ; N ccedilla ; B 37 -251 610 574 ;
185
+ C -1 ; WX 660 ; N adieresis ; B 27 -18 613 769 ;
186
+ C -1 ; WX 520 ; N Ecircumflex ; B 61 0 460 944 ;
187
+ C -1 ; WX 440 ; N scaron ; B 19 -18 421 774 ;
188
+ C -1 ; WX 660 ; N thorn ; B 47 -185 629 740 ;
189
+ C -1 ; WX 1000 ; N trademark ; B 9 296 821 740 ;
190
+ C -1 ; WX 640 ; N egrave ; B 31 -18 610 851 ;
191
+ C -1 ; WX 336 ; N threesuperior ; B 8 287 328 749 ;
192
+ C -1 ; WX 460 ; N zcaron ; B 20 0 455 774 ;
193
+ C -1 ; WX 660 ; N atilde ; B 27 -18 613 767 ;
194
+ C -1 ; WX 660 ; N aring ; B 27 -18 613 834 ;
195
+ C -1 ; WX 640 ; N ocircumflex ; B 25 -18 615 774 ;
196
+ C -1 ; WX 520 ; N Edieresis ; B 61 0 459 939 ;
197
+ C -1 ; WX 840 ; N threequarters ; B 18 0 803 749 ;
198
+ C -1 ; WX 580 ; N ydieresis ; B 8 -185 571 769 ;
199
+ C -1 ; WX 580 ; N yacute ; B 8 -185 571 849 ;
200
+ C -1 ; WX 240 ; N iacute ; B 26 0 305 849 ;
201
+ C -1 ; WX 740 ; N Acircumflex ; B 7 0 732 944 ;
202
+ C -1 ; WX 640 ; N Uacute ; B 55 -15 585 1019 ;
203
+ C -1 ; WX 640 ; N eacute ; B 31 -18 610 849 ;
204
+ C -1 ; WX 840 ; N Ograve ; B 33 -15 807 1021 ;
205
+ C -1 ; WX 660 ; N agrave ; B 27 -18 613 851 ;
206
+ C -1 ; WX 640 ; N Udieresis ; B 55 -15 585 939 ;
207
+ C -1 ; WX 660 ; N acircumflex ; B 27 -18 613 774 ;
208
+ C -1 ; WX 280 ; N Igrave ; B -45 0 234 1021 ;
209
+ C -1 ; WX 336 ; N twosuperior ; B 13 296 322 749 ;
210
+ C -1 ; WX 640 ; N Ugrave ; B 55 -15 585 1021 ;
211
+ C -1 ; WX 840 ; N onequarter ; B 92 0 746 740 ;
212
+ C -1 ; WX 640 ; N Ucircumflex ; B 55 -15 585 944 ;
213
+ C -1 ; WX 520 ; N Scaron ; B 12 -15 493 944 ;
214
+ C -1 ; WX 280 ; N Idieresis ; B -32 0 312 939 ;
215
+ C -1 ; WX 240 ; N idieresis ; B -52 0 292 769 ;
216
+ C -1 ; WX 520 ; N Egrave ; B 61 0 459 1021 ;
217
+ C -1 ; WX 840 ; N Oacute ; B 33 -15 807 1019 ;
218
+ C -1 ; WX 600 ; N divide ; B 48 -20 552 526 ;
219
+ C -1 ; WX 740 ; N Atilde ; B 7 0 732 937 ;
220
+ C -1 ; WX 740 ; N Aring ; B 7 0 732 969 ;
221
+ C -1 ; WX 840 ; N Odieresis ; B 33 -15 807 939 ;
222
+ C -1 ; WX 740 ; N Adieresis ; B 7 0 732 939 ;
223
+ C -1 ; WX 740 ; N Ntilde ; B 70 0 671 937 ;
224
+ C -1 ; WX 500 ; N Zcaron ; B 19 0 481 944 ;
225
+ C -1 ; WX 560 ; N Thorn ; B 72 0 545 740 ;
226
+ C -1 ; WX 280 ; N Iacute ; B 46 0 325 1019 ;
227
+ C -1 ; WX 600 ; N plusminus ; B 48 -62 552 556 ;
228
+ C -1 ; WX 600 ; N multiply ; B 59 12 541 494 ;
229
+ C -1 ; WX 520 ; N Eacute ; B 61 0 459 1019 ;
230
+ C -1 ; WX 620 ; N Ydieresis ; B -2 0 622 939 ;
231
+ C -1 ; WX 336 ; N onesuperior ; B 72 296 223 740 ;
232
+ C -1 ; WX 600 ; N ugrave ; B 50 -18 544 851 ;
233
+ C -1 ; WX 600 ; N logicalnot ; B 48 108 552 425 ;
234
+ C -1 ; WX 600 ; N ntilde ; B 54 0 547 767 ;
235
+ C -1 ; WX 840 ; N Otilde ; B 33 -15 807 937 ;
236
+ C -1 ; WX 640 ; N otilde ; B 25 -18 615 767 ;
237
+ C -1 ; WX 780 ; N Ccedilla ; B 34 -251 766 755 ;
238
+ C -1 ; WX 740 ; N Agrave ; B 7 0 732 1021 ;
239
+ C -1 ; WX 840 ; N onehalf ; B 62 0 771 740 ;
240
+ C -1 ; WX 742 ; N Eth ; B 25 0 691 740 ;
241
+ C -1 ; WX 400 ; N degree ; B 57 426 343 712 ;
242
+ C -1 ; WX 620 ; N Yacute ; B -2 0 622 1019 ;
243
+ C -1 ; WX 840 ; N Ocircumflex ; B 33 -15 807 944 ;
244
+ C -1 ; WX 640 ; N oacute ; B 25 -18 615 849 ;
245
+ C -1 ; WX 576 ; N mu ; B 38 -187 539 555 ;
246
+ C -1 ; WX 600 ; N minus ; B 48 193 552 313 ;
247
+ C -1 ; WX 640 ; N eth ; B 27 -18 616 754 ;
248
+ C -1 ; WX 640 ; N odieresis ; B 25 -18 615 769 ;
249
+ C -1 ; WX 740 ; N copyright ; B -12 -12 752 752 ;
250
+ C -1 ; WX 600 ; N brokenbar ; B 233 -100 366 740 ;
251
+ EndCharMetrics
252
+ StartKernData
253
+ StartKernPairs 218
254
+
255
+ KPX A y -50
256
+ KPX A w -65
257
+ KPX A v -70
258
+ KPX A u -20
259
+ KPX A quoteright -90
260
+ KPX A Y -80
261
+ KPX A W -60
262
+ KPX A V -102
263
+ KPX A U -40
264
+ KPX A T -25
265
+ KPX A Q -50
266
+ KPX A O -50
267
+ KPX A G -40
268
+ KPX A C -40
269
+
270
+ KPX B A -10
271
+
272
+ KPX C A -40
273
+
274
+ KPX D period -20
275
+ KPX D comma -20
276
+ KPX D Y -45
277
+ KPX D W -25
278
+ KPX D V -50
279
+ KPX D A -50
280
+
281
+ KPX F period -129
282
+ KPX F e -20
283
+ KPX F comma -162
284
+ KPX F a -20
285
+ KPX F A -75
286
+
287
+ KPX G period -20
288
+ KPX G comma -20
289
+ KPX G Y -15
290
+
291
+ KPX J period -15
292
+ KPX J a -20
293
+ KPX J A -30
294
+
295
+ KPX K y -20
296
+ KPX K u -15
297
+ KPX K o -45
298
+ KPX K e -40
299
+ KPX K O -30
300
+
301
+ KPX L y -23
302
+ KPX L quoteright -30
303
+ KPX L quotedblright -30
304
+ KPX L Y -80
305
+ KPX L W -55
306
+ KPX L V -85
307
+ KPX L T -46
308
+
309
+ KPX O period -30
310
+ KPX O comma -30
311
+ KPX O Y -30
312
+ KPX O X -30
313
+ KPX O W -20
314
+ KPX O V -45
315
+ KPX O T -15
316
+ KPX O A -60
317
+
318
+ KPX P period -200
319
+ KPX P o -20
320
+ KPX P e -20
321
+ KPX P comma -220
322
+ KPX P a -20
323
+ KPX P A -100
324
+
325
+ KPX Q comma 20
326
+
327
+ KPX R W 25
328
+ KPX R V -10
329
+ KPX R U 25
330
+ KPX R T 40
331
+ KPX R O 25
332
+
333
+ KPX S comma 20
334
+
335
+ KPX T y -10
336
+ KPX T w -55
337
+ KPX T u -46
338
+ KPX T semicolon -29
339
+ KPX T r -30
340
+ KPX T period -91
341
+ KPX T o -49
342
+ KPX T hyphen -75
343
+ KPX T e -49
344
+ KPX T comma -82
345
+ KPX T colon -15
346
+ KPX T a -70
347
+ KPX T O -15
348
+ KPX T A -25
349
+
350
+ KPX U period -20
351
+ KPX U comma -20
352
+ KPX U A -40
353
+
354
+ KPX V u -55
355
+ KPX V semicolon -33
356
+ KPX V period -145
357
+ KPX V o -101
358
+ KPX V i -15
359
+ KPX V hyphen -75
360
+ KPX V e -101
361
+ KPX V comma -145
362
+ KPX V colon -18
363
+ KPX V a -95
364
+ KPX V O -45
365
+ KPX V G -20
366
+ KPX V A -102
367
+
368
+ KPX W y -15
369
+ KPX W u -30
370
+ KPX W semicolon -33
371
+ KPX W period -106
372
+ KPX W o -46
373
+ KPX W i -10
374
+ KPX W hyphen -35
375
+ KPX W e -47
376
+ KPX W comma -106
377
+ KPX W colon -15
378
+ KPX W a -50
379
+ KPX W O -20
380
+ KPX W A -58
381
+
382
+ KPX Y u -52
383
+ KPX Y semicolon -23
384
+ KPX Y period -145
385
+ KPX Y o -89
386
+ KPX Y hyphen -100
387
+ KPX Y e -89
388
+ KPX Y comma -145
389
+ KPX Y colon -10
390
+ KPX Y a -93
391
+ KPX Y O -30
392
+ KPX Y A -80
393
+
394
+ KPX a t 5
395
+ KPX a p 20
396
+ KPX a b 5
397
+
398
+ KPX b y -20
399
+ KPX b v -20
400
+
401
+ KPX c y -20
402
+ KPX c l -15
403
+ KPX c k -15
404
+
405
+ KPX comma space -50
406
+ KPX comma quoteright -70
407
+ KPX comma quotedblright -70
408
+
409
+ KPX e y -20
410
+ KPX e x -20
411
+ KPX e w -20
412
+ KPX e v -20
413
+
414
+ KPX f period -40
415
+ KPX f o -20
416
+ KPX f l -15
417
+ KPX f i -15
418
+ KPX f f -20
419
+ KPX f dotlessi -15
420
+ KPX f comma -40
421
+ KPX f a -15
422
+
423
+ KPX g i 25
424
+ KPX g a 15
425
+
426
+ KPX h y -30
427
+
428
+ KPX k y -5
429
+ KPX k o -30
430
+ KPX k e -40
431
+
432
+ KPX m y -20
433
+ KPX m u -20
434
+
435
+ KPX n y -15
436
+ KPX n v -30
437
+
438
+ KPX o y -20
439
+ KPX o x -30
440
+ KPX o w -20
441
+ KPX o v -30
442
+
443
+ KPX p y -20
444
+
445
+ KPX period space -50
446
+ KPX period quoteright -70
447
+ KPX period quotedblright -70
448
+
449
+ KPX quotedblleft A -50
450
+
451
+ KPX quotedblright space -50
452
+
453
+ KPX quoteleft quoteleft -80
454
+ KPX quoteleft A -50
455
+
456
+ KPX quoteright v -10
457
+ KPX quoteright t 10
458
+ KPX quoteright space -50
459
+ KPX quoteright s -15
460
+ KPX quoteright r -20
461
+ KPX quoteright quoteright -80
462
+ KPX quoteright d -50
463
+
464
+ KPX r y 40
465
+ KPX r v 40
466
+ KPX r u 20
467
+ KPX r t 20
468
+ KPX r s 20
469
+ KPX r q -8
470
+ KPX r period -73
471
+ KPX r p 20
472
+ KPX r o -15
473
+ KPX r n 21
474
+ KPX r m 15
475
+ KPX r l 20
476
+ KPX r k 5
477
+ KPX r i 20
478
+ KPX r hyphen -60
479
+ KPX r g 1
480
+ KPX r e -4
481
+ KPX r d -6
482
+ KPX r comma -75
483
+ KPX r c -7
484
+
485
+ KPX s period 20
486
+ KPX s comma 20
487
+
488
+ KPX space quoteleft -50
489
+ KPX space quotedblleft -50
490
+ KPX space Y -60
491
+ KPX space W -25
492
+ KPX space V -80
493
+ KPX space T -25
494
+ KPX space A -20
495
+
496
+ KPX v period -90
497
+ KPX v o -20
498
+ KPX v e -20
499
+ KPX v comma -90
500
+ KPX v a -30
501
+
502
+ KPX w period -90
503
+ KPX w o -30
504
+ KPX w e -20
505
+ KPX w comma -90
506
+ KPX w a -30
507
+
508
+ KPX x e -20
509
+
510
+ KPX y period -100
511
+ KPX y o -30
512
+ KPX y e -20
513
+ KPX y comma -100
514
+ KPX y c -35
515
+ KPX y a -30
516
+ EndKernPairs
517
+ EndKernData
518
+ StartComposites 56
519
+ CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
520
+ CC Acircumflex 2 ; PCC A 0 0 ; PCC circumflex 100 170 ;
521
+ CC Adieresis 2 ; PCC A 0 0 ; PCC dieresis 120 170 ;
522
+ CC Agrave 2 ; PCC A 0 0 ; PCC grave 160 170 ;
523
+ CC Aring 2 ; PCC A 0 0 ; PCC ring 190 135 ;
524
+ CC Atilde 2 ; PCC A 0 0 ; PCC tilde 130 170 ;
525
+ CC Eacute 2 ; PCC E 0 0 ; PCC acute 50 170 ;
526
+ CC Ecircumflex 2 ; PCC E 0 0 ; PCC circumflex -10 170 ;
527
+ CC Edieresis 2 ; PCC E 0 0 ; PCC dieresis 10 170 ;
528
+ CC Egrave 2 ; PCC E 0 0 ; PCC grave 50 170 ;
529
+ CC Iacute 2 ; PCC I 0 0 ; PCC acute -45 170 ;
530
+ CC Icircumflex 2 ; PCC I 0 0 ; PCC circumflex -130 170 ;
531
+ CC Idieresis 2 ; PCC I 0 0 ; PCC dieresis -110 170 ;
532
+ CC Igrave 2 ; PCC I 0 0 ; PCC grave -95 170 ;
533
+ CC Ntilde 2 ; PCC N 0 0 ; PCC tilde 130 170 ;
534
+ CC Oacute 2 ; PCC O 0 0 ; PCC acute 210 170 ;
535
+ CC Ocircumflex 2 ; PCC O 0 0 ; PCC circumflex 150 170 ;
536
+ CC Odieresis 2 ; PCC O 0 0 ; PCC dieresis 170 170 ;
537
+ CC Ograve 2 ; PCC O 0 0 ; PCC grave 210 170 ;
538
+ CC Otilde 2 ; PCC O 0 0 ; PCC tilde 180 170 ;
539
+ CC Scaron 2 ; PCC S 0 0 ; PCC caron -10 170 ;
540
+ CC Uacute 2 ; PCC U 0 0 ; PCC acute 145 170 ;
541
+ CC Ucircumflex 2 ; PCC U 0 0 ; PCC circumflex 50 170 ;
542
+ CC Udieresis 2 ; PCC U 0 0 ; PCC dieresis 70 170 ;
543
+ CC Ugrave 2 ; PCC U 0 0 ; PCC grave 75 170 ;
544
+ CC Yacute 2 ; PCC Y 0 0 ; PCC acute 135 170 ;
545
+ CC Ydieresis 2 ; PCC Y 0 0 ; PCC dieresis 60 170 ;
546
+ CC Zcaron 2 ; PCC Z 0 0 ; PCC caron 5 170 ;
547
+ CC aacute 2 ; PCC a 0 0 ; PCC acute 120 0 ;
548
+ CC acircumflex 2 ; PCC a 0 0 ; PCC circumflex 60 0 ;
549
+ CC adieresis 2 ; PCC a 0 0 ; PCC dieresis 80 0 ;
550
+ CC agrave 2 ; PCC a 0 0 ; PCC grave 120 0 ;
551
+ CC aring 2 ; PCC a 0 0 ; PCC ring 150 0 ;
552
+ CC atilde 2 ; PCC a 0 0 ; PCC tilde 90 0 ;
553
+ CC eacute 2 ; PCC e 0 0 ; PCC acute 110 0 ;
554
+ CC ecircumflex 2 ; PCC e 0 0 ; PCC circumflex 50 0 ;
555
+ CC edieresis 2 ; PCC e 0 0 ; PCC dieresis 70 0 ;
556
+ CC egrave 2 ; PCC e 0 0 ; PCC grave 110 0 ;
557
+ CC iacute 2 ; PCC dotlessi 0 0 ; PCC acute -65 0 ;
558
+ CC icircumflex 2 ; PCC dotlessi 0 0 ; PCC circumflex -150 0 ;
559
+ CC idieresis 2 ; PCC dotlessi 0 0 ; PCC dieresis -130 0 ;
560
+ CC igrave 2 ; PCC dotlessi 0 0 ; PCC grave -115 0 ;
561
+ CC ntilde 2 ; PCC n 0 0 ; PCC tilde 60 0 ;
562
+ CC oacute 2 ; PCC o 0 0 ; PCC acute 110 0 ;
563
+ CC ocircumflex 2 ; PCC o 0 0 ; PCC circumflex 50 0 ;
564
+ CC odieresis 2 ; PCC o 0 0 ; PCC dieresis 70 0 ;
565
+ CC ograve 2 ; PCC o 0 0 ; PCC grave 110 0 ;
566
+ CC otilde 2 ; PCC o 0 0 ; PCC tilde 80 0 ;
567
+ CC scaron 2 ; PCC s 0 0 ; PCC caron -50 0 ;
568
+ CC uacute 2 ; PCC u 0 0 ; PCC acute 125 0 ;
569
+ CC ucircumflex 2 ; PCC u 0 0 ; PCC circumflex 30 0 ;
570
+ CC udieresis 2 ; PCC u 0 0 ; PCC dieresis 50 0 ;
571
+ CC ugrave 2 ; PCC u 0 0 ; PCC grave 55 0 ;
572
+ CC yacute 2 ; PCC y 0 0 ; PCC acute 115 0 ;
573
+ CC ydieresis 2 ; PCC y 0 0 ; PCC dieresis 40 0 ;
574
+ CC zcaron 2 ; PCC z 0 0 ; PCC caron -15 0 ;
575
+ EndComposites
576
+ EndFontMetrics