Auto-sync: 2026-06-25 22:43:25 (part 6)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +17 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.py +2573 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/collections.pyi +262 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.py +1563 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorbar.pyi +139 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.py +703 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colorizer.pyi +102 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.py +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/colors.pyi +449 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.py +141 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/container.pyi +56 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.py +1702 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/contour.pyi +140 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dates.py +1840 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.py +1139 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/dviread.pyi +89 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.py +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/figure.pyi +428 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.py +1645 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/font_manager.pyi +136 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so +3 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/ft2font.pyi +312 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.py +788 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/gridspec.pyi +160 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.py +225 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/hatch.pyi +68 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.py +1786 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/image.pyi +215 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.py +269 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/inset.pyi +25 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.py +309 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/layout_engine.pyi +62 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.py +1371 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend.pyi +152 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.py +812 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/legend_handler.pyi +294 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.py +1720 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/lines.pyi +153 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.py +908 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/markers.pyi +51 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.py +140 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mathtext.pyi +33 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.py +913 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mlab.pyi +100 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmex10.afm +220 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmmi10.afm +326 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmr10.afm +343 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmsy10.afm +195 -0
- outputs/audit_venv/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm/cmtt10.afm +156 -0
- 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
|